Is it possible to overwrite data to the In Endpoint, that is, overwrite data in the buffer before the host has a chance to read it? In my device I am sending a series of responses to a request from the host. Each of these responses are less than 64 bytes. I know the IN is double buffered, is it possible that the 3rd write can overwrite a buffer if the first 2 have not been read yet? Or is all that prevented by hardware? I am looking at a Keil example of writing data to an endpoint and it just writes to the tranmit data register without any type of checking?
"Is it possible to overwrite data to the In Endpoint"
Which USB MCU are you working on? It's device specific.
But at least I know, there is no USB MCU whose datasheet clearly describes about the result of overwrite to full endpoint buffer. It isn't expected.
Instead, some USB MCU has a command to flash the endpoint buffer, though not all of USB MCU have. "I am looking at a Keil example of writing data to an endpoint and it just writes to the tranmit data register without any type of checking?
KEIL examples are interrupt driven as for the IN endpoint handling. The interrupt for the IN endpoint is triggered when the endpoint buffer goes empty. Then, no check is required.
Tsuneo
I am using the 2364.
So I wrote an interface layer of software that sits above the Keil based USB driver. When I present a buffer of data to this interface layer to write to the IN endpoint, if it is less than 64 bytes it calls the USB_WriteEP () function in the Keil code then exits. If it is greater than 64 bytes it bascially waits for the endpoint buffer interrupt then writes more data repeating till everything is sent.
My concern is when I attempt to send a series of data where each item is less than 64 bytes. I dont wait around for the buffer empty interrupt in this case so in theory it sounds like I can mistakenly perform another write to the buffer before the buffer empty interrupt occurs.
"I am using the 2364."
NXP LPC2364 doesn't have a good command to flush the buffer of IN endpoint. 'Set Endpoint Status' command initializes (flushes) the IN buffer(s) by ST bit, but it clears the data toggle too. This feature targets Set_Configuration and Set_Feature( ENDPOINT ) support, not for just flushing buffer. "When I present a buffer of data to this interface layer to write to the IN endpoint, if it is less than 64 bytes it calls the USB_WriteEP () function in the Keil code then exits. If it is greater than 64 bytes it bascially waits for the endpoint buffer interrupt then writes more data repeating till everything is sent."
Fine. It's an ideal method.
a) Like UART The handling of IN endpoint is similar to UART TX. These 'ports' are driven by a TX-buffer-empty flag (hardware flag), either over an interrupt or by polling.
Suppose that you send a length of string over UART TX. - Fill a buffer with the string - When a TX-busy flag is FALSE, - - Put the first byte to UART TX (or enable TX-empty flag) -- the first kick - UART Interrupt occurs - In the UART ISR, put the next byte to TX, until the string expires. - - At the end of string, clear the TX-busy flag
Please note, we introduce another software flag, TX-busy, here.
When this UART has a depth of FIFO for TX, 'put byte' is replaced to 'puts bytes until FIFO full'.
IN endpoint is also handled in the same way, but it sends a packet instead of just a byte; 'put byte' is replaced to 'put bytes up to wMaxPacketSize (=64)'. The outline of the flow is utterly the same.
Most of KEIL examples does the first kick in the Set_Configuration handler. And no 'end of string'. Therefore, the IN endpoint ISR is called endlessly, always when the last packet is sent. This implementation is simple. In this scheme, however, the timing is triggered by the IN transaction of the host side, not by the event of the device side. In UART analogy, it likes to sends always null character.
For the applications in the real world, data is sent only when required. To do in this way, the first kick should be done in the event handler on which the data becomes ready. For example, - In a timer ISR, firmware scans a keyboard - When it detects make/break of key(s), it puts the first kick to send the key code(s). The first kick is done in other ISR than the IN endpoint ISR, or a task on the main loop.
b) Atomic access When we take this scheme, USB_WriteEP() is called out of the USB ISR (IN endpoint ISR is a part of the USB ISR). We have to take care of the atomicity of endoint access. USB_WriteEP() calls 'Select Endpoint' command to set the target endpoint. Also, many other USB routines do. When USB_WriteEP() of our handler is interrupted by the USB interrupt, our target endpoint is changed. And vice-versa.
To ensure atomic access, - The interrupt priority of our handler should be the same with USB interrupt. - For main loop task, USB_WriteEP() is guarded by disabling USB interrupt around it. "My concern is when I attempt to send a series of data where each item is less than 64 bytes. I dont wait around for the buffer empty interrupt in this case so in theory it sounds like I can mistakenly perform another write to the buffer before the buffer empty interrupt occurs."
You should check buffer empty. - Directly poll the FE (full/empty) flag returned by 'Select Endpoint' command to the USB engine. OR - In the IN endpoint ISR, set a flag to show buffer empty. This flag is reset after the endpoint buffer is filled by USB_WriteEP()
If the buffer is not empty, - retry it for several times - discard the data
The story continues to the question, "how to ensure timely data exchange over USB?" I'll write my answer afterward.
Thanks.
The documentation states the select endpoint command also initializes an internal pointer to the start of the selected buffer in EP_RAM. Sounds like this is more than I need to do as all i want is to look at the select endpoint register.
Currently my IN endpoint ISR sets an event which I wait on, but only when writing more than 64 bytes. I have added that logic to all writes and am seeing the data I would expect, so I believe I was overwritting the buffer. This is essentially setting a flag in the ISR which was one of your suggestions.
How to ensure timely data exchange over USB
In above post, I've written about the device side of bulk IN transfer for Full-Speed (FS) device. The host side is here.
a) Read it in advance - Polling-IN transaction As USB is host-centric, device cannot send data until host requests it by an IN transaction. But host doesn't know when the device puts data. Then, host puts IN transactions before the device puts data, and repeat the transactions. This method is called as Polling-IN transaction.
While the endpoint is empty, the USB engine on the device returns NAK to IN transaction from the host. The host controller (hardware) repeats IN transaction endlessly until the device returns DATA. For bulk IN transfer, you'll see more than 30 IN-NAKs on a single USB frame (1ms), when the bulk IN endpoint is assigned full bandwidth. That is, when the device places data to the IN endpoint, it is sent to host within the latency of 1/30 ms or less.
Polling-IN transaction is implemented on class drivers, like HID, CDC, etc. These PC device drivers issue IN transfer repeatedly by itself. The data retrieved from the device is stored the input buffer on the device driver. Host app read out data from this input buffer, indirectly.
Even using a generic bulk device driver, you can implement polling-IN transaction. The key is, repeated Read and buffering on your host app.
For Windows, OVERLAPPED ReadFile is called repeatedly in a sub-thread. When a ReadFile completes, the data is stored to a global buffer and next ReadFile is called immediately. To ensure no-gap transfer, more than single ReadFile are applied.
Please note, this method is same as maximizing the transfer speed.
// outline of the thread for polling-IN transaction UINT USBReadProc( LPVOID pParam ) { const int kNum_of_Calls = 4; OVERLAPPED ov[ kNum_of_Calls ]; HANDLE event_list[ kNum_of_Calls + 1 ]; // Initialize OVERLAPPED for (int i = 0; i < kNum_of_Calls; i++) { ZeroMemory( &ov[i], sizeof(ov[i]); ov[i].hEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); } // Call multiple ReadFile at a time event_list[0] = end_of_App_event; For ( int i = 0; i < kNum_of_Calls; i++ ) { event_list[i+1] = &ov[i]; ReadFile( ..., buf[i], ..., &ov[i] ); } // Wait the completion of calls and handle it while( true ) { DWORD result = WaitForMultipleObjects( kNum_of_Calls + 1, event_list, FALSE, INFINITE ); switch( result ) { WAIT_OBJECT_0: // end_of_App_event WAIT_TIMEOUT: goto close_and_end; default: // check the completion status GetOverlappedResult( ..., &ov[result - 1], ... ); // // transfer read buffer to global buffer // // next ReadFile ReadFile( ..., buf[result - 1], ... ov[result - 1] ); break; } } close_and_end: CancelIo( ... ); CloseHandle( ov[i].hEvent ); ... return 0 }
b) Ensure bus bandwidth for bulk Greater bus bandwidth is assigned to the endpoint, it gets more chance to start transfer. However, bulk transfer is the lowest priority.
Interrupt and Isochronous transfer have the first priority on USB bus. These transfers reserve the bandwidth at the device enumeration. Next is Control transfer, up to 10% bandwidth of FS bus is assigned. Bulk transfer is assigned just the rest of the bandwidth. Moreover, all active bulk endpoints on the devices on the bus share this rest bandwidth.
To ensure bus bandwidth for the bulk endpoint, - Connect the FS device over a multi-TT hub, all FS devices on the hub enjoy full bandwidth. - Connect it over a single-TT hub, without any other device on the hub - Connect it directly to PC USB port (TT = Transaction Translator)
See this post for the way how TT works for FS device(s) "USB Hub faster? and Suspend Problem?" on Microchip USB forum forum.microchip.com/tm.aspx
Multi-TT hub Belkin F5U234v1 (4port), F5U237v1 (7port) IOGEAR GUH274 (4port)
To check your hub for single-TT or multi-TT, It is known by bDeviceProtocol field of the hub device descriptor.
bDeviceProtocol FS/LS hub 0 HS single-TT 1 HS multi-TT 2
USBView read out of Belkin F5U234v1 multi-TT hub
Device Descriptor: bcdUSB: 0x0200 bDeviceClass: 0x09 bDeviceSubClass: 0x00 bDeviceProtocol: 0x02 bMaxPacketSize0: 0x40 (64) idVendor: 0x050D (Belkin Components) idProduct: 0x0234 bcdDevice: 0x0000 iManufacturer: 0x00 iProduct: 0x00 iSerialNumber: 0x00 bNumConfigurations: 0x01
USBView on FTDI site www.ftdichip.com/.../usbview.zip
On a semi-related issue, is it possible for the device side to re-initiaite a sort of USB reset? Something similiar to when you first plug in the USB cable and the device is recognized by the host? The D+ line on our board is pulled high, not under software control so I can not manipulate that.
Paul
"is it possible for the device side to re-initiaite a sort of USB reset? "
Usually, "soft-detach/attach" using D+ (or D-) pull-up resistor is applied for this purpose, for such as firmware update. But in your case, D+ pull-up is fixed one.
I've seen an implementation in which, - D+ pull-up resistor is an external one and fixed - At the power-up, the firmware sets the USB engine to drive D+/D- line low (SE0), until the start-up initialization (except for the engine) finishes. - When initialized, the firmware resets the engine once, releases D+/D- line drive and initializes it.
At plug-in, (root) hub doesn't drive D+/D- line until it detects connection; until one of these lines goes high. Therefore, this method is relatively safe, though I don't like it :-)
For re-enumeration, however, this method may cause output conflict of D+/D- line drivers between your device and hub. The hub puts SOF periodically. When there is any other device on the hub, OUT transactions to other device are sent to your device, too.
If you have to do it on the device side, modify the circuit to control D+ pull-up resistor.
To issue bus reset from the host side, - 'device reset' control request, when the PC device driver supports it. - For Windows, SetupDiChangeState ( DICS_ENABLE / DICS_DISABLE )