From f9b86e6a68f9c30a62753bcfb920a5b0b158b9d3 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 4 Feb 2024 02:16:55 +1300 Subject: [PATCH 1/3] refactor: correct full_refresh_time for GxEPX2_213_BN --- src/epd/GxEPD2_213_BN.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/epd/GxEPD2_213_BN.h b/src/epd/GxEPD2_213_BN.h index b2af5d3..19ca7dc 100644 --- a/src/epd/GxEPD2_213_BN.h +++ b/src/epd/GxEPD2_213_BN.h @@ -35,7 +35,7 @@ class GxEPD2_213_BN : public GxEPD2_EPD static const bool hasFastPartialUpdate = true; static const uint16_t power_on_time = 100; // ms, e.g. 95868us static const uint16_t power_off_time = 250; // ms, e.g. 140350us - static const uint16_t full_refresh_time = 8100; // ms, e.g. 4011934us + static const uint16_t full_refresh_time = 4100; // ms, e.g. 4011934us static const uint16_t partial_refresh_time = 750; // ms, e.g. 736721us // constructor GxEPD2_213_BN(int16_t cs, int16_t dc, int16_t rst, int16_t busy, SPIClass &spi); From 1e298b822439f45f4296bef1ff4207065297b3d2 Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 3 Mar 2024 20:00:58 +1300 Subject: [PATCH 2/3] Adjust driver for Wireless Paper V1.0 Implement a missing soft-reset. Change fast refresh border waveform --- src/epd/GxEPD2_213_BN.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/epd/GxEPD2_213_BN.cpp b/src/epd/GxEPD2_213_BN.cpp index 0e20d9d..8e0bf37 100644 --- a/src/epd/GxEPD2_213_BN.cpp +++ b/src/epd/GxEPD2_213_BN.cpp @@ -351,13 +351,9 @@ void GxEPD2_213_BN::_InitDisplay() _writeData(0x00); _writeCommand(0x11); //data entry mode _writeData(0x03); - _writeCommand(0x3C); //BorderWavefrom - _writeData(0x05); _writeCommand(0x21); // Display update control _writeData(0x00); _writeData(0x80); - _writeCommand(0x18); //Read built-in temperature sensor - _writeData(0x80); _setPartialRamArea(0, 0, WIDTH, HEIGHT); _using_partial_mode = false; _init_display_done = true; @@ -385,8 +381,24 @@ const unsigned char GxEPD2_213_BN::lut_partial[] PROGMEM = 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, }; +void GxEPD2_213_BN::_Init_Full() +{ + _InitDisplay(); + _writeCommand(0x3C); // Border Waveform + _writeData(0x05); + _writeCommand(0x18); //Read built-in temperature sensor + _writeData(0x80); + _using_partial_mode = false; +} + + void GxEPD2_213_BN::_Init_Part() { + _InitDisplay(); + _writeCommand(0x3C); // Border Waveform + _writeData(0xC0); // Floating (less noise-accumulation?) + _writeCommand(0x18); //Read built-in temperature sensor + _writeData(0x80); _writeCommand(0x32); _writeDataPGM(lut_partial, sizeof(lut_partial)); _using_partial_mode = true; @@ -394,7 +406,7 @@ void GxEPD2_213_BN::_Init_Part() void GxEPD2_213_BN::_Update_Full() { - _using_partial_mode = false; + if (_using_partial_mode) _Init_Full(); _PowerOn(); _writeCommand(0x22); _writeData(0xf4); From cd0ba04c58e1a74068f7a20758b86ae5d26dd32a Mon Sep 17 00:00:00 2001 From: Todd Herbert Date: Sun, 3 Mar 2024 20:02:59 +1300 Subject: [PATCH 3/3] New code for Wireless Paper V1.1 Implement fast refresh --- src/GxEPD2.h | 4 +- src/epd/GxEPD2_213_FC1.cpp | 542 ++++++++++++++----------------------- src/epd/GxEPD2_213_FC1.h | 81 +++--- 3 files changed, 249 insertions(+), 378 deletions(-) diff --git a/src/GxEPD2.h b/src/GxEPD2.h index 668cc6f..e620bc4 100644 --- a/src/GxEPD2.h +++ b/src/GxEPD2.h @@ -44,8 +44,8 @@ class GxEPD2 GDEH0213B72, Waveshare_2_13_bw_B72 = GDEH0213B72, GDEH0213B73, Waveshare_2_13_bw_B73 = GDEH0213B73, GDEM0213B74, Waveshare_2_13_bw_B74 = GDEM0213B74, - DEPG0213BN, // Heltec Wireless Paper v1.0, backported from GxEPD v1.5.5 - GDEM0213FC1, + DEPG0213BN, // Heltec Wireless Paper v1.0, backported from GxEPD v1.5.5 + LCMEN2R13EFC1, // Heltec Wireless Paper v1.1, custom GDEW0213I5F, Waveshare_2_13_flex = GDEW0213I5F, GDEW0213M21, GDEW0213T5D, diff --git a/src/epd/GxEPD2_213_FC1.cpp b/src/epd/GxEPD2_213_FC1.cpp index 8c70fb0..b724a15 100644 --- a/src/epd/GxEPD2_213_FC1.cpp +++ b/src/epd/GxEPD2_213_FC1.cpp @@ -1,59 +1,61 @@ -// Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare. -// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! +// ################################################################## +// # Meshtastic: custom driver for Heltec Wireless Paper V1.1 # +// ################################################################## // -// based on Demo Example from Good Display, available here: http://www.e-paper-display.com/download_detail/downloadsId=806.html -// Panel: ICMEN2R13EFC1 : Heltec Wireless Paper v1.1 -// Controller : SSD1680 : https://www.good-display.com/companyfile/101.html +// From discussion: https://forum.arduino.cc/t/lcmen2r13efc1-discussion-heltec-wireless-paper/1211430 // -// Author: Jean-Marc Zingg +// Panel: LCMEN2R13EFC1 +// Controller: JD79656 (undocumented as of 2024/02/18) +// Board: Heltec Wireless Paper V1.1 // -// Version: see library.properties -// -// Library: https://github.com/ZinggJM/GxEPD2 +// --- NOTE! --- +// This display is NOT officially supported by GxEPD2. +// This fork is not affiliated with the original author. +// This code is not intended for general use. #include "GxEPD2_213_FC1.h" GxEPD2_213_FC1::GxEPD2_213_FC1(int16_t cs, int16_t dc, int16_t rst, int16_t busy, SPIClass &spi) : GxEPD2_EPD(cs, dc, rst, busy, LOW, 6000000, WIDTH, HEIGHT, panel, hasColor, hasPartialUpdate, hasFastPartialUpdate, spi) { - -} - -void GxEPD2_213_FC1::clearScreen(uint8_t value) -{ - writeScreenBuffer(value); - refresh(true); } -void GxEPD2_213_FC1::writeScreenBuffer(uint8_t value) -{ - _initial_write = false; // initial full screen buffer clean done - if (!_using_partial_mode) _Init_Part(); - _writeCommand(0x13); // set current +// Generic: clear display memory (one buffer only), using specified command +void GxEPD2_213_FC1::_writeScreenBuffer(uint8_t command, uint8_t value) { + _writeCommand(command); // set current for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++) { _writeData(value); } - _writeCommand(0x11); - _writeData(0x00); + yield(); // Allegedly: keeps ESP32 and ESP8266 WDT happy +} - _writeCommand(0x10); // preset previous - for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++) - { - _writeData(0xFF); // 0xFF is white - } - +// Clear display: memory + screen image +void GxEPD2_213_FC1::clearScreen(uint8_t value) +{ + _Wake(); + // _Init_Full(); + _writeScreenBuffer(0x13, value); // Clear "NEW" (red) mem + _writeScreenBuffer(0x10, value); // Clear "OLD" (black) mem + refresh(false); // Full refresh + _initial_write = false; + _initial_refresh = false; } -void GxEPD2_213_FC1::writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) +// Clear display's memory - no refresh +void GxEPD2_213_FC1::writeScreenBuffer(uint8_t value) { - _writeImage(0x13, bitmap, x, y, w, h, invert, mirror_y, pgm); + _Wake(); + _writeScreenBuffer(0x13, value); // Clear "NEW" (red) mem + _writeScreenBuffer(0x10, value); // Clear "OLD" (black) mem + _initial_write = false; } +// Generic: write image to memory, using specified command void GxEPD2_213_FC1::_writeImage(uint8_t command, const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { - if (_initial_write) writeScreenBuffer(); // initial full screen buffer clean - delay(1); // yield() to avoid WDT on ESP8266 and ESP32 + // Mostly redundant - controller IC does not support partial update, only fast-refresh + // Potentially still used for the "mirror" code? Untested int16_t wb = (w + 7) / 8; // width bytes, bitmaps are padded x -= x % 8; // byte boundary w = wb * 8; // byte boundary @@ -66,11 +68,10 @@ void GxEPD2_213_FC1::_writeImage(uint8_t command, const uint8_t bitmap[], int16_ w1 -= dx; h1 -= dy; + // Controller doesn't support partial refresh, so we'll only allow fullscreen images, just incase + if (w < WIDTH || h < HEIGHT) + return; - if ((w1 <= 0) || (h1 <= 0)) return; - if (!_using_partial_mode) _Init_Part(); - _writeCommand(0x91); // partial in - _setPartialRamArea(x1, y1, w1, h1); _writeCommand(command); //uint8_t extras[h1] = {0}; @@ -95,193 +96,70 @@ void GxEPD2_213_FC1::_writeImage(uint8_t command, const uint8_t bitmap[], int16_ data = bitmap[idx]; } if (invert) data = ~data; - // Here lies an attempt to save those final two lines of the screen, - // The first two bits of the byte are what are displayed on each row of the screen - // I cannot figure out why the buffer is ending like it is, I am guessing the final row is - // the co - //if (i < (h1/4)){ - // if (j == 15){ - // extras[i*4] = data; - // extras[(i*4)+1] = data << 2; - // extras[(i*4)+2] = data << 4; - // extras[(i*4)+3] = data << 6; - // _writeData(extras[i]); - // } else { - // _writeData(data); - // } - //} else { - if (j == 15){ - // _writeData(extras[i]); - _writeData(0xFF); - } else { - _writeData(data); - } - //} - } - } - - _writeCommand(0x92); // partial out - delay(1); // yield() to avoid WDT on ESP8266 and ESP32 -} - -void GxEPD2_213_FC1::writeImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, - int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) -{ - _writeImagePart(0x13, bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); -} - -void GxEPD2_213_FC1::_writeImagePart(uint8_t command, const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, - int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) -{ - if (_initial_write) writeScreenBuffer(); // initial full screen buffer clean - delay(1); // yield() to avoid WDT on ESP8266 and ESP32 - if ((w_bitmap < 0) || (h_bitmap < 0) || (w < 0) || (h < 0)) return; - if ((x_part < 0) || (x_part >= w_bitmap)) return; - if ((y_part < 0) || (y_part >= h_bitmap)) return; - int16_t wb_bitmap = (w_bitmap + 7) / 8; // width bytes, bitmaps are padded - x_part -= x_part % 8; // byte boundary - w = w_bitmap - x_part < w ? w_bitmap - x_part : w; // limit - h = h_bitmap - y_part < h ? h_bitmap - y_part : h; // limit - x -= x % 8; // byte boundary - w = 8 * ((w + 7) / 8); // byte boundary, bitmaps are padded - int16_t x1 = x < 0 ? 0 : x; // limit - int16_t y1 = y < 0 ? 0 : y; // limit - int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit - int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit - int16_t dx = x1 - x; - int16_t dy = y1 - y; - w1 -= dx; - h1 -= dy; - if ((w1 <= 0) || (h1 <= 0)) return; - if (!_using_partial_mode) _Init_Part(); - _writeCommand(0x91); // partial in - _setPartialRamArea(x1, y1, w1, h1); - _writeCommand(command); - for (int16_t i = 0; i < h1; i++) - { - for (int16_t j = 0; j < w1 / 8; j++) - { - uint8_t data; - // use wb_bitmap, h_bitmap of bitmap for index! - int16_t idx = mirror_y ? x_part / 8 + j + dx / 8 + ((h_bitmap - 1 - (y_part + i + dy))) * wb_bitmap : x_part / 8 + j + dx / 8 + (y_part + i + dy) * wb_bitmap; - if (pgm) - { -#if defined(__AVR) || defined(ESP8266) || defined(ESP32) - data = pgm_read_byte(&bitmap[idx]); -#else - data = bitmap[idx]; -#endif - } - else - { - data = bitmap[idx]; - } - if (invert) data = ~data; + _writeData(data); } - } - _writeData(0xFF); - _writeCommand(0x92); // partial out - delay(1); // yield() to avoid WDT on ESP8266 and ESP32 -} - -void GxEPD2_213_FC1::writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) -{ - if (black) - { - writeImage(black, x, y, w, h, invert, mirror_y, pgm); - } -} - -void GxEPD2_213_FC1::writeImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, - int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) -{ - if (black) - { - writeImagePart(black, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); - } -} - -void GxEPD2_213_FC1::writeNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) -{ - if (data1) - { - writeImage(data1, x, y, w, h, invert, mirror_y, pgm); - } -} - -void GxEPD2_213_FC1::drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) -{ - writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm); - refresh(x, y, w, h); -} - -void GxEPD2_213_FC1::drawImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, - int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) -{ - writeImagePart(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); - refresh(x, y, w, h); + } + yield(); // Allegedly: keeps ESP32 and ESP8266 WDT happy } -void GxEPD2_213_FC1::drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) +// Write image to "NEW" (red) memory +void GxEPD2_213_FC1::writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { - if (black) - { - drawImage(black, x, y, w, h, invert, mirror_y, pgm); - } -} + if (_initial_refresh) clearScreen(); // Screen image is unknown at startup: make sure it is clear. -void GxEPD2_213_FC1::drawImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, - int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) -{ - if (black) - { - drawImagePart(black, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); - } + _Wake(); + // _Init_Part(); + + _writeImage(0x13, bitmap, x, y, w, h, invert, mirror_y, pgm); // Run using the generic method, passing the command for write "NEW" mem } -void GxEPD2_213_FC1::drawNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) -{ - if (data1) - { - drawImage(data1, x, y, w, h, invert, mirror_y, pgm); - } +// Write image to "OLD" (black) memory. Used for determining which pixels mode during fast refresh +void GxEPD2_213_FC1::writeImageAgain(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) { + // Pass-down + _writeImage(0x10, bitmap, x, y, w, h, invert, mirror_y, pgm); // Run using the generic method, passing the command for write "OLD" mem } +// Update the display image void GxEPD2_213_FC1::refresh(bool partial_update_mode) { - if (partial_update_mode) refresh(0, 0, WIDTH, HEIGHT); + if (partial_update_mode) refresh(0, 0, WIDTH, HEIGHT); // If requested, run a fast refresh instead else { - if (_using_partial_mode) _Init_Full(); _Update_Full(); _initial_refresh = false; // initial full update done } } +// Update the display image, using fast refresh void GxEPD2_213_FC1::refresh(int16_t x, int16_t y, int16_t w, int16_t h) { if (_initial_refresh) return refresh(false); // initial update needs be full update - x -= x % 8; // byte boundary - w -= x % 8; // byte boundary - int16_t x1 = x < 0 ? 0 : x; // limit - int16_t y1 = y < 0 ? 0 : y; // limit - int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit - int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit - w1 -= x1 - x; - h1 -= y1 - y; - if (!_using_partial_mode) _Init_Part(); - _writeCommand(0x91); // partial in - _setPartialRamArea(x1, y1, w1, h1); _Update_Part(); - _writeCommand(0x92); // partial out } +// Custom reset function - less delay() +void GxEPD2_213_FC1::_reset() { + digitalWrite(_rst, LOW); + pinMode(_rst, OUTPUT); + delay(_reset_duration); + pinMode(_rst, INPUT_PULLUP); + _waitWhileBusy("_reset", 200); // "200ms" not used, actually reads busy pin + + _hibernating = false; + _configured_for_full = false; + _configured_for_fast = false; +} + +// End the update process (NOT to be confused with hibernate) +// ZinggJM notes: turns off generation of panel driving voltages, avoids screen fading over time 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 void GxEPD2_213_FC1::hibernate() { _PowerOff(); @@ -290,25 +168,11 @@ void GxEPD2_213_FC1::hibernate() _writeCommand(0x07); // deep sleep mode _writeData(0xA5); // enter deep sleep _hibernating = true; + _configured_for_full = false; + _configured_for_fast = false; } } -void GxEPD2_213_FC1::_setPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h) -{ - uint16_t xe = (x + w - 1) | 0x0007; // byte boundary inclusive (last byte) - uint16_t ye = y + h - 1; - x &= 0xFFF8; // byte boundary - _writeCommand(0x90); // partial window - _writeData(x % 256); - _writeData(xe % 256); - _writeData(y / 256); - _writeData(y % 256); - _writeData(ye / 256); - _writeData(ye % 256); - //_writeData(0x01); // don't see any difference - _writeData(0x00); // don't see any differenc -} - void GxEPD2_213_FC1::_PowerOn() { if (!_power_is_on) @@ -327,150 +191,93 @@ void GxEPD2_213_FC1::_PowerOff() _waitWhileBusy("_PowerOff", power_off_time); } _power_is_on = false; - _using_partial_mode = false; } -void GxEPD2_213_FC1::_InitDisplay() +// Common setup for both full refresh and fast refresh +void GxEPD2_213_FC1::_Wake() { - if (_hibernating) _reset(); - delay(10); // 10ms according to specs - _writeCommand(0x12); //SWRESET - delay(10); // 10ms according to specs - - _writeCommand(0x00); //panel setting - _writeData(0x1f); //LUT from OTP£¬KW-BF KWR-AF BWROTP 0f BWOTP 1f - - _writeCommand(0x4D); // Dithering? - _writeData(0x55); // 01010101 - _writeData(0x00); - _writeData(0x00); - _writeCommand(0xA9); - _writeData(0x25); // 00100101 - _writeData(0x00); - _writeData(0x00); - _writeCommand(0xF3); - _writeData(0x0A); // 00001010 - _writeData(0x00); - _writeData(0x00); - - _writeCommand(0x44); // set Ram-X address start/end position - _writeData(0x00); - _writeData(0x0F); // 0x0F-->(15+1)*8=128 - - _writeCommand(0x45); // set Ram-Y address start/end position - _writeData(0xF9); // 0xF9-->(249+1)=250 - _writeData(0x00); - _writeData(0x00); - _writeData(0x00); + // Note: no hibernation for meshtastic code, because display memory is lost (quirk of controller IC?) + if (_hibernating) + return _reset(); - _writeCommand(0x03); - _writeData(0x00); - - _writeCommand(0x3C); // BorderWavefrom - _writeData(0x01); // GS/VSS/LUT1 - - _writeCommand(0x18); // Temperature Sensor Control? - _writeData(0x80); // Internal Sensor - - _writeCommand(0x4E); // set RAM x address count to 0; - _writeData(0x00); - _writeCommand(0x4F); // set RAM y address count to 0xF9-->(249+1)=250; - _writeData(0xF9); - _writeData(0x00); + // At start-up: rescue display from any weird states + if (_initial_refresh) + return _reset(); } -// experimental partial screen update LUTs, with balanced charge option -// LUTs are filled with zeroes - -// this panel doesn't seem to need balanced charge -#define T1 0 // charge balance pre-phase -#define T2 0 // optional extension -#define T3 25 // color change phase (b/w) -#define T4 0 // optional extension for one color -#define T5 0 // white sustain phase -#define T6 0 // black sustain phase - -const unsigned char GxEPD2_213_FC1::lut_20_vcomDC_partial[] PROGMEM = +// Load config for full refresh. Called only by _InitDisplay() +void GxEPD2_213_FC1::_Init_Full() { - 0x00, T1, T2, T3, T4, 1, // 00 00 00 00 -}; + if (_configured_for_full) return; // If already configured, abort -const unsigned char GxEPD2_213_FC1::lut_21_ww_partial[] PROGMEM = -{ // 10 w - 0x02, T1, T2, T3, T5, 1, // 00 00 00 10 -}; - -const unsigned char GxEPD2_213_FC1::lut_22_bw_partial[] PROGMEM = -{ // 10 w - 0x48, T1, T2, T3, T4, 1, // 01 00 10 00 - //0x5A, T1, T2, T3, T4, 1, // 01 01 10 10 more white -}; + // Soft reset (via panel setting register) + _writeCommand(0x00); + _writeData(0); // [0] RST_N + _waitWhileBusy("soft reset", 0); -const unsigned char GxEPD2_213_FC1::lut_23_wb_partial[] PROGMEM = -{ // 01 b - 0x84, T1, T2, T3, T4, 1, // 10 00 01 00 - //0xA5, T1, T2, T3, T4, 1, // 10 10 01 01 more black -}; + // Panel setting + // [7:6] Display Res, [5] LUT, [4] BW / BWR [3] Scan Vert, [2] Shift Horiz, [1] Booster, [0] !Reset + _writeCommand(0x00); + _writeData(0b11 << 6 | 1 << 4 | 1 << 3 | 1 << 2 | 1 << 1 | 1 << 0); -const unsigned char GxEPD2_213_FC1::lut_24_bb_partial[] PROGMEM = -{ // 01 b - 0x01, T1, T2, T3, T6, 1, // 00 00 00 01 -}; + // VCOM and data interval setting + // [7:6] Border, [5:4] Data polarity (default), [3:0] VCOM and Data interval (default) + _writeCommand(0x50); + _writeData(0b10 << 6 | 0b11 << 4 | 0b0111 << 0); -void GxEPD2_213_FC1::_Init_Full() -{ - _InitDisplay(); - _PowerOn(); - _using_partial_mode = false; + _configured_for_full = true; + _configured_for_fast = false; } +// Load config for fast refresh. Called only by _InitDisplay() void GxEPD2_213_FC1::_Init_Part() { - _InitDisplay(); - /**/ - if (hasPartialUpdate) - { - _writeCommand(0x01); //POWER SETTING - _writeData (0x03); - _writeData (0x00); - _writeData (0x2b); - _writeData (0x2b); - _writeData (0x03); - - _writeCommand(0x06); //boost soft start - _writeData (0x00); //A - _writeData (0x00); //B - _writeData (0x00); //C - - // _writeCommand(0x22); - // _writeData(0xC0); - - _writeCommand(0x30); - _writeData (0x3C); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ - - _writeCommand(0x82); //vcom_DC setting - _writeData (0x12); - - // _writeCommand(0x50); // Some border setting command could be useful later? - // _writeData(0x17); - - _writeCommand(0x20); - _writeDataPGM(lut_20_vcomDC_partial, sizeof(lut_20_vcomDC_partial), 44 - sizeof(lut_20_vcomDC_partial)); - _writeCommand(0x21); - _writeDataPGM(lut_21_ww_partial, sizeof(lut_21_ww_partial), 42 - sizeof(lut_21_ww_partial)); - _writeCommand(0x22); - _writeDataPGM(lut_22_bw_partial, sizeof(lut_22_bw_partial), 42 - sizeof(lut_22_bw_partial)); - _writeCommand(0x23); - _writeDataPGM(lut_23_wb_partial, sizeof(lut_23_wb_partial), 42 - sizeof(lut_23_wb_partial)); - _writeCommand(0x24); - _writeDataPGM(lut_24_bb_partial, sizeof(lut_24_bb_partial), 42 - sizeof(lut_24_bb_partial)); - } - _PowerOn(); - _using_partial_mode = true; + if (_configured_for_fast) return; // If already configured, abort + + // Soft reset (via panel setting register) + _writeCommand(0x00); + _writeData(0); // [0] RST_N + _waitWhileBusy("soft reset", 0); + + // Panel setting + // [7:6] Display Res, [5] LUT, [4] BW / BWR [3] Scan Vert, [2] Shift Horiz, [1] Booster, [0] !Reset + _writeCommand(0x00); + _writeData( 0b11 << 6 | 1 << 5 | 1 << 4 | 1 << 3 | 1 << 2 | 1 << 1 | 1 << 0 ); + + // VCOM and data interval setting + // [7:6] Border, [5:4] Data polarity (default), [3:0] VCOM and Data interval (default) + _writeCommand(0x50); + _writeData( 0b10 << 6 | 0b01 << 4 | 0b0111 << 0 ); + + // Load the various LUTs + _writeCommand(0x20); // VCOM + for(uint8_t i=0;i < sizeof(lut_20_vcomDC_partial); i++) + _writeData(pgm_read_byte_near(lut_20_vcomDC_partial + i)); + + _writeCommand(0x21); // White -> White + for(uint8_t i=0;i < sizeof(lut_21_ww_partial); i++) + _writeData(pgm_read_byte_near(lut_21_ww_partial + i)); + + _writeCommand(0x22); // Black -> White + for(uint8_t i=0;i < sizeof(lut_22_bw_partial); i++) + _writeData(pgm_read_byte_near(lut_22_bw_partial + i)); + + _writeCommand(0x23); // White -> Black + for(uint8_t i=0;i < sizeof(lut_23_wb_partial); i++) + _writeData(pgm_read_byte_near(lut_23_wb_partial + i)); + + _writeCommand(0x24); // Black -> Black + for(uint8_t i=0;i < sizeof(lut_24_bb_partial); i++) + _writeData(pgm_read_byte_near(lut_24_bb_partial + i)); + + _configured_for_fast = true; + _configured_for_full = false; } void GxEPD2_213_FC1::_Update_Full() { + _Init_Full(); + _PowerOn(); _writeCommand(0x12); _waitWhileBusy("_Update_Full", full_refresh_time); _PowerOff(); @@ -478,7 +285,72 @@ void GxEPD2_213_FC1::_Update_Full() void GxEPD2_213_FC1::_Update_Part() { + _Init_Part(); + _PowerOn(); _writeCommand(0x12); _waitWhileBusy("_Update_Part", partial_refresh_time); _PowerOff(); } + +// Fast refresh waveform is unofficial (experimental?) +// https://github.com/todd-herbert/heltec-eink-modules/tree/v4.1.2/src/Displays/LCMEN2R13EFC1/LUTs + +const unsigned char GxEPD2_213_FC1::lut_20_vcomDC_partial[] PROGMEM = +{ +0x01, 0x03, 0x03, 0x02, 0x01, 0x01, 0x01, +0x01, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +const unsigned char GxEPD2_213_FC1::lut_21_ww_partial[] PROGMEM = +{ + 0x01, 0x03, 0x03, 0x02, 0x81, 0x01, 0x01, + 0x01, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +const unsigned char GxEPD2_213_FC1::lut_22_bw_partial[] PROGMEM = +{ + 0x01, 0x83, 0x83, 0x82, 0x01, 0x01, 0x01, + 0x01, 0x83, 0x82, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +const unsigned char GxEPD2_213_FC1::lut_23_wb_partial[] PROGMEM = +{ + 0x01, 0x43, 0x43, 0x02, 0x01, 0x01, 0x01, + 0x01, 0x43, 0x42, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +const unsigned char GxEPD2_213_FC1::lut_24_bb_partial[] PROGMEM = +{ + 0x01, 0x03, 0x03, 0x42, 0x41, 0x01, 0x01, + 0x01, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; \ No newline at end of file diff --git a/src/epd/GxEPD2_213_FC1.h b/src/epd/GxEPD2_213_FC1.h index 9ad654c..f07293d 100644 --- a/src/epd/GxEPD2_213_FC1.h +++ b/src/epd/GxEPD2_213_FC1.h @@ -1,15 +1,17 @@ -// Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare. -// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! +// ################################################################## +// # Meshtastic: custom driver for Heltec Wireless Paper V1.1 # +// ################################################################## // -// based on Demo Example from Good Display, available here: http://www.e-paper-display.com/download_detail/downloadsId=806.html -// Panel: GDEM0213B74 : https://www.good-display.com/product/375.html -// Controller : SSD1680 : https://www.good-display.com/companyfile/101.html +// From discussion: https://forum.arduino.cc/t/lcmen2r13efc1-discussion-heltec-wireless-paper/1211430 // -// Author: Jean-Marc Zingg +// Panel: LCMEN2R13EFC1 +// Controller: JD79656 (undocumented as of 2024/02/18) +// Board: Heltec Wireless Paper V1.1 // -// Version: see library.properties -// -// Library: https://github.com/ZinggJM/GxEPD2 +// --- NOTE! --- +// This display is NOT officially supported by GxEPD2. +// This fork is not affiliated with the original author. +// This code is not intended for general use. #ifndef _GxEPD2_213_FC1_H_ #define _GxEPD2_213_FC1_H_ @@ -23,61 +25,58 @@ class GxEPD2_213_FC1 : public GxEPD2_EPD static const uint16_t WIDTH = 128; static const uint16_t WIDTH_VISIBLE = 122; static const uint16_t HEIGHT = 250; - static const GxEPD2::Panel panel = GxEPD2::GDEM0213FC1; + static const GxEPD2::Panel panel = GxEPD2::LCMEN2R13EFC1; static const bool hasColor = false; - static const bool hasPartialUpdate = true; - static const bool hasFastPartialUpdate = false; - static const uint16_t power_on_time = 100; // ms, e.g. 95109us - static const uint16_t power_off_time = 150; // ms, e.g. 140344us - static const uint16_t full_refresh_time = 3600; // ms, e.g. 3501806us - static const uint16_t partial_refresh_time = 500; // ms, e.g. 455406us + static const bool hasPartialUpdate = true; // Controller IC does not support partial update, but it *does* support fast refresh. + static const bool hasFastPartialUpdate = true; + static const uint16_t power_on_time = 0; // Undetermined, unused + static const uint16_t power_off_time = 0; + static const uint16_t full_refresh_time = 0; + static const uint16_t partial_refresh_time = 0; // constructor GxEPD2_213_FC1(int16_t cs, int16_t dc, int16_t rst, int16_t busy, SPIClass &spi); // methods (virtual) - // Support for Bitmaps (Sprites) to Controller Buffer and to Screen void clearScreen(uint8_t value = 0xFF); // init controller memory and screen (default white) void writeScreenBuffer(uint8_t value = 0xFF); // init controller memory (default white) // write to controller memory, without screen refresh; x and w should be multiple of 8 void writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); - void writeImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, - int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); - void writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); - void writeImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, - int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); - // write sprite of native data to controller memory, without screen refresh; x and w should be multiple of 8 - void writeNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); - // write to controller memory, with screen refresh; x and w should be multiple of 8 - void drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); - void drawImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, - int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); - void drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); - void drawImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, - int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); - // write sprite of native data to controller memory, with screen refresh; x and w should be multiple of 8 - void drawNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); + void writeImageAgain(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); 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, partial 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) + + // Unimplemented for meshtastic + public: + void writeImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) {} + void drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) {} + void drawImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, + int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false) {} + void _setPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {} // (Normally private, moved here only so it is with the other unimplemented methods) + private: void _writeScreenBuffer(uint8_t command, uint8_t value); void _writeImage(uint8_t command, const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); void _writeImagePart(uint8_t command, const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); - void _setPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h); + void _reset(); // Custom, uses _waitWhileBusy() instead of delay() + void _Wake(); void _PowerOn(); void _PowerOff(); - void _InitDisplay(); void _Init_Full(); void _Init_Part(); void _Update_Full(); void _Update_Part(); + private: - static const unsigned char lut_20_vcomDC_partial[]; - static const unsigned char lut_21_ww_partial[]; - static const unsigned char lut_22_bw_partial[]; - static const unsigned char lut_23_wb_partial[]; - static const unsigned char lut_24_bb_partial[]; + bool _configured_for_fast = false; + bool _configured_for_full = false; + static const unsigned char lut_20_vcomDC_partial[56]; + static const unsigned char lut_21_ww_partial[56]; + static const unsigned char lut_22_bw_partial[56]; + static const unsigned char lut_23_wb_partial[56]; + static const unsigned char lut_24_bb_partial[56]; }; #endif