Hardware companion
The Lily build
Course 4's reaction–diffusion sim, running for real on a LilyGO T-QT Pro — a thumbnail-sized ESP32-S3 with a 0.85″ 128×128 color screen. Tune a pattern in the playground, export it, flash it, and it lives in your pocket. The two side buttons cycle presets and reseed.
Browser to board
The website and the firmware run the identical kernel — same 9-point Laplacian, same explicit Euler step — so a preset behaves the same in both. This is the whole pipeline:
Hardware
The display is driven with TFT_eSPI's GC9A01 driver (it speaks to the GC9107 correctly). All of this is
configured in platformio.ini build flags — no library files are edited. Pin map verified
against Bodmer's Setup211_LilyGo_T_QT_Pro_S3.h.
| Signal | GPIO |
|---|---|
| SPI MOSI | 2 |
| SPI SCLK | 3 |
| LCD CS | 5 |
| LCD DC | 6 |
| LCD RST | 1 |
| Backlight | 10 |
| Button — next preset | 0 (BOOT) |
| Button — reseed | 47 |
SPI runs at 40 MHz. MISO is unused (−1). Both buttons are active-low to ground.
Flash it
The cable question
The T-QT charges and flashes over its USB-C port — you need a USB-C data cable:
- Works the white USB-C cable from a recent MacBook charger or an iPhone 15/16 (USB 2.0 data is plenty).
- Catch a charge-only cable: the screen may light up but no serial port appears. If
pio device listshows nothing, swap cables. - No an old iPhone Lightning cable won't fit; if your Mac is USB-A only, use a USB-A→USB-C data cable.
No driver needed on macOS — the ESP32-S3 uses native USB and shows up as a standard usbmodem port.
Test on your Mac first
Before flashing, run the pattern in a desktop window — it executes the
exact same kernel the firmware does (shared gs_kernel.h), so what you see is
byte-for-byte what the chip renders. No board, no chip emulation — the simulation is just portable math.
brew install sdl2 # one-time make -C sim run # opens the simulator window
Keys mirror the hardware buttons: N / → next preset · R reseed · Space pause · Q quit. This is how the 64→128 extinction bug got caught before a single flash.
Quick start
cd ~/Lily pio device list # confirm the board shows up pio run -t upload # build + flash pio device monitor # watch the log (Ctrl+C to quit)
# comment after it — zsh passes the
#… as arguments and PlatformIO errors with “Got unexpected extra arguments.” Type just the command.
If upload can't connect
Put the board into download mode by hand, then upload again:
- Hold BOOT (the button nearest the USB-C port).
- Tap RESET (the other button) while still holding BOOT.
- Release BOOT.
- Run
pio run -t uploadagain. - Tap RESET once when it finishes, to run the firmware.
Command reference
Build & flash
pio run | Compile only (no flash) |
|---|---|
pio run -t upload | Compile + flash the board |
pio run -t upload -v | Verbose — shows the exact esptool calls |
pio run -t clean | Delete build artifacts (force clean rebuild) |
pio run -t upload --upload-port /dev/cu.usbmodem101 | Flash to a specific port |
pio run -t erase | Erase the entire flash (then re-upload) |
Connect & monitor
pio device list | List serial ports — the board is a /dev/cu.usbmodem… |
|---|---|
pio device monitor | Open the serial log at 115200 baud |
pio device monitor -p /dev/cu.usbmodem101 -b 115200 | Monitor a specific port / baud |
Ctrl+C | Exit the monitor |
The monitor auto-decodes crash backtraces into file:line
(monitor_filters = esp32_exception_decoder in platformio.ini), so panics point at real source.
Troubleshooting
Flashing & connection
| Symptom | Fix |
|---|---|
No board in pio device list | Charge-only cable → swap it. Go straight into the Mac, not a hub. Tap RESET and retry. |
| “Failed to connect to ESP32-S3” | Enter download mode: hold BOOT, tap RESET, release BOOT, upload again. |
| Port name changed after flashing | Normal on the S3 — bootloader and firmware ports differ. Re-run pio device list. |
| Boot loop / keeps resetting | Open the monitor; a decoded panic prints the crash location. “Brownout” usually means a weak cable/port. |
pio: command not found | export PATH="/opt/homebrew/bin:$PATH" |
Display
The display config is the one part that couldn't be tested without the board. Each fix is a one-line change
in platformio.ini, then pio run -t upload again.
Snap a photo and send it — every one of these is quick once it's visible.
| Symptom | Fix |
|---|---|
| Blank / black, no glow | Backlight or init issue — likely TFT_BL or driver. Ask. |
| Photo-negative (dark↔bright) | Change -D TFT_INVERSION_ON=1 → =0 |
| Red and blue swapped | Add -D TFT_RGB_ORDER=TFT_BGR |
| Image shifted / wrapped a few px | GC9107 needs a small offset — note which way it's shifted. |
| Mirrored / rotated wrong | Change tft.setRotation(0) in src/main.cpp (try 1–3). |
| All black after a few seconds | Pattern went extinct — tap BOOT for next preset, or IO47 to reseed. |
Serial & crashes
On boot you should see, in pio device monitor:
T-QT Pro — Gray-Scott reaction-diffusion preset 0: Lily F=0.0175 k=0.0435 Dv/Du=0.45 steps=12
Nothing prints → wrong port, or the board booted before the monitor connected (tap RESET with it open).
Gibberish → wrong baud (should be 115200). A backtrace → it's decoded to file:line; paste the whole thing over.
Baked-in presets
Three presets ship in the firmware; Lily is the boot default. The BOOT button cycles
through them. Edit include/lily_presets.h and reflash — the format matches the playground's
Copy C header export, so you can paste a fresh set straight in.
Customizing
- Presets:
include/lily_presets.h—name,F,k,dvdu,palette(0 Ocean · 1 Ember · 2 Orchid · 3 Mono),steps. Preset 0 boots. - Resolution:
gs::W/gs::Hininclude/gs_kernel.h(128×128, one cell per pixel — shared by firmware and simulator). - Orientation:
tft.setRotation(0). - Brightness: backlight is full-on; ask for PWM dimming.