docs/reference/usb_concepts.rst
USB Concepts
This document provides a brief introduction to USB protocol fundamentals that are essential for understanding TinyUSB development.
TinyUSB uses consistent function prefixes to organize its API:
tud_task(), tud_cdc_write())tuh_task(), tuh_cdc_receive())This naming makes it easy to identify which part of the stack a function belongs to and ensures there are no naming conflicts when using both device and host stacks together.
Universal Serial Bus (USB) is a standardized communication protocol designed for connecting devices to hosts (typically computers). Understanding these core concepts is essential for effective TinyUSB development.
USB Host: The controlling side of a USB connection (typically a computer). The host:
TinyUSB Host Stack: Enable with CFG_TUH_ENABLED=1 in tusb_config.h. Call tuh_task() regularly in your main loop. See the :doc:../getting_started Quick Start Examples for implementation details.
USB Device: The peripheral side (keyboard, mouse, storage device, etc.). Devices:
TinyUSB Device Stack: Enable with CFG_TUD_ENABLED=1 in tusb_config.h. Call tud_task() regularly in your main loop. See the :doc:../getting_started Quick Start Examples for implementation details.
OTG (On-The-Go): Some devices can switch between host and device roles dynamically. TinyUSB Support: Both stacks can be enabled simultaneously on OTG-capable hardware. See examples/dual/ for dual-role implementations.
Every USB transfer consists of the host issuing a request, and the device replying to that request. The host is the bus master and initiates all communication. Devices cannot initiate sending data; for unsolicited incoming data, polling is used by the host.
USB defines four transfer types, each intended for different use cases:
Used for device configuration and control commands.
Characteristics:
Usage: Device enumeration, configuration changes, class-specific commands
TinyUSB Context: Handled automatically by the core stack for standard requests; class drivers handle class-specific requests. Endpoint 0 is managed by src/device/usbd.c and src/host/usbh.c. Configure buffer size with CFG_TUD_ENDPOINT0_SIZE (typically 64 bytes).
Used for large amounts of data that don't require guaranteed timing.
Characteristics:
Usage: File transfers, large data communication, CDC serial data
TinyUSB Context: Used by MSC (mass storage) and CDC classes for data transfer. Configure endpoint buffer sizes with CFG_TUD_MSC_EP_BUFSIZE and CFG_TUD_CDC_EP_BUFSIZE. See src/class/msc/ and src/class/cdc/ for implementation details.
Used for small, time-sensitive data with guaranteed maximum latency.
Characteristics:
Usage: Keyboard/mouse input, sensor data, status updates
TinyUSB Context: Used by HID class for input reports. Configure with CFG_TUD_HID and CFG_TUD_HID_EP_BUFSIZE. Send reports using tud_hid_report() or tud_hid_keyboard_report(). See src/class/hid/ and HID examples in examples/device/hid_*/.
Used for time-critical streaming data.
Characteristics:
Usage: Audio, video streaming
TinyUSB Context: Used by Audio class for streaming audio data. Configure with CFG_TUD_AUDIO and related audio configuration macros. See src/class/audio/ and audio examples in examples/device/audio_*/ for UAC2 implementation.
Endpoint: A communication channel between host and device.
TinyUSB Endpoint Management: Configure maximum endpoints with CFG_TUD_ENDPOINT_MAX. Endpoints are automatically allocated by enabled classes. See your board's usb_descriptors.c for endpoint assignments.
Direction:
tx/rx, the device perspective is used typically: E.g., tud_cdc_tx_complete_cb() designates the callback executed once the device has completed sending data to the host (in device mode).Addressing: Endpoints are addressed as EPx IN/OUT (e.g., EP1 IN, EP2 OUT)
Each endpoint is configured with a specific transfer type (control, bulk, interrupt, or isochronous), a direction (IN, OUT, or bidirectional for control only), a maximum packet size that depends on USB speed and transfer type, and an interval for interrupt and isochronous endpoints.
TinyUSB Configuration: Endpoint characteristics are defined in descriptors (usb_descriptors.c) and automatically configured by the stack. Buffer sizes are set via CFG_TUD_*_EP_BUFSIZE macros.
Transfer Results: USB transfers can complete with different results. An ACK indicates a successful transfer, while a NAK signals that the device is not ready (commonly used for flow control). A STALL response indicates an error condition or unsupported request, and Timeout occurs when a transfer fails to complete within the expected time frame.
Flow Control in USB: Unlike network protocols, USB doesn't use traditional congestion control. Instead, devices use NAK responses when not ready to receive data, applications implement buffering and proper timing strategies, and some classes (like CDC) support hardware flow control mechanisms such as RTS/CTS.
TinyUSB Handling: Transfer results are represented as xfer_result_t enum values. The stack automatically handles NAK responses and timing. STALL conditions indicate application-level errors that should be addressed in class drivers.
A USB device progresses through several states:
TinyUSB State Management: State transitions are handled automatically by src/device/usbd.c. You can implement tud_mount_cb() and tud_umount_cb() to respond to configuration changes, and tud_suspend_cb()/tud_resume_cb() for power management.
When a device is connected, the host follows this process:
TinyUSB Role: The device stack handles steps 1-6 automatically; your application handles step 7.
Descriptors are data structures that describe device capabilities:
Describes the device (VID, PID, USB version, etc.)
Describes device configuration (power requirements, interfaces, etc.)
Describes a functional interface (class, endpoints, etc.)
Describes endpoint characteristics (type, direction, size, etc.)
Human-readable strings (manufacturer, product name, etc.)
TinyUSB Implementation: You provide descriptors in usb_descriptors.c via callback functions:
tud_descriptor_device_cb() - Device descriptortud_descriptor_configuration_cb() - Configuration descriptortud_descriptor_string_cb() - String descriptorsThe stack automatically handles descriptor requests during enumeration. See examples in examples/device/*/usb_descriptors.c for reference implementations.
USB classes define standardized protocols for device types:
Class Code: Identifies the device type in descriptors Class Driver: Software that implements the class protocol Class Requests: Standardized commands for the class
Common TinyUSB-Supported Classes:
CFG_TUD_CDCCFG_TUD_HIDCFG_TUD_MSCCFG_TUD_AUDIOCFG_TUD_MIDICFG_TUD_DFUCFG_TUD_VENDOR.. note::
Vendor Class Buffer Configuration: Unlike other USB classes, the vendor class supports setting buffer sizes to 0 in tusb_config.h (CFG_TUD_VENDOR_RX_BUFSIZE = 0) to disable internal buffering. When disabled, data goes directly to tud_vendor_rx_cb() and the tud_vendor_read()/tud_vendor_write() functions are not available - applications must handle data directly in callbacks.
See examples/device/*/tusb_config.h for configuration examples.
USB supports multiple speed modes:
Low Speed (1.5 Mbps):
Full Speed (12 Mbps):
High Speed (480 Mbps):
Super Speed (5 Gbps):
TinyUSB Speed Support: Most TinyUSB ports support Full Speed and High Speed. Speed is typically auto-detected by hardware. Configure speed requirements in board configuration (hw/bsp/FAMILY/boards/BOARD/board.mk) and ensure your MCU supports the desired speed.
USB controllers are hardware peripherals that handle the low-level USB protocol implementation. Understanding how they work helps explain TinyUSB's architecture and portability.
What Controllers Do:
Key Components: USB controllers consist of several key components working together. The Physical Layer provides USB signal drivers and receivers for electrical interfacing. The Protocol Engine handles USB packets and ACK/NAK responses according to the USB specification. Endpoint Buffers provide hardware FIFOs or RAM for data storage during transfers. Finally, the Interrupt Controller generates events for software processing when USB activities occur.
Different MCU vendors implement USB controllers with varying architectures. To list a few common patterns:
FIFO-Based Controllers (e.g., STM32 OTG, NXP LPC):
Buffer-Based Controllers (e.g., STM32 FSDEV, Microchip SAMD, RP2040):
Descriptor-Based Controllers (e.g., NXP EHCI-style):
TinyUSB abstracts controller differences through the TinyUSB Device Controller Driver (DCD) layer. These internal details don't matter to users of TinyUSB typically; however, when debugging, knowledge about internal details helps sometimes.
Portable Interface (src/device/usbd.h):
Controller-Specific Drivers (src/portable/VENDOR/FAMILY/):
Common DCD Functions:
dcd_init() - Initialize controller hardwaredcd_edpt_open() - Configure endpoint with type and sizedcd_edpt_xfer() - Start data transfer on endpointdcd_int_handler() - Process USB interruptsdcd_connect()/dcd_disconnect() - Control USB bus connectionTinyUSB also abstracts USB host controllers through the Host Controller Driver (HCD) layer for host mode applications.
Portable Interface (src/host/usbh.h):
Common HCD Functions:
hcd_init() - Initialize host controller hardwarehcd_port_connect_status() - Check device connection statushcd_port_reset() - Reset connected devicehcd_edpt_open() - Open communication pipe to device endpointhcd_edpt_xfer() - Transfer data to/from connected deviceHost vs Device Architecture: While DCD is reactive (responds to host requests), HCD is active (initiates all communication). Host controllers manage device enumeration, driver loading, and transfer scheduling to multiple connected devices.
Core Architectural Principle: TinyUSB uses a deferred interrupt processing model where all USB hardware events are captured in interrupt service routines (ISRs) but processed later in non-interrupt context.
Event Flow:
tud_task() or tuh_task() to process queued eventsBuffer Integration: The deferred processing model works seamlessly with TinyUSB's buffer/FIFO design. Since callbacks run in task context (not ISR), it's safe and straightforward to enqueue TX data directly in RX callbacks - for example, processing incoming CDC data and immediately sending a response.
Typical USB Event Processing:
dcd_int_handler() reads controller statustud_task() processes queued eventsTinyUSB implements USB classes through a standardized driver pattern that provides consistent integration with the core stack while allowing class-specific functionality.
Standardized Entry Points: Each class driver implements these core functions:
*_init() - Initialize class driver state and buffers*_reset() - Reset to initial state on USB bus reset*_open() - Parse and configure interfaces during enumeration*_control_xfer_cb() - Handle class-specific control requests*_xfer_cb() - Handle transfer completion callbacksMulti-Instance Support: Classes support multiple instances using _n suffixed APIs:
.. code-block:: c
// Single instance (default instance 0) tud_cdc_write(data, len);
// Multiple instances tud_cdc_n_write(0, data, len); // Instance 0 tud_cdc_n_write(1, data, len); // Instance 1
Integration with Core Stack: Class drivers are automatically discovered and integrated through function pointers in driver tables. The core stack calls class drivers during enumeration, control requests, and data transfers without requiring explicit registration.
TinyUSB classes have different architectural patterns based on their buffering capabilities and callback designs.
Most classes like CDC, MIDI, and HID always use internal buffers for data management. These classes provide notification-only callbacks such as tud_cdc_rx_cb(uint8_t itf) that signal when data is available, requiring applications to use class-specific APIs like tud_cdc_read() and tud_cdc_write() to access the data. HID is slightly different in that it provides direct buffer access in some callbacks (tud_hid_set_report_cb() receives buffer and size parameters), but it still maintains internal endpoint buffering that cannot be disabled.
The Vendor Class is unique in that it supports both buffered and direct modes. When buffered, vendor class behaves like other classes with tud_vendor_read() and tud_vendor_write() APIs. However, when buffering is disabled by setting buffer size to 0, the vendor class provides direct buffer access through tud_vendor_rx_cb(itf, buffer, bufsize) callbacks, eliminating internal FIFO overhead and providing direct endpoint control.
Block-Oriented Classes like MSC operate differently by handling large data blocks through callback interfaces. The application implements storage access functions such as tud_msc_read10_cb() and tud_msc_write10_cb(), while the TinyUSB stack manages the USB protocol aspects and the application manages the underlying storage.
USB provides power to devices:
Bus-Powered: Device draws power from USB bus (up to 500mA) Self-Powered: Device has its own power source Suspend/Resume: Devices must enter low-power mode when bus is idle
TinyUSB Power Management:
tud_suspend_cb() and tud_resume_cb() for power managementbMaxPower field)tud_remote_wakeup() to wake the host from suspend (if supported)CFG_TUD_USBD_ENABLE_REMOTE_WAKEUP../getting_started for basic setupexamples/device/*/tusb_config.h for configuration examplesexamples/device/ and examples/host/ directories