In the first part of this blog, we laid the groundwork by discussing the fundamentals of USB communication and the concepts behind Custom HID devices. With that foundation in place, it’s time to delve deeper into the practical aspect of USB communication—packet flow during HID enumeration.
Enumeration is the process where the USB host detects, identifies, and configures a newly connected device. During this phase, various packets are exchanged between the host and the device, each serving a specific purpose. Understanding these packets, their formats, and their roles is crucial for debugging, optimizing, and creating USB devices from scratch.
In this part, we’ll dissect the packet flow step by step, starting with the control transfer requests sent by the host, followed by the device’s responses. We’ll explore the purpose of each packet type, analyze their structure, and explain how they contribute to the HID enumeration process. By the end of this section, you’ll gain a clear understanding of how the USB host and device communicate at the protocol level, paving the way for implementing your own Custom HID device.
USB Enumeration Process in Brief
When the STM32 microcontroller powers on (POR), it initializes the USB peripheral and waits for the device to be connected. Upon plugging in, the host issues a USB reset to prepare the device for communication, followed by a series of standard requests: the Device Descriptor Request to identify basic device properties, the Set Address Request to assign a unique address, the Device Qualifier Request to query high-speed support (optional for full-speed devices), and the Device Configuration Request to activate the device. The host may also request String Descriptors for human-readable information(This is basically 4 requests and their responses. Language ID, Product Name, Manufacture Name and Serial number) and the Set Configuration Descriptor to finalize the setup. Additionally, during HID-specific enumeration, the host sends Set Idle Request to configure idle behavior and HID Report Descriptor Request to fetch the HID report format.
The enumeration process, as described above, works in the sequence shown in the diagram on the right. In the diagram, I have outlined the flow of each request and its corresponding response, providing a visual representation of how the host and device communicate during enumeration. Each step, from the device descriptor request to the set configuration request, is illustrated to help you understand the sequence of interactions that occur.

USB 2.0 Specification
The USB specification documents are available on the official USB website, usb.org. Since we are working with USB 2.0, I have already downloaded the document for this version(Download). You can access it directly from the official site as well. In this document, every requests and responses are described in detail. The request I got from the host and the response I have sent for each requests are shown below.
The USB Request Format
A USB request is sent by the host to the device during communication, particularly during the enumeration process. It consists of an 8-byte setup packet, which is the first stage of a control transfer. This setup packet contains important fields that help define the specific operation the host wishes to perform on the device.
Name of Field | Size | Description |
---|---|---|
bmRequestType | 1 byte | Bit 7: Data Direction 0 – OUT (Host to device) 1 – IN (Device to host) Bit 6-5: Type of request 00 – Standard Request 01 – Interface Request 10 – Vendor Request 11 – Reserved D4-0: Recipient 0 – Device 1 – Interface 2 – Endpoint 3 – Other 4..31 – Reserved |
bRequest | 1 byte | specifies the specific request that the host is making to the device |
wValue | 2 byte | The meaning of this field depends on the specific request. It typically holds the index of the descriptor or a value related to the request. |
wIndex | 2 byte | This field provides additional context for the request. It can be used to specify an interface or endpoint, depending on the type of request. |
wLength | 2 byte | Defines the length of the data to be transferred in the response |
Type of USB requests
- GET_DESCRIPTOR – Retrieves descriptors such as Device, Configuration, String, Interface, Endpoint, and HID descriptors.
- SET_ADDRESS – Assigns a unique address to the device for communication.
- SET_CONFIGURATION – Selects a specific configuration for the device.
- GET_CONFIGURATION – Retrieves the currently active configuration.
- GET_STATUS – Fetches the status of the device, interface, or endpoint (e.g., self-powered or remote-wakeup support).
- CLEAR_FEATURE – Clears a specific feature (e.g., endpoint halt).
- SET_FEATURE – Sets a specific feature (e.g., enable remote wakeup or halt an endpoint).
- GET_INTERFACE – Retrieves the currently active alternate setting for an interface.
- SET_INTERFACE – Selects an alternate setting for an interface.
Requests used for custom HID device enumeration
Device Descriptor Request
The device descriptor request is a type of GET_DESCRIPTOR. The device descriptor is the data structure that provides essential information about a USB device to the host during the enumeration process. This is the initial request with maximum packet size (64 bytes) that the endpoint 0 can support since the host doesn’t know about the descriptor size. The host may use some standard size during this request like 8, 16, 32.. based on the USB version. A sample request and response are shown below.
Request: {
80, // (0x80) Requesting an IN, standard, Device request
06, // (0x06) Request is GET_DESCRIPTOR
00, 01, // (0x0100) Request for Device descriptor
00, 00, // (0x0000) Not used here
40, 00 // (0x0040) Expected length 64 bytes
}
Response: {
12, // Length of response - 18 bytes (1 byte)
01, // Type of descriptor - device descriptor (1 byte)
00, 20, // What version of USB supports - (0x0200) USB2.0 (2 bytes)
00, // Device class - Device class defined in interface descriptor (1 byte)
00, // Device subclass
00, // Device protocol
40, // Maximum packet size - 64 bytes (1 byte)
83, 04, // Vendor ID - 16 bit number assigned for the manufacture by USB.org (2 bytes)
5A, 57, // Product ID - 16 bit number model ID assigned by vendor or product (2 bytes)
00, 20 // Device release number - eg 0x0200 for 2.0 (2 bytes)
01, // Index of string manufacture (1 byte)
02, // Index of string product descriptor (1 byte)
03, // index of string serial number (1 byte)
01 // Number of configurations - 1 (1 byte)
}
Set Address Request
After receiving the device descriptor, the host assigns a unique address to the USB device using a SET_ADDRESS request. This is a standard USB control transfer that allows the host to communicate with the device on the USB bus. While the host does not expect any data response for this request, it ensures the device can handle subsequent requests after the address is set.
Request: {
00, // (0x00) Requesting an OUT, standard, Device request
05, // (0x05) Request is SET_ADDRESS
02, 00, // (0x0002) The address to be set is 2
00, 00, // (0x0000) Reserved, set as 0
00, 00 // (0x0000) Expected length is 0 byte
}
Response: {}
Device Descriptor Request
During the first request, the device may respond with either a full or partial descriptor based on the request length. From this response, the USB host determines the actual size of the descriptor and issues a second request to retrieve it. In this stage, the device is expected to send the complete device descriptor. In our case, since we already sent the entire descriptor during the first request, we simply send the same response again for the second request.
Request: {
80, // (0x80) Requesting an IN, standard, Device request
06, // (0x06) Request is GET_DESCRIPTOR
00, 01, // (0x0100) Request for Device descriptor
00, 00, // (0x0000) Not used here
12, 00 // (0x0012) Expected length 18 bytes
}
Response: {
12, // Length of response - 18 bytes (1 byte)
01, // Type of descriptor - device descriptor (1 byte)
00, 20, // What version of USB supports - (0x0200) USB2.0 (2 bytes)
00, // Device class - Device class defined in interface descriptor (1 byte)
00, // Device subclass
00, // Device protocol
40, // Maximum packet size - 64 bytes (1 byte)
83, 04, // Vendor ID - 16 bit number assigned for the manufacture by USB.org (2 bytes)
5A, 57, // Product ID - 16 bit number model ID assigned by vendor or product (2 bytes)
00, 20 // Device release number - eg 0x0200 for 2.0 (2 bytes)
01, // Index of string manufacture (1 byte)
02, // Index of string product descriptor (1 byte)
03, // index of string serial number (1 byte)
01 // Number of configurations - 1 (1 byte)
}
Qualifier Descriptor Request
The device qualifier descriptor describes information about a high-speed capable device that would change if the device were operating at the other speed. For example, if the device is currently operating at full-speed, the device_qualifier returns information about how it would operate at high-speed and vice-versa.
If a full-speed only device receives a GET_DESCRIPTOR request for a device qualifier, it must respond with a STALL handshake.
Request: {
80, // (0x80) Requesting an IN, standard, Device request
06, // (0x06) Request is GET_DESCRIPTOR
00, 06, // (0x0600) Request for device qualifier
00, 00, // (0x0000) Not used here
0A, 00 // (0x000A) Expected length 10 bytes
}
Response: {
0A, // (0x0A) Length of response is 10 bytes
06, // (0x06) This is qualifier response
00, 02, // (0x0002) USB spec version number (0x0200 for 2.0)
00, // (0x00) Class code
00, // (0x00) Sub class code
00, // (0x00) Protocol code
40, // (0x40) Maximum packet size 64 bytes
01, // (0x01) Number of other speed configurations
00 // (0x00) Reserved
}
Configuration Descriptor Request
The Standard Configuration Descriptor is a USB descriptor that provides information about a device’s specific configuration. It is retrieved by the host using the GET_DESCRIPTOR request. This descriptor includes details about the device’s power requirements and the number of interfaces and endpoints it supports in a specific configuration.
This two-step process is standard in USB enumeration to handle devices with varying descriptor sizes. By first requesting 9 bytes, the host avoids retrieving unnecessary data and ensures it only requests the exact amount needed in the second step.
The configuration descriptor for custom HID interface composed of 4 parts
- Configuration Descriptor (9 bytes)
- Interface Descriptor (9 bytes)
- HID Descriptor (9 bytes)
- Endpoint Descriptor (7 bytes x 2 endpoints)
Request: {
80, // (0x80) Requesting an IN, standard, Device request
06, // (0x06) Request is GET_DESCRIPTOR
00, 02, // 0x0200 configuration descriptor
00, 00, // Not used
29, 00 // 0x0009 (9 bytes) in the first request and 0x0029 (41 bytes) in the second request
}
Response: {
// Configuration Descriptor (9 bytes)
09, // (0x09) Size of configuration descriptor (9 bytes)
02, // (0x02) Descriptor type (Configuration)
29, 00 // (0x0029) Total size of all descriptors in this configurations (41 bytes)
01, // (0x01) Number of interfaces (1)
01, // (0x01) Value to select this configuration (1)
00, // (0x00) Index of string descriptor for this configuration (0 - means no string descriptor)
C0, // (0xC0) Configuration char (6th bit - self powered)
32, // (0x32) Maximum power consumption of the USB device from the bus in
// this specific configuration when the device is fully operational (100mA).
// Interface Descriptors (9 bytes)
09, // (0x09) Size of interface descriptor (9 bytes)
04, // (0x04) Descriptor type (Interface)
00, // (0x00) Interface number
00, // (0x00) Alternate settings
02, // (0x02) Number of endpoints (2)
03, // (0x03) Interface Class (3 - HID)
00, // (0x00) No subclass
00, // (0x00) No protocol
00, // (0x00) Index of string descriptor for this interface (0 - means no string descriptor)
// HID Descriptor
09, // (0x09) Length of HID descriptor
21, // (0x21) Descriptor type HID
11, 01, // (0x0111) HID Class specification version in BCD (1.11)
00, // (0x00) Target country code (0 - not localized)
01, // (0x01) Number of additional descriptor (1)
22, // (0x22) Type of additional descriptor (report descriptor)
03, 00, // (0x0003) Length of additional descriptor (3)
// Type and length of additional descriptor can be repeated multiple times upto number of additional descriptors
// Endpoint descriptors 1 (IN) (7 bytes)
07, // 7 (Size of the Endpoint Descriptor).
05, // 5 (Endpoint Descriptor).
81, // 0x81 (Endpoint 1, IN).
03, // 0x03 (Interrupt transfer).
02, 00, // Maximum packet size
05, // 0x05 polling interval 5ms
// Endpoint descriptors 2 (OUT) (7 bytes)
07, // 7 (Size of the Endpoint Descriptor).
05, // 5 (Endpoint Descriptor).
01, // 0x01 (Endpoint 1, OUT).
03, // 0x03 (Interrupt transfer).
02, 00, // Maximum packet size
05 // 0x05 polling interval 5ms
}
Language ID String Request
The Language ID request and response help the host identify which languages the device can use for string descriptors. After receiving the language ID(s), the host can then request additional string descriptors (e.g., manufacturer name, product name) in the appropriate language.
This mechanism ensures that devices support internationalization and localization of string descriptors, allowing for a customized user experience based on the language preferences of the host system.
Request: {
80, // (0x80) Requesting an IN, standard, Device request
06, // (0x06) Request is GET_DESCRIPTOR
00, 03, // 0x0300 String descriptor
00, 00, // Index is 0 (language descriptor)
FF, 00 // 0x00FF maximum 255 bytes
}
Response: {
04, // Length of response (4 bytes)
03, // String descriptor
09, 04 // 0x0409 for english
}
Product String Request
The product string typically identifies the product name of the device (e.g., the name of a keyboard or mouse) and is part of the String descriptors category, which provides human-readable information about the USB device.
Request: {
80, // (0x80) Requesting an IN, standard, Device request
06, // (0x06) Request is GET_DESCRIPTOR
02, 03, // 0x0302 Product string descriptor (index 2 is mapped to product string in the device descriptor)
09, 04, // Index is 0 (language descriptor)
FF, 00 // 0x00FF maximum 255 bytes
}
Response: {
3A, // (0x3A) Total length of response
03, // (0x03) String descriptor
53, 00, 54, // Corresponding to "STM32 Custom Human Interface"
00, 4D, 00, 33, 00,
32, 00, 20, 00, 43,
00, 75, 00, 73, 00,
74, 00, 6F, 00, 6D,
00, 20, 00, 48, 00,
75, 00, 6D, 00, 61,
00, 6E, 00, 20, 00,
69, 00, 6E, 00, 74,
00, 65, 00, 72, 00,
66, 00, 61, 00, 63,
00, 65, 00
}
Manufacturer String Request
Request: {
80, // (0x80) Requesting an IN, standard, Device request
06, // (0x06) Request is GET_DESCRIPTOR
01, 03, // 0x0301 Manufacture string descriptor (index 1 is mapped to manufacture string in the device descriptor)
09, 04, // Index is 0 (language descriptor)
FF, 00 // 0x00FF maximum 255 bytes
}
Response: {
26, // (0x26) Total length of response
03, // (0x03) String descriptor
53, 00, 54, // Corresponding to "STMicroelectronics"
00, 4D, 00, 69, 00,
63, 00, 72, 00, 6F,
00, 65, 00, 6C, 00,
65, 00, 63, 00, 74,
00, 72, 00, 6F, 00,
6E, 00, 69, 00, 63,
00, 73, 00
}
Serial Number String Request
is used to retrieve the serial number of a USB device. It is part of the String descriptors mechanism, allowing the host to request additional identifying information about a device, such as its serial number, which is often unique to each device. This is useful for identifying and distinguishing devices of the same type or model.
Request: {
80, // (0x80) Requesting an IN, standard, Device request
06, // (0x06) Request is GET_DESCRIPTOR
03, 03, // 0x0303 Serial number string descriptor (index 3 is mapped to serial number in the device descriptor)
09, 04, // Index is 0 (language descriptor)
FF, 00 // 0x00FF maximum 255 bytes
}
Response: {
10, // (0x10) Total length of response
03, // (0x03) String descriptor
31, 00, 32, // Corresponding to "1234567890ABCDEF"
00, 33, 00, 34, 00,
35, 00, 36, 00, 37,
00, 38, 00, 39, 00,
30, 00, 41, 00, 42,
00, 43, 00, 44, 00,
45, 00, 46, 00
}
Set Configuration Request
The Set Configuration Request is a standard USB request sent by the host to configure a USB device after it has been enumerated. This request allows the host to select one of the available configurations provided by the device, which defines the device’s power consumption, interfaces, and endpoint descriptors. It is crucial because the device may have multiple configurations (e.g., different modes of operation or power settings), and the host uses this request to choose the appropriate configuration for communication. The request is sent on the control endpoint using the bRequest value 0x09 with the wValue specifying the configuration index.
Request: {
00, // Host-to-Device, Standard, Device recipient.
09, // SET_CONFIGURATION request.
01, 00, // Configuration value (0x0001 specified in configuration descriptor) to set.
00, 00, // Not used
00, 00 // No data expected
}
Response: {}
Set Idle Request (HID Class specific)
This request is used to limit the reporting frequency of an interrupt in endpoint. Specifically, this request causes the endpoint to NAK any polls on an interrupt in endpoint while its current report remains unchanged. In the absence of a change, polling will continue to be NAKed for a given time-based duration
Request: {
21, // Host-to-Device, Class, Interface recipient.
0A, // SET_IDLE request (HID-specific).
00, 00, // High byte: Idle duration (0 for infinite). Low byte: Report ID (0 for all).
00, 00, // Interface number (usually 0 for single interface devices).
00, 00 // No data expected
}
Response: {}
HID Report Request (HID Class Specific)
If we are going to send/receive raw data, we don’t need to define any HID report. So just define as follows
Request: {
81, // Device-to-Host, Standard, Interface recipient.
06, // GET_DESCRIPTOR request.
00, 22, // Descriptor Type (0x2200 = HID Report Descriptor).
00, 00, // Interface number (usually 0 for single interface devices).
03, 00 // Expected length 3 bytes
}
Response: {
A1, 01, // Collection (Application)
C0 // End collection
}