What Is the HID Keyboard Report Format?

The 8-byte structure of an HID keyboard report: the modifier byte, the reserved byte, and 6 keycode slots. How to send a report from STM32 middleware.

Updated 5 min read
Đọc bằng English Tiếng Việt
USB cover

An HID keyboard report is the packet firmware sends to the host any time a key’s state changes. The easy thing to get wrong here is that this report doesn’t contain ASCII characters. Firmware doesn’t send the letter a. It sends the usage ID for the a key, and the host decides the final character based on the current keyboard layout and the modifier value.

Many HID keyboards support the 8-byte boot keyboard format because it’s simple and works well with firmware, BIOS, and UEFI. Some more advanced keyboards use their own Report Protocol instead, an NKRO bitmap or a consumer control report, for example.

1. The 8-byte structure

Offset  Content
──────────────────────────────────────────
  0     Modifier byte
  1     Reserved (0x00)
  2     Keycode 1
  3     Keycode 2
  4     Keycode 3
  5     Keycode 4
  6     Keycode 5
  7     Keycode 6

In C:

typedef struct {    uint8_t modifier;       /* Modifier keys (Ctrl, Shift, Alt, GUI) */    uint8_t reserved;       /* Always 0x00                           */    uint8_t keycodes[6];    /* Up to 6 simultaneous keycodes         */} HID_KeyboardReport_t;

Here’s a real report, captured with Wireshark, from pressing “1” on a keypad: 00 00 1E 00 00 00 00 00. 0x1E is the HID usage for the 1/! key in the Keyboard Usage Page.

2. The modifier byte

The modifier byte is a bitmask, with each bit tied to one modifier key:

Bit Mask Key
0 0x01 Left Ctrl
1 0x02 Left Shift
2 0x04 Left Alt
3 0x08 Left GUI
4 0x10 Right Ctrl
5 0x20 Right Shift
6 0x40 Right Alt
7 0x80 Right GUI

An example of pressing Left Shift + A:

HID_KeyboardReport_t report = {0};report.modifier    = 0x02;    /* Left Shift */report.keycodes[0] = 0x04;    /* HID keycode for 'a' */

3. The keycode array: 6-key rollover

The 6 keycode slots allow up to 6 keys, on top of the modifiers, to be pressed at once. This is also the limit of the Boot Protocol.

/* On keypress: find an empty slot and fill it in */for (uint8_t i = 0; i < 6; i++) {    if (report.keycodes[i] == 0x00) {        report.keycodes[i] = new_keycode;        break;    }}/* On key release: find that keycode and clear it */for (uint8_t i = 0; i < 6; i++) {    if (report.keycodes[i] == new_keycode) {        report.keycodes[i] = 0x00;        break;    }}

No sorting or compaction needed. Keycode 0x00 means “no key here.” The host only cares about non-zero values.

4. ErrorRollOver

When firmware can’t determine a valid key combination, a diode-less matrix with more than 6 keys pressed, for example, the keyboard’s Boot Protocol has an ErrorRollOver usage, 0x01:

The ErrorRollOver report:  00 00 01 01 01 01 01 01
Followed by a null report: 00 00 00 00 00 00 00 00

A null report should follow right after ErrorRollOver, so the host knows the state has been reset. This isn’t a software error, and the host doesn’t display any message or special character for it either. So from the end user’s perspective, it has no visible effect at all.

The only way to actually confirm this report happened is with a USB protocol analyzer, a Wireshark capture, or a similar dedicated USB debugging tool.

5. Sending a report from application code

The example below uses the ST USB Device Library. API names and calling conventions can differ depending on the middleware.

extern USBD_HandleTypeDef hUsbDeviceFS;void Keyboard_SendReport(const HID_KeyboardReport_t *report){    USBD_HID_SendReport(&hUsbDeviceFS,                        (uint8_t *)report,                        sizeof(HID_KeyboardReport_t));}

6. Reporting when no key is pressed

A keypress is only half the story. The host also needs to know when a key has been released.

HID_KeyboardReport_t empty_report = {0};Keyboard_SendReport(&empty_report);

If firmware only sends a report on keypress but never sends an empty report once every key is released, the host will assume the key is still held down and repeat it endlessly.

A common approach is “tap-style”: right after each key-down report, firmware immediately sends a null report on the very next call, instead of waiting for an actual release event. This lets firmware control its own repeat rate, rather than letting the OS apply its own typematic repeat at whatever pace it defaults to.

7. Rollover and ghosting

6-key rollover (6KRO) is the Boot Protocol’s limit: up to 6 keycodes plus modifiers. If you need to support more simultaneous keys than that, design a custom Report Descriptor instead of trying to squeeze it into the 8-byte boot report.

N-key rollover (NKRO) has no limit on simultaneous keys, but it needs a different HID Report Descriptor (a bitmap instead of an array) and isn’t compatible with Boot Protocol.

Ghosting is a hardware phenomenon on diode-less matrices. It’s a hardware problem, and has nothing to do with the HID report format itself.

Public references

Found this article useful?

Share, give feedback, or support if you find this content valuable.

Feedback

Nội dung liên quan

Một số bài viết, ghi chú hoặc project có liên quan đến nội dung bạn vừa đọc.

Biến note thành bài viết hoàn chỉnh

Notes là nơi ghi nhanh khái niệm.