M5Stack Cardputer-Adv firmware development
Cardputer-Adv (SKU K132-Adv) = Stamp-S3A = ESP32-S3FN8: dual-core LX7 @240 MHz,
8 MB flash, no PSRAM, ESP32-S3 native USB (USB-Serial/JTAG, VID:PID 303a:1001),
1.14" ST7789 240×135, TCA8418 I2C keyboard controller, ES8311 audio codec.
Everything in this skill was verified on real hardware (2026-09, macOS 15, PlatformIO 6.2,
Arduino core 2.0.16 / espressif32@6.7.0). Timings, pins and failure modes are measured, not copied.
Follow this workflow
- Confirm the board enumerates:
ls /dev/cu.usbmodem* and ioreg -p IOUSB -w0 | grep -i jtag.
- Scaffold the PlatformIO project from references/toolchain.md.
The
dio flash mode line is not optional — see rule 1.
- Let M5Unified auto-detect the board. Do not hand-initialise the panel, keyboard or
codec;
M5Cardputer.begin() already does all of it for board_M5CardputerADV.
- Do not call
Serial.begin() in the default build (rule 2). Report progress with the
RGB LED instead — it is the only status channel that survives a hang.
- Build, then flash with scripts/flash.sh (esptool 5.x, not PlatformIO's).
- Physically power-cycle the board (unplug/replug USB) and read the LED stage before
concluding anything about your code.
- Only then debug functionally, with references/debugging.md
as the symptom → cause table.
Non-negotiable rules
Each of these cost a full debugging session; they look like "my code is broken" but are not.
| # |
Rule |
What it looks like when violated |
| 1 |
board_build.flash_mode = dio. Never qio, even though the stock esp32-s3-devkitc-1 board definition defaults to it - and note that M5Stack's own PlatformIO snippet in the product PDF omits this line, so the official recipe inherits qio. |
Endless reboot, black screen. The ROM prints boot:0x3 (DOWNLOAD(USB/UART0)) — the app never runs at all. With dio the same image prints boot:0xb (SPI_FAST_FLASH_BOOT) and boots. |
| 2 |
Do not call Serial.begin() unless a host is actively draining the port. With ARDUINO_USB_CDC_ON_BOOT=1 the CDC write path blocks once the TX buffer fills, hanging setup() before any M5 init. |
App hangs with the RGB LED stuck on the first colour you set. Guard serial output behind a MIDI_SERIAL_DEBUG-style macro that defaults to 0. |
| 3 |
Do not open /dev/cu.usbmodem* from a host script while the app is supposed to keep running. Opening the port toggles DTR/RTS, and the ESP32-S3 interprets that as a reset pulse. |
A perfect fake crash loop: every read attempt yields a ROM banner. This misleads diagnosis badly — see references/debugging.md. |
| 4 |
Flash with esptool 5.x. PlatformIO's bundled esptool 4.5.1 cannot sync with this board. |
A fatal error occurred: No serial data received while 5.x talks to the same port fine. |
| 5 |
Entering download mode needs a physical power cycle (unplug USB). esptool's own reset leaves the ROM downloader latched. |
After a successful flash the app never starts until you unplug/replug, so "the flash worked but nothing runs". |
| 6 |
Charge with the side switch ON (M5 documentation); OFF is the download-mode position. |
Board does not charge / behaves inconsistently when powered from USB. |
Corollary of rules 2+3+5: if the running app does not own the USB peripheral
(because it never called Serial.begin()), then esptool's auto-reset works again and
scripts/softboot.py can restart the app without a physical replug.
That is why rule 2 pays for itself.
Hardware quick reference
Display ST7789V2 240x135 RST=G33 DC=G34 MOSI=G35 SCK=G36 CS=G37 BL=G38 (PWM)
Audio ES8311 (I2C addr 0x18, shared bus G8/G9)
I2S: BCK=G41 WS/LRCK=G43 DATA out=G42 DATA in (mic)=G46
Keyboard TCA8418RTWR on the same I2C bus (4x14 = 56 keys), INT = G11
IMU BMI270 (I2C, ADV only - absent on the original Cardputer)
IR emitter = G44
Battery ADC = G10
microSD CS=G12 MOSI=G14 CLK=G40 MISO=G39 (SPI bus shared with the EXT port)
Grove HY2.0-4P: G1 / G2 (+5V, GND) EXT 14P: RESET=G3 INT=G4 BUSY=G6 UART_TX=G13 UART_RX=G15
RGB LED data = G21, and G38 must be driven HIGH first (it also drives the backlight)
Boot G0 = download-select button; side switch OFF = download position
Full official pin table, EXT/Grove bus, and Cardputer vs Cardputer-Adv differences:
references/hardware.md.
Verified official documentation URLs, the exact official procedures (download mode, charging,
RGB LED rail), and how to read the SPA-hosted docs with a plain fetch:
references/official-docs.md.
Toolchain recipe (PlatformIO, working baseline)
[env:cardputer-adv]
platform = espressif32@6.7.0
board = esp32-s3-devkitc-1
framework = arduino
board_build.flash_size = 8MB
board_build.flash_mode = dio ; <- rule 1
board_build.partitions = default_8MB.csv
board_upload.flash_size = 8MB
upload_speed = 460800 ; 1500000 fails on this USB bridge
build_flags = -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 -DCORE_DEBUG_LEVEL=1 -Wall
lib_deps =
https://github.com/m5stack/M5Unified.git
https://github.com/m5stack/M5GFX.git
https://github.com/m5stack/M5Cardputer.git
Pin the three libraries by commit when reproducibility matters. Details and the
flash/boot procedures: references/toolchain.md.
Library-level notes worth knowing up front
M5Unified knows this board: board_t::board_M5CardputerADV is auto-detected by M5GFX
via GPIO/I2C probing. Never hard-code pins for the panel, keyboard or codec.
M5Cardputer is the convenience wrapper: M5Cardputer.begin(cfg, true) = M5.begin() +
Keyboard.begin(); M5Cardputer.update() pumps both. It exposes Display, Speaker,
Mic, Keyboard, BtnA.
- The default tone waveform is a 16-point sine, which a 1 W speaker cannot radiate below
roughly 300 Hz — low notes are effectively silent. Pass a sawtooth table to
Speaker.tone(freq, dur, ch, stop, raw, len) instead.
Speaker_Class gives 8 virtual channels; stop_current_sound=true stops only the target
channel, so per-note voice allocation gives clean polyphony.
API details, keyboard KeysState fields and the waveform tables:
references/m5-library-notes.md.
Reusable resources
- scripts/flash.sh — flash a PlatformIO build with esptool 5.x (correct offsets).
- scripts/softboot.py — restart the app over DTR/RTS instead of replugging.
- scripts/serial_capture.py — one-shot capture that documents the
reset-on-open trap instead of walking into it.
- references/official-docs.md — verified M5Stack doc URLs, the
PDF fallback for the JavaScript docs site, verbatim official procedures, and the two places
where M5Stack's own published recipe differs from a configuration that actually boots.
- examples/midi-keyboard/ — a complete, hardware-verified app:
8-voice polyphonic synth, TCA8418 keyboard mapping, chord recognition, custom waveforms,
LED stage reporting, and an M5GFX UI. Use it as the reference implementation.
1---2name: cardputer-adv3description: Build, flash, and debug firmware for the M5Stack Cardputer-Adv (K132-Adv, Stamp-S3A / ESP32-S3FN8) and the original Cardputer on macOS or Linux with PlatformIO + Arduino + M5Unified/M5Cardputer. Use when a task involves Cardputer hardware or firmware - scaffolding a PlatformIO project for it, fixing a boot loop / black screen / reboot cycle after flashing, choosing DIO vs QIO flash mode, the ESP32-S3 native USB-Serial-JTAG traps (esptool version, entering download mode, a serial port open resetting the chip), driving the 1.14" ST7789 display, the TCA8418 I2C keyboard, the ES8311 audio codec, or the RGB LED and backlight pins.4---56# M5Stack Cardputer-Adv firmware development78Cardputer-Adv (SKU K132-Adv) = **Stamp-S3A = ESP32-S3FN8**: dual-core LX7 @240 MHz,9**8 MB flash, no PSRAM**, ESP32-S3 **native USB** (USB-Serial/JTAG, VID:PID `303a:1001`),101.14" **ST7789** 240×135, **TCA8418** I2C keyboard controller, **ES8311** audio codec.1112Everything in this skill was verified on real hardware (2026-09, macOS 15, PlatformIO 6.2,13Arduino core 2.0.16 / espressif32@6.7.0). Timings, pins and failure modes are measured, not copied.1415## Follow this workflow16171. Confirm the board enumerates: `ls /dev/cu.usbmodem*` and `ioreg -p IOUSB -w0 | grep -i jtag`.182. Scaffold the PlatformIO project from [references/toolchain.md](references/toolchain.md).19 The `dio` flash mode line is **not optional** — see rule 1.203. Let **M5Unified auto-detect the board**. Do not hand-initialise the panel, keyboard or21 codec; `M5Cardputer.begin()` already does all of it for `board_M5CardputerADV`.224. Do **not** call `Serial.begin()` in the default build (rule 2). Report progress with the23 **RGB LED** instead — it is the only status channel that survives a hang.245. Build, then flash with [scripts/flash.sh](scripts/flash.sh) (esptool 5.x, not PlatformIO's).256. Physically power-cycle the board (unplug/replug USB) and read the LED stage before26 concluding anything about your code.277. Only then debug functionally, with [references/debugging.md](references/debugging.md)28 as the symptom → cause table.2930## Non-negotiable rules3132Each of these cost a full debugging session; they look like "my code is broken" but are not.3334| # | Rule | What it looks like when violated |35|---|------|----------------------------------|36| 1 | `board_build.flash_mode = dio`. **Never `qio`**, even though the stock `esp32-s3-devkitc-1` board definition defaults to it - and note that M5Stack's own PlatformIO snippet in the product PDF omits this line, so the official recipe inherits `qio`. | Endless reboot, black screen. The ROM prints `boot:0x3 (DOWNLOAD(USB/UART0))` — the app never runs at all. With `dio` the same image prints `boot:0xb (SPI_FAST_FLASH_BOOT)` and boots. |37| 2 | Do **not** call `Serial.begin()` unless a host is actively draining the port. With `ARDUINO_USB_CDC_ON_BOOT=1` the CDC write path **blocks once the TX buffer fills**, hanging `setup()` before any M5 init. | App hangs with the RGB LED stuck on the first colour you set. Guard serial output behind a `MIDI_SERIAL_DEBUG`-style macro that defaults to 0. |38| 3 | Do **not** open `/dev/cu.usbmodem*` from a host script while the app is supposed to keep running. Opening the port toggles DTR/RTS, and the ESP32-S3 interprets that as a **reset pulse**. | A perfect fake crash loop: every read attempt yields a ROM banner. This misleads diagnosis badly — see [references/debugging.md](references/debugging.md). |39| 4 | Flash with **esptool 5.x**. PlatformIO's bundled esptool 4.5.1 **cannot sync** with this board. | `A fatal error occurred: No serial data received` while 5.x talks to the same port fine. |40| 5 | Entering download mode needs a **physical** power cycle (unplug USB). esptool's own reset leaves the ROM downloader latched. | After a successful flash the app never starts until you unplug/replug, so "the flash worked but nothing runs". |41| 6 | Charge with the side switch **ON** (M5 documentation); `OFF` is the download-mode position. | Board does not charge / behaves inconsistently when powered from USB. |4243**Corollary of rules 2+3+5:** if the running app does **not** own the USB peripheral44(because it never called `Serial.begin()`), then esptool's auto-reset works again and45[scripts/softboot.py](scripts/softboot.py) can restart the app without a physical replug.46That is why rule 2 pays for itself.4748## Hardware quick reference4950```51Display ST7789V2 240x135 RST=G33 DC=G34 MOSI=G35 SCK=G36 CS=G37 BL=G38 (PWM)52Audio ES8311 (I2C addr 0x18, shared bus G8/G9)53 I2S: BCK=G41 WS/LRCK=G43 DATA out=G42 DATA in (mic)=G4654Keyboard TCA8418RTWR on the same I2C bus (4x14 = 56 keys), INT = G1155IMU BMI270 (I2C, ADV only - absent on the original Cardputer)56IR emitter = G4457Battery ADC = G1058microSD CS=G12 MOSI=G14 CLK=G40 MISO=G39 (SPI bus shared with the EXT port)59Grove HY2.0-4P: G1 / G2 (+5V, GND) EXT 14P: RESET=G3 INT=G4 BUSY=G6 UART_TX=G13 UART_RX=G1560RGB LED data = G21, and G38 must be driven HIGH first (it also drives the backlight)61Boot G0 = download-select button; side switch OFF = download position62```6364Full official pin table, EXT/Grove bus, and Cardputer vs Cardputer-Adv differences:65[references/hardware.md](references/hardware.md).66Verified official documentation URLs, the exact official procedures (download mode, charging,67RGB LED rail), and how to read the SPA-hosted docs with a plain fetch:68[references/official-docs.md](references/official-docs.md).6970## Toolchain recipe (PlatformIO, working baseline)7172```ini73[env:cardputer-adv]74platform = espressif32@6.7.075board = esp32-s3-devkitc-176framework = arduino77board_build.flash_size = 8MB78board_build.flash_mode = dio ; <- rule 179board_build.partitions = default_8MB.csv80board_upload.flash_size = 8MB81upload_speed = 460800 ; 1500000 fails on this USB bridge82build_flags = -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 -DCORE_DEBUG_LEVEL=1 -Wall83lib_deps =84 https://github.com/m5stack/M5Unified.git85 https://github.com/m5stack/M5GFX.git86 https://github.com/m5stack/M5Cardputer.git87```8889Pin the three libraries by commit when reproducibility matters. Details and the90flash/boot procedures: [references/toolchain.md](references/toolchain.md).9192## Library-level notes worth knowing up front9394- **`M5Unified` knows this board**: `board_t::board_M5CardputerADV` is auto-detected by M5GFX95 via GPIO/I2C probing. Never hard-code pins for the panel, keyboard or codec.96- **`M5Cardputer` is the convenience wrapper**: `M5Cardputer.begin(cfg, true)` = `M5.begin()` +97 `Keyboard.begin()`; `M5Cardputer.update()` pumps both. It exposes `Display`, `Speaker`,98 `Mic`, `Keyboard`, `BtnA`.99- **The default tone waveform is a 16-point sine**, which a 1 W speaker cannot radiate below100 roughly 300 Hz — low notes are effectively silent. Pass a **sawtooth** table to101 `Speaker.tone(freq, dur, ch, stop, raw, len)` instead.102- `Speaker_Class` gives 8 virtual channels; `stop_current_sound=true` stops **only the target103 channel**, so per-note voice allocation gives clean polyphony.104105API details, keyboard `KeysState` fields and the waveform tables:106[references/m5-library-notes.md](references/m5-library-notes.md).107108## Reusable resources109110- [scripts/flash.sh](scripts/flash.sh) — flash a PlatformIO build with esptool 5.x (correct offsets).111- [scripts/softboot.py](scripts/softboot.py) — restart the app over DTR/RTS instead of replugging.112- [scripts/serial_capture.py](scripts/serial_capture.py) — one-shot capture that documents the113 reset-on-open trap instead of walking into it.114- [references/official-docs.md](references/official-docs.md) — verified M5Stack doc URLs, the115 PDF fallback for the JavaScript docs site, verbatim official procedures, and the two places116 where M5Stack's own published recipe differs from a configuration that actually boots.117- [examples/midi-keyboard/](examples/midi-keyboard/) — a complete, hardware-verified app:118 8-voice polyphonic synth, TCA8418 keyboard mapping, chord recognition, custom waveforms,119 LED stage reporting, and an M5GFX UI. Use it as the reference implementation.