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:
- Copying the LPC11Uxx build configuration
- Changing "Properties -> C/C++ Build -> Settings -> MCU C Compiler -> Symbols" to define
__LPC13UXX__
and__USE_CMSIS=CMSISv2p00_LPC13Uxx
. - 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. - Changing the device under "Properties -> C/C++ Build -> MCU settings" to LPC1347.
- Editing the file nxpUSBlib/nxpUSBlibConfig.h to define
USE_USB_ROM_STACK
to1
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 &= (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 &= (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.
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.