cancel
Showing results for 
Search instead for 
Did you mean: 

Questions about the STM32 usbd_cdc_core usb OTG driver

chmorgan
Associate II
Posted on April 29, 2014 at 19:11

Hello.

Using stm32f107 here.

I've been using the v2.1.0 stm32 usb host device library to build a cdc device. I used the vcp example and modified it, but I'm seeing cases where a 64 packet is being lost.

I think I may have determined the cause of the issue. I modified usbd_cdc_core.c to add some accessors to let me write into the APP_Rx_Buffer with memcpy. I'm performing these writes into the buffer from task level (running embos here), by disabling interrupts and then calling the routines.

I suspect the issue may be due to the way DCD_EP_Tx() works. It looks like Handle_USBAsynchXfer() is assuming that the APP_Rx_ptr_out can be advanced, because the routine is running at isr time and because DCD_EP_Tx() is processing the payload. But it looks like DCD_EP_Tx() is simply setting the transaction up. I suspect my issue is that at task time I'm out running the transfer.

The transfer is setup by DCD_EP_Tx(), APP_Rx_ptr_out is advanced and then task execution resumes, but the data hasn't been sent yet. At this time the task writes some number of bytes into the APP_Rx_Buffer, overwriting the data that is still pending to be sent.

Am I misunderstanding how this is working? How can I ensure that I can write into the APP_Rx_buffer outside of a usb isr without overwriting data pending to be sent?

#stm32-usb-otg
8 REPLIES 8
chen
Associate II
Posted on April 29, 2014 at 19:19

Hi

''Am I misunderstanding how this is working? How can I ensure that I can write into the APP_Rx_buffer outside of a usb isr without overwriting data pending to be sent?''

Yes, you have pretty much figured it out.

There are 2 variables associated with APP_Rx_buffer

The buffer pointer and a buffer count.

Your access code for writing to APP_Rx_buffer should check/use these before modifying

chmorgan
Associate II
Posted on April 29, 2014 at 20:03

I think I'm doing what you are recommending, here is the code I'm using:

size_t usbd_cdc_AppWrite(const void *buf, size_t length)
{
uint8_t *pInput = (uint8_t*)buf;
size_t bytesToWrite = MIN(length, usbd_cdc_AppSpaceAvailable());
size_t bytesWritten = 0;
size_t lengthToWrite;
while(bytesToWrite)
{
// if in <

out

then we can write the data in one chunk, the output
// location doesn't span the end of the buffer
if(APP_Rx_ptr_in < APP_Rx_ptr_out)
{
lengthToWrite

=

bytesToWrite

;
memcpy(&APP_Rx_Buffer[APP_Rx_ptr_in], pInput, lengthToWrite);
} else // writing to the buffer can potentially wrap, depending on how much data
// is being written and how close the in pointer is to the end of the buffer
{
// how much space until the end of the buffer?
size_t

spaceUntilBufferEnd

=

APP_RX_DATA_SIZE

- APP_Rx_ptr_in;
lengthToWrite

=

MIN

(spaceUntilBufferEnd, bytesToWrite);
memcpy(&APP_Rx_Buffer[APP_Rx_ptr_in], pInput, lengthToWrite);
}
pInput += lengthToWrite;
APP_Rx_ptr_in += lengthToWrite;
bytesToWrite

-

=

lengthToWrite

;
bytesWritten += lengthToWrite;
// wrap the 'in' pointer
if(APP_Rx_ptr_in == APP_RX_DATA_SIZE)
{
APP_Rx_ptr_in

=

0

;
}
}
return bytesWritten;
}
size_t usbd_cdc_AppSpaceAvailable()
{
size_t

spaceAvailable

=

APP_RX_DATA_SIZE

- usbd_cdc_AppSpaceUsed();
// NOTE: we subtract 1 to avoid writing data and having the in/out pointers equal as
// that would indicate that the buffer was empty
return spaceAvailable - 1;
}
size_t usbd_cdc_AppSpaceUsed()
{
size_t spaceUsed;
// in == out indicates that the buffer is empty
if(APP_Rx_ptr_out == APP_Rx_ptr_in)
{
spaceUsed

=

0

;
} else
{
if(APP_Rx_ptr_out > APP_Rx_ptr_in)
{
spaceUsed = APP_RX_DATA_SIZE - (APP_Rx_ptr_out - APP_Rx_ptr_in);
} else
{
spaceUsed = APP_Rx_ptr_in - APP_Rx_ptr_out;
}
}
return spaceUsed;
}

But I suspect the issue is a bit more subtle. If DCD_EP_Tx() isn't causing the data to be sent immediately the sequence looks like: - usb isr (sof or datain) - DCD_EP_Tx() - APP_Rx_ptr_out += x - isr exits - task resumes running - usbd_cdc_AppWrite() is called and can write over the data prepared, but not yet sent, because APP_Rx_ptr_out has been advanced so that part of the circular buffer appears empty.
chmorgan
Associate II
Posted on April 29, 2014 at 22:40

Alright, I made some changes to rule this out.

I introduced a new variable, uint32_t APP_Rx_ptr_out_sent, that I use for the new functions I added in the previous post. This variable is set to be APP_Rx_ptr_out in the DataIn() handler before the next transaction is prepared. This should certainly be after the data was sent.

I also corrected my calculation for available space to only decrement if the value is > 0 to avoid the potential for a large negative value.

