December 1, 2023

Let's learn USB! -- "daedalean" stm32f103_usb

I found this project on Github, cloned it, built it, began to work with it, and really liked it. So I decided to do something I have rarely done before, I forked my own copy of it.

He also has a similar project for the F411 that I will be looking at soon (I hope).

How to make a fork

It is that easy. Now I will clone a working copy. I do this using the git method and ssh since I have an ssh key setup. This is quick and easy. I am in business.

Why do I like this?

It is nice clean C code and gets the job done in only 700 lines (for the USB driver). He takes an interesting approach of defining hardware base addresses in the linker script and then referencing them in the code.

It uses just a simple Makefile to build, this guy is doing things the right way.

Let's do stuff with it

The compiler was complaining about an anonymous enum in main.c. I made some simple changes and the whole thing compiles by just typing make.

I use an ST-Link device connected to my Maple board, so I add an entry to the Makefile so that "make flash" now does the proper thing using openocd to download code.

No enumeration is/was happening. This is because of the disconnect circuit on the Maple. I add some more code to main.c to set up PC12 for this and now the board enumerates with vid 0483 and pid 5722. Linux does not have this in its list of known gadgets, so it assigns no driver. The trick is this command:

su
echo 0483 5722 >/sys/bus/usb-serial/drivers/generic/new_id
Now it shows up as /dev/ttyUSB1.
Linux gripes a bit in the logs (but who care?).
Nov 30 22:54:22 trona kernel: usbserial_generic 2-1.2:1.0: The "generic" usb-serial driver is only for testing and one-off prototypes.
Nov 30 22:54:22 trona kernel: usbserial_generic 2-1.2:1.0: Tell linux-usb@vger.kernel.org to add your device to a proper driver.
Nov 30 22:54:22 trona kernel: usbserial_generic 2-1.2:1.0: generic converter detected
Nov 30 22:54:22 trona kernel: usb 2-1.2: generic converter now attached to ttyUSB1

The serial console works and is chattering away. At 921600 baud of all things, sending this message about once a second, incrementing the count.

USB 6627: DEFAULT
...
USB 1118: CONFIGURED
Here "DEFAULT" (or "CONFIGURED") is the usb status string.

When I connect to ttyUSB1 using picocom, I see:

bingo 388
bingo 389
...

Best of all the LED on the maple board is blinking with my hack to set up A5 as the proper maple LED.
I can go to bed happy now, having accomplished something.

Another day of hacking

I like the USB code, but there is a bunch of crazy business with a fancy printf and a circular buffer scheme will callbacks and complex nonsense. All that should be hidden inside of some kind of nice API. Maybe something like "printf()"? I got fed up with it, shoved it aside and pulled in my own printf (which is actually called printf). This caused enumeration to break. My serial putc was not interrupt driven and did not have a queue. But I got busy and fixed that too.

Indeed, once I gave the serial IO a queue and made it interrupt drive, the USB enumeration started working again!

Enumeration output

After much strange business with the serial port (that I still need to investigate), I did get the following capture of the enumeration traffic! We did this be using queued interrupt driven serial IO during enumeration and switching to plain old reliable polled serial IO once enumeration finished. This is simply a workaround for whatever buggy business afflicts our serial interrupts.
0 Enum Reset
1 Enum Reset
2 Enum Rx S 8210 EA60 8 8006000100004000
3 Enum Tx   8000 62A0 18 120100020000004083042257000200000001
4 Enum Rx   8010 A220 0
5 Enum Reset
6 Enum Rx S 8210 EA60 8 0005670000000000
7 Enum Tx   8000 62A0 0
8 Enum Rx S 8210 EA60 8 8006000100001200
9 Enum Tx   8000 62A0 18 120100020000004083042257000200000001
10 Enum Rx   8010 A220 0
11 Enum Rx S 8010 EA60 8 8006000600000A00
12 Enum Rx S 8210 EA60 8 8006000600000A00
13 Enum Rx S 8010 EA60 8 8006000600000A00
14 Enum Rx S 8010 EA60 8 8006000200000900
15 Enum Tx   8000 62A0 9 090220000101008032
16 Enum Rx   8010 A220 0
17 Enum Rx S 8010 EA60 8 8006000200002000
18 Enum Tx   8200 62A0 32 09022000010100803209040000020A0000000705010240000007058102400000
19 Enum Rx   8010 A220 0
20 Enum Rx S 8010 EA60 8 0009010000000000
21 Enum Tx   8000 62A0 0
What I find interesting is that no strings are sent. None are asked for either! This is because the device descriptor has 3 places for string indices, and they were all set to zero (for vendor, product, and serial). We might change this to test our skill and ability and understanding.

Use of the timer in the "daed" code

It sets up TIM3_IRQ_Handler. Why timer3? We have 2,3, and 4 that all look much the same. Timer 1 looks special with 4 different vectors set aside for it.

It needs to be enabled (in the RCC APB! enable register), right alongside of USB.
The NVIC needs to be told about the interrupt.
Then it gets set up for 1 Hz interrupts as follows:

TIM3.DIER |= TIM_DIER_UIE;
TIM3.PSC = 7200 - 1;  // 72MHz/7200   = 10KHz
TIM3.ARR = 10000 - 1; // 10KHz/10000  = 1Hz
TIM3.CR1 |= TIM_CR1_CEN;
The DIER is the "DMA/Interrupt" enable register, and we set the UIE (update interrupt enable) bit.
The PSC is the prescaler, and ARR is "auto reload" register.
Finally setting CEN in the CR1 register enables the counting.

The handler routine checks the UIF bit in the status register, perhaps to reject spurious interrupts, then clears this bit if it was set.

It is as simple as that, and you get 1 Hz interrupts.


Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org