Electronic – USB CDC : Help with Zero Packets

usbusb device

I'm trying to understand the need for Zero packets in USB-CDC transmissions. From what I gather, this seems to touch upon some grey areas of the USB spec or in differences in implementation of host USB drivers. I'm interested in both the 'correct' behavior (as per the USB spec) and typical behavior as far as regular USB hosts are concerned (Linux, Windows, OSX). Any relevant insight would be appreciated.

  1. A hypothetical example of a USB device would be one which is transmitting over a single USB-CDC endpoint, simply continuously generating and transmitting bytes infinitely. For this example, assume that the generation of data is considerably slower than USB-CDC transmission. The approach I would like to use is collect 64 bytes of data (device packet size), and when 64 bytes are ready, transmit a single packet. No zero packets will ever be sent, no short packets will ever sent. Any host-initiated polling will be NAKed when there is no complete packet ready.

    1. Is this a valid way to transmit CDC data?

    2. Are there any USB hosts that are known to have issues with this approach?

    3. If the USB device is a composite USB device, and one of the provided endpoints is a CDC endpoint as described, is there any change to the answers of a. and b.?

  2. In the device of 1., if, instead of sending NAKs when there is nothing to send, the device were to send back a 0 packet, is it likely to still work? How does the overall transmission change – in terms of how it is interpreted by a serial port emulator and/or the effective bandwidth?

  3. If the device, instead, transmits a few bytes, waits for a response. The length of these 'few' bytes is not known apriori. The host must be able to receive, process, and respond to these few bytes – they can't just sit in a hardware / driver buffer waiting for the entire transmission to end. Assume for the moment that USB-CDC recieve-by-the-device is somehow working and stable. All I'm interested in in zero packet transmission from the device.

    1. If the number of bytes to be sent out is not a multiple of 64 bytes (packet size), then simply ending with the 'short' packet would be enough to mark end of transmission, yes?

    2. If the number of bytes to be sent is a multiple of 64 (packet size), then what happens if a zero packet is not sent after the last transmitted packet to mark end of transmission?

Best Answer

Apparently, some host device drivers, like in Windows, use the indication that you have no more data immediately to pass accumulated data up to the app. Without that, Windows apparently buffers the data and passes up to the app only when some limit is reached (seems to be 64 bytes, but not sure).

USB takes a less than full IN (received from device) packet as indication of no more immediate data. This means if you have no more data and the previous IN packet filled the buffer, then you have to send a 0 length packet in response to the next IN token. You would think that NACKing the next IN token clearly indicates you don't have immediate data, but apparently that's not correct for USB.

I just ran into this issue head on. I've been using my PIC 18 USB code for many years and over a dozen projects without issues. Up until now, I used 64 byte buffers for the app data endpoints. I just did another USB device, but this time using a smaller PIC 18 where I couldn't afford the three 64 byte buffers for each endpoint. I used 16 byte buffers instead.

This PIC was receiving information over a serial connection, then sending it on to the host via USB. Communication sometimes stalled. This was traced back to happening after a full 16 byte packet was sent, then no more data available. Windows kept the 16 bytes without passing them up to the application.

We temporarily kludged around this by having the serial data stream contain NULL commands occasionally when nothing else was going on. These are only 1 byte long. If the above case happened, Windows would keep the 16 bytes. Then a packet would come along shortly containing only the NULL command byte. Since that is not a full packet, Windows considers that the end of immediate data and releases the accumulated 17 bytes to the applications.

When I get back next week, I'll fix my PIC 18 USB code to send a empty packet whenever the previous packet was full and there is nothing new to send. Then it will NACK subsequent IN tokens. Currently it NACKs IN tokens whenever it doesn't have anything immediate to send.

Since I haven't made the change yet, I can't say for sure that this was the problem and that the intended solution addresses it. However, the occasional NULL command workaround worked, and the USB spec does seem to agree that you should send a non-full packet whenever there is no more immediate data.

I still don't understand why NACKing the next IN token doesn't convey the same information, but apparently it doesn't.

Update

I have meanwhile fixed my PIC 18 USB stack. It now sends one zero-length packet if the previous packet was full length, and there is nothing immediately available to send. After non-full-length packets, which includes the deliberate zero-length packets, with nothing immediate to send, IN tokens are NACKed as before. I believe this is now following the letter of the USB spec. A USB analyzer shows the correct sequence of packets according to this interpretation.

The NULL-sending temporary kludge mentioned above was removed, and everything seems to work correctly.