jenswilly.dk


nxpUSBlib on LPC1347

Since I had some problems gettings USB CDC working I decided to try using NXP's nxpUSBlib instead of the more simple examples that I only got almost working. But I ran into a couple of problems...

LPC1347 support

The first problem was getting the thing compiling for the LPC1347. The most recent version (0.97) does support the LPC1347 but not quite.

Having downloaded the archive and imported everything into Code Red LPCXpresso IDE as per the readme, I tried to configure the BSP (development board), CDL (CMSIS and device) and nxpUSBlib projects for the LPC1347 MCU. No problems.
But when I tried to select the LPC1347 for the Example_VirtualSerialDevice project it turned out that there was no such build configuration. There is a LPC13Uxx configuration for the Example_MassStorageDevice, however.

So the first task was to create a suitable build configuration for the LPC1347 for the Example_VirtualSerialDevice which involved:

  1. Copying the LPC11Uxx build configuration
  2. Changing "Properties -> C/C++ Build -> Settings -> MCU C Compiler -> Symbols" to define __LPC13UXX__ and __USE_CMSIS=CMSISv2p00_LPC13Uxx.
  3. Changing "[same config path as above] -> Includes" to use "${workspace_loc:/CDL/CMSISv2p10_LPC13Uxx/inc}" and "${workspace_loc:/CDL/CMSISv2p10_LPC13Uxx/lpcinc}" instead of the LPC11Uxx ones.
  4. Changing the device under "Properties -> C/C++ Build -> MCU settings" to LPC1347.
  5. Editing the file nxpUSBlib/nxpUSBlibConfig.h to define USE_USB_ROM_STACK to 1 in order to take advantage of the ROM-based USB drivers.

And that got it compiling.

Problem with buffer in UsbdCdc_Bulk_OUT_Hdlr

The Example_VirtualSerialDevice is supposed to echo whatever is received on the USB CDC. So if I connect to device it should just echo back whatever I type. Which worked fine with the first character I typed but any subsequent character was echoed back as garbled nonsense characters. Dangit.

After equal parts scratching my head and wishing I had hair to pull out and perusal of the source code I started wondering about this part of the code in the file libraries/nxpUSBLib/Drivers/USB/Core/LPC/DCD/USBRom/usbd_cdc.c:

ErrorCode_t UsbdCdc_Bulk_OUT_Hdlr(USBD_HANDLE_T hUsb, void* data, uint32_t event)
{
    uint8_t EpAdd = USB_ENDPOINT_OUT(CALLBACK_UsbdCdc_Register_DataOutEndpointNumber());
    if (event == USB_EVT_OUT)
    {
        UsbdCdc_EPOUT_buffer_count = USBD_API->hw->ReadEP(UsbHandle, EpAdd, UsbdCdc_EPOUT_buffer);
    }
    else if(event == USB_EVT_OUT_NAK)
    {
        if(UsbdCdc_EPOUT_buffer_count == 0)
        {
            /* Reset EP OUT buffer */
            UsbdCdc_EPOUT_buffer_index = 0;
            /* Send DMA request */
            USBD_API->hw->ReadReqEP(UsbHandle, EpAdd, UsbdCdc_EPOUT_buffer, CDC_EP_SIZE);
        }
    }
    return LPC_OK;
}

This is the code that is called whenever the host (i.e. the computer) sends data to the MCU.
For both USB_EVT_OUT and USB_EVT_NAK events, data is read into the UsbdCdc_EPOUT_buffer buffer at offset 0. But you will notice that the UsbdCdc_EPOUT_buffer_index is only reset to 0 for USB_EVT_OUT_NAK events.

This is a problem because the data in UsbdCdc_EPOUT_buffer is copied into the UsbdCdc_OUT_buffer buffer in UsbdCdc_IO_Buffer_Sync_Task() using this code:

for(i=0; i<avail_count; i++)
{
    UsbdCdc_OUT_buffer.data[UsbdCdc_OUT_buffer.wr_index] = UsbdCdc_EPOUT_buffer[UsbdCdc_EPOUT_buffer_index];
    UsbdCdc_EPOUT_buffer_index++;

    UsbdCdc_OUT_buffer.count++;
    UsbdCdc_OUT_buffer.wr_index++;
    UsbdCdc_OUT_buffer.wr_index &amp;= (ROMDRIVER_CDC_DATA_BUFFER_SIZE - 1);
    /* Sync 2 buffers must be implemented when all other tasks completed */
    UsbdCdc_EPOUT_buffer_count--;
}

