I'm implementing an HID class device and have based my program on the HID USB program supplied by Keil. I'm new to USB but am a fairly experienced programmer otherwise.
I have got a basic program working fine bit with single byte input and output reports. I've tested the functionality using a client called SimpleHIDWrite.exe
I need to expand both in and out reports to 8 bytes each to communicate with the host. Has anybody successfully modified this example program or does anyone have any advice on how to do it properly?
My guess is that I need to edit the report descriptor and also set up the inreport and outreport as arrays. Is there anything I need to watch out for?
My target is the LPC2141.
Any advice or information would be much appreciated!
Thanks, Gareth.
In order to put out InReports as and when required I need to set up an IN EP (interrupt) on Endpoint 1 - with an event handler like the one in the lower code sample?
An interrupt IN EP is mandatory for every HID device implementation. Input reports are sent over this EP.
>I just need to properly understand what mechanism prompts the system to generate the interrupt which causes the report to be transferred to the host.
As USB is host centric, devices don't have any method to send data actively. Just when an IN transaction comes from host, the device gets chance to send data. This is the reason why PC HID class driver polls the interrupt IN EP repeatedly by IN transactions.
The sequence to send an input report is as follows, 1) First, firmware fills the IN endpoint buffer with an input report using USB_WriteEP() call 2) The USB engine waits for an IN transaction from host 3) When an IN transaction comes, the engine sends the data on the endpoint buffer 4) When the IN transaction completes, the engine generates a hardware interrupt on this EP 5) RL-ARM library interprets the hardware interrupt to an event
Please note, the EP hardware interrupt occurs just after the transaction finishes successfully (data is sent). The hardware interrupt doesn't occur when the IN endpoint buffer is empty on transaction, and the engine returns NAK. ie. the IN EP hardware interrupt means the timing when the endpoint buffer becomes empty right now.
Then, when are we allowed to put an input report to the EP buffer? The answer is any time, unless the EP is occupied by the last report. In above snippet, the IN EP event is used just to release the semaphore, to notify that the IN EP is available now. Also, your firmware can put an input report everywhere on your code, where required.
Tsuneo
Hi Tsuneo,
I implemented the changes but it does not seem to be working quite right. I used SimpleHIDWrite to test the new application (loop back on EP1). I hope this is okay? The repeated IN reports have stopped - in fact I did not appear to be receiving any reports at the PC. What I expected to see was the written command followed by a single identical report being read back from the device. I could see the written report but no read report - hence my assessment that the changes are not working ...
I am working with 8 byte packets IN and OUT so I have ...
usbcfg.h #define USB_MAX_PACKET0 8
usbdesc.c /* USB Configuration Descriptor */ /* All Descriptors (Configuration, Interface, Endpoint, Class, Vendor) */ const U8 USB_ConfigDescriptor[] = { /* Configuration 1 */ USB_CONFIGUARTION_DESC_SIZE, /* bLength */ USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType */ WBVAL( /* wTotalLength */ USB_CONFIGUARTION_DESC_SIZE + USB_INTERFACE_DESC_SIZE + HID_DESC_SIZE + USB_ENDPOINT_DESC_SIZE + // added USB_ENDPOINT_DESC_SIZE // added ), 0x01, /* bNumInterfaces */ 0x01, /* bConfigurationValue: 0x01 is used to select this configuration */ 0x00, /* iConfiguration: no string to describe this configuration */ USB_CONFIG_BUS_POWERED /*|*/ /* bmAttributes */ /*USB_CONFIG_REMOTE_WAKEUP*/, USB_CONFIG_POWER_MA(100), /* bMaxPower, device power consumption is 100 mA */ /* Interface 0, Alternate Setting 0, HID Class */ USB_INTERFACE_DESC_SIZE, /* bLength */ USB_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType */ 0x00, /* bInterfaceNumber */ 0x00, /* bAlternateSetting */ // 0x01, /* bNumEndpoints */ 0x02, /* bNumEndpoints */ USB_DEVICE_CLASS_HUMAN_INTERFACE, /* bInterfaceClass */ HID_SUBCLASS_NONE, /* bInterfaceSubClass */ HID_PROTOCOL_NONE, /* bInterfaceProtocol */ 0x04, /* iInterface */ /* HID Class Descriptor */ /* HID_DESC_OFFSET = 0x0012 */ HID_DESC_SIZE, /* bLength */ HID_HID_DESCRIPTOR_TYPE, /* bDescriptorType */ WBVAL(0x0100), /* 1.00 */ /* bcdHID */ 0x00, /* bCountryCode */ 0x01, /* bNumDescriptors */ HID_REPORT_DESCRIPTOR_TYPE, /* bDescriptorType */ WBVAL(HID_REPORT_DESC_SIZE), /* wDescriptorLength */ /* Endpoint, HID Interrupt In */ USB_ENDPOINT_DESC_SIZE, /* bLength */ USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */ USB_ENDPOINT_IN(1), /* bEndpointAddress */ USB_ENDPOINT_TYPE_INTERRUPT, /* bmAttributes */ // WBVAL(0x0004), /* wMaxPacketSize */ WBVAL(0x0008), /* wMaxPacketSize */ 0x20, /* 32ms */ /* bInterval */ // 0x01, /* 1ms */ /* bInterval */ /* Endpoint, HID Interrupt Out */ USB_ENDPOINT_DESC_SIZE, /* bLength */ USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */ USB_ENDPOINT_OUT(1), /* bEndpointAddress */ USB_ENDPOINT_TYPE_INTERRUPT, /* bmAttributes */ // WBVAL(0x0004), /* wMaxPacketSize */ WBVAL(0x0008), /* wMaxPacketSize */ 0x20, /* 32ms */ /* bInterval */ // 0x01, /* 1ms */ /* bInterval */ /* Terminator */ 0 /* bLength */ };
usbuser.c static OS_SEM USB_IN_EP1_Semaphore; // semaphore for IN EP1 /* * USB Endpoint 1 Task * Handles USB Endpoint 1 Events */ #if (USB_EP_EVENT & (1 << 1)) __task void USB_EndPoint1 (void) { U16 evt; U32 report_size; // added for (;;) { os_evt_wait_or(0xFFFF, 0xFFFF); /* Wait for an Event */ evt = os_evt_get(); /* Get Event Flags */ if (evt & USB_EVT_OUT) { // OUT EP1 interrupt comes report_size = USB_ReadEP( HID_EP_OUT, &OutReport[0] ); // read out the output report // usually, your firmware parse the output report, here // in this snippet, just copy output report to input one directly, for loopback // SetOutReport(); memcpy( InReport, OutReport, report_size ); // send an input report to IN EP os_sem_wait( USB_IN_EP1_Semaphore, 0xffff ); // wait until IN EP is available // USB_WriteEP( HID_EP_IN, InReport, HID_INPUT_REPORT_BYTES ); // pass it to the endpoint USB_WriteEP( HID_EP_IN, InReport, sizeof(InReport) ); // pass it to the endpoint } if (evt & USB_EVT_IN) { os_sem_send( USB_IN_EP1_Semaphore ); // IN EP1 is free /* GetInReport(); // USB_WriteEP(HID_EP_IN, &InReport, sizeof(InReport)); USB_WriteEP(HID_EP_IN, &InReport[0], sizeof(InReport)); */ } } } #endif /* * USB Core Task * Handles USB Core Events */ __task void USB_Core (void) { #if (USB_CONFIGURE_EVENT || USB_INTERFACE_EVENT || USB_FEATURE_EVENT) U16 evt; #endif for (;;) { os_evt_wait_or(0xFFFF, 0xFFFF); /* Wait for an Event */ #if (USB_CONFIGURE_EVENT || USB_INTERFACE_EVENT || USB_FEATURE_EVENT) evt = os_evt_get(); /* Get Event Flags */ #endif #if USB_CONFIGURE_EVENT if (evt & USB_EVT_SET_CFG) { if (USB_Configuration) { /* Check if USB is configured */ os_sem_init( USB_IN_EP1_Semaphore, 1 ); // IN EP1 is available now /* GetInReport(); // USB_WriteEP(HID_EP_IN, &InReport, sizeof(InReport)); USB_WriteEP(HID_EP_IN, &InReport[0], sizeof(InReport)); */ } } #endif #if USB_INTERFACE_EVENT if (evt & USB_EVT_SET_IF) { } #endif #if USB_FEATURE_EVENT if (evt & USB_EVT_SET_FEATURE) { } if (evt & USB_EVT_CLR_FEATURE) { } #endif } }
hiduser.c BOOL HID_GetReport (void) { /* ReportID = SetupPacket.wValue.WB.L; */ switch (SetupPacket.wValue.WB.H) { case HID_REPORT_INPUT: // GetInReport(); // memcpy(EP0Buf,InReport,sizeof(InReport)); memcpy( EP0Buf, InReport, SetupPacket.wLength ); break; case HID_REPORT_OUTPUT: return (__FALSE); /* Not Supported */ case HID_REPORT_FEATURE: /* EP0Buf[] = ...; */ /* break; */ return (__FALSE); /* Not Supported */ } return (__TRUE); } BOOL HID_SetReport (void) { /* ReportID = SetupPacket.wValue.WB.L; */ switch (SetupPacket.wValue.WB.H) { case HID_REPORT_INPUT: return (__FALSE); /* Not Supported */ case HID_REPORT_OUTPUT: // memcpy(OutReport,EP0Buf,sizeof(OutReport)); memcpy( OutReport, EP0Buf, SetupPacket.wLength ); // SetOutReport(); break; case HID_REPORT_FEATURE: return (__FALSE); /* Not Supported */ } return (__TRUE); }
I did not implement the feature report handling at this stage. I commented everything out in SetOutReport and GetInReport I will continue to check over the code but if you can spot anything obvious or perhaps point me in a certain direction that would be great. I've probably done something silly or missed something! Or possibly SimpleHODWRite not the right program for testing? But I think it should work okay? Many thanks in advance! Gareth.
Gareth,
what compiler for PC are you using? I can send you my bare HID testprogram built with VC6
basically, after device detection and FileOpen it is just a matter of WriteFile/ReadFile, nothing else.
In case you want it, just send me a message via my contact form in order to transfer your mail address. you can find the form if you just take my last name and add .de
regards Ulrich
Hi Tsuneo (and Ulrich)
Thank you for both taking the trouble to read this thread! As you can probably tell - I am a little new to programming for USB and just knowing someone is out there watching is a great comfort!!!
I am delighted to say that I've worked through everything again (and again ...) and I seem to have solved my problems (for now).
a) I was not using the array pointer properly when filling IN EP1 (cut and paste error - doh!!) b) also I may have been using the 'Set Report' command rather then 'Write' command in SimpleHIDWrite program ...
Anyway the main problem was the line of code which sent the copied InReport to the EP.
So I now have looping reports working properly with only one IN report send to the PC on receipt of an OUT report from the PC!!
HOORAY! It's a good step as I now have full control of when IN reports are sent. I now just have to fit the exact functionality required for my project.
I will keep you posted as to how I'm getting on!
MANY THANKS ONCE AGAIN! Best regards, Gareth.
Following my success earlier today I have sorted out the information requests from the host - everything seems to be working very nicely!
I'm about to start work on sending reports when information changes ... In your example snippet you show a dedicated task. I assume that it is okay to place this task pretty much anywhere?
I was going to create a task (which repeats, say every 50ms) for monitoring variable changes and place this in the main part of the application program separate from the USB parts. I assume it's okay to do this? It looks like the use of the semaphore which is set every IN request allows for 'remote' processing of the EP1.
I plan to implement this tomorrow. Any comments you might have would be very welcome!
Congratulation!! I'm glad you got good start of your USB life ;-)
> I assume that it is okay to place this task pretty much anywhere?
It's right.