How to develop a USB Host device with the Xiao ESP32-S3

I want to develop code to run in an Xiao ESP32-S3 that is a USB host with a WiFi connection.

As I need the USB connector on the board to download and debug the software, but I also need to use the USB connector to connect to the USB device.

What is the best way to achieve this? (I really don’t want to have to swap USB cables al the time and I also need to see the debug output as the code is running.)

Thanks

Susan

I wasn’t able to find a solution to get this working (USB Host - Integrated JTAG/CDC and OTG at the same time) so used TCP and OTA to debug and flash the code.
Since the board design had been completed already, I wasn’t able to use JTAG and an off-board debugger.
I do have a spare serial port but that was quite slow.

To develop a USB Host device with the XIAO ESP32-S3, you can use its built-in USB-OTG feature. The ESP32-S3 supports both USB device and USB host modes.

First, connect the USB D+ and D- lines to your USB connector and make sure you provide 5V VBUS power to the external USB device (keyboard, mouse, flash drive, etc.). The XIAO board itself does not supply 5V by default, so an external 5V source is required.

Next, install the ESP32 Arduino core or use ESP-IDF. In Arduino, enable USB host support and use the TinyUSB / USBHost libraries. Espressif provides ready examples for USB host HID devices like keyboards and mice.

After uploading the example, your XIAO ESP32-S3 can detect and communicate with connected USB devices. This is the simplest way to start USB host development on the XIAO ESP32-S3.

1 Like

Hi Susan, this is a common limitation with the XIAO ESP32-S3 since it only has one USB-C port shared between programming and USB host. A practical way to avoid swapping cables is to keep the USB port in host mode for your device and use the TX/RX pins with an external USB-to-Serial adapter for debugging and logs — this lets you see real-time output without unplugging anything. You might find this related project useful: ESP32-S3-Dual Usb Type C - Share Project - PCBWay , which shows an ESP32-S3 board with dual USB Type-C ports (one for UART and one for OTG/USB-host) that illustrates how people are splitting USB roles in hardware. For software examples, this GitHub USB host demo is also helpful: esp-idf/examples/peripherals/usb/host at master · espressif/esp-idf · GitHub. Using UART debug alongside USB host mode is usually the most stable, hassle-free solution

2 Likes

Hi there,

So, What is the USB Host device you want to plug in ? Keyboard / Mouse ?
Yes it is indeed possible , in fact some have done that on here. But used BLE as the Wireless part.

Your Best solution, known to work is simplest, most traditional: keep USB-C for programming, use UART for debug, use a separate USB host connector

Recommended for reliable development.

  • Program/flash: use the XIAO’s USB-C as normal (USB device to PC).
  • Runtime debug: use Serial1 on D6/D7 (GPIO43/44) to a cheap USB-UART dongle.
  • USB Host peripheral connection: you do not use the same USB-C jack for both roles; instead you add a second connector (USB-A or another USB-C breakout) wired to D+ / D− (same lines) through a USB 2.0 switch :backhand_index_pointing_left: :grin:

You still need 5V on VBUS**. Host mode isn’t just “software”; the host must power the bus (or use a powered hub that does). If VBUS isn’t present, most USB devices won’t even wake up to enumerate. That part doesn’t go away no matter how clever the code is. :face_with_open_eyes_and_hand_over_mouth:
A single physical USB-C port can’t be your PC debug/program link and a USB Host port to a peripheral at the same time, unless you add extra hardware (hub/switch)

The biggest point: USB Host won’t work unless the device you plug in gets 5V on VBUS. The XIAO’s USB-C port is great for USB-device-to-PC, but it typically doesn’t magically become a powered host port; you’ll need to provide 5V to VBUS externally if the board doesn’t. (This is exactly why people see “nothing happens” with keyboards/mice

I took the liberty of trying this and If you have a USB hub that will take a external 5v , via 1/4" pin jack (or any powered) switch to get the 5V required for the host to present to the peripheral . etc.

Below is a clean, practical demo:

  • LED blinks on USER_LED (GPIO21) (that’s the orange user LED on the XIAO).
    • USB-C runs USB Host (HID keyboard)
  • Debug is on D6/D7 via Serial1, so you can keep the native USB dedicated to host.
// =====================================================
// XIAO ESP32S3 - USB-C as USB HOST (HID) + UART Debug + Blinky
// Debug console: Serial1 on D6/D7 (GPIO43/GPIO44)
// LED: USER_LED (GPIO21)
// USB data pins: ESP32-S3 native USB uses GPIO19 (D-) / GPIO20 (D+)
// =====================================================

#include <Arduino.h>
#include "EspUsbHost.h"

// ---- XIAO ESP32S3 UART pins ----
#define DBG_TX D6   // GPIO43 (TX)  :contentReference[oaicite:7]{index=7}
#define DBG_RX D7   // GPIO44 (RX)  :contentReference[oaicite:8]{index=8}
#define BAUD   115200

// ---- XIAO USER LED ----
#define LED_PIN 21  // USER_LED = GPIO21 :contentReference[oaicite:9]{index=9}

class MyUsbHost : public EspUsbHost {
public:
  void onKeyboardKey(uint8_t ascii, uint8_t keycode, uint8_t modifier) override {
    (void)keycode; (void)modifier;
    if (ascii >= 32 && ascii <= 126) {
      Serial1.printf("[KBD] '%c'\r\n", (char)ascii);
    } else if (ascii == '\r') {
      Serial1.println("[KBD] ENTER");
    } else {
      Serial1.printf("[KBD] ascii=0x%02X\r\n", ascii);
    }
  }

  void onGone(const usb_host_client_event_msg_t *eventMsg) override {
    (void)eventMsg;
    Serial1.println("[USB] Device disconnected");
  }
};

MyUsbHost usb;

uint32_t lastBlink = 0;
bool ledState = false;

void setup() {
  // UART debug on pins (not native USB)
  Serial1.begin(BAUD, SERIAL_8N1, DBG_RX, DBG_TX);
  delay(200);

  Serial1.println();
  Serial1.println("=== XIAO ESP32S3: USB HOST (USB-C) + UART Debug + Blinky ===");
  Serial1.println("Debug: Serial1 on D6(TX)/D7(RX)  |  LED: GPIO21");
  Serial1.println("IMPORTANT: USB HOST requires 5V on VBUS for the plugged-in USB device.");
  Serial1.println();

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  Serial1.println("[USB] Starting host...");
  usb.begin();
  Serial1.println("[USB] Host started. Plug in a USB HID keyboard (needs VBUS power!).");
}

void loop() {
  // Keep USB host stack serviced
  usb.task();

  // Blinky
  const uint32_t now = millis();
  if (now - lastBlink >= 500) {
    lastBlink = now;
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState ? HIGH : LOW);
    Serial1.printf("[BLINK] LED=%d  t=%lu ms\r\n", ledState, (unsigned long)now);
  }
}

These are really the only things you have to do , BTW Wiring you must do (or it won’t enumerate)

1) UART debug (to a USB-UART dongle)

  • Dongle TX → XIAO D7 (RX / GPIO44)
  • Dongle RX → XIAO D6 (TX / GPIO43)
  • GND → GND

2) USB Host power (VBUS) (switch/Hub)

If you don’t power VBUS, you’ll get exactly what you described: host code runs, but no device events.
HTH
GL :slight_smile: PJ :v:

  • Open your USB-UART serial monitor at 115200 → you should see the header + blink logs.
  • Plug a USB keyboard into the XIAO’s USB-C using an OTG adapter and make sure the keyboard actually gets 5V → you should see [KBD] prints.
  • If you get blink prints but no USB events: it’s almost always VBUS not powered, or you’re using a cable/adapter that doesn’t assert host mode.

FYI, Worth noting is the Real ESP32 Dev Kit-C has two USB-C ports as @olivia_49 points out.

1 Like

Firstly, thank you everyone who as responded.

@grobasoz - you have actually triggered something that I would actually required later on and that is adding OTA to the project. Therefore I’ll need to go ‘down that rabbit hole’ anyway so that is probably the place to start. Thanks for the suggestion. (I’m still not sure how to debug - perhaps using another serial port…)

@unikeyicelectronics - that is pretty much what I’ve investigated. However looking at the schematics of the Xiao ESP32-S3 Sense board that I’ll be using, the D+ and D- pads on the bottom of the board are also connected in parallel to the USB connector so that comes back to the same problem.

@olivia_49 - The idea of using the Tx/Rx pins is something to look into, as is the project that you have linked to. (Oh Great - even more homework!!! :grinning_face: )

@PJ_Glasso - Thanks for the code. I actually tend to use the ESP-IDF framework but the ideas are there. I’m actually fairly familiar with USB (certainly not an expert) and my question was more around if there was a way to use some of the other pins on the ESP32-S3 for a second USB connector (at least the D+ and D- - as you say the 5V needs to be provided by something else and this will be taken into consideration in the final design).

Thanks again all - I’ll be back after a bit of experimenting if I run into problems.

Susan

3 Likes

For JTAG debugging I use the JTAG interface.
For simple “serial” tests and debugging, I use a TCP socket and/or Web Interface.
I just use OTA over WiFi for flashing (very fast :+1:).