2025-12-19 9:52 AM
When using a USB CDC device, especially at high data rates, there is a possibility for packet loss. This post illustrates the issue with what I consider convincing evidence. This isn't a STM32 bug, but it's something you may encounter when interfacing with an STM32 device so I felt it is relevant here.
As a bonus, this also shows the achievable speeds with USB FS CDC to be ~9.2 Mbps under ideal circumstances (fast, good code on both sides of the connection).
Here's the setup to illustrate the issue:
On the STM32, I'm initializing a USB FS CDC device (sometimes also called a VCP) and sending out packets as fast as possible. The bus speed of USB FS is 12 MHz.
On the PC side, I'm opening the serial port in Python and reading data. When data comes in, I ensure no data has been missed by verifying the uint64_t values are increasing. If data is missed, I print that out. Every second, I give a summary of what happened in the last second, including the effective bitrate and the maximum number of bytes read from the port.
If I poll the port continuously and let the cpu spin wild, this functions as intended and I get about 9.2 Mbps without any loss of data. Side note: this is about the max you will ever get on USB FS. If you use a hub, it will be slower. It would be interesting to know the theoretical maximum speed after all required USB overhead.
...
Received 1146624 bytes over the last 1 second (9.17 Mbps) (n=151, minread=4096, maxread=16384)
Received 1150976 bytes over the last 1 second (9.21 Mbps) (n=164, minread=4096, maxread=12288)
Received 1138688 bytes over the last 1 second (9.11 Mbps) (n=155, minread=4096, maxread=12288)
Received 1155072 bytes over the last 1 second (9.24 Mbps) (n=149, minread=4096, maxread=12288)
Received 1146880 bytes over the last 1 second (9.18 Mbps) (n=153, minread=4096, maxread=16384)
...
You can also see that the maxread is 16 kB which I suspect is the internal (Windows) buffer size of the serial port. If I put a larger delay, the effective bitrate slows down but the maxread doesn't increase past 16 kB, as expected.
To get the issue to occur, I put a small delay after polling. This lets the internal buffer fill up. When this happens, I see a packet loss of a multiple of 64 bytes. Here's an example:
...
Received 1134464 bytes over the last 1 second (9.08 Mbps) (n=154, minread=3968, maxread=16384)
dropped 128 bytes of data (values 0x2BCE00-0x2BCE0F)
Received 1146752 bytes over the last 1 second (9.17 Mbps) (n=146, minread=4096, maxread=16384)
...
dropped 64 bytes of data (values 0x358600-0x358607)
Received 1134528 bytes over the last 1 second (9.08 Mbps) (n=146, minread=4096, maxread=16384)
The data loss as a percentage is around 0.01% which makes it hard to find if you're not expecting it.
I'm also using WireShark to capture the USB packets as they're received. Looking at these, I can see the packet with 0x2BCE00 was received by the PC but never made it to the Python serial port interface. In this case, the missing packet had 128 bytes of payload and they were all missed.
In other cases, only 64 bytes were lost. Always a multiple of 64 bytes.
So where is the bug?
Notes:
Nothing changes the internal 16 kB buffer size. Trying set_buffer_size has no impact. Adjusting the sliders in the Control Panel serial port advanced controls has no impact. It is always 16384 bytes.
I'm using an NUCLEO-F429ZI to demonstrate this, but the issue is not specific to this board or this family. It happens on other chips and other chip families as well.
Workarounds:
I've attached my main.c file with the relevant user code.
I've also attached my python code (rename extension to py).