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.

About USB bulk transfer with buffer: Modification of usb_dev_bulk example

Good Evening to all of you!

I am seeking for your advice and help for the following issue I encounter:

I am trying to modify the usb_dev_bulk example code and use it in a proof-of-concept application. It is the very first time that I develop uC code for USB.

In that application, data from SSI3 (configured as Freescale SPI Mode 0) are written to an array and that array's contents are sent over USB to the host PC at regular time intervals.

As a "heartbeat", I use Timer4 configured to overflow 10 times per second; this gives the desired sampling rate for the SPI device that is attached to the uC.

(I followed advice about alternatives to SysTick given to me in this thread; the aim is to push that up to 1000 or even 10000 times per second.)

Moreover, I am running a USB monitoring software tool to monitor raw data traffic on the USB lines.

What I have noticed so far is the following:

When running the usb_dev_bulk example code along with the Win32 application on my PC, I can monitor the TX and RX data successfully, exactly as they appear in the Win32 application. As you may recall, that application gets ASCII data from the PC and toggles the case of alphabetic characters, while leaving everything else intact.

My altered code includes a modified version of the EchoNewDataToHost() function that does not read anything from the host PC but just sends data to the host PC upon request in the Timer4 ISR. That function requires a pointer to the USB bulk structure g_sBulkDevice that is defined in usb_bulk_structs.c file.

// Global pointer to the USB device
tUSBDBulkDevice* pvDevice;

...

In function main() :

pvDevice = USBDBulkInit(0, &g_sBulkDevice);

When running my altered version, although I pass that pointer when calling function SendDataToHost(), I found out while stepping through the code that this pointer does not have the correct value for reasons that I really do not understand. Data are not present on the TX channel of the USB:

SendDataToHost(pvDevice, g_RX_buffer, NO_OF_BUF_SAMPLES*(NO_OF_SPI_BYTES-1)); // pvDevice does not point where it should be (&g_sBulkDevice)

Please find attached all the project files and I will thank you to advise me.

