- 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>
164 lines
6.0 KiB
C++
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
|