Electronics > Microcontrollers

32F417: How does USB CDC OUT (PC -> target) flow control work?

(1/4) > >>

peter-h:
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


--- Code: ---
/**
  * @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".
  * 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);
}

--- End code ---

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 because USBD_CDC_ReceivePacket calls a whole load of stuff which might get called by USB interrupts also. 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.

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. 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 many layers there are in the data flow, this is the USB OUT (PC to target) interrupt:

peter-h:
In case someone finds this in years to come, I have got this running, I think, and posted it here
https://community.st.com/s/question/0D53W00001qnO9ESAU/32f417-how-does-usb-cdc-out-pc-target-flow-control-work?t=1664975189841

pcprogrammer:
USB is indeed complicated. There are many aspects in the whole control, but most of it is done by the USB controller. The number of bytes transferred from the host (PC) to the device (board) depends on the number given in the descriptor for the device. When set to 64 the host will send blocks of 64 bytes and the device has to handle these blocks.

I have played with this on the STM32F103 and STM32F303 devices on a bare metal basis. All my tries with the code generated with the ST tools failed for what ever reason. This is what I made for the F103 mimicking a CH340 on the USB side. https://github.com/pecostm32/STM32F103C8_USB_CH340

I found this site very helpful: https://www.beyondlogic.org/usbnutshell/usb1.shtml

peter-h:
I think the USB code generated by Cube MX does not implement any of the "practical" stuff like flow control. It just does the data moving.

So for example, with MSC, there is no way to control data flow PC -> board when writing a serial FLASH filesystem (sector write ~15ms). The only way I found which works was to just delay the ACK to the PC by the sector write. USB does not seem to care and somebody said that FLASH sticks do the same (I posted a thread or two already on here on the MSC stuff).

And CDC didn't do it either.

I didn't realise the 64 bytes was configurable. I thought it was inherent in USB FS and CDC, thus limiting CDC to 64kbytes/sec (USB packet rate is 1kHz).

bson:
Don't you just do an IN transfer indicating RX data available on the CDC interrupt endpoint?  The host polls the interrupt endpoint and when it sees the RX flag the driver will proceed to do a regular IN data transfer.  But I'm not that familiar with CDC, having never implemented it or even looked at an implementation.

Navigation

[0] Message Index

[#] Next page

There was an error while thanking
Thanking...
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod