Report Descriptors

To define an HID device, you need to supply an HID report descriptor. When you plug in an HID device, it sends its report descriptor(s) to the host computer. The report descriptor is binary data that specifies the device type and the details of the reports that the device sends and receives.

A report is binary data. A report sent from the device to the host computer is called an IN report. A report sent from the host to the device is an OUT report. IN and OUT are named from the perspective of the host.

For instance, a mouse report descriptor will declare that it is a device of type Mouse, with a certain number of Buttons and possibly a scroll Wheel. Every time you move the mouse, push a button, or move its scroll wheel, the mouse will send an IN report with data describing which buttons that are currently pushed, how far the mouse has moved in X and Y directions, and how much the scroll wheel has been turned.

Similarly, a keyboard will send IN reports saying which regular keys are pressed and which modifier keys (Shift, Ctrl, etc.) are pressed at the same time. In addition, most keyboards can receive OUT reports back from the host computer, which tell the keyboard to turn on and off its LEDs, such as the shift-lock indicator.

A Sample Report Descriptor

Below is a CircuitPython boot.py file that includes an example of a gamepad report descriptor. 

The descriptor is a bytes string named GAMEPAD_REPORT_DESCRIPTOR. Note how the descriptor specifies a Usage Page, which is the general class of device, in this case, Generic Desktop Controls, and then a particular Usage, which is Game Pad. Then the descriptor specifies a Report ID, which here is 4. The report ID can be any value from 1 to 255, but must be unique for this report among all the reports in the devices presented by CircuitPython.

The descriptor then declares 16 on/off (0 or 1) Buttons, which fit in one bit each (Report Count of 16 and Report Size of 1), and four joystick axes, which are Usages X, Y, Z, and Rz. Each joystick value varies from -127 to 127, and fits in 8 bits.

The rest of the code creates a Device based on the descriptor, and includes it in a list of devices that also includes the default keyboard, mouse, and consumer control devices that CircuitPython usually presents. The Device constructor specifies the Report ID's used, and how many bytes are in the IN and OUT reports for each Report ID. In this case the IN report is 6 bytes long, and there is no OUT report. See the Customizing USB Devices Guide for more details about why this code needs to be in boot.py, and what usb_hid.enable() is doing.

# boot.py
import usb_hid

# This is only one example of a gamepad descriptor.
# It may not suit your needs, or be supported on your host computer.

GAMEPAD_REPORT_DESCRIPTOR = bytes((
    0x05, 0x01,  # Usage Page (Generic Desktop Ctrls)
    0x09, 0x05,  # Usage (Game Pad)
    0xA1, 0x01,  # Collection (Application)
    0x85, 0x04,  #   Report ID (4)
    0x05, 0x09,  #   Usage Page (Button)
    0x19, 0x01,  #   Usage Minimum (Button 1)
    0x29, 0x10,  #   Usage Maximum (Button 16)
    0x15, 0x00,  #   Logical Minimum (0)
    0x25, 0x01,  #   Logical Maximum (1)
    0x75, 0x01,  #   Report Size (1)
    0x95, 0x10,  #   Report Count (16)
    0x81, 0x02,  #   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    0x05, 0x01,  #   Usage Page (Generic Desktop Ctrls)
    0x15, 0x81,  #   Logical Minimum (-127)
    0x25, 0x7F,  #   Logical Maximum (127)
    0x09, 0x30,  #   Usage (X)
    0x09, 0x31,  #   Usage (Y)
    0x09, 0x32,  #   Usage (Z)
    0x09, 0x35,  #   Usage (Rz)
    0x75, 0x08,  #   Report Size (8)
    0x95, 0x04,  #   Report Count (4)
    0x81, 0x02,  #   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    0xC0,        # End Collection
))

gamepad = usb_hid.Device(
    report_descriptor=GAMEPAD_REPORT_DESCRIPTOR,
    usage_page=0x01,           # Generic Desktop Control
    usage=0x05,                # Gamepad
    report_ids=(4,),           # Descriptor uses report ID 4.
    in_report_lengths=(6,),    # This gamepad sends 6 bytes in its report.
    out_report_lengths=(0,),   # It does not receive any reports.
)

usb_hid.enable(
    (usb_hid.Device.KEYBOARD,
     usb_hid.Device.MOUSE,
     usb_hid.Device.CONSUMER_CONTROL,
     gamepad)
)

Creating a Report Descriptor

Writing your own HID report descriptor from scratch requires a lot of detailed knowledge, and is beyond the scope of this guide. However, you can often find the report descriptor for an existing HID device you want to emulate with a web search, and just use it as is, or modify it slightly for your purposes.  If you have a device but don't have a published report descriptor for it, there are tools available to capture the report descriptor when you plug the device in. usbhid-dump is a such a tool you can use on Linux, and here are some similar tools for Windows and macOS. There are many others.

There are many tutorials about HID report descriptors available, such as this one. And here's an online tool that can decipher existing report descriptors.

You will also need to write a CircuitPython driver to handle your new device. The driver assembles a report and then passes it back to the host computer. You can see examples of HID devices drivers in the adafruit_hid library. And the following pages in this guide will give you several examples of custom HID devices and drivers which you can just copy, or adapt for your own purposes. 

This guide was first published on Oct 01, 2021. It was last updated on 2021-10-01 09:57:06 -0400.

This page (Report Descriptors) was last updated on Oct 23, 2021.

Text editor powered by tinymce.