# Espressif's Additions to TinyUSB [](https://components.espressif.com/components/espressif/esp_tinyusb) This component extends TinyUSB with features that simplify integration into ESP-IDF applications. It provides both default and customizable configurations for TinyUSB, enabling USB device functionality on ESP chips with USB-OTG support. ### Run-time configuration During configuration, the following parameters can be set when installing the driver: - Descriptors - Peripheral port - Task parameters (size, priority, and CPU affinity) - USB PHY parameters ### Default configuration Run-time default configuration for Device Stack is managed internally via the `TINYUSB_DEFAULT_CONFIG()` macros. ### Custom configuration Manual configuration for Device Stack: descriptors, peripheral port, task, and USB PHY parameters can be set as needed. ### Build-Time configuration Configure the Device Stack using `menuconfig`: - TinyUSB log verbosity - Default device/string descriptor used by the default configuration macros - Class-specific options (CDC-ACM, MSC, MIDI, HID, DFU, BTH, ECM/NCM/RNDIS, Vendor etc.) ### Supported classes - USB Serial Device (CDC-ACM) with optional Virtual File System support. - Input and output streams through the USB Serial Device (available only when Virtual File System support is enabled). - Mass Storage Device Class (MSC): create USB flash drives using SPI Flash or SD/MMC as storage media. - Support for other USB classes (MIDI, HID, etc.) directly via TinyUSB. ## How to use? This component is distributed via [IDF component manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). Just add `idf_component.yml` file to your main component with the following content: ``` yaml ## IDF Component Manager Manifest File dependencies: esp_tinyusb: "~2.0.0" ``` Or simply run: ``` idf.py add-dependency esp_tinyusb~2.0.0 ``` ## Breaking changes - [v2.0.0](https://docs.espressif.com/projects/esp-usb/docs/device/migration-guide/v2) ## USB Device Stack: usage, installation & configuration Hardware-related documentation can be found in the [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_device.html). ### Structure overview The Device Stack is built on top of TinyUSB and provides: - Custom USB descriptor support - Serial device (CDC-ACM) support - Standard stream redirection through the serial device - Storage media support (SPI Flash and SD Card) for the USB MSC class - A dedicated task for TinyUSB servicing ### Installation Install the Device Stack by calling `tinyusb_driver_install` with a `tinyusb_config_t` structure. A default configuration is available using the `TINYUSB_DEFAULT_CONFIG()` macro. The default installation automatically configures the port (High-speed if supported by the hardware, otherwise Full-speed), task (with default parameters), USB PHY, Device Event callback and descriptors. Default descriptors are provided for the following USB classes: CDC, MSC, and NCM. > **⚠️ Important:** For demonstration purposes, all error handling logic has been removed from the code examples. Do not ignore proper error handling in actual development. ```c #include "tinyusb_default_config.h" void main(void) { const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); tinyusb_driver_install(&tusb_cfg); } ``` ### Device Event callback USB Device Event callback allows to get the following events during USB Device lifecycle: - `TINYUSB_EVENT_ATTACHED`: Device attached to the USB Host - `TINYUSB_EVENT_DETACHED`: Device detached from the USB Host To configure the USB Device Event Callback, provide the callback to the `TINYUSB_DEFAULT_CONFIG()` macros: ```c #include "tinyusb_default_config.h" static void device_event_handler(tinyusb_event_t *event, void *arg) { switch (event->id) { case TINYUSB_EVENT_ATTACHED: case TINYUSB_EVENT_DETACHED: default: break; } } void main(void) { const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(device_event_handler); tinyusb_driver_install(&tusb_cfg); } ``` User Argument could be passed to the USB Device Event callback as a second argument (optional): ```c #include "tinyusb_default_config.h" static context_t *context; static void device_event_handler(tinyusb_event_t *event, void *arg) { context_t *context = (context_t*) arg; switch (event->id) { case TINYUSB_EVENT_ATTACHED: case TINYUSB_EVENT_DETACHED: default: break; } } void main(void) { const tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(device_event_handler, (void*) context); tinyusb_driver_install(&tusb_cfg); } ``` ### Peripheral port When several peripheral ports are available by the hardware, the specific port could be configured manually: ```c #include "tinyusb_default_config.h" void main(void) { tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); #if (TUD_OPT_HIGH_SPEED) tusb_cfg.port = TINYUSB_PORT_HIGH_SPEED_0; #else tusb_cfg.port = TINYUSB_PORT_FULL_SPEED_0; #endif tinyusb_driver_install(&tusb_cfg); } ``` ### Task configuration When the default parameters of the internal task should be changed: ```c #include "tinyusb_default_config.h" void main(void) { tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); tusb_cfg.task = TINYUSB_TASK_CUSTOM(4096 /*size */, 4 /* priority */, 0 /* affinity: 0 - CPU0, 1 - CPU1 ... */); tinyusb_driver_install(&tusb_cfg); } ``` ### USB Descriptors configuration Configure USB descriptors using the `tinyusb_config_t` structure: - `descriptor.device` - `descriptor.string` - `descriptor.full_speed_config` - `descriptor.high_speed_config` - `descriptor.qualifier` If any descriptor field is set to `NULL`, default descriptor will be assigned during installation. Values of default descriptors could be configured via `menuconfig`. ```c #include "tinyusb_default_config.h" void main(void) { tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); tusb_cfg.descriptor.device = &custom_device_descriptor; tusb_cfg.descriptor.full_speed_config = custom_full_speed_configuration; #if (TUD_OPT_HIGH_SPEED) tusb_cfg.descriptor.high_speed_config = custom_high_speed_configuration; #endif // TUD_OPT_HIGH_SPEED tinyusb_driver_install(&tusb_cfg); } ``` ### USB PHY configuration & Self-Powered Device For self-powered devices, monitoring the VBUS voltage is required. To do this: - Configure a GPIO pin as an input, using an external voltage divider or comparator to detect the VBUS state. - Set `self_powered = true` and assign the VBUS monitor GPIO in the `tinyusb_config_t` structure. ```c #include "tinyusb_default_config.h" void main(void) { tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); tusb_cfg.phy.self_powered = true; tusb_cfg.phy.vbus_monitor_io = GPIO_NUM_0; tinyusb_driver_install(&tusb_cfg); } ``` If external PHY is used: ```c #include "tinyusb_default_config.h" #include "esp_private/usb_phy.h" void main(void) { // Initialize the USB PHY externally usb_new_phy(...); tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(); tusb_cfg.phy.skip_setup = true; tinyusb_driver_install(&tusb_cfg); } ``` ## USB Device Classes: usage, installation & configuration ### USB Serial Device (CDC-ACM) To enable USB Serial Device: - select the option from `menuconfig`. - initialize the USB Serial Device with `tusb_cdc_acm_init` and a `tinyusb_config_cdcacm_t` structure ```c const tinyusb_config_cdcacm_t acm_cfg = { .cdc_port = TINYUSB_CDC_ACM_0, .rx_unread_buf_sz = 64, .callback_rx = NULL, .callback_rx_wanted_char = NULL, .callback_line_state_changed = NULL, .callback_line_coding_changed = NULL }; tusb_cdc_acm_init(&acm_cfg); ``` Redirect standard I/O streams to USB with `esp_tusb_init_console` and revert with `esp_tusb_deinit_console`. ### USB Mass Storage Device (MSC) To enable Mass Storage Device: - select the option from `menuconfig` - configure storage for MSC Device class: SPI Flash or SD/MMC (when supported by the hardware). **SPI-Flash Storage** ```c static wl_handle_t storage_init_spiflash(void) { wl_handle_t wl; // Find partition // Mount Wear Levelling return wl; } void main(void) { wl_handle_t wl_handle = storage_init_spiflash(); tinyusb_msc_storage_handle_t storage_hdl; const tinyusb_msc_storage_config_t cfg = { .medium.wl_handle = wl_handle, }; tinyusb_msc_new_storage_spiflash(&cfg, &storage_hdl); } ``` **SD-Card Storage** ```c static sdmmc_card_t *storage_init_sdmmc(void) { sdmmc_card_t *card; // Config sdmmc // Init sdmmc_host // Init sdmmc slot // Init sdmmc card return card; } void main(void) { sdmmc_card_t *card = storage_init_sdmmc(); tinyusb_msc_storage_handle_t storage_hdl; const tinyusb_msc_storage_config_t cfg = { .medium.card = card, }; tinyusb_msc_new_storage_sdmmc(&cfg, &storage_hdl); } ``` **Storage callback** Storage event callback is called, when one of the following events occurred: - `TINYUSB_MSC_EVENT_MOUNT_START`: Start mount from APP to USB or from USB to APP - `TINYUSB_MSC_EVENT_MOUNT_COMPLETE`: Complete mount from USB to APP or from APP to USB - `TINYUSB_MSC_EVENT_FORMAT_REQUIRED`: Filesystem not found, format needed - `TINYUSB_MSC_EVENT_MOUNT_FAILED`: Error occurred during mounting filesystem To use or enable storage callback there are two options available. Set the callback with specific call after creating the storage: ```c static void storage_event_handle(tinyusb_msc_storage_handle_t handle, tinyusb_msc_event_t *event, void *arg) { switch (event->id) { case TINYUSB_MSC_EVENT_MOUNT_START: case TINYUSB_MSC_EVENT_MOUNT_COMPLETE: case TINYUSB_MSC_EVENT_MOUNT_FAILED: case TINYUSB_MSC_EVENT_FORMAT_REQUIRED: default: break; } } void main(void) { sdmmc_card_t *card = storage_init_sdmmc(); tinyusb_msc_storage_handle_t storage_hdl; const tinyusb_msc_storage_config_t cfg = { .medium.card = card, }; tinyusb_msc_new_storage_sdmmc(&cfg, &storage_hdl); tinyusb_msc_set_storage_callback(storage_event_handle, NULL /* user argument for the event callback */); } ``` Install the TinyUSB MSC Storage driver explicitly and provide the storage event via configuration: ```c void main(void) { sdmmc_card_t *card = storage_init_sdmmc(); const tinyusb_msc_driver_config_t driver_cfg = { .storage_event_cb = storage_event_handle, .storage_event_arg = NULL /* user argument for the storage event callback */, }; tinyusb_msc_install_driver(&driver_cfg); tinyusb_msc_storage_handle_t storage_hdl; const tinyusb_msc_storage_config_t cfg = { .medium.card = card, }; tinyusb_msc_new_storage_sdmmc(&cfg, &storage_hdl); } ``` ### MSC Performance Optimization - **Single-buffer approach:** Buffer size is set via `CONFIG_TINYUSB_MSC_BUFSIZE`. - **Performance:** SD cards offer higher throughput than internal SPI flash due to architectural constraints. **Performance Table (ESP32-S3):** | FIFO Size | Read Speed | Write Speed | |-----------|------------|-------------| | 512 B | 0.566 MB/s | 0.236 MB/s | | 8192 B | 0.925 MB/s | 0.928 MB/s | **Performance Table (ESP32-P4):** | FIFO Size | Read Speed | Write Speed | |-----------|------------|-------------| | 512 B | 1.174 MB/s | 0.238 MB/s | | 8192 B | 4.744 MB/s | 2.157 MB/s | | 32768 B | 5.998 MB/s | 4.485 MB/s | **Performance Table (ESP32-S2, SPI Flash):** | FIFO Size | Write Speed | |-----------|-------------| | 512 B | 5.59 KB/s | | 8192 B | 21.54 KB/s | **Note:** Internal SPI flash is for demonstration only; use SD cards or external flash for higher performance. ## Examples You can find examples in [ESP-IDF on GitHub](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/device).
6a3fb95ed8e48a0e12cfe695fd46ee7f86d1d0a4
idf.py add-dependency "espressif/esp_tinyusb^2.0.0-rc0"