Hi everyone,
I'm working on a custom system monitoring device and have a requirement for a USB HID input report longer than 64 bytes. From what I've read, it's possible to send it as multiple transactions but I can't figure out how to make Keil's USBHID sample code do it.
Any ideas?
Thanks, Jason
Report Descriptor This report descriptor defines 191 bytes Input and 150 bytes Output report
#define INREPORT_SIZE 191 #define OUTREPORT_SIZE 150 /* HID Report Descriptor */ const BYTE HID_ReportDescriptor[] = { HID_UsagePageVendor( 0x00 ), HID_Usage( 0x01 ), HID_Collection( HID_Application ), HID_LogicalMin( 0 ), HID_LogicalMaxS( 0xFF ), HID_ReportSize( 8 ), // bits HID_ReportCount( INREPORT_SIZE ), // bytes HID_Usage( 0x01 ), HID_Input( HID_Data | HID_Variable | HID_Absolute ), HID_ReportCount( OUTREPORT_SIZE ), // bytes HID_Usage( 0x01 ), HID_Output( HID_Data | HID_Variable | HID_Absolute ), HID_EndCollection, };
HID_ReportCount() macro supports upto 255 (single byte) as its parameter. For more bytes (up to 0xFFFF), apply this HID_ReportCountS() macro (two bytes)
#define HID_ReportCountS(x) 0x96,(x&0xFF),((x>>8)&0xFF)
I don't know what is the reliable maximum report size on Windows, but I recommend you less than or equal to 4 Kbytes (page size of PCI bus). Split the input report The report is split by wMaxPacketSize (endpoint descriptor) of the endpoint. It's usually 64 bytes for full-speed HID device, but confirm your descriptor definition.
Above 191 bytes report is split into three transactions,
64, 64, 63
Above report descriptor defines no report ID. Then, this division is fine. If you use any report ID for more input-report formats, the report is preceded by the report ID, the division becomes as follows.
(1byte report ID + 63), 64, 64
If any other input report longer than this one is defined, this report transfer should be terminated by ZLP (Zero-Length Packet), because the last transaction matches to wMaxPacketSize
(1byte report ID + 63), 64, 64, ZLP
The largest report don't need to terminate with ZLP, even when it ends up with wMaxPacketSize transaction.
Complicated? :-) Endpoint handling When the hardware finishes to collect whole data for the report, the firmware makes up the entire report on a buffer. The first transaction (64 bytes) is put to the endpoint buffer using USB_WriteEP(), in the data acquisition routine. See this post for the details.
"HID Mouse keeps sending unneeded data!" http://www.keil.com/forum/docs/thread11531.asp
The second and later transactions are handled in the endpoint ISR, USB_EndPointN. This ISR is called when the endpoint buffer becomes empty by a successful transaction. Every time the USB_EndPointN is called, put the next transaction, if any, by USB_WriteEP().
You have to fiddle several flags and counter to make this work as expected.
Tsuneo
Thanks for the response Tsuneo! I gave it a try but it still doesn't seem to be working. I think it may have something to do with USB_ConfigEP that deals with the Endpoint descriptor. The code is:
void USB_ConfigEP (USB_ENDPOINT_DESCRIPTOR *pEPD) { DWORD num; num = EPAdr(pEPD->bEndpointAddress); REALIZE_EP |= (1 << num); EP_INDEX = num; MAXPACKET_SIZE = pEPD->wMaxPacketSize; while ((DEV_INT_STAT & EP_RLZED_INT) == 0); DEV_INT_CLR = EP_RLZED_INT; }
It assumes that the max packet size a word. Should MAXPACKET_SIZE be the two bytes (my report is 100 bytes) added together?
The important bits of my code are:
Report Descriptor
HID_UsagePageVendor(0xFF), // ESA Config HID_Usage(0x03), // ESA Water Cooling HID_Collection(HID_Application), HID_ReportID(0x11), // Input Report 11 //HID_UsagePage(0xFF), HID_Usage(0x04), HID_LogicalMin(0), HID_LogicalMax(255), HID_ReportCount(99), HID_ReportSize(8), HID_Input(HID_Data | HID_Array | HID_Absolute), HID_EndCollection,
Endpoint Descriptor
/* Endpoint, HID Interrupt In */ USB_ENDPOINT_DESC_SIZE, /* bLength */ USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */ USB_ENDPOINT_IN(1), /* bEndpointAddress */ USB_ENDPOINT_TYPE_INTERRUPT, /* bmAttributes */ 0x40,0x24, /* wMaxPacketSize */ 0x20, /* 32ms */ /* bInterval */
USB_EndPoint1
void USB_EndPoint1 (DWORD event) { DWORD byte_count = 0; set_chb_err_led(FALSE); switch (event) { case USB_EVT_IN: // Alternate which report we output so we don't stomp the buffer switch (report_block_number) { case 0: GetInReportMCC(); byte_count = USB_WriteEP(0x81, InReport, 64); report_block_number = 1; break; case 1: byte_count = USB_WriteEP(0x81, InReport, 36); report_block_number = 0; break; } if (byte_count > 0) set_usb_status(0); if (byte_count == 0) set_usb_status(1); break; } }
Does this look correct? Thanks again for all the help. I'm on a really tight timeline so it's appreciated.
Jason Lewis
"Should MAXPACKET_SIZE be the two bytes (my report is 100 bytes) added together?"
You don't need to touch MAXPACKET_SIZE of USB_ConfigEP. Please note, wMaxPacketSize means the division unit for transactions. It doesn't mean the transfer size itself. Transfer size is defined by the HID_ReportCount() on the report descriptor. And the wMaxPacketSize for full-speed interrupt and bulk transfer is limited to 64 bytes by the USB spec.
Recently, I wrote about the relation of "Transfer - transaction - packet" in this post. It describes how wMaxPacketSize works on the USB protocol. It is applied to bulk and interrupt (HID) transfer.
"C8051F340 USB_Bulk example question" on SiLabs Forum www.cygnal.org/.../001627.html "Report Descriptor"
HID_ReportID(0x11), // Input Report 11
As the report descriptor has just single definition of input report, you can do without the report ID. Of course, you can apply report ID as you wrote, too. If you have plan to add more input reports, the report ID will ease the extension. "Endpoint Descriptor"
0x40,0x24, /* wMaxPacketSize */
This is the problem. Set wMaxPacketSize to 64 (LSB first, as usual on USB).
0x40,0x00, /* wMaxPacketSize = 64 */
"USB_EndPoint1"
DWORD byte_count = 0;
Make byte_count to static, or global. USB_EndPoint1() is called by the USB ISR every time a transaction finishes. Anyway, I think this byte_count is not useful in your code, because you does it with report_block_number.
In this implementation, your device continuously puts the input report at the rate defined by the endpoint descriptor (bInterval). As it is 32 ms, the first transaction (64 bytes) and later one (32 bytes) are sent to the host alternately, in this rate. (The entire transfer takes 64 ms).
If you want to send the report just when the device detects a change on the target, rewrite it as described in above post.
Awesome, that did it. Turns out that I had all the code right initially with the exception of USB_Endpoint1.
Thanks Tsuneo!
Okay, another related question. I also need to be able to send the controller long reports (140 bytes) via a control transfer. I've verified that the report is being sent and the controller is receiving them (64, 64, and 12). I can't figure out how to modify the HIDUSB code to parse the pieces together. Any help would be appreciated.
Sorry for my late response,
"I also need to be able to send the controller long reports (140 bytes) via a control transfer."
You can support OUT report over control transfer, but primarily OUT report is handled by an OUT endpoint. - For the firmware, handling of control transfer is heavy than interrupt endpoint - For Windows XP and Vista host app, WriteFile primarily goes to OUT endpoint.
In this topic, we discussed about the way to add OUT endpoint to HID. You can extend this method for the greater report size than 64, as you did it to the IN endpoint.
"LPC2148 usb controller" http://www.keil.com/forum/docs/thread11137.asp
Instead of OUT report, Feature report is primarily handled by control transfer. If you want to exercise the control write transfer implementation, I'll guide you.