ESP32-S3-Touch-LCD-1.28 first light: GC9A01 round display + backlight + CST816S
touch working via LovyanGFX. Draws a label and red dots on touch.
- src/hub_s3.cpp: LovyanGFX LGFX config for this board (GC9A01 SPI
SCLK10/MOSI11/MISO12/CS9/DC8/RST14, backlight GPIO2; CST816S touch I2C
SDA6/SCL7 polled, addr 0x15).
- platformio.ini: new [env:hub_s3] (esp32-s3-devkitc-1, 4MB, LovyanGFX).
Not in default_envs. Flash: esp32s3, bootloader@0x0 +boot_app0@0xe000.
Foundation for the on-device UI hub. PSRAM not yet enabled (not needed for
bring-up; will enable for LVGL). Next: I2C control bus to Boards B/C.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous delay buffer re-derived the read position from the write head on
every A2DP pull (read = write - delay), so it sampled the buffer at the
Bluetooth pull-rate while positioning by the I2S write-rate — skipping/
repeating samples (robotic) and, with no cushion at delay=0, constantly
under/overrunning until it destabilized and crashed.
Replace with a real FIFO: a sequential read pointer that advances by the
frames consumed, held BASE_DELAY_MS (40 ms jitter cushion) + the touch trim
behind the I2S write head, with underrun (pad silence) and overrun (resync)
handling. Also carry partial frames across I2S reads so L/R never slips.
Verified on hardware: clean audio to both speakers, stable for hours,
touch-pad sync aligns JBL and Cardo.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Wi-Fi/web/ESP-NOW sync approach was unviable: Wi-Fi + Bluetooth-A2DP +
the delay buffer exhausted RAM on the classic ESP32 (~20 KB free), and the
Bluetooth stack was so starved the source boards couldn't even connect to a
speaker. Confirmed on hardware: with Wi-Fi up the JBL would not connect; with
Wi-Fi removed it connects instantly (~65 KB free).
- board_source.cpp: remove Wi-Fi/ESP-NOW. Keep the I2S-reader-task + ring
delay line (now 200 ms; plenty of RAM without Wi-Fi). Adjust delay live by
ear via two capacitive-touch pads — "+" on GPIO4 (T0), "-" on GPIO27 (T7);
tap = 5 ms step, hold = ramp. Persisted to flash (debounced).
- board_sink.cpp: reverted to the simple A2DP sink + I2S master (no Wi-Fi).
- platformio.ini: drop SPEAKER_ID. Remove relay_config.h.
All three boards connect reliably with healthy heap. Touch pins read ~120
untouched; threshold 40.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds runtime speaker-sync control to the relay:
- src/relay_config.h: shared Wi-Fi AP creds + ESP-NOW RelayDelayMsg
(delay_ms[JBL], delay_ms[Cardo]) on a fixed channel.
- board_sink.cpp (Board A): hosts a Wi-Fi AP "BikeAudio-Setup" + a small
web UI (two sliders, no app) at 192.168.4.1, and broadcasts the chosen
per-speaker delays to the source boards over ESP-NOW. Still A2DP sink +
I2S master. (Tune while parked — Wi-Fi/BT coexist on one radio.)
- board_source.cpp (Boards B/C): inserts an adjustable delay line between
I2S in and A2DP out — a dedicated reader task fills a ring buffer (up to
~250 ms) and the A2DP callback reads delay_frames behind the write head.
Delay arrives via ESP-NOW (per SPEAKER_ID) and is persisted to flash
(Preferences), so it survives power cycles.
- platformio.ini: source envs get -DSPEAKER_ID (0=JBL, 1=Cardo).
Lets the rider trim JBL vs Cardo timing to sync the two speakers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>