In other words, when data is received with a USB_EVT_OUT event, that data is read into UsbdCdc_EPOUT_buffer at offset 0 but the sync method increments UsbdCdc_EPOUT_buffer_index every time a character is copied into UsbdCdc_OUT_buffer. And since the buffer index is not reset, it will copy garbage data. Double-dangit.

The fix

I changed the UsbdCdc_Bulk_OUT_Hdlr() to this:

ErrorCode_t UsbdCdc_Bulk_OUT_Hdlr(USBD_HANDLE_T hUsb, void* data, uint32_t event)
{
    uint8_t EpAdd = USB_ENDPOINT_OUT(CALLBACK_UsbdCdc_Register_DataOutEndpointNumber());
    if (event == USB_EVT_OUT)
    {
        // Reset the buffer index since data is read into UsbdCdc_EPOUT_buffer at offset 0
        UsbdCdc_EPOUT_buffer_index = 0;
        UsbdCdc_EPOUT_buffer_count = USBD_API->hw->ReadEP(UsbHandle, EpAdd, UsbdCdc_EPOUT_buffer);
    }
    else if(event == USB_EVT_OUT_NAK)
    {
        if(UsbdCdc_EPOUT_buffer_count == 0)
        {
            /* Reset EP OUT buffer */
            UsbdCdc_EPOUT_buffer_index = 0;
            /* Send DMA request */
            USBD_API->hw->ReadReqEP(UsbHandle, EpAdd, UsbdCdc_EPOUT_buffer, CDC_EP_SIZE);
        }
    }
    return LPC_OK;
}

And then everything was echoed back just fine.

Problem sending more than 64 bytes

And then I ran into the next problem when I tried to send back a GPS status string. Again with the scratching of the head and the perusal of the USB code and some debugging. Which led me to look at this code in UsbdCdc_SendData():

while(cnt)
{
    if(UsbdCdc_IN_buffer.count <= ROMDRIVER_CDC_DATA_BUFFER_SIZE)
    {
        UsbdCdc_IN_buffer.data[UsbdCdc_IN_buffer.wr_index] = buffer[buffer_index++];
        UsbdCdc_IN_buffer.wr_index++;
        UsbdCdc_IN_buffer.wr_index &amp;= (ROMDRIVER_CDC_DATA_BUFFER_SIZE -1);
        UsbdCdc_IN_buffer.count++;
        cnt--;
    }
}

This code copies data from the source data buffer (buffer) into the UsbdCdc_IN_buffer from where it will be copied into UsbdCdc_EPIN_buffer in UsbdCdc_IO_Buffer_Sync_Task() and from that buffer it will be sent to the USB endpoint in UsbdCdc_Bulk_IN_Hdlr().

The problem with this code is that if we're trying to send more than there is currently room for in the buffer – that is if UsbdCdc_IN_buffer.count exceeds ROMDRIVER_CDC_DATA_BUFFER_SIZE bytes (which is defined as 64) – the if-statement is false and cnt will not be decreased further and thus the while-loop will just keep looping.
And since the UsbdCdc_IN_buffer is emptied in UsbdCdc_IO_Buffer_Sync_Task() which is called from the main loop, the buffer is never emptied and the loop never exits. So the problem was simply that the GPS status string was longer than 64 bytes. Triple-dangit.

The easy fix

The easy way to fix this is simply to increase the size of the UsbdCdc_IN_buffer so we'll never try to write more than the buffer can hold. I'll try that first.
This ought to work since the UsbdCdc_EPIN_buffer is already 512 bytes large.

The better fix

A better way of fixing this would involve emptying the buffer in interrupt context instead of doing it from the main loop. That way the looping while-loop will be interrupted and UsbdCdc_IN_buffer.count will be decreased as the buffer is copied to the UsbdCdc_EPIN_buffer buffer and then more data can be copied into UsbdCdc_IN_buffer.count. I'll try that next.

Update: it looks like the easy fix works. I would still like to fix it the better way though.


One comment on "nxpUSBlib on LPC1347"
Hens says:

Isn't this driver code that you changed just a copy of what is burned into the device ROM? So, if you define USE_USB_ROM_STACK, it seems that none of your changes will ever be seen/run.

Leave a Reply