I want to develop mass storage device using LPC2148.I am just referring code from Keil for USBMem. My problem is - I get Get_descriptor command from PC to which I respond with device descriptor. On receiving 8th byte it generates Reset. Then I get Set_address command. On response to it, I execute Set Address command in protocol engine. Then again I get Get_descriptor command (It should actually have index field of 18 but I receive with 64). Again after 8th byte it generates Reset and again gives Set_address command instead of Get_descriptor command for Configuration descriptor. What can be the problem?
Ok. I am trying to debug my program now. May be something is wrong with it. Will let u know as soon as I find it.
In LPC2148_USBMem example, instead of setting address of device on reception of Set_address command, they are setting it, on reception of IN packet. Is there any reason for that? I am setting it on reception of Set_address command only.
I'll show you the implementation details of Set_Address request handling, using two successful examples.
1) The first one is taken from KEIL implementation. KEIL follows traditional way, switch the device address after STATUS stage of Set_Address request - (USB_EndPoint0() - USB_EVT_SETUP - USB_REQUEST_SET_ADDRESS) At the SETUP stage handling, after parsing it as Set_Address request, - (USB_ReqSetAddress()) the device address is kept in a variable, USB_DeviceAddress. A flag on USB_DeviceAddress is enabled to show further process is required. - (USB_EndPoint0() - USB_EVT_IN) At the STATUS stage of Set_Address request, the firmware registers USB_DeviceAddress to the USB engine. SET_ADDRESS command is passed to the USB engine twice, to make it effective immediately (specific to NXP LPC family)
I suppose that KEIL has chosen this implementation for stack compatibility of other USB engine than NXP LPC family, like STM32, etc.
Excerpt from KEIL USB device code usbcore.c /* * USB Endpoint 0 Event Callback * Parameters: event * Return Value: none */ void USB_EndPoint0 (U32 event) { switch (event) { case USB_EVT_SETUP: // <----- SETUP transaction comes USB_SetupStage(); USB_DirCtrlEP(SetupPacket.bmRequestType.BM.Dir); EP0Data.Count = SetupPacket.wLength; /* Number of bytes to transfer */ switch (SetupPacket.bmRequestType.BM.Type) { case REQUEST_STANDARD: switch (SetupPacket.bRequest) { case USB_REQUEST_SET_ADDRESS: // <----- it is Set_Address request if (!USB_ReqSetAddress()) { // <----- pass it to Set_Address handler goto stall_i; } USB_StatusInStage(); break; ... ... case USB_EVT_IN : // <----- IN transaction just finishes if (SetupPacket.bmRequestType.BM.Dir == REQUEST_DEVICE_TO_HOST) { USB_DataInStage(); /* send data */ } else { // <----- this IN transaction is STATUS stage if (USB_DeviceAddress & 0x80) { // <----- process specific to STATUS stage of Set_Address USB_DeviceAddress &= 0x7F; // <----- recover original device address, masking flag bit USB_SetAddress(USB_DeviceAddress); // <----- set the address to the engine } } break; /* end case USB_EVT_IN */ ... ... /* * Set Address USB Request * Parameters: None (global SetupPacket) * Return Value: TRUE - Success, FALSE - Error */ __inline BOOL USB_ReqSetAddress (void) { switch (SetupPacket.bmRequestType.BM.Recipient) { case REQUEST_TO_DEVICE: USB_DeviceAddress = 0x80 | SetupPacket.wValue.WB.L; // <----- the address is kept on a variable with flag break; default: return (__FALSE); } return (__TRUE); } usbhw.c /* * USB Set Address Function * Parameters: adr: USB Address * Return Value: None */ void USB_SetAddress (U32 adr) { // <----- set the address to the engine, twice WrCmdDat(CMD_SET_ADDR, DAT_WR_BYTE(DEV_EN | adr)); /* Don't wait for next */ WrCmdDat(CMD_SET_ADDR, DAT_WR_BYTE(DEV_EN | adr)); /* Setup Status Phase */ }
2) The second one comes from LPCUSB by Bertrik LPCUSB registers the device address directly to the engine at the SETUP stage of Set_Address handling before STATUS stage comes, like other request handling. This way is allowed for NXP LPC family, thanking to the feature specific to LPC USB engine. In this case, SET ADDRESS command to the engine is just once.
Excerpt from LPCUSB lpcusb.svn.sourceforge.net/.../usbstdreq.c /** Local function to handle a standard device request @param [in] pSetup The setup packet @param [in,out] *piLen Pointer to data length @param [in,out] ppbData Data buffer. @return TRUE if the request was handled successfully */ static BOOL HandleStdDeviceReq(TSetupPacket *pSetup, int *piLen, U8 **ppbData) { U8 *pbData = *ppbData; switch (pSetup->bRequest) { case REQ_GET_STATUS: // bit 0: self-powered // bit 1: remote wakeup = not supported pbData[0] = 0; pbData[1] = 0; *piLen = 2; break; case REQ_SET_ADDRESS: // <------- Set_Address request handler USBHwSetAddress(pSetup->wValue); // <------- Set address to the engine before STATUS stage break; // <------- like handling of other requests case REQ_GET_DESCRIPTOR: DBG("D%x", pSetup->wValue); return USBGetDescriptor(pSetup->wValue, pSetup->wIndex, piLen, ppbData); lpcusb.svn.sourceforge.net/.../usbhw_lpc.c /** Sets the USB address. @param [in] bAddr Device address to set */ void USBHwSetAddress(U8 bAddr) { USBHwCmdWrite(CMD_DEV_SET_ADDRESS, DEV_EN | bAddr); // <------- set the address to the engine, once }
Both ways work. But which one is better for LPC family? I take LPCUSB way. The reason is two-fold, 1) It gives simpler code. 2) If we would take the traditional coding like KEIL, there is a chance that we miss the next request from host when the USB interrupt is disturbed by another higher-priority interrupts. The USB engine of NXP LPC family is designed to prevent this accident. There is no reason to stick to the traditional coding, wasting up NXP effort ;-)
Tsuneo
Actually, this can be a silly question, but in Keil program, why they don't face problem of interrupt within interrupt? When initially I get Get_descriptor command, if i set TxPLen=18, then I don't get Reset generated by host after reception of 8th byte because when reset occurs I am in ISR only, busy writing 9th to 18th bytes. To avoid it when I get Get_descriptor command for 1st time, I set TxPLen=08 only. Can there be any better solution or is there any mistake from my side in understanding the program?
Actually, this can be a silly question, but in Keil program, why they don't face problem of interrupt within interrupt?
Because nested interrupts are disabled by default. This is not a silly question; it just shows that you need to read somewhat more. Try an ARM (7) architecture guide.
"Because nested interrupts are disabled by default."
They're not disabled; standard ARM(7) architecture doesn't have direct support per se. You have to insert code to provide the facility.
> When initially I get Get_descriptor command, if i set TxPLen=18, then I don't get Reset generated by host after reception of 8th byte because when reset occurs I am in ISR only, busy writing 9th to 18th bytes.
1) How many bytes does the host request in the wLength field of the SETUP data of the Get_Descriptor request ? 2) How many bytes do you return in the bMaxPacketSize0 field of the device descriptor ? Post your device descriptor.
> How many bytes does the host request in the wLength field of the SETUP data of the Get_Descriptor request? 0x40. > How many bytes do you return in the bMaxPacketSize0 field of the device descriptor ? 0x40. Here is my device descriptor -
const unsigned char USB_DeviceDescriptor[] = { 0x12, /* bLength */ 0x01, /* bDescriptorType */ 0x00,0x02, /* 2.0 */ /* bcdUSB */ 0x00, /* bDeviceClass */ 0x00, /* bDeviceSubClass */ 0x00, /* bDeviceProtocol */ 0x40, /* bMaxPacketSize0 */ 0x51,0xc2, /* idVendor */ 0x03,0x13, /* idProduct */ 0x00,0x02, /* 2.0 */ /* bcdDevice */ 0x00, /* iManufacturer */ 0x00, /* iProduct */ 0x00, /* iSerialNumber */ 0x01, /* bNumConfigurations */ };
Your descriptor is fine.
Did you set up this bMaxPacketSize0 (0x40) to the USB engine?
usbhw.c void USB_Reset (void) { EP_INDEX = 0; // <------ setup bMaxPacketSize0 to EP0 IN/OUT of USB engine MAXPACKET_SIZE = USB_MAX_PACKET0; EP_INDEX = 1; MAXPACKET_SIZE = USB_MAX_PACKET0; while ((DEV_INT_STAT & EP_RLZED_INT) == 0);
Maybe, you have trouble on the handling of USB protocol engine commands.
While your firmware is writing to the descriptor to the default IN EP, host never reads it, because the data is not validated yet. When your firmware puts Validate Buffer command, the data is passed to the endpoint. As your writing process to the endpoint is disturbed, you miss some sequence to the USB protocol engine.
I recommend you to examine the successful implementations, like KEIL and LPCUSB, for the usage details of protocol engine commands. Compare these routines with your code.
KEIL - usbhw.c: USB_Reset(), USB_ReadEP(), USB_WriteEP(), USB_ISR()
The USB engine (SIE) of LPC family is a little difficult for USB starters to write low-level USB stack. You have to synchronize to the USB protocol engine everywhere, not just fiddle the USB registers.
Isn't it better to start with easier USB engine? Like SiLabs C8051F32x/34x, Atmel AT90USB, ST Micro STM32, etc. But I don't recommend EZ-USB for USB study, because it's too easy :-)
Tsuneo,
Sorry....Out of topic:
You should write a book about USB specifically in MCUs.
I know there are some good books about General USB, like: "USB Complete". But I think that your knowledge and excellent communication skills could produce a really good book.
Didn't you think about it?
Tsuneo, I agree! Bradford
http://www.keil.com/forum/docs/thread16320.asp
I recommend you to examine the successful implementations. KEIL - usbhw.c: USB_Reset(), USB_ReadEP(), USB_WriteEP(), USB_ISR() Actually i have referred them and understood them. I checked my routines too. They are same only. That's why i am getting confused because I am not getting where the problem is. I will let you know as soon as I get anything.
For last two weeks, stuck at the same position. I think the problem occurs during handling Set_address command only. Tried by implementing all ways for setting address. But still trying to find the bug.
Is it your custom board, or one of dev board made by some manufacturer?
Run above KEIL example on your board first, to rule out any hardware problem of the board. KEIL example is written for their MCB2140 board. You may apply minor changes to the example code, because of the hardware difference.
MCB2140 schematic http://www.keil.com/mcb2140/mcb2140-schematics.pdf
The check points are,
1) Crystal frequency and PLL setting MCB2140 mounts 12MHz crystal. For other frequency, modify PLL setting. usbhw.c USB_Init() OTG_CLK_CTRL
2) CONNECT control and VBUS detection pins MCB2140 apply a fixed D+ pull-up resistor (R35, 1.5k) without CONNECT pin (P0.31) control. VBUS detection on P0.23 is optional (J15) If your board has these feature, modify PINSEL setting. usbhw.c USB_Init() PINSEL3/4
With these short modifications, KEIL example should work on your board. If it doesn't work, check your hardware.
Anyway, having a working example on your board will encourage you so much ;-) And then, it also works as a good reference for your original USB stack.
Got finally!!! Thanks for your constant support! By implementing Bettrick's method, I got the Get_descriptor command with length field 18. Challenge - Still not getting Get_descriptor for configuration descriptor. Can it be problem of Vendor id or product id? I am not using Keil board. It's board designed by local company.
Actually I am using LPC2148 CPU module designed by local company. Can I use vendor id and product id by Keil?