I have a bit of a problem with generating a HID descriptor. I want to use simple reports with ID1 for input and ID2 for output with 64 bytes of data.
I realized that
I've got my custom HID device detected by Windows 7 with this (built by guess and stealing from examples):
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x00, // USAGE (Undefined)
0xa1, 0x01, // COLLECTION (Application)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x85, 0x01, // REPORT_ID (1)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x40, // REPORT_COUNT (64)
0x09, 0x00, // USAGE (Undefined)
0x81, 0x82, // INPUT (Data,Var,Abs,Vol) - to the host
0x85, 0x02, // REPORT_ID (2)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x40, // REPORT_COUNT (64)
0x09, 0x00, // USAGE (Undefined)
0x91, 0x82, // OUTPUT (Data,Var,Abs,Vol) - from the host
0xc0 // END_COLLECTION
}; /* CustomHID_ReportDescriptor */
I'm not sure if it will work properly though. We will see.
Here is a link to the Spec Sheet (or "manual") for your reading purposes.
To answer some of your questions, REPORT_SIZE
is specified in bits and REPORT_COUNT
can be used to specify how many "Usages" are being reported with the indicated properties. For example, you can set the properties for the X
and Y
usages and the have the REPORT_COUNT
specified as 2 (one for X and one for Y) and then specify the INPUT
to add those usages to the report. Then continue on describing other usages.
Also, don't forget to have the usages byte aligned. Since REPORT_COUNT
is specified in bits, it is easy to forget to have usages byte aligned. So if one usage is only 1 bit, then you need to specify that there will be 7 bits in that byte not being used before moving to the next usage if it requires more than 7 bits.
HID report descriptors are in key-value pairs. To understand them, read the HID specification and chapter 6 is helpful in this case. Where do you find how these values came from and how is it affecting the descriptor? If we change them:
Key Value
--------------------------------------
0x85, 0x02, // REPORT_ID (2)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x40, // REPORT_COUNT (64)
@Trick:
If you are facing a problem to understand REPORT_COUNT (0x95) AND REPORT_SIZE (0x75), then you can imagine it as a two-dimensional array.
Array[count][size] = Array[4][8] /* Values are in [Bytes][bit] */
Where 4 represents the total number of reports to be send and 8 shows the size of each report.
That is,
struct reports
{
uint8_t report_1;
uint8_t report_2;
uint8_t report_3;
uint8_t report_4;
};
All the official documentation is available on usb.org. To understand HID Report Descriptors you need to read some of the documents on the HID Information page. In particular, you should try to understand:
Having said that, the documentation is notoriously obtuse and will require considerable effort to digest.
REPORT_SIZE is the size of a report in bits not bytes. Think of REPORT_SIZE as the width of a field (in bits) and the REPORT_COUNT as the number of fields (of that width). This is made clear in the "Device Class Definition for HID 1.11" document, Section 6.2.2.7 "Global Items" as follows:
Global Item Tag One-byte Prefix Description
Report Size 0111 01 nn Unsigned integer specifying the size of the report
fields in bits. This allows the parser to build an
item map for the report handler to use. For more
information, see Section 8: Report Protocol.
As a guide, a reasonable (i.e I haven't tested it) Report Descriptor that describes a 64-byte input buffer (to the host with a REPORT_ID of 0x01) and a 64-byte output buffer (from the host with a REPORT_ID of 0x02) could be as follows:
0x06, 0x00, 0xFF, // (GLOBAL) USAGE_PAGE 0xFF00 Vendor-defined
0xA1, 0x01, // (MAIN) COLLECTION 0x01 Application (Usage=0x0: Page=, Usage=, Type=) <-- Warning: USAGE type should be CA (Application)
0x15, 0x00, // (GLOBAL) LOGICAL_MINIMUM 0x00 (0) <-- Redundant: LOGICAL_MINIMUM is already 0
0x26, 0xFF, 0x00, // (GLOBAL) LOGICAL_MAXIMUM 0x00FF (255)
0x75, 0x08, // (GLOBAL) REPORT_SIZE 0x08 (8) Number of bits per field
0x85, 0x01, // (GLOBAL) REPORT_ID 0x01 (1)
0x95, 0x40, // (GLOBAL) REPORT_COUNT 0x40 (64) Number of fields
0x09, 0x01, // (LOCAL) USAGE 0xFF000001
0x81, 0x02, // (MAIN) INPUT 0x00000002 (64 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap
0x85, 0x02, // (GLOBAL) REPORT_ID 0x02 (2)
0x09, 0x01, // (LOCAL) USAGE 0xFF000001
0x91, 0x02, // (MAIN) OUTPUT 0x00000002 (64 fields x 8 bits) 0=Data 1=Variable 0=Absolute 0=NoWrap 0=Linear 0=PrefState 0=NoNull 0=NonVolatile 0=Bitmap
0xC0, // (MAIN) END_COLLECTION Application
Which corresponds to the following C-language structure definitions:
//--------------------------------------------------------------------------------
// Vendor-defined inputReport 01 (Device --> Host)
//--------------------------------------------------------------------------------
typedef struct
{
uint8_t reportId; // Report ID = 0x01 (1)
uint8_t VEN_VendorDefined0001[64]; // FF00 0001 Value = 0 to 255
} inputReport01_t;
//--------------------------------------------------------------------------------
// Vendor-defined outputReport 02 (Device <-- Host)
//--------------------------------------------------------------------------------
typedef struct
{
uint8_t reportId; // Report ID = 0x02 (2)
uint8_t VEN_VendorDefined0001[64]; // FF00 0001 Value = 0 to 255
} outputReport02_t;
Should you specify LOGICAL_MINIMUM and LOGICAL_MAXIMUM for each report? No.
Some items are GLOBAL (which means that, as the report descriptor is parsed sequentially, their values remain until they are explicitly changed by another GLOBAL item) and others are LOCAL (which means that their values are reset to defaults whenever a MAIN item is encountered). Both LOGICAL_MINIMUM and LOGICAL_MAXIMUM are GLOBAL items, so you only need to re-specify their values if you want the value to change. In my opinion, the specification would have been clearer if the official names for items were prefixed by GLOBAL_, LOCAL_ and MAIN_ but unfortunately we all have to live with the spec as it stands.
The above example was decoded using a free tool on SourceForge called hidrdd
As @aja stated above, the official USB documentation is rather obtuse. I created this template (mostly with help from this page) as a simple starting point to communicate with a custom board. The HID code is intended to replace the Virtual COM Port protocol. The big advantage with HID is that no driver is required.
uint8_t CUSTOM_HID_ReportDesc[REPORT_DESC_SIZE] =
{
0x06, 0x00, 0xFF, // Global Usage page = 0xFF00 (Vendor-defined pages are in the range 0xFF00 through 0xFFFF)
0x09, 0x01, // Local Usage (vendor usage 1)
0xA1, 0x01, // Main Collection (application) begin
0x15, 0x00, // Global Logical minimum (0) applies to each byte
0x26, 0xFF, 0x00, // Global Logical maximum (255)
0x75, 0x08, // Global Report Size (8 bits)
// 14 bytes | Output message 1 (sent from host to device)
0x85, 1, // Global Report ID (cannot be 0)
0x98, 64, // Global Report Count (number of Report Size fields, in this case 64 bytes)
0x19, 0x01, // Local Usage Minimum (each Report Count must be associated with a Usage)
0x19, 0x40, // Local Usage Maximum
0x91, 0x02, // Main Output (data, array, absolute)
// 24 bytes | Input message 1 (sent from device to host)
0x85, 1, // Global Report ID (cannot be 0)
0x98, 64, // Global Report Count (number of Report Size fields)
0x19, 0x01, // Local Usage Minimum (each Report Count must be associated with a Usage)
0x19, 0x40, // Local Usage Maximum
0x81, 0x02, // Main Input (data, array, absolute)
// 34 bytes | Output message 2 (sent from host to device)
0x85, 2, // Global Report ID (cannot be 0)
0x98, 12, // Global Report Count (number of Report Size fields)
0x19, 0x01, // Local Usage Minimum (each Report Count must be associated with a Usage)
0x19, 0x40, // Local Usage Maximum
0x91, 0x02, // Main Output (data, array, absolute)
// 44 bytes | Input message 2 (sent from device to host)
0x85, 2, // Global Report ID (cannot be 0)
0x98, 57, // Global Report Count (number of Report Size fields)
0x19, 0x01, // Local Usage Minimum (each Report Count must be associated with a Usage)
0x19, 0x40, // Local Usage Maximum
0x81, 0x02, // Main Input (data, array, absolute)
// 54 bytes | End (add one byte)
0xC0 // Main Collection (application) end
}
A couple of things to note:
#define REPORT_DESC_SIZE (55)
).On the Windows side, I use Mike O'Brien's HIDLibrary. HID reports are typically prepended with the Report ID--in HIDLibrary, use the HidReport.ReportID
field to set/get the value. On the board side, remember that the first byte of the report will be the Report ID.