A press-any-key, always-makes-a-sound English-alphabet learning toy for a 3-year-old, running on the M5Stack Cardputer-ADV. Every letter key plays a short phonics song, the screen writes the letter stroke-by-stroke and animates the word, and the number keys turn it into a tiny music player. All content lives in swappable content packs — change the voice, accent, or language without touching the firmware.
📖 中文文档 → README.zh-CN.md
⬇️ Get it running: download the firmware
.bin+ ready-to-use SD pack from Releases, or flash via M5Burner (search Cardputer ABC). The SD pack goes on a FAT32 card — flashing the firmware alone isn't enough.
It started as a music player. I loaded this little M5Cardputer with custom kids' songs I'd made for my toddler, and he was hooked — he carried it around for days telling everyone "this is the little computer daddy gave me." We have other small-screen gadgets he likes, but the physical keyboard is what made him certain this one is a real computer. So I asked myself: how do I make the most of that keyboard for a 3-year-old? Everything below is the answer. (He's now learning his letters from it — and mashing E far more than any other key, for reasons no one understands.)
A single-file Arduino firmware for the M5Stack Cardputer-ADV that turns its 56-key keyboard into a toddler-proof learning toy. The design rule: every key makes a sound — a 3-year-old mashing keys must never hit silence.
Three ways to play share one device:
- ABC mode (letter keys A–Z) — press a letter → it plays that letter's short phonics song (letter name ×2 + phonics ×3 + words), while the screen:
- writes the letter stroke by stroke (uppercase then lowercase, Hershey vector font),
- then alternates the big letter ↔ word pictures, beat-synced to the song's loudness,
- plays a per-word motion animation, then an effect animation on the 3-beat action words (bite ×3, siren ×3, …),
- ends with an ~8s group photo → slow rainbow standby screen + a peeking standby face.
- Any new letter key interrupts instantly (the core experience for a key-mashing toddler).
- Two play styles, switchable in settings: Full (play everything) or Step (one segment per press — same key advances name → phonics → words).
- Game mode (v1.2) — the device asks for a letter (big glyph + spoken name), the child finds the key. Correct → congrats sound + streak counter; wrong → the pressed key's name is spoken and a left/right hint appears (zero-penalty — a miss becomes a mini-lesson). Three visual skins (mole / shoot / drive), switch live with
◀ ▶. Re-prompts once after 4 s, then goes quiet. Enable via Settings → mode → Game. Works best with an atom pack (e.g.us-teach). - Music-player mode (number keys 0–9) — each digit plays a folder
/audio/<n>/of your own audio (mp3 / wav / m4a / aac / flac / ogg / opus). MP3 is decoded on-device with libhelix. Shows the embedded cover art (ID3 APIC, or acover.jpgfallback) + title (ID3 tag or filename) + a switchable FFT visualizer (spectrum / radial spectrum / breathing orb / none).◀ ▶skip tracks.
Non-letter keys give a quick "ding" on a separate audio channel — feedback without interrupting playback.
- No PSRAM. The Cardputer-ADV is a Stamp-S3A (ESP32-S3FN8, 8 MB flash, no PSRAM). Audio is streamed from SD, not loaded whole into memory.
- Content packs are first-class. A "version" = a directory
/packs/<name>/. Different voice / accent / language is just a different folder. Switch in the on-device settings menu or by editing one line in/packs/active.txt— the firmware needs no code changes to add or swap content. - Animations are pre-rendered flip frames. The hard pixel work is done on a computer (Pillow); the device just flips through 240×135 JPGs.
- USB drive mode (MSC). Expose the SD card over USB to drag files without pulling the card (see below) — no microSD card reader needed.
- Night mode (eye-care / bedtime). The screen stays off and any key just plays audio; re-open the settings menu to switch back.
- English / Chinese UI. Switch the whole settings interface language on-device (CJK font built in).
- FFT music visualizers. A self-contained 256-point FFT drives a real spectrum, a radial spectrum, or a breathing orb.
- Power management (v1.2). State-aware screen-off (3 min while playing — audio keeps going; 30 s idle), deep sleep after 15 min (µA-level; wake with the power button), a battery icon with ⚡ while charging, and low-battery protection (banner below ~10%, auto-shutdown below ~3% to prevent deep discharge).
- Usage log for parents (v1.2). Every keypress + game attempt is appended as JSONL to
/cardputer-abc/log.jsonl; optional on-device WiFi + NTP give real timestamps.
| Board | M5Stack Cardputer-ADV (Stamp-S3A: ESP32-S3FN8, 8 MB flash, no PSRAM) |
| Audio | ES8311 codec + NS4150B amp + 1 W speaker |
| Display | 1.14" 240×135 ST7789V2 |
| Input | 56-key keyboard (TCA8418 matrix controller) |
| Storage | microSD (FAT32) |
The original (non-ADV) Cardputer shares the same SoC and SD pinout, so it may run unmodified, but it's untested here. ADV is the verified target.
| Key | Action |
|---|---|
| Letter A–Z | Play that letter (interruptible by any new letter) |
| Space / Enter | Pause / resume |
= / - or [ / ] |
Volume up / down |
, (◀) / / (▶) |
Previous / next letter — next track in player mode — switch skin in game mode |
Number 0–9 |
Play music folder /audio/<n>/ |
| Fn + Backspace (or Fn + ?) | Open settings menu (two opposite-corner keys — a toddler can't hit both one-handed) |
| Fn + V | Quick-cycle the player visualizer |
| Any other key | "ding" |
Settings menu (navigate with ;▲ .▼; Enter selects/activates; Backspace exits; everything persists to NVS): language (EN/中文) · content pack · art style · motion fx · mode (Full / Step / Game) · night mode · player visualizer · screen-off timeout · WiFi · time sync · USB drive mode · SD info · restore defaults · battery %. List choices open a popup picker; toggles open an explainer dialog.
Arduino IDE or arduino-cli, board package m5stack:esp32, library M5Cardputer ≥ 1.1.1 (pulls in M5Unified / M5GFX).
# USBMode=default enables USB-drive (MSC) mode; UploadMode=cdc does a 1200bps-touch
# reset under TinyUSB so you don't have to press BOOT.
# PartitionScheme=huge_app is REQUIRED since v1.2 (the WiFi stack doesn't fit the default 1.2MB app partition).
arduino-cli compile \
--fqbn m5stack:esp32:m5stack_cardputer:PSRAM=disabled,PartitionScheme=huge_app,USBMode=default,UploadMode=cdc \
firmware/cardputer_abc
arduino-cli upload -p <PORT> \
--fqbn m5stack:esp32:m5stack_cardputer:PSRAM=disabled,PartitionScheme=huge_app,USBMode=default,UploadMode=cdc \
firmware/cardputer_abcThe board has no PSRAM, so leave PSRAM disabled. No toolchain? Flash via M5Burner instead.
Zero-dependency contract checks (no pytest, no ffprobe). Run before flashing:
python3 tests/run_all.py # fast checks, <0.2s
python3 tests/run_all.py --compile # + compile smoke (~13s)
python3 tests/run_all.py --sd /Volumes/XXX/packs # + validate a real SD cardThey guard the implicit couplings that neither the compiler nor the device reports at the
time you break them — firmware tables vs. the word list, settings-row indices, the Arduino
auto-prototype ordering trap, and SD pack structure/audio format. See tests/README.md.
The card is plain FAT32. Two ways to copy files onto it:
- Pull the microSD and copy on your computer — the obvious way.
- USB drive mode (no card removal) — plug the device into your computer over USB, open the settings menu (Fn + Backspace), select USB drive, and confirm. The device reboots and the SD card shows up as a USB mass-storage drive on your computer. Drag songs/packs over, then reboot the device to return to normal. (This relies on the firmware being flashed with
USBMode=default, which the FQBN above already sets.)
Card layout:
SD root (FAT32)
├── packs/
│ ├── active.txt # one line: the active pack's folder name
│ └── us-kid/ # a content pack
│ ├── A/ … Z/ # per-letter: 10-full.wav + flip frames + images
│ ├── ding.wav
│ └── pack.txt # optional: human-readable name; line 2 "fps=N" overrides flip fps
└── audio/ # music-player content (your own files)
├── 1/ twinkle.mp3
└── 2/ count.mp3 cover.jpg
On first boot the firmware auto-creates an /audio scaffold if it's missing. The music player works with any audio you drop in. For ABC mode, download a ready-to-use content pack from the Releases page and extract it to the SD root — or bring your own.
firmware/cardputer_abc/ Single-file Arduino firmware (.ino) + bundled libhelix-mp3 + Hershey font
docs/ Design + decision log, player-mode spec, display/eye-care notes
tests/ Zero-dep contract checks (firmware invariants + SD pack structure)
Key docs: docs/design.md (decision log), docs/digit-audio-player.md (player mode), docs/display-flicker-and-eye-care.md (filming the screen / eye-care).
ABC content packs (songs + animation frames + art) are distributed separately as a downloadable resource on the Releases page — they're not part of this firmware repo, which is kept firmware-only. The content-generation pipeline lives outside this repo.
Apache-2.0 — see LICENSE and NOTICE. Apache-2.0 requires anyone redistributing a modified version to state which files they changed and to keep the attribution notices. The bundled libhelix-mp3 is RealNetworks' code under the RPSL/RCSL (not covered by Apache-2.0) — see its headers. Downloadable content packs bundle OpenMoji art (CC BY-SA 4.0) and Suno-generated songs (Suno's terms) — see each pack's own CREDITS.