We are running a survey to help us improve the experience for all of our members. If you see the survey appear, please take the time to tell us about your experience if you can.
(Sorry for my limited English ability.)
I have been assigned to develop an USB based I/O board (software portion). The requirement of this I/O board has never been discussed and never been defined, and the hardware has not been made. So I am working on the LPC23xx evaluation boards. What I have been told is that, this USB based I/O board is made to work with a X86 platform running Linux.
I read some Tsuneo's (Tsuneo Chinzei) articles, and eventually decided to choose the LPCUSB for my LPC2378 evaluation board. The major reasons are: 1. Tsuneo said that it is the best open source project for LPC23xx. 2. Its Linux driver is ready. (The standard one in the Linux kernel.)
I built a Cygwin/YAGARTO GNU ARM toolchain environment to perform some simple tests for LPCUSB. And found that, the main_serial.c works. Then, I migrated the LPCUSB to the KEIL environment, and the main_serial.c still works. But during the migrations and the tests, I found that, or I guessed that, the LPCUSB USB stack is good; however, its examples are only for demonstrating purpose, so they are not well tested.
The major problem for me currently is that, the [static void USBFrameHandler(U16 wFrame)] function does not work properly, because the [fBulkInBusy] variable has never been reset to FALSE. Actually, the real circumstance is: If I send one byte from the X86 desktop to the LPC2378 board, the LPC2378 can send one byte back to the X86 desktop. If I do not send one byte from the X86 desktop to the LPC2378 board, then the LPC2378 board can not send anything to the X86 desktop. I am not so sure that, it is a bug that I have to fix, or it is not a bug, I just need to implement some more USB functions to complete the mechanism.
I don't actually know that, how it works originally. Is that the host sends data with a BulkOut, and this BulkOut invokes a BulkIn? Or the LPC2378 board sends data to the host when it is polled every milisecond by the host? What is the correct way to send data from the LPC2378 board to the host?
Is there any USB-CDC tutorial for a novice? Or could someone please kindly provide some advices?
For me, sometimes, it is difficult to clarify that, is it a bug that I have to fix it; or it is some magic that I should not touch it. For example:
In the usbhw_lpc.c, the original one:
void USBSetHeadDDForDMA(const U8 bEp, volatile U32* udcaHeadArray[32], volatile const U32 *dmaDescriptorPtr) { udcaHeadArray[EP2IDX(bEp)] = (U32) dmaDescriptorPtr; }
KEIL toolchain reports this error:
..\source\usbhw_lpc.c(791): error: #513: a value of type "U32" cannot be assigned to an entity of type "volatile U32 *"
So I change it to:
void USBSetHeadDDForDMA(const U8 bEp, volatile U32* udcaHeadArray[32], volatile const U32 *dmaDescriptorPtr) { udcaHeadArray[EP2IDX(bEp)] = (U32 *) dmaDescriptorPtr; }
Is this a typo? or this is not?
John,
If I do not send one byte from the X86 desktop to the LPC2378 board, then the LPC2378 board can not send anything to the X86 desktop
assuming you are developing a device, it is incorrect to initiate a transaction from the device. the host must take the initiative first.
Note that "Out" and "In" in the device source files refer to the host - thus, "BulkOut" serves data coming into the device from the host, while "BulkIn" serves data transmitted to the host.
USB CDC tutorial provided by Keil's latest MDK can be found at this relative path:
\Keil\ARM\Boards\Keil\MCB2300\USBCDC
In the main_serial.c:
int main(void) { // omitted enableIRQ(); // omitted
In the armVIC.c:
unsigned enableIRQ(void) { unsigned _cpsr; _cpsr = __get_cpsr(); __set_cpsr(_cpsr & ~IRQ_MASK); return _cpsr; } /* I modified the below function to meet the KEIL requirement */ /* Not so sure if my understanding and modification are correct. */ static unsigned int __get_cpsr(void) { unsigned int cpsr_val; __asm ("mrs cpsr_val, cpsr"); return cpsr_val; } /* I modified the below function to meet the KEIL requirement */ /* Not so sure if my understanding and modification are correct. */ static void __set_cpsr(unsigned int cpsr_val) { __asm ("msr cpsr_cxsf, cpsr_val"); }
I think that, calling the enableIRQ() from main() will NOT work, because enableIRQ() needs the privileged modes. am I right?
Is my understanding and modification for __get_cpsr() and __set_cpsr() correct?
You need to make "enableIRQ" a SWI function.
correction: this may not be required. please check the documentation!
Hi Tamir,
Thanks.
I checked the
and confirmed that, it is a sample project.
Do you know any USB-CDC documentation which is simple enough for a novice?
What is the correct way to send data from the USB-CDC device to the host? Only when it is polled every milisecond by the host?
the device is only addressed when the host sends data to it. it is not polled every x milliseocnds unlike MSDs, for instance. operation is simple: device is mapped to a serial port on the host. you only need to change the contents of BulkIn, BulkOut and make sure the baudrate, data bits and stop bits on both sides agree. also, you need to install the provided .inf file.
> What is the correct way to send data from the USB-CDC device to the host? Only when it is polled every milisecond by the host?
CDC device driver polls bulk IN endpoint (Polling IN transactions) repeatedly. It's true for the default CDC driver on most of OS, Windows, Linux, Mac OSX, etc. It's a difference from a generic device driver.
While the bus is not busy, you'll see 30-40 times of IN-NAKs on the bulk IN endpoint in a 1ms USB frame. You have chances to start IN transfer on each IN transaction. But once you finishes the IN transfer (end with short packet), you don't see any IN-NAK until next frame. This behavior makes 1 transfer per 1 frame feature. > If I send one byte from the X86 desktop to the LPC2378 board, the LPC2378 can send one byte back to the X86 desktop. If I do not send one byte from the X86 desktop to the LPC2378 board, then the LPC2378 board can not send anything to the X86 desktop.
The bulk IN endpoint is independent from bulk OUT endpoint. LPCUSB source also doesn't relate them each other. So, it's caused by your custom code. Post your code, which was added by you for above process.
Tsuneo
Hi Tsuneo,
Many thanks for your exposition and help.
I downloaded the source code from
lpcusb.svn.sourceforge.net/.../trunk
in January.
And did some modifications to migrate them to the KEIL environment. (I hope that these modifications didn't bring any side-effect to the LPCUSB.)
After the migration, the code works -> If I send one byte from the X86 desktop to the LPC2378 board, the LPC2378 can send one byte back to the X86 desktop.
The below is the original code, it works:
static void USBFrameHandler(U16 wFrame) { if (!fBulkInBusy && (fifo_avail(&txfifo) != 0)) { // send first packet SendNextBulkIn(BULK_IN_EP, TRUE); } } int main(void) { int c; /* Omitted */ // echo any character received (do USB stuff in interrupt) while (1) { c = VCOM_getchar(); if (c != EOF) { // show on console if ((c == 9) || (c == 10) || (c == 13) || ((c >= 32) && (c <= 126))) { DBG("%c", c); } else { DBG("."); } VCOM_putchar(c+1); } } // return 0; }
The below is the code I further modified, It does not work -> If I do not send one byte from the X86 desktop to the LPC2378 board, then the LPC2378 board can not send anything to the X86 desktop.:
int main(void) { int c = 'A', next; /* Omitted */ // echo any character received (do USB stuff in interrupt) while (1) { next = (txfifo.head + 1) % VCOM_FIFO_SIZE; if (next != txfifo.tail) { VCOM_putchar(c); } } // return 0; }
If I also modify the below portion, then it seems work.
static void USBFrameHandler(U16 wFrame) { // if (!fBulkInBusy && (fifo_avail(&txfifo) != 0)) { if ((fifo_avail(&txfifo) != 0)) { // send first packet SendNextBulkIn(BULK_IN_EP, TRUE); } }
I checked the whole project, and found that the [fBulkInBusy] variable has never been reset to FALSE.
Some related code that I did not modify.
static void SendNextBulkIn(U8 bEP, BOOL fFirstPacket) { int iLen; // this transfer is done fBulkInBusy = FALSE; // first packet? if (fFirstPacket) { fChainDone = FALSE; } // last packet? if (fChainDone) { return; } // get up to MAX_PACKET_SIZE bytes from transmit FIFO into intermediate buffer for (iLen = 0; iLen < MAX_PACKET_SIZE; iLen++) { if (!fifo_get(&txfifo, &abBulkBuf[iLen])) { break; } } // send over USB USBHwEPWrite(bEP, abBulkBuf, iLen); fBulkInBusy = TRUE; // was this a short packet? if (iLen < MAX_PACKET_SIZE) { fChainDone = TRUE; } } static void BulkIn(U8 bEP, U8 bEPStatus) { SendNextBulkIn(bEP, FALSE); }
I found 3 [ fBulkInBusy = FALSE; ] totally.
1. fBulkInBusy will soon be set to TRUE.
static void SendNextBulkIn(U8 bEP, BOOL fFirstPacket) { int iLen; // this transfer is done fBulkInBusy = FALSE; /* Omitted */ // send over USB USBHwEPWrite(bEP, abBulkBuf, iLen); fBulkInBusy = TRUE; // was this a short packet? if (iLen < MAX_PACKET_SIZE) { fChainDone = TRUE; } }
2. VCOM_init() is called only once.
void VCOM_init(void) { fifo_init(&txfifo, txdata); fifo_init(&rxfifo, rxdata); fBulkInBusy = FALSE; fChainDone = TRUE; }
3.
/** USB device status handler Resets state machine when a USB reset is received. */ static void USBDevIntHandler(U8 bDevStatus) { if ((bDevStatus & DEV_STATUS_RESET) != 0) { fBulkInBusy = FALSE; } }
I don't know how to implement a proper USB function to reset the fBulkInBusy to FALSE.
You are fiddling the example in half understanding. Then, it doesn't work as you intend. Before fiddling the example, realize the background structure well.
The CDC example implements FIFOs (ring buffers) upon raw IN/OUT endpoints (EPs).
For raw IN/OUT EPs, 1) raw IN EP, - USBHwEPWrite() writes to a data packet (up to 64 bytes) into the EP buffer. - When the EP finishes to send the packet, a callback routine registered by USBHwRegisterEPIntHandler(BULK_IN_EP, [i]BulkIn[/i]); is called.
2) raw OUT EP, - When the EP finishes to receive a packet from host, a callback routine registered by USBHwRegisterEPIntHandler(BULK_OUT_EP, [i]BulkOut[/i]); is called. - Firmware reads out the packet using USBHwEPRead()
That's all. For the data exchange of short-size packet, you'll use the raw EP directly.
For the FIFOs, 3) As of the device --> host direction, - VCOM_putchar() puts a byte of data into the txfifo - USBFrameHandler() (registered by USBHwRegisterFrameHandler(USBFrameHandler);) polls txfifo at every 1ms SOF timing. If any data on the txfifo, USBFrameHandler() puts a packet (up to 64 bytes) into the IN EP. - When the IN EP finishes to send the packet, BulkIn() is called (registered by USBHwRegisterEPIntHandler(BULK_IN_EP,)). And then, SendNextBulkIn() is called. If any other data still remains on txfifo, SendNextBulkIn() puts another packet to the IN EP. OR, if the last packet ends up with full-size packet (64 bytes), SendNextBulkIn() puts ZLP (Zero-Length Packet) to the IN EP, to terminate the transfer.
4) As of the host --> device direction, - When the OUT EP finishes to receive a packet, BulkOut() (registered by USBHwRegisterEPIntHandler(BULK_OUT_EP, BulkOut);) is called. - BulkOut() unloads the packet from the OUT EP to rxfifo - VCOM_getchar() reads out one byte of data from rxfifo, if any.
Lastly, the LPCUSB example detects bus reset using USBDevIntHandler() and USBHwRegisterDevIntHandler(USBDevIntHandler);
In above bus reset handler, a user flag, fBulkInBusy, is initialized. This scheme also works in usual enumeration. But in good USB practice, this kind of initialization is placed in Set_Configuration handler.
As for the USB term, Transfer - Transaction - Packet, read this post. www.cygnal.org/.../001627.html
I am studying the precious information you provided.
Sorry for my limited technique ability.
Now, I notice that I am a little confused with some USB term.
Copied from [USB Made Simple]
If an IN endpoint is defined as using Bulk transfers, then the host will transfer data from it using IN transactions.
Interrupt transfers are regularly scheduled IN or OUT transactions, although the IN direction is the more common usage.
So there is NO "Bulk IN transactions" nor "Interrupt IN transactions"; they are the same "IN transactions".
Am I right?
CDC device driver polls bulk IN endpoint (Polling IN transactions) repeatedly.
Then a "raw IN EP" (not a "bulk IN EP", right?) responses to the host with DATA0/DATA1 Packet.
When the EP finishes to send the packet, a callback routine registered by USBHwRegisterEPIntHandler(BULK_IN_EP, BulkIn); is called.
Since it is a "raw IN EP", why it is called BULK_IN_EP? and why the function is called BulkIn? why the function is called after the DATA Packet had been sent? why not called before the DATA Packet had been sent? I mean that, what is the real role of this function?
The original code works, because BulkIn function is called after the DATA Packet had been sent, so the fBulkInBusy is set to FALSE. But I just can't understand the type of the "IN EP", and the naming/usage of the callback routine. Does every USB-CDC device follows this flow?
why the function is called after the DATA Packet had been sent? why not called before the DATA Packet had been sent?
as stated above, IN is in terms of the host, thus data flowing from the device to the host. it is AFTER the packet has been sent because it has to be: FIRST, you write something in reply to the host, and then BulkIn gets called to give you the chance to deliver more data. that's how it works. BulkIn will never be called unless you first wrote something to the bus!
and I don't think your technique is an issue at all - quite the contrary. your questions indicate depth and an eager to learn, certainly in comparisons the common "do my homework" poster!