SPI_demo_USB.zip

  • Hello George

    Do you mean that the USB application on the PC side does not receive any data or does the TX handler in the uC never get called?

    Regards
    Amit
  • Hi Amit, I 'm not really sure; after posting, I tried a little more before going home and I think that the TxHandler() function is not called. I compared what happens in the usb_dev_bulk example with what happens in my modified code that I posted.

    The USB monitoring application does show data when I run the usb_dev_bulk example; it shows exactly what I type - and is sent to the uC - and what the uC returns. By the way, having the Win32 application for the usb_dev_bulk example running but with my modified code, it shows some random activity when I try to send characters through the WIn32 application. This shows that something - or everything? - is completely messed up in the uC's memory!

    I am really sure that there is something that I have not understood and is rather obscure. Something is not called or not properly called in my modified code. Please explain to me about callbacks etc. so that I get a firm grasp on how to use the API to write a useful program.

  • Hello George,

    Yes. The issue may be in the TxHandler not being called and you would need to check the mechanism from the buffer update to the transmission of the buffer.

    Regards
    Amit
  • Let me be more specific, Amit.

    What I find hard to understand is how I should call that handler, TxHandler(). At present, when I want to send bytes over the USB, I copy the contents of a buffer array that I have set up, g_RX_buffer, to g_pui8USBTxBuffer. Upon completion of that, a USB_TX_COMPLETE event is sent.

    Up to now, I thought that as being correct but it does not work. Where am I wrong?

    I just thought of a possible reason: g_pui8USBTxBuffer is a member of struct :

    // Extract from usb_bulk_structs.c:
    uint8_t g_pui8USBTxBuffer[BULK_BUFFER_SIZE];
    uint8_t g_pui8TxBufferWorkspace[USB_BUFFER_WORKSPACE_SIZE];
    const tUSBBuffer g_sTxBuffer =
    {
        true,                            // This is a transmit buffer.
        TxHandler,                       // pfnCallback
        (void *)&g_sBulkDevice,          // Callback data is our device pointer.
        USBDBulkPacketWrite,             // pfnTransfer
        USBDBulkTxPacketAvailable,       // pfnAvailable
        (void *)&g_sBulkDevice,          // pvHandle
        g_pui8USBTxBuffer,               // pi8Buffer
        BULK_BUFFER_SIZE,                // ui32BufferSize
        g_pui8TxBufferWorkspace          // pvWorkspace
    };

    Then should I write my data to g_sTxBuffer.g_pui8USBTxBuffer? To be honest, I feel a little dumb with those typedefs and so on after hours on end...

  • Hello George

    You are right in your interpretation. Let me break the Rx based example to independently send Tx packets alone. This will take me some time though. Note that the usb_bulk_example may still not work on PC side as it assumes a Tx from PC before a Tx from uC.

    Regards
    Amit
  • Hi Amit, I think I managed to see some data!

    Here is what I did:

    I verified that TxHandler() was not called by copying function EchoNewDataToHost() to my project and making RxHandler() identical to the same function in usb_dev_bulk example. This means that EchoNewDataToHost() was called every time there were data from the host PC to the uC.

    Moreover, in Timer4IntHandler(), I commented out the call to SendDataToHost().

    By doing so, my code worked exactly as in the example code.

    It seemed to me that the USB_EVENT_RX_AVAILABLE must be captured before sending anything. In that way, the uC returned as many characters as it received, and those characters were from the SPI slave device as I confirmed after some hours of debugging. :(

    I altered the VC++ application so that it read USB packets sent from the uC. (Implicitly set to "echo" mode.) This was a key step, as I understood; the host PC must read the USB so that traffic appears. It is kind of different from the plain, old serial port where the signals from the UART are present, with or without the PC being connected. (It is the first time I ever deal with USB, so please bear with me.)

    I discovered that, on the uC side, g_pui8USBTxBuffer contains the first packet and ... junk afterwards.

    On the PC side, the contents of g_pui8USBTxBuffer appear exactly as they are in the uC.

    In SendDataToHost(), after first packet is transmitted - and it is transmitted OK - g_pui8USBTxBuffer is filled with rubbish. This would probably be due to a bad pointer.

    I checked out this line in SendDataToHost():

    g_pui8USBTxBuffer[ui32WriteIndex] = pi8Data[ui32WriteIndex];

    and found out that the rubbish came from pi8Data[ui32WriteIndex]. For ui32WriteIndex > (no. of bytes per packet), it does not contain anything meaningful.

    Here is the current version of SendDataToHost():

    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;
    
        // 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
    		// TODO: in the future, all this work should be done by uDMA
    		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);
    }

    OK, the code is not as neat as possible, but I will clean up things.

    I will continue my work by modifying the Win32 application so that a correct interpretation of the incoming data from the uC takes place.

  • Hello George,

    That was a very good and detailed update. The USB bulk example code is not for a serial CDC class device which is asynchronous for the TX and RX paths. The USB bulk example code can be used for a generic communication w/o the need for CDC class to be enumerated.

    Regards
    Amit
  • Here I am again with updates on that project:

    The objective is to read data from an SPI slave device and send them to a host PC through USB bulk transfers.

    I have attached here the entire project with its source files.

    To do that, I implemented a multiple buffer on the TM4C1294 as follows:

    I create an array of bytes, having NO_OF_BUFFERS elements in the first dimension ("rows", I would say) and NO_OF_BUF_SAMPLES*NO_OF_PACKET_BYTES in the second dimension ("columns").

    volatile uint8_t g_DataBuffer[NO_OF_BUFFERS][NO_OF_BUF_SAMPLES*NO_OF_PACKET_BYTES]; // this is a multiple buffer
    volatile int8_t i8BufIndex = 0; // indicates which buffer is in use for data transfer FROM the SPI
    volatile uint8_t ui8BufToUSB = 0; // indicates which buffer is in use for data transfer TO the USB

    Each row of that array is a buffer and is written with data coming from the SPI slave device.  When half of the rows are full, I continue writing data to the buffers and I start to send data to USB. Furthermore, I have implemented an elementary flow-control: for each buffer that is received by the host PC, the characters 'O' and 'K' (for OK) are sent back to the uC and only after receiving them a new transmission is made.

    Here is the while(1) loop in my code:

    	while(1)
    	{
    		// When Timer4 overflows, it is time to get a new sample
    		if(g_bSampleNow)
    		{
    			g_bSampleNow = false;	// Reset flag
    			// Get a sample of the ADXL362 output in the form of an 8-byte packet.
    			SPITransfer(pui8DataTx, &g_DataBuffer[ui8BufIndex][(ui8Index++)*NO_OF_PACKET_BYTES], 2, NO_OF_PACKET_BYTES);
    			//TestPattern(&g_DataBuffer[ui8BufIndex][(ui8Index++)*NO_OF_PACKET_BYTES], 2, NO_OF_PACKET_BYTES); // TEST ONLY with multiple buffer
    			// At this point, a total of NO_OF_PACKET_BYTES bytes are placed in the buffer.
    
    			// When the buffer is full, send its contents to USB.
    			if((ui8Index % NO_OF_BUF_SAMPLES) == 0)
    			{
    				ui8Index = 0;	// Reset counter
    
    				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], NO_OF_BUF_SAMPLES*NO_OF_PACKET_BYTES);
    					
    					// ui8BufToUSB is an index to the buffer that is currently SENT to USB					
    					ui8BufToUSB++;
    					ui8BufToUSB = ui8BufToUSB % NO_OF_BUFFERS;
    
    					// Take a snapshot of the latest transmit count.
    					ui32TxCount = g_ui32TxCount;
    
    					// ui8BufIndex is an index to the buffer that is currently WRITTEN with SPI data
    					ui8BufIndex++;
    					ui8BufIndex = i8BufIndex % NO_OF_BUFFERS; 
    
    					// Clear the contents of the receive buffer - flow control
    					ClearBuffer(pui8USBRXData, sizeof(pUSBRXOK));
    
    					// Perform elementary flow control
    					while(!g_ui8ClearToSend)
    						g_ui8ClearToSend = CompareArrays(pui8USBRXData, pUSBRXOK, sizeof(pUSBRXOK));
    				}
    				else
    				{
    					ui8BufIndex++;
    					uif(i8BufIndex == (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
    					}
    				}
    			}
    			//...
    		}
    	//...
    	}

    In addition to that, I have implemented a function that generates simulated data for testing: I just copied the function that performs SPI transfers and replaced the part of the code that gets data from the SPI slave device with the following:

    ...
    // TestPattern(uint8_t* pui8RX, uint8_t NoOfTXBytes, uint8_t NoOfRXBytes)
    //
    // Fills array pui8RX with bytes, starting from 0x00 and ending at (NoOfRXBytes-1).
    // Used for TESTING ONLY.
    static void TestPattern(uint8_t* pui8RX, uint8_t NoOfTXBytes, uint8_t NoOfRXBytes)
    {
        uint8_t i = 0;
        uint8_t j = 0;
        uint8_t temp;
        static int8_t step = 1;
        static uint8_t testPattern = 0;
    
        CS_Low();	// set ~CS to logic LOW - start of transmission
        ROM_SysCtlDelay(1);
        while(i < NoOfTXBytes)
        {
        	temp = 0x00;
        	ROM_SSIDataPut(SSI3_BASE, temp);
        	while(SSIBusy(SSI3_BASE));
        	ROM_SSIDataGet(SSI3_BASE, (uint32_t*)&temp);
        	i++;
        }
        while(j < NoOfRXBytes)
        {
        	temp = 0x00;
        	ROM_SSIDataPut(SSI3_BASE, temp);
        	while(SSIBusy(SSI3_BASE));
        	//*(pui8RX+j) = (j == 0) ? testPattern : 0; // for a sawtooth/triangular waveform on X only
        	*(pui8RX+j) = (j % 2 == 0) ? testPattern : 0; // for a sawtooth/triangular waveform on X, Y, Z and T
        	//ROM_SSIDataGet(SSI3_BASE, (uint32_t*)(pui8RX+j)); // This is part of the original SPITransfer() function
        	j++;
        }
        // The following lines result in a triangular waveform
        if(testPattern == 255)
        	step = -1;
        else
        	if(testPattern == 0)
        		step = 1;
    
        testPattern += step;
    
        ROM_SysCtlDelay(1);
        CS_High();	// ~CS - set it to logic HIGH - end of transmission
    }

    Despite my tedious efforts, I have been stuck for almost an entire day on the following issue:

    When I call the TestPattern() function in my while(1) loop, everything works OK; I can capture the data that flow back and forth the USB lines and confirm that they are as they should be.

    However, when I call the SPITransfer() function in my while(1) loop, the uC program hangs when the variable ui8BufIndex has the value of the index of the last element in my multiple buffer. After calling SendDataToHost(), program execution continues with the FaultISR() function, which is an endless loop in my case. Moreover, register NVIC_FAULT_STAT has the value 0x00008200 (Configurable Fault Status [Memory Mapped]) and register NVIC_FAULT_ADDR has the value 0x40000200 (Bus Fault Address [Memory Mapped]). (I checked out for those after having read this, albeit it is kind of "fine print" for me...)

    I cannot think of any apparent reason for having that issue. I do not understand what the SPITransfer() function has to do with the multiple buffer itself.

    Any hints, anyone?

    SPI_demo_USB_20151102.zip

  • Hello George,

    The address 0x40000200 is that of WatchDog0. May be in the code loop there is a reference to an incorrect address. If you can step debug the SPITransfer function, it would tell you where the exact code line which causes the bus fault.

    Regards
    Amit
  • OK Amit, I''l try this tomorrow morning at work and let you know about the result. One more question, though: What could fire the watchdog and halt program execution? Function SPITransfer() has been used from the very beginning of my development and was debugged to work as intended to.

  • Hello George,

    The FaultStat shows a precise bus fault. This would mean that an access to the register space was made by the CPU, either due to typo in the address in the application code or due to a variable that acted as a an address pointer.

    Regards
    Amit
  • Eventually, I managed to find out what was wrong with my code.

    I had not realized that the array of char g_DataBuffer was huge; by browsing through the datasheet and other related documentation, as well as this forum, I realized that I was overflowing my stack. I shrank that array to the absolute minimum and modified my host PC code to do multiple buffering there (where there is a lot of memory available in comparison with my uC) and everything worked OK.

    Another thing: the SPITransfer() function had a serious bug in the reception part. The local variable temp was declared as of type uint8_t, but ROM_SSIDataGet() requires a pointer to uint32_t as second argument. For that reason, I kept on getting another three elements of my buffer filled with 0x00. This is the corrected part:

        while(j < NoOfRXBytes)
        {
        	temp = 0x00;
        	ROM_SSIDataPut(SSI3_BASE, temp);
        	while(SSIBusy(SSI3_BASE));
        	ROM_SSIDataGet(SSI3_BASE, &temp);
        	*(pui8RX+j) = (uint8_t)temp; // explicit casting here - OK
        	//ROM_SSIDataGet(SSI3_BASE, pui8RX+j); // no casting - 4 bytes will be written instead of 1
        	j++;
        }

    Thanks a lot, Amit; you pointed my attention to where the problem was and everything works now.

  • Hello George

    Most welcome. Sometimes the direction change helps

    Regards
    Amit