March 15, 2025

Black Pill boards - F411 USB -- USB endpoints, part 6 - experiments reading

I wrote a small C program to run on linux and talk to our F411 usb device. I open /dev/ttyACM0, then read and write.

First I just opened and closed the port. I see this on the serial console:

Rx setup (8) 2122030000000000
Setup interface request
-- testing -- 364
-- testing -- 365
Rx setup (8) 2122000000000000
Setup interface request
I did an open, a 2 second delay, then a close. I was surprised and interested to see that making a connection (using open()) and ending a connection (via close()) both inform our USB device about what has happened using setup packets on endpoint 0. This suggests that it should be possible to find out just what these do and for software on the F411 to know when there actually is a connection.

Note that I continually print the "testing" messages every second.

Next I do a 50 byte write and see:

Rx setup (8) 2122030000000000
VCP_DataRx 20000F40 50
Rx setup (8) 2122000000000000
No surprises in that, so let's try a 200 byte write:
VCP_DataRx 20000F40 64
VCP_DataRx 20000F40 64
VCP_DataRx 20000F40 64
VCP_DataRx 20000F40 8
No surprises there either. The data arrives in 64 byte "chunks" as determined by how much data can get moved in a single USB transaction.

This looks like a problem

Note: it is not a bug or a problem -- set below.

Going beyond this, I see buggy behavior. Sending 2000 bytes sends part of the data, then everything "locks up". Sending 1000 bytes works sometimes, but eventually it also locks up when I repeat the test over and over. It always fails when I send the data immediately after the open.

The lockup described

On the linux side, I do the open, the write, and the close. The close "hangs", but after a timeout (30 seconds) it finishes. On the F411 side, I see the setup packet related to the open, but I never see a VCP_DataRx message. After the timeout, I see the setup packet related to the close.

I never see a problem when I do a single write and limit the size of my writes to 50 bytes (less than the 64 byte size of a USB transaction.

However, when I attempt to do 10 writes back to back with 50 bytes each, it works the first two times, then on the third group of 10, it locks up after four 50 byte packets.

Could this be serial timing interference?

I comment out the message in VCP_DataRx(). I get 4 groups of 10 just fine, but the 5th hangs. Now I turn off all debug (so I won't see the notices of the setup packets on open and close.) And again, I get 4 groups of 10 fine, but the 5th hangs.

So, it doesn't look like serial timing interference. I do not get actual write errors on the linux side, but the close hangs for some reason.

I'll also note that once a hang happens, the F411 is permanently hung. Even though the unix side times out and allows close to finish, the situation in the F411 is "stuck" and all efforts to write data now fail.

I test using 50 byte packets with 0.1 second delays between them and after 56 packets, get a hang. I change the delay to 0.2 second and I still get a hang after 56 packets!

As a last test, I changed the delay to 1 second and reactivated the print for VCP_DataRx. After 40 packets of 50 bytes, the VCP_DataRx messages stopped and 16 packets later, the linux side writes hung. So 56 again.

Aha! The problem solved

The answer is simple -- nobody is listening! 40*50 is 1000, so once 1000 bytes has piled up and nobody has read it, things jam up. I set up a listener, using usb_hookup() and things go fine. I send:
200 writes of 50 bytes (10000 total) with 1 millisecond delays.
200 writes of 50 bytes (10000 total) with no delays.
200 writes of 100 bytes (10000 total) with no delays.
The last go across as:
VCP_DataRx 20000F44 64
VCP_DataRx 20000F44 36
Finally I send 500 writes of 200 bytes and it works fine -- and is very fast.

Frames and maximum transfer rates

Note that FS usb is 12 Mbit/second. However, we know that we have 1000 transactions with 64 bytes moved per second. That is 64K bytes/second (call a byte 10 bits and that is 640K bits per second). Well, maybe the overhead with USB really is that high. Or there are things I don't understand yet.

I'm actually reading that FS usb can send 64 bytes every 512 us, which would be close to double my rate, thus 128K bytes/second. I see that bulk endpoints can have 1024 byte payloads which would certainly change things.

Not only that, but there can be up to 19 bulk transactions of 64 bytes in a frame. Frames do come at 1000 Hz. With 1000 * 19 * 64 bytes per second, we get 1.2 Mbytes/second. This does seem to be the maximum theoretical transfer speed for FS.

Note also that in library/usbd_cdc_core.c our endpoints get announced during enumeration as "Bulk".

Conclusion

I was surprised how this worked out. Understanding how both sides handle a "problem" at the other end is worth understanding in detail.

On my list of things to do is to write code on the linux side to use "usblib" or whatever it is currently called. I expect this to be educational and hope that it will allow better monitoring of what is going on with the USB connection.


Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org