Phase 0/1: display_setup (LovyanGFX + LVGL) and LVGL theme

- include/theme.h: replace M5Stack-based RGB565 constants with LVGL
  lv_color_hex helpers; same dark-mode palette + tile accent colors,
  portrait SCREEN_W=320 / SCREEN_H=480
- src/display/display_setup.{h,cpp}: custom LGfx device class with
  Panel_ST7796 + Bus_Parallel8 (esp32s3) + Light_PWM + Touch_FT5x06
  (FT6336U is FT5x06 family), setRotation(1) for portrait 320x480
- LVGL display driver with two PSRAM partial draw buffers (320x80)
- LVGL pointer indev wired to LovyanGFX touch readback
- display_loop() drives lv_tick_inc + lv_timer_handler
This commit is contained in:
2026-08-02 10:28:49 +02:00
parent 9e5056333c
commit abb5c62f3b
3 changed files with 204 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <lvgl.h>
// Dark-mode theme constants and tile accent colors for the WT32-SC01 Plus
// dashboard. Replaces the former M5Stack-specific theme.h; the same RGB565
// values are kept as 16-bit literals so they can be reused both for LVGL
// (via lv_color_hex) and for direct LovyanGFX drawing if needed.
namespace Theme {
// Logical portrait resolution after rotation (320 wide x 480 tall).
constexpr int SCREEN_W = 320;
constexpr int SCREEN_H = 480;
// Base palette (RGB565 kept for reference; LVGL uses the hex forms below).
// BG 0x0000 pure black
// BG_CARD 0x1082 very dark grey (tile / separator)
// TEXT 0xFFFF white
// TEXT_DIM 0x8410 mid grey
inline lv_color_t bg() { return lv_color_hex(0x000000); }
inline lv_color_t bgCard() { return lv_color_hex(0x1C1C1E); }
inline lv_color_t text() { return lv_color_hex(0xFFFFFF); }
inline lv_color_t textDim() { return lv_color_hex(0x9A9A9E); }
// Tile accent backgrounds (muted, iOS-dark-mode style).
inline lv_color_t accentWeather() { return lv_color_hex(0x1B3A5C); } // muted blue
inline lv_color_t accentCalendar() { return lv_color_hex(0x3A2A5C); } // muted purple
inline lv_color_t accentMvg() { return lv_color_hex(0x2C5C3A); } // muted green
// Symbol / status colors.
inline lv_color_t accentSun() { return lv_color_hex(0xFBC02D); }
inline lv_color_t accentRain() { return lv_color_hex(0x4FC3F7); }
inline lv_color_t accentUBahn() { return lv_color_hex(0xE62D2D); } // MVV U-Bahn red
inline lv_color_t accentSBahn() { return lv_color_hex(0x1A7A3A); } // MVV S-Bahn green
inline lv_color_t accentWarn() { return lv_color_hex(0xFFB300); }
inline lv_color_t accentError() { return lv_color_hex(0xF44336); }
inline lv_color_t accentStorm() { return lv_color_hex(0xFFD54F); }
} // namespace Theme
+153
View File
@@ -0,0 +1,153 @@
#include "display_setup.h"
#include <LovyanGFX.hpp>
#include <driver/i2c.h>
#include <board_pins.h>
#include <theme.h>
// Defined at file scope (not anonymous namespace) so it matches the
// `extern` declaration in display_setup.h without ambiguity.
lv_display_t *lv_display_gfx = nullptr;
namespace {
// --- LovyanGFX device for WT32-SC01 Plus ---
// ST7796 8-bit parallel panel + FT6336U (FT5x06 family) capacitive touch.
class LGfx : public lgfx::LGFX_Device {
lgfx::Panel_ST7796 _panel_instance;
lgfx::Bus_Parallel8 _bus_instance;
lgfx::Light_PWM _light_instance;
lgfx::Touch_FT5x06 _touch_instance;
public:
LGfx(void) {
// 8-bit parallel i80/8080 bus (ESP32-S3 variant).
{
auto cfg = _bus_instance.config();
cfg.freq_write = 16000000;
cfg.freq_read = 8000000;
cfg.pin_wr = BoardPins::LCD_WR;
cfg.pin_rd = BoardPins::LCD_RD;
cfg.pin_rs = BoardPins::LCD_DC; // DC = RS
cfg.pin_d0 = BoardPins::LCD_D0;
cfg.pin_d1 = BoardPins::LCD_D1;
cfg.pin_d2 = BoardPins::LCD_D2;
cfg.pin_d3 = BoardPins::LCD_D3;
cfg.pin_d4 = BoardPins::LCD_D4;
cfg.pin_d5 = BoardPins::LCD_D5;
cfg.pin_d6 = BoardPins::LCD_D6;
cfg.pin_d7 = BoardPins::LCD_D7;
_bus_instance.config(cfg);
_panel_instance.setBus(&_bus_instance);
}
// ST7796 panel geometry / control pins.
{
auto cfg = _panel_instance.config();
cfg.pin_cs = BoardPins::LCD_CS;
cfg.pin_rst = BoardPins::LCD_RST;
cfg.pin_busy = -1;
cfg.panel_width = 320;
cfg.panel_height = 480;
cfg.offset_x = 0;
cfg.offset_y = 0;
cfg.offset_rotation = 0;
cfg.readable = true;
cfg.invert = false;
cfg.rgb_order = false;
cfg.dlen_16bit = false;
cfg.bus_shared = true;
cfg.memory_width = 320;
cfg.memory_height = 480;
_panel_instance.config(cfg);
}
// Backlight via PWM.
{
auto cfg = _light_instance.config();
cfg.pin_bl = BoardPins::LCD_BACKLIGHT;
cfg.invert = false;
cfg.freq = 44100;
cfg.pwm_channel = 7;
_light_instance.config(cfg);
_panel_instance.setLight(&_light_instance);
}
// FT6336U touch (FT5x06 family) on I2C, addr 0x38 (default).
{
auto cfg = _touch_instance.config();
cfg.x_min = 0;
cfg.x_max = 319;
cfg.y_min = 0;
cfg.y_max = 479;
cfg.pin_int = BoardPins::TOUCH_INT;
cfg.bus_shared = true;
cfg.offset_rotation = 0;
cfg.i2c_port = I2C_NUM_0;
cfg.pin_sda = BoardPins::TOUCH_SDA;
cfg.pin_scl = BoardPins::TOUCH_SCL;
cfg.i2c_addr = 0x38;
cfg.freq = 400000;
_touch_instance.config(cfg);
_panel_instance.setTouch(&_touch_instance);
}
setPanel(&_panel_instance);
}
};
LGfx lcd;
// LVGL draw buffers in PSRAM (partial rendering, two buffers).
constexpr uint32_t LVGL_BUF_PIXELS = 320 * 80;
lv_color_t *lvgl_buf1 = nullptr;
lv_color_t *lvgl_buf2 = nullptr;
// --- LVGL display flush callback -> LovyanGFX ---
void lv_display_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) {
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
lcd.startWrite();
lcd.setAddrWindow(area->x1, area->y1, w, h);
lcd.writePixels((lgfx::rgb565_t *)px_map, w * h);
lcd.endWrite();
lv_display_flush_ready(disp);
}
// --- LVGL touch read callback -> LovyanGFX touch ---
void lv_touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data) {
lgfx::touch_point_t tp;
if (lcd.getTouch(&tp)) {
data->point.x = tp.x;
data->point.y = tp.y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
} // namespace
void display_init() {
lcd.init();
lcd.setRotation(1); // Portrait 320x480 on WT32-SC01 Plus
lcd.fillScreen(0x0000); // black (RGB565)
lcd.setBrightness(180);
// Allocate LVGL draw buffers in PSRAM.
lvgl_buf1 = (lv_color_t *)heap_caps_malloc(sizeof(lv_color_t) * LVGL_BUF_PIXELS, MALLOC_CAP_SPIRAM);
lvgl_buf2 = (lv_color_t *)heap_caps_malloc(sizeof(lv_color_t) * LVGL_BUF_PIXELS, MALLOC_CAP_SPIRAM);
lv_init();
lv_display_gfx = lv_display_create(Theme::SCREEN_W, Theme::SCREEN_H);
lv_display_set_color_format(lv_display_gfx, LV_COLOR_FORMAT_RGB565);
lv_display_set_buffers(lv_display_gfx, lvgl_buf1, lvgl_buf2,
sizeof(lv_color_t) * LVGL_BUF_PIXELS,
LV_DISPLAY_RENDER_MODE_PARTIAL);
lv_display_set_flush_cb(lv_display_gfx, lv_display_flush_cb);
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, lv_touch_read_cb);
lv_indev_set_display(indev, lv_display_gfx);
}
void display_loop() {
lv_tick_inc(5);
lv_timer_handler();
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <lvgl.h>
// Initializes the WT32-SC01 Plus display (LovyanGFX, ST7796 8-bit parallel,
// portrait 320x480) and the FT6336U capacitive touch, wires both into LVGL.
// Must be called once from setup() before any UI code.
void display_init();
// LVGL display driver instance (exposed so main loop can drive it).
extern lv_display_t *lv_display_gfx;
// Drive LVGL timers + touch readback. Call once per loop() iteration.
void display_loop();