mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-15 07:51:19 +10:00
* fix(display): don't leak a TFT driver instance on every screen wake On variants whose Screen::handleSetOn() re-runs ui->init() on each display wake (Heltec Tracker V1.x/V2, VTFT_LEDA and ST7796 boards, MUZI/Cardputer via dispdev->init()), OLEDDisplay::init() unconditionally re-invokes connect(), and TFTDisplay::connect() allocated a fresh driver instance each time without freeing the old one. Each OFF->ON transition orphaned a full LGFX device (measured: exactly 1,260 bytes per wake on a Heltec Wireless Tracker V2). Since PowerFSM wakes the screen on every received text message, a device on a busy channel leaked tens of KB per day - matching field reports of heap climbing from 78% to 92% within a day. Null-guard the driver allocations (matching the existing linePixelBuffer/ repaintChunkBuffer guards below them) so re-entry re-runs tft->init() on the existing instance. Bench-validated on a Tracker V2: free heap now flat across 13 consecutive wake cycles, with the panel still re-initializing and rendering on every wake. * fix(ble): don't leak BluetoothPhoneAPI and callbacks on BLE re-enable NimbleBluetooth::setup()/setupService() re-run when Bluetooth is re-enabled after deinit(), but BLEDevice::deinit() only frees the GATT objects - the caller-owned allocations were re-created unguarded on every cycle: - bluetoothPhoneAPI: ~3.5KB PhoneAPI object per cycle, and the orphaned instance is an OSThread that stays registered with the scheduler, so a duplicate thread kept servicing the same static queues - toRadioCallbacks / fromRadioCallbacks / security + server callbacks: one small object each per cycle - the BLESecurity shim was heap-allocated and never freed even on first boot; it only forwards to static setters, so use a stack instance Reuse the existing objects on re-setup; the setCallbacks() calls still run every time since the characteristics themselves are new. * chore: remove unused PacketCache PacketCache landed in #8341 but no consumer was ever wired up - repo-wide, the only references to PacketCache/packetCache are in its own two files. As designed it also malloc()s per cached packet with no eviction or size cap, so it should be re-reviewed for bounds before any future use. Remove the dead code; git history preserves it if a bounded revival is wanted. * fix(ble): reset stale session state when reusing BluetoothPhoneAPI Review follow-up: reusing bluetoothPhoneAPI across BLE enable cycles could hand the next session the previous session's dirty state. The only full cleanup path (onDisconnect) is skipped when deinit()'s bounded disconnect wait expires before the event lands (its 2s cap matches the connection supervision timeout, so a phone that walked out of range makes this a coin flip): the reused object then enters the next session mid-config, with stale queue contents served to the new phone and a stale connection handle that defeats the checkConnectionTimeout self-heal. Factor onDisconnect's cleanup into resetBleSessionState() and run it from setupService() when reusing the instance, restoring the old fresh-object invariant. Also switch the four stateless callback objects to function- local statics (the resolved framework BLE wrapper stores plain pointers and never frees them, so static instances are safe and avoid the guarded heap allocations), correct the comment that claimed deinit() frees the GATT objects (it deletes only the BLEServer itself; the services and characteristics it created remain a small library-side leak per cycle), and fix the inverted connection check in getRssi() that made BLE RSSI always read 0 on ESP32-S3/C6. * refactor(display): construct-once guard around the whole driver ladder Review follow-up: one if (!tft) around the #if/#elif/#else construction block instead of a guard per branch, so a future display family can't reintroduce the per-wake leak by copying an unguarded branch. Make the HACKADAY bus pointer local to the construction (it was a write-only global), and point the comment at the Screen::handleSetOn gates instead of hand-listing boards that would go stale. * fix(ble): use-after-free notifying a freed characteristic after deinit() deinit() nulled bleServer and BatteryCharacteristic but left fromNumCharacteristic dangling after BLEDevice::deinit(true) freed the GATT graph, and it never detached the PhoneAPI fromNumChanged observer. When deinit()'s bounded 2s disconnect wait expires before onDisconnect runs (the same stale-bond / host-reset race resetBleSessionState was built for), close() is skipped, so the observer stays attached with state == STATE_SEND_PACKETS. After BLE is off, the next mesh packet drives MeshService fromNumChanged -> PhoneAPI::onNotify -> onNowHasData -> fromNumCharacteristic->notify() on freed memory (the framework BLE wrapper's notify() dereferences the freed server via getConnectedCount). Unlike sendLog(), onNowHasData() had no isConnected guard. deinit() now calls resetBleSessionState() to detach the observer and reset session state unconditionally (also forcing the conn handle to NONE so checkConnectionTimeout can't be fooled by the stale handle), nulls fromNumCharacteristic/logRadioCharacteristic like the other freed pointers, and onNowHasData() bails on a null characteristic. Reachable on all ESP32/S3/C6 NimBLE boards via admin disable-bluetooth or a sleep transition while a phone is connected. Found by a follow-up lifecycle audit of the re-enable changes in this PR.
1677 lines
66 KiB
C++
1677 lines
66 KiB
C++
#include "configuration.h"
|
||
#include "main.h"
|
||
#include "memory/MemAudit.h"
|
||
#if USE_TFTDISPLAY
|
||
|
||
#if ARCH_PORTDUINO
|
||
#include "platform/portduino/PortduinoGlue.h"
|
||
#endif
|
||
|
||
#ifndef TFT_BACKLIGHT_ON
|
||
#define TFT_BACKLIGHT_ON HIGH
|
||
#endif
|
||
|
||
#ifdef GPIO_EXTENDER
|
||
#include <SparkFunSX1509.h>
|
||
#include <Wire.h>
|
||
extern SX1509 gpioExtender;
|
||
#endif
|
||
|
||
#if defined(ST7735S)
|
||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||
|
||
#ifndef TFT_INVERT
|
||
#define TFT_INVERT true
|
||
#endif
|
||
|
||
class LGFX : public lgfx::LGFX_Device
|
||
{
|
||
lgfx::Panel_ST7735S _panel_instance;
|
||
lgfx::Bus_SPI _bus_instance;
|
||
lgfx::Light_PWM _light_instance;
|
||
|
||
public:
|
||
LGFX(void)
|
||
{
|
||
{
|
||
auto cfg = _bus_instance.config();
|
||
|
||
// configure SPI
|
||
cfg.spi_host = ST7735_SPI_HOST; // ESP32-S2,S3,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
|
||
cfg.spi_mode = 0;
|
||
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
|
||
// 80MHz by an integer)
|
||
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
|
||
cfg.spi_3wire = false; // Set to true if reception is done on the MOSI pin
|
||
cfg.use_lock = true; // Set to true to use transaction locking
|
||
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
|
||
// SPI_DMA_CH_AUTO=auto setting)
|
||
cfg.pin_sclk = ST7735_SCK; // Set SPI SCLK pin number
|
||
cfg.pin_mosi = ST7735_SDA; // Set SPI MOSI pin number
|
||
cfg.pin_miso = ST7735_MISO; // Set SPI MISO pin number (-1 = disable)
|
||
cfg.pin_dc = ST7735_RS; // Set SPI DC pin number (-1 = disable)
|
||
|
||
_bus_instance.config(cfg); // applies the set value to the bus.
|
||
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
|
||
}
|
||
|
||
{ // Set the display panel control.
|
||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||
|
||
cfg.pin_cs = ST7735_CS; // Pin number where CS is connected (-1 = disable)
|
||
cfg.pin_rst = ST7735_RESET; // Pin number where RST is connected (-1 = disable)
|
||
cfg.pin_busy = ST7735_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||
|
||
// The following setting values are general initial values for each panel, so please comment out any
|
||
// unknown items and try them.
|
||
|
||
cfg.panel_width = TFT_WIDTH; // actual displayable width
|
||
cfg.panel_height = TFT_HEIGHT; // actual displayable height
|
||
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
|
||
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
|
||
cfg.offset_rotation = 0; // Rotation direction value offset 0~7 (4~7 is upside down)
|
||
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
|
||
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
|
||
cfg.readable = true; // Set to true if data can be read
|
||
cfg.invert = TFT_INVERT; // Set to true if the light/darkness of the panel is reversed
|
||
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
|
||
cfg.dlen_16bit =
|
||
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
|
||
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
|
||
|
||
// Set the following only when the display is shifted with a driver with a variable number of pixels, such as the
|
||
// ST7735 or ILI9163.
|
||
cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
|
||
cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
|
||
_panel_instance.config(cfg);
|
||
}
|
||
|
||
#ifdef TFT_BL
|
||
// Set the backlight control
|
||
{
|
||
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
|
||
|
||
cfg.pin_bl = TFT_BL; // Pin number to which the backlight is connected
|
||
cfg.invert = true; // true to invert the brightness of the backlight
|
||
// cfg.freq = 44100; // PWM frequency of backlight
|
||
// cfg.pwm_channel = 1; // PWM channel number to use
|
||
|
||
_light_instance.config(cfg);
|
||
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
|
||
}
|
||
#endif
|
||
|
||
setPanel(&_panel_instance);
|
||
}
|
||
};
|
||
|
||
static LGFX *tft = nullptr;
|
||
|
||
#elif defined(RAK14014)
|
||
#include <RAK14014_FT6336U.h>
|
||
#include <TFT_eSPI.h>
|
||
TFT_eSPI *tft = nullptr;
|
||
FT6336U ft6336u;
|
||
|
||
static uint8_t _rak14014_touch_int = false; // TP interrupt generation flag.
|
||
static void rak14014_tpIntHandle(void)
|
||
{
|
||
_rak14014_touch_int = true;
|
||
}
|
||
|
||
#elif defined(HACKADAY_COMMUNICATOR)
|
||
#include <Arduino_GFX_Library.h>
|
||
Arduino_GFX *tft = nullptr;
|
||
|
||
#elif defined(ST72xx_DE)
|
||
#include <LovyanGFX.hpp>
|
||
#include <TCA9534.h>
|
||
#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp>
|
||
#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp>
|
||
TCA9534 ioex;
|
||
|
||
class LGFX : public lgfx::LGFX_Device
|
||
{
|
||
lgfx::Bus_RGB _bus_instance;
|
||
lgfx::Panel_RGB _panel_instance;
|
||
lgfx::Touch_GT911 _touch_instance;
|
||
|
||
public:
|
||
const uint16_t screenWidth = TFT_WIDTH;
|
||
const uint16_t screenHeight = TFT_HEIGHT;
|
||
|
||
bool init_impl(bool use_reset, bool use_clear) override
|
||
{
|
||
ioex.attach(Wire);
|
||
ioex.setDeviceAddress(0x18);
|
||
ioex.config(1, TCA9534::Config::OUT);
|
||
ioex.config(2, TCA9534::Config::OUT);
|
||
ioex.config(3, TCA9534::Config::OUT);
|
||
ioex.config(4, TCA9534::Config::OUT);
|
||
|
||
ioex.output(1, TCA9534::Level::H);
|
||
ioex.output(3, TCA9534::Level::L);
|
||
ioex.output(4, TCA9534::Level::H);
|
||
|
||
pinMode(1, OUTPUT);
|
||
digitalWrite(1, LOW);
|
||
ioex.output(2, TCA9534::Level::L);
|
||
delay(20);
|
||
ioex.output(2, TCA9534::Level::H);
|
||
delay(100);
|
||
pinMode(1, INPUT);
|
||
|
||
return LGFX_Device::init_impl(use_reset, use_clear);
|
||
}
|
||
|
||
LGFX(void)
|
||
{
|
||
{
|
||
auto cfg = _panel_instance.config();
|
||
|
||
cfg.memory_width = screenWidth;
|
||
cfg.memory_height = screenHeight;
|
||
cfg.panel_width = screenWidth;
|
||
cfg.panel_height = screenHeight;
|
||
cfg.offset_x = 0;
|
||
cfg.offset_y = 0;
|
||
cfg.offset_rotation = 0;
|
||
_panel_instance.config(cfg);
|
||
}
|
||
|
||
{
|
||
auto cfg = _panel_instance.config_detail();
|
||
cfg.use_psram = 0;
|
||
_panel_instance.config_detail(cfg);
|
||
}
|
||
|
||
{
|
||
auto cfg = _bus_instance.config();
|
||
cfg.panel = &_panel_instance;
|
||
cfg.pin_d0 = ST72xx_B0; // B0
|
||
cfg.pin_d1 = ST72xx_B1; // B1
|
||
cfg.pin_d2 = ST72xx_B2; // B2
|
||
cfg.pin_d3 = ST72xx_B3; // B3
|
||
cfg.pin_d4 = ST72xx_B4; // B4
|
||
cfg.pin_d5 = ST72xx_G0; // G0
|
||
cfg.pin_d6 = ST72xx_G1; // G1
|
||
cfg.pin_d7 = ST72xx_G2; // G2
|
||
cfg.pin_d8 = ST72xx_G3; // G3
|
||
cfg.pin_d9 = ST72xx_G4; // G4
|
||
cfg.pin_d10 = ST72xx_G5; // G5
|
||
cfg.pin_d11 = ST72xx_R0; // R0
|
||
cfg.pin_d12 = ST72xx_R1; // R1
|
||
cfg.pin_d13 = ST72xx_R2; // R2
|
||
cfg.pin_d14 = ST72xx_R3; // R3
|
||
cfg.pin_d15 = ST72xx_R4; // R4
|
||
|
||
cfg.pin_henable = ST72xx_DE;
|
||
cfg.pin_vsync = ST72xx_VSYNC;
|
||
cfg.pin_hsync = ST72xx_HSYNC;
|
||
cfg.pin_pclk = ST72xx_PCLK;
|
||
cfg.freq_write = 13000000;
|
||
|
||
#ifdef ST7265_HSYNC_POLARITY
|
||
cfg.hsync_polarity = ST7265_HSYNC_POLARITY;
|
||
cfg.hsync_front_porch = ST7265_HSYNC_FRONT_PORCH; // 8;
|
||
cfg.hsync_pulse_width = ST7265_HSYNC_PULSE_WIDTH; // 4;
|
||
cfg.hsync_back_porch = ST7265_HSYNC_BACK_PORCH; // 8;
|
||
|
||
cfg.vsync_polarity = ST7265_VSYNC_POLARITY; // 0;
|
||
cfg.vsync_front_porch = ST7265_VSYNC_FRONT_PORCH; // 8;
|
||
cfg.vsync_pulse_width = ST7265_VSYNC_PULSE_WIDTH; // 4;
|
||
cfg.vsync_back_porch = ST7265_VSYNC_BACK_PORCH; // 8;
|
||
|
||
cfg.pclk_idle_high = 1;
|
||
cfg.pclk_active_neg = ST7265_PCLK_ACTIVE_NEG; // 0;
|
||
// cfg.pclk_idle_high = 0;
|
||
// cfg.de_idle_high = 1;
|
||
#endif
|
||
|
||
#ifdef ST7262_HSYNC_POLARITY
|
||
cfg.hsync_polarity = ST7262_HSYNC_POLARITY;
|
||
cfg.hsync_front_porch = ST7262_HSYNC_FRONT_PORCH; // 8;
|
||
cfg.hsync_pulse_width = ST7262_HSYNC_PULSE_WIDTH; // 4;
|
||
cfg.hsync_back_porch = ST7262_HSYNC_BACK_PORCH; // 8;
|
||
|
||
cfg.vsync_polarity = ST7262_VSYNC_POLARITY; // 0;
|
||
cfg.vsync_front_porch = ST7262_VSYNC_FRONT_PORCH; // 8;
|
||
cfg.vsync_pulse_width = ST7262_VSYNC_PULSE_WIDTH; // 4;
|
||
cfg.vsync_back_porch = ST7262_VSYNC_BACK_PORCH; // 8;
|
||
|
||
cfg.pclk_idle_high = 1;
|
||
cfg.pclk_active_neg = ST7262_PCLK_ACTIVE_NEG; // 0;
|
||
// cfg.pclk_idle_high = 0;
|
||
// cfg.de_idle_high = 1;
|
||
#endif
|
||
|
||
#ifdef SC7277_HSYNC_POLARITY
|
||
cfg.hsync_polarity = SC7277_HSYNC_POLARITY;
|
||
cfg.hsync_front_porch = SC7277_HSYNC_FRONT_PORCH; // 8;
|
||
cfg.hsync_pulse_width = SC7277_HSYNC_PULSE_WIDTH; // 4;
|
||
cfg.hsync_back_porch = SC7277_HSYNC_BACK_PORCH; // 8;
|
||
|
||
cfg.vsync_polarity = SC7277_VSYNC_POLARITY; // 0;
|
||
cfg.vsync_front_porch = SC7277_VSYNC_FRONT_PORCH; // 8;
|
||
cfg.vsync_pulse_width = SC7277_VSYNC_PULSE_WIDTH; // 4;
|
||
cfg.vsync_back_porch = SC7277_VSYNC_BACK_PORCH; // 8;
|
||
|
||
cfg.pclk_idle_high = 1;
|
||
cfg.pclk_active_neg = SC7277_PCLK_ACTIVE_NEG; // 0;
|
||
// cfg.pclk_idle_high = 0;
|
||
// cfg.de_idle_high = 1;
|
||
#endif
|
||
|
||
_bus_instance.config(cfg);
|
||
}
|
||
_panel_instance.setBus(&_bus_instance);
|
||
|
||
{
|
||
auto cfg = _touch_instance.config();
|
||
cfg.x_min = 0;
|
||
cfg.x_max = TFT_WIDTH;
|
||
cfg.y_min = 0;
|
||
cfg.y_max = TFT_HEIGHT;
|
||
cfg.pin_int = -1;
|
||
cfg.pin_rst = -1;
|
||
cfg.bus_shared = true;
|
||
cfg.offset_rotation = 0;
|
||
|
||
cfg.i2c_port = 0;
|
||
cfg.i2c_addr = 0x5D;
|
||
cfg.pin_sda = I2C_SDA;
|
||
cfg.pin_scl = I2C_SCL;
|
||
cfg.freq = 400000;
|
||
_touch_instance.config(cfg);
|
||
_panel_instance.setTouch(&_touch_instance);
|
||
}
|
||
|
||
setPanel(&_panel_instance);
|
||
}
|
||
};
|
||
|
||
static LGFX *tft = nullptr;
|
||
|
||
#elif defined(ILI9488_CS)
|
||
#include <LovyanGFX.hpp> // Graphics and font library for ILI9488 driver chip
|
||
|
||
class LGFX : public lgfx::LGFX_Device
|
||
{
|
||
lgfx::Panel_ILI9488 _panel_instance;
|
||
lgfx::Bus_SPI _bus_instance;
|
||
lgfx::Light_PWM _light_instance;
|
||
lgfx::Touch_GT911 _touch_instance;
|
||
|
||
public:
|
||
LGFX(void)
|
||
{
|
||
{
|
||
auto cfg = _bus_instance.config();
|
||
|
||
// configure SPI
|
||
cfg.spi_host = ILI9488_SPI_HOST; // ESP32-S2,S3,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
|
||
cfg.spi_mode = 0;
|
||
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
|
||
// 80MHz by an integer)
|
||
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
|
||
cfg.spi_3wire = false; // Set to true if reception is done on the MOSI pin
|
||
cfg.use_lock = true; // Set to true to use transaction locking
|
||
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
|
||
// SPI_DMA_CH_AUTO=auto setting)
|
||
cfg.pin_sclk = ILI9488_SCK; // Set SPI SCLK pin number
|
||
cfg.pin_mosi = ILI9488_SDA; // Set SPI MOSI pin number
|
||
cfg.pin_miso = ILI9488_MISO; // Set SPI MISO pin number (-1 = disable)
|
||
cfg.pin_dc = ILI9488_RS; // Set SPI DC pin number (-1 = disable)
|
||
|
||
_bus_instance.config(cfg); // applies the set value to the bus.
|
||
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
|
||
}
|
||
|
||
{ // Set the display panel control.
|
||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||
|
||
cfg.pin_cs = ILI9488_CS; // Pin number where CS is connected (-1 = disable)
|
||
cfg.pin_rst = -1; // Pin number where RST is connected (-1 = disable)
|
||
cfg.pin_busy = -1; // Pin number where BUSY is connected (-1 = disable)
|
||
|
||
// The following setting values are general initial values for each panel, so please comment out any
|
||
// unknown items and try them.
|
||
|
||
cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
|
||
cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
|
||
cfg.panel_width = TFT_WIDTH; // actual displayable width
|
||
cfg.panel_height = TFT_HEIGHT; // actual displayable height
|
||
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
|
||
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
|
||
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
||
#ifdef TFT_DUMMY_READ_PIXELS
|
||
cfg.dummy_read_pixel = TFT_DUMMY_READ_PIXELS; // Number of bits for dummy read before pixel readout
|
||
#else
|
||
cfg.dummy_read_pixel = 9; // Number of bits for dummy read before pixel readout
|
||
#endif
|
||
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
|
||
cfg.readable = true; // Set to true if data can be read
|
||
cfg.invert = true; // Set to true if the light/darkness of the panel is reversed
|
||
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
|
||
cfg.dlen_16bit =
|
||
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
|
||
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
|
||
|
||
// Set the following only when the display is shifted with a driver with a variable number of pixels, such as the
|
||
// ST7735 or ILI9163.
|
||
// cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
|
||
// cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
|
||
_panel_instance.config(cfg);
|
||
}
|
||
|
||
#ifdef ILI9488_BL
|
||
// Set the backlight control
|
||
{
|
||
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
|
||
|
||
cfg.pin_bl = ILI9488_BL; // Pin number to which the backlight is connected
|
||
cfg.invert = false; // true to invert the brightness of the backlight
|
||
// cfg.freq = 44100; // PWM frequency of backlight
|
||
// cfg.pwm_channel = 1; // PWM channel number to use
|
||
|
||
_light_instance.config(cfg);
|
||
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
|
||
}
|
||
#endif
|
||
|
||
#if HAS_TOUCHSCREEN
|
||
// Configure settings for touch screen control.
|
||
{
|
||
auto cfg = _touch_instance.config();
|
||
|
||
cfg.pin_cs = -1;
|
||
cfg.x_min = 0;
|
||
cfg.x_max = TFT_HEIGHT - 1;
|
||
cfg.y_min = 0;
|
||
cfg.y_max = TFT_WIDTH - 1;
|
||
cfg.pin_int = SCREEN_TOUCH_INT;
|
||
#ifdef SCREEN_TOUCH_RST
|
||
cfg.pin_rst = SCREEN_TOUCH_RST;
|
||
#endif
|
||
cfg.bus_shared = true;
|
||
cfg.offset_rotation = TFT_OFFSET_ROTATION;
|
||
// cfg.freq = 2500000;
|
||
|
||
// I2C
|
||
cfg.i2c_port = TOUCH_I2C_PORT;
|
||
cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
|
||
#ifdef SCREEN_TOUCH_USE_I2C1
|
||
cfg.pin_sda = I2C_SDA1;
|
||
cfg.pin_scl = I2C_SCL1;
|
||
#else
|
||
cfg.pin_sda = I2C_SDA;
|
||
cfg.pin_scl = I2C_SCL;
|
||
#endif
|
||
// cfg.freq = 400000;
|
||
|
||
_touch_instance.config(cfg);
|
||
_panel_instance.setTouch(&_touch_instance);
|
||
}
|
||
#endif
|
||
|
||
setPanel(&_panel_instance);
|
||
}
|
||
};
|
||
|
||
static LGFX *tft = nullptr;
|
||
|
||
#elif defined(ST7789_CS)
|
||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||
#if defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT)
|
||
#include "chsc6x.h"
|
||
#include "lgfx/v1/Touch.hpp"
|
||
namespace lgfx
|
||
{
|
||
inline namespace v1
|
||
{
|
||
class TOUCH_CHSC6X : public ITouch
|
||
{
|
||
public:
|
||
TOUCH_CHSC6X(void)
|
||
{
|
||
_cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
|
||
_cfg.x_min = 0;
|
||
_cfg.x_max = 240;
|
||
_cfg.y_min = 0;
|
||
_cfg.y_max = 320;
|
||
};
|
||
|
||
bool init(void) override
|
||
{
|
||
if (chsc6xTouch == nullptr) {
|
||
#if (TOUCH_I2C_PORT == 1)
|
||
chsc6xTouch = new chsc6x(&Wire1, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN);
|
||
#else
|
||
chsc6xTouch = new chsc6x(&Wire, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN);
|
||
#endif
|
||
}
|
||
chsc6xTouch->chsc6x_init();
|
||
return true;
|
||
};
|
||
|
||
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override
|
||
{
|
||
uint16_t raw_x, raw_y;
|
||
if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y) == 0) {
|
||
tp[0].x = 320 - 1 - raw_y;
|
||
tp[0].y = 240 - 1 - raw_x;
|
||
tp[0].size = 1;
|
||
tp[0].id = 1;
|
||
return 1;
|
||
}
|
||
tp[0].size = 0;
|
||
return 0;
|
||
};
|
||
|
||
void wakeup(void) override{};
|
||
void sleep(void) override{};
|
||
|
||
private:
|
||
chsc6x *chsc6xTouch = nullptr;
|
||
};
|
||
} // namespace v1
|
||
} // namespace lgfx
|
||
#endif
|
||
class LGFX : public lgfx::LGFX_Device
|
||
{
|
||
lgfx::Panel_ST7789 _panel_instance;
|
||
lgfx::Bus_SPI _bus_instance;
|
||
lgfx::Light_PWM _light_instance;
|
||
#if HAS_TOUCHSCREEN
|
||
#if defined(T_WATCH_S3) || defined(ELECROW)
|
||
lgfx::Touch_FT5x06 _touch_instance;
|
||
#elif defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT)
|
||
lgfx::TOUCH_CHSC6X _touch_instance;
|
||
#else
|
||
lgfx::Touch_GT911 _touch_instance;
|
||
#endif
|
||
#endif
|
||
|
||
public:
|
||
LGFX(void)
|
||
{
|
||
{
|
||
auto cfg = _bus_instance.config();
|
||
|
||
// SPI
|
||
cfg.spi_host = ST7789_SPI_HOST;
|
||
cfg.spi_mode = 0;
|
||
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
|
||
// 80MHz by an integer)
|
||
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
|
||
#ifdef SPI_3_WIRE
|
||
cfg.spi_3wire = SPI_3_WIRE;
|
||
#else
|
||
cfg.spi_3wire = true; // Set to true if reception is done on the MOSI pin
|
||
#endif
|
||
cfg.use_lock = true; // Set to true to use transaction locking
|
||
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
|
||
// SPI_DMA_CH_AUTO=auto setting)
|
||
cfg.pin_sclk = ST7789_SCK; // Set SPI SCLK pin number
|
||
cfg.pin_mosi = ST7789_SDA; // Set SPI MOSI pin number
|
||
cfg.pin_miso = ST7789_MISO; // Set SPI MISO pin number (-1 = disable)
|
||
cfg.pin_dc = ST7789_RS; // Set SPI DC pin number (-1 = disable)
|
||
|
||
_bus_instance.config(cfg); // applies the set value to the bus.
|
||
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
|
||
}
|
||
|
||
{ // Set the display panel control.
|
||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||
|
||
cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
|
||
cfg.pin_rst = ST7789_RESET; // Pin number where RST is connected (-1 = disable)
|
||
cfg.pin_busy = ST7789_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||
|
||
// The following setting values are general initial values for each panel, so please comment out any
|
||
// unknown items and try them.
|
||
#if defined(T_WATCH_S3)
|
||
cfg.panel_width = 240;
|
||
cfg.panel_height = 240;
|
||
cfg.memory_width = 240;
|
||
cfg.memory_height = 320;
|
||
cfg.offset_x = 0;
|
||
cfg.offset_y = 0; // No vertical shift needed - panel is top-aligned
|
||
cfg.offset_rotation = 2; // Rotate 180° to correct upside-down layout
|
||
#else
|
||
cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
|
||
cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
|
||
cfg.panel_width = TFT_WIDTH; // actual displayable width
|
||
cfg.panel_height = TFT_HEIGHT; // actual displayable height
|
||
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
|
||
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
|
||
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
||
#endif
|
||
#ifdef TFT_DUMMY_READ_PIXELS
|
||
cfg.dummy_read_pixel = TFT_DUMMY_READ_PIXELS; // Number of bits for dummy read before pixel readout
|
||
#else
|
||
cfg.dummy_read_pixel = 9; // Number of bits for dummy read before pixel readout
|
||
#endif
|
||
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
|
||
cfg.readable = true; // Set to true if data can be read
|
||
cfg.invert = true; // Set to true if the light/darkness of the panel is reversed
|
||
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
|
||
cfg.dlen_16bit =
|
||
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
|
||
#if defined(HAS_SDCARD)
|
||
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
|
||
#else
|
||
cfg.bus_shared = false;
|
||
#endif
|
||
// Set the following only when the display is shifted with a driver with a variable number of pixels, such as the
|
||
// ST7735 or ILI9163.
|
||
// cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
|
||
// cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
|
||
_panel_instance.config(cfg);
|
||
}
|
||
|
||
#ifdef ST7789_BL
|
||
// Set the backlight control. (delete if not necessary)
|
||
{
|
||
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
|
||
|
||
cfg.pin_bl = ST7789_BL; // Pin number to which the backlight is connected
|
||
cfg.invert = false; // true to invert the brightness of the backlight
|
||
// cfg.pwm_channel = 0;
|
||
|
||
_light_instance.config(cfg);
|
||
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
|
||
}
|
||
#endif
|
||
|
||
#if HAS_TOUCHSCREEN
|
||
// Configure settings for touch screen control.
|
||
{
|
||
auto cfg = _touch_instance.config();
|
||
|
||
cfg.pin_cs = -1;
|
||
cfg.x_min = 0;
|
||
cfg.x_max = TFT_HEIGHT - 1;
|
||
cfg.y_min = 0;
|
||
cfg.y_max = TFT_WIDTH - 1;
|
||
cfg.pin_int = SCREEN_TOUCH_INT;
|
||
#ifdef SCREEN_TOUCH_RST
|
||
cfg.pin_rst = SCREEN_TOUCH_RST;
|
||
#endif
|
||
cfg.bus_shared = true;
|
||
cfg.offset_rotation = TFT_OFFSET_ROTATION;
|
||
// cfg.freq = 2500000;
|
||
|
||
// I2C
|
||
cfg.i2c_port = TOUCH_I2C_PORT;
|
||
cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
|
||
#ifdef SCREEN_TOUCH_USE_I2C1
|
||
cfg.pin_sda = I2C_SDA1;
|
||
cfg.pin_scl = I2C_SCL1;
|
||
#else
|
||
cfg.pin_sda = I2C_SDA;
|
||
cfg.pin_scl = I2C_SCL;
|
||
#endif
|
||
// cfg.freq = 400000;
|
||
|
||
_touch_instance.config(cfg);
|
||
_panel_instance.setTouch(&_touch_instance);
|
||
}
|
||
#endif
|
||
|
||
setPanel(&_panel_instance); // Sets the panel to use.
|
||
}
|
||
};
|
||
|
||
static LGFX *tft = nullptr;
|
||
|
||
#elif defined(ST7796_CS)
|
||
#include <LovyanGFX.hpp> // Graphics and font library for ST7796 driver chip
|
||
|
||
class LGFX : public lgfx::LGFX_Device
|
||
{
|
||
lgfx::Panel_ST7796 _panel_instance;
|
||
lgfx::Bus_SPI _bus_instance;
|
||
lgfx::Light_PWM _light_instance;
|
||
|
||
public:
|
||
LGFX(void)
|
||
{
|
||
{
|
||
auto cfg = _bus_instance.config();
|
||
|
||
// SPI
|
||
cfg.spi_host = ST7796_SPI_HOST;
|
||
cfg.spi_mode = 0;
|
||
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
|
||
// 80MHz by an integer)
|
||
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
|
||
cfg.spi_3wire = false;
|
||
cfg.use_lock = true; // Set to true to use transaction locking
|
||
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
|
||
// SPI_DMA_CH_AUTO=auto setting)
|
||
cfg.pin_sclk = ST7796_SCK; // Set SPI SCLK pin number
|
||
cfg.pin_mosi = ST7796_SDA; // Set SPI MOSI pin number
|
||
cfg.pin_miso = ST7796_MISO; // Set SPI MISO pin number (-1 = disable)
|
||
cfg.pin_dc = ST7796_RS; // Set SPI DC pin number (-1 = disable)
|
||
|
||
_bus_instance.config(cfg); // applies the set value to the bus.
|
||
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
|
||
}
|
||
|
||
{ // Set the display panel control.
|
||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||
|
||
cfg.pin_cs = ST7796_CS; // Pin number where CS is connected (-1 = disable)
|
||
cfg.pin_rst = ST7796_RESET; // Pin number where RST is connected (-1 = disable)
|
||
cfg.pin_busy = ST7796_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||
|
||
// cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
|
||
// cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
|
||
cfg.panel_width = TFT_WIDTH; // actual displayable width
|
||
cfg.panel_height = TFT_HEIGHT; // actual displayable height
|
||
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
|
||
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
|
||
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
||
#ifdef TFT_DUMMY_READ_PIXELS
|
||
cfg.dummy_read_pixel = TFT_DUMMY_READ_PIXELS; // Number of bits for dummy read before pixel readout
|
||
#else
|
||
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
|
||
#endif
|
||
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
|
||
cfg.readable = true; // Set to true if data can be read
|
||
cfg.invert = true; // Set to true if the light/darkness of the panel is reversed
|
||
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
|
||
cfg.dlen_16bit =
|
||
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
|
||
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
|
||
|
||
_panel_instance.config(cfg);
|
||
}
|
||
|
||
#ifdef ST7796_BL
|
||
// Set the backlight control. (delete if not necessary)
|
||
{
|
||
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
|
||
|
||
cfg.pin_bl = ST7796_BL; // Pin number to which the backlight is connected
|
||
cfg.invert = false; // true to invert the brightness of the backlight
|
||
cfg.freq = 44100;
|
||
cfg.pwm_channel = 7;
|
||
|
||
_light_instance.config(cfg);
|
||
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
|
||
}
|
||
#endif
|
||
|
||
setPanel(&_panel_instance); // Sets the panel to use.
|
||
}
|
||
};
|
||
|
||
static LGFX *tft = nullptr;
|
||
|
||
#elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER)
|
||
|
||
#include <LovyanGFX.hpp> // Graphics and font library for ILI9341/ILI9342 driver chip
|
||
|
||
#if defined(ILI9341_BACKLIGHT_EN) && !defined(TFT_BL)
|
||
#define TFT_BL ILI9341_BACKLIGHT_EN
|
||
#endif
|
||
|
||
class LGFX : public lgfx::LGFX_Device
|
||
{
|
||
#if defined(ILI9341_DRIVER)
|
||
lgfx::Panel_ILI9341 _panel_instance;
|
||
#elif defined(ILI9342_DRIVER)
|
||
lgfx::Panel_ILI9342 _panel_instance;
|
||
#endif
|
||
lgfx::Bus_SPI _bus_instance;
|
||
lgfx::Light_PWM _light_instance;
|
||
|
||
public:
|
||
LGFX(void)
|
||
{
|
||
{
|
||
auto cfg = _bus_instance.config();
|
||
|
||
// configure SPI
|
||
#if defined(ILI9341_DRIVER)
|
||
cfg.spi_host = ILI9341_SPI_HOST; // ESP32-S2,S3,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
|
||
#elif defined(ILI9342_DRIVER)
|
||
cfg.spi_host = ILI9342_SPI_HOST; // ESP32-S2,S3,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
|
||
#endif
|
||
cfg.spi_mode = 0;
|
||
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
|
||
// 80MHz by an integer)
|
||
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
|
||
cfg.spi_3wire = false; // Set to true if reception is done on the MOSI pin
|
||
cfg.use_lock = true; // Set to true to use transaction locking
|
||
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
|
||
// SPI_DMA_CH_AUTO=auto setting)
|
||
cfg.pin_sclk = TFT_SCLK; // Set SPI SCLK pin number
|
||
cfg.pin_mosi = TFT_MOSI; // Set SPI MOSI pin number
|
||
cfg.pin_miso = TFT_MISO; // Set SPI MISO pin number (-1 = disable)
|
||
cfg.pin_dc = TFT_DC; // Set SPI DC pin number (-1 = disable)
|
||
|
||
_bus_instance.config(cfg); // applies the set value to the bus.
|
||
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
|
||
}
|
||
|
||
{ // Set the display panel control.
|
||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||
|
||
cfg.pin_cs = TFT_CS; // Pin number where CS is connected (-1 = disable)
|
||
cfg.pin_rst = TFT_RST; // Pin number where RST is connected (-1 = disable)
|
||
cfg.pin_busy = TFT_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||
|
||
// The following setting values are general initial values for each panel, so please comment out any
|
||
// unknown items and try them.
|
||
|
||
cfg.panel_width = TFT_WIDTH; // actual displayable width
|
||
cfg.panel_height = TFT_HEIGHT; // actual displayable height
|
||
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
|
||
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
|
||
cfg.offset_rotation = 0; // Rotation direction value offset 0~7 (4~7 is upside down)
|
||
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
|
||
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
|
||
cfg.readable = true; // Set to true if data can be read
|
||
cfg.invert = false; // Set to true if the light/darkness of the panel is reversed
|
||
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
|
||
cfg.dlen_16bit =
|
||
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
|
||
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
|
||
|
||
// Set the following only when the display is shifted with a driver with a variable number of pixels, such as the
|
||
// ST7735 or ILI9163.
|
||
cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
|
||
cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
|
||
_panel_instance.config(cfg);
|
||
}
|
||
|
||
#ifdef TFT_BL
|
||
// Set the backlight control
|
||
{
|
||
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
|
||
|
||
cfg.pin_bl = TFT_BL; // Pin number to which the backlight is connected
|
||
cfg.invert = false; // true to invert the brightness of the backlight
|
||
// cfg.freq = 44100; // PWM frequency of backlight
|
||
// cfg.pwm_channel = 1; // PWM channel number to use
|
||
|
||
_light_instance.config(cfg);
|
||
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
|
||
}
|
||
#endif
|
||
|
||
setPanel(&_panel_instance);
|
||
}
|
||
};
|
||
|
||
static LGFX *tft = nullptr;
|
||
|
||
#elif defined(ST7735_CS)
|
||
#include <TFT_eSPI.h> // Graphics and font library for ILI9342 driver chip
|
||
|
||
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
|
||
#elif ARCH_PORTDUINO
|
||
#include "Panel_sdl.hpp"
|
||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||
|
||
class LGFX : public lgfx::LGFX_Device
|
||
{
|
||
lgfx::Bus_SPI _bus_instance;
|
||
|
||
lgfx::ITouch *_touch_instance;
|
||
|
||
public:
|
||
lgfx::Panel_Device *_panel_instance;
|
||
|
||
LGFX(void)
|
||
{
|
||
if (portduino_config.displayPanel == st7789)
|
||
_panel_instance = new lgfx::Panel_ST7789;
|
||
else if (portduino_config.displayPanel == st7735)
|
||
_panel_instance = new lgfx::Panel_ST7735;
|
||
else if (portduino_config.displayPanel == st7735s)
|
||
_panel_instance = new lgfx::Panel_ST7735S;
|
||
else if (portduino_config.displayPanel == st7796)
|
||
_panel_instance = new lgfx::Panel_ST7796;
|
||
else if (portduino_config.displayPanel == ili9341)
|
||
_panel_instance = new lgfx::Panel_ILI9341;
|
||
else if (portduino_config.displayPanel == ili9342)
|
||
_panel_instance = new lgfx::Panel_ILI9342;
|
||
else if (portduino_config.displayPanel == ili9488)
|
||
_panel_instance = new lgfx::Panel_ILI9488;
|
||
else if (portduino_config.displayPanel == hx8357d)
|
||
_panel_instance = new lgfx::Panel_HX8357D;
|
||
#if defined(SDL_h_)
|
||
|
||
else if (portduino_config.displayPanel == x11)
|
||
_panel_instance = new lgfx::Panel_sdl;
|
||
#endif
|
||
else {
|
||
_panel_instance = new lgfx::Panel_NULL;
|
||
LOG_ERROR("Unknown display panel configured!");
|
||
}
|
||
|
||
auto buscfg = _bus_instance.config();
|
||
buscfg.spi_mode = 0;
|
||
buscfg.spi_host = portduino_config.display_spi_dev_int;
|
||
|
||
buscfg.pin_dc = portduino_config.displayDC.pin; // Set SPI DC pin number (-1 = disable)
|
||
|
||
_bus_instance.config(buscfg); // applies the set value to the bus.
|
||
if (portduino_config.displayPanel != x11)
|
||
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
|
||
|
||
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
|
||
LOG_DEBUG("Width: %d, Height: %d", portduino_config.displayWidth, portduino_config.displayHeight);
|
||
cfg.pin_cs = portduino_config.displayCS.pin; // Pin number where CS is connected (-1 = disable)
|
||
cfg.pin_rst = portduino_config.displayReset.pin;
|
||
if (portduino_config.displayRotate) {
|
||
cfg.panel_width = portduino_config.displayHeight; // actual displayable width
|
||
cfg.panel_height = portduino_config.displayWidth; // actual displayable height
|
||
} else {
|
||
cfg.panel_width = portduino_config.displayWidth; // actual displayable width
|
||
cfg.panel_height = portduino_config.displayHeight; // actual displayable height
|
||
}
|
||
cfg.offset_x = portduino_config.displayOffsetX; // Panel offset amount in X direction
|
||
cfg.offset_y = portduino_config.displayOffsetY; // Panel offset amount in Y direction
|
||
cfg.offset_rotation = portduino_config.displayOffsetRotate; // Rotation direction value offset 0~7 (4~7 is mirrored)
|
||
cfg.invert = portduino_config.displayInvert; // Set to true if the light/darkness of the panel is reversed
|
||
|
||
_panel_instance->config(cfg);
|
||
|
||
// Configure settings for touch control.
|
||
if (portduino_config.touchscreenModule) {
|
||
if (portduino_config.touchscreenModule == xpt2046) {
|
||
_touch_instance = new lgfx::Touch_XPT2046;
|
||
} else if (portduino_config.touchscreenModule == stmpe610) {
|
||
_touch_instance = new lgfx::Touch_STMPE610;
|
||
} else if (portduino_config.touchscreenModule == ft5x06) {
|
||
_touch_instance = new lgfx::Touch_FT5x06;
|
||
}
|
||
auto touch_cfg = _touch_instance->config();
|
||
|
||
touch_cfg.pin_cs = portduino_config.touchscreenCS.pin;
|
||
touch_cfg.x_min = 0;
|
||
touch_cfg.x_max = portduino_config.displayHeight - 1;
|
||
touch_cfg.y_min = 0;
|
||
touch_cfg.y_max = portduino_config.displayWidth - 1;
|
||
touch_cfg.pin_int = portduino_config.touchscreenIRQ.pin;
|
||
touch_cfg.bus_shared = true;
|
||
touch_cfg.offset_rotation = portduino_config.touchscreenRotate;
|
||
if (portduino_config.touchscreenI2CAddr != -1) {
|
||
touch_cfg.i2c_addr = portduino_config.touchscreenI2CAddr;
|
||
} else {
|
||
touch_cfg.spi_host = portduino_config.touchscreen_spi_dev_int;
|
||
}
|
||
|
||
_touch_instance->config(touch_cfg);
|
||
_panel_instance->setTouch(_touch_instance);
|
||
}
|
||
#if defined(SDL_h_)
|
||
if (portduino_config.displayPanel == x11) {
|
||
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
|
||
sdl_panel_->setup();
|
||
sdl_panel_->addKeyCodeMapping(SDLK_RETURN, SDL_SCANCODE_KP_ENTER);
|
||
}
|
||
#endif
|
||
setPanel(_panel_instance); // Sets the panel to use.
|
||
}
|
||
};
|
||
|
||
static LGFX *tft = nullptr;
|
||
|
||
#elif defined(HX8357_CS)
|
||
#include <LovyanGFX.hpp> // Graphics and font library for HX8357 driver chip
|
||
|
||
class LGFX : public lgfx::LGFX_Device
|
||
{
|
||
lgfx::Panel_HX8357D _panel_instance;
|
||
lgfx::Bus_SPI _bus_instance;
|
||
#if defined(USE_XPT2046)
|
||
lgfx::Touch_XPT2046 _touch_instance;
|
||
#endif
|
||
|
||
public:
|
||
LGFX(void)
|
||
{
|
||
// Panel_HX8357D
|
||
{
|
||
// configure SPI
|
||
auto cfg = _bus_instance.config();
|
||
|
||
cfg.spi_host = HX8357_SPI_HOST;
|
||
cfg.spi_mode = 0;
|
||
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
|
||
// 80MHz by an integer)
|
||
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
|
||
cfg.spi_3wire = false; // Set to true if reception is done on the MOSI pin
|
||
cfg.use_lock = true; // Set to true to use transaction locking
|
||
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
|
||
// SPI_DMA_CH_AUTO=auto setting)
|
||
cfg.pin_sclk = HX8357_SCK; // Set SPI SCLK pin number
|
||
cfg.pin_mosi = HX8357_MOSI; // Set SPI MOSI pin number
|
||
cfg.pin_miso = HX8357_MISO; // Set SPI MISO pin number (-1 = disable)
|
||
cfg.pin_dc = HX8357_RS; // Set SPI DC pin number (-1 = disable)
|
||
|
||
_bus_instance.config(cfg); // applies the set value to the bus.
|
||
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
|
||
}
|
||
{
|
||
// Set the display panel control.
|
||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||
|
||
cfg.pin_cs = HX8357_CS; // Pin number where CS is connected (-1 = disable)
|
||
cfg.pin_rst = HX8357_RESET; // Pin number where RST is connected (-1 = disable)
|
||
cfg.pin_busy = HX8357_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||
|
||
cfg.panel_width = TFT_WIDTH; // actual displayable width
|
||
cfg.panel_height = TFT_HEIGHT; // actual displayable height
|
||
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
|
||
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
|
||
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is upside down)
|
||
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
|
||
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
|
||
cfg.readable = true; // Set to true if data can be read
|
||
cfg.invert = TFT_INVERT; // Set to true if the light/darkness of the panel is reversed
|
||
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
|
||
cfg.dlen_16bit = false;
|
||
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
|
||
|
||
_panel_instance.config(cfg);
|
||
}
|
||
#if defined(USE_XPT2046)
|
||
{
|
||
// Configure settings for touch control.
|
||
auto touch_cfg = _touch_instance.config();
|
||
|
||
touch_cfg.pin_cs = TOUCH_CS;
|
||
touch_cfg.x_min = 0;
|
||
touch_cfg.x_max = TFT_HEIGHT - 1;
|
||
touch_cfg.y_min = 0;
|
||
touch_cfg.y_max = TFT_WIDTH - 1;
|
||
touch_cfg.pin_int = -1;
|
||
touch_cfg.bus_shared = true;
|
||
touch_cfg.offset_rotation = 1;
|
||
|
||
_touch_instance.config(touch_cfg);
|
||
_panel_instance.setTouch(&_touch_instance);
|
||
}
|
||
#endif
|
||
setPanel(&_panel_instance);
|
||
}
|
||
};
|
||
|
||
static LGFX *tft = nullptr;
|
||
|
||
#elif defined(ST7701_CS)
|
||
#include <LovyanGFX.hpp> // Graphics and font library for ST7701 driver chip
|
||
#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp>
|
||
#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp>
|
||
|
||
class PanelInit_ST7701 : public lgfx::Panel_ST7701
|
||
{
|
||
public:
|
||
const uint8_t *getInitCommands(uint8_t listno) const override
|
||
{
|
||
// 180 degree hw rotation: vertical flip, horizontal flip
|
||
static constexpr const uint8_t list1[] = {0x36, 1, 0x10, // MADCTL for vertical flip
|
||
0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x10, // Command2 BK0 SEL
|
||
0xC7, 1, 0x04, // SDIR: X-direction Control (Horizontal Flip)
|
||
0xFF, 5, 0x77, 0x01, 0x00, 0x00, 0x00, // Command2 BK0 DIS
|
||
0xFF, 0xFF};
|
||
switch (listno) {
|
||
case 1:
|
||
return list1;
|
||
default:
|
||
return lgfx::Panel_ST7701::getInitCommands(listno);
|
||
}
|
||
}
|
||
};
|
||
|
||
class LGFX : public lgfx::LGFX_Device
|
||
{
|
||
PanelInit_ST7701 _panel_instance;
|
||
lgfx::Bus_RGB _bus_instance;
|
||
lgfx::Light_PWM _light_instance;
|
||
lgfx::Touch_FT5x06 _touch_instance;
|
||
|
||
public:
|
||
LGFX(void)
|
||
{
|
||
{
|
||
auto cfg = _panel_instance.config();
|
||
cfg.memory_width = 800;
|
||
cfg.memory_height = 480;
|
||
cfg.panel_width = TFT_WIDTH;
|
||
cfg.panel_height = TFT_HEIGHT;
|
||
cfg.offset_x = TFT_OFFSET_X;
|
||
cfg.offset_y = TFT_OFFSET_Y;
|
||
_panel_instance.config(cfg);
|
||
}
|
||
|
||
{
|
||
auto cfg = _panel_instance.config_detail();
|
||
cfg.pin_cs = ST7701_CS;
|
||
cfg.pin_sclk = ST7701_SCK;
|
||
cfg.pin_mosi = ST7701_SDA;
|
||
// cfg.use_psram = 1;
|
||
_panel_instance.config_detail(cfg);
|
||
}
|
||
|
||
{
|
||
auto cfg = _bus_instance.config();
|
||
cfg.panel = &_panel_instance;
|
||
#ifdef SENSECAP_INDICATOR
|
||
cfg.pin_d0 = GPIO_NUM_15; // B0
|
||
cfg.pin_d1 = GPIO_NUM_14; // B1
|
||
cfg.pin_d2 = GPIO_NUM_13; // B2
|
||
cfg.pin_d3 = GPIO_NUM_12; // B3
|
||
cfg.pin_d4 = GPIO_NUM_11; // B4
|
||
|
||
cfg.pin_d5 = GPIO_NUM_10; // G0
|
||
cfg.pin_d6 = GPIO_NUM_9; // G1
|
||
cfg.pin_d7 = GPIO_NUM_8; // G2
|
||
cfg.pin_d8 = GPIO_NUM_7; // G3
|
||
cfg.pin_d9 = GPIO_NUM_6; // G4
|
||
cfg.pin_d10 = GPIO_NUM_5; // G5
|
||
|
||
cfg.pin_d11 = GPIO_NUM_4; // R0
|
||
cfg.pin_d12 = GPIO_NUM_3; // R1
|
||
cfg.pin_d13 = GPIO_NUM_2; // R2
|
||
cfg.pin_d14 = GPIO_NUM_1; // R3
|
||
cfg.pin_d15 = GPIO_NUM_0; // R4
|
||
|
||
cfg.pin_henable = GPIO_NUM_18;
|
||
cfg.pin_vsync = GPIO_NUM_17;
|
||
cfg.pin_hsync = GPIO_NUM_16;
|
||
cfg.pin_pclk = GPIO_NUM_21;
|
||
cfg.freq_write = 12000000;
|
||
|
||
cfg.hsync_polarity = 0;
|
||
cfg.hsync_front_porch = 10;
|
||
cfg.hsync_pulse_width = 8;
|
||
cfg.hsync_back_porch = 50;
|
||
|
||
cfg.vsync_polarity = 0;
|
||
cfg.vsync_front_porch = 10;
|
||
cfg.vsync_pulse_width = 8;
|
||
cfg.vsync_back_porch = 20;
|
||
|
||
cfg.pclk_active_neg = 0;
|
||
cfg.de_idle_high = 1;
|
||
cfg.pclk_idle_high = 0;
|
||
#endif
|
||
_bus_instance.config(cfg);
|
||
}
|
||
_panel_instance.setBus(&_bus_instance);
|
||
|
||
{
|
||
auto cfg = _light_instance.config();
|
||
cfg.pin_bl = ST7701_BL;
|
||
_light_instance.config(cfg);
|
||
}
|
||
_panel_instance.light(&_light_instance);
|
||
|
||
{
|
||
auto cfg = _touch_instance.config();
|
||
cfg.pin_cs = -1;
|
||
cfg.x_min = 0;
|
||
cfg.x_max = 479;
|
||
cfg.y_min = 0;
|
||
cfg.y_max = 479;
|
||
cfg.pin_int = -1; // don't use SCREEN_TOUCH_INT;
|
||
cfg.pin_rst = SCREEN_TOUCH_RST;
|
||
cfg.bus_shared = true;
|
||
cfg.offset_rotation = TFT_OFFSET_ROTATION;
|
||
|
||
cfg.i2c_port = TOUCH_I2C_PORT;
|
||
cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
|
||
cfg.pin_sda = I2C_SDA;
|
||
cfg.pin_scl = I2C_SCL;
|
||
cfg.freq = 400000;
|
||
_touch_instance.config(cfg);
|
||
_panel_instance.setTouch(&_touch_instance);
|
||
}
|
||
|
||
setPanel(&_panel_instance);
|
||
}
|
||
};
|
||
|
||
static LGFX *tft = nullptr;
|
||
|
||
#elif defined(VARIANT_DISPLAY_DRIVER)
|
||
// Board-specific framebuffer backends (class LGFX) can livee in the
|
||
// variant files - variant_display.h (declaration) and
|
||
// variant_display.cpp (bodies) - so this shared
|
||
// file isn't inflated for a single board. It exposes the same surface TFTDisplay
|
||
// drives, so the generic `tft = new LGFX;` in connect() works.
|
||
#include "variant_display.h"
|
||
|
||
static LGFX *tft = nullptr;
|
||
|
||
#endif
|
||
#include "SPILock.h"
|
||
#include "TFTColorRegions.h"
|
||
#include "TFTDisplay.h"
|
||
#include "TFTPalette.h"
|
||
#include <SPI.h>
|
||
|
||
#ifdef UNPHONE
|
||
#include "unPhone.h"
|
||
extern unPhone unphone;
|
||
#endif
|
||
|
||
GpioPin *TFTDisplay::backlightEnable = NULL;
|
||
|
||
namespace
|
||
{
|
||
static constexpr uint8_t kFullRepaintChunkRows = 8;
|
||
|
||
static inline uint16_t getThemeDefaultOnColor()
|
||
{
|
||
return graphics::TFTPalette::White;
|
||
}
|
||
|
||
static inline uint16_t getThemeDefaultOffColor()
|
||
{
|
||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||
return graphics::getThemeBodyBg();
|
||
#else
|
||
return TFT_BLACK;
|
||
#endif
|
||
}
|
||
} // namespace
|
||
|
||
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
|
||
{
|
||
LOG_DEBUG("TFTDisplay!");
|
||
|
||
#ifdef TFT_BL
|
||
GpioPin *p = new GpioHwPin(TFT_BL);
|
||
|
||
if (!TFT_BACKLIGHT_ON) { // Need to invert the pin before hardware
|
||
auto virtPin = new GpioVirtPin();
|
||
new GpioNotTransformer(
|
||
virtPin, p); // We just leave this created object on the heap so it can stay watching virtPin and driving en_gpio
|
||
p = virtPin;
|
||
}
|
||
#else
|
||
GpioPin *p = new GpioVirtPin(); // Just simulate a pin
|
||
#endif
|
||
backlightEnable = p;
|
||
|
||
#if ARCH_PORTDUINO
|
||
if (portduino_config.displayRotate) {
|
||
setGeometry(GEOMETRY_RAWMODE, portduino_config.displayWidth, portduino_config.displayWidth);
|
||
} else {
|
||
setGeometry(GEOMETRY_RAWMODE, portduino_config.displayHeight, portduino_config.displayHeight);
|
||
}
|
||
|
||
#elif defined(SCREEN_ROTATE)
|
||
setGeometry(GEOMETRY_RAWMODE, TFT_HEIGHT, TFT_WIDTH);
|
||
#else
|
||
setGeometry(GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
|
||
#endif
|
||
}
|
||
|
||
TFTDisplay::~TFTDisplay()
|
||
{
|
||
// Clean up allocated line pixel buffer to prevent memory leak
|
||
if (linePixelBuffer != nullptr) {
|
||
free(linePixelBuffer);
|
||
linePixelBuffer = nullptr;
|
||
}
|
||
if (repaintChunkBuffer != nullptr) {
|
||
free(repaintChunkBuffer);
|
||
repaintChunkBuffer = nullptr;
|
||
}
|
||
memaudit::set("display", 0);
|
||
}
|
||
|
||
// Write the buffer to the display memory
|
||
void TFTDisplay::display(bool fromBlank)
|
||
{
|
||
concurrency::LockGuard g(spiLock);
|
||
|
||
uint32_t x, y;
|
||
uint32_t y_byteIndex;
|
||
uint8_t y_byteMask;
|
||
uint32_t x_FirstPixelUpdate;
|
||
uint32_t x_LastPixelUpdate;
|
||
bool isset, dblbuf_isset;
|
||
uint16_t colorTftWhite, colorTftBlack;
|
||
bool somethingChanged = false;
|
||
|
||
// Theme defaults for non-role pixels.
|
||
const uint16_t defaultOnColor = getThemeDefaultOnColor();
|
||
const uint16_t defaultOffColor = getThemeDefaultOffColor();
|
||
static uint16_t lastDefaultOnColor = 0;
|
||
static uint16_t lastDefaultOffColor = 0;
|
||
static bool haveLastDefaults = false;
|
||
const bool themeDefaultsChanged =
|
||
!haveLastDefaults || (defaultOnColor != lastDefaultOnColor) || (defaultOffColor != lastDefaultOffColor);
|
||
const bool forceFullRepaint = fromBlank || themeDefaultsChanged;
|
||
|
||
// If theme defaults changed, reset panel background immediately so stale pixels don't linger.
|
||
if (forceFullRepaint) {
|
||
tft->fillScreen(defaultOffColor);
|
||
}
|
||
|
||
colorTftWhite = (defaultOnColor >> 8) | ((defaultOnColor & 0xFF) << 8);
|
||
colorTftBlack = (defaultOffColor >> 8) | ((defaultOffColor & 0xFF) << 8);
|
||
|
||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||
static uint32_t lastColorFrameSignature = 0;
|
||
const bool hasColorRegions = graphics::getTFTColorRegionCount() > 0;
|
||
const uint32_t colorFrameSignature = graphics::getTFTColorFrameSignature();
|
||
const bool forceFullColorRepaint = forceFullRepaint || (colorFrameSignature != lastColorFrameSignature);
|
||
|
||
// When region roles/layout changed, color can differ even with identical monochrome glyph bits.
|
||
// Repaint full frame only for those frames, then return to diff-based updates.
|
||
if (forceFullColorRepaint) {
|
||
for (uint32_t yStart = 0; yStart < displayHeight; yStart += kFullRepaintChunkRows) {
|
||
const uint32_t rowsThisChunk = min<uint32_t>(kFullRepaintChunkRows, displayHeight - yStart);
|
||
for (uint32_t row = 0; row < rowsThisChunk; row++) {
|
||
y = yStart + row;
|
||
y_byteIndex = (y / 8) * displayWidth;
|
||
y_byteMask = (1 << (y & 7));
|
||
|
||
uint16_t *chunkRow = repaintChunkBuffer + (row * displayWidth);
|
||
|
||
// Step 1: fill the whole row with the default colors. No per-pixel
|
||
// region scan, so background pixels (the bulk of the screen) are O(1).
|
||
for (x = 0; x < displayWidth; x++) {
|
||
isset = (buffer[x + y_byteIndex] & y_byteMask) != 0;
|
||
chunkRow[x] = isset ? colorTftWhite : colorTftBlack;
|
||
}
|
||
|
||
// Step 2: overprint each region overlapping this row, applied in
|
||
// ascending index order so the highest-index region wins (matches
|
||
// resolveTFTColorPixel precedence). Only region-covered pixels are
|
||
// re-touched, so total cost is ~screen + sum of region spans.
|
||
if (hasColorRegions) {
|
||
graphics::beginTFTColorRow(static_cast<int16_t>(y));
|
||
for (uint8_t k = 0; k < graphics::tftColorRowCount; k++) {
|
||
const graphics::TFTColorRegion &r = graphics::colorRegions[graphics::tftColorRowRegions[k]];
|
||
int32_t xs = r.x > 0 ? r.x : 0;
|
||
int32_t xe = r.x + r.width;
|
||
if (xe > (int32_t)displayWidth)
|
||
xe = (int32_t)displayWidth;
|
||
for (int32_t xx = xs; xx < xe; xx++) {
|
||
isset = (buffer[xx + y_byteIndex] & y_byteMask) != 0;
|
||
chunkRow[xx] = isset ? r.onColorBe : r.offColorBe;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
#if defined(HACKADAY_COMMUNICATOR)
|
||
tft->draw16bitBeRGBBitmap(0, yStart, repaintChunkBuffer, displayWidth, rowsThisChunk);
|
||
#else
|
||
tft->pushImage(0, yStart, displayWidth, rowsThisChunk, repaintChunkBuffer);
|
||
#endif
|
||
}
|
||
|
||
memcpy(buffer_back, buffer, displayBufferSize);
|
||
lastColorFrameSignature = colorFrameSignature;
|
||
haveLastDefaults = true;
|
||
lastDefaultOnColor = defaultOnColor;
|
||
lastDefaultOffColor = defaultOffColor;
|
||
graphics::clearTFTColorRegions();
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
y = 0;
|
||
while (y < displayHeight) {
|
||
y_byteIndex = (y / 8) * displayWidth;
|
||
y_byteMask = (1 << (y & 7));
|
||
|
||
// Step 1: Do a quick scan of 8 rows together. This allows fast-forwarding over unchanged screen areas.
|
||
if (y_byteMask == 1) {
|
||
if (!forceFullRepaint) {
|
||
for (x = 0; x < displayWidth; x++) {
|
||
if (buffer[x + y_byteIndex] != buffer_back[x + y_byteIndex])
|
||
break;
|
||
}
|
||
} else {
|
||
for (x = 0; x < displayWidth; x++) {
|
||
if (buffer[x + y_byteIndex] != 0)
|
||
break;
|
||
}
|
||
}
|
||
if (x >= displayWidth) {
|
||
// No changed pixels found in these 8 rows, fast-forward to the next 8
|
||
y = y + 8;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// Step 2: Scan this row for changed span (first and last changed pixel).
|
||
uint32_t x_FirstChanged = 0;
|
||
for (x_FirstChanged = 0; x_FirstChanged < displayWidth; x_FirstChanged++) {
|
||
isset = buffer[x_FirstChanged + y_byteIndex] & y_byteMask;
|
||
|
||
if (!forceFullRepaint) {
|
||
// get src pixel in the page based ordering the OLED lib uses
|
||
dblbuf_isset = buffer_back[x_FirstChanged + y_byteIndex] & y_byteMask;
|
||
if (isset != dblbuf_isset) {
|
||
break;
|
||
}
|
||
} else if (isset) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Did we find a pixel that needs updating on this row?
|
||
if (x_FirstChanged < displayWidth) {
|
||
uint32_t x_LastChanged = displayWidth - 1;
|
||
while (x_LastChanged > x_FirstChanged) {
|
||
isset = buffer[x_LastChanged + y_byteIndex] & y_byteMask;
|
||
if (!forceFullRepaint) {
|
||
dblbuf_isset = buffer_back[x_LastChanged + y_byteIndex] & y_byteMask;
|
||
if (isset != dblbuf_isset) {
|
||
break;
|
||
}
|
||
} else if (isset) {
|
||
break;
|
||
}
|
||
x_LastChanged--;
|
||
}
|
||
|
||
// Align the first pixel for update to an even number so the total alignment of
|
||
// the data will be at 32-bit boundary, which is required by GDMA SPI transfers.
|
||
x_FirstPixelUpdate = x_FirstChanged & ~1U;
|
||
x_LastPixelUpdate = x_LastChanged | 1U;
|
||
if (x_LastPixelUpdate >= displayWidth) {
|
||
x_LastPixelUpdate = displayWidth - 1;
|
||
}
|
||
|
||
// Step 3: Copy only the changed span into the pixel line buffer.
|
||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||
if (hasColorRegions)
|
||
graphics::beginTFTColorRow(static_cast<int16_t>(y));
|
||
#endif
|
||
for (x = x_FirstPixelUpdate; x <= x_LastPixelUpdate; x++) {
|
||
isset = buffer[x + y_byteIndex] & y_byteMask;
|
||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||
if (hasColorRegions) {
|
||
linePixelBuffer[x] =
|
||
graphics::resolveTFTColorPixelRow(static_cast<int16_t>(x), isset, colorTftWhite, colorTftBlack);
|
||
} else {
|
||
linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
|
||
}
|
||
#else
|
||
linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
|
||
#endif
|
||
}
|
||
#if defined(HACKADAY_COMMUNICATOR)
|
||
tft->draw16bitBeRGBBitmap(x_FirstPixelUpdate, y, &linePixelBuffer[x_FirstPixelUpdate],
|
||
(x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1);
|
||
#else
|
||
// Step 4: Send the changed pixels on this line to the screen as a single block transfer.
|
||
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
|
||
tft->pushImage(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
|
||
&linePixelBuffer[x_FirstPixelUpdate]);
|
||
#endif
|
||
somethingChanged = true;
|
||
}
|
||
y++;
|
||
}
|
||
// Copy the Buffer to the Back Buffer
|
||
if (somethingChanged)
|
||
memcpy(buffer_back, buffer, displayBufferSize);
|
||
|
||
#if GRAPHICS_TFT_COLORING_ENABLED
|
||
lastColorFrameSignature = colorFrameSignature;
|
||
#endif
|
||
haveLastDefaults = true;
|
||
lastDefaultOnColor = defaultOnColor;
|
||
lastDefaultOffColor = defaultOffColor;
|
||
graphics::clearTFTColorRegions();
|
||
}
|
||
|
||
void TFTDisplay::sdlLoop()
|
||
{
|
||
#if defined(SDL_h_)
|
||
static int lastPressed = 0;
|
||
static int shuttingDown = false;
|
||
if (portduino_config.displayPanel == x11) {
|
||
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)tft->_panel_instance;
|
||
if (sdl_panel_->loop() && !shuttingDown) {
|
||
LOG_WARN("Window Closed!");
|
||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||
inputBroker->injectInputEvent(&event);
|
||
}
|
||
// debounce
|
||
if (lastPressed != 0 && !sdl_panel_->gpio_in(lastPressed))
|
||
return;
|
||
if (!sdl_panel_->gpio_in(37)) {
|
||
lastPressed = 37;
|
||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||
inputBroker->injectInputEvent(&event);
|
||
} else if (!sdl_panel_->gpio_in(36)) {
|
||
lastPressed = 36;
|
||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||
inputBroker->injectInputEvent(&event);
|
||
} else if (!sdl_panel_->gpio_in(38)) {
|
||
lastPressed = 38;
|
||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||
inputBroker->injectInputEvent(&event);
|
||
} else if (!sdl_panel_->gpio_in(39)) {
|
||
lastPressed = 39;
|
||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||
inputBroker->injectInputEvent(&event);
|
||
} else if (!sdl_panel_->gpio_in(SDL_SCANCODE_KP_ENTER)) {
|
||
lastPressed = SDL_SCANCODE_KP_ENTER;
|
||
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0};
|
||
inputBroker->injectInputEvent(&event);
|
||
} else {
|
||
lastPressed = 0;
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
|
||
// Send a command to the display (low level function)
|
||
void TFTDisplay::sendCommand(uint8_t com)
|
||
{
|
||
// handle display on/off directly
|
||
switch (com) {
|
||
case DISPLAYON: {
|
||
// LOG_DEBUG("Display on");
|
||
backlightEnable->set(true);
|
||
#if ARCH_PORTDUINO
|
||
display(true);
|
||
if (portduino_config.displayBacklight.pin > 0)
|
||
digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON);
|
||
#elif defined(HACKADAY_COMMUNICATOR)
|
||
tft->displayOn();
|
||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096) && \
|
||
!defined(HELTEC_MESH_NODE_T1)
|
||
tft->wakeup();
|
||
tft->powerSaveOff();
|
||
#endif
|
||
|
||
#ifdef VTFT_CTRL
|
||
digitalWrite(VTFT_CTRL, LOW);
|
||
#endif
|
||
#ifdef UNPHONE
|
||
unphone.backlight(true); // using unPhone library
|
||
#endif
|
||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
|
||
#elif !defined(M5STACK) && !defined(ST7789_CS) && \
|
||
!defined(HACKADAY_COMMUNICATOR) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
|
||
tft->setBrightness(172);
|
||
#endif
|
||
break;
|
||
}
|
||
case DISPLAYOFF: {
|
||
// LOG_DEBUG("Display off");
|
||
backlightEnable->set(false);
|
||
#if ARCH_PORTDUINO
|
||
tft->clear();
|
||
if (portduino_config.displayBacklight.pin > 0)
|
||
digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON);
|
||
#elif defined(HACKADAY_COMMUNICATOR)
|
||
tft->displayOff();
|
||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096) && \
|
||
!defined(HELTEC_MESH_NODE_T1)
|
||
tft->sleep();
|
||
tft->powerSaveOn();
|
||
#endif
|
||
|
||
#ifdef VTFT_CTRL
|
||
digitalWrite(VTFT_CTRL, HIGH);
|
||
#endif
|
||
#ifdef UNPHONE
|
||
unphone.backlight(false); // using unPhone library
|
||
#endif
|
||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
|
||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
|
||
tft->setBrightness(0);
|
||
#endif
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// Drop all other commands to device (we just update the buffer)
|
||
}
|
||
|
||
void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
|
||
{
|
||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
|
||
// todo
|
||
#elif !defined(HACKADAY_COMMUNICATOR)
|
||
tft->setBrightness(_brightness);
|
||
LOG_DEBUG("Brightness is set to value: %i ", _brightness);
|
||
#endif
|
||
}
|
||
|
||
void TFTDisplay::flipScreenVertically()
|
||
{
|
||
#if defined(T_WATCH_S3)
|
||
LOG_DEBUG("Flip TFT vertically"); // T-Watch S3 right-handed orientation
|
||
tft->setRotation(0);
|
||
#endif
|
||
}
|
||
|
||
bool TFTDisplay::hasTouch(void)
|
||
{
|
||
#ifdef RAK14014
|
||
return true;
|
||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && !defined(HELTEC_MESH_NODE_T1)
|
||
return tft->touch() != nullptr;
|
||
#else
|
||
return false;
|
||
#endif
|
||
}
|
||
|
||
bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
|
||
{
|
||
#ifdef RAK14014
|
||
if (_rak14014_touch_int) {
|
||
_rak14014_touch_int = false;
|
||
/* The X and Y axes have to be switched */
|
||
*y = ft6336u.read_touch1_x();
|
||
*x = TFT_HEIGHT - ft6336u.read_touch1_y();
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && !defined(HELTEC_MESH_NODE_T1)
|
||
return tft->getTouch(x, y);
|
||
#else
|
||
return false;
|
||
#endif
|
||
}
|
||
|
||
void TFTDisplay::setDetected(uint8_t detected)
|
||
{
|
||
(void)detected;
|
||
}
|
||
|
||
// Connect to the display
|
||
bool TFTDisplay::connect()
|
||
{
|
||
concurrency::LockGuard g(spiLock);
|
||
LOG_INFO("Do TFT init");
|
||
// connect() re-runs on every display wake on variants whose handleSetOn() re-inits
|
||
// the UI (see the gates in Screen::handleSetOn); construct the driver exactly once.
|
||
if (!tft) {
|
||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
|
||
tft = new TFT_eSPI;
|
||
#elif defined(HACKADAY_COMMUNICATOR)
|
||
Arduino_DataBus *bus =
|
||
new Arduino_ESP32SPI(TFT_DC, TFT_CS, 38 /* SCK */, 21 /* MOSI */, GFX_NOT_DEFINED /* MISO */, HSPI /* spi_num */);
|
||
tft = new Arduino_NV3007(bus, 40, 0 /* rotation */, false /* IPS */, 142 /* width */, 428 /* height */,
|
||
12 /* col offset 1 */, 0 /* row offset 1 */, 14 /* col offset 2 */, 0 /* row offset 2 */,
|
||
nv3007_279_init_operations, sizeof(nv3007_279_init_operations));
|
||
#else
|
||
tft = new LGFX;
|
||
#endif
|
||
}
|
||
|
||
backlightEnable->set(true);
|
||
LOG_INFO("Power to TFT Backlight");
|
||
|
||
#ifdef UNPHONE
|
||
unphone.backlight(true); // using unPhone library
|
||
#endif
|
||
#ifdef HACKADAY_COMMUNICATOR
|
||
bool beginStatus = tft->begin();
|
||
if (beginStatus)
|
||
LOG_DEBUG("TFT Success!");
|
||
else
|
||
LOG_ERROR("TFT Fail!");
|
||
#else
|
||
tft->init();
|
||
#endif
|
||
|
||
#if defined(M5STACK)
|
||
tft->setRotation(0);
|
||
#elif defined(RAK14014)
|
||
tft->setRotation(1);
|
||
tft->setSwapBytes(true);
|
||
// tft->fillScreen(TFT_BLACK);
|
||
ft6336u.begin();
|
||
pinMode(SCREEN_TOUCH_INT, INPUT_PULLUP);
|
||
attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING);
|
||
#elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2) || defined(HELTEC_MESH_NODE_T096)
|
||
tft->setRotation(1); // T-Deck has the TFT in landscape
|
||
#elif defined(T_WATCH_S3)
|
||
tft->setRotation(2); // T-Watch S3 left-handed orientation
|
||
#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) || defined(T_LORA_PAGER)
|
||
tft->setRotation(0); // use config.yaml to set rotation
|
||
#else
|
||
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
|
||
#endif
|
||
tft->fillScreen(getThemeDefaultOffColor());
|
||
|
||
if (this->linePixelBuffer == NULL) {
|
||
this->linePixelBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth);
|
||
|
||
if (!this->linePixelBuffer) {
|
||
LOG_ERROR("Not enough memory to create TFT line buffer\n");
|
||
return false;
|
||
}
|
||
memaudit::add("display", sizeof(uint16_t) * displayWidth);
|
||
}
|
||
if (this->repaintChunkBuffer == NULL) {
|
||
this->repaintChunkBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth * kFullRepaintChunkRows);
|
||
|
||
if (!this->repaintChunkBuffer) {
|
||
LOG_ERROR("Not enough memory to create TFT repaint chunk buffer\n");
|
||
return false;
|
||
}
|
||
memaudit::add("display", sizeof(uint16_t) * displayWidth * kFullRepaintChunkRows);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
#endif // USE_TFTDISPLAY
|