FLUX — time becomes light
Custom firmware for the M5Stack StopWatch (ESP32-S3, 466x466 round AMOLED, BMI270 IMU, CST820 touch, haptics, speaker/mic). Built on the factory UserDemo HAL, with the entire app layer replaced by a direct-render neon particle engine.
| App | What it does |
|---|---|
| FLUX TIME | The flagship watch face. The time itself is made of ~1300 glowing particles. Tilt the watch and time sags like liquid. Shake it and time shatters, bounces around the round rim, then flows back together. Touch parts the particles around your finger. Minute changes melt into the next time. A = cycle 4 color themes (plasma / ember / aurora / sakura), B = manual burst. |
| CHRONO | A stopwatch where a golden comet orbits the bezel once per minute, leaving a glowing trail. Laps freeze cyan markers on the orbit. Tap or A = start/stop, B = lap (running) / reset (stopped). Haptic feedback on everything. 10ms precision. |
| MU | A little spirit that lives in the watch. Its eyes roll with gravity (IMU), it watches your finger, gets dizzy when shaken, chirps when poked, purrs (haptics) when petted, and falls asleep if ignored. |
| SPECTRUM | Factory mic FFT visualizer. |
| ALARM | Factory alarm clock. |
| SETUP | Brightness / volume / buttons / time / about. |
Navigation: swipe or tap the arrows in the launcher, tap an icon to open. Hold A+B together to go home from any app.
- Renders straight into the AMOLED panel framebuffer (PSRAM row pointers
exposed via
Panel_FrameBufferBase::getLinesBuffer()), one QSPI flush per frame — no LVGL, no full-sprite copy. - Additive-blended integer glow stamps (radius 1..56, lazily built).
- Dirty-rect erasing instead of full clears (only regions drawn last frame are cleared).
- IMU polled in a background task on core 1; render loop never blocks on i2c.
- Steady 33 fps at full 466x466.
ESP-IDF v5.5.4:
python3 ./fetch_repos.py # fetch components (applies patches/M5GFX.patch etc.)
idf.py build
idf.py -p /dev/ttyACM0 flashThe firmware listens on the USB-Serial-JTAG console for single-byte
commands (see main/hal/hal_debug.cpp):
| Cmd | Effect |
|---|---|
P |
Dump a real screenshot of the panel framebuffer as base64 |
C / L / R |
Inject tap: center / left arrow / right arrow |
@x,y,ms\n |
Inject tap at x,y for ms |
a / b |
Inject button A / B click |
Q |
Go home |
D YYYY-MM-DD HH:MM:SS\n |
Set RTC |
Host helpers:
python3 tools/screenshot.py out.png # capture screenshot
python3 tools/ctl.py settime # sync RTC to host clock
python3 tools/ctl.py tap 233 233 200 # tap injection
python3 tools/ctl.py log 10 # tail serial logIcons in assets_src/ were generated with gpt-image-2 and converted with
tools/png2lvgl.py.
Based on M5Stack's M5StopWatch-UserDemo
(MIT) — the HAL, launcher, and the SPECTRUM / ALARM / SETUP apps come from it.
Third-party components (M5GFX, LVGL, mooncake, smooth_ui_toolkit, ArduinoJson,
BMI270 driver) are fetched by fetch_repos.py under their respective
MIT/BSD licenses; patches/M5GFX.patch adds a one-line framebuffer accessor
used by the fx engine. This project is released under the MIT License (see
LICENSE).