This document explains TinyUSB's internal architecture, design principles, and how different components work together.
TinyUSB is designed for resource-constrained embedded systems with strict memory requirements:
TinyUSB uses no dynamic allocation - all memory is statically allocated at compile time for predictability. All buffers have bounded, compile-time defined sizes to prevent overflow issues. The TinyUSB core avoids heap allocation, resulting in predictable memory usage where consumption is fully deterministic.
TinyUSB achieves thread safety through a deferred interrupt model:
- ISR deferral: USB interrupts are captured and deferred to task context
- Single-threaded processing: All USB protocol handling occurs in task context
- Queue-based design: Events are queued from ISR and processed in
tud_task() - RTOS integration: Proper semaphore/mutex usage for shared resources
The stack is designed to work across diverse microcontroller families:
- Hardware abstraction: MCU-specific code isolated in portable drivers
- OS abstraction: RTOS dependencies isolated in OSAL layer
- Modular design: Features can be enabled/disabled at compile time
- Standard compliance: Strict adherence to USB specifications
TinyUSB follows a layered architecture from hardware to application:
- Application Layer: Your main application code that uses TinyUSB APIs.
- Class Drivers: Implement specific USB device classes (CDC, HID, MSC, etc.) and handle class-specific requests.
- Device/Host Core: Implements USB protocol state machines, endpoint management, and core USB functionality.
- OS Abstraction: Provides threading primitives and synchronization for different RTOS environments.
- Device/Host Controller Driver: drivers that interface with MCU USB peripherals. Several MCUs may share a common driver.
This section is concerned with the Device Stack, i.e., the component of TinyUSB used in USB devices (that talk to a USB host).
Device Controller Driver (DCD):
- MCU-specific USB device peripheral driver
- Handles endpoint configuration and data transfers
- Abstracts hardware differences between MCU families
- Located in src/portable/VENDOR/USBIP/
USB Device Core (USBD):
- Implements USB device state machine
- Handles standard USB requests (Chapter 9)
- Manages device configuration and enumeration
- Located in src/device/
Class Drivers:
- Implement USB class specifications
- Handle class-specific requests and data transfer
- Provide application APIs
- Located in src/class/*/
Control Transfers (Setup Requests):
USB Bus → DCD → USBD Core → Class Driver → Application
↓
Standard requests handled in core
↓
Class-specific requests → Class Driver
Data Transfers:
Application → Class Driver → USBD Core → DCD → USB Bus
USB Bus → DCD → USBD Core → Class Driver → Application
TinyUSB uses a deferred interrupt model for thread safety:
- Interrupt Occurs: USB hardware generates interrupt
- ISR Handler:
dcd_int_handler()captures event, minimal processing - Event Queuing: Events queued for later processing
- Task Processing:
tud_task()(called by application code) processes queued events - Callback Execution: Application callbacks executed in task context
USB IRQ → ISR → Event Queue → tud_task() → Class Callbacks → Application
This section is concerned with the Host Stack, i.e., the component of TinyUSB used in USB hosts, managing connected USB devices.
Host Controller Driver (HCD):
- MCU-specific USB host peripheral driver
- Manages USB pipes and data transfers
- Handles host controller hardware
- Located in src/portable/VENDOR/FAMILY/
USB Host Core (USBH):
- Implements USB host functionality
- Manages device enumeration and configuration
- Handles pipe management and scheduling
- Located in src/host/
Hub Driver:
- Manages USB hub devices
- Handles port management and device detection
- Supports multi-level hub topologies
- Located in src/host/
The host stack follows USB enumeration process:
- Device Detection: Hub or root hub detects device connection
- Reset and Address: Reset device, assign unique address
- Descriptor Retrieval: Get device, configuration, and class descriptors
- Driver Matching: Find appropriate class driver for device
- Configuration: Configure device and start communication
- Class Operation: Normal class-specific communication
Device Connect → Reset → Get Descriptors → Load Driver → Configure → Operate
All USB classes follow a similar architecture:
Device Classes:
- *_device.c: Device-side implementation
- *_device.h: Device API definitions
- Implement class-specific descriptors
- Handle class requests and data transfer
Host Classes:
- *_host.c: Host-side implementation
- *_host.h: Host API definitions
- Manage connected devices of this class
- Provide application interface
See usbd.c.
Required Functions:
- reset(): Reset class state
- open(): Configure class endpoints
- control_xfer_cb(): Handle control requests
- xfer_cb(): Handle data transfer completion
Optional Functions:
- init(): Initialize class driver
- close(): Clean up class resources
- deinit(): Deinitialize class driver
- sof(): Start-of-frame processing
- xfer_isr(): Called from USB ISR context on transfer completion. Data will get queued for xfer_cb() only if this returns false.
Each class is responsible for: - Interface Descriptors: Define class type and endpoints - Class-Specific Descriptors: Additional class requirements - Endpoint Descriptors: Define data transfer characteristics
TinyUSB uses only static memory allocation; it allocates fixed-size endpoint buffers for each configured endpoint, static buffers for class-specific data handling, a fixed buffer dedicated to control transfers, and static event queues for deferred interrupt processing.
Endpoint Buffers:
- Allocated per endpoint at compile time
- Size defined by CFG_TUD_*_EP_BUFSIZE macros
- Used for USB data transfers
FIFO Buffers:
- Ring buffers for streaming data
- Size defined by CFG_TUD_*_RX/TX_BUFSIZE macros
- Separate read/write pointers
TinyUSB uses a cooperative task model; it provides main tasks - tud_task() for device and tuh_task() for host operation. These tasks must be called regularly (typically less than 1ms intervals) to ensure all USB events are processed in task context, where application callbacks also execute.
Bare Metal:
- Application calls tud_task() in main loop
- No threading primitives needed
- Simplest integration method
FreeRTOS: - USB task runs at high priority - Semaphores used for synchronization - Queue for inter-task communication
Other RTOS: - Similar patterns with RTOS-specific primitives - OSAL layer abstracts RTOS differences
Interrupt Service Routine: - Minimal processing in ISR - Event capture and queuing only - Quick return to avoid blocking
Deferred Processing: - All complex processing in task context - Thread-safe access to data structures - Application callbacks in known context
Flash Memory: - Core stack: 8-15KB depending on features - Each class: 1-4KB additional - Portable driver: 2-8KB depending on MCU
RAM Usage: - Core stack: 1-2KB - Endpoint buffers: User configurable - Class buffers: Depends on configuration