Joypad OS supports ESP32-S3 as a build target for the BT2USB app. The ESP32-S3 has both BLE and USB OTG, making it a compact standalone BLE-to-USB HID gamepad adapter.
| Feature | Pico W / Pico 2 W | ESP32-S3 |
|---|---|---|
| Bluetooth | Classic BT + BLE | BLE only |
| USB Output | USB Device (RP2040) | USB OTG Device |
| Controllers | All BT controllers | BLE controllers only |
| Build System | pico-sdk / CMake | ESP-IDF / CMake |
| Firmware Update | Drag-and-drop (.uf2) | Drag-and-drop (.uf2) via TinyUF2 |
BLE-only limitation: ESP32-S3 only supports BLE controllers. Classic Bluetooth controllers (like DualShock 3 in BT mode) will not work. Most modern controllers support BLE.
| Controller | Status |
|---|---|
| Xbox One / Series (BLE mode) | Supported |
| 8BitDo controllers (BLE mode) | Supported |
| Switch 2 Pro (BLE) | Supported |
| Generic BLE HID gamepads | Supported |
| Controller | Why |
|---|---|
| DualShock 3 | Classic BT only |
| DualShock 4 | Classic BT only |
| DualSense | Classic BT only |
| Switch Pro | Classic BT only |
| Wii U Pro | Classic BT only |
| Wiimote | Classic BT only |
These controllers pair over Classic BT and require the Pico W build.
| Board | Flash | Status | Notes |
|---|---|---|---|
| Seeed XIAO ESP32-S3 | 8MB | Tested | User LED on GPIO 21 (active low) |
| ESP32-S3-DevKitC | Varies | Should work | Untested |
ESP32-S3 is the only ESP32 variant with both BLE and USB OTG:
| Chip | BLE | USB OTG | bt2usb? |
|---|---|---|---|
| ESP32 | Yes (+ Classic) | No | No |
| ESP32-S2 | No | Yes | No |
| ESP32-S3 | Yes | Yes | Yes |
| ESP32-C3/C6/H2 | Yes | No | No |
The ESP32-S3 build uses Adafruit TinyUF2 as a bootloader, enabling the same drag-and-drop .uf2 firmware updates as RP2040 boards.
TinyUF2 lives in a factory partition on flash. When activated, it presents a USB mass storage drive. Drop a .uf2 file on the drive and the firmware updates automatically.
8MB Flash Layout
────────────────────────────────────────
0x000000 Bootloader (TinyUF2's custom 2nd-stage)
0x008000 Partition table
0x009000 NVS (20KB)
0x00E000 OTA data (8KB) — tells bootloader which app to run
0x010000 ota_0 (2MB) — Joypad firmware lives here
0x210000 ota_1 (2MB) — unused
0x410000 factory (256KB) — TinyUF2 mass storage app
0x450000 ffat (3.7MB) — TinyUF2 virtual FAT filesystem
Normal boot: bootloader reads OTA data, boots ota_0 (Joypad app). TinyUF2 mode: bootloader boots factory partition (TinyUF2), USB drive appears. Recovery: if ota_0 is corrupted/empty, bootloader falls back to factory (TinyUF2) automatically.
Flash the official TinyUF2 release for your board once. This installs the bootloader, partition table, and TinyUF2:
- Download the TinyUF2 release for your board from Adafruit TinyUF2 releases
- For XIAO ESP32-S3:
tinyuf2-seeed_xiao_esp32s3-X.XX.X.zip
- For XIAO ESP32-S3:
- Extract the zip and flash
combined.bin:esptool.py --chip esp32s3 -b 921600 write_flash 0x0 combined.bin
- The device reboots into TinyUF2 — a USB drive named
XIAOS3BOOTappears - Drop a
.uf2firmware file onto the drive (or runmake flash-uf2-bt2usb_esp32s3)
After this one-time setup, all future updates are drag-and-drop.
Three ways to enter TinyUF2 from a running app:
| Method | How |
|---|---|
| Double-tap reset | Press the reset button twice within 500ms |
| CDC command | Send BOOTSEL over the USB serial port |
| Recovery | If the app is corrupted, TinyUF2 boots automatically |
Drag-and-drop (recommended for end users):
- Enter TinyUF2 mode (double-tap reset or send
BOOTSEL) - A USB drive appears (
XIAOS3BOOTon XIAO) - Copy the
.uf2file to the drive - Device reboots into the new firmware
Via make (recommended for development):
# Build + flash via TinyUF2 USB drive (device must be in TinyUF2 mode)
make flash-uf2-bt2usb_esp32s3
# Build + flash via esptool USB serial (device must be in download mode)
make flash-bt2usb_esp32s3Generating .uf2 files:
make uf2-bt2usb_esp32s3
# Output: releases/joypad_<version>_bt2usb_esp32s3.uf2# One-time setup: install ESP-IDF and tools
make init-espThis clones ESP-IDF v6.0 to ~/esp-idf and installs the ESP32-S3 toolchain. If you already have ESP-IDF installed, it will skip the clone and just install tools.
From the repo root:
make bt2usb_esp32s3 # Build
make uf2-bt2usb_esp32s3 # Build + generate .uf2
make flash-uf2-bt2usb_esp32s3 # Build + flash .uf2 via TinyUF2 drive
make flash-bt2usb_esp32s3 # Build + flash via esptool
make monitor-bt2usb_esp32s3 # UART serial monitor (Ctrl+] to exit)Or from the esp/ directory:
cd esp
source env.sh # Activate ESP-IDF environment
make build # Build
make uf2 # Build + generate .uf2
make flash # Build + flash via esptool
make monitor # UART serial monitorBoard-specific sdkconfig overrides go in esp/sdkconfig.board.<name>:
echo 'CONFIG_SOME_OPTION=y' > esp/sdkconfig.board.myboard
cd esp && make BOARD=myboard build- Flash the firmware
- The adapter starts scanning for BLE devices automatically on boot
- Put your controller into BLE pairing mode
- Once paired, the controller reconnects automatically on subsequent use
| Action | Function |
|---|---|
| Click | Start 60-second BLE scan |
| Double-click | Cycle USB output mode |
| Triple-click | Reset to default HID mode |
| Hold | Disconnect all devices and clear bonds |
| LED State | Meaning |
|---|---|
| Blinking | No device connected (scanning/idle) |
| Solid on | Device connected |
Double-click the button to cycle through output modes: XInput, DInput, Switch, PS3, PS4/PS5.
Connect to the USB serial port for device management:
| Command | Function |
|---|---|
VERSION |
Show firmware version |
DEVICE |
Show connected controller info |
BOOTSEL |
Reboot into TinyUF2 mode |
HELP |
List all commands |
ESP32-S3 uses FreeRTOS with two tasks:
Main Task BTstack Task
USB device polling BLE scanning/pairing
LED status updates HID report processing
Button input Controller data -> router
Storage persistence
BTstack runs in its own FreeRTOS task. All BLE operations (scanning, pairing, data) happen in that task. The main task handles USB output, LED, and button input.
The ESP32-S3 build compiles the same shared source files as the Pico W build:
- Core services (router, players, profiles, hotkeys, storage, LEDs)
- All USB device output modes (HID, XInput, PS3, PS4, Switch, etc.)
- All BT HID drivers (vendor-specific and generic)
- BTstack host integration
- CDC serial command interface
Platform-specific code is abstracted through src/platform/platform.h.
| Location | Purpose |
|---|---|
esp/main/main.c |
FreeRTOS entry point |
esp/main/flash_esp32.c |
NVS-based settings storage |
esp/main/button_esp32.c |
GPIO button driver |
esp/main/btstack_config.h |
BLE-only BTstack configuration |
esp/main/tusb_config_esp32.h |
TinyUSB device configuration |
esp/partitions_uf2.csv |
Partition table (matches TinyUF2 layout) |
esp/tools/ota_data_ota0.bin |
Pre-built OTA data (boots ota_0) |
esp/tools/uf2conv.py |
UF2 file conversion tool |
src/platform/esp32/platform_esp32.c |
Platform HAL (time, reboot, double-tap reset) |
src/bt/transport/bt_transport_esp32.c |
BLE transport layer |