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 transfer and possible loss/corruption of data

Good Evening everybody,


I have been working on an application that gets data from an ADC chip and streams them over USB using bulk transfers. Much of the effort that relates to this work has been described in this post. Key features of that application are:

  • Data transfer from the ADC to the MCU over SSI (SPI) is performed using an external interrupt on the MCU; the DOUT line of the ADC goes low when a new result is available and this fires an external interrupt whose ISR gathers the data.
  • Data are stored in a multiple buffer in a circular fashion; when that buffer is halfway full, data are sent over USB to the host application of a PC starting from the "top" (the beginning) of the buffer. This has been implemented and tested in another application. USB raw data are observed using a monitoring application.
  • One of the ADC channels is connected to a sinusoidal signal that is supplied by a signal generator. If everything worked properly, a plot of the data would be an exact replica of that signal.
  • Data from all activated channels of the ADC (3 channels) are stored in the following form: <channel 0 data><channel 1 data><channel 2 data><channel 0 data><channel 1 data><channel 2 data><channel 0 data><channel 1 data><channel 2 data>...

But monitoring the USB raw data reveals that what is stored to the host PC's disk is not of that form but some data are lost.

I checked things thoroughly in the debugger and noticed the following:

Each part of the multiple buffer is 192 bytes long. What I do in the MCU's code is that I send data over USB every 192 bytes. I have set BULK_BUFFER_SIZE to 384 bytes instead of 256 bytes, like in the USB bulk example supplied with TivaWare. However, after the 128th byte - or after the 64th byte - data are lost.

What is more strange is the fact that data loss happens when sending those over USB; the data stored in the MCU memory are intact and are copied byte-to-byte to the USB TX buffer correctly. Please refer to the function that performs that task at the end of this post. Moreover, that function does not return the number of bytes that are written to USB but it adds 128 more bytes.

It seems that I have not understood in depth how data are sent over USB using the buffers in the usb_dev_bulk example.

Any ideas, anyone? This might have a simple solution but I have become blind after exhaustive work on this...

// Modified from function EchoNewDataToHost() in usb_dev_bulk example supplied by TI.
static uint32_t SendDataToHost(tUSBDBulkDevice *psDevice, uint8_t *pi8Data, uint_fast32_t ui32NumBytes)
{
    uint_fast32_t ui32Loop, ui32Space, ui32Count;
    uint_fast32_t ui32WriteIndex;
    tUSBRingBufObject sTxRing;

    // Get the current buffer information to allow us to write directly to
    // the transmit buffer (we already have enough information from the
    // parameters to access the receive buffer directly).
    USBBufferInfoGet(&g_sTxBuffer, &sTxRing);

    // How much space is there in the transmit buffer?
    ui32Space = USBBufferSpaceAvailable(&g_sTxBuffer);

    // How many characters can we process this time round?
    //ui32Loop = (ui32Space < ui32NumBytes) ? ui32Space : ui32NumBytes;
    //ui32Count = ui32Loop;
    ui32Count = (ui32Space < ui32NumBytes) ? ui32Space : ui32NumBytes; // I changed the above 2 lines to that

    // Set up to process the characters by directly accessing the USB buffers.
    ui32WriteIndex = sTxRing.ui32WriteIndex;

    ui32Loop = 0;
    while(ui32Loop < ui32NumBytes)
    {
        // Write bytes to USB TX buffer
	g_pui8USBTxBuffer[ui32WriteIndex] = pi8Data[ui32Loop];


        // Move to the next character taking care to adjust the pointer for
        // the buffer wrap if necessary.
        ui32WriteIndex++;
        ui32WriteIndex = (ui32WriteIndex == BULK_BUFFER_SIZE) ? 0 : ui32WriteIndex;

        ui32Loop++;
    }

    // We've processed the data in place so now send the processed data
    // back to the host.
    USBBufferDataWritten(&g_sTxBuffer, ui32Count);

    // We processed as much data as we can directly from the receive buffer so
    // we need to return the number of bytes to allow the lower layer to
    // update its read pointer appropriately.
    return(ui32Count);
}

  • Hello George,

    Did you check the value of the parameter "ui32MsgValue" when the callback function assigned for the transmit data channel is called? In the USB Device Bulk example this call back function is called "TxHandler".

    Also who calls the function "SendDatatoHost"? What is the value passed to the parameter "ui32NumBytes"?

    Thanks,
    Sai
  • Hello Sai,

    I checked the value of ui32MsgValue and it is equal to 64. The TxHandler() function is entered twice before returning to SendDataToHost(). That's why SendDataToHost() returns 128 more bytes than what I expected to.

    Here is the code for TxHandler():

    // Modified from the usb_dev_bulk example supplied by TI.
    uint32_t TxHandler(void *pvCBData, uint32_t ui32Event, uint32_t ui32MsgValue,
              void *pvMsgData)
    {
    	// Send any data that are ready to be sent
    	// and update the transmit counter.
        if(ui32Event == USB_EVENT_TX_COMPLETE)
        {
        	g_ui32TxCount += ui32MsgValue;
    
            // Flush our buffers.
           USBBufferFlush(&g_sTxBuffer);
           USBBufferFlush(&g_sRxBuffer);
        }
    }

    Here is the sequence of function calls:

    Below is the main loop in main() function:

     while(1)
     {
        	if(g_bIsSampling)
    	{
    		if(g_bSendToUSB) // flag is set in the ISR
    		{
    			SendDataToUSB(); // SendDataToHost() is called within that function
    			g_bSendToUSB = false;
    		}
    
        		// Have we been asked to update the status display?
    		if(g_ui32USBFlags & COMMAND_STATUS_UPDATE)
    		{
    			g_ui32USBFlags &= ~COMMAND_STATUS_UPDATE;
    		}
    	}
    }

    Below is the ISR that implements both sampling and data transfer over USB:

    // Interrupt handler for pin PQ3, triggered when LOW.
    // Reads the data register of the ADC.
    void ISR_DataReady(void)
    {
    	// do the following only when USB is connected and system performs sampling
    	if(g_bUSBConfigured && g_bIsSampling)
    	{
    		// Temporarily disable interrupt
    		ROM_GPIOIntDisable(GPIO_PORTQ_BASE, GPIO_INT_PIN_3);
    
    		// Clear interrupt source.
    		ROM_GPIOIntClear(GPIO_PORTQ_BASE, GPIO_INT_PIN_3);
    
    		// Read the data register of the ADC.
    		ReadSample(&g_DataBuffer[ui8BufIndex][ui8Index*NO_OF_SAMPLE_BYTES]);
    		ui8Index++;
    
    		// At this point, a total of NO_OF_SAMPLE_BYTES bytes
    		// are placed in the buffer.
    
    		// When a part of the multiple buffer is full, send its contents over USB.
    		// The necessary check here is if ui8Index has reached noOfChannels*NO_OF_BUF_SAMPLES.
    		// In this way, each one of the active channels will have contributed NO_OF_BUF_SAMPLES samples.
    		if(ui8Index == (noOfChannels*NO_OF_BUF_SAMPLES))
    		{
    			ui8Index = 0;	// Reset counter
    			g_bSendToUSB = true; // set flag to send data over USB
    		}
    
    		// After having read the data register of the ADC, re-enable interrupt
    		ROM_GPIOIntEnable(GPIO_PORTQ_BASE, GPIO_INT_PIN_3);
    	}
    }

    When g_bSendToUSB is true, the following function is called:

    // Sends buffer contents to USB.
    void SendDataToUSB(void)
    {
    	// g_bIsFirstTime is initialized as true
    	// to indicate the first time that data
    	// are written to the multiple buffer.
    	// When the multiple buffer is halfway full for the first time,
    	// this flag is set false.
    	if(!g_bIsFirstTime)
    	{
    		// Send data to the host over USB from the buffer that is NOT in use
    		g_ui32TxCount += SendDataToHost(&g_sBulkDevice,
    						&g_DataBuffer[ui8BufToUSB][0],
    						noOfChannels*NO_OF_BUF_SAMPLES*NO_OF_SAMPLE_BYTES);
    		// the returned value does not always match the no. of bytes that should be transferred over USB!!!
    
    		// ui8BufToUSB is an index to the buffer that is currently READ and its contents are sent to the USB
    		ui8BufToUSB++;
    		ui8BufToUSB = (ui8BufToUSB == NO_OF_BUFFERS) ? 0 : ui8BufToUSB;
    
    		// ui8BufIndex is an index to the buffer that is currently WRITTEN with data coming from the SPI device
    		ui8BufIndex++;
    		ui8BufIndex = (ui8BufIndex == NO_OF_BUFFERS) ? 0 : ui8BufIndex;
    	}
    	else
    	{
    		ui8BufIndex++;
    		if(ui8BufIndex == (NO_OF_BUFFERS/2)) // arrived halfway (since NO_OF_BUFFERS is a power of 2)
    		{
    			g_bIsFirstTime = false;
    			ui8BufToUSB = 0;	// send data to the host PC starting from the first buffer
    		}
    	}
    }

    that, in turn, calls SendDataToHost():

    // Modified from function EchoNewDataToHost() in usb_dev_bulk example supplied by TI.
    static uint32_t SendDataToHost(tUSBDBulkDevice *psDevice, uint8_t *pi8Data, uint_fast32_t ui32NumBytes)
    {
        uint_fast32_t ui32Loop, ui32Space, ui32Count;
        uint_fast32_t ui32WriteIndex;
        tUSBRingBufObject sTxRing;
    
        // Get the current buffer information to allow us to write directly to
        // the transmit buffer (we already have enough information from the
        // parameters to access the receive buffer directly).
        USBBufferInfoGet(&g_sTxBuffer, &sTxRing);
    
        // How much space is there in the transmit buffer?
        ui32Space = USBBufferSpaceAvailable(&g_sTxBuffer);
    
        // How many characters can we process this time round?
        //ui32Loop = (ui32Space < ui32NumBytes) ? ui32Space : ui32NumBytes; // TI original code
        //ui32Count = ui32Loop; // TI original code
        ui32Count = (ui32Space < ui32NumBytes) ? ui32Space : ui32NumBytes; // my version
    
        // Set up to process the characters by directly accessing the USB buffers.
        ui32WriteIndex = sTxRing.ui32WriteIndex;
    
        ui32Loop = 0;
        while(ui32Loop < ui32NumBytes)
        {
            // Write bytes to USB TX buffer
    	g_pui8USBTxBuffer[ui32WriteIndex] = pi8Data[ui32Loop];
    
            // Move to the next character taking care to adjust the pointer for
            // the buffer wrap if necessary.
            ui32WriteIndex++;
            ui32WriteIndex = (ui32WriteIndex == BULK_BUFFER_SIZE) ? 0 : ui32WriteIndex;
    
            ui32Loop++;
        }
    
        // We've processed the data in place so now send the processed data
        // back to the host.
        USBBufferDataWritten(&g_sTxBuffer, ui32Count);
    
        // We processed as much data as we can directly from the receive buffer so
        // we need to return the number of bytes to allow the lower layer to
        // update its read pointer appropriately.
        return(ui32Count);
    }

    Hope things are clearer now. Any ideas?

  • Hello Geroge,

    One issue I saw in the function "SendDataToHost" is that irrespective of the space available in the buffer "g_pui8USBTXBuffer", "ui32NumBytes" bytes are copied into it. The variable "ui32Count" (which has the available space in the buffer) is not used to decide how many bytes should be copied into "g_pui8USBTXBuffer".

    Not sure if that is the cause of your error, but can you correct that and then try and see if the error is resolved?

    Thanks,
    Sai
  • Hi George,

    >BULK_BUFFER_SIZE to 384 bytes instead of 256 bytes

    Didn't read through your program but know we had to up it to 2048 bytes or lost data at the PC side. You can enable the bulk flow transfer message to print your flow rate achieved in the USB pipe. Ours was topping 256Kbps sending text message strings at continuous default buffer rate may have been 192 chars/sec or so and been some time ago.

  • BP101 and Sai, thank you for your contributions.

    I made the following changes in SendDataToHost() and now the samples of the sinusoidal signal that is connected to one of the 3 ADC channels seem to match a signal without discontinuities:

    // Modified from function EchoNewDataToHost() in usb_dev_bulk example supplied by TI.
    static uint32_t SendDataToHost(tUSBDBulkDevice *psDevice, uint8_t *pi8Data, uint_fast32_t ui32NumBytes)
    {
        uint_fast32_t ui32Loop, ui32Space, ui32Count;
        uint_fast32_t ui32WriteIndex;
        tUSBRingBufObject sTxRing;
    
        // Get the current buffer information to allow us to write directly to
        // the transmit buffer (we already have enough information from the
        // parameters to access the receive buffer directly).
        USBBufferInfoGet(&g_sTxBuffer, &sTxRing);
    
        // How much space is there in the transmit buffer?
        ui32Space = USBBufferSpaceAvailable(&g_sTxBuffer);
    
        // How many characters can we process this time round?
        ui32Loop = (ui32Space < ui32NumBytes) ? ui32Space : ui32NumBytes; // TI original code
        ui32Count = ui32Loop; // TI original code
    
        // Set up to process the characters by directly accessing the USB buffers.
        ui32WriteIndex = sTxRing.ui32WriteIndex;
    
        while(ui32Loop)  // TI original code
        {
            // Write bytes to USB TX buffer
        	g_pui8USBTxBuffer[ui32WriteIndex] = pi8Data[ui32Count - ui32Loop];
    
            // Move to the next character taking care to adjust the pointer for
            // the buffer wrap if necessary.
            ui32WriteIndex++;
            ui32WriteIndex = (ui32WriteIndex == BULK_BUFFER_SIZE) ? 0 : ui32WriteIndex;
    
            ui32Loop--;  // TI original code
        }
    
        // We've processed the data in place so now send the processed data
        // back to the host.
        USBBufferDataWritten(&g_sTxBuffer, ui32Count);
    
        // We processed as much data as we can directly from the receive buffer so
        // we need to return the number of bytes to allow the lower layer to
        // update its read pointer appropriately.
        return(ui32Count);
    }

    I checked things both with low and high sampling rates and with all 3 channels enabled; in all cases, the samples of the sinusoidal signal show no discontinuities.

    Thanks again for helping me towards the right direction.

  • We scrapped that function and write bulk USB ring buffer directly testing for overflow buffers each pass.

    Keeping the buffer hand shake fairly simple.

        //
        // How much space is there in the transmit buffer?
        //
         ui32Space = USBBufferSpaceAvailable(&g_sTxBuffer);
    
        //
        // If the USB buffer space will not hold the
        // string try again later when it will.
        //
        if(ui32Space < 256)
        {
          /* Flush the TX buffer of any overflow data */
            USBBufferFlush(&g_sTxBuffer);
        	return;
        }
    
    Based on UARTPrintf():
    
            //
            // Write this portion of the string.
            //
            //UARTwrite(pcString, ui32Idx);
    
            USBBufferWrite(&g_sTxBuffer, pcString, ui32Idx);

     

  • BP101 said:
    We scrapped that function and...

    Who's, "we?"