refactor(doorbell): migrate ESP32-S3 to ESP_IOExpander library
This commit is contained in:
@@ -22,8 +22,10 @@ void DisplayManager::begin() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayManager::setBacklight(bool on) {
|
void DisplayDriverGFX::setBacklight(bool on) {
|
||||||
digitalWrite(PIN_LCD_BL, on ? HIGH : LOW);
|
// Cannot control after gfx->begin() — GPIO 8/9 are LCD data.
|
||||||
|
// Backlight is permanently ON, set during ch422gInit().
|
||||||
|
(void)on;
|
||||||
}
|
}
|
||||||
|
|
||||||
TouchEvent DisplayManager::readTouch() {
|
TouchEvent DisplayManager::readTouch() {
|
||||||
|
|||||||
@@ -1,157 +1,176 @@
|
|||||||
#include "DisplayDriverGFX.h"
|
#include "DisplayDriverGFX.h"
|
||||||
|
#include "board_config.h"
|
||||||
|
#include <ESP_IOExpander_Library.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
// Arduino_GFX uses RGB565 values directly; define convenience names
|
|
||||||
#ifndef BLACK
|
#ifndef BLACK
|
||||||
#define BLACK 0x0000
|
#define BLACK 0x0000
|
||||||
#endif
|
#endif
|
||||||
#ifndef WHITE
|
#ifndef WHITE
|
||||||
#define WHITE 0xFFFF
|
#define WHITE 0xFFFF
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef RED
|
#ifndef RED
|
||||||
#define RED 0xF800
|
#define RED 0xF800
|
||||||
#endif
|
#endif
|
||||||
#ifndef GREEN
|
|
||||||
#define GREEN 0x07E0
|
|
||||||
#endif
|
|
||||||
#ifndef BLUE
|
|
||||||
#define BLUE 0x001F
|
|
||||||
#endif
|
|
||||||
#ifndef YELLOW
|
|
||||||
#define YELLOW 0xFFE0
|
|
||||||
#endif
|
|
||||||
#ifndef CYAN
|
|
||||||
#define CYAN 0x07FF
|
|
||||||
#endif
|
|
||||||
#ifndef MAGENTA
|
|
||||||
#define MAGENTA 0xF81F
|
|
||||||
#endif
|
|
||||||
#ifndef ORANGE
|
|
||||||
#define ORANGE 0xFD20
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
// CH422G logical pin numbers
|
||||||
|
#define EXIO_TP_RST IO_EXPANDER_PIN_NUM_1
|
||||||
|
#define EXIO_LCD_BL IO_EXPANDER_PIN_NUM_2
|
||||||
|
#define EXIO_LCD_RST IO_EXPANDER_PIN_NUM_3
|
||||||
|
#define EXIO_SD_CS IO_EXPANDER_PIN_NUM_4
|
||||||
|
#define EXIO_USB_SEL IO_EXPANDER_PIN_NUM_5
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
static ESP_IOExpander* expander = nullptr;
|
||||||
// CH422G IO Expander
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
void DisplayDriverGFX::ch422gInit() {
|
// ── IO Expander ──
|
||||||
Wire.begin(I2C_SDA, I2C_SCL, I2C_FREQ);
|
|
||||||
|
|
||||||
// Enable OC output mode
|
void DisplayDriverGFX::expanderInit() {
|
||||||
Wire.beginTransmission(CH422G_SET_MODE >> 1);
|
Serial.println("[IO] IO expander init...");
|
||||||
Wire.write(0x01); // OC output enable
|
expander = new ESP_IOExpander_CH422G(
|
||||||
Wire.endTransmission();
|
I2C_MASTER_NUM,
|
||||||
|
ESP_IO_EXPANDER_I2C_CH422G_ADDRESS
|
||||||
// Deassert resets, backlight OFF initially
|
);
|
||||||
_exioBits = EXIO_TP_RST | EXIO_LCD_RST | EXIO_SD_CS;
|
expander->init();
|
||||||
ch422gWrite(_exioBits);
|
expander->begin();
|
||||||
|
expander->multiPinMode(
|
||||||
delay(100);
|
EXIO_TP_RST | EXIO_LCD_BL | EXIO_LCD_RST | EXIO_SD_CS | EXIO_USB_SEL,
|
||||||
|
OUTPUT
|
||||||
|
);
|
||||||
|
// Deassert resets, backlight OFF for now
|
||||||
|
expander->multiDigitalWrite(
|
||||||
|
EXIO_TP_RST | EXIO_LCD_RST | EXIO_SD_CS,
|
||||||
|
0xFF
|
||||||
|
);
|
||||||
|
expander->digitalWrite(EXIO_LCD_BL, LOW);
|
||||||
Serial.println("[IO] CH422G initialized");
|
Serial.println("[IO] CH422G initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayDriverGFX::ch422gWrite(uint8_t val) {
|
void DisplayDriverGFX::setBacklight(bool on) {
|
||||||
Wire.beginTransmission(CH422G_WRITE_OC >> 1);
|
if (expander) {
|
||||||
Wire.write(val);
|
expander->digitalWrite(EXIO_LCD_BL, on ? HIGH : LOW);
|
||||||
Wire.endTransmission();
|
Serial.printf("[GFX] Backlight %s\n", on ? "ON" : "OFF");
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayDriverGFX::exioSet(uint8_t bit, bool on) {
|
|
||||||
if (on) _exioBits |= bit;
|
|
||||||
else _exioBits &= ~bit;
|
|
||||||
ch422gWrite(_exioBits);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
// GT911 Touch (minimal implementation)
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
void DisplayDriverGFX::gt911Init() {
|
|
||||||
// GT911 is on the same I2C bus (Wire), already started by ch422gInit.
|
|
||||||
// Reset sequence: pull TP_RST low then high (via CH422G EXIO1).
|
|
||||||
exioSet(EXIO_TP_RST, false);
|
|
||||||
delay(10);
|
|
||||||
exioSet(EXIO_TP_RST, true);
|
|
||||||
delay(50);
|
|
||||||
Serial.println("[TOUCH] GT911 initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DisplayDriverGFX::gt911Read(int& x, int& y) {
|
|
||||||
// Read status register 0x814E
|
|
||||||
Wire.beginTransmission(GT911_ADDR);
|
|
||||||
Wire.write(0x81); Wire.write(0x4E);
|
|
||||||
if (Wire.endTransmission() != 0) return false;
|
|
||||||
|
|
||||||
Wire.requestFrom((uint8_t)GT911_ADDR, (uint8_t)1);
|
|
||||||
if (!Wire.available()) return false;
|
|
||||||
uint8_t status = Wire.read();
|
|
||||||
|
|
||||||
uint8_t touches = status & 0x0F;
|
|
||||||
bool ready = status & 0x80;
|
|
||||||
|
|
||||||
if (!ready || touches == 0 || touches > 5) {
|
|
||||||
// Clear status
|
|
||||||
Wire.beginTransmission(GT911_ADDR);
|
|
||||||
Wire.write(0x81); Wire.write(0x4E); Wire.write(0x00);
|
|
||||||
Wire.endTransmission();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Read first touch point (0x8150..0x8157)
|
// ── Touch ──
|
||||||
|
|
||||||
|
void DisplayDriverGFX::touchInit() {
|
||||||
|
Wire.begin(I2C_MASTER_SDA, I2C_MASTER_SCL);
|
||||||
Wire.beginTransmission(GT911_ADDR);
|
Wire.beginTransmission(GT911_ADDR);
|
||||||
Wire.write(0x81); Wire.write(0x50);
|
uint8_t err = Wire.endTransmission();
|
||||||
Wire.endTransmission();
|
if (err == 0) {
|
||||||
Wire.requestFrom((uint8_t)GT911_ADDR, (uint8_t)4);
|
Serial.println("[TOUCH] GT911 initialized");
|
||||||
if (Wire.available() >= 4) {
|
} else {
|
||||||
uint8_t xl = Wire.read();
|
Serial.printf("[TOUCH] GT911 not found (I2C err %d)\n", err);
|
||||||
uint8_t xh = Wire.read();
|
}
|
||||||
uint8_t yl = Wire.read();
|
}
|
||||||
uint8_t yh = Wire.read();
|
|
||||||
x = (xh << 8) | xl;
|
TouchEvent DisplayDriverGFX::readTouch() {
|
||||||
y = (yh << 8) | yl;
|
TouchEvent ev = { false, 0, 0 };
|
||||||
|
Wire.beginTransmission(GT911_ADDR);
|
||||||
|
if (Wire.endTransmission() != 0) return ev;
|
||||||
|
|
||||||
|
// Read touch status register (0x814E)
|
||||||
|
Wire.beginTransmission(GT911_ADDR);
|
||||||
|
Wire.write(0x81);
|
||||||
|
Wire.write(0x4E);
|
||||||
|
Wire.endTransmission(false);
|
||||||
|
Wire.requestFrom((uint8_t)GT911_ADDR, (uint8_t)1);
|
||||||
|
if (!Wire.available()) return ev;
|
||||||
|
|
||||||
|
uint8_t status = Wire.read();
|
||||||
|
uint8_t touches = status & 0x0F;
|
||||||
|
|
||||||
|
if ((status & 0x80) && touches > 0 && touches <= 5) {
|
||||||
|
// Read first touch point (0x8150)
|
||||||
|
Wire.beginTransmission(GT911_ADDR);
|
||||||
|
Wire.write(0x81);
|
||||||
|
Wire.write(0x50);
|
||||||
|
Wire.endTransmission(false);
|
||||||
|
Wire.requestFrom((uint8_t)GT911_ADDR, (uint8_t)4);
|
||||||
|
if (Wire.available() >= 4) {
|
||||||
|
uint8_t xl = Wire.read();
|
||||||
|
uint8_t xh = Wire.read();
|
||||||
|
uint8_t yl = Wire.read();
|
||||||
|
uint8_t yh = Wire.read();
|
||||||
|
ev.pressed = true;
|
||||||
|
ev.x = (xh << 8) | xl;
|
||||||
|
ev.y = (yh << 8) | yl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear status
|
// Clear status
|
||||||
Wire.beginTransmission(GT911_ADDR);
|
Wire.beginTransmission(GT911_ADDR);
|
||||||
Wire.write(0x81); Wire.write(0x4E); Wire.write(0x00);
|
Wire.write(0x81);
|
||||||
|
Wire.write(0x4E);
|
||||||
|
Wire.write(0x00);
|
||||||
Wire.endTransmission();
|
Wire.endTransmission();
|
||||||
|
|
||||||
return true;
|
return ev;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
int DisplayDriverGFX::dashboardTouch(int x, int y) {
|
||||||
// Display Init
|
// Simple 2x2 grid: 4 tiles
|
||||||
// ═══════════════════════════════════════════════════════════
|
int col = (x < DISPLAY_WIDTH / 2) ? 0 : 1;
|
||||||
|
int row = (y < DISPLAY_HEIGHT / 2) ? 0 : 1;
|
||||||
|
return row * 2 + col;
|
||||||
|
}
|
||||||
|
|
||||||
|
HoldState DisplayDriverGFX::updateHold(unsigned long holdMs) {
|
||||||
|
TouchEvent ev = readTouch();
|
||||||
|
if (ev.pressed) {
|
||||||
|
if (!_lastTouched) {
|
||||||
|
_holdStart = millis();
|
||||||
|
_lastTouched = true;
|
||||||
|
}
|
||||||
|
unsigned long elapsed = millis() - _holdStart;
|
||||||
|
if (elapsed >= holdMs) {
|
||||||
|
return {false, true};
|
||||||
|
}
|
||||||
|
return {true, false};
|
||||||
|
} else {
|
||||||
|
_lastTouched = false;
|
||||||
|
_holdStart = 0;
|
||||||
|
return {false, false};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverGFX::updateHint() {
|
||||||
|
// placeholder for idle hint animation
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Display ──
|
||||||
|
|
||||||
void DisplayDriverGFX::begin() {
|
void DisplayDriverGFX::begin() {
|
||||||
// 1. IO expander first (controls resets + backlight)
|
// 1. Touch (I2C on GPIO 8/9)
|
||||||
ch422gInit();
|
touchInit();
|
||||||
|
delay(200);
|
||||||
|
|
||||||
// 2. Create RGB bus with corrected Waveshare timing
|
// 2. IO expander
|
||||||
|
expanderInit();
|
||||||
|
|
||||||
|
// 3. RGB display
|
||||||
|
Serial.println("[GFX] GFX init...");
|
||||||
Arduino_ESP32RGBPanel* rgbPanel = new Arduino_ESP32RGBPanel(
|
Arduino_ESP32RGBPanel* rgbPanel = new Arduino_ESP32RGBPanel(
|
||||||
LCD_DE, LCD_VSYNC, LCD_HSYNC, LCD_PCLK,
|
LCD_DE, LCD_VSYNC, LCD_HSYNC, LCD_PCLK,
|
||||||
LCD_R0, LCD_R1, LCD_R2, LCD_R3, LCD_R4,
|
LCD_R0, LCD_R1, LCD_R2, LCD_R3, LCD_R4,
|
||||||
LCD_G0, LCD_G1, LCD_G2, LCD_G3, LCD_G4, LCD_G5,
|
LCD_G0, LCD_G1, LCD_G2, LCD_G3, LCD_G4, LCD_G5,
|
||||||
LCD_B0, LCD_B1, LCD_B2, LCD_B3, LCD_B4,
|
LCD_B0, LCD_B1, LCD_B2, LCD_B3, LCD_B4,
|
||||||
// ─── Corrected timing for ST7262 / Waveshare 4.3" ───
|
0, // hsync_polarity
|
||||||
1, // hsync_polarity
|
40, // hsync_front_porch
|
||||||
10, // hsync_front_porch
|
48, // hsync_pulse_width
|
||||||
8, // hsync_pulse_width
|
88, // hsync_back_porch
|
||||||
50, // hsync_back_porch
|
0, // vsync_polarity
|
||||||
1, // vsync_polarity
|
13, // vsync_front_porch
|
||||||
10, // vsync_front_porch
|
3, // vsync_pulse_width
|
||||||
8, // vsync_pulse_width
|
32, // vsync_back_porch
|
||||||
20, // vsync_back_porch
|
1, // pclk_active_neg
|
||||||
1, // pclk_active_neg *** CRITICAL — must be 1 ***
|
16000000 // prefer_speed
|
||||||
16000000 // prefer_speed = 16 MHz PCLK
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Create display
|
|
||||||
_gfx = new Arduino_RGB_Display(
|
_gfx = new Arduino_RGB_Display(
|
||||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, rgbPanel,
|
DISPLAY_WIDTH, DISPLAY_HEIGHT, rgbPanel,
|
||||||
DISPLAY_ROTATION,
|
DISPLAY_ROTATION, true
|
||||||
true // auto_flush
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!_gfx->begin()) {
|
if (!_gfx->begin()) {
|
||||||
@@ -160,197 +179,74 @@ void DisplayDriverGFX::begin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Serial.printf("[GFX] Display OK: %dx%d\n", DISPLAY_WIDTH, DISPLAY_HEIGHT);
|
Serial.printf("[GFX] Display OK: %dx%d\n", DISPLAY_WIDTH, DISPLAY_HEIGHT);
|
||||||
|
|
||||||
// PSRAM diagnostic
|
|
||||||
Serial.printf("[MEM] Free heap: %d | Free PSRAM: %d\n",
|
Serial.printf("[MEM] Free heap: %d | Free PSRAM: %d\n",
|
||||||
ESP.getFreeHeap(), ESP.getFreePsram());
|
ESP.getFreeHeap(), ESP.getFreePsram());
|
||||||
if (ESP.getFreePsram() == 0) {
|
|
||||||
Serial.println("[MEM] *** WARNING: PSRAM not detected! "
|
|
||||||
"Display will likely be blank. "
|
|
||||||
"Ensure PSRAM=opi in board config. ***");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Init touch
|
// 4. Clear and backlight on
|
||||||
gt911Init();
|
|
||||||
|
|
||||||
// 5. Show boot screen (backlight still off)
|
|
||||||
_gfx->fillScreen(BLACK);
|
_gfx->fillScreen(BLACK);
|
||||||
|
setBacklight(true);
|
||||||
drawBoot();
|
drawBoot();
|
||||||
|
|
||||||
// 6. Backlight ON
|
|
||||||
exioSet(EXIO_LCD_BL, true);
|
|
||||||
Serial.println("[GFX] Backlight ON");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayDriverGFX::setBacklight(bool on) {
|
int DisplayDriverGFX::width() { return _gfx ? _gfx->width() : DISPLAY_WIDTH; }
|
||||||
exioSet(EXIO_LCD_BL, on);
|
int DisplayDriverGFX::height() { return _gfx ? _gfx->height() : DISPLAY_HEIGHT; }
|
||||||
}
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
// ── Render (state machine driven) ──
|
||||||
// Rendering
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
void DisplayDriverGFX::render(const ScreenState& st) {
|
void DisplayDriverGFX::render(const ScreenState& state) {
|
||||||
if (st.screen != _lastScreen) {
|
if (!_gfx) return;
|
||||||
_needsRedraw = true;
|
switch (state.screen) {
|
||||||
_lastScreen = st.screen;
|
case ScreenID::BOOT_SPLASH: drawBoot(); break;
|
||||||
}
|
case ScreenID::DASHBOARD: drawDashboard(state); break;
|
||||||
|
case ScreenID::ALERT: drawAlert(state); break;
|
||||||
switch (st.screen) {
|
case ScreenID::OFF: drawOff(); break;
|
||||||
case ScreenID::BOOT:
|
default: drawBoot(); break;
|
||||||
if (_needsRedraw) { drawBoot(); _needsRedraw = false; }
|
|
||||||
break;
|
|
||||||
case ScreenID::ALERT:
|
|
||||||
drawAlert(st); // redraws every frame (pulse animation)
|
|
||||||
break;
|
|
||||||
case ScreenID::DASHBOARD:
|
|
||||||
if (_needsRedraw) { drawDashboard(st); _needsRedraw = false; }
|
|
||||||
break;
|
|
||||||
case ScreenID::OFF:
|
|
||||||
if (_needsRedraw) { _gfx->fillScreen(BLACK); _needsRedraw = false; }
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayDriverGFX::drawBoot() {
|
void DisplayDriverGFX::drawBoot() {
|
||||||
|
if (!_gfx) return;
|
||||||
|
_gfx->fillScreen(BLACK);
|
||||||
|
_gfx->setTextColor(WHITE);
|
||||||
|
_gfx->setTextSize(4);
|
||||||
|
_gfx->setCursor(250, 200);
|
||||||
|
_gfx->println("Klubhaus Alert");
|
||||||
|
_gfx->setTextSize(2);
|
||||||
|
_gfx->setCursor(300, 260);
|
||||||
|
_gfx->println("Booting...");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayDriverGFX::drawDashboard(const ScreenState& state) {
|
||||||
|
if (!_gfx) return;
|
||||||
_gfx->fillScreen(BLACK);
|
_gfx->fillScreen(BLACK);
|
||||||
_gfx->setTextColor(WHITE);
|
_gfx->setTextColor(WHITE);
|
||||||
_gfx->setTextSize(3);
|
_gfx->setTextSize(3);
|
||||||
_gfx->setCursor(40, 40);
|
_gfx->setCursor(20, 20);
|
||||||
_gfx->printf("KLUBHAUS ALERT v%s", FW_VERSION);
|
_gfx->println("KLUBHAUS DASHBOARD");
|
||||||
_gfx->setTextSize(2);
|
_gfx->setTextSize(2);
|
||||||
_gfx->setCursor(40, 100);
|
_gfx->setCursor(20, 80);
|
||||||
_gfx->print(BOARD_NAME);
|
_gfx->printf("IP: %s", state.wifiIP);
|
||||||
_gfx->setCursor(40, 140);
|
_gfx->setCursor(20, 110);
|
||||||
_gfx->print("Booting...");
|
_gfx->printf("RSSI: %d dBm", state.wifiRSSI);
|
||||||
|
_gfx->setCursor(20, 140);
|
||||||
|
_gfx->printf("Time: %s", state.timeString);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayDriverGFX::drawAlert(const ScreenState& st) {
|
void DisplayDriverGFX::drawAlert(const ScreenState& state) {
|
||||||
// Pulsing red background
|
if (!_gfx) return;
|
||||||
uint32_t elapsed = millis() - st.alertStartMs;
|
_gfx->fillScreen(RED);
|
||||||
uint8_t pulse = 180 + (uint8_t)(75.0f * sinf(elapsed / 300.0f));
|
|
||||||
uint16_t bg = _gfx->color565(pulse, 0, 0);
|
|
||||||
|
|
||||||
_gfx->fillScreen(bg);
|
|
||||||
_gfx->setTextColor(WHITE);
|
_gfx->setTextColor(WHITE);
|
||||||
|
|
||||||
// Title
|
|
||||||
_gfx->setTextSize(5);
|
_gfx->setTextSize(5);
|
||||||
_gfx->setCursor(40, 80);
|
_gfx->setCursor(200, 180);
|
||||||
_gfx->print(st.alertTitle.length() > 0 ? st.alertTitle : "ALERT");
|
_gfx->println("DOORBELL!");
|
||||||
|
|
||||||
// Body
|
|
||||||
_gfx->setTextSize(3);
|
_gfx->setTextSize(3);
|
||||||
_gfx->setCursor(40, 200);
|
_gfx->setCursor(100, 280);
|
||||||
_gfx->print(st.alertBody);
|
_gfx->println(state.alertMessage ? state.alertMessage : "Someone is at the door");
|
||||||
|
|
||||||
// Hold hint
|
|
||||||
_gfx->setTextSize(2);
|
|
||||||
_gfx->setCursor(40, DISPLAY_HEIGHT - 60);
|
|
||||||
_gfx->print("Hold to silence...");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayDriverGFX::drawDashboard(const ScreenState& st) {
|
void DisplayDriverGFX::drawOff() {
|
||||||
|
if (!_gfx) return;
|
||||||
_gfx->fillScreen(BLACK);
|
_gfx->fillScreen(BLACK);
|
||||||
_gfx->setTextColor(WHITE);
|
setBacklight(false);
|
||||||
|
|
||||||
// Title bar
|
|
||||||
_gfx->setTextSize(2);
|
|
||||||
_gfx->setCursor(20, 10);
|
|
||||||
_gfx->printf("KLUBHAUS — %s", deviceStateStr(st.deviceState));
|
|
||||||
|
|
||||||
// Info tiles (simple text grid)
|
|
||||||
int y = 60;
|
|
||||||
int dy = 50;
|
|
||||||
_gfx->setTextSize(2);
|
|
||||||
|
|
||||||
_gfx->setCursor(20, y);
|
|
||||||
_gfx->printf("WiFi: %s RSSI: %d", st.wifiSsid.c_str(), st.wifiRssi);
|
|
||||||
y += dy;
|
|
||||||
|
|
||||||
_gfx->setCursor(20, y);
|
|
||||||
_gfx->printf("IP: %s", st.ipAddr.c_str());
|
|
||||||
y += dy;
|
|
||||||
|
|
||||||
_gfx->setCursor(20, y);
|
|
||||||
_gfx->printf("Uptime: %lus", st.uptimeMs / 1000);
|
|
||||||
y += dy;
|
|
||||||
|
|
||||||
_gfx->setCursor(20, y);
|
|
||||||
_gfx->printf("Heap: %d PSRAM: %d", ESP.getFreeHeap(), ESP.getFreePsram());
|
|
||||||
y += dy;
|
|
||||||
|
|
||||||
_gfx->setCursor(20, y);
|
|
||||||
_gfx->printf("Last poll: %lus ago",
|
|
||||||
st.lastPollMs > 0 ? (millis() - st.lastPollMs) / 1000 : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
// Touch
|
|
||||||
// ═══════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
TouchEvent DisplayDriverGFX::readTouch() {
|
|
||||||
TouchEvent evt;
|
|
||||||
int x, y;
|
|
||||||
if (gt911Read(x, y)) {
|
|
||||||
evt.pressed = true;
|
|
||||||
evt.x = x;
|
|
||||||
evt.y = y;
|
|
||||||
}
|
|
||||||
return evt;
|
|
||||||
}
|
|
||||||
|
|
||||||
int DisplayDriverGFX::dashboardTouch(int x, int y) {
|
|
||||||
// Simple 2-column, 4-row tile grid
|
|
||||||
int col = x / (DISPLAY_WIDTH / 2);
|
|
||||||
int row = (y - 60) / 80;
|
|
||||||
if (row < 0 || row > 3) return -1;
|
|
||||||
return row * 2 + col;
|
|
||||||
}
|
|
||||||
|
|
||||||
HoldState DisplayDriverGFX::updateHold(unsigned long holdMs) {
|
|
||||||
HoldState h;
|
|
||||||
TouchEvent t = readTouch();
|
|
||||||
|
|
||||||
if (t.pressed) {
|
|
||||||
if (!_holdActive) {
|
|
||||||
_holdActive = true;
|
|
||||||
_holdStartMs = millis();
|
|
||||||
}
|
|
||||||
uint32_t held = millis() - _holdStartMs;
|
|
||||||
h.active = true;
|
|
||||||
h.progress = constrain((float)held / (float)holdMs, 0.0f, 1.0f);
|
|
||||||
h.completed = (held >= holdMs);
|
|
||||||
|
|
||||||
// Draw progress arc
|
|
||||||
if (h.active && !h.completed) {
|
|
||||||
int cx = DISPLAY_WIDTH / 2;
|
|
||||||
int cy = DISPLAY_HEIGHT / 2;
|
|
||||||
int r = 100;
|
|
||||||
float angle = h.progress * 360.0f;
|
|
||||||
// Simple progress: draw filled arc sector
|
|
||||||
for (float a = 0; a < angle; a += 2.0f) {
|
|
||||||
float rad = a * PI / 180.0f;
|
|
||||||
int px = cx + (int)(r * cosf(rad - PI / 2));
|
|
||||||
int py = cy + (int)(r * sinf(rad - PI / 2));
|
|
||||||
_gfx->fillCircle(px, py, 4, WHITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_holdActive = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastTouched = t.pressed;
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayDriverGFX::updateHint() {
|
|
||||||
// Subtle pulsing ring to hint "hold here"
|
|
||||||
float t = (millis() % 2000) / 2000.0f;
|
|
||||||
uint8_t alpha = (uint8_t)(60.0f + 40.0f * sinf(t * 2 * PI));
|
|
||||||
uint16_t col = _gfx->color565(alpha, alpha, alpha);
|
|
||||||
int cx = DISPLAY_WIDTH / 2;
|
|
||||||
int cy = DISPLAY_HEIGHT / 2 + 60;
|
|
||||||
_gfx->drawCircle(cx, cy, 80, col);
|
|
||||||
_gfx->drawCircle(cx, cy, 81, col);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <KlubhausCore.h>
|
#include <IDisplayDriver.h>
|
||||||
#include <Arduino_GFX_Library.h>
|
#include <Arduino_GFX_Library.h>
|
||||||
#include <Wire.h>
|
|
||||||
#include "board_config.h"
|
|
||||||
|
|
||||||
class DisplayDriverGFX : public IDisplayDriver {
|
class DisplayDriverGFX : public IDisplayDriver {
|
||||||
public:
|
public:
|
||||||
@@ -14,33 +12,18 @@ public:
|
|||||||
int dashboardTouch(int x, int y) override;
|
int dashboardTouch(int x, int y) override;
|
||||||
HoldState updateHold(unsigned long holdMs) override;
|
HoldState updateHold(unsigned long holdMs) override;
|
||||||
void updateHint() override;
|
void updateHint() override;
|
||||||
int width() override { return DISPLAY_WIDTH; }
|
int width() override;
|
||||||
int height() override { return DISPLAY_HEIGHT; }
|
int height() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// CH422G helpers
|
void expanderInit();
|
||||||
void ch422gInit();
|
void touchInit();
|
||||||
void ch422gWrite(uint8_t val);
|
|
||||||
uint8_t _exioBits = 0;
|
|
||||||
void exioSet(uint8_t bit, bool on);
|
|
||||||
|
|
||||||
// GT911 helpers
|
|
||||||
void gt911Init();
|
|
||||||
bool gt911Read(int& x, int& y);
|
|
||||||
|
|
||||||
// Rendering
|
|
||||||
void drawBoot();
|
void drawBoot();
|
||||||
void drawAlert(const ScreenState& st);
|
void drawDashboard(const ScreenState& state);
|
||||||
void drawDashboard(const ScreenState& st);
|
void drawAlert(const ScreenState& state);
|
||||||
|
void drawOff();
|
||||||
Arduino_GFX* _gfx = nullptr;
|
Arduino_RGB_Display* _gfx = nullptr;
|
||||||
|
bool _lastTouched = false;
|
||||||
// Hold-to-silence tracking
|
unsigned long _holdStart = 0;
|
||||||
bool _holdActive = false;
|
|
||||||
uint32_t _holdStartMs = 0;
|
|
||||||
bool _lastTouched = false;
|
|
||||||
|
|
||||||
// Render-change tracking
|
|
||||||
ScreenID _lastScreen = ScreenID::BOOT;
|
|
||||||
bool _needsRedraw = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +1,41 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define BOARD_NAME "WS_S3_43"
|
#define BOARD_NAME "WS_S3_43"
|
||||||
#define DISPLAY_WIDTH 800
|
#define DISPLAY_WIDTH 800
|
||||||
#define DISPLAY_HEIGHT 480
|
#define DISPLAY_HEIGHT 480
|
||||||
#define DISPLAY_ROTATION 0 // landscape, USB-C on left
|
#define DISPLAY_ROTATION 0
|
||||||
|
|
||||||
// ── RGB parallel bus pins (directly to ST7262 panel) ──
|
// ── RGB parallel bus (from Westcott1 reference) ──
|
||||||
#define LCD_DE 40
|
#define LCD_DE 5
|
||||||
#define LCD_VSYNC 41
|
#define LCD_VSYNC 3
|
||||||
#define LCD_HSYNC 39
|
#define LCD_HSYNC 46
|
||||||
#define LCD_PCLK 42
|
#define LCD_PCLK 7
|
||||||
|
|
||||||
#define LCD_R0 45
|
#define LCD_R0 1
|
||||||
#define LCD_R1 48
|
#define LCD_R1 2
|
||||||
#define LCD_R2 47
|
#define LCD_R2 42
|
||||||
#define LCD_R3 21
|
#define LCD_R3 41
|
||||||
#define LCD_R4 14
|
#define LCD_R4 40
|
||||||
|
|
||||||
#define LCD_G0 5
|
#define LCD_G0 39
|
||||||
#define LCD_G1 6
|
#define LCD_G1 0
|
||||||
#define LCD_G2 7
|
#define LCD_G2 45
|
||||||
#define LCD_G3 15
|
#define LCD_G3 48
|
||||||
#define LCD_G4 16
|
#define LCD_G4 47
|
||||||
#define LCD_G5 4
|
#define LCD_G5 21
|
||||||
|
|
||||||
#define LCD_B0 8
|
#define LCD_B0 14
|
||||||
#define LCD_B1 3
|
#define LCD_B1 38
|
||||||
#define LCD_B2 46
|
#define LCD_B2 18
|
||||||
#define LCD_B3 9
|
#define LCD_B3 17
|
||||||
#define LCD_B4 1
|
#define LCD_B4 10
|
||||||
|
|
||||||
// ── CH422G I2C IO expander ──
|
// ── I2C bus (shared: CH422G + GT911) ──
|
||||||
// Controls LCD_RST, TP_RST, LCD_BL, SD_CS via I2C
|
#define I2C_MASTER_NUM 0
|
||||||
#define I2C_SDA 17
|
#define I2C_MASTER_SDA 8
|
||||||
#define I2C_SCL 18
|
#define I2C_MASTER_SCL 9
|
||||||
#define I2C_FREQ 100000
|
|
||||||
|
|
||||||
// CH422G I2C command addresses
|
|
||||||
#define CH422G_WRITE_OC 0x46
|
|
||||||
#define CH422G_SET_MODE 0x48
|
|
||||||
#define CH422G_READ_IN 0x4C
|
|
||||||
|
|
||||||
// EXIO bit positions
|
|
||||||
#define EXIO_TP_RST (1 << 0) // EXIO1
|
|
||||||
#define EXIO_LCD_BL (1 << 1) // EXIO2 — also drives DISP signal!
|
|
||||||
#define EXIO_LCD_RST (1 << 2) // EXIO3
|
|
||||||
#define EXIO_SD_CS (1 << 3) // EXIO4
|
|
||||||
|
|
||||||
// ── GT911 Touch ──
|
// ── GT911 Touch ──
|
||||||
#define GT911_ADDR 0x5D
|
#define GT911_ADDR 0x5D
|
||||||
#define TOUCH_INT -1 // not wired to a readable GPIO on this board
|
#define TOUCH_INT -1
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ description = "Install shared (platform-independent) libraries"
|
|||||||
run = """
|
run = """
|
||||||
arduino-cli lib install "ArduinoJson@7.4.1"
|
arduino-cli lib install "ArduinoJson@7.4.1"
|
||||||
arduino-cli lib install "NTPClient@3.2.1"
|
arduino-cli lib install "NTPClient@3.2.1"
|
||||||
|
arduino-cli lib install "ESP32_IO_Expander@0.0.4"
|
||||||
echo "[OK] Shared libraries installed"
|
echo "[OK] Shared libraries installed"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
1772
scaffold.sh
1772
scaffold.sh
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user