I’d like to share some knowledge I gained about Human Interface Devices (HID) that are connected to a host over USB and its HID Report. I was especially interested in the way how HIDs are enumerated by the host and how the data communication protocol is implemented. Please consider that I have just very little knowledge about the USB protocol in general and the goal of my research was to find a fast and simple solution to get button events of a HID.
First I had to understand some USB basics. With the help of the book “USB Complete Fourth Edition” by Jan Axelson, I was able to cover many knowledge gaps without losing my head due to the size and deepness of the USB protocol specification.
Soon I stumbled over HID Reports and with them I found the key to share data with an HID.
Create and understand an HID Report
Whenever a USB device is pluged into a USB port, the host initiates an enumeration process to get information about the device, such as vendor ID, product ID, manufacturer string, device and interface descriptors, etc. To access these information, the host creates and sends request reports to the newly attached device. If the device supports an HID interface, the host requests the corresponding HID report descriptor and the devices firmware/application has to provide one.
To understand how HID report descriptors are structured the above mentioned book of Jan Axelson again proved to be the right source of information. Together with the lists in the document “HID Usage Tables” of the USB Implementers’ Forum, I trusted myselfe to be capable of creating an HID report descriptor with the HID Descriptor Tool from USB.org (to parse raw HID descriptors and requests into C-Arrays with human-readable descriptions, you can use this tool of Frank Zhao).
Here is an example of an HID Report Descriptor:
0x05, 0x0b, // USAGE_PAGE (Telephony Devices)
0x09, 0x05, // USAGE (Headset)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x01, // REPORT_ID (1)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x09, 0x20, // USAGE (Hook Switch)
0x09, 0x2f, // USAGE (Phone Mute)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x06, // REPORT_COUNT (6)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x85, 0x02, // REPORT_ID (2)
0x05, 0x08, // USAGE_PAGE (LEDs)
0x09, 0x09, // USAGE (Mute)
0x09, 0x17, // USAGE (Off-Hook)
0x09, 0x18, // USAGE (Ring)
0x95, 0x03, // REPORT_COUNT (3)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0x95, 0x05, // REPORT_COUNT (5)
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
0xc0 // END_COLLECTION
Interpretation of the HID Report example
An instruction in an HID Report always consists of at least two bytes. The first byte defines what type of value is following, whereas the second byte is the value itselfe. Adopted to the first line of the example HID Report Descriptor “0x05” is signaling that a USAGE_PAGE definition is going to follow and “0x0b” stands for the Telefony Device USAGE_PAGE.
Thus the descriptor reveals, that the connected HID is a Telephony Device (USAGE_PAGE) of the type Headset (USAGE) with two buttons (Hook Switch and Phone Mute) and three LED states (Mute, Off-Hook and Ring). It provides two HID Reports, each with a size of one byte which have to be interpreted as following:
The report with the ID 1 is an input report, containing two bits of information, bit for Hook Switch events and bit for Phone Mute button events. Due to the fact, that an HID Report has to consist of a whole byte multiplied by an integer value, 1 byte is the minimal report size. That is why the remaining bits have to be filled with padding bits (lines 12 and 13 of the example descriptor). Input or output are definitions with respect to the hosts view. So while buttons, switches and keys are inputs for the host, LEDs or displays are outputs from the host, which can be adressed by the it using output reports. The report example with the ID 2 is such an output report, containing three bits representing the three LEDs, Ring-, Off-Hook- and Mute-LED. This output report also includes required padding bits (lines 21 and 22 of the example descriptor).
Input and Output Reports
To give an example how a control sequence over USB could look like, let’s assume a practical use case:
If the user presses the Hook-Switch button an input report is sent from the device to the host wit the following data attached:
0x01, 0x01 // Report ID, Data
Bit 1 of report ID1 is set
If the host successfully receives the data, he wants to set the Off-Hook-LED of the device and sends an output report with the data set to:
0x02, 0x02 // Report ID, Data
Bit 2 of report ID2 is set