This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

USB Bulk Packet Size

Hi Guys,

Playing around with StellarisWare and a USB Bulk application based off of the usb_dev_bulk example. If I send a packet out on my host of 256 bytes the RxHandler will often get called multiple times with 64 bytes worth of data. Likewise if I send out multiple smaller packets in a short amount of time, they'll often be concatenated as a single call to RxHandler. Is this correct behavior and if so is there some way to either configure things so that I only get entire packets (up to 1-2k) and/or somehow get the entire packet size?

Various bits of configuration code below in case it helps.

#define BULK_BUFFER_SIZE 512
const tUSBDBulkDevice g_sBulkDevice =
{
    USB_VID_STELLARIS,
    USB_PID_BULK,
    500,
    USB_CONF_ATTR_SELF_PWR,
    USBBufferEventCallback,
    (void *)&g_sRxBuffer,
    NULL,
    NULL,
  //  USBBufferEventCallback,
  //  (void *)&g_sTxBuffer,
    g_pStringDescriptors,
    NUM_STRING_DESCRIPTORS,
    &g_sBulkInstance
};

unsigned char g_pucUSBRxBuffer[BULK_BUFFER_SIZE];
unsigned char g_pucRxBufferWorkspace[USB_BUFFER_WORKSPACE_SIZE];
const tUSBBuffer g_sRxBuffer =
{
    false,                           // This is a receive buffer.
    RxHandler,                       // pfnCallback
    (void *)&g_sBulkDevice,          // Callback data is our device pointer.
    USBDBulkPacketRead,              // pfnTransfer
    USBDBulkRxPacketAvailable,       // pfnAvailable
    (void *)&g_sBulkDevice,          // pvHandle
    g_pucUSBRxBuffer,                // pcBuffer
    BULK_BUFFER_SIZE,                // ulBufferSize
    g_pucRxBufferWorkspace           // pvWorkspace
};


 // Enable the GPIO peripheral used for USB, and configure the USB pins.
 ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
 ROM_GPIOPinTypeUSBAnalog(GPIO_PORTD_BASE, GPIO_PIN_4 | GPIO_PIN_5);
 // Initialize the receive buffers.
 USBBufferInit((tUSBBuffer *)&g_sRxBuffer);
 // Set the USB stack mode to Device mode with VBUS monitoring.
 USBStackModeSet(0, USB_MODE_FORCE_DEVICE, 0);
 // Pass our device information to the USB library and place the device on the bus.
 USBDBulkInit(0, (tUSBDBulkDevice *)&g_sBulkDevice);
  • Paul Haddad said:
    is there some way to either configure things so that I only get entire packets (up to 1-2k) and/or somehow get the entire packet size?


    There are two options to pass the transfer size over the bulk OUT endpoint, from host to the device.

    1) Transfer size in header
    Your host application appends header bytes, which hold the transfer size, before the data body.

    2) Short packet termination
    Your host application sends a ZLP (Zero-Length-Packet) after the data transfer, when the transfer size is just the multiple of 64 bytes. For WinUSB, you may enable SHORT_PACKET_TERMINATE policy for the OUT pipe, so that WinUSB add ZLP automatically.

    The description of SHORT_PACKET_TERMINATE policy on this MSDN page is wrong.

    "WinUsb_SetPipePolicy function"
    http://msdn.microsoft.com/en-us/library/ff540304%28VS.85%29.aspx
    SHORT_PACKET_TERMINATE
    - Enabling SHORT_PACKET_TERMINATE causes the driver to send a zero-length packet at the end of every write request to the host controller.

    You'll find better description on this MS document.

    "How to Use WinUSB to Communicate with a USB Device"
    http://msdn.microsoft.com/en-us/library/windows/hardware/gg487341.aspx
    http://download.microsoft.com/download/9/C/5/9C5B2167-8017-4BAE-9FDE-D599BAC8184A/WinUsb_HowTo.docx

    Table 5. Pipe Policy Behavior
    SHORT_PACKET_TERMINATE
    - This policy is valid only for bulk and interrupt OUT endpoints (setting this policy on IN endpoints has no effect)
    - If enabled, all writes to the endpoint are terminated with a short packet.

    Tsuneo

  • Paul Haddad said:
    If I send a packet out on my host of 256 bytes the RxHandler will often get called multiple times with 64 bytes worth of data. Likewise if I send out multiple smaller packets in a short amount of time, they'll often be concatenated as a single call to RxHandler.

    The usb_dev_bulk example on StellarisWare implements a ring buffer over the bulk OUT endpoint. RxHandler is called by the ring buffer handler (USBBufferEventCallback), not directly by the endpoint interrupt. Also, deffered tick operation (BulkTickHandler) gives another chances of RxHandler call. This complexity makes you mislead.

    When the host application sends 64 bytes or less, PC host controller puts single OUT transaction to the device. When the host app sends greater than 64 bytes, PC host controller splits this write call into a sequence of 64 bytes transactions, with the last transaction of 64 bytes or less.

    The USB engine on the Stellaris raises endpoint interrupt for each transaction.
    On StellarisWare\usblib\device\usbdbulk.c, endpoint interrupt invokes ProcessDataFromHost()

    Endpoint interrupt -> HandleEndpoints() -> ProcessDataFromHost()

    The reception of a transaction corresponds to ProcessDataFromHost() call exactly one by one.
    Here is an excerpt from ProcessDataFromHost() code.

    static tBoolean
    ProcessDataFromHost(const tUSBDBulkDevice *psDevice, unsigned long ulStatus)
    {
        ...
        ...
        // Has a packet been received?
        //
        if(ulEPStatus & USB_DEV_RX_PKT_RDY)
        {
            //
            // Set the flag we use to indicate that a packet read is pending.  This
            // will be cleared if the packet is read.  If the client doesn't read
            // the packet in the context of the USB_EVENT_RX_AVAILABLE callback,
            // the event will be signaled later during tick processing.
            //
            SetDeferredOpFlag(&psInst->usDeferredOpFlags, BULK_DO_PACKET_RX, true);   <-- (1)

            //
            // How big is the packet we've just been sent?
            //
            ulSize = MAP_USBEndpointDataAvail(psInst->ulUSBBase,                      <-- (2)
                                              psInst->ucOUTEndpoint);

            //
            // The receive channel is not blocked so let the caller know
            // that a packet is waiting.  The parameters are set to indicate
            // that the packet has not been read from the hardware FIFO yet.
            //
            psDevice->pfnRxCallback(psDevice->pvRxCBData,                             <-- (3)
                                    USB_EVENT_RX_AVAILABLE, ulSize,
                                    (void *)0);
        }

    In ProcessDataFromHost(),
    (1) usDeferredOpFlags raises at BULK_DO_PACKET_RX, which enables deffered tick operation.
    (2) The exact transaction (packet) size is read out here.
    (3) pfnRxCallback is called with USB_EVENT_RX_AVAILABLE
    This pfnRxCallback is assigned to USBBufferEventCallback() in StellarisWare\usblib\usbbuffer.c

    usb_bulk_struct.c
    const tUSBDBulkDevice g_sBulkDevice =
    {
        USB_VID_STELLARIS,
        USB_PID_BULK,
        500,
        USB_CONF_ATTR_SELF_PWR,
        USBBufferEventCallback,    <--- psDevice->pfnRxCallback
        ...

    Also, BulkTickHandler() calls USBBufferEventCallback( USB_EVENT_RX_AVAILABLE ), at every tick.
    USBBufferEventCallback( USB_EVENT_RX_AVAILABLE ) calls RxHandler()

    In this way, RxHandler() is called not just at every transaction reception, but also by tick events after reception.

    To know the exact timing and the size of transactions, you have to intercept ProcessDataFromHost() process.

    Tsuneo

  • Tsuneo,

    Thanks for the info. The Host is a Mac so not sure there's a SHORT_PACKET_TERMINATE like option (at least I didn't see one when looking around). No big deal since I basically implemented the transfer size in header approach.

    For anyone coming across this thread in the future I basically did the below. The Host encodes data as a Magic Header Byte (0x44), followed by a short data length, a byte command, then the actual data. The key thing here is that if you return < than USBBufferDataAvailable bytes, those bytes won't get consumed by the Buffer and will be available next time a packet comes in. So for a large data packet, this handler will get called many times until there's enough data in the buffer to consume. Similarly for small packets, this might get called once and need to handle multiple commands.

    I'm still playing around with the below, but hopefully it helps someone else in the future.

     unsigned long bytesRead = 0;
     unsigned long bufferDataAvailable;
     tUSBRingBufObject ringBuffer;
     USBBufferInfoGet(&g_sRxBuffer, &ringBuffer);
    
    
     while ((bufferDataAvailable = USBBufferDataAvailable(&g_sRxBuffer))) {
     if (ringBuffer.pucBuf[ringBuffer.ulReadIndex] == 0x44) {
     if (bufferDataAvailable > 3) {
     unsigned short packetLength = (ringBuffer.pucBuf[(ringBuffer.ulReadIndex + 1)%ringBuffer.ulSize] << 8) + ringBuffer.pucBuf[(ringBuffer.ulReadIndex + 2)%ringBuffer.ulSize];
     if (packetLength > MAX_PACKET_SIZE) {
     UARTprintf("Packet too large %d\n", packetLength);
     // Skip a byte in case its an error
     USBBufferDataRemoved(&g_sRxBuffer, 1);
     bytesRead++;
     } else if (packetLength <= bufferDataAvailable - 4) {
     unsigned char packetCommand;
     static unsigned char packetData[MAX_PACKET_SIZE];
     DEBUG_PRINT("Have a packet\n");
     USBBufferDataRemoved(&g_sRxBuffer, 3);
     USBBufferRead(&g_sRxBuffer, &packetCommand, 1);
     USBBufferRead(&g_sRxBuffer, packetData, packetLength);
     handle_command(packetCommand, packetData, packetLength);
     bytesRead += 4 + packetLength;
     } else {
     DEBUG_PRINT("Not enough data need %d have %d\n", packetLength, bufferDataAvailable - 4);
     break;
     }
     } else {
     break;
     }
     } else  {
     UARTprintf("Junk Data 0x%x\n", ringBuffer.pucBuf[ringBuffer.ulReadIndex]);
     USBBufferDataRemoved(&g_sRxBuffer, 1);
     bytesRead++;
    }
     }
    
    
     DEBUG_PRINT("Read in %d bytes out, remaining %d.\n", bytesRead, USBBufferDataAvailable(&g_sRxBuffer));
     return bytesRead;
     }