kid-arcade · part 2

The 7-Inch Upgrade: Same Kid's Toy, Serious Hardware

Part 1 was the $25 toy: one chip, one file, one screen. It is still the one to build first. But once it had earned its place on car rides, I wanted a couch version with a bigger screen, nicer type and room for the visuals to breathe. That turned into a port to genuinely serious hardware, and the port taught me more about modern ESP32 development than the original build did.

This is the full technical story: the architecture, the gotchas that each cost an evening, and the workflow that let one person do a two-chip embedded port in spare hours.

Both versions live in the same repo, gsandi/esp32-kid-learning-arcade. The 7-inch port is on the feat/esp32-p4 branch, where the partition table, flash.sh and the SDMMC workaround below all live. Fair warning: the repo’s step-by-step README covers the 4-inch build, not this one. The P4 code is there and it works, but you are reading source rather than following a guide.

The port started with exactly one sentence typed into a terminal, preserved verbatim in the project’s prompt log: “i have a new EP4 panel connected can you see if u can connect?” No plan, no spec. The board had arrived that day. Everything below unfolded from there.

The hardware

Elecrow CrowPanel Advanced 7” ESP32-P4 HMI. This is not a $25 board, it is closer to $60 or $70, but the spec sheet reads like a small tablet:

Component Spec
MCU ESP32-P4 (RISC-V, up to 360 MHz), 16 MB flash, 32 MB PSRAM
Display 7” IPS, 1024×600, MIPI DSI (2 lanes), the interface real tablets use, not SPI
Touch GT911 capacitive over I2C
Wireless None on the P4 itself. An onboard ESP32-C6 co-processor provides Wi-Fi/BT over SDIO
Audio I2S amplifier + speaker
Storage SD card slot (SDMMC)
Backlight PWM-dimmable

Two things on that table change your life as a builder. Capacitive touch means the per-panel calibration ritual from part 1 simply vanishes, because the panel knows where your finger is. And MIPI DSI means the display pipeline is a real one: high resolution, DMA-fed, no SPI bottleneck.

One thing complicates it: the P4 has no radio. Wireless lives on a separate ESP32-C6 chip running its own firmware. You are now maintaining a two-chip system whether you like it or not.

Day-one gotcha before any code. The board has two USB ports, one of which is the UART/flash port and one of which is not, and nothing on the silkscreen makes that obvious. If the board will not take firmware, you are probably in the wrong port. The vendor’s documentation ZIP (Elecrow publishes it per-product) is worth downloading before the board arrives. Feeding those PDFs to the AI pair up front is what turned pinout archaeology into lookups.

The architecture shift

The 4-inch build was Arduino-flavored: PlatformIO plus TFT_eSPI, hand-drawn pixels. None of that carries over. The P4 build is native ESP-IDF (Espressif’s own framework) with LVGL 9 for UI:

Layer v1 (4-inch) v2 (7-inch)
Framework Arduino-style / PlatformIO ESP-IDF 6 (still driven via PlatformIO)
Display TFT_eSPI over SPI esp_lcd MIPI DSI + EK79007 panel driver
Touch XPT2046 raw reads esp_lcd_touch_gt911 component
UI Hand-drawn pixels LVGL 9 via esp_lvgl_port
Wireless none ESP-Hosted + esp_wifi_remote over SDIO to the C6

In part 1 I argued against LVGL. Here I reversed, and both calls were right for their board. At 320×480 with 520 KB of RAM a UI framework is overhead. At 1024×600 with 32 MB of PSRAM and animated screen transitions, hand-drawing pixels is masochism. A framework starts earning its cost somewhere around that pixel count.

The panel mounts portrait: physically 1024×600, rotated in software to 600×1024. LVGL 9 handles the rotation, and your layout code just thinks in portrait.

What made the port cheap: the game logic, meaning question generation, round structure and the star economy, is plain C++ that never knew what a display was. Only the draw layer changed. I made that separation almost by accident in v1, and it is the design decision from this project that has transferred furthest.

Living with two chips

The C6 co-processor needs its own 1.2 MB firmware blob. My solution was to bake it into the P4’s flash as a dedicated partition, and have the P4 auto-flash the C6 over SDIO at boot if needed:

nvs,      data, nvs,   0x9000,   0x6000
phy_init, data, phy,   0xF000,   0x1000
slave_fw, data, 0xff,  0x10000,  0x13E000   ← C6 firmware blob lives here
otadata,  data, ota,   0x14E000, 0x2000
app0,     app,  ota_0, 0x150000, 0x400000   ← running app
app1,     app,  ota_1, 0x550000, 0x400000   ← OTA staging slot

A single flash.sh builds and flashes both chips in one shot. Gotcha number one lived here: the C6 auto-flash logic must skip when the slave partition is empty or first boot wedges, and you want a --force flag for when the C6 firmware genuinely changed.

