From f27cf38046d2533cab368ff1fc525a27cf4494c9 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Tue, 5 Mar 2024 18:28:45 +1300 Subject: [PATCH 1/4] Move Wireless Paper V1.1 custom hibernate behavior to GxEPD2 Previously implemented with a macro, in meshtastic/firmware. --- src/epd/GxEPD2_213_FC1.cpp | 13 +------------ src/epd/GxEPD2_213_FC1.h | 2 +- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/epd/GxEPD2_213_FC1.cpp b/src/epd/GxEPD2_213_FC1.cpp index b724a15..31d1b1c 100644 --- a/src/epd/GxEPD2_213_FC1.cpp +++ b/src/epd/GxEPD2_213_FC1.cpp @@ -158,19 +158,10 @@ void GxEPD2_213_FC1::powerOff() _PowerOff(); } -// Put the display into an extra-low power state. Hard reset required to wake. -// Caution: will wipe display memory - problematic for fast-refresh, so not used by meshtastic +// No hibernate for this display, only power off. Preserves image memory for fast refresh. void GxEPD2_213_FC1::hibernate() { _PowerOff(); - if (_rst >= 0) - { - _writeCommand(0x07); // deep sleep mode - _writeData(0xA5); // enter deep sleep - _hibernating = true; - _configured_for_full = false; - _configured_for_fast = false; - } } void GxEPD2_213_FC1::_PowerOn() @@ -280,7 +271,6 @@ void GxEPD2_213_FC1::_Update_Full() _PowerOn(); _writeCommand(0x12); _waitWhileBusy("_Update_Full", full_refresh_time); - _PowerOff(); } void GxEPD2_213_FC1::_Update_Part() @@ -289,7 +279,6 @@ void GxEPD2_213_FC1::_Update_Part() _PowerOn(); _writeCommand(0x12); _waitWhileBusy("_Update_Part", partial_refresh_time); - _PowerOff(); } // Fast refresh waveform is unofficial (experimental?) diff --git a/src/epd/GxEPD2_213_FC1.h b/src/epd/GxEPD2_213_FC1.h index f07293d..74dafd2 100644 --- a/src/epd/GxEPD2_213_FC1.h +++ b/src/epd/GxEPD2_213_FC1.h @@ -44,7 +44,7 @@ class GxEPD2_213_FC1 : public GxEPD2_EPD void refresh(bool partial_update_mode = false); // screen refresh from controller memory to full screen void refresh(int16_t x, int16_t y, int16_t w, int16_t h); // screen refresh from controller memory, fast-refresh void powerOff(); // turns off generation of panel driving voltages, avoids screen fading over time - void hibernate(); // turns powerOff() and sets controller to deep sleep for minimum power use, ONLY if wakeable by RST (rst >= 0) + void hibernate(); // For this display, no deep sleep, only power off. Preserves image memory for fast refresh // Unimplemented for meshtastic public: From 8234d199859537768c5cd106bdc3c4901b1ece65 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Thu, 7 Mar 2024 01:51:41 +1300 Subject: [PATCH 2/4] Support async full refresh in meshtastic/firmware --- src/GxEPD2_BW.h | 19 +++++++++++++++++++ src/GxEPD2_EPD.cpp | 20 ++++++++++++++++++++ src/GxEPD2_EPD.h | 2 ++ src/epd/GxEPD2_213_FC1.cpp | 1 + 4 files changed, 42 insertions(+) diff --git a/src/GxEPD2_BW.h b/src/GxEPD2_BW.h index 4a3d6f0..44be16f 100644 --- a/src/GxEPD2_BW.h +++ b/src/GxEPD2_BW.h @@ -12,6 +12,11 @@ #ifndef _GxEPD2_BW_H_ #define _GxEPD2_BW_H_ +// This version of meshtastic/GxEPD2 has been modified (defiled) to allow nextPage() to run full refreshes asynchronously +// This macro ensures that the relevant modifications to meshtastic/firmware will only run with a correctly modified version of GxEPD2 +// Note: this async behavior is unrelated to the callback system implemented in newer versions of ZinggJM/GxEPD2 +#define HAS_EINK_ASYNCFULL + // uncomment next line to use class GFX of library GFX_Root instead of Adafruit_GFX //#include @@ -239,6 +244,16 @@ class GxEPD2_BW : public GxEPD2_GFX_BASE_CLASS _second_phase = false; } + // Transplanted from nextPage(), non-paged, full refresh + // This is the code which runs after the refresh() call + // Method is called by meshtastic/firmware, once polling GxEPD2_BW::isBusy() reports that the physical refresh is complete + void endAsyncFull() { + if (epd2.hasFastPartialUpdate) { + epd2.writeImageAgain(_buffer, 0, 0, WIDTH, HEIGHT); + } + epd2.powerOff(); + } + bool nextPage() { if (1 == _pages) @@ -258,12 +273,16 @@ class GxEPD2_BW : public GxEPD2_GFX_BASE_CLASS { epd2.writeImageForFullRefresh(_buffer, 0, 0, WIDTH, HEIGHT); epd2.refresh(false); +#if defined(USE_EINK_DYNAMICDISPLAY) // This macro defined in meshtastic/firmware + // -- meshtastic: moved to endAsyncFull() -- +#else if (epd2.hasFastPartialUpdate) { epd2.writeImageAgain(_buffer, 0, 0, WIDTH, HEIGHT); //epd2.refresh(true); // not needed } epd2.powerOff(); +#endif } return false; } diff --git a/src/GxEPD2_EPD.cpp b/src/GxEPD2_EPD.cpp index 243f400..59aba58 100644 --- a/src/GxEPD2_EPD.cpp +++ b/src/GxEPD2_EPD.cpp @@ -96,6 +96,10 @@ void GxEPD2_EPD::_reset() void GxEPD2_EPD::_waitWhileBusy(const char* comment, uint16_t busy_time) { + // For full refreshes, meshtastic/firmare will poll GxEPD2_EPD::isBusy() instead of waiting here + if (_isUpdatingFull(comment)) + return; + if (_busy >= 0) { delay(1); // add some margin to become active @@ -241,3 +245,19 @@ void GxEPD2_EPD::_endTransfer() if (_cs >= 0) digitalWrite(_cs, HIGH); _spi.endTransaction(); } + +// Polled by meshtastic/firmware, during async full-refresh +bool GxEPD2_EPD::isBusy() { + return (digitalRead(_busy) == _busy_level); +} + +// Used to skip _waitWhileBusy(), for meshtastic async +bool GxEPD2_EPD::_isUpdatingFull(const char* comment) { + // On first update, a fast-refresh might also clear screen using a full-refresh + // If meshtastic/firmware asked for fast-refresh, it will expect this full-refresh to block. So: force that to happen + if (_initial_refresh) + return false; + + // True if comment matches + return ( strcmp(comment, "_Update_Full") == 0 ); +} diff --git a/src/GxEPD2_EPD.h b/src/GxEPD2_EPD.h index 2d78ca3..704b55a 100644 --- a/src/GxEPD2_EPD.h +++ b/src/GxEPD2_EPD.h @@ -91,6 +91,7 @@ class GxEPD2_EPD { return (a > b ? a : b); }; + bool isBusy(); // Used in meshtastic/firmware, to poll after nextPage(), for async full refresh protected: void _reset(); void _waitWhileBusy(const char* comment = 0, uint16_t busy_time = 5000); @@ -104,6 +105,7 @@ class GxEPD2_EPD void _startTransfer(); void _transfer(uint8_t value); void _endTransfer(); + bool _isUpdatingFull(const char* comment); // Meshtastic: Determine if _waitWhileBusy() should be skipped (for async), by comparing the comment string.. protected: int8_t _cs, _dc, _rst, _busy, _busy_level; uint32_t _busy_timeout; diff --git a/src/epd/GxEPD2_213_FC1.cpp b/src/epd/GxEPD2_213_FC1.cpp index 31d1b1c..99fe166 100644 --- a/src/epd/GxEPD2_213_FC1.cpp +++ b/src/epd/GxEPD2_213_FC1.cpp @@ -38,6 +38,7 @@ void GxEPD2_213_FC1::clearScreen(uint8_t value) _writeScreenBuffer(0x13, value); // Clear "NEW" (red) mem _writeScreenBuffer(0x10, value); // Clear "OLD" (black) mem refresh(false); // Full refresh + _PowerOff(); _initial_write = false; _initial_refresh = false; } From f04cce20e62a337073ad57c7f35c1111177617e2 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Thu, 7 Mar 2024 22:38:42 +1300 Subject: [PATCH 3/4] fix: don't skip _waitWhileBusy for EInkDisplay base class --- src/GxEPD2_EPD.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/GxEPD2_EPD.cpp b/src/GxEPD2_EPD.cpp index 59aba58..9c4e658 100644 --- a/src/GxEPD2_EPD.cpp +++ b/src/GxEPD2_EPD.cpp @@ -96,9 +96,11 @@ void GxEPD2_EPD::_reset() void GxEPD2_EPD::_waitWhileBusy(const char* comment, uint16_t busy_time) { - // For full refreshes, meshtastic/firmare will poll GxEPD2_EPD::isBusy() instead of waiting here + // For full refreshes, meshtastic/firmare will poll GxEPD2_EPD::isBusy() instead of waiting here (for EInkDynamicDisplay only) +#if defined(USE_EINK_DYNAMICDISPLAY) if (_isUpdatingFull(comment)) return; +#endif if (_busy >= 0) { From 8aa46f2b675c339b8e6d20bfb127a6d826c7a596 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 10 Mar 2024 01:25:45 +1300 Subject: [PATCH 4/4] move initial refresh handling to meshtastic/firmware --- src/GxEPD2_EPD.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/GxEPD2_EPD.cpp b/src/GxEPD2_EPD.cpp index 9c4e658..47b798f 100644 --- a/src/GxEPD2_EPD.cpp +++ b/src/GxEPD2_EPD.cpp @@ -255,11 +255,6 @@ bool GxEPD2_EPD::isBusy() { // Used to skip _waitWhileBusy(), for meshtastic async bool GxEPD2_EPD::_isUpdatingFull(const char* comment) { - // On first update, a fast-refresh might also clear screen using a full-refresh - // If meshtastic/firmware asked for fast-refresh, it will expect this full-refresh to block. So: force that to happen - if (_initial_refresh) - return false; - // True if comment matches return ( strcmp(comment, "_Update_Full") == 0 ); }