I also disabled a workaround I spotted here for an interrupt bit that wasn't being reset and causing an isr storm.

Still seeing a dropout after several largish transfers of data.

On a similar subject, anyone recommend a particular brand of usb analyzer? I have an ellisys usb explorer 200 and it isn't providing a trace that I find easy to follow (although you can tell here that I'm no expert).
chmorgan
Associate II
Posted on May 01, 2014 at 18:48

I was able to make some progress here.

- I added counters to my application, to the usbd_cdc_core functions that receive data and to when the transaction prepare is called. These all match, proving some proof that data sent is actually what is being delivered to the usb core driver.

- Got the analyzer working pretty well. I think the fact that the transactions are all splits (running through a hub) is making it more difficult to read the traces. I can see all of the data being sent and what I believe to be the host acking the transaction with the next ssplit in.

- Tracing on the host side (Linux in a Parallels VM under Mac) with wireshark shows all of the data EXCEPT the last 64 byte frame.

So it looks like something on the Mac/Parallels/Linux is either not receiving the final frame or not forwarding it on to the VM. Or maybe its stuck in a buffer somewhere? Going to test with a native Linux machine to see if it reproduces there but would appreciate any suggestions people have for what the root cause may be.

I was wondering if there is a driver issue that somehow isn't indicating that the transfer is complete or something like that....
chmorgan
Associate II
Posted on May 01, 2014 at 21:35

And another set of information that may reveal the issue to someone.

Host is Linux, tested under 3.14.x and 3.11.x kernels.

With the wireshark usbmon trace I'm seeing the data transfer stop. I unplug the device and the wireshark trace shows the communication to handle the discussion and then the final payload packet.

So it appears that the data must have been sent from the device as the data showed up on the trace AFTER the device was unplugged.

In addition, until the device is unplugged the data is not delivered. In the wireshark trace and application you can see the data stop coming in and then the wireshark trace gets the final packet after unplugging. So its like the data is stuck inside of the Linux kernel for some reason and the unplug event causes it to be communicated...
swannb
Associate
Posted on June 12, 2014 at 10:06

Hi,

Had a very similar issue when using the STM32 USB device driver V1.1.0. The issue appears to be with the way the library handles packets equal to the maximum bulk data payload size (See section 5.8.3, Bulk Transfer Packet Size Constraints of the USB spec).

The bulk transfer is only completed when a transfer of less than the maximum bulk data payload size is sent, or a zero length packet is sent. As such, the receiving driver is expecting more data as the zero length packet was not being sent

Haven't had a look at ST's newer driver, or if they fixed the issue we had, but this might be enough information to get you on the right track.

Cheers,

Brett

paolo
Associate II
Posted on June 23, 2014 at 16:52

Hi Brett,

I'm also facing a problem really similar to your one: when I send a packet with bulk data payload length that equals maximum one, seems that packet is not sent. Moreover I understood that endpoing sw fifo is correctly filled and endpoing notifies transfer completion, but packet is not received on host side. As already written by Christopher, that ''missing'' packet is sent only when a new packet is enqueued to USB driver.

What I still didn't understand, is why not every packet with maximum endpoint payload length is not sent: I'm facing, but I'm not sure, that only if last packet enqueued caused a transfer of maximum lenght this is not sent...

Anyway I temporarly found a dummy workaround: avoid to send a packet with bulk data payload length that equals maximum endpoint one. e.g.: if maximum is 64 bytes, then I enqueue within usbd_cdc_DataIn and Handle_USBAsynchXfer only packet with length less than or equal 63 bytes...

Please let me know if this workaround is also useful for you.

Anyway thank you a lot for your hints!

Ciao

      Paolo

ckern
Associate II
Posted on September 14, 2015 at 03:46

I am having the same problem - I am using memcpy within the usbd_cdc_vcp.c file. It appears that when the system hangs, it is when USB_Tx_State == 1. When I step through the driver, specifically, the usbd_cdc_DataIn and Handle_USBAsynchXfer functions within the usbd_cdc_core.c file, it never executes the usbd_cdc_DataIn function anymore (during the system hang). My understanding is that when the host does its periodic low-level 'pinging' of my device (yes, I know it is a bit more complicated than a 'ping', so to speak), it will call the usbd_cdc_DataIn callback function. This never happens anymore; only the Handle_USBAsynchXfer function is called due to the usbd_cdc_SOF counter/timer. My opinion is that the orignal post is correct - that there is some sort of race condition that has occurred. My feeling is that although the datain/dataout pointers have been moved and the data has been placed in the circular queue, the host may not have ACKed the data and/or the ZLP, so it is still expecting the data to come across.

Has anyone found a fix to this issue? Is there a way to poll my device driver and see if the last packet sent was actually accepted by the host? Is there a bit somewhere that keeps track of the status of the data sent vs data ACKed?

I have indeed looked through manual, and could not find what I was looking for. I am REALLY hoping I overlooked the register needed to check for a successful transfer.

Something to note is that if I do not use memcpy, but rather just put the data in the queue one byte at a time, everything is fine.

I am transferring up to 20Kb at a time, always checking for USB_Tx_State == 0 before adding data to the queue. My understanding is that the driver can handle this amount of data, it just breaks it up into 512 bytes of payloads at a time. The system hang can be after 5 minnutes or 2 hours - hard to replicate, but it does always fail at some point or another. I have not put any sort of snooper on the USB lines yet, and I hope to not have to... 🙂

Thanks in advance for the support.

-- Chris