USB Device Fundamentals
Embedded → USB & Device-Class Protocols
Enumeration: how a host learns what you are
When plugged in, the host only knows a data line was pulled high (the speed-negotiation signal — full-speed pulls D+ via a 1.5kΩ resistor). Everything else — what the device is, what it can do — is learned through control transfers on endpoint 0, the one endpoint every device must implement. This is enumeration: reset the bus, assign address 0, request the device descriptor in small chunks (to learn EP0's max packet size), assign a real address, then read the full device/config/interface/endpoint descriptors.
The descriptor hierarchy
Descriptors are small, fixed-format structs exposed read-only over EP0 — the host only ever requests one of these specific structures. They nest strictly:
Device Descriptor (1 per device — VID, PID, class, EP0 max packet size)
└─ Configuration Descriptor (1+ — power draw, number of interfaces)
└─ Interface Descriptor (1+ — which class driver handles this: HID/CDC/MIDI…)
└─ Endpoint Descriptor (0+ — direction, type, max packet size, polling interval)A device can expose multiple interfaces under one configuration — how a board can be a virtual COM port and a mass-storage drive at once (see the TinyUSB stack lesson's cdc_msc example): each interface gets its own host-side driver, matched by class code.
VID/PID: who the host asks for a driver
Every device descriptor carries a 16-bit Vendor ID and Product ID. The VID is licensed from the USB-IF; hobbyist projects commonly borrow an open-source-friendly VID (e.g. TinyUSB's default 0xCafe) rather than fabricate one — a colliding pair can confuse a host with a driver already bound to that combination. For a class the host already has a built-in driver for (HID, CDC-ACM, mass storage), VID/PID mostly just distinguishes your device in the OS list — the class code selects the driver.
Endpoint types: four transfer flavors, four guarantees
Every non-zero endpoint is one of four types, each trading off latency, bandwidth, and error handling differently:
| Type | Guaranteed timing? | Retried on error? | Typical use |
|---|---|---|---|
| Control | no | yes | EP0 — enumeration, class requests |
| Interrupt | yes (polled interval) | yes | HID reports (keyboard, mouse) |
| Bulk | no | yes | CDC serial data, mass storage |
| Isochronous | yes (fixed slot) | no | Audio, video streaming |
This is why a keyboard feels instant (interrupt endpoint, guaranteed a slot every polling interval) while a mass-storage transfer can stall and resume (bulk endpoint — no timing guarantee, but every packet is retried until it arrives correctly). Isochronous is the odd one out: it guarantees bandwidth but never retries, because a late audio sample is worse than a dropped one.
Practice
lsusb -v (or usbview) — read the printed device, configuration, interface, and endpoint descriptors directly. Find the VID/PID, the interface class code, and the endpoint types. Compare what you find against the hierarchy diagram above; every field in that dump is exactly what the enumeration handshake in this lesson's demo pulled from the device.Completion saves automatically after you read through to the end.
Next lesson →