How Do You Design a USB HID Report Descriptor for a Custom Device?
Last updated 15 July 2026 · 6 min read
Direct Answer
A USB HID (Human Interface Device) report descriptor is a byte array, sent to the host during enumeration, that describes the exact structure and meaning of every piece of data a HID device exchanges with the host — it is not optional boilerplate copied from a keyboard example; it's the schema the host's generic HID driver uses to parse every report the device sends or receives. Building a custom (non-keyboard, non-mouse) HID device means writing this descriptor from scratch using HID Usage Tables — standardised or vendor-defined Usage Pages and Usage IDs that give each field semantic meaning — organised into Input, Output, and Feature reports inside a Collection structure, and getting the descriptor wrong (a bit-count mismatch, an incorrectly signed field, a missing terminating End Collection) is by far the most common reason a HID device enumerates successfully but exchanges garbled or misaligned data.
Detailed Explanation
Every USB HID device sends the host a report descriptor during enumeration — a compact, byte-encoded description of exactly what data the device exchanges and what each bit and byte means. The host's generic HID driver parses this descriptor once, at enumeration time, and uses it to correctly interpret every subsequent report without needing a device-specific driver — this is what lets a HID mouse, keyboard, or custom gamepad work on any HID-compliant host with zero installed software. For a genuinely custom device (a sensor dashboard controller, a custom input peripheral, a small data-logging accessory), the descriptor has to be written deliberately for that device's actual data — copying a keyboard's report descriptor and only changing the report size, without understanding the structure, is the single most common cause of a device that enumerates as HID but exchanges data the host application can't correctly parse.
The descriptor is organised around three concepts:
- Usage Pages and Usage IDs — the semantic vocabulary. A Usage Page groups related meanings (Generic Desktop, Keyboard/Keypad, LEDs, and so on); a Usage ID within that page names a specific field's meaning (X-axis, a specific key, a specific LED). Standard pages exist for common device types; the range
0xFF00–0xFFFFis reserved for vendor-defined pages for anything that doesn't fit a standard category. - Collections — a structural grouping (most commonly an Application Collection) that bundles a set of related Input/Output/Feature items into one logical report structure, analogous to a struct definition in firmware.
- Reports (Input/Output/Feature) — the actual data fields, each declared with a bit size (
Report Size), a count (Report Count), a numeric range (Logical Minimum/Logical Maximum), and whether the value is signed. See the FAQ below for the direction and typical use of each report type.
Practical Examples
A custom sensor dashboard controller reporting three 16-bit signed axis values and one 8-bit button-state byte needs a report descriptor that declares exactly that layout: three Input items each with Report Size 16, Logical Minimum/Logical Maximum set to the actual signed range the sensor produces, inside an Application Collection under a vendor-defined Usage Page (since "three arbitrary sensor axes plus a button byte" doesn't map cleanly onto any standard Usage Page). Getting the Report Size or the signed/unsigned flag wrong here doesn't produce an enumeration failure — the device still enumerates and reports data — but every value the host reads back is bit-misaligned or has the wrong sign, which is a much harder class of bug to trace than an outright enumeration failure because nothing in the USB stack flags it as an error.
A firmware configuration utility that needs to occasionally read or write a calibration value from a custom HID device — infrequently, and not as part of the normal streaming data — is the textbook case for a Feature report rather than folding that value into the regular Input report stream, keeping the frequent data path and the occasional configuration path cleanly separated in the descriptor structure itself.
Design Considerations
- Match
Report SizeandReport Countexactly to the firmware's actual data layout, and verify the total report byte length the descriptor declares matches what the firmware actually sends on every transfer — a mismatch here is the most common source of silently misaligned data. - Set
Logical Minimum/Logical Maximumto the field's true numeric range, not a placeholder, since these values determine both the signed/unsigned interpretation and the valid range the host-side HID API will report — an incorrect range can cause legitimate values to be silently clamped or misinterpreted as negative. - Use a vendor-defined Usage Page (0xFF00–0xFFFF) for any field that doesn't genuinely map to a standard HID usage, rather than reusing a standard page's Usage ID for an unrelated purpose just because the bit width happens to fit — this produces a descriptor that's technically valid but semantically wrong, which can confuse any host-side tooling that does interpret standard usages generically.
- Validate the finished descriptor with a USB protocol analyser or a HID descriptor parser/validator tool before writing host-side parsing code, rather than debugging both the descriptor and the host application simultaneously — confirming the descriptor itself is well-formed first isolates which side of the interface a data-format bug actually lives on. This is the same "capture the actual wire behaviour before assuming firmware or host is at fault" principle covered in USB enumeration debugging, applied to report content rather than device/configuration descriptors.
- Decide between HID and an alternative USB device class based on required throughput and latency, not developer convenience — see the FAQ below for the trade-off against CDC and vendor-specific WinUSB interfaces.
- Custom HID device firmware: designing a report descriptor that correctly matches both firmware and host-side application code is exactly the kind of USB stack implementation work Zeus Design's embedded firmware team handles for custom peripheral products.
Common Mistakes
- Copying a keyboard or mouse report descriptor as a starting template and only changing the report size, without understanding the Usage Page/Usage ID/Collection structure being copied — the result often enumerates and "mostly works" while silently misreporting specific fields in ways that are hard to trace back to the descriptor.
- Mismatching the declared
Report Size/Report Countagainst what the firmware actually transmits, producing data that's bit-shifted or byte-misaligned on the host side despite the device enumerating without any error. - Forgetting the signed/unsigned distinction on numeric fields, causing values above the midpoint of the field's bit range to be silently reinterpreted as negative by host-side HID parsing libraries that respect the descriptor's declared logical range.
- Reusing a standard Usage Page's Usage ID for an unrelated custom field instead of using the vendor-defined range, producing a descriptor that parses without error but is semantically incorrect for any tooling that interprets standard usages.
- Skipping descriptor validation with a protocol analyser or parser tool, and instead debugging a data-format mismatch by iterating on host-side application code alone — often masking a descriptor-level bug rather than fixing it.
Frequently Asked Questions
- What's the difference between an Input, Output, and Feature report?
- An Input report carries data from the device to the host — sensor readings, button states, or any value the device is reporting. An Output report carries data from the host to the device, most commonly used for things like keyboard LED states, but equally applicable to any host-to-device control value in a custom design. A Feature report is bidirectional and typically used for configuration data that isn't part of the normal, frequent data stream — a polling rate setting or a calibration value the host reads or writes occasionally rather than every transfer. The correct choice for a given field is a design decision based on which direction the data flows and how frequently it changes, not an arbitrary label.
- Can I use a vendor-defined Usage Page instead of a standard one?
- Yes, and for most custom (non-keyboard, non-mouse, non-gamepad) HID devices this is the normal approach. The HID Usage Tables reserve the range 0xFF00 to 0xFFFF for vendor-defined Usage Pages specifically so a custom device isn't forced to misuse a standard page's semantics just to get HID-class enumeration and driver-free operation. Using a vendor-defined page means the host OS's generic HID driver still handles enumeration and low-level transport, but the application software interpreting the reports needs to know the vendor-specific field layout independently — there's no generic host-side interpretation of a vendor page's meaning the way there is for a standard keyboard or mouse usage.
- Why would I use HID instead of a CDC virtual serial port or a vendor-specific WinUSB interface for a custom device?
- HID's main advantage is driver-free operation across Windows, macOS, and Linux without any INF file, code-signed driver, or WinUSB descriptor set — the generic in-box HID driver binds automatically based on the device's class code alone, which materially simplifies distribution and end-user installation for many small embedded accessories. The trade-off is HID's report size and polling-interval constraints, which make it a poor fit for high-throughput or low-latency streaming applications; a CDC virtual serial port ([composite device design](/questions/usb-composite-device-descriptor-design) covers combining CDC with other interfaces) or a vendor-specific WinUSB interface is usually the better choice once the required data rate or latency exceeds what HID's report model comfortably supports.
References
Related Questions
What Is USB?
USB is a host-device protocol for PC connectivity and power delivery. Learn about descriptors, CDC-ACM, HID, DFU classes, and Full Speed vs High Speed.
How Do You Design USB Composite Device Descriptors for Multiple Interfaces?
USB composite devices expose multiple interfaces (CDC, HID, MSC) from one device. Learn Interface Association Descriptors and common driver-binding pitfalls.
Why Is My USB Device Failing Enumeration?
USB enumeration failures usually trace to one of five causes: descriptor errors, VID/PID conflicts, clock accuracy, D+ pull-up timing, or power draw.
What Is the Difference Between USB 2.0, USB 3.x, and USB4?
USB 2.0, USB 3.x, and USB4 compared: speed tiers, connector evolution, and which version and PHY to target for an embedded design.
How Do You Design a Galvanically Isolated USB Interface?
Designing an isolated USB interface: isolator IC selection, isolated USB power delivery, and the signal-integrity limits of isolating Full Speed USB.
How Do You Implement USB OTG (On-The-Go) in an Embedded Product?
USB OTG lets an embedded product act as host or device. Learn ID-pin detection, HNP/SRP role negotiation, dual-role firmware, and VBUS current limiting.