Files
wadamesh/variants/heltec_v4/LGFXDisplay.cpp
T
041dd60b8d touch: beta_46 — control-center redesign, Do Not Disturb, contact safety + board fixes
- Contact-loss fix (microSD devices): atomic temp+swap saveContacts, unified robust
  boot SD-mount ladder, and a storage-truth line in the Store-on-SD setting.
- Heltec V4-R8: route MISO for the shared FSPI bus so the microSD mounts; wake-from-sleep
  freeze fixed (panel sleep goes through LovyanGFX's own bus, not the HSPI shim).
- Do Not Disturb (originally PR #156 by Tesso Codemonkey / @codemonkeybr): scheduled
  sound-silencing window in Settings > Sound + a status-bar bell.
- Control-center redesign: round icon-only chips that spread to fill each row (T-Deck 2x4,
  V4 portrait 2 rows), frosted-glass panel, real lock glyph + dynamic bell/bell-slash DND
  chip + keyboard icon+word chip (new cc_icons_16 FontAwesome font), per-chip confirmation
  toasts, press-grow clip fix, and a V4-portrait status-bar BLE/clock overlap fix.
- LilyGo T-Pager: map tile cache falls back to the microSD under the launcher (fixes the
  "storage error / reflash the tiles partition" dead-end).

Co-Authored-By: Tesso Codemonkey <798026+codemonkeybr@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:30:05 +02:00

164 lines
6.0 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
#if defined(HELTEC_LORA_V4_R8)
#include "LGFXDisplay.h"
#include <Arduino.h>
#ifndef LGFX_INVERT_COLOR
#define LGFX_INVERT_COLOR true // default: black bg (ST7789 INVON)
#endif
LGFXDisplay::LGFXDisplay(RefCountedDigitalPin* peripher_power)
: DisplayDriver(240, 320),
_periph_power(peripher_power)
{
_isOn = false;
_color = 0xFFFF;
// ---- SPI bus config ----
// SPI2_HOST (FSPI), already begun by SPI.begin() in radio_init(). The micro-SD
// slot reuses this same bus (bus_shared=true releases it between transactions).
{
auto cfg = _bus.config();
cfg.spi_host = SPI2_HOST;
cfg.spi_mode = 0;
cfg.freq_write = 20000000; // 20 MHz — conservative for this PCB
cfg.freq_read = 16000000;
cfg.spi_3wire = false;
cfg.use_lock = true;
cfg.dma_channel = SPI_DMA_CH_AUTO;
cfg.pin_sclk = PIN_TFT_SCL; // 16
cfg.pin_mosi = PIN_TFT_SDA; // 15
// MISO=45 MUST be routed here even though the ST7789 panel is write-only: the
// shared micro-SD (CS=3) READS its data back over this same pin. LovyanGFX owns
// SPI2 (it runs first in setup — the display renders fine), so if LGFX leaves
// MISO unrouted (-1), the later Arduino SPI.begin() can't retrofit it onto the
// already-initialised bus and every SD read returns nothing. That is exactly the
// tester signature: a known-good FAT32 card "wants formatting" (FAT can't be read)
// and f_mkfs hangs (writes go out on MOSI, the read-back verify never lands).
// readable=false in the panel config still keeps LGFX from ever reading the panel.
cfg.pin_miso = PIN_TFT_MISO; // 45 — needed for shared micro-SD reads
cfg.pin_dc = PIN_TFT_DC; // 48
_bus.config(cfg);
_panel.setBus(&_bus);
}
// ---- ST7789 panel config ----
{
auto cfg = _panel.config();
cfg.pin_cs = PIN_TFT_CS; // 47
cfg.pin_rst = PIN_TFT_RST; // 21
cfg.pin_busy = -1;
cfg.panel_width = 240;
cfg.panel_height = 320;
cfg.offset_x = 0;
cfg.offset_y = 0;
cfg.offset_rotation = 0;
cfg.readable = false;
cfg.invert = LGFX_INVERT_COLOR; // configurable via -D LGFX_INVERT_COLOR
cfg.rgb_order = false;
cfg.dlen_16bit = false;
cfg.bus_shared = true;
_panel.config(cfg);
}
// ---- Backlight ----
{
auto cfg = _light.config();
cfg.pin_bl = PIN_TFT_LEDA_CTL; // 44
cfg.invert = false;
cfg.freq = 44000;
cfg.pwm_channel = 7;
_light.config(cfg);
_panel.setLight(&_light);
}
_lcd.setPanel(&_panel);
}
bool LGFXDisplay::begin() {
if (_isOn) return true;
if (_periph_power) _periph_power->claim();
_lcd.init();
_lcd.setSwapBytes(true); // LVGL pixels are LE; ST7789 needs BE
// PORTRAIT as-inited (240x320) — the V4-shared UI contract: portrait never calls
// setDisplayRotation() ("portrait leaves the panel as inited"), landscape calls it with 1/3.
// The old hardcoded setRotation(1) here put the panel in landscape under portrait frames —
// the tester's "boot logo in the wrong orientation".
_lcd.setRotation(0);
_lcd.setBrightness(255);
_lcd.fillScreen(0x0000);
setLogicalSize(_lcd.width(), _lcd.height());
_isOn = true;
Serial.printf("[TFT] LGFXDisplay %dx%d rotation=0 (portrait)\n", _lcd.width(), _lcd.height());
return true;
}
void LGFXDisplay::turnOn() {
if (_isOn) return;
if (_periph_power) _periph_power->claim();
_lcd.setBrightness(255);
_isOn = true;
}
void LGFXDisplay::turnOff() {
if (!_isOn) return;
_lcd.setBrightness(0);
_isOn = false;
if (_periph_power) _periph_power->release();
}
void LGFXDisplay::clear() { _lcd.fillScreen(0x0000); }
void LGFXDisplay::startFrame(Color) { _lcd.fillScreen(0x0000); }
void LGFXDisplay::setTextSize(int sz) { _lcd.setTextSize(sz); }
static inline uint16_t _c16(DisplayDriver::Color c) {
switch (c) {
case DisplayDriver::DARK: return 0x0000;
case DisplayDriver::LIGHT: return 0xFFFF;
case DisplayDriver::RED: return 0xF800;
case DisplayDriver::GREEN: return 0x07E0;
case DisplayDriver::BLUE: return 0x001F;
case DisplayDriver::YELLOW: return 0xFFE0;
case DisplayDriver::ORANGE: return 0xFD20;
default: return 0xFFFF;
}
}
void LGFXDisplay::setColor(Color c) {
_color = _c16(c);
_lcd.setTextColor(_color);
}
void LGFXDisplay::setCursor(int x, int y) { _lcd.setCursor(x, y); }
void LGFXDisplay::print(const char* s) { _lcd.print(s); }
void LGFXDisplay::fillRect(int x,int y,int w,int h) { _lcd.fillRect(x,y,w,h,_color); }
void LGFXDisplay::drawRect(int x,int y,int w,int h) { _lcd.drawRect(x,y,w,h,_color); }
void LGFXDisplay::drawXbm(int x,int y,const uint8_t* b,int w,int h) { _lcd.drawXBitmap(x,y,b,w,h,_color); }
uint16_t LGFXDisplay::getTextWidth(const char* s) { return _lcd.textWidth(s); }
void LGFXDisplay::endFrame() {}
void LGFXDisplay::writePixelsRGB565(int x, int y, int w, int h, const uint16_t* pixels) {
if (!_isOn || !pixels || w <= 0 || h <= 0) return;
_lcd.startWrite();
_lcd.setAddrWindow(x, y, w, h);
_lcd.writePixels(const_cast<uint16_t*>(pixels), (uint32_t)(w * h));
_lcd.endWrite();
}
// UI contract (see UITask applyRotation): called with 1 for ROT_90, 3 for ROT_270; portrait
// stays as-inited (rotation 0). Honor the argument — the old hardcoded 1 ignored it.
void LGFXDisplay::setDisplayRotation(uint8_t r) {
_lcd.setRotation(r & 3);
setLogicalSize(_lcd.width(), _lcd.height());
}
// Anti-burn-in panel sleep over LGFX's own SPI2 bus (SLPIN stops the oscillator/
// booster/LC drive; panel RAM is retained so wake = SLPOUT and the old frame is
// back with no redraw). Using LGFX's bus — not a second HSPI SPIClass on the same
// pins — is what keeps the shared FSPI display bus intact across a wake.
void LGFXDisplay::panelSleep(bool sleep) {
if (sleep) _lcd.sleep(); // SLPIN
else _lcd.wakeup(); // SLPOUT
}
#endif // HELTEC_LORA_V4_R8