Skip to content

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:

Terminal window
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

Terminal window
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 /usb
const 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

Terminal window
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

Terminal window
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:

SettingDefaultPurpose
CONFIG_USB_HOST_DEBOUNCE_DELAY_MS250Device connection debounce
CONFIG_USB_HOST_RESET_HOLD_MS30Port reset duration
CONFIG_USB_HOST_RESET_RECOVERY_MS30Recovery after reset
CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS10Recovery after SetAddress
CONFIG_USB_HOST_HUBS_SUPPORTEDOffEnable external USB hub support

Limitations

The ESP32-S3 USB OTG has some constraints to be aware of:

ConstraintDetail
SpeedFull-speed only (12 Mbps), no high-speed (480 Mbps)
Channels8 host channels — each endpoint uses one channel
Hub supportAvailable but uses channels; practical limit ~3-4 devices
PHY sharingUSB-OTG and USB-Serial/JTAG share the same PHY — only one active at a time
TransfersAsynchronous only; no synchronous/blocking API
VBUS sensingNo hardware VBUS voltage monitoring

Troubleshooting

Device not enumerating

  1. Check that the OTG jumper is soldered (or external 5V is supplied to the device)
  2. Verify the USB OTG adapter cable is wired correctly (ID pin grounded)
  3. Confirm the usb_host_task is running and calling usb_host_lib_handle_events()
  4. Check ESP-IDF log output — set USB_HOST log level to DEBUG in menuconfig

Device enumerates but class driver doesn’t work

  1. Verify the class driver component is added (idf.py add-dependency)
  2. Check that the client is registered with usb_host_client_register()
  3. 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