S3 hub Phase 1: round LCD + touch bring-up (LovyanGFX)

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>
This commit is contained in:
blue — ESP32/PlatformIO firmware 2026-06-11 10:12:17 -04:00
parent 0b1c34074f
commit 9ed1899285
2 changed files with 115 additions and 0 deletions

View File

@ -37,3 +37,16 @@ build_flags = '-DTARGET_SPEAKER="JBL Charge 5"'
[env:source_cardo]
build_src_filter = +<board_source.cpp>
build_flags = '-DTARGET_SPEAKER="Tangerine EDGE"'
; --- Hub: ESP32-S3-Touch-LCD-1.28 (round GC9A01 LCD + CST816S touch) ----------
; Different chip (esp32s3) from the relay boards. Drives the UI; later the
; wired control bus to Boards B/C. NOT in default_envs — build with -e hub_s3.
[env:hub_s3]
platform = espressif32 @ ~6.6.0
board = esp32-s3-devkitc-1
framework = arduino
board_upload.flash_size = 4MB
board_build.partitions = default.csv
monitor_speed = 115200
build_src_filter = +<hub_s3.cpp>
lib_deps = lovyan03/LovyanGFX@^1.1.16

102
src/hub_s3.cpp Normal file
View File

@ -0,0 +1,102 @@
/**
* BikeAudio Hub (ESP32-S3-Touch-LCD-1.28) : PHASE 1 bring-up
*
* First light: drive the round GC9A01 LCD + backlight, and read the CST816S
* touch, to prove the board before building the GUI. No control bus / GUI yet.
*
* Board pins (Waveshare ESP32-S3-Touch-LCD-1.28):
* GC9A01 SPI: SCLK=10 MOSI=11 MISO=12 CS=9 DC=8 RST=14 backlight=2
* CST816S touch (I2C): SDA=6 SCL=7 (polled; INT left unused)
*
* Build: pio run -e hub_s3 | flash: esp32s3, bootloader@0x0
*/
#define LGFX_USE_V1
#include <Arduino.h>
#include <LovyanGFX.hpp>
class LGFX : public lgfx::LGFX_Device {
lgfx::Panel_GC9A01 _panel;
lgfx::Bus_SPI _bus;
lgfx::Light_PWM _light;
lgfx::Touch_CST816S _touch;
public:
LGFX() {
{ auto c = _bus.config();
c.spi_host = SPI2_HOST;
c.spi_mode = 0;
c.freq_write = 40000000;
c.pin_sclk = 10;
c.pin_mosi = 11;
c.pin_miso = 12;
c.pin_dc = 8;
_bus.config(c); _panel.setBus(&_bus); }
{ auto c = _panel.config();
c.pin_cs = 9;
c.pin_rst = 14;
c.panel_width = 240;
c.panel_height = 240;
c.offset_x = 0;
c.offset_y = 0;
c.readable = false;
c.invert = true; // GC9A01 typically needs inversion
c.rgb_order = false;
_panel.config(c); }
{ auto c = _light.config();
c.pin_bl = 2;
c.freq = 12000;
c.pwm_channel = 7;
_light.config(c); _panel.setLight(&_light); }
{ auto c = _touch.config();
c.i2c_port = 0;
c.pin_sda = 6;
c.pin_scl = 7;
c.pin_int = -1; // poll over I2C (avoid INT-pin ambiguity)
c.pin_rst = -1;
c.i2c_addr = 0x15;
c.freq = 400000;
c.x_min = 0; c.x_max = 239; c.y_min = 0; c.y_max = 239;
_touch.config(c); _panel.setTouch(&_touch); }
setPanel(&_panel);
}
};
LGFX lcd;
void setup() {
Serial.begin(115200);
delay(300);
Serial.println("=== BikeAudio Hub — S3 round LCD first light ===");
lcd.init();
lcd.setRotation(0);
lcd.setBrightness(200);
lcd.fillScreen(TFT_BLACK);
lcd.drawCircle(120, 120, 118, TFT_DARKGREY);
lcd.setTextColor(TFT_CYAN, TFT_BLACK);
lcd.setTextDatum(middle_center);
lcd.drawString("BikeAudio", 120, 100, &fonts::FreeSansBold18pt7b);
lcd.setTextColor(TFT_WHITE, TFT_BLACK);
lcd.drawString("hub: first light", 120, 140, &fonts::Font2);
lcd.drawString("touch me", 120, 160, &fonts::Font2);
Serial.println("[LCD] drawn; touch to draw dots");
}
void loop() {
int32_t x, y;
if (lcd.getTouch(&x, &y)) {
lcd.fillCircle(x, y, 5, TFT_RED);
Serial.printf("[TOUCH] %d, %d\n", x, y);
}
static unsigned long last = 0;
if (millis() - last > 5000) {
Serial.printf("[hub] alive heap=%u psram=%u\n", ESP.getFreeHeap(), ESP.getFreePsram());
last = millis();
}
delay(15);
}