Universal Serial Bus (USB) is one of the most commonly used interfaces in modern devices, enabling seamless communication between a host (like a PC or Raspberry Pi) and a peripheral (like a keyboard, mouse, or custom hardware). While USB simplifies user experience with its plug-and-play capabilities, it is an intricate protocol with numerous layers of functionality beneath the surface. USB supports multiple device classes, including Human Interface Device (HID), Mass Storage, Audio, and Communication, making it incredibly versatile.
During my journey of learning about USB, I discovered that there is a lack of clear, detailed documentation explaining how USB works internally, especially from the perspective of a developer working with microcontrollers. Most resources abstract away the details, focusing instead on tools like CubeMX or pre-built libraries. While these tools are helpful, they often obscure the underlying mechanisms, leaving a significant gap in understanding.
To address this, I decided to write this blog post to share the knowledge I gained during my learning process. This post will walk you through how USB works internally and demonstrate how to implement a Custom HID (Human Interface Device) using an STM32 microcontroller, with a focus on register-level programming. By avoiding abstraction layers like CubeMX, this approach aims to give you a clear, hands-on understanding of the USB protocol and its inner workings.
Finally, to complete the learning experience, we will use a Raspberry Pi as the USB host to test and interact with our custom device. Letโs dive deep into USB internals and uncover how to create a Custom HID device step by step.
This post is structured into three parts to guide you step-by-step:
- The this part explains the basics of USB HID communication, focusing on theoretical concepts to build a solid foundation.
- The second part dives into the packet flow and format during USB HID enumeration, illustrating the communication between the host and the device.
- The third part is where we get hands-on, implementing a Custom HID device using the STM32 microcontroller with register-level programming.
Hardware Requirements
- STM32F103C6T6 (“Blue Pill”): A low-cost microcontroller with a USB peripheral.
- Raspberry Pi 4: Acts as a USB host for testing and communication. If you don’t have raspberry-pi you can use a linux PC.
- USB cable.
- Development Environment:
- Any text editor
- STM32 Reference Manual (RM0008).
- STM32 Blue pill datasheet
- Python (on Host) for writing host-side communication scripts.
- tcpdump in the host for debugging purpose
- Wireshark for packet analysis
What is USB
Universal Serial Bus (USB) is a standardized communication protocol that allows devices to exchange data with a host system (e.g., a computer or Raspberry Pi). Introduced in the mid-1990s, USB replaced older interfaces like serial and parallel ports, offering faster data transfer rates, hot-swapping capabilities, and a unified connector standard.
USB operates as a master-slave protocol, where the host (master) initiates and controls all communication with devices (slaves). It supports a variety of transfer types, such as:
- Control Transfers: Used for configuration and device management.
- Bulk Transfers: Designed for large, non-time-critical data (e.g., file transfers).
- Interrupt Transfers: Suitable for small, time-critical data (e.g., keyboard input).
- Isochronous Transfers: Used for real-time data streams (e.g., audio or video).
USB devices are identified and configured through descriptors that describe their functionality, capabilities, and endpoints.
What are USB Classes?
USB defines device classes to standardize the behavior of peripherals. These classes simplify communication by providing predefined specifications for specific device types. For example:
- Human Interface Device (HID): Includes devices like keyboards, mice, and game controllers.
- Mass Storage: Supports devices like USB flash drives and external hard drives.
- Audio: For sound devices like microphones and speakers.
- Communication: Includes network adapters and modems.
- Video: For webcams and video capture devices.
Each class has a Class Code, which is part of the device descriptor, enabling the host to load the appropriate driver. For example, HID devices use the 0x03 class code.
What is HID Class?
The Human Interface Device (HID) class is designed for devices that interact directly with humans, typically for input or control. HID simplifies data exchange by defining a standard HID Report Descriptor, which describes the format of the data sent or received.
Key Features of HID:
- Driver Support: Most operating systems (Windows, Linux, macOS) include built-in drivers for HID devices, eliminating the need for custom drivers.
- Flexibility: Supports a wide range of devices beyond traditional input peripherals, including sensors and industrial control equipment.
- Low Latency: Uses interrupt transfers for fast data exchange.
Examples of HID Devices:
- Keyboards, mice, joysticks
- Game controllers
- Barcode scanners
- Custom hardware for specific applications
HID devices communicate through structured reports, which are described by the HID Report Descriptor. This descriptor specifies data formats, including input and output fields, data sizes, and logical ranges.
Why Custom HID?
While standard HID devices like keyboards and mice follow predefined specifications, Custom HID allows developers to design devices with unique functionalities. This is particularly useful when the requirements don’t fit into the existing standard device definitions.
Applications of Custom HID:
- Custom Input Devices: Specialized keyboards for industrial environments or creative applications (e.g.gaming).
- Data Acquisition Systems: Devices that collect data from sensors and send it to the host for analysis.
- Control Systems: Hardware that controls machinery, robots, or other industrial equipment.
- Prototyping: Building and testing new concepts or hardware before scaling production.
Using Custom HID, you can define your own HID Report Descriptor to specify the type and structure of data exchanged between the device and the host. This flexibility makes it an excellent choice for projects requiring custom features, including unique input or output formats.
USB Descriptors
Descriptors are data structures that describe the device’s properties and capabilities to the host. The host retrieves these descriptors during enumeration.
Types of Descriptors:
- Device Descriptor: Provides basic information such as vendor ID, product ID, and supported USB version.
- Configuration Descriptor: Describes power requirements, interfaces, and endpoint configurations.
- String Descriptor: Provides human-readable information such as manufacturer and product name.
- HID Descriptor: Specific to HID devices, it points to the HID Report Descriptor.
- HID Report Descriptor: Defines the format and structure of data exchanged between the host and the device.
USB Endpoints
USB endpoints are communication channels on a USB device used to transfer data between the device and the host. Each endpoint is identified by an endpoint number and direction (IN or OUT). The STM32 blue pill support upto 8 endpoints. Endpoint 0 is reserved for control transfers, while other endpoints are used for different types of data transfers: bulk (large data), interrupt (small, periodic data), and isochronous (timed data, like audio or video). Each endpoint has a maximum packet size and is described in the device’s descriptors.
USB Interfaces
A USB interface is a logical grouping of related endpoints within a USB device, representing a specific functionality. It is part of a device configuration, with each interface defined by an interface descriptor that specifies details like the interface number, class (e.g., HID, Audio), subclass, protocol, and the endpoints it uses. A single USB device can have multiple interfaces to support different features, such as printing, scanning, or audio playback, as in a multifunction device. Interfaces can also have alternate settings to adjust their behavior, such as changing bandwidth requirements. During enumeration, the host queries the interface descriptors to identify the device’s capabilities and establish communication.
USB PID and VID
The Product ID (PID) and Vendor ID (VID) are two important details of a USB device during the device enumeration. These two values are used while implementing the USB device driver.
- Vendor ID (VID): A unique 16-bit identifier assigned to a manufacturer by the USB Implementers Forum.
- Product ID (PID): A 16-bit identifier assigned by the manufacturer to distinguish different products.
Endpoint Direction
In USB communication, endpoints are unidirectional and categorized based on the direction of data flow relative to the host: IN (device to host) and OUT (host to device). An IN endpoint is used when the USB device sends data to the host, such as a keyboard transmitting keystrokes or a custom HID device sending sensor data. The host polls these endpoints to check for new data periodically. On the other hand, an OUT endpoint is used when the host sends data to the device, such as configuration commands during enumeration or control signals to update an LED’s state on a custom HID device.
What is next
We have thoroughly covered the essential details of USB Custom HID communication. Now, letโs move on to the actual packet flow, exploring each message in detail and understanding the purpose of every packet in the next part of this blog series.
Great post. I was checking constantly this blog and I’m impressed!
Very useful info specifically the last part ๐ I care for
such info a lot. I was looking for this particular
info for a very long time. Thank you and good luck.