From 762004ac9fa887e45d8aaf2bc1c72bd35a7a62c9 Mon Sep 17 00:00:00 2001 From: Norbert Walter Date: Sun, 1 Mar 2026 15:55:34 +0000 Subject: [PATCH] Implement shaddow frame buffer for TFT display --- lib/obp60task/OBP60Extensions.cpp | 35 ++++++- lib/obp60task/OBP60Extensions.h | 149 +++++++++++++++++++++++++++++- lib/obp60task/obp60task.cpp | 14 ++- 3 files changed, 187 insertions(+), 11 deletions(-) diff --git a/lib/obp60task/OBP60Extensions.cpp b/lib/obp60task/OBP60Extensions.cpp index 5c5bad6..eaec447 100644 --- a/lib/obp60task/OBP60Extensions.cpp +++ b/lib/obp60task/OBP60Extensions.cpp @@ -57,9 +57,30 @@ GxEPD2_BW & getdisplay(){r #endif #ifdef DISPLAY_ST7796 -// only instantiate; class defined in header -static LGFX display; -LGFX & getdisplay(){return display;} +// panel device + offscreen shadow framebuffer +static LGFX panelDisplay; +static LGFXCanvas shadowDisplay(&panelDisplay); +static bool shadowDisplayInitialized = false; + +LGFXCanvas & getdisplay(){return shadowDisplay;} +LGFX & getpaneldisplay(){return panelDisplay;} + +bool initDisplayShadowBuffer(){ + if (shadowDisplayInitialized) return true; + + shadowDisplay.setPsram(true); + shadowDisplay.setColorDepth(16); + shadowDisplay.setTextDatum(textdatum_t::baseline_left); + + if (shadowDisplay.createSprite(GxEPD_WIDTH, GxEPD_HEIGHT) == nullptr) { + shadowDisplayInitialized = false; + return false; + } + + shadowDisplay.fillScreen(GxEPD_BLACK); + shadowDisplayInitialized = true; + return true; +} #endif // Horter I2C moduls @@ -258,7 +279,9 @@ void deepSleep(CommonData &common){ getdisplay().setCursor(65, 175); getdisplay().print("To wake up press key and wait 5s"); displayNextPage(); // Update display contents - #ifndef DISPLAY_ST7796 + #ifdef DISPLAY_ST7796 + getpaneldisplay().powerSave(true); // Display power save + #else getdisplay().powerOff(); // Display power off #endif setPortPin(OBP_POWER_50, false); // Power off ePaper display @@ -285,7 +308,9 @@ void deepSleep(CommonData &common){ getdisplay().setCursor(65, 175); getdisplay().print("To wake up press wheel and wait 5s"); displayNextPage(); // Partial update - #ifndef DISPLAY_ST7796 + #ifdef DISPLAY_ST7796 + getpaneldisplay().powerSave(true); // Display power save + #else getdisplay().powerOff(); // Display power off #endif setPortPin(OBP_POWER_EPD, false); // Power off ePaper display diff --git a/lib/obp60task/OBP60Extensions.h b/lib/obp60task/OBP60Extensions.h index 7923c75..db86a46 100644 --- a/lib/obp60task/OBP60Extensions.h +++ b/lib/obp60task/OBP60Extensions.h @@ -268,7 +268,148 @@ private: lgfx::Panel_ST7796 _panel_instance; }; -LGFX & getdisplay(); +class LGFXCanvas : public lgfx::LGFX_Sprite { +public: + explicit LGFXCanvas(lgfx::LGFX_Device* parent = nullptr) : lgfx::LGFX_Sprite(parent) {} + + using lgfx::LGFX_Sprite::setFont; + void setFont(const GFXfont *font) { + if (font == nullptr) { + _currentAdfFont = nullptr; + lgfx::LGFX_Sprite::setFont(nullptr); + return; + } + + if (font->glyph == nullptr || font->bitmap == nullptr || font->last < font->first) { + _currentAdfFont = nullptr; + lgfx::LGFX_Sprite::setFont(nullptr); + return; + } + + const uint16_t glyphCount = static_cast(font->last - font->first + 1); + if (glyphCount == 0) { + lgfx::LGFX_Sprite::setFont(nullptr); + return; + } + + if (_adfGlyphCount != glyphCount || _adfGlyphBridge == nullptr) { + if (_adfGlyphBridge != nullptr) { + free(_adfGlyphBridge); + _adfGlyphBridge = nullptr; + _adfGlyphCount = 0; + } + _adfGlyphBridge = static_cast(malloc(sizeof(lgfx::GFXglyph) * glyphCount)); + if (_adfGlyphBridge == nullptr) { + lgfx::LGFX_Sprite::setFont(nullptr); + return; + } + _adfGlyphCount = glyphCount; + } + + for (uint16_t index = 0; index < glyphCount; ++index) { + _adfGlyphBridge[index].bitmapOffset = font->glyph[index].bitmapOffset; + _adfGlyphBridge[index].width = font->glyph[index].width; + _adfGlyphBridge[index].height = font->glyph[index].height; + _adfGlyphBridge[index].xAdvance = font->glyph[index].xAdvance; + _adfGlyphBridge[index].xOffset = font->glyph[index].xOffset; + _adfGlyphBridge[index].yOffset = font->glyph[index].yOffset; + } + + _adfFontBridge = lgfx::GFXfont( + const_cast(font->bitmap), + _adfGlyphBridge, + font->first, + font->last, + font->yAdvance + ); + _currentAdfFont = font; + lgfx::LGFX_Sprite::setFont(&_adfFontBridge); + } + + void getTextBounds(const String &txt, int16_t x, int16_t y, + int16_t *x0, int16_t *y0, + uint16_t *w, uint16_t *h) { + if (w == nullptr || h == nullptr) { + return; + } + if (_currentAdfFont == nullptr || txt.length() == 0) { + *w = textWidth(txt); + *h = fontHeight(); + if (x0) *x0 = x; + if (y0) *y0 = y - static_cast(*h); + return; + } + + const float sx = getTextSizeX(); + const float sy = getTextSizeY(); + + int32_t cursorX = x; + int32_t cursorY = y; + int32_t minX = 0; + int32_t minY = 0; + int32_t maxX = 0; + int32_t maxY = 0; + bool hasPixel = false; + + for (size_t i = 0; i < txt.length(); ++i) { + char c = txt[i]; + if (c == '\r') continue; + if (c == '\n') { + cursorX = x; + cursorY += static_cast(_currentAdfFont->yAdvance * sy); + continue; + } + if (c < _currentAdfFont->first || c > _currentAdfFont->last) { + continue; + } + + const GFXglyph* glyph = &_currentAdfFont->glyph[static_cast(c) - _currentAdfFont->first]; + const int32_t gw = static_cast(glyph->width * sx); + const int32_t gh = static_cast(glyph->height * sy); + const int32_t gx1 = cursorX + static_cast(glyph->xOffset * sx); + const int32_t gy1 = cursorY + static_cast(glyph->yOffset * sy); + + if (gw > 0 && gh > 0) { + const int32_t gx2 = gx1 + gw - 1; + const int32_t gy2 = gy1 + gh - 1; + if (!hasPixel) { + minX = gx1; minY = gy1; maxX = gx2; maxY = gy2; + hasPixel = true; + } else { + if (gx1 < minX) minX = gx1; + if (gy1 < minY) minY = gy1; + if (gx2 > maxX) maxX = gx2; + if (gy2 > maxY) maxY = gy2; + } + } + cursorX += static_cast(glyph->xAdvance * sx); + } + + if (hasPixel) { + if (x0) *x0 = static_cast(minX); + if (y0) *y0 = static_cast(minY); + *w = static_cast(maxX - minX + 1); + *h = static_cast(maxY - minY + 1); + } else { + if (x0) *x0 = x; + if (y0) *y0 = y; + *w = 0; + *h = 0; + } + } + + void setFullWindow() { /* no-op on TFT */ } + +private: + lgfx::GFXfont _adfFontBridge { nullptr, nullptr, 0, 0, 0 }; + lgfx::GFXglyph* _adfGlyphBridge = nullptr; + uint16_t _adfGlyphCount = 0; + const GFXfont* _currentAdfFont = nullptr; +}; + +LGFXCanvas & getdisplay(); +LGFX & getpaneldisplay(); +bool initDisplayShadowBuffer(); #endif // Page display return values @@ -344,7 +485,7 @@ inline void displayDrawBitmap(int16_t x, int16_t y, inline void displayFirstPage() { #ifdef DISPLAY_ST7796 - // TFT LCD doesn't need firstPage() + initDisplayShadowBuffer(); #else getdisplay().firstPage(); #endif @@ -352,7 +493,9 @@ inline void displayFirstPage() { inline void displayNextPage() { #ifdef DISPLAY_ST7796 - // TFT LCD doesn't need nextPage() for refresh + if (initDisplayShadowBuffer()) { + getdisplay().pushSprite(0, 0); + } #else getdisplay().nextPage(); #endif diff --git a/lib/obp60task/obp60task.cpp b/lib/obp60task/obp60task.cpp index d16f749..963902b 100644 --- a/lib/obp60task/obp60task.cpp +++ b/lib/obp60task/obp60task.cpp @@ -285,7 +285,9 @@ void underVoltageError(CommonData &common) { getdisplay().setCursor(65, 175); getdisplay().print("Charge battery and restart system"); displayNextPage(); // Partial update - #ifndef DISPLAY_ST7796 + #ifdef DISPLAY_ST7796 + getpaneldisplay().powerSave(true); // Display power save + #else getdisplay().powerOff(); // Display power off #endif setPortPin(OBP_POWER_EPD, false); // Power off ePaper display @@ -307,7 +309,9 @@ void underVoltageError(CommonData &common) { getdisplay().setCursor(65, 175); getdisplay().print("To wake up repower system"); displayNextPage(); // Partial update - #ifndef DISPLAY_ST7796 + #ifdef DISPLAY_ST7796 + getpaneldisplay().powerSave(true); // Display power save + #else getdisplay().powerOff(); // Display power off #endif #endif @@ -380,12 +384,16 @@ void OBP60Task(GwApi *api){ #ifdef DISPLAY_GDEY042T81 getdisplay().init(115200, true, 2, false); // Init for Waveshare boards with "clever" reset circuit, 2ms reset pulse #elif defined DISPLAY_ST7796 - getdisplay().init(); // Init for ST7796 TFT LCD + getpaneldisplay().init(); // Init for ST7796 TFT LCD panel #else getdisplay().init(115200); // Init for normal displays #endif + #ifdef DISPLAY_ST7796 + getpaneldisplay().setRotation(0); // Set display orientation (horizontal) + #else getdisplay().setRotation(0); // Set display orientation (horizontal) + #endif displaySetFullWindow(); // Set full Refresh (E-Ink only) displayFirstPage(); // set first page getdisplay().fillScreen(commonData.bgcolor);