Hi,
I use a modified version of Keil's USB CDC-ACM function class implementation. It works really well but I am having a problem in the way I am using it. I use the virtual COM-port to connect as a modem to the host PC. My target connects through PPP to the USB CDC-ACM input/output. This allows me to create multiple sockets for multi-channel TCP/IP communication. This creates the problem! With increasing communication throughput my application showes signs of instability. I am looking for a way how I could limit the data throughput at the USB device level. Could someone provide me with an feasable example of how to achieve this? I appreciate your help - thank you!
-Frank
Keil CDC example misses these points on bulk endpoints (EPs) handling for CDC.
a) bulk OUT EP The transfer speed is automatically tuned using NAK flow control. While the EP is occupied by current received data, the transfer from host is delayed by NAKing. In this way, lossless transfer is achieved.
When the data is left unread on the EP buffer, no more EP interrupt is generated. Then, we have to poll the endpoint status periodically out side of the EP ISR, too. SOF interrupt is often used for this purpose.
A typical implementation is, In USB_SOF_Event() (usb_user.c) [b]AND[/b] USB_EndPoint2() ISR - USB_EVT_OUT selector, call this subroutine.
- Select the bulk OUT EP (USB_CTRL) - Check PKT_RDY bit of RX_PLENGTH - - if not ready, return - Compare the packet length and the room on the RX buffer of your firmware - - if no room, return - Read out data from the EP to the RX buffer - Clear the EP (CMD_SEL_EP and CMD_CLR_BUF)
This subroutine is easily made modifying USB_ReadEP() (usbhw.c) Just add 2-3 lines.
b) bulk IN EP Here, NAK flow control is applied, too. Just when we have data to send, the data is put to the bulk IN EP. When no data is written to the IN EP, no more EP interrupt is generated. Then, we have to poll the number of data on the TX buffer periodically out side of the EP ISR, too. SOF interrupt is often used for this purpose.
When the last transaction is just 64 bytes, host controller waits for next short packets forever. Until receiving a shot packet, host controller doesn't pass the data to the input buffer on the PC device driver. In this case, Zero-Length Packet (ZLP) should be put to IN EP, to speed up the response of host. This ZLP is also added in SOF ISR.
A typical implementation is, In USB_EndPoint2() - always sends full (64 bytes) packet
USB_EndPoint2() ISR - USB_EVT_IN selector - If the number of the data on the TX buffer is less than 64, return - Check the FE bit of "Select Endpoint" command, if no empty EP buffer, return - - WrCmd( CMD_SEL_EP(EPAdr(EPNum)) ); if ( CMD_DATA & EP_SEL_F != 0 ) return; - Send 64 bytes from the buffer to the IN EP using USB_WriteEP() - Raise a flag - flag_send_ZLP
In USB_SOF_Event(), call this subroutine
- Check the FE bit of "Select Endpoint" command, if no empty EP buffer, return - send_cnt = number of data on the TX buffer - if (send_cnt == 0) && (! flag_send_ZLP), return - if send_cnt >= 64 bytes, send_cnt = 63 bytes (short packet) - Send specified number of data, USB_WriteEP( CDC_DEP_IN, TX_buffer, send_cnt ); - drop flag_send_ZLP
For TX and RX buffer, cyclic buffers fit well for this implementation.
"Could someone provide me with an feasable example of how to achieve this?
Implement above modification by yourself for your exercise :-) Or wait until KEIL brush up the implementation.
Tsuneo
Hi Tsuneo,
thank you for your detailed reply!
Revised above USB_SOF_Event() subroutine for bulk IN EP This algorithm gives better transfer speed.
b) bulk IN EP
USB_EndPoint2() ISR - USB_EVT_IN selector - If the number of the data on the TX buffer is less than 64, return - Check the FE bit of "Select Endpoint" command, if no empty EP buffer, return - - WrCmd( CMD_SEL_EP(EPAdr(CDC_DEP_IN)) ); if ( CMD_DATA & EP_SEL_F != 0 ) return; - Send 64 bytes from the buffer to the IN EP - - USB_WriteEP( CDC_DEP_IN, TX_buffer, 64 ); - Raise a flag - flag_send_ZLP
- Check the FE bit of "Select Endpoint" command, if no empty EP buffer, return - send_cnt = number of data on the TX buffer - if (send_cnt == 0) && (! flag_send_ZLP), return - if send_cnt >= 64 bytes, - - send_cnt = 64 bytes, and raise flag_send_ZLP - else drop flag_send_ZLP - Send specified number of data - - USB_WriteEP( CDC_DEP_IN, TX_buffer, send_cnt );
Good morning Tsuneo,
you suggested: - Select the bulk OUT EP (USB_CTRL) - Check PKT_RDY bit of RX_PLENGTH - if not ready, return
Here is a part of my USB_ReadEP:
USB_CTRL = ((EPNum & 0x0F) << 2) | CTRL_RD_EN; cnt = RX_PLENGTH; if ((cnt & PKT_RDY) == 0) { return (0); }
I return if the packet is not ready. The function is then called again in USB_SOF_Event () until a packet is ready and can be processed.
This solution doesn't work! It seems that after selecting the endpoint I have to finish the operation.
Do you know what the problem is?
Thank you! -Frank
I tried to implement your suggested approach but noticed that there is a problem with the hardware.
I don't know if there is another way to implement this?
I would appreciate if you could answer to my described problem.
Thank you, -Frank
Did you enable SOF (Start Of Frame) Event?
usbcfg.h #define USB_SOF_EVENT 1
I did enable SOF and verified in a debug session that the interrupt is working.
I think your description is the correct solution but the USB controller doesn't support your described behavior. This is at least what I was able to verify.
I can only spend limited time with this problem but I need to find a solution for this problem (our overall communication depends on this implementation).
Thank you! - Frank
"I return if the packet is not ready. The function is then called again in USB_SOF_Event () until a packet is ready and can be processed."
I see, I remember the details of this engine. Once RD_EN bit is enabled on the USB Control register (USBCtrl) to read out the Receive Packet Length register (USBRxPLen), we should read out the endpoint buffer, too. Then, we can't get the packet length without reading out the buffer. Inconvenient feature.
Then, instead of reading out the exact packet length, we have to assume it full length, 64 bytes. "Select Endpoint" command shows the buffer is empty or not.
a) bulk OUT EP In USB_SOF_Event() (usb_user.c) [b]AND[/b] USB_EndPoint2() ISR - USB_EVT_OUT selector, call this subroutine.
- Check the FE bit of "Select Endpoint" command, if the EP buffer is empty, return - - WrCmd( CMD_SEL_EP(EPAdr(CDC_DEP_IN)) ); if ( CMD_DATA & EP_SEL_F == 0 ) return; - If the room on the RX buffer is less than 64 bytes, return - read out to the RX buffer using USB_ReadEP() - - USB_ReadEP( CDC_DEP_IN, RX_buffer )
Oops, in above post,
CDC_DEP_IN ---> CDC_DEP_OUT
"but never succeeded in reading FE bit status"
I mistakenly use WrCmd() in above posts. It is replaced to RdCmdDat() as follows.
a) bulk OUT EP - Check the FE bit of "Select Endpoint" command, if the EP buffer is empty, return - - WrCmd( CMD_SEL_EP(EPAdr(CDC_DEP_OUT)) ); if ( CMD_DATA & EP_SEL_F == 0 ) return; // - wrong
if ( RdCmdDat( CMD_SEL_EP(EPAdr(CDC_DEP_OUT)) ) & EP_SEL_F == 0 ) return;
b) bulk IN EP - Check the FE bit of "Select Endpoint" command, if no empty EP buffer, return - - WrCmd( CMD_SEL_EP(EPAdr(EPNum)) ); if ( CMD_DATA & EP_SEL_F != 0 ) return; // - wrong
if ( RdCmdDat( CMD_SEL_EP(EPAdr(CDC_DEP_IN)) ) & EP_SEL_F != 0 ) return;
thank you for your example.
- Frank
you look like very skilled person in discussed area. I have found a different post (probably older and thus closed already - I can't reply there):
http://www.keil.com/forum/docs/thread12298.asp
It's regarding the composite virtual COM ports under M$ (related to usbser.sys). I'm trying to start uderstanding the CDC/ACM and would like to bring up more VCOMs on one device (LPC2368) with one (default, non-vendor specific) driver. You wrote you was testing it via IAD and/or multiple CDCs.
Do you have any results from this? Is there any resource/example you could point me to or provide to speed up my understanding in this area?
Thanks.
Regards Pavel