Skip to content

sfjohnso/ESP32

Repository files navigation

ESP32-S3 BLE Keyboard

Turn an Adafruit Feather ESP32-S3 (No PSRAM) into a Bluetooth Low Energy HID keyboard that an iPhone can pair with. Text sent to the board over USB serial is typed out to the paired phone as keystrokes.

Current status — WORKING end to end

  • Board identified, hardware/peripherals documented (HARDWARE.md)
  • Toolchain installed (arduino-cli 1.5.1 + esp32 core 3.3.10)
  • Firmware written: BLE HID keyboard + USB-serial-to-keystroke bridge
  • Built with USBMode=hwcdc (single stable serial port) and flashed
  • iPhone pairing confirmed — appears as "ESP32-S3 Keyboard", pairs/bonds
  • Serial bridge verified — text sent over USB serial is typed to the phone (tested: "Roster"/"roster" -> [typed] 7 chars ack, appeared in Notes)
  • LED heartbeat: 3 quick blinks at boot, then fast blink (advertising) / slow blink (connected)

Day-to-day use (board already flashed)

  1. Pair once on the iPhone: Settings > Bluetooth > "ESP32-S3 Keyboard".
  2. Focus a text field on the phone (e.g. the Notes app).
  3. Send text: powershell -ExecutionPolicy Bypass -File scripts\send.ps1 -Port COM6 -Message "Hello from ESP32!"

Rebuilding from scratch on a new machine

  1. scripts\setup.ps1 (rebuild toolchain, ~1.5 GB)
  2. scripts\build.ps1 (compile, uses hwcdc)
  3. If flash.ps1 hits error 31, put board in bootloader (hold BOOT, tap RESET, release BOOT), find its COM port, then scripts\flash.ps1 -Port COMx
  4. Pair on iPhone, then scripts\send.ps1 -Port COMx -Message "..."

How it works

  • The sketch uses only the official ESP32 Arduino core's built-in BLE library (BLEHIDDevice) — no third-party libraries.
  • BLE security is set to Secure Connections + Bonding because iOS only accepts HID keyboard input over an encrypted/bonded link.
  • loop() reads everything available on Serial (USB) and types it to the phone, pressing Enter on newline (configurable via APPEND_ENTER_ON_NEWLINE).
  • The LED is a heartbeat so you can see the program is alive.

Toolchain (pinned)

Component Version Source
arduino-cli 1.5.1 downloads.arduino.cc (installed to %USERPROFILE%\tools)
ESP32 Arduino core esp32:esp32@3.3.10 board manager URL below
Board FQBN esp32:esp32:adafruit_feather_esp32s3_nopsram:USBMode=hwcdc
Board manager URL https://espressif.github.io/arduino-esp32/package_esp32_index.json

setup.ps1 installs arduino-cli per-user (no admin needed). The core download is ~1.5 GB and the first compile builds the whole framework from source (this took ~30 min on the original, slower machine; the new machine with admin + more cores should be faster). Subsequent compiles are cached and take seconds.

Hardware quirks we learned (important — saves hours)

  • Native USB, no separate USB-serial chip. The ESP32-S3 USB behaves very differently from classic ESP32 dev boards.
  • Bootloader entry for flashing: auto-reset is unreliable. If flash.ps1 fails with PermissionError(13 ... not functioning) (Windows error 31), enter the ROM bootloader manually: hold BOOT, tap RESET, release BOOT. The board then enumerates as native USB-Serial/JTAG (VID 303A), often on a new COM number.
  • Two USB identities:
    • App firmware (Adafruit factory image) showed up as VID 239A / PID 8113.
    • ROM bootloader / hardware USB-Serial/JTAG shows up as VID 303A / PID 1001.
  • Serial open must NOT assert DTR/RTS — on the hardware USB-Serial/JTAG those lines are wired to reset/boot. send.ps1 opens with both de-asserted.
  • Opening the port can still reset the chip (rst:0x15 USB_UART_CHIP_RESET), which briefly drops the BLE link. send.ps1 therefore waits ~5 s after opening for the phone to auto-reconnect, and retries if the firmware reports [skip].
  • USBMode=hwcdc + Serial.setTxTimeoutMs(0) are both required. Without the non-blocking TX timeout, Serial.print() in setup() blocks forever when no terminal is attached and the firmware never reaches loop() (no LED, no BLE).
  • If the board seems dead (no LED, not advertising) after lots of flashing/ serial poking, it may be wedged in ROM download mode — unplug/replug to get a clean power-on boot.
  • ESP32-S3 is BLE-only (no Bluetooth Classic). iOS pairs with BLE HID fine.
  • Core 3.x note: the old BLE2902 descriptor class was removed; the sketch does not use it (CCCD is handled automatically by BLEHIDDevice).

Sending messages to the phone

A BLE keyboard types wherever the phone's cursor is — it does not "send" a message on its own. Focus a text field first (Notes is easiest). Newline triggers Enter, so send.ps1 -Port COMx -Message "hi" types hi then Enter. Use -NoEnter to skip the Enter (e.g. when typing into a search box you don't want to submit).

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors