A push-to-talk (PTT) MIDI controller for M5Stack AtomS3 Lite (ESP32-S3). Pressing a footswitch or the built-in button sends a MIDI note over either Bluetooth LE MIDI or native USB MIDI. Designed for use with SmartSDR on iOS as a wireless/wired PTT.
⚡ Just want to install it? Flash the latest firmware from your browser at m5ptt.ok1cdj.com — no IDE, no drivers. See Flash from your browser.
📖 Operating the device? See the User Manual.
Two operating modes, selected at boot:
- Voice mode (default): the footswitch or built-in button is push-to-talk.
- CW mode: the built-in button triggers CWX keyer memories by click count (footswitch is ignored). Booting into CW mode flashes R (·−·) to confirm.
The mode is remembered across power cycles (stored in flash). Hold the built-in button while powering on to toggle between voice and CW; the new mode is saved and used on subsequent boots until you toggle again.
The device presents as M5PTT over USB and as M5PTT_XXXX over BLE (the
XXXX suffix is the last two bytes of the BLE MAC, so each unit is unique).
All actions are momentary note pulses; map them in SmartSDR's Mapping Editor.
| Action | Note | Map to |
|---|---|---|
| Voice PTT | 99 | PTT (Note On press / Note Off release) |
| CW single click | 100 | CWX macro 1 |
| CW double click | 101 | CWX macro 2 |
| CW triple click | 102 | CWX macro 3 |
| CW long press | 103 | CWX stop / abort |
Single-click resolves after a ~350 ms window (needed to distinguish double/triple click); a long press (~600 ms) fires the abort note.
The active transport is chosen automatically so the host never receives duplicate notes over two links:
| Power / connection | Active transport | Bluetooth |
|---|---|---|
| Plugged into a USB host (computer / iPad) | USB MIDI | dropped, advertising off |
| Powered from a charger / power bank (no data host) | BLE MIDI | advertising on |
When a USB host enumerates the device it becomes the sole transport: any BLE client is disconnected and advertising stops. Unplug from the host and it falls back to BLE and resumes advertising.
Testing note: while cabled to a computer for flashing/serial, the device is in USB mode and BLE is off. To test the BLE path, power it from a plain USB charger or battery (not a data host).
| Function | Pin | Notes |
|---|---|---|
| External footswitch | GPIO1 | Grove port, G1 (yellow wire), INPUT_PULLUP, active-low |
| Built-in button | GPIO41 | active-low |
| Status LED | GPIO35 | 1× WS2812C RGB |
| USB | USB-C | Native USB / OTG (CDC serial + USB MIDI) |
Both inputs are debounced (50 ms).
| Color | Meaning |
|---|---|
| Red | PTT active |
| Green | USB host connected (USB MIDI active), idle |
| Solid blue / blue blink | Voice mode: BLE connected / advertising |
| Solid magenta / magenta blink | CW mode: BLE connected / advertising |
| Morse "R" at boot | CW mode confirmed |
| N white blinks | CW memory N sent (1/2/3) |
| Long red blink | CW abort sent |
The easiest way, no toolchain required. Open m5ptt.ok1cdj.com in Chrome, Edge, or Brave on a desktop — Web Serial isn't available in Safari or on iOS/iPadOS — then:
- Unplug the AtomS3 Lite.
- Press and hold the top button, plug in a USB-C data cable while holding, and keep holding for ~2 s after it powers up. This enters download mode; the board has no auto-reset circuit, so it's needed every time you flash.
- Click Connect device, pick the matching serial port, then release the button.
- Wait for the flash to finish — that's it.
The page always serves the latest released firmware and shows its version next to the install button.
Requires PlatformIO.
make all # build
make upload # flash over USB-C
make clean # clean build artifactsSerial monitor at 115200 baud (pio device monitor) — outputs a [dbg]
status line at 1 Hz plus [BLE]/[USB] transport-change events.
Because the firmware uses native USB (OTG) mode for USB MIDI, esptool's
automatic reset-to-bootloader does not work. Before make upload, put the
board in download mode manually:
- Press and hold the top button for ~2 s until the internal LED turns green.
- Release, then run
make upload.
- USB mode: the AtomS3 board defaults to
ARDUINO_USB_MODE=1(hardware USB-Serial/JTAG); this project forces=0(OTG/TinyUSB) inplatformio.iniso it can enumerate as a USB MIDI device. - BLE advertising: built explicitly — the 128-bit MIDI service UUID in the
primary packet (so iOS recognizes it as MIDI) and the unique
M5PTT_XXXXname in the scan response (it won't fit alongside the 128-bit UUID in one packet). No BLE appearance is advertised:0x03C0is the HID appearance, which made SmartSDR misclassify the device (stuck at "waiting for controller"). - BLE connection state: the BLE-MIDI library's connect callbacks don't fire
under NimBLE-Arduino 2.x (changed virtual signature), so connection state is
polled via
getConnectedCount(). - Host-cached USB name: the USB product string is
M5PTT, but macOS/iOS CoreMIDI caches USB-MIDI names by VID:PID. If an old name persists, remove the stale device on the host.
Intended for USB or external power (AtomS3 Lite has no battery/PMIC). To stay reliable with iOS BLE (which drops peripherals that miss connection events), light sleep is not used. Power is instead reduced by never starting WiFi, lowering BLE TX power to −12 dBm, shutting down BLE entirely when on USB, and keeping the LED dim.
MIT — see LICENSE.
