32F417: How does USB CDC OUT (PC -> target) flow control work?
I have my board running well, and flow control works in the "IN" direction (board -> PC).
But there are strange issues. I am testing with a loopback program which has variable size packets and at a point which is very close to 1024 bytes I see a lot of errors.
I do have a 1024 byte buffer in that data flow but changing that to 2048 doesn't change anything, and I suspect the flow control isn't working.
As with everything-USB it is damn complicated but AFAICT the data path is:
1) USB (FS mode) does an interrupt on each RX packet, and it has 1-64 bytes of data.
2) An ISR copies this into a 64 byte buffer
3) The ISR calls this
/**
* @brief Data received over USB OUT endpoint are sent over CDC interface
* through this function.
*
* We get here when there is some OUT data (OUT = PC to target). That data is
* loaded into cdc_receive_temp_buffer[64], supplied to us here as "buf".
* This is the call flow: https://peter-ftp.co.uk/screenshots/202209065715674317.jpg
* Data is then copied into our circular buffer cdc_receive_buffer[1024].
* No more data can be sent by PC until USBD_CDC_ReceivePacket gets called.
* This function is called by an ISR so no need to disable interrupts.
*
* @param Buf: Buffer
* @param Len: Number of data received (in bytes)
*
*/
static int8_t CDC_Receive_FS(uint8_t* buf, uint32_t *len)
{
if (*len > 0)
{
// copy over the data from buf, length len, into our circular buffer
for (uint16_t i = 0; i < *len; i++)
{
cdc_receive_buffer[cdc_receive_buffer_rx_index++] = buf[i];
cdc_receive_buffer_rx_index %= CDC_RX_BUFFER_SIZE;
if (cdc_receive_buffer_rx_index == cdc_receive_buffer_get_index)
{
// Buffer is full - discard a byte from it. This is necessary to resolve the ambiguity of
// the two pointers being equal if FULL or EMPTY. We make sure they are never equal after an input.
// Increment the get index to ensure that only the last BUFFER-1 bytes can be accessed
cdc_receive_buffer_get_index++;
cdc_receive_buffer_get_index %= CDC_RX_BUFFER_SIZE;
}
}
}
// Enable reception of next packet from Host
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return (USBD_OK);
}
4) The target application extracts data from the circular buffer
The last line above, USBD_CDC_ReceivePacket, is supposed to enable the next 1-64 byte packet to arrive from the PC. This is executed regardless of whether that circular buffer overflowed, which is not ideal because there is no flow control, but if I don't call USBD_CDC_ReceivePacket I need to call it later when the target application has extracted some data, but this is a difficult problem because with no data arriving that interrupt won't be happening. So I would need to call it from some foreground code, but then I have reentrancy issues.
The obvious way is to skip the USBD_CDC_ReceivePacket if that buffer is full and call it from the code which is extracting data from that circular buffer, whenever >=64 bytes of room is in there.
The above code is standard; it is the ST port of some common USB code for the 32F4 family, supplied with Cube IDE. As usual there are loads of people posting all over the place trying to solve this.
However I have established that when there are errors, the above buffer overflow condition is not happening. So it is something else.
AIUI, when a PC application sends data to USB VCP, it opens a file called say COM3 and sends data to it, in packets of any length. You can send 100k if you want. USB then packages this into 1-64 byte packets and sends it down the wire. There is no 1024 or whatever packet limit involved.
For an idea of how much code there is:

