Understanding the USB Protocol
A Practical Developer's Guide, from Signaling to Firmware
From electrical signaling to enumeration traces and firmware implementation: a from-scratch, byte-level explanation of the USB protocol for embedded and firmware developers.
Table of Contents
PART 1 — How USB Works (Physical layer → packets → transactions → transfers)
1. Introduction
Universal Serial Bus (USB) is the standard that connects almost every peripheral to a computer today: mice, keyboards, storage devices, audio interfaces, and countless embedded gadgets. For application developers, USB is invisible — plug in a device and it works. For firmware developers, USB is a protocol that has to be implemented, byte by byte, inside a microcontroller.
This tutorial explains USB from the ground up, and then goes further than most introductions by working through real byte-level examples: an actual token packet, an actual SETUP request, an actual device descriptor, and a full enumeration trace. The goal is to give you a mental model solid enough to read a USB peripheral's reference manual, follow a USB analyzer capture, or write a bare-metal USB device driver.
USB is a host-centric bus. There is exactly one host and one or more devices. Devices never initiate communication on their own; the host always starts every transaction. This single design decision shapes almost everything else about the protocol.
The tutorial is organized into five parts: how the bus works electrically and at the packet level; how a device becomes recognized by the host; how bulk data actually moves once a device is set up; what a firmware implementation needs to provide; and how to read and debug a real enumeration trace.
2. Architecture Overview
It helps to think of a USB device's firmware as being organized into a small number of layers, each responsible for a different level of abstraction:
● Physical / signaling layer — turns bits into voltage transitions on the D+ and D- wires, and back again.
● Packet layer — groups bits into well-formed packets with identifiers, addresses, and error-checking codes.
● Protocol layer (transactions and transfers) — combines packets into meaningful exchanges: "read this data", "here is data for you", "that request is not supported".
● Device / class layer — the application logic that decides what the device actually does: a mouse reporting movement, a mass-storage device serving disk blocks, and so on.
A microcontroller with a built-in USB peripheral typically handles the physical layer and much of the packet layer in hardware, leaving the firmware responsible for protocol-level decisions and the device/class behavior. Part 4 of this tutorial returns to exactly where that line is usually drawn.
3. The Physical Layer
3.1 Differential Signaling
USB (at Low Speed and Full Speed) uses two wires, D+ and D-, driven as a differential pair. Instead of comparing a single signal wire to ground, the receiver compares D+ to D- directly, which makes the signal far more resistant to electrical noise.
The bus has a small number of defined states, based on the relative voltages of D+ and D-:
● J state — the idle / logic-high differential state for the device's current speed.
● K state — the opposite differential state, used to encode transitions.
● Single-Ended Zero (SE0) — both D+ and D- are pulled low at the same time; used to signal a Reset or the End-Of-Packet condition.
● Single-Ended One (SE1) — both lines high at once; not valid during normal operation, and normally indicates an error or disconnect.
Which physical line represents a logic "1" versus "0" depends on the device's speed class (Low Speed and Full Speed use opposite pull-up conventions), which is also how the host initially detects a newly attached device's speed — by seeing whether D+ or D- is pulled high through a 1.5 kΩ resistor inside the device.
3.2 NRZI Encoding
USB does not send raw bits as direct voltage levels the way a UART might. Instead it uses Non-Return-to-Zero Inverted (NRZI) encoding: a logical "0" bit causes a transition (J to K, or K to J); a logical "1" bit causes no transition — the line stays in its current state.
NRZI keeps the signal largely self-clocking, since transitions let the receiver's clock-recovery circuit stay locked to the sender. A long run of "1" bits produces no transitions at all, which would let the receiver's clock drift — solved by bit stuffing.
3.3 Bit Stuffing
To guarantee a transition often enough for clock recovery to stay locked, the transmitter inserts an extra "0" bit after every six consecutive "1" bits in the data stream, before NRZI encoding is applied. The receiver applies the same rule in reverse, discarding a stuffed bit after every run of six "1"s it decodes. Bit stuffing is transparent to firmware — it never sees the stuffed bits — but it is why a raw oscilloscope capture of a packet doesn't map one-to-one with the packet's logical bits.
3.4 Speed Classes
|
Speed |
Data Rate |
Typical Use |
|
Low Speed (LS) |
1.5 Mbit/s |
Simple HID devices: keyboards, mice |
|
Full Speed (FS) |
12 Mbit/s |
Most general-purpose peripherals |
|
High Speed (HS) |
480 Mbit/s |
USB 2.0 storage, cameras, audio interfaces |
The speed class is signaled electrically as soon as the device is attached, letting the host controller configure its own transceiver before any packets are exchanged. Part 4 covers why High Speed needs a further negotiation step beyond this initial pull-up detection.
4. Packets: The Basic Unit of Communication
Every exchange on the bus is built from packets, each with the same overall shape: a SYNC field (a fixed bit pattern letting the receiver's clock-recovery circuitry lock on), a PID byte (identifying the packet type), packet-specific fields, and an EOP (End Of Packet, a defined Single-Ended-Zero condition).
4.1 The PID Byte
The PID byte is actually only 4 meaningful bits, sent twice: once normally and once bitwise-inverted, giving 8 transmitted bits. This redundancy lets the receiver immediately detect a corrupted PID. The table below gives the byte value of every defined PID — the value you'd see in a register or FIFO dump, written the normal way (MSB on the left). How that byte's bits actually get put on the wire, bit by bit, is a separate question covered in Section 9.
|
PID Name |
Category |
4-bit Code |
Transmitted Byte |
|
OUT |
Token |
0001 |
0xE1 |
|
IN |
Token |
1001 |
0x69 |
|
SOF |
Token |
0101 |
0xA5 |
|
SETUP |
Token |
1101 |
0x2D |
|
DATA0 |
Data |
0011 |
0xC3 |
|
DATA1 |
Data |
1011 |
0x4B |
|
DATA2 (HS only) |
Data |
0111 |
0x87 |
|
MDATA (HS only) |
Data |
1111 |
0x0F |
|
ACK |
Handshake |
0010 |
0xD2 |
|
NAK |
Handshake |
1010 |
0x5A |
|
STALL |
Handshake |
1110 |
0x1E |
|
NYET (HS only) |
Handshake |
0110 |
0x96 |
|
PING (HS only) |
Special |
0100 |
0xB4 |
|
SPLIT (HS only) |
Special |
1000 |
0x78 |
|
PRE / ERR |
Special |
1100 |
0x3C |
Note: These byte values are what a raw endpoint FIFO dump or a USB peripheral's PID register will show. They are constant across every USB device in the world — they're part of the specification, not something a device chooses. A logic analyzer trace of the wire itself will instead show these same bits in their LSB-first transmission order, as Section 9.1 shows for the IN token's PID.
5. Token Packets
A token packet is always sent by the host and marks the start of a transaction. It contains a PID, a 7-bit ADDR (device address 0–127), a 4-bit ENDP (endpoint number 0–15), and a 5-bit CRC covering ADDR and ENDP together.
|
Token |
Meaning |
|
OUT |
Host is about to send data to the device. |
|
IN |
Host is requesting the device send data to it. |
|
SETUP |
Host is starting a control transfer with a standard 8-byte request. |
|
SOF |
Start-of-Frame — a periodic timing/heartbeat packet, carrying an 11-bit frame number instead of ADDR/ENDP. |
6. Data Packets
A data packet carries the payload — from 0 up to the endpoint's configured maximum packet size. It consists of the PID (DATA0 or DATA1), the payload bytes, and a 16-bit CRC covering the payload.
USB alternates between DATA0 and DATA1 PIDs across successive data packets on the same endpoint ("data toggle"). If the device expects DATA1 but the host resends DATA0 — because it never saw an ACK for a previous attempt — the mismatch reveals a retransmission, letting a duplicate be discarded safely without any explicit sequence-number field.
7. Handshake Packets
A handshake packet is the simplest kind — just a PID and nothing else — and it reports the outcome of a transaction:
|
Handshake |
Meaning |
|
ACK |
Data was received correctly. |
|
NAK |
Device is not currently ready to send or accept data; the host should retry later. Not an error. |
|
STALL |
The endpoint cannot process the request at all — often an unsupported request or a real error condition. |
|
NYET |
"Not Yet" — High-Speed only; data received, but the device can't accept more yet (used in split transactions). |
NAK is routine — a keyboard endpoint NAKs almost every time the host polls it, since there's usually no new key event to report. STALL is closer to an error and typically requires software intervention (often an explicit CLEAR_FEATURE(ENDPOINT_HALT) request) to clear.
8. Transactions
A transaction is one token packet plus the data and/or handshake packets that follow it — the smallest complete unit of USB communication.
8.1 IN Transaction (device → host)
● Host sends an IN token.
● Device responds with a DATA packet (or a NAK/STALL handshake if it has nothing to send, or an error).
● If data was sent, the host responds with an ACK once it has received the data correctly.
8.2 OUT Transaction (host → device)
● Host sends an OUT token.
● Host sends a DATA packet.
● Device responds with ACK, NAK, or STALL.
8.3 SETUP Transaction
● Host sends a SETUP token.
● Host sends a DATA0 packet containing an 8-byte standard request structure.
● Device must respond with ACK — a SETUP transaction cannot be NAK'd.
SETUP transactions always use DATA0, and the device must accept them, because SETUP is how the host begins a control transfer, and every USB device must always be ready to respond to control transfers on endpoint 0.
9. Byte-Level Packet Walkthrough
This section works through what actually goes on the wire — as opposed to what firmware sees — for a token packet and for complete IN and OUT transactions.
9.1 An Actual Token Packet
Take an IN token addressed to device 5, endpoint 1. Fields, in transmission order (each sent LSB-first):
|
Field |
Size |
Value |
Binary (as transmitted, LSB first) |
|
SYNC |
8 bits |
fixed pattern |
00000001 |
|
PID |
8 bits |
IN = 0x69 |
10010110 |
|
ADDR |
7 bits |
5 |
1010000 |
|
ENDP |
4 bits |
1 |
1000 |
|
CRC5 |
5 bits |
computed over ADDR+ENDP |
(hardware-generated) |
|
EOP |
~3 bit times |
SE0, SE0, J |
— |
Note: CRC5 is intentionally shown as "hardware-generated" rather than a specific bit pattern here: on every real USB peripheral (host or device side) this field is generated and checked automatically by hardware, per the algorithm in USB 2.0 specification Chapter 8. Firmware essentially never touches it directly, so this tutorial won't risk giving you a hand-derived value to memorize — the important part is knowing which 11 bits it protects and where it sits in the packet.
Firmware never sees SYNC, the raw NRZI-encoded bits, bit-stuffing, or CRC5/CRC16 directly — a real USB peripheral strips all of that in hardware and simply tells firmware "a valid IN token addressed to endpoint 1 arrived" (usually via an interrupt flag and an endpoint/address register), or silently discards the packet if a CRC check failed.
9.2 A Complete IN Transaction on the Wire
Suppose the host wants to read 4 bytes from endpoint 1 of device 5, and the device currently has data ready:
● 1. Host → Device: IN token (PID 0x69, ADDR=5, ENDP=1, CRC5)
● 2. Device → Host: DATA1 packet (PID 0x4B, 4 payload bytes, CRC16)
● 3. Host → Device: ACK handshake (PID 0xD2)
If the device had nothing ready, step 2 would instead be a NAK handshake (PID 0x5A) and the transaction would end there — the host simply tries again on its next scheduled poll of that endpoint.
9.3 A Complete OUT Transaction on the Wire
● 1. Host → Device: OUT token (PID 0xE1, ADDR=5, ENDP=2, CRC5)
● 2. Host → Device: DATA0 or DATA1 packet (PID depends on toggle state, payload bytes, CRC16)
● 3. Device → Host: ACK (0xD2), NAK (0x5A), or STALL (0x1E) handshake
Comparing 9.2 and 9.3 side by side is a good way to internalize the core asymmetry in USB: for an IN transaction the device is the one sending data; for an OUT transaction the host is.
10. Frames and Bus Bandwidth
The host periodically broadcasts a Start-of-Frame (SOF) token to every device on the bus — every 1 ms at Full/Low Speed, or every 125 µs (a "microframe") at High Speed. SOF gives devices a shared sense of timing, which matters for isochronous transfers like audio, and marks the boundary the host uses to schedule transactions.
Because the bus is shared, the host controller performs bandwidth allocation: deciding how many transactions of each type fit inside a single frame. Isochronous and interrupt endpoints reserve a slice of every frame's bandwidth when the device is configured; control and bulk transfers use whatever is left over.
11. Transfer Types
A transfer is a higher-level exchange made up of one or more transactions, matched to the kind of data being moved. Every endpoint other than endpoint 0 (always control) is configured for exactly one of the following.
|
Transfer Type |
Reliability |
Timing Guarantee |
Typical Use |
|
Control |
Guaranteed (retried on error) |
None specific |
Device configuration, standard requests |
|
Bulk |
Guaranteed (retried on error) |
None (best-effort) |
Mass storage, printers — large, non-urgent data |
|
Interrupt |
Guaranteed (retried on error) |
Bounded latency, polled at a fixed interval |
Mice, keyboards, joysticks — small, time-sensitive data |
|
Isochronous |
Not guaranteed (no retries) |
Fixed bandwidth and timing each frame |
Audio, video streaming — timing matters more than perfection |
11.1 Control Transfers in Detail
Control transfers are built from up to three stages:
● Setup stage — a single SETUP transaction carrying the 8-byte request.
● Data stage (optional) — zero or more IN or OUT transactions carrying the request payload, in one consistent direction.
● Status stage — a single transaction, in the opposite direction from the data stage (or an IN if there was no data stage), confirming completion. Its data packet always carries zero bytes.
Part 2 walks through a full control transfer, byte for byte, as part of the enumeration trace.
PART 2 — How a Device Becomes Recognized (States → EP0 → descriptors → enumeration)
12. USB Device States
|
State |
Description |
|
Attached |
Physically connected, but the bus hasn't been reset yet. |
|
Powered |
Receiving power (relevant mainly for bus-powered devices, once VBUS is detected). |
|
Default |
Has just seen a bus reset; responds at address 0 only, using default endpoint 0 parameters. |
|
Address |
Has been assigned a unique address via SET_ADDRESS, but not yet configured. |
|
Configured |
Has accepted a SET_CONFIGURATION request; all endpoints are active and it is ready for normal use. |
|
Suspended |
The bus has been idle for a defined period; device should reduce power consumption. |
13. Endpoint Addressing
It's easy to conflate "endpoint number" and "endpoint address", but USB treats them as related, distinct concepts.
● Endpoint number — a 4-bit value (0–15) identifying the logical channel inside the device. Endpoint 0 is always the default control endpoint.
● Direction — IN (device → host) or OUT (host → device). Endpoint 0 is unique in being bidirectional using the same number for both directions.
● Endpoint address — the byte used in descriptors and in software to refer to a specific direction of a specific endpoint number: bit 7 is the direction bit (1 = IN, 0 = OUT), bits 6:4 are reserved (0), and bits 3:0 are the endpoint number.
So a device can have, at most, one OUT endpoint and one IN endpoint per endpoint number (0 through 15) — they are addressed independently even though they share a number.
|
Endpoint Address (hex) |
Binary |
Meaning |
|
0x00 |
0000 0000 |
Endpoint 0 OUT (part of the default control pipe) |
|
0x80 |
1000 0000 |
Endpoint 0 IN (part of the default control pipe) |
|
0x01 |
0000 0001 |
Endpoint 1 OUT |
|
0x81 |
1000 0001 |
Endpoint 1 IN |
|
0x82 |
1000 0010 |
Endpoint 2 IN |
Endpoint 0 is special for three reasons: every device must implement it, it is always control-type, and it is the only endpoint guaranteed to be active before the device is configured — which is exactly why the entire enumeration process happens over endpoint 0.
14. The 8-Byte SETUP Request, Decoded
Every SETUP transaction carries the same 8-byte structure. Here is a real, complete example — a GET_DESCRIPTOR request asking for the full 18-byte device descriptor — decoded field by field:
80 06 00 01 00 00 12 00
|
Bytes |
Field |
Value |
Meaning |
|
80 |
bmRequestType |
1000 0000b |
Bit 7=1: device-to-host. Bits 6:5=00: Standard request. Bits 4:0=00000: recipient is the Device. |
|
06 |
bRequest |
6 |
GET_DESCRIPTOR |
|
00 01 |
wValue |
0x0100 |
Low byte 0x00 = descriptor index 0. High byte 0x01 = descriptor type 1 (DEVICE). |
|
00 00 |
wIndex |
0x0000 |
Not used for a device descriptor request; set to 0. |
|
12 00 |
wLength |
0x0012 = 18 |
Host will read up to 18 bytes in the data stage — exactly the size of a device descriptor. |
Note: Multi-byte fields (wValue, wIndex, wLength) are little-endian, so "12 00" means 0x0012, not 0x1200. This is a very common source of off-by-a-factor-of-256 bugs when hand-assembling SETUP packets.
A shorter variant is common early in enumeration, when the host doesn't yet know the device's max packet size and just wants the first 8 bytes: bmRequestType/bRequest/wValue/wIndex stay the same, but wLength becomes 0x0008:
80 06 00 01 00 00 08 00
15. Descriptors, Byte by Byte
Descriptors are structured, self-describing blocks of data a device presents to the host so it can be understood without any prior knowledge of the specific device. Every descriptor starts with the same two-field header — a length byte and a type byte — which lets a host walk a chain of descriptors even if it doesn't recognize every one.
15.1 Device Descriptor (18 bytes)
|
Offset |
Field |
Size |
Meaning |
|
0 |
bLength |
1 |
Always 18 (0x12) for a device descriptor. |
|
1 |
bDescriptorType |
1 |
1 = DEVICE. |
|
2–3 |
bcdUSB |
2 |
USB spec version in BCD, e.g. 0x0200 = USB 2.00. |
|
4 |
bDeviceClass |
1 |
Device class (0 = defined per-interface, common for composite devices). |
|
5 |
bDeviceSubClass |
1 |
Subclass, meaning depends on bDeviceClass. |
|
6 |
bDeviceProtocol |
1 |
Protocol, meaning depends on class/subclass. |
|
7 |
bMaxPacketSize0 |
1 |
Max packet size for endpoint 0 (8, 16, 32, or 64). |
|
8–9 |
idVendor |
2 |
USB-IF assigned Vendor ID (VID). |
|
10–11 |
idProduct |
2 |
Vendor-assigned Product ID (PID). |
|
12–13 |
bcdDevice |
2 |
Device release number in BCD. |
|
14 |
iManufacturer |
1 |
Index of the manufacturer string descriptor (0 = none). |
|
15 |
iProduct |
1 |
Index of the product string descriptor (0 = none). |
|
16 |
iSerialNumber |
1 |
Index of the serial number string descriptor (0 = none). |
|
17 |
bNumConfigurations |
1 |
Number of possible configurations. |
15.2 Configuration Descriptor (9 bytes header)
|
Offset |
Field |
Size |
Meaning |
|
0 |
bLength |
1 |
Always 9 (0x09). |
|
1 |
bDescriptorType |
1 |
2 = CONFIGURATION. |
|
2–3 |
wTotalLength |
2 |
Total length of this descriptor plus every interface/endpoint/class descriptor that follows it in the same response. |
|
4 |
bNumInterfaces |
1 |
Number of interfaces in this configuration. |
|
5 |
bConfigurationValue |
1 |
Value used by SET_CONFIGURATION to select this configuration. |
|
6 |
iConfiguration |
1 |
String descriptor index (0 = none). |
|
7 |
bmAttributes |
1 |
Bit 6 = self-powered, bit 5 = remote wakeup supported. Bit 7 is always set for historical reasons. |
|
8 |
bMaxPower |
1 |
Maximum bus current draw, in 2 mA units (e.g. 50 = 100 mA). |
15.3 Interface Descriptor (9 bytes)
|
Offset |
Field |
Size |
Meaning |
|
0 |
bLength |
1 |
Always 9 (0x09). |
|
1 |
bDescriptorType |
1 |
4 = INTERFACE. |
|
2 |
bInterfaceNumber |
1 |
Zero-based index of this interface. |
|
3 |
bAlternateSetting |
1 |
Alternate setting index (0 for the default). |
|
4 |
bNumEndpoints |
1 |
Number of endpoints used by this interface, excluding endpoint 0. |
|
5 |
bInterfaceClass |
1 |
USB class code (e.g. 0x03 = HID, 0x08 = Mass Storage). |
|
6 |
bInterfaceSubClass |
1 |
Meaning depends on class. |
|
7 |
bInterfaceProtocol |
1 |
Meaning depends on class/subclass. |
|
8 |
iInterface |
1 |
String descriptor index (0 = none). |
15.4 Endpoint Descriptor (7 bytes)
|
Offset |
Field |
Size |
Meaning |
|
0 |
bLength |
1 |
Always 7 (0x07). |
|
1 |
bDescriptorType |
1 |
5 = ENDPOINT. |
|
2 |
bEndpointAddress |
1 |
Direction bit + endpoint number, as covered in Section 13. |
|
3 |
bmAttributes |
1 |
Bits 1:0 select transfer type: 00 Control, 01 Isochronous, 10 Bulk, 11 Interrupt. |
|
4–5 |
wMaxPacketSize |
2 |
Maximum packet size this endpoint accepts/sends. |
|
6 |
bInterval |
1 |
Polling interval (interrupt/isochronous) — units depend on speed class. |
15.5 HID Descriptor (9 bytes, one report descriptor)
|
Offset |
Field |
Size |
Meaning |
|
0 |
bLength |
1 |
9 (0x09) for a HID descriptor with one class descriptor entry. |
|
1 |
bDescriptorType |
1 |
0x21 = HID. |
|
2–3 |
bcdHID |
2 |
HID class spec version in BCD. |
|
4 |
bCountryCode |
1 |
Localized hardware, or 0 if not country-specific. |
|
5 |
bNumDescriptors |
1 |
Number of class descriptors that follow (usually 1). |
|
6 |
bDescriptorType |
1 |
0x22 = Report. |
|
7–8 |
wDescriptorLength |
2 |
Length in bytes of the HID Report Descriptor. |
The Report Descriptor itself (pointed to by the HID descriptor above) is a separate, compact tag-based structure describing exactly what an input report contains — buttons, axes, their ranges and units — letting a generic OS-level HID driver interpret arbitrary devices without device-specific code.
16. A Complete Descriptor Tree Example
Putting the previous section's tables together, here is what the device and configuration descriptors of a minimal single-button HID mouse actually contain, laid out as the host would receive them:
|
Descriptor |
Key Fields |
|
Device |
bcdUSB=0x0200, bDeviceClass=0x00, bMaxPacketSize0=64, idVendor/idProduct = vendor-assigned, bNumConfigurations=1 |
|
Configuration |
wTotalLength = 9+9+9+7 = 34 bytes total, bNumInterfaces=1, bmAttributes=0x80 (bus-powered), bMaxPower=50 (100 mA) |
|
Interface 0 |
bInterfaceClass=0x03 (HID), bInterfaceSubClass=0x01 (Boot), bInterfaceProtocol=0x02 (Mouse), bNumEndpoints=1 |
|
HID |
bcdHID=0x0111, bNumDescriptors=1, wDescriptorLength = length of the report descriptor below |
|
Endpoint |
bEndpointAddress=0x81 (EP1 IN), bmAttributes=0x03 (Interrupt), wMaxPacketSize=4, bInterval=10 |
When the host issues GET_DESCRIPTOR(Configuration) with a large enough wLength, it receives all five of these descriptors concatenated in exactly this order, in a single data stage: the 9-byte configuration descriptor, followed immediately by the 9-byte interface descriptor, the 9-byte HID descriptor, and the 7-byte endpoint descriptor — 34 bytes total, matching wTotalLength. This concatenation, rather than separate requests for each piece, is why wTotalLength exists: it tells the host exactly how many bytes to read in one data stage to get the whole tree.
17. Standard Device Requests
Every SETUP transaction carries the 8-byte structure introduced in Section 14, regardless of what the request does.
|
Field |
Size |
Purpose |
|
bmRequestType |
1 byte |
Direction, type (standard/class/vendor), and recipient (device/interface/endpoint) |
|
bRequest |
1 byte |
Which request this is, e.g. GET_DESCRIPTOR or SET_ADDRESS |
|
wValue |
2 bytes |
Request-specific parameter (e.g. descriptor type & index) |
|
wIndex |
2 bytes |
Request-specific parameter (often an interface or endpoint number) |
|
wLength |
2 bytes |
Number of bytes expected in the data stage, if any |
17.1 Common Standard Requests
|
Request |
Purpose |
|
GET_STATUS |
Query remote-wakeup / self-powered / halt status |
|
CLEAR_FEATURE / SET_FEATURE |
Clear or set a specific feature, e.g. ENDPOINT_HALT |
|
SET_ADDRESS |
Assign the device its bus address (enumeration step) |
|
GET_DESCRIPTOR / SET_DESCRIPTOR |
Read (or, rarely, write) a descriptor |
|
GET_CONFIGURATION / SET_CONFIGURATION |
Query or select the active configuration |
|
GET_INTERFACE / SET_INTERFACE |
Query or select an alternate interface setting |
|
SYNCH_FRAME |
Used with isochronous endpoints to report frame synchronization |
Class-specific requests (like HID's SET_IDLE, SET_REPORT, and GET_REPORT) reuse the same 8-byte structure, distinguished by the type bits inside bmRequestType, so the same control-transfer machinery handles both standard and class requests without special-casing at the transaction level.
18. The Complete Enumeration Trace
Enumeration is the sequence that happens automatically whenever a device is plugged in, before it becomes usable. At a high level, it follows this sequence:
RESET -> GET_DESCRIPTOR(Device,8) -> SET_ADDRESS -> GET_DESCRIPTOR(Device,18) -> GET_DESCRIPTOR(Config,9) -> GET_DESCRIPTOR(Config,full) -> SET_CONFIGURATION
Expanded into individual steps, with the actual control transfers involved:
● 1. Attach & speed detection — the device asserts its pull-up resistor; the host's root hub detects it and determines speed class from which line is pulled high.
● 2. Bus reset — the host holds D+ and D- low (SE0) for a defined minimum duration. The device resets internal state and starts responding at address 0.
● 3. Partial GET_DESCRIPTOR(Device) — SETUP payload 80 06 00 01 00 00 08 00, requesting just 8 bytes, primarily to learn bMaxPacketSize0 (byte offset 7 of the device descriptor).
● 4. SET_ADDRESS — SETUP payload 00 05 05 00 00 00 00 00 (bRequest=0x05, wValue=0x0005 assigning address 5). The device must ACK this at address 0, then respond at the new address for everything after the status stage — the address change itself only takes effect after the status stage completes.
● 5. Full GET_DESCRIPTOR(Device) — SETUP payload 80 06 00 01 00 00 12 00, now addressed to the new device address, reading all 18 bytes.
● 6. Partial GET_DESCRIPTOR(Configuration) — SETUP payload 80 06 00 02 00 00 09 00 (wValue high byte 0x02 = CONFIGURATION), reading just the 9-byte header to learn wTotalLength.
● 7. Full GET_DESCRIPTOR(Configuration) — same request with wLength set to wTotalLength from step 6, returning the whole descriptor tree from Section 16 in one data stage.
● 8. (Optional) GET_DESCRIPTOR(String) — bmRequestType=80, bRequest=06, wValue high byte 0x03 = STRING, to fetch human-readable manufacturer/product/serial text, if the earlier descriptors' string indices were non-zero.
● 9. SET_CONFIGURATION — SETUP payload 00 09 01 00 00 00 00 00 (bRequest=0x09, wValue=configuration value from Section 15.2). The device activates its endpoints and moves to the Configured state.
● 10. Class-specific setup — for the HID mouse example, the host typically also issues GET_DESCRIPTOR(Report) to fetch the report descriptor, and SET_IDLE (a HID class request) before beginning normal interrupt-endpoint polling.
Every one of these is a full control transfer (setup stage + optional data stage + status stage) built from the transactions covered in Section 8 — enumeration is really just several control transfers in a row, each depending on information learned from the one before it.
PART 3 — How Data Is Actually Moved (Bulk / Interrupt / Isochronous / Control, once a device is configured)
19. Life After Enumeration
Once a device is configured, the host's driver stack takes over, and communication settles into the pattern determined by each endpoint's transfer type from Section 11:
● A HID mouse's interrupt IN endpoint gets polled by the host on a fixed schedule (its bInterval); most polls return NAK because there's no new movement to report.
● A mass-storage device's bulk endpoints move large blocks of data with no fixed timing — the host just issues as many transactions as the bus has room for.
● An isochronous audio endpoint gets exactly one transaction's worth of bandwidth reserved every frame, whether or not there's actually new audio data ready — dropped samples are preferred over a broken timing guarantee.
● Endpoint 0 continues handling any further control transfers throughout the device's life — class requests, vendor requests, and any standard requests the host issues later (like re-reading GET_STATUS).
The transaction and packet mechanics don't change after enumeration; what changes is which transfer type is in play and how the host's driver schedules it.
PART 4 — Implementing USB Firmware (Classes → composite devices → topology → power → errors → HS → OTG)
20. USB Device Classes
A USB class is a standardized protocol layered on top of control/bulk/interrupt/isochronous transfers, letting a generic, OS-provided driver talk to any compliant device without vendor-specific code. bInterfaceClass (Section 15.3) is what tells the host which driver to load.
|
Class |
Class Code |
Typical Devices |
Why It Matters to the Host |
|
HID |
0x03 |
Keyboards, mice, joysticks, custom controllers |
One generic driver interprets any device via its Report Descriptor — no per-device driver needed. |
|
CDC / ACM |
0x02 |
USB-to-serial adapters, virtual COM ports |
Lets a microcontroller show up as a familiar serial port to any OS. |
|
Mass Storage |
0x08 |
Flash drives, external SSDs, SD readers |
Wraps SCSI commands (via the Bulk-Only Transport protocol) so any OS's existing storage stack works unmodified. |
|
Audio |
0x01 |
USB microphones, speakers, audio interfaces |
Standardizes sample formats and isochronous streaming so no per-device audio driver is needed. |
|
Vendor-Specific |
0xFF |
Anything without a fitting standard class |
Requires a custom driver; used when no standard class matches the device's actual behavior. |
Classes matter because they determine how much host-side software you have to write. A HID or CDC device can often be talking to a stock OS driver within minutes of writing correct descriptors; a vendor-specific device needs a custom driver or user-space library on every platform you want to support.
21. Composite Devices
A single physical USB device can expose more than one function by declaring multiple interfaces inside one configuration — this is a composite device. A keyboard-with-integrated-trackpad, for example, typically exposes two HID interfaces from one device: one behaving as a boot keyboard, another as a boot mouse, each with its own interface descriptor, its own class/subclass/protocol, and its own endpoint(s), all inside a single configuration descriptor's descriptor tree.
The host enumerates the device exactly as described in Section 18 — it's still one device descriptor and one (or more) configuration descriptors — but once GET_DESCRIPTOR(Configuration) returns the full tree, the OS loads a separate driver instance against each interface independently, based on that interface's own class code. Section 15.3's bNumInterfaces field is how the host first learns there's more than one function to bind drivers to.
22. USB Topology
USB is physically a tiered-star topology, not a bus in the electrical-signal sense its name suggests:
● Host controller — the silicon (in a PC, SBC, or MCU acting as a host) that owns the bus and schedules every transaction.
● Root hub — logically a hub built into the host controller itself; every physical port on your computer is a downstream port of the root hub.
● External hubs — devices that are themselves USB devices (they enumerate and have their own address) but also provide additional downstream ports, letting the tree branch further.
● Devices — leaf nodes of the tree; a device cannot have anything downstream of it (only a hub can).
The specification allows up to 5 tiers of hubs between the root hub and a device (a limit driven by signal-timing budgets, more relevant at Low/Full Speed). Every device on the tree, no matter how many hubs deep, gets its own unique 7-bit address (1–127) assigned during its own enumeration — address 0 is reserved for whatever device is currently mid-enumeration and hasn't been assigned one yet.
23. Power
● VBUS — the +5 V supply line every USB connector carries. A device only starts drawing meaningful current, and asserting its speed pull-up, once VBUS is present.
● Bus-powered vs. self-powered — declared in the configuration descriptor's bmAttributes (Section 15.2). A bus-powered device draws all its power from VBUS and must stay within the limit it declared in bMaxPower; a self-powered device has its own supply and typically declares a minimal VBUS draw.
● Current limits — an unconfigured device is limited to 100 mA (bMaxPower = 50, in 2 mA units) until it's configured; a configured device may request up to 500 mA at Full/High Speed (bMaxPower = 250) without needing a special high-power negotiation.
● Suspend current — after the bus has been idle for the defined suspend timeout, a device must reduce its total current draw substantially (typically to a few mA) regardless of its normal operating current, to comply with the specification's suspend power budget.
● Remote wakeup — an optional capability (declared via bmAttributes bit 5) letting a suspended device signal the host to resume the bus — e.g. a keyboard waking a sleeping PC on a keypress — by driving a defined resume signaling sequence rather than waiting passively.
24. Error Handling
USB is designed to detect errors at the packet level and recover automatically wherever possible, rather than relying on the application layer to notice something went wrong.
|
Error |
Detected By |
Typical Recovery |
|
CRC error |
CRC5 (token) or CRC16 (data) mismatch at the receiver |
Packet is silently dropped; no handshake is returned, which the sender interprets as a timeout and retries. |
|
Bit-stuff error |
An invalid/unexpected bit pattern breaks the bit-stuffing rule |
Treated like a corrupted packet — dropped, triggering the same timeout-based retry. |
|
Timeout |
Expected response (data or handshake) doesn't arrive within the defined turnaround window |
Sender retries the transaction, typically up to a bus-defined retry limit before giving up and reporting an error upstream. |
|
Babble |
A device keeps driving the bus past the point it should have stopped (e.g. past EOP) |
Host controller disables the port; often surfaces to the OS as a device error requiring re-enumeration. |
|
Data toggle mismatch |
Received DATA0/DATA1 PID doesn't match the expected toggle state |
Receiver silently discards the packet as a duplicate but still ACKs it, since the sender clearly didn't see the previous ACK. |
A recurring theme is that most of this is handled by hardware or the host controller driver, invisibly to device firmware — firmware generally only needs to worry about STALL conditions it deliberately signals, and about NAK, which (as covered in Section 7) isn't really an error at all.
25. High-Speed Details
The 1.5 kΩ pull-up described in Section 3 only ever signals Low Speed or Full Speed capability — it cannot, by itself, put a device into High-Speed mode, because a High-Speed-capable device must still be backward compatible with Full-Speed-only hosts and hubs.
Instead, High Speed is negotiated after the initial Full-Speed-style reset, through a sequence called chirp:
● The device, if High-Speed capable, drives a Chirp-K signal for a defined duration immediately after detecting the host's reset.
● If the host (or the hub it's attached through) is also High-Speed capable, it responds with an alternating sequence of Chirp-K / Chirp-J signals.
● Once the device detects at least three of these alternating K-J chirp pairs from the host, it switches its transceiver to High-Speed signaling levels and termination, and the reset completes in High-Speed mode.
● If the device never sees the host's chirp response, it falls back to operating at Full Speed — the negotiation is designed to fail safe toward compatibility rather than toward a broken link.
This is also why a High-Speed device plugged into an old Full-Speed-only host, or downstream of a Full-Speed-only hub, works correctly at Full Speed rather than failing outright: the chirp handshake simply never completes, and both sides settle on the speed they actually share.
26. USB On-The-Go and Host Mode
USB On-The-Go (OTG) extends the strict host/device split so a single port on a small device — a phone, or a microcontroller with a USB peripheral capable of host mode — can act as either a host or a device, without needing two separate connectors.
● ID pin — OTG connectors add a fifth pin beyond the standard four; whether it's grounded (Micro-A/USB-C configured as host) or floating (Micro-B/USB-C configured as device) tells the controller which role to start in.
● Dual-role device (DRD) — a device capable of switching roles, either based on the ID pin or dynamically via negotiation.
● Session Request Protocol (SRP) — lets a device without its own strong power source ask an attached host to turn on VBUS and start a session, rather than requiring the host to always be powered and waiting.
● Host Negotiation Protocol (HNP) — lets the two ends of an OTG link swap which one is acting as host, after a session is already established, without physically unplugging anything.
For embedded developers, this matters most when a microcontroller's USB peripheral needs to read from a USB flash drive, a USB-to-serial gadget, or a MIDI keyboard directly — i.e. the microcontroller itself needs to act as the host, driving SOF, issuing tokens, and managing device addresses, rather than just responding to a PC as a device (everything described in Parts 1–3 of this tutorial, but with the roles reversed).
27. A Minimal Firmware Implementation
It helps to be explicit about where the dividing line usually falls between what a microcontroller's built-in USB peripheral does in hardware, and what firmware still has to provide, for a simple USB device (e.g. the HID mouse used as a running example in Part 2).
|
Layer |
Typically Handled By Hardware |
Typically Left to Firmware |
|
Physical |
NRZI encode/decode, bit stuffing/unstuffing, differential transceiver, speed-class pull-up |
Nothing — this layer is essentially always fully automatic on integrated USB peripherals. |
|
Packet |
SYNC, PID generation/checking, CRC5/CRC16 generation/checking, EOP detection |
Nothing on most peripherals, though some very low-cost or bit-banged implementations require firmware to do parts of this in software. |
|
Transaction |
Token/data/handshake sequencing for a given endpoint, per-endpoint FIFOs, automatic ACK/NAK based on FIFO state, data toggle tracking |
Deciding when data is ready to push into a TX FIFO, and reading data out of an RX FIFO promptly enough to avoid stalling. |
|
Protocol / requests |
Delivering received SETUP packets and endpoint interrupts to firmware |
Parsing bmRequestType/bRequest/wValue/wIndex/wLength, deciding how to respond, and building/queuing the correct response descriptor bytes. |
|
Device / class |
Nothing — entirely application logic |
Maintaining the device's own state machine (Section 12), storing the descriptor tables from Part 2, implementing class-specific requests (e.g. HID's SET_IDLE), and producing the actual application data (e.g. mouse movement reports). |
Concretely, a minimal HID mouse firmware's main responsibilities usually come down to: storing the descriptor byte tables from Section 16 in flash; handling endpoint-0 SETUP interrupts by matching bRequest/wValue against the standard requests from Section 17 and queuing the appropriate descriptor bytes or handshake; implementing SET_ADDRESS and SET_CONFIGURATION by updating the device's own state (Section 12); and, once configured, periodically pushing a 4-byte HID report (buttons + X/Y deltas) into endpoint 1's IN FIFO whenever the host polls it and new movement is available.
PART 5 — Practical USB Debugging (Reading a trace and recognizing common failures)
28. Reading an Enumeration Trace
When a USB protocol analyzer (hardware, or a software capture of host-side traffic) shows an enumeration, it will look like a sequence of the same control transfers listed in Section 18, each broken into its packets. A healthy trace has a very recognizable rhythm:
● A short burst of activity around the reset, then a SETUP token + DATA0 + (device) ACK for the 8-byte device descriptor request.
● A gap while the device processes, then IN tokens with DATA1/DATA0 responses alternating, each ACK'd, until 8 bytes have been transferred, followed by a zero-length status transaction.
● A SET_ADDRESS control transfer, notably still addressed to device 0.
● Every subsequent control transfer now addressed to the new device address, repeating for the full device descriptor, then the configuration descriptor (partial, then full), then optionally strings, then SET_CONFIGURATION.
If you're staring at a real capture, the fastest way to orient yourself is to find the SET_ADDRESS transaction first — everything before it is addressed to device 0, and everything after it is addressed to the new device number, which makes the trace much easier to read top to bottom.
29. Common Enumeration Failures
|
Symptom |
Likely Cause |
|
Device isn't detected at all |
Pull-up resistor missing/misconfigured, VBUS not present, or a hardware/wiring fault on D+/D-. |
|
Device resets in a loop |
Firmware crashing or hanging while handling the first SETUP packet, or an incorrect bMaxPacketSize0 causing the host to misread the 8-byte partial descriptor response. |
|
"Unknown device" / descriptor request fails |
Malformed device descriptor (wrong bLength/bDescriptorType), or firmware not responding to the control transfer's status stage. |
|
Device enumerates but OS loads no driver |
bInterfaceClass/SubClass/Protocol don't match any driver the OS recognizes, or wTotalLength in the configuration descriptor doesn't match the actual bytes returned. |
|
Device works, then randomly disconnects |
Power budget exceeded (drawing more current than bMaxPower declared), or a babble/timeout condition from a firmware bug in endpoint handling under load. |
|
Mouse/keyboard enumerates but never reports input |
Interrupt endpoint never gets new data pushed into its IN FIFO, or the endpoint descriptor's wMaxPacketSize doesn't match what firmware actually writes. |
In nearly every case, comparing a failing trace against the healthy rhythm from Section 28 — and against the exact byte layouts from Sections 14 through 17 — turns "it doesn't work" into a specific, fixable mismatch.
30. Summary
USB is built as a stack of increasingly abstract concepts, each resting on the one below it: electrical differential signaling becomes NRZI-encoded bits, bits become packets identified by their PID, packets combine into transactions, transactions combine into transfers suited to different kinds of data, and a structured sequence of control transfers — enumeration — lets any compliant device introduce itself to any compliant host without prior knowledge of one another.
With the byte-level examples in Parts 2 and 3, the state and power model in Part 4, and the debugging habits in Part 5, a USB reference manual, a protocol analyzer trace, or a microcontroller's USB peripheral registers should read far less like magic and far more like a direct, if detailed, implementation of the ideas covered in this tutorial.

No comments:
Post a Comment