16
votes

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 despite RTFMing and googling I still do not have a clue about some fields in HID descriptor.

What is a hint or a manual where I can find the meaning of all descriptor fields? All I could find was examples for HID-mouse, joystick, and keyboard.

For example - REPORT_SIZE - is it size in bytes or in bits? And why is there also REPORT_COUNT? If I have 64 bytes in the report, must LOGICAL_MAXIMUM be 255 or 255*64?

Should I write LOGICAL_MAX and MIN for every report or not?

Or maybe will this one (generated rather by guessing) suffice?

char ReportDescriptor[39] = {
    0x05, 0x01,         // USAGE_PAGE (Generic Desktop)
    0x09, 0x00,         // USAGE (Undefined)
    0xa1, 0x01,         // COLLECTION (Application)
    0x85, 0x01,         //   REPORT_ID (1)
    0x09, 0x00,         //   USAGE (Undefined)
    0x15, 0x00,         //   LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,   //   LOGICAL_MAXIMUM (255)
    0x75, 0x40,         //   REPORT_SIZE (64)
    0x96, 0x00, 0x02,   //   REPORT_COUNT (512)
    0x81, 0x82,         //   INPUT (Data,Var,Abs,Vol)
    0x85, 0x02,         //   REPORT_ID (2)
    0x09, 0x00,         //   USAGE (Undefined)
    0x15, 0x00,         //   LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,   //   LOGICAL_MAXIMUM (255)
    0x75, 0x40,         //   REPORT_SIZE (64)
    0x96, 0x00, 0x02,   //   REPORT_COUNT (512)
    0x91, 0x82,         //   OUTPUT (Data,Var,Abs,Vol)
    0xc0                // END_COLLECTION
};
5
I know you said you RTFM, but also asked for a manual anyway - have you checked the HID specification from usb.org, specifically section 6.2.2 on Report Descriptors?Preston
Report size is in bytes. Report count is the number of reports that you are supporting. Section 5.8 describes logical minimum and maximum. If you want something less "spec"-y then look at Jan Axelson's USB Complete, it has a great HID description.Preston
@Preston, actually, yes, I have. And all I can see is description of some of the fields. USAGE, COLLECTION, INPUT, OUTPUT. And an example of HID-mouse. That's useful but hardly enough. If report count is the number of reports - why there is three of them in one HID-mouse descriptor? Thanks for the book, I'll look into it!Amomum
Report size is in bits rather than bytes. It's correctly stated in the accepted answer.sunside

5 Answers

26
votes
  1. 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:

    • The "Device Class Definition for HID 1.11" document - which describes the Human Interface Device report format
    • The "HID Usage Tables 1.12" document - which describes the values of many Usage Pages and Usages within those pages that can appear in a Report Descriptor

    Having said that, the documentation is notoriously obtuse and will require considerable effort to digest.

  2. 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.
    
  3. 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;
    
  4. 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.

  5. The above example was decoded using a free tool on SourceForge called hidrdd

8
votes

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:

  • More input/output pairs can easily be added: just give them another Report ID. Each message definition consists of 10 bytes, so it's simple to add up.
  • We keep track of the number of bytes in the descriptor so that the size of the array can be computed (#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.

3
votes

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.

0
votes

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.

0
votes

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;
};