Keep T-Deck radio2 scanning active during screen redraws
Render into a PSRAM canvas and release the shared SPI bus between bounded TFT strips. Service only the radio driver at cooperative checkpoints and use the buffered radio HAL, preserving packet and transmit guards. Cover pixel parity, transaction boundaries, allocation failure, partial updates and sleep/wake behavior with native tests.
This commit is contained in:
1 parent
4ad1aec200
commit
a944b1779d
6 files changed
+607
-14
No files matched your search
@@ -1,6 +1,154 @@
|
||||
#include "ST7789LCDDisplay.h"
|
||||
#include "ColorTheme.h"
|
||||
|
||||
#ifdef LILYGO_TDECK
|
||||
#include <esp_heap_caps.h>
|
||||
#include <new>
|
||||
#include <string.h>
|
||||
#include <helpers/UsbLogging.h>
|
||||
|
||||
// Adafruit_GFX supplies text and bitmap rasterization without allocating its
|
||||
// own framebuffer. The 153,600-byte RGB565 frame must live in external RAM.
|
||||
class TDeckCanvas : public Adafruit_GFX {
|
||||
ST7789LCDDisplay& _owner;
|
||||
uint16_t* _pixels;
|
||||
uint16_t _pixel_work = 0;
|
||||
|
||||
void cooperate(uint16_t pixels) {
|
||||
_pixel_work += pixels;
|
||||
if (_pixel_work >= 128) {
|
||||
_pixel_work = 0;
|
||||
_owner.serviceRender();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
TDeckCanvas(ST7789LCDDisplay& owner, int w, int h)
|
||||
: Adafruit_GFX(w, h), _owner(owner),
|
||||
_pixels(static_cast<uint16_t*>(heap_caps_calloc(
|
||||
size_t(w) * h, sizeof(uint16_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT))) {
|
||||
setTextWrap(false);
|
||||
cp437(true);
|
||||
}
|
||||
~TDeckCanvas() { heap_caps_free(_pixels); }
|
||||
uint16_t* pixels() const { return _pixels; }
|
||||
|
||||
void drawPixel(int16_t x, int16_t y, uint16_t color) override {
|
||||
if (_pixels && x >= 0 && y >= 0 && x < WIDTH && y < HEIGHT)
|
||||
_pixels[size_t(y) * WIDTH + x] = color;
|
||||
cooperate(1);
|
||||
}
|
||||
void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override {
|
||||
if (!_pixels || y < 0 || y >= HEIGHT || w <= 0) return;
|
||||
const int left = x < 0 ? 0 : x;
|
||||
const int right = int(x) + w > WIDTH ? WIDTH : int(x) + w;
|
||||
if (left >= right) return;
|
||||
uint16_t* row = _pixels + size_t(y) * WIDTH;
|
||||
for (int xx = left; xx < right; ++xx) row[xx] = color;
|
||||
cooperate(right - left);
|
||||
}
|
||||
void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override {
|
||||
if (!_pixels || x < 0 || x >= WIDTH || h <= 0) return;
|
||||
const int top = y < 0 ? 0 : y;
|
||||
const int bottom = int(y) + h > HEIGHT ? HEIGHT : int(y) + h;
|
||||
for (int yy = top; yy < bottom; ++yy) {
|
||||
_pixels[size_t(yy) * WIDTH + x] = color;
|
||||
cooperate(1);
|
||||
}
|
||||
}
|
||||
void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override {
|
||||
if (w <= 0 || h <= 0) return;
|
||||
const int top = y < 0 ? 0 : y;
|
||||
const int bottom = int(y) + h > HEIGHT ? HEIGHT : int(y) + h;
|
||||
for (int yy = top; yy < bottom; ++yy) drawFastHLine(x, yy, w, color);
|
||||
}
|
||||
void fillScreen(uint16_t color) override { fillRect(0, 0, WIDTH, HEIGHT, color); }
|
||||
void writeFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override {
|
||||
drawFastHLine(x, y, w, color);
|
||||
}
|
||||
void writeFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override {
|
||||
drawFastVLine(x, y, h, color);
|
||||
}
|
||||
void writeFillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override {
|
||||
fillRect(x, y, w, h, color);
|
||||
}
|
||||
};
|
||||
|
||||
ST7789LCDDisplay::~ST7789LCDDisplay() { delete _canvas; }
|
||||
|
||||
bool ST7789LCDDisplay::prepareCanvas() {
|
||||
if (_canvas_attempted) return _canvas != nullptr;
|
||||
_canvas_attempted = true;
|
||||
_canvas = new (std::nothrow) TDeckCanvas(*this, width(), height());
|
||||
if (!_canvas || !_canvas->pixels()) {
|
||||
delete _canvas;
|
||||
_canvas = nullptr;
|
||||
mesh::usbConsolePort().println("WARN: T-Deck display PSRAM allocation failed; using direct redraw");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ST7789LCDDisplay::isBufferedRendering() const {
|
||||
return _buffered_rendering && _canvas != nullptr;
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::setBufferedRendering(bool enabled) {
|
||||
_frame_active = false;
|
||||
_buffered_rendering = enabled;
|
||||
if (enabled && _panel_ready) prepareCanvas();
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::serviceRender(bool force) {
|
||||
if (!_buffered_rendering || !_render_yield || _render_callback_active) return;
|
||||
if (!force && uint32_t(micros() - _last_render_yield_us) < 250) return;
|
||||
_render_callback_active = true;
|
||||
_render_yield();
|
||||
_render_callback_active = false;
|
||||
_last_render_yield_us = micros();
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::flushCanvas(int x, int y, int w, int h) {
|
||||
if (!isBufferedRendering() || !_isOn || w <= 0 || h <= 0) return;
|
||||
const int right = int32_t(x) + w > width() ? width() : int32_t(x) + w;
|
||||
const int bottom = int32_t(y) + h > height() ? height() : int32_t(y) + h;
|
||||
x = x < 0 ? 0 : x;
|
||||
y = y < 0 ? 0 : y;
|
||||
w = right - x;
|
||||
if (w <= 0 || y >= bottom) return;
|
||||
|
||||
// At most 960 pixels (0.384 ms at 40 MHz), plus command/driver overhead,
|
||||
// per transaction. Copy out of PSRAM so SPI never depends on DMA from it.
|
||||
constexpr size_t StripPixels = 960;
|
||||
uint16_t strip[StripPixels];
|
||||
const int rows_per_strip = StripPixels / w;
|
||||
serviceRender(true);
|
||||
while (y < bottom) {
|
||||
const int rows = bottom - y < rows_per_strip ? bottom - y : rows_per_strip;
|
||||
for (int row = 0; row < rows; ++row) {
|
||||
memcpy(strip + size_t(row) * w,
|
||||
_canvas->pixels() + size_t(y + row) * width() + x,
|
||||
size_t(w) * sizeof(uint16_t));
|
||||
}
|
||||
display.startWrite();
|
||||
display.setAddrWindow(x, y, w, rows);
|
||||
display.writePixels(strip, size_t(w) * rows, true);
|
||||
display.endWrite();
|
||||
// CS is high and the transfer is complete before the radio can use the
|
||||
// shared physical clock/data pins. No recursive application-loop call.
|
||||
serviceRender(true);
|
||||
y += rows;
|
||||
}
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::finishCanvasPrimitive(int x, int y, int w, int h) {
|
||||
serviceRender();
|
||||
// Dashboard rows can be updated without startFrame/endFrame. Preserve their
|
||||
// immediate behavior while sending only their changed rectangle in strips.
|
||||
if (!_frame_active) flushCanvas(x, y, w, h);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef PIN_TFT_MISO
|
||||
#define PIN_TFT_MISO -1
|
||||
#endif
|
||||
@@ -93,6 +241,16 @@ bool ST7789LCDDisplay::begin() {
|
||||
if (!_probe_complete) mesh::ui::heltec_r8::backlightOff();
|
||||
#endif
|
||||
if (!_isOn) {
|
||||
#ifdef LILYGO_TDECK
|
||||
// This board has no TFT reset pin or separately switched panel supply.
|
||||
// Blanking its backlight does not require a slow controller reinit on wake.
|
||||
if (_panel_ready) {
|
||||
if (_peripher_power) _peripher_power->claim();
|
||||
if (PIN_TFT_LEDA_CTL != -1) digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
_isOn = true;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef HELTEC_V4_R8_TFT
|
||||
// turnOff() leaves this panel configured and powered - its reset line is
|
||||
// shared with the touch controller, so it is never parked low - which makes
|
||||
@@ -186,6 +344,9 @@ bool ST7789LCDDisplay::begin() {
|
||||
|
||||
_panel_ready = true;
|
||||
_isOn = true;
|
||||
#ifdef LILYGO_TDECK
|
||||
if (_buffered_rendering) prepareCanvas();
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -224,15 +385,35 @@ void ST7789LCDDisplay::turnOff() {
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::clear() {
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
#if defined(LILYGO_TDECK) || defined(MESHCORE_HELTEC_R8_TFT_DETECTION)
|
||||
if (!_isOn) return;
|
||||
#endif
|
||||
#ifdef LILYGO_TDECK
|
||||
if (isBufferedRendering()) {
|
||||
if (!_isOn) return;
|
||||
_canvas->fillScreen(ST77XX_BLACK);
|
||||
finishCanvasPrimitive(0, 0, width(), height());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
display.fillScreen(ST77XX_BLACK);
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::startFrame(ColorVal bkg) {
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
#if defined(LILYGO_TDECK) || defined(MESHCORE_HELTEC_R8_TFT_DETECTION)
|
||||
if (!_isOn) return;
|
||||
#endif
|
||||
#ifdef LILYGO_TDECK
|
||||
if (isBufferedRendering()) {
|
||||
if (!_isOn) return;
|
||||
_frame_active = true;
|
||||
serviceRender(true);
|
||||
_canvas->fillScreen(bkg);
|
||||
_canvas->setTextColor(_color = UIColor::primary_txt);
|
||||
_canvas->setTextSize(1);
|
||||
_canvas->cp437(true);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
display.fillScreen(bkg);
|
||||
display.setTextColor(_color = UIColor::primary_txt);
|
||||
@@ -241,58 +422,119 @@ void ST7789LCDDisplay::startFrame(ColorVal bkg) {
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::setTextSize(int sz) {
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
#if defined(LILYGO_TDECK) || defined(MESHCORE_HELTEC_R8_TFT_DETECTION)
|
||||
if (!_isOn) return;
|
||||
#endif
|
||||
#ifdef LILYGO_TDECK
|
||||
if (isBufferedRendering()) {
|
||||
_canvas->setTextSize(sz > 0 ? sz : 1);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
display.setTextSize(sz > 0 ? sz : 1);
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::setColor(ColorVal c) {
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
#if defined(LILYGO_TDECK) || defined(MESHCORE_HELTEC_R8_TFT_DETECTION)
|
||||
if (!_isOn) return;
|
||||
#endif
|
||||
#ifdef LILYGO_TDECK
|
||||
if (isBufferedRendering()) {
|
||||
_canvas->setTextColor(_color = c);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
display.setTextColor(_color = c);
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::setCursor(int x, int y) {
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
#if defined(LILYGO_TDECK) || defined(MESHCORE_HELTEC_R8_TFT_DETECTION)
|
||||
if (!_isOn) return;
|
||||
#endif
|
||||
#ifdef LILYGO_TDECK
|
||||
if (isBufferedRendering()) {
|
||||
_canvas->setCursor(x, y);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
display.setCursor(x, y);
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::print(const char* str) {
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
#if defined(LILYGO_TDECK) || defined(MESHCORE_HELTEC_R8_TFT_DETECTION)
|
||||
if (!_isOn) return;
|
||||
#endif
|
||||
#ifdef LILYGO_TDECK
|
||||
if (isBufferedRendering()) {
|
||||
if (!_isOn) return;
|
||||
int16_t x, y;
|
||||
uint16_t w, h;
|
||||
_canvas->getTextBounds(str, _canvas->getCursorX(), _canvas->getCursorY(), &x, &y, &w, &h);
|
||||
_canvas->print(str);
|
||||
finishCanvasPrimitive(x, y, w, h);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
display.print(str);
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::fillRect(int x, int y, int w, int h) {
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
#if defined(LILYGO_TDECK) || defined(MESHCORE_HELTEC_R8_TFT_DETECTION)
|
||||
if (!_isOn) return;
|
||||
#endif
|
||||
#ifdef LILYGO_TDECK
|
||||
if (isBufferedRendering()) {
|
||||
if (!_isOn) return;
|
||||
_canvas->fillRect(x, y, w, h, _color);
|
||||
finishCanvasPrimitive(x, y, w, h);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
display.fillRect(x, y, w, h, _color);
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::drawRect(int x, int y, int w, int h) {
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
#if defined(LILYGO_TDECK) || defined(MESHCORE_HELTEC_R8_TFT_DETECTION)
|
||||
if (!_isOn) return;
|
||||
#endif
|
||||
#ifdef LILYGO_TDECK
|
||||
if (isBufferedRendering()) {
|
||||
if (!_isOn) return;
|
||||
_canvas->drawRect(x, y, w, h, _color);
|
||||
finishCanvasPrimitive(x, y, w, h);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
display.drawRect(x, y, w, h, _color);
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
#if defined(LILYGO_TDECK) || defined(MESHCORE_HELTEC_R8_TFT_DETECTION)
|
||||
if (!_isOn) return;
|
||||
#endif
|
||||
#ifdef LILYGO_TDECK
|
||||
if (isBufferedRendering()) {
|
||||
if (!_isOn) return;
|
||||
_canvas->drawBitmap(x, y, bits, w, h, _color);
|
||||
finishCanvasPrimitive(x, y, w, h);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// The common UI bitmaps are MSB-first, as expected by drawBitmap.
|
||||
display.drawBitmap(x, y, bits, w, h, _color);
|
||||
}
|
||||
|
||||
uint16_t ST7789LCDDisplay::getTextWidth(const char* str) {
|
||||
#ifdef MESHCORE_HELTEC_R8_TFT_DETECTION
|
||||
#if defined(LILYGO_TDECK) || defined(MESHCORE_HELTEC_R8_TFT_DETECTION)
|
||||
if (!_isOn) return 0;
|
||||
#endif
|
||||
#ifdef LILYGO_TDECK
|
||||
if (isBufferedRendering()) {
|
||||
int16_t x, y;
|
||||
uint16_t w, h;
|
||||
_canvas->getTextBounds(str, 0, 0, &x, &y, &w, &h);
|
||||
return w;
|
||||
}
|
||||
#endif
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
@@ -301,5 +543,15 @@ uint16_t ST7789LCDDisplay::getTextWidth(const char* str) {
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::endFrame() {
|
||||
#ifdef LILYGO_TDECK
|
||||
if (!_isOn) {
|
||||
_frame_active = false;
|
||||
return;
|
||||
}
|
||||
if (isBufferedRendering()) {
|
||||
_frame_active = false;
|
||||
flushCanvas(0, 0, width(), height());
|
||||
}
|
||||
#endif
|
||||
// display.display();
|
||||
}
|
||||
Reference in new issue
Block a user