The joke writes itself. This toy is proudly offline, and I shipped a second chip’s entire networking stack to support it. The Wi-Fi does earn its keep, in clock sync, a weather widget on the home screen, and the OTA updates below.

The three gotchas, so you don’t pay for them

1. The PSRAM wallpaper trick. The home screen is a full-screen photo, 1.2 MB of RGB565 pixels compiled into flash. Rendering it straight from flash caused visible jitter on every redraw, because LVGL kept re-reading flash through the XIP cache and evicting hot code. The fix is to copy the image from flash into PSRAM once at boot and point LVGL at the PSRAM copy forever. Jitter gone. If your embedded UI stutters only when a background image is visible, this is why.

2. Dual SDMMC. The C6 radio link uses SDMMC slot 1 and the SD card uses slot 0. Different pins, same host peripheral, and ESP-Hosted has already initialized that host by the time you mount the card. On ESP-IDF 6.x the SD mount needs a dummy host init/deinit dance to coexist. The symptom otherwise is that the SD mount fails only when Wi-Fi is enabled, which sends you off debugging entirely the wrong subsystem.

3. Sound without an audio pipeline. No sound files and no codec. Each feedback tone is a sine wave generated as PCM at runtime and pushed to I2S: a correct answer is a rising E5 to A5, wrong is a soft low G3, round-complete is a little arpeggio. Volume is amplitude scaling, wired to a settings slider. The one rule is that every audio_play() spawns a detached FreeRTOS task, so the UI thread never blocks on a speaker.

The features a big screen unlocked

  • OTA updates. Admin panel, enter the laptop’s IP, and the device pulls new firmware over HTTP in 4 KB chunks into the spare app partition and reboots into it. After this existed the USB cable stopped leaving the drawer, because a firmware tweak while the kid is using the toy takes two minutes.

  • SD-card question bank. The device reads /sdcard/questions.csv at boot and appends to the built-in questions, never replaces them. A missing or bad card costs you nothing and the toy always works. One CSV line per question:

    ADD,7,8,15,14,16,0        # 7+8: options 15/14/16, first is correct
    SKIP,6,6,12,18,24,30,36,0 # skip-count by 6
    MULT,7,8,56,48,63,0       # 7×8

    New school topic means editing a text file on a card. No recompile, no laptop.

  • The quiet stuff that makes it feel finished: an NTP clock with a time-of-day greeting, a screensaver after 2 minutes idle (black screen, small white clock, effectively an always-on display), brightness and volume sliders persisting to NVS, and the same 5-questions-stars-only game rules as v1. The product philosophy did not change with the hardware. Only the canvas did.

The workflow that made this possible solo

This port is two chips, five subsystems, and a display interface I had never touched, done in evening sessions. The honest enabler was AI tooling used as a direct pair-programmer:

  • Claude Code in the terminal did the heavy lifting: MIPI DSI panel bring-up, the LVGL port, the SDMMC coexistence fix, the OTA implementation. The rhythm was tight loops, where the agent writes and builds and I flash and report what the screen actually shows. ESP-IDF errors are verbose and ancient-forum-flavored, and an agent that has effectively read every forum thread turns “cryptic linker error” into “your partition table is 4 KB short” in seconds.
  • A second AI tool as reviewer, not builder. I periodically ran the diff through a different model (Codex, and lately Google’s Antigravity with its own models) for a cold read. Different models miss different things, and a reviewer with no memory of writing the code is usefully ruthless. Cheap insurance on firmware that a kid will mash on.
  • Notes as the source of truth. Every gotcha above went into a project note the moment it was solved: pinouts, the partition table, why the dummy SDMMC init exists. Both the AI sessions and future-me read those notes before touching code. On a months-long spare-time project the constraint is not typing speed, it is re-loading context. Write it down once and every later session, human or AI, starts warm.

The division of labor stayed the same as part 1. The tools wrote most of the code, and every product decision and every minute of holding-the-device testing was mine. That balance is the part I would tell anyone to copy.

Which one should you build?

Build the $25 one from part 1 first, because the value per dollar is absurd and the whole thing is an afternoon. Build this one when you have caught the bug, want a couch device, and are ready for a two-chip system with a real display pipeline.

Either way the code is at gsandi/esp32-kid-learning-arcade: main for the 4-inch build, which has a full step-by-step setup guide, and feat/esp32-p4 for this one, which does not have one yet. The board is the Elecrow CrowPanel Advanced 7” ESP32-P4 HMI, and the vendor’s documentation ZIP is worth grabbing at the same time.

Both are in his hands now. He does not know one cost three times the other. He knows the big one has sound and his knight wallpaper on it, and that both of them are his, in a way no tablet with someone else’s business model ever would be.