Firmware for a Seeed Studio XIAO ESP32C3 that bridges standard 5-pin DIN MIDI to ESP-NOW using ESP32_Host_MIDI. Two boards running this sketch become a wireless MIDI cable.
Board profile: XIAO ESP32C3 (board = seeed_xiao_esp32c3). 21 × 17.8 mm, native USB-C, 4 MB QIO flash.
Attach the included u.FL antenna before powering the board. The C3 XIAO has no onboard antenna. Without it, ESP-NOW transmit can look fine while the other board receives nothing.
Do not use an ESP32-C3 Super Mini. Those clones have a broken RF layout (ceramic antenna against the crystal). This firmware targets the XIAO only.
The library does not route MIDI by itself.
UARTConnectionandESPNowConnectionare bothMIDITransports.midiHandler.addTransport(...)registers them.midiHandler.task()polls each one.- Incoming bytes are parsed into one shared event queue. Every event has
event.sourcepointing at the transport that received it. sendMidiMessage()only transmits. It does not enqueue the message again.
The bridge therefore:
if (event.source == UART) send only to ESP-NOW
if (event.source == ESP-NOW) send only to UART
otherwise drop
DIN-5 IN --> UART0 (GPIO20 / D7 RX) --> queue --> ESP-NOW radio
DIN-5 OUT <-- UART0 (GPIO21 / D6 TX) <-- queue <-- ESP-NOW radio
That is the same pattern as the library's MIDI-Router example. The jam example is simpler because it never uses MIDIHandler: it calls espNow.setMidiCallback(...) itself. Do not copy that here — addTransport() already installs the callback that fills the queue.
Receive and parsing come from the library's ESPNowConnection. src/ESPNowMidi.h only:
- waits for Arduino-ESP32 3.x STA to start before
begin() - sends to a unicast MAC when
ESPNOW_PEER_MACis set (upstreamsendMidiMessage()always broadcasts)
| Topic | Behavior |
|---|---|
| Addressing | Broadcast by default. Optional unicast via ESPNOW_PEER_MAC (the other board's MAC printed at boot). |
| Channel | ESPNOW_CHANNEL (default 1). Both boards must match. |
| Payload | 2–3 MIDI bytes. Notes, CC, program change, channel pressure, pitch bend. |
| Not carried | SysEx (too long). Clock / Start / Stop (1-byte realtime is dropped on ESP-NOW receive). |
Default pins (src/config.h): GPIO 20 (D7 / RX) = MIDI IN, GPIO 21 (D6 / TX) = MIDI OUT. Debug logs use native USB CDC (Serial on USB-C). The ESP32-C3 has no UART2; MIDI runs on UART0 (Serial0) so the USB console stays free.
Seeed recommends D6 as an output (it is U0TXD and dumps bootloader text at reset). That matches MIDI OUT. MIDI IN on D7 (U0RXD) is the labeled RX pin. A few garbage bytes may appear on MIDI OUT at boot — ignore them, or mute the downstream device until the bridge prints its banner.
There is no user LED (only a charge indicator). Activity pulses are off unless you wire an LED + ~150 Ω to D10 (GPIO 10) and set STATUS_LED_PIN to 10.
- DIN pin 4 → 220 Ω → optocoupler LED anode
- DIN pin 5 → LED cathode
- Optocoupler output → GPIO 20 (D7 / RX)
- Follow the coupler datasheet for VCC / pull-up (3.3 V on the ESP32 side)
- XIAO 5V (USB) → 220 Ω → DIN pin 4
- GPIO 21 (D6 / TX) → 220 Ω → DIN pin 5
- DIN pin 2 → GND (cable shield)
3.3 V out works with a lot of modern gear; 5 V is closer to the MIDI spec.
If the board brownout-resets when Wi-Fi transmits, use a short USB cable straight into the computer (not a hub).
pio run -e seeed-xiao-esp32c3 -t upload
pio device monitorNeed Arduino-ESP32 3.x (pioarduino). Upload is 921600 over native USB. The board JSON already enables USB CDC on boot; platformio.ini repeats those flags.
- Data cable, not charge-only. Prefer a short cable straight into the computer, not a hub.
- Close the serial monitor before Upload.
- Port never appears. Click RESET once while plugged in. Still missing: hold BOOT, plug in USB, then release BOOT to enter the ROM bootloader.
- Upload times out. Hold BOOT, click Upload, keep holding until writing starts, then release. If that still times out: hold BOOT, tap RESET, keep holding BOOT.
- Drop
upload_speedto 460800 if 921600 is flaky.
- Boards Manager: esp32 by Espressif, 3.0 or newer.
- Board: XIAO_ESP32C3.
- USB CDC On Boot: Enabled.
- Flash Size: 4MB. Flash Mode: QIO. Upload Speed: 921600.
- Library Manager: install ESP32_Host_MIDI.
- Copy
src/main.cpp,src/config.h, andsrc/ESPNowMidi.hinto a sketch folder (renamemain.cpptoyour_sketch.ino).
#define MIDI_RX_PIN 20
#define MIDI_TX_PIN 21
#define ESPNOW_CHANNEL 1
#define ESPNOW_PEER_MAC 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // broadcastUse two boards on the same channel. For unicast, set each board's ESPNOW_PEER_MAC to the other board's printed MAC.
UART DIN-5 <-> ESP-NOW MIDI bridge
ESP-NOW mode: broadcast
This board MAC AA:BB:CC:DD:EE:FF
ESP-NOW channel: 1
UART MIDI: RX=GPIO20 TX=GPIO21 @ 31250 baud (UART0)
Transports:
[0] UART connected=1
[1] ESP-NOW connected=1
UART -> NOW NoteOn ch=1 C4 vel=100
NOW -> UART NoteOff ch=1 C4 vel=0
stats UART->NOW=12 NOW->UART=12
MIT