2014-03-28 05:41 AM
Dear list,
I use the USB-VCP example, from STM32_USB-Host-Device_Lib_V2.1.0, for my application, but am running into some trouble when trying to get the proper flowcontrol for my application. I removed the usart code and separated input and output in the example. There now only is a background process that uses VCP to communicate with a PC. My background sends data to the USB IN endpoint using VCP_DataTx, and reads data from the USB OUT endpoint via a simple fifo filled by VCP_DataRx. See the code below for that. But VCP_DataRx stalls if the fifo is full, and the background process does not make process anymore, effectively creating a deadlock. I assume that VCP_DataRx is called from an interrupt routine. I am trying to figure a way to solve this problem, but still using the VCP code as unchanged as possible. Is there a way to trigger flow control (''XOFF'') in the USB OUT endpoint in another way than stalling VCP_DataRx? If so, then I could call that code from VCP_DataRx when there still is enough room in the fifo. (enough meaning, enough to make the current (and maybe the next) call to VCP_DataRx finish.. This of course depends upon the maximum size of the parameter Len) Then the background could to its work until the fifo is allmost empty and then start the USB OUT endpoint again (''XON''). Is this something that makes sense? Some trickery in one of the USB interrupt routines? Or is there another approach possible? Thanks in advance, Sietse PS. I could start using a RTOS, and than make both the usb interrupts and the background process into RTOS processes, but I would like to avoid that complexity.static uint16_t VCP_DataRx (uint8_t* Buf, uint32_t Len)
{
int i = Len; int j;
while (i > fifo_free(&recfifo)); /* Wait busy, and deadlock! */
j = 0;
while (j<i)
fifo_put(&recfifo,Buf[j]); j++;
return USBD_OK;
}
For completeness I also include the background function that I use.
// Used in background. non-blocking
int rtIOStreamRecv(
int streamID,
void * dst,
size_t size,
size_t * sizeRecvd) {
int i; uint8_t c; uint32_t j;
if (size == 0) {
return 0;
}
else
if ((i=fifo_avail(&recfifo))) {
if (i > size) i = size;
for ( j=0; j<i; j++) {
if (fifo_get(&recfifo,&c)) {
((char *)dst)[j] = c;
}
}
return i;
} else
return 0;
}
#vcp-flow-control
2014-03-29 01:04 PM
> Is there a way to trigger flow control (''XOFF'') in the USB OUT endpoint in another way than stalling VCP_DataRx?
1) NAK flow control NAK is returned when the OUT endpoint is not ready (not armed) Here is corresponding part of the CDC device source code\STM32_USB-Host-Device_Lib_V2.1.0\Libraries\STM32_USB_Device_Library\Class\cdc\src\usbd_cdc_core.c
static uint8_t usbd_cdc_DataOut (void *pdev, uint8_t epnum)
{
uint16_t USB_Rx_Cnt;
/* Get the received data buffer and update the counter */
USB_Rx_Cnt = ((USB_OTG_CORE_HANDLE*)pdev)->dev.out_ep[epnum].xfer_count;
/* USB data will be immediately processed, this allow next USB traffic being
NAKed till the end of the application Xfer */
APP_FOPS.pIf_DataRx(USB_Rx_Buffer, USB_Rx_Cnt); // <----- (a)
/* Prepare Out endpoint to receive next packet */
DCD_EP_PrepareRx(pdev, // <----- (b)
CDC_OUT_EP,
(uint8_t*)(USB_Rx_Buffer),
CDC_DATA_OUT_PACKET_SIZE);
return USBD_OK;
}
(a) this functiono pointer calls your VCP_DataRx()
(b) DCD_EP_PrepareRx() arms the OUT endpoint, again.
That is, until your code calls DCD_EP_PrepareRx(), the OUT endpoint NAKs
Modify above code, as follows,
if ( APP_FOPS.pIf_DataRx(USB_Rx_Buffer, USB_Rx_Cnt) == USBD_OK )
{
/* Prepare Out endpoint to receive next packet */
DCD_EP_PrepareRx(pdev,
CDC_OUT_EP,
(uint8_t*)(USB_Rx_Buffer),
CDC_DATA_OUT_PACKET_SIZE);
}
return USBD_OK;
}
Your VCP_DataRx() returns USBD_BUSY when your FIFO is full
When the FIFO gets enough room,
- unload data from recfifo,Buf[]
- call DCD_EP_PrepareRx() with above parameter, for the next packet
2) Hardware (DTR) flow control
You may send DTR signaling from the device, over the interrupt IN endpoint using line_state packet. This signaling is available for FIFO flow control.
Unfortunately, the CDC device code in STM32_USB-Host-Device_Lib_V2.1.0 doesn't support this feature yet. And as the coder of this source mixes up the interrupt IN endpoint with class requests (they have common CDC_CMD_PACKET_SZE), the implementation is troublesome (including this bug fix).
If you would like this feature, I'll show you the implementation (and bug fix), but today my free time has been expired ;)
Tsuneo
2014-03-31 07:27 AM
2014-09-26 06:36 AM
Hi. I've been using NAK flow control for a while, but it seems there are some problems.
If the microcontroller is unable to process the data for more than a fraction of a second, data is lost. It seems that under Windows especially, USB stops trying to deliver a packet if it NAKs more than 5 packets (at a guess).Is this expected? is there a way around it?A proper flow control example via line_state would be amazing.2014-09-27 04:19 AM
> It seems that under Windows especially, USB stops trying to deliver a packet if it NAKs more than 5 packets (at a guess).
Sound like your host PC application assigns timeout to the transfer. When timeout expires, the transfer request is canceled by the host stack. Repeated NAK-retries by the USB hardwares (on both sides) stop by this timeout-cancel. Apply reasonal value to your timeout, so that the device may keep enough time to process the data.> A proper flow control example via line_state would be amazing. NAK flow control is as reliable as ''hardware'' flow control. Anyway, I've already shown you ''2) Hardware (DTR) flow control'' in above post. Tsuneo2014-09-29 01:36 AM
> Apply reasonal value to your timeout, so that the device may keep enough time to process the data.
You mean a timeout on the host? I'm using the standard usbser.sys driver, so where would I do that? And how would I also fix it on Mac, Linux, Chromebook and Android?I thought that the whole point of VCP was that it didn't need special drivers/configuration for each platform.Also, given the user may sent something that may take an arbitrarily long time to execute, I guess I can't just make the timeout infinite?> Anyway, I've already shown you ''2) Hardware (DTR) flow control'' in above post.To be fair, you asked ''If you would like this feature, I'll show you the implementation'' so I was saying I would quite like to see it.
In your post you say that the existing software has a bug do to with the packet size and that implementation would be troublesome, so I don't think it's unreasonable to ask for some hints.2017-01-26 11:30 PM
Hi
Tsuneo,
Please can you attach
Hardware (DTR) flow control code .
2017-01-31 05:44 AM
Two points were raised in this discussion -- what can be done on the micro side for flow control, and how the host reacts when the micro 'NAKs'. Here is example code that provides a fifo buffer that properly 'NAKs' and which is based upon the demo VCP software generated by CubeMX. I can't help you on the host side. In the Linux/Unix world it is up to the application to handle timeout events from file system calls. I don't do windows, so I have no idea how they might be handled there. If you'd like a complete example of VCP look here:
https://github.com/geoffreymbrown/stm32cubef3-examples/tree/master/vcom
The basic principle is simple. Packet reception is only enabled if there is sufficient space in the fifo to hold a maximum (64byte in this case) packet. When a packet is received (vcom_Receive_FS), future packet reception is enabled (USBD_CDC_ReceivePacket) only if there is space to hold the next packet. In the event that reception is stalled, it is re-enabled when the application removes enough data from the input fifo (vcom_read) so that a maximum packet could be received. Since this was taken from a working example, there are some LED on/off events that are extraneous. On the transmit code, (vcom_write), handling of 64byte packets is illustrated.
#define QUEUE_SIZE 256
extern USBD_HandleTypeDef *hUsbDevice_0;
static volatile int rxKick = 0;
static struct Queue rxQ;
struct Queue {
uint32_t pRD, pWR;
uint8_t q[QUEUE_SIZE];
};
#define QueueAvail(q) ((QUEUE_SIZE + (q)->pWR - (q)->pRD) % QUEUE_SIZE)
#define QueueSpace(q) (QUEUE_SIZE - 1 - QueueAvail(q))
static int Enqueue(struct Queue *q, const uint8_t *data, uint32_t len){
int i;
len = MIN(len,QueueSpace(q));
for (i = 0; i < len; i++) {
q->q[q->pWR] = data[i];
q->pWR = (q->pWR + 1) % QUEUE_SIZE;
}
return len;
}
static int Dequeue(struct Queue *q, uint8_t *data, uint32_t len){
int i;
len = MIN(len,QueueAvail(q));
for (i = 0; i < len; i++){
data[i] = q->q[q->pRD];
q->pRD = (q->pRD + 1) % QUEUE_SIZE;
}
return len;
}
int8_t vcom_Receive_FS (uint8_t* Buf, uint32_t *Len){
int count = *Len;
HAL_GPIO_TogglePin(GPIOE,LD7_Pin);
if (*Len && (Enqueue(&rxQ, Buf, *Len) != *Len)) {
HAL_GPIO_WritePin(GPIOE,LD3_Pin,0); //overflow
}
// release receive buffer ?
if (USB_FS_MAX_PACKET_SIZE >= QueueSpace(&rxQ)) {
HAL_GPIO_TogglePin(GPIOE,LD5_Pin);
rxKick = 1;
} else {
USBD_CDC_ReceivePacket(hUsbDevice_0);
}
return (USBD_OK);
}
uint32_t vcom_read_cnt(void) {
return QueueAvail(&rxQ);
}
uint32_t vcom_read(uint8_t *pBuf, uint32_t buf_len) {
uint32_t cnt = Dequeue(&rxQ, pBuf, buf_len);
// release receive buffer
if (rxKick && (USB_FS_MAX_PACKET_SIZE <= QueueSpace(&rxQ))) {
HAL_GPIO_TogglePin(GPIOE,LD5_Pin);
rxKick = 0;
USBD_CDC_ReceivePacket(hUsbDevice_0);
}
return cnt;
}
uint32_t vcom_write(uint8_t *buf, uint32_t len) {
if (CDC_Transmit_FS(buf, len) == USBD_OK) {
if (!(len & 63)) // transmitting multiple of packet size
CDC_Transmit_FS(buf,0); // send zero length packet
return len;
}
else
return 0;
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
2017-02-16 08:20 AM
Some time ago I changed to using CubeMX, and the USB-middleware software therein.
That works perfectly.