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:).

how would I connect a separate usbc port to my xiao esp32s3? I connected data + and - on xiao to the d-+ on my external usbc, then plugged in the external usbc to my pc but it didn’t detect it, I had the board powered through the b+ and - pads on the back and confirmed it works.

for reference: my goal is to connect a faster usbc charger for my battery pack and use that port for usbc data connection. (As there are no charging ports that also have data connection I will use 2 additional external ports one for data and power transfer and the second to handle the recharging of batteries)

Hi there,

And welcome here,

So the S3 second com port ie. pin 6 & 7 (SilkScreen) Can be used no problem, You will need a proper TTL to RS-232 (Usb-C)converter as the logic level are different. Between you Com port on the computer and The IO_Pins of Xiao (3.3V)

In this configuration, The Main USB is the Battery charging Port as well as the Programming Port. and Either Com of the Ports can be used to send or receive data fro XIAO. :+1:

HTH
GL :slight_smile: PJ :v:

1 Like

Thank you for your help PJ,

I didn’t understand a lot of that, but I took some time to look things up to hopefully save your time. I’ll restate what you said from what I think you meant
I was using the D+ and - on the back of the esp32s3 for data transfer but the pins 6 and 7 will work instead the ones that are RX and TX. I need a module like “2PCS USB to TTL Serial Converter Adapter Type-C FT232RL Module 3.3V 5.5V FTDI Breakout Board Compatible with Arduino“ (on amazon) to be the port that takes in data and power.
I think the com is like the ground? But I’m not sure what you mean by the computer, like the device I send data with through the usb?
is the main usb the TTL to RS232 you mentioned, does that let me charge my 18650 batteries at 3A? Or do I still need a component to regulate the charge safely?

and lastly I don’t understand being able to send data from the com of the ports I thought that was a baseline 0V.

This is much appreciated, thank you for your time

Sincerely,

HANNAH

what are you trying to use the extra USB port for… we assumed communicating with another device/computer

you would not be able to use this extra USB to charge anything

Hi there,

Ok, SO I mean by com port, that is short for communication port.
Yes the D+ and D- pads/pins are also a communications port too.

Each port does have .. TX , RX and a GND, or common (0volts) as you know. :grin:

Yes, converters are needed on both if you want to interface it with the outside word , unless the device is a TTL logic level already, in that case just connect TXD to RXD on the end device, and Visa-versa..RXD to TXD on the other device.

I have used /run & charged a without issues, a single 3.7vdc 18650 with the Xiao’s on-board charge controller.

All of the Expansion and dev boards will charge one. Each Xiao has different charge current so check out the Wiki for the XIAO Family, shows each one 's charge current.

HTH
GL :slight_smile: PJ :v:

You may be able to use one of these to charge the 18650 (at 2A), and keep the XIAO USB for normal communications.

The VBus (14) and Gnd (13) would power this unit, and the battery could be connected to the XIAO Bat+ and Bat- via a blocking diode. This would be a charge only setup.

1 Like