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.
Fantastic response Tsuneo - many thanks!
I actually ended up working out how to do it on my own and it all seems to be working nicely. I ended up just working through it methodically and learned quite a bit about USB comms at the same time.
I ended up doing pretty much exactly what you have suggested but I will check back through and compare my solution with your suggestions.
I edited the HID_Get/SetReport functions slightly differently (and not as elegantly) by using a simple 'for' loop to fill up/empty the Endpoint 0 buffer but it seems to work fine.
The USB comms protocol with the host specifies commands from the host as 8 byte packets and the same for data transferred from the device to the host. I am using SimpleHIDWrite.exe to simulate host/device transfers on my PC and so far everything seems to work fine.
I notice that the OUT transfers are done on Endpoint 0 while IN transfers use Endpoint 1 - what is the advantage of doing this? Should I set up a separate endpoint for the OUT transfers too? I'm not sure what the benefit would be.
I found quite a bit of advice on the ARM website in the end but when I did my original post I was pannicking a bit. It's great that I got such a comprehensive and easy to understand response from an obvious expert - so that you very much!!
Gareth.
> I notice that the OUT transfers are done on Endpoint 0 while IN transfers use Endpoint 1 - what is the advantage of doing this? Should I set up a separate endpoint for the OUT transfers too? I'm not sure what the benefit would be.
The advantage of interrupt OUT endpoint (EP) is light and quick execution on the firmware. When your PC app puts output reports frequently, the OUT EP reduces MCU load.
Another feature of the OUT EP is that your firmware is allowed to delay the timing to parse new report on the OUT EP, as long as you like. This feature is useful, for example, when your firmware doesn't want to execute new command passed by PC app, until the execution of last command finishes. Instead, for an output report over Set_Report request, your firmware should finish the request within given timeout. As of the background of this matter, HID spec defines that an interrupt IN EP is mandatory for every HID device, but an interrupt OUT EP is optional.
The report type (input/output/feature) determines the pipe available for the report.
input report - interrupt IN EP, Get_Report request output report - interrupt OUT EP, Set_Report request feature report - Get_Report, Set_Report requests
For Windows app, - ReadFile() reads out input report(s) passed over the interrupt IN EP. HidD_GetInputReport() issues a Get_Report( input ) request to the device.
- When the HID device has an interrupt OUT EP, WriteFile() puts an output report to the OUT EP. Without the OUT EP, the WriteFile() call is redirected to Set_Report( output ) request. HidD_SetOutputReport() always issues a Set_Report( output ) request to the device.
- Feature reports are exchanged using HidD_GetFeature() and HidD_SetFeature(). These APIs issue Get_Report( feature ) / Set_Report( feature ) request, respectively. When you run this this KEIL example on SimpleHIDWrite.exe, you would notice that this firmware always puts input reports repeatedly. In most of practical applications, this behavior is never the desired one. Many programmers like query-reply communication, query over an output report, and reply over single input report. Maybe, the next thing is changing this behavior to more practical one.
Is it your case?
Tsuneo
In my application the device firmware is required to accept and react to commands from the host. Information is sent back to the host a)when requested by the host b) when important data changes. So I guess that sending InReports as a stream sort of defeats the object.
I should definitely start thinking about changing to a system of InReports which are sent as specific times to specific requests from the host - or at the other times when variables have changed. More like the query/reply system that you mentioned.
I guess I can do this by setting up a separate IN EP? My assumption is that I would simply have to set up an event handler to deal with this case - similar to the OUT EP that is already in place?
Them I would see reports being issued only at the designated times when using SimpleHIDWrite.
Any further comments/advice you have would be greatly appreciated!
... and obviously I will need to edit the Config Descriptor to declare the IN EP!
The repeated input reports are NEVER the requirement of HID. It's just firmware implementation problem. For example, your USB mouse and keyboard runs on HID, but these devices don't work like KEIL example. They put an input report just when user moves the mouse, just when user keys in.
On the KEIL example, the repeated input reports are generated in these code.
C:\Keil\ARM\Boards\Keil\MCB2140\RL\USB\RTX_HID\usbuser.c /* * USB Core Task * Handles USB Core Events */ __task void USB_Core (void) { ... ... #if USB_CONFIGURE_EVENT if (evt & USB_EVT_SET_CFG) { if (USB_Configuration) { /* Check if USB is configured */ GetInReport(); // <------------ USB_WriteEP(HID_EP_IN, &InReport, sizeof(InReport)); // <------------ } } #endif ... ... /* * USB Endpoint 1 Task * Handles USB Endpoint 1 Events */ #if (USB_EP_EVENT & (1 << 1)) __task void USB_EndPoint1 (void) { U16 evt; for (;;) { os_evt_wait_or(0xFFFF, 0xFFFF); /* Wait for an Event */ evt = os_evt_get(); /* Get Event Flags */ if (evt & USB_EVT_IN) { GetInReport(); // <------------ USB_WriteEP(HID_EP_IN, &InReport, sizeof(InReport)); // <------------ } } } #endif
In above code, When Set_Configuration request comes at the end of enumeration, the example loads the first input report to the interrupt EP, using USB_WriteEP(). When an IN transaction completes on the EP (ie. the report on the EP is sent to host), the USB engine generates an interrupt on the EP. This interrupt is always caught by USB_EndPoint1() task. This task charges new input report to the EP.
On the host side, PC HID class driver always polls the IN EP using IN transactions at the bInterval rate (defined on the endpoint descriptor). Because the example puts an input report to every IN transaction, repeated input reports are sent to the host.
There is no need for the firmware to respond to every IN transaction from host. When no packet (report) is loaded on the IN EP, the USB engine automatically returns NAK. NAK is a normal response on USB communication, used for flow-control, which means try again afterword. When host receives NAK, it repeats the last transaction again without any error.
Here is modification of the KEIL example. In this snippet, we implement, - Interrupt OUT EP - Feature report handling - Loopback of an output report to an input report from interrupt OUT EP to interrupt IN EP - Loopback of a feature report from Set_Report( feature ) to Get_Report( feature )
Jan Axelson's generic_hid_cs is good for the PC test application of this mods (Thanks to Jan! ) "The HID Page" http://www.lvr.com/hidpage.htm generic_hid_cs www.lvr.com/.../generic_hid_cs_46.zip
The snippet below shows just mods on the firmware code.
usbcfg.h #define USB_MAX_PACKET0 64
usbdesc.c #include "hiduser.h" // moved to hiduser.h //#define HID_INPUT_REPORT_BYTES 1 /* size of report in Bytes */ //#define HID_OUTPUT_REPORT_BYTES 1 /* size of report in Bytes */ //#define HID_FEATURE_REPORT_BYTES 1 /* size of report in Bytes */ /* 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 + // <-------- USB_ENDPOINT_DESC_SIZE // <-------- increased by OUT EP ), 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 */ 0x02, /* bNumEndpoints */ // <-------- increased to 2 EP 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(0x0040), /* wMaxPacketSize */ // <-------- increased to 64 bytes 0x01, /* 1ms */ /* bInterval */ // <-------- changed to 1ms /* Endpoint, HID Interrupt Out */ // <-------- added OUT EP USB_ENDPOINT_DESC_SIZE, /* bLength */ USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */ USB_ENDPOINT_OUT(1), /* bEndpointAddress */ USB_ENDPOINT_TYPE_INTERRUPT, /* bmAttributes */ WBVAL(0x0040), /* wMaxPacketSize */ 0x01, /* 1ms */ /* bInterval */ /* Terminator */ 0 /* bLength */ };
usbuser.c #include <string.h> 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; 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 ); // 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 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 } if (evt & USB_EVT_IN) { // IN EP1 interrupt comes os_sem_send( USB_IN_EP1_Semaphore ); // IN EP1 is free /* GetInReport(); USB_WriteEP(HID_EP_IN, &InReport, 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)); */ } } #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.h /* HID report size (count) */ #define HID_INPUT_REPORT_BYTES 64 /* size of report in Bytes */ #define HID_OUTPUT_REPORT_BYTES 64 /* size of report in Bytes */ #define HID_FEATURE_REPORT_BYTES 64 /* size of report in Bytes */ /* HID In/Out Endpoint Address */ #define HID_EP_OUT 0x01 #define HID_EP_IN 0x81
hiduser.c #include <string.h> BOOL HID_GetReport (void) { /* ReportID = SetupPacket.wValue.WB.L; */ switch (SetupPacket.wValue.WB.H) { case HID_REPORT_INPUT: GetInReport(); // EP0Buf[0] = InReport; memcpy( EP0Buf, InReport, SetupPacket.wLength ); break; case HID_REPORT_OUTPUT: return (__FALSE); /* Not Supported */ case HID_REPORT_FEATURE: GetFeatureReport(); memcpy( EP0Buf, FeatureReport, SetupPacket.wLength ); break; } 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: // OutReport = EP0Buf[0]; memcpy( OutReport, EP0Buf, SetupPacket.wLength ); SetOutReport(); break; case HID_REPORT_FEATURE: memcpy( FeatureReport, EP0Buf, SetupPacket.wLength ); SetFeatureReport(); break; } return (__TRUE); }
demo.h /* HID Demo Variables */ extern U8 InReport[ HID_INPUT_REPORT_BYTES ]; extern U8 OutReport[ HID_OUTPUT_REPORT_BYTES ]; extern U8 FeatureReport[ HID_FEATURE_REPORT_BYTES ]; /* HID Demo Functions */ extern void GetInReport (void); extern void SetOutReport (void); extern void GetFeatureReport (void); extern void SetFeatureReport (void);
demo.c #include "hiduser.h" #include "demo.h" U8 InReport[ HID_INPUT_REPORT_BYTES ]; /* HID Input Report */ U8 OutReport[ HID_OUTPUT_REPORT_BYTES ]; /* HID Out Report */ U8 FeatureReport[ HID_FEATURE_REPORT_BYTES ]; /* HID Feature Report */ /*------------------------------------------------------------------------------ Get HID Input Report -> InReport *------------------------------------------------------------------------------*/ void GetInReport (void) { // this function is called just when Get_Report( input ) request comes // fill InReport[] with an input report, here // if ((FIO2PIN & PB_INT0) == 0) { /* Check if PBINT is pressed */ // InReport = 0x01; // } else { // InReport = 0x00; // } } /*------------------------------------------------------------------------------ Set HID Output Report <- OutReport *------------------------------------------------------------------------------*/ void SetOutReport (void) { // this function is called just when Set_Report( output ) request comes // At the entry of this function, OutReport[] holds an output report from the host // IOPIN1 = (IOPIN1 & ~LED_MSK) | (OutReport << 16); } /*------------------------------------------------------------------------------ Get HID Feature Report -> FeatureReport *------------------------------------------------------------------------------*/ void GetFeatureReport (void) { // this function is called just when Get_Report( feature ) request comes // fill FeatureReport[] with an input report, here } /*------------------------------------------------------------------------------ Set HID Feature Report <- FeatureReport *------------------------------------------------------------------------------*/ void SetFeatureReport (void) { // this function is called just when Set_Report( feature ) request comes // At the entry of this function, FeatureReport[] holds an feature report from the host }
This makes sense and I suspected that the streaming input reports were just the way the example was set up.
Can I check my understanding of your latest post? ...
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? So I need to use the descriptor to describe 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. I suppose what I'm asking is how the GetInReport function in the main program is related to the mechanism within the USB engine which then transmits the report via the EP to the host?
Just for example ... an important variable in my system changes (similar to the mouse moving) and I want to send that new value to the host. When this happens I can detect it and update the InReport array within GetInReport. What is the part which knows that this must be transmitted NOW?
Thanks for your ongoing assistance - I am making fast progress and I feel that my understanding is moving forward all the time!
Gareth
Good day Tsuneo and all
quite interesting to find this thread right on top ... :D
Here is the situation: I already changed HID reports successfully on ARM7/2366. (to 60) This was almost straight forward and works fine. So I am not new to all the stuff (But of course still light year's away from other 'T' - user's expertise)
Now I wanted to do the same with the latest USB HID sample provided for MCB1700 (so this included a switch to CORTEX)
I did all the stuff above, and in principal, it works too, but only for the Report Sizes 1,2,3 and 4. (and yes, I enlarged EP0Buf to 64 (resp MAX_PACKET_SIZE0)
whenever I got to reports > 4, it stops working
is there somewhere a known maximum or bug ?
MANY MANY thanks in advance for any illumination ULI
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.
Hi ULI,
> I did all the stuff above, and in principal, it works too, but only for the Report Sizes 1,2,3 and 4. (and yes, I enlarged EP0Buf to 64 (resp MAX_PACKET_SIZE0)
Sound like the limit of 4 bytes comes from wMaxPacketSize of the interrupt IN endpoint, doesn't it?
C:\Keil\ARM\Boards\Keil\MCB1700\USBHID\usbdesc.c const U8 USB_ConfigDescriptor[] = { ... ... /* 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 */ <--------- 0x0040 ie. 64 bytes 0x20, /* 32ms */ /* bInterval */
grrrrr............................ I was close to losing consciousness upon reading this..
THANK YOU TSUNEO ONCE MORE !!!!!!!!!!!!! I had marked all the spots of modification then with the ARM 7 stuff, oviously all except one ...
in general, what is your opinion regarding these samples in quality? from my viewpoint, they could a lot better regarding modularization and ease of use... eg my current (solved) problem: it would have been a lot easier to have just U8 InReport[REPORT_SIZE] and only one single point for definition and modification
or, eg if looking at the string descriptors ...
how to handle a "reallife" serial number properly ? this sample won't work if it is stored in an EEProm
what do you think of my idea to solve this ? keep all strings in ordinary const char and, upon startup (before USBInit !!) go through them and expand them to wide strings in the given string descriptor structure. Upon extraction, I can easily fill in my real serial number from elsewhere. does this sound OK ?
again: a big thanx
ULI
PS to Garrett: apologies for stumbling in
> in general, what is your opinion regarding these samples in quality?
Not so bad, but not so good. I recommend the programmers of KEIL stack to read as many stack code from others. Most of USB MCU manufacturers release their own USB stack. Also, examine the interfaces of commercial USB stacks. These materials will give many suggestion on the organization of the stack.
> keep all strings in ordinary const char and, upon startup (before USBInit !!) go through them and expand them to wide strings in the given string descriptor structure. Upon extraction, I can easily fill in my real serial number from elsewhere. does this sound OK ?
It's OK. One step advance, this string expansion process is directly integrated in Get_Descriptor( string ) handler, so that the string expansion goes on demand. And then, you can save RAM space for the expanded strings.
If this stack would have an extra hook for SETUP handler, we could modify the behavior of the stack without changing the stack code at all. This kind of hook is seen on other stacks, like Microchip, ST micro, EZUSB, etc.
thank you once more!
Strings on demand sounds smart.
My problem is that a REAL understanding from bottom to top is still not existent, despite your posts and Axelson's books. Nowadays, all the sales guys say: get this library, get this component, stick it together, add some glue code and then lets sell it. No spare time to go into detail, no way to become a real expert, especially if in the meantime (last time I was at USB in detail was March or so) so many other topics were hot and needed immediate attention.
regarding the KEIL code, I think its limitations show up once you want to transform it into "real code". from my point of view, it should be easy to separate the sources and have the option to treat them in a "library style" (eg separate folders, maybe even in compiled form) this is not possible here as they have mixed up everything. (eg the core includes demo.h, and all resides in one folder) First thing for me to do after I got it to basically communicate will be "cleaning up and separating" (and hoping it will not be broken afterwards)
btw, for these tests I always use the HID Exerciser. (cool tool)
<<<Sometimes>>> (not sure about the circumstances and not regularly) upon arrival of one of my (Keil driven) devices, the detection (readout of strings) fails and the exerciser only lists VID+PID in hex. Any idea what this might be? This seems never to happen with other devices.
Another point I always have to add:
why not post your paypal account here ? ;)) Your comments are so helpful ...and probably take so much of your time...keeping you from "real" work. Or maybe write a book ;)
What I would find very interesting is a list of all your USB children ;))) -> thinking of all the devices you were responsible for. I bet, there are some Tsuneo driven parts in my house :D -> probably not possible due to nda limitations ;((
Vielen Dank! Uli
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.