USB Host Mode
The ESP32-S3’s USB OTG controller can act as a USB host, allowing you to connect keyboards, mice, flash drives, serial adapters, and cameras directly to the DevKitC-1.
Hardware Requirements
Before writing any code, the board needs a hardware modification and proper power setup.
1. Solder the OTG Jumper
On the bottom of the board, there is an unpopulated 2-pad footprint labeled “USB OTG”. This is a 0Ω jumper that connects the 5V rail to the USB VBUS pin on J2, allowing the board to supply power to connected devices.
Options:
- Solder bridge: Apply a small blob of solder across both pads
- 0Ω 0402/0603 resistor: For a cleaner, reversible modification
- External power: Skip the jumper entirely and power the USB device separately
2. Power the Board Externally
When the OTG jumper is soldered, do not power the board through J2 (the ESP USB port). The jumper bypasses the Schottky diode D7, which means connecting J2 to your PC would short two 5V sources together.
Safe power options:
- Power through J4 (UART USB port) — this has its own Schottky diode D1
- Power through the 5V pin on the header (J1, pin 21)
- Power through the 3V3 pin on the header (J1, pin 2) — bypasses the LDO
3. Connect a USB OTG Adapter
You need a Micro-USB OTG adapter (Micro-B male to USB-A female) to connect standard USB devices to J2.
Software Architecture
The ESP-IDF USB Host stack is layered:
graph TD
A["Class Drivers (HID / MSC / CDC / UVC)"] -->|"usb_host_install()"| B["USB Host Library"]
B --> C["Host Controller Driver"]
C --> D["USB PHY Driver<br/>GPIO19 (D-), GPIO20 (D+)"]
style A fill:#2563eb,color:#fff,stroke:#1d4ed8
style B fill:#0891b2,color:#fff,stroke:#0e7490
style C fill:#4f46e5,color:#fff,stroke:#4338ca
style D fill:#059669,color:#fff,stroke:#047857
Calling usb_host_install() initializes the entire stack and automatically configures GPIO19/GPIO20 for the internal USB PHY. You don’t need to configure the pins manually.
Minimal USB Host Setup
Every USB host application needs a daemon task to handle library-level events (device connect/disconnect, enumeration):
#include "usb/usb_host.h"#include "esp_log.h"
static const char *TAG = "usb_host";
static void usb_host_task(void *arg){ const usb_host_config_t host_config = { .skip_phy_setup = false, .intr_flags = ESP_INTR_FLAG_LEVEL1, }; ESP_ERROR_CHECK(usb_host_install(&host_config)); ESP_LOGI(TAG, "USB Host library installed");
while (1) { uint32_t event_flags; usb_host_lib_handle_events(portMAX_DELAY, &event_flags); if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { ESP_LOGI(TAG, "No more clients"); } if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { ESP_LOGI(TAG, "All devices freed"); } }}
void app_main(void){ xTaskCreatePinnedToCore(usb_host_task, "usb_host", 4096, NULL, 5, NULL, 0);}Class Drivers
Espressif provides managed class drivers via the ESP Component Registry. These sit on top of the Host Library and handle protocol-specific communication.
HID — Keyboards and Mice
Add the component to your project:
idf.py add-dependency "espressif/usb_host_hid"The HID driver handles report descriptors, input report parsing, and device polling:
#include "usb/hid_host.h"
static void hid_host_event_callback(const hid_host_event_t *event, void *arg){ switch (event->event) { case HID_DEVICE_CONNECTED: ESP_LOGI(TAG, "HID device connected"); break; case HID_DEVICE_DISCONNECTED: ESP_LOGI(TAG, "HID device disconnected"); break; }}
static void hid_input_report_callback(const uint8_t *data, int length, void *arg){ // Parse keyboard/mouse reports here ESP_LOG_BUFFER_HEX(TAG, data, length);}See the full example: examples/peripherals/usb/host/hid
MSC — Flash Drives
idf.py add-dependency "espressif/usb_host_msc"The MSC driver exposes a block device interface that integrates with ESP-IDF’s Virtual Filesystem (VFS):
#include "usb/msc_host.h"#include "usb/msc_host_vfs.h"
// After MSC device connects:// Mount the USB drive to /usbconst esp_vfs_fat_mount_config_t mount_config = { .format_if_mount_failed = false, .max_files = 5, .allocation_unit_size = 16 * 1024,};Once mounted, standard POSIX file operations (fopen, fread, fwrite) work on /usb/ paths.
See the full example: examples/peripherals/usb/host/msc
CDC-ACM — Serial Devices
idf.py add-dependency "espressif/usb_host_cdc_acm"Connect USB serial adapters, modems, or other CDC-ACM devices:
#include "usb/cdc_acm_host.h"
static void cdc_data_callback(const uint8_t *data, size_t length, void *arg){ ESP_LOGI(TAG, "Received %d bytes from USB serial device", length); ESP_LOG_BUFFER_HEX(TAG, data, length);}See the full example: examples/peripherals/usb/host/cdc/cdc_acm_host
UVC — USB Cameras
idf.py add-dependency "espressif/usb_host_uvc"Capture video frames from USB cameras. Note that full-speed USB (12 Mbps) limits resolution and frame rate.
See the full example: examples/peripherals/usb/host/uvc
Configuration (menuconfig)
Key settings under Component config → USB Host:
| Setting | Default | Purpose |
|---|---|---|
CONFIG_USB_HOST_DEBOUNCE_DELAY_MS | 250 | Device connection debounce |
CONFIG_USB_HOST_RESET_HOLD_MS | 30 | Port reset duration |
CONFIG_USB_HOST_RESET_RECOVERY_MS | 30 | Recovery after reset |
CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS | 10 | Recovery after SetAddress |
CONFIG_USB_HOST_HUBS_SUPPORTED | Off | Enable external USB hub support |
Limitations
The ESP32-S3 USB OTG has some constraints to be aware of:
| Constraint | Detail |
|---|---|
| Speed | Full-speed only (12 Mbps), no high-speed (480 Mbps) |
| Channels | 8 host channels — each endpoint uses one channel |
| Hub support | Available but uses channels; practical limit ~3-4 devices |
| PHY sharing | USB-OTG and USB-Serial/JTAG share the same PHY — only one active at a time |
| Transfers | Asynchronous only; no synchronous/blocking API |
| VBUS sensing | No hardware VBUS voltage monitoring |
Troubleshooting
Device not enumerating
- Check that the OTG jumper is soldered (or external 5V is supplied to the device)
- Verify the USB OTG adapter cable is wired correctly (ID pin grounded)
- Confirm the
usb_host_taskis running and callingusb_host_lib_handle_events() - Check ESP-IDF log output — set
USB_HOSTlog level toDEBUGin menuconfig
Device enumerates but class driver doesn’t work
- Verify the class driver component is added (
idf.py add-dependency) - Check that the client is registered with
usb_host_client_register() - Some devices require specific interface claim order
Power issues
- Symptoms: Device connects briefly then disconnects, or USB errors in log
- The SGM2212 LDO provides 500mA at 3.3V — USB devices drawing significant current from the 5V rail (via the OTG jumper) bypass the LDO but still share the upstream power source
- For high-power USB devices (>100mA), use a powered USB hub or external 5V supply