mirror of
https://github.com/meshtastic/firmware.git
synced 2026-10-08 22:29:03 -04:00
fix(heltec): sleep the T1 and T096 panels on screen-off to stop image retention (#11894)
* fix(heltec-t1): sleep the panel on screen-off to stop image retention The T1 is excluded from the LovyanGFX sleep()/wakeup() calls because it uses TFT_eSPI, so DISPLAYOFF only dropped the backlight and the ST7735 kept driving the last frame unlit for the whole screen-off timeout. That constant static image is what burns ghost pixels into the panel. Add opt-in TFT_SLEEP_WHEN_OFF for the TFT_eSPI path: DISPOFF + SLPIN on screen-off, SLPOUT (120 ms) + DISPON on wake, issued as raw MIPI DCS since TFT_eSPI exposes no sleep API. Frame memory survives sleep-in, so the previous frame reappears and the dirty-window diff continues unchanged. The sleep flag keeps the double displayOn() in Screen::handleSetOn() from paying the delay twice. * fix(heltec-t096): sleep the panel on screen-off to stop image retention The T096 sits behind the same TFT_eSPI exclusion as the T1, so DISPLAYOFF only dropped its backlight while the ST7735S kept driving the last frame unlit for the whole screen-off timeout. Same panel, same bus, same burn-in. Opt in to TFT_SLEEP_WHEN_OFF; the TFTDisplay side of the fix is already generic. * fix(tft): harden the TFT_SLEEP_WHEN_OFF wake/sleep sequence Drive VTFT_CTRL LOW before SLPOUT, so the rail is up before the panel is addressed. Wait out the remainder of the 120 ms the controller needs after SLPIN before sending SLPOUT, so a wake landing as the screen timeout fires is not dropped. Guard DISPLAYOFF on panelAsleep to match DISPLAYON. Correct the T1 VTFT_CTRL comment: LOW enables the rail, not HIGH. * fix(tft): wait out only what is left of the sleep-in window before SLPOUT Throttle::isWithinTimespanMs() is true for the whole 120 ms after SLPIN, so the wake path paid a fresh 120 ms on top of however much had already elapsed. A wake 119 ms after the SLPIN waited ~120 ms rather than ~1 ms, up to 119 ms of avoidable latency on every quick off/on. Add Throttle::remainingMs(), which returns what is left of the interval and saturates at 0 instead of underflowing to a ~49 day wait. It reads the clock once, so a caller that tests and then waits cannot be preempted between the two and land on that underflow - which a separate isWithinTimespanMs() plus subtraction at the call site could. heltec-mesh-node-t1 and -t096 both build; neither is board_level = pr, so CI does not compile this path. Docker native suite green, 1515/1515. * trunk --------- Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
This commit is contained in:
6 files changed
+141
-40
No files matched your search
+72
-39
@@ -350,8 +350,8 @@ class LGFX : public lgfx::LGFX_Device
|
||||
|
||||
cfg.pclk_idle_high = 1;
|
||||
cfg.pclk_active_neg = ST7265_PCLK_ACTIVE_NEG; // 0;
|
||||
// cfg.pclk_idle_high = 0;
|
||||
// cfg.de_idle_high = 1;
|
||||
// cfg.pclk_idle_high = 0;
|
||||
// cfg.de_idle_high = 1;
|
||||
#endif
|
||||
|
||||
#ifdef ST7262_HSYNC_POLARITY
|
||||
@@ -367,8 +367,8 @@ class LGFX : public lgfx::LGFX_Device
|
||||
|
||||
cfg.pclk_idle_high = 1;
|
||||
cfg.pclk_active_neg = ST7262_PCLK_ACTIVE_NEG; // 0;
|
||||
// cfg.pclk_idle_high = 0;
|
||||
// cfg.de_idle_high = 1;
|
||||
// cfg.pclk_idle_high = 0;
|
||||
// cfg.de_idle_high = 1;
|
||||
#endif
|
||||
|
||||
#ifdef SC7277_HSYNC_POLARITY
|
||||
@@ -384,8 +384,8 @@ class LGFX : public lgfx::LGFX_Device
|
||||
|
||||
cfg.pclk_idle_high = 1;
|
||||
cfg.pclk_active_neg = SC7277_PCLK_ACTIVE_NEG; // 0;
|
||||
// cfg.pclk_idle_high = 0;
|
||||
// cfg.de_idle_high = 1;
|
||||
// cfg.pclk_idle_high = 0;
|
||||
// cfg.de_idle_high = 1;
|
||||
#endif
|
||||
|
||||
_bus_instance.config(cfg);
|
||||
@@ -463,22 +463,22 @@ class LGFX : public lgfx::LGFX_Device
|
||||
// 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)
|
||||
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.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.)
|
||||
@@ -594,8 +594,8 @@ class TOUCH_CHSC6X : public ITouch
|
||||
return 0;
|
||||
};
|
||||
|
||||
void wakeup(void) override{};
|
||||
void sleep(void) override{};
|
||||
void wakeup(void) override {};
|
||||
void sleep(void) override {};
|
||||
|
||||
private:
|
||||
chsc6x *chsc6xTouch = nullptr;
|
||||
@@ -633,7 +633,7 @@ class LGFX : public lgfx::LGFX_Device
|
||||
#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
|
||||
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 /
|
||||
@@ -662,8 +662,8 @@ class LGFX : public lgfx::LGFX_Device
|
||||
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
|
||||
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
|
||||
@@ -676,14 +676,14 @@ class LGFX : public lgfx::LGFX_Device
|
||||
#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
|
||||
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.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
|
||||
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
|
||||
@@ -794,20 +794,20 @@ class LGFX : public lgfx::LGFX_Device
|
||||
|
||||
// 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)
|
||||
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.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.)
|
||||
@@ -1464,6 +1464,7 @@ static LGFX *tft = nullptr;
|
||||
#include "TFTColorRegions.h"
|
||||
#include "TFTDisplay.h"
|
||||
#include "TFTPalette.h"
|
||||
#include "mesh/Throttle.h"
|
||||
#include <SPI.h>
|
||||
|
||||
#ifdef UNPHONE
|
||||
@@ -1849,14 +1850,23 @@ void TFTDisplay::sdlLoop()
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef TFT_BLANK_ON_DISPLAY_OFF
|
||||
// LovyanGFX exposes sleep in/out but not display on/off, so send the MIPI DCS opcodes directly.
|
||||
#if defined(TFT_BLANK_ON_DISPLAY_OFF) || defined(TFT_SLEEP_WHEN_OFF)
|
||||
// Neither LovyanGFX nor TFT_eSPI exposes display on/off, so send the MIPI DCS opcodes directly.
|
||||
static constexpr uint8_t kCmdDispOff = 0x28;
|
||||
static constexpr uint8_t kCmdDispOn = 0x29;
|
||||
// Quiet time the controller needs after sleep out before it will accept the next command.
|
||||
static constexpr uint32_t kSleepOutSettleMs = 120;
|
||||
#endif
|
||||
|
||||
#ifdef TFT_SLEEP_WHEN_OFF
|
||||
// TFT_eSPI has no sleep()/wakeup() either. Frame memory survives sleep-in, so the last frame
|
||||
// reappears on sleep-out and the dirty-window diff carries on.
|
||||
static constexpr uint8_t kCmdSleepIn = 0x10;
|
||||
static constexpr uint8_t kCmdSleepOut = 0x11;
|
||||
static bool panelAsleep = false;
|
||||
static uint32_t sleepInMs = 0;
|
||||
#endif
|
||||
|
||||
// Send a command to the display (low level function)
|
||||
void TFTDisplay::sendCommand(uint8_t com)
|
||||
{
|
||||
@@ -1903,6 +1913,21 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
tft->wakeup();
|
||||
tft->powerSaveOff();
|
||||
#endif
|
||||
#elif defined(TFT_SLEEP_WHEN_OFF)
|
||||
// Screen::handleSetOn() calls displayOn() twice per wake; only the first one has work to do.
|
||||
if (panelAsleep) {
|
||||
#ifdef VTFT_CTRL
|
||||
digitalWrite(VTFT_CTRL, LOW); // rail up before the panel is addressed
|
||||
#endif
|
||||
// SLPOUT within 120 ms of SLPIN is ignored, e.g. a button press as the timeout fires.
|
||||
// Wait out what is left of that window, not a fresh 120 ms on top of it.
|
||||
if (uint32_t settleLeft = Throttle::remainingMs(sleepInMs, kSleepOutSettleMs))
|
||||
delay(settleLeft);
|
||||
tft->writecommand(kCmdSleepOut);
|
||||
delay(kSleepOutSettleMs); // datasheet minimum before the panel accepts DISPON
|
||||
tft->writecommand(kCmdDispOn);
|
||||
panelAsleep = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(TFT_NV3001B) || defined(TFT_BLANK_ON_DISPLAY_OFF)
|
||||
@@ -1955,6 +1980,14 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
tft->sleep();
|
||||
tft->powerSaveOn();
|
||||
#endif
|
||||
#elif defined(TFT_SLEEP_WHEN_OFF)
|
||||
// Without this the LCD keeps driving the last frame unlit, which is what builds image retention.
|
||||
if (!panelAsleep) {
|
||||
tft->writecommand(kCmdDispOff);
|
||||
tft->writecommand(kCmdSleepIn);
|
||||
sleepInMs = millis();
|
||||
panelAsleep = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef VTFT_CTRL
|
||||
|
||||
@@ -38,6 +38,15 @@ bool Throttle::isWithinTimespanMs(uint32_t lastExecutionMs, uint32_t timeSpanMs)
|
||||
return (now - lastExecutionMs) < timeSpanMs;
|
||||
}
|
||||
|
||||
/// @brief How much of an interval is left since a stored event, 0 once the interval has passed
|
||||
/// @param lastExecutionMs The last execution time in milliseconds
|
||||
/// @param intervalMs The interval in milliseconds
|
||||
uint32_t Throttle::remainingMs(uint32_t lastExecutionMs, uint32_t intervalMs)
|
||||
{
|
||||
uint32_t elapsed = Time::getMillis() - lastExecutionMs;
|
||||
return elapsed < intervalMs ? intervalMs - elapsed : 0;
|
||||
}
|
||||
|
||||
/// @brief Check whether an absolute deadline has arrived, correctly across the millis() wrap
|
||||
/// @param deadlineMs The deadline, as a millis() value
|
||||
/// See the header for the range limit and the sentinel requirement.
|
||||
|
||||
@@ -17,6 +17,11 @@ class Throttle
|
||||
return !isWithinTimespanMs(lastExecutionMs, intervalMs);
|
||||
}
|
||||
|
||||
/// What is left of intervalMs since lastExecutionMs, 0 once it has passed. One clock read, so a
|
||||
/// caller waiting out the remainder cannot be preempted between the test and the subtraction.
|
||||
/// Same sentinel rule as isWithinTimespanMs(): 0 is not treated as "never run".
|
||||
static uint32_t remainingMs(uint32_t lastExecutionMs, uint32_t intervalMs);
|
||||
|
||||
/// True once an absolute deadline has arrived. Use this rather than comparing against millis()
|
||||
/// directly: that inverts while the deadline sits on the far side of the 32-bit wrap, so the
|
||||
/// action either fires immediately or blocks for about the interval it should have waited.
|
||||
|
||||
@@ -55,6 +55,54 @@ void test_hasElapsed_boundary_is_inclusive()
|
||||
TEST_ASSERT_FALSE(Throttle::hasElapsed(9001, 1000)); // 999ms elapsed
|
||||
}
|
||||
|
||||
// --- remainingMs() ---
|
||||
|
||||
// The point of the helper: what is left of the window, not the whole window. A caller that waits
|
||||
// out the remainder must not pay again for time that has already gone by.
|
||||
void test_remainingMs_returns_only_what_is_left()
|
||||
{
|
||||
Time::setTestMillis(10000);
|
||||
TEST_ASSERT_EQUAL_UINT32(500, Throttle::remainingMs(9500, 1000)); // 500ms elapsed of a 1000ms window
|
||||
TEST_ASSERT_EQUAL_UINT32(1, Throttle::remainingMs(9001, 1000)); // 999ms elapsed
|
||||
TEST_ASSERT_EQUAL_UINT32(1000, Throttle::remainingMs(10000, 1000)); // nothing elapsed yet
|
||||
}
|
||||
|
||||
// Saturates at 0 rather than underflowing to a ~49 day wait, which is what a bare
|
||||
// intervalMs - elapsed would produce once the interval has passed.
|
||||
void test_remainingMs_saturates_at_zero_once_elapsed()
|
||||
{
|
||||
Time::setTestMillis(10000);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, Throttle::remainingMs(9000, 1000)); // exactly the interval
|
||||
TEST_ASSERT_EQUAL_UINT32(0, Throttle::remainingMs(8000, 1000)); // well past it
|
||||
TEST_ASSERT_EQUAL_UINT32(0, Throttle::remainingMs(0, 1000));
|
||||
}
|
||||
|
||||
// Nonzero exactly while isWithinTimespanMs() is true, so `if (remainingMs(...))` is a drop-in for
|
||||
// the predicate at a call site that then waits out the rest.
|
||||
void test_remainingMs_is_nonzero_exactly_within_the_window()
|
||||
{
|
||||
Time::setTestMillis(10000);
|
||||
const uint32_t cases[][2] = {{9500, 1000}, {8000, 1000}, {9000, 1000}, {9001, 1000}, {10000, 1}, {0, 5000}};
|
||||
for (auto &c : cases) {
|
||||
TEST_ASSERT_EQUAL(Throttle::isWithinTimespanMs(c[0], c[1]), Throttle::remainingMs(c[0], c[1]) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
void test_remainingMs_survives_millis_wrap()
|
||||
{
|
||||
const uint32_t lastRun = 0xFFFFFF00u; // 256ms before the wrap
|
||||
Time::setTestMillis(lastRun);
|
||||
|
||||
Time::advanceTestMillis(100); // still before the wrap
|
||||
TEST_ASSERT_EQUAL_UINT32(900, Throttle::remainingMs(lastRun, 1000));
|
||||
|
||||
Time::advanceTestMillis(200); // wraps to 0x0000002C - 300ms elapsed in total
|
||||
TEST_ASSERT_EQUAL_UINT32(700, Throttle::remainingMs(lastRun, 1000));
|
||||
|
||||
Time::advanceTestMillis(800); // 1100ms elapsed, past both the window and the wrap
|
||||
TEST_ASSERT_EQUAL_UINT32(0, Throttle::remainingMs(lastRun, 1000));
|
||||
}
|
||||
|
||||
// --- rollover: the headline property ---
|
||||
|
||||
// A window opened just before the 32-bit wrap must still close correctly after it.
|
||||
@@ -226,6 +274,10 @@ void setup()
|
||||
RUN_TEST(test_isWithinTimespan_boundary_is_exclusive);
|
||||
RUN_TEST(test_hasElapsed_is_complement_of_isWithinTimespan);
|
||||
RUN_TEST(test_hasElapsed_boundary_is_inclusive);
|
||||
RUN_TEST(test_remainingMs_returns_only_what_is_left);
|
||||
RUN_TEST(test_remainingMs_saturates_at_zero_once_elapsed);
|
||||
RUN_TEST(test_remainingMs_is_nonzero_exactly_within_the_window);
|
||||
RUN_TEST(test_remainingMs_survives_millis_wrap);
|
||||
RUN_TEST(test_isWithinTimespan_survives_millis_wrap);
|
||||
RUN_TEST(test_long_interval_survives_wrap);
|
||||
RUN_TEST(test_deadlinePassed_basic);
|
||||
|
||||
@@ -54,6 +54,7 @@ extern "C" {
|
||||
#define TFT_OFFSET_X 24
|
||||
#define TFT_OFFSET_Y 0
|
||||
#define TFT_INVERT false
|
||||
#define TFT_SLEEP_WHEN_OFF // sleep the panel on screen-off instead of driving it unlit
|
||||
#define SCREEN_TRANSITION_FRAMERATE 3 // fps
|
||||
#define DISPLAY_FORCE_SMALL_FONTS
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ extern "C" {
|
||||
#define ST7735_MISO -1
|
||||
#define ST7735_BUSY -1
|
||||
#define ST7735_BL (0 + 15)
|
||||
#define VTFT_CTRL (0 + 13) // Active HIGH, powers the ST7735 display
|
||||
#define VTFT_CTRL (0 + 13) // Active LOW: LOW powers the ST7735 display rail
|
||||
#define SPI_FREQUENCY 80000000
|
||||
#define SPI_READ_FREQUENCY 16000000
|
||||
#define SCREEN_ROTATE
|
||||
@@ -51,6 +51,7 @@ extern "C" {
|
||||
#define TFT_OFFSET_X 24
|
||||
#define TFT_OFFSET_Y 0
|
||||
#define TFT_INVERT false
|
||||
#define TFT_SLEEP_WHEN_OFF // sleep the panel on screen-off instead of driving it unlit
|
||||
#define DISPLAY_FORCE_SMALL_FONTS
|
||||
#define FORCE_LOW_RES 1 // 80px-wide panel causes artifacts with full-res UI elements
|
||||
|
||||
|
||||
Reference in new issue
Block a user