From ba535433543f40b0e15280c7beb9e0afabe56142 Mon Sep 17 00:00:00 2001 From: Quency-D <55523105+Quency-D@users.noreply.github.com> Date: Thu, 29 May 2025 22:10:25 +0800 Subject: [PATCH] Add a new screen for heltec_wireless_paper. (#6894) Co-authored-by: Ben Meadors --- src/graphics/EInkDisplay2.cpp | 64 +++- src/graphics/EInkDisplay2.h | 6 +- src/graphics/EInkMultiWrapper.h | 327 ++++++++++++++++++ src/graphics/niche/Drivers/EInk/E0213A367.cpp | 126 +++++++ src/graphics/niche/Drivers/EInk/E0213A367.h | 44 +++ .../heltec_wireless_paper/nicheGraphics.h | 57 ++- variants/heltec_wireless_paper/platformio.ini | 6 +- 7 files changed, 624 insertions(+), 6 deletions(-) create mode 100644 src/graphics/EInkMultiWrapper.h create mode 100644 src/graphics/niche/Drivers/EInk/E0213A367.cpp create mode 100644 src/graphics/niche/Drivers/EInk/E0213A367.h diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 5a2749482..b518299f7 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -174,7 +174,7 @@ bool EInkDisplay::connect() } } -#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \ +#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \ defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \ defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) { @@ -228,6 +228,68 @@ bool EInkDisplay::connect() auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *spi1); adafruitDisplay = new GxEPD2_BW(*lowLevel); + // Init GxEPD2 + adafruitDisplay->init(); + adafruitDisplay->setRotation(3); + } +#elif defined(HELTEC_WIRELESS_PAPER) + { + uint8_t model; + pinMode(PIN_EINK_SCLK, OUTPUT); + pinMode(PIN_EINK_DC, OUTPUT); + pinMode(PIN_EINK_CS, OUTPUT); + pinMode(PIN_EINK_RES, OUTPUT); + + //rest e-ink + digitalWrite(PIN_EINK_RES, LOW); + delay(20); + digitalWrite(PIN_EINK_RES, HIGH); + delay(20); + + digitalWrite(PIN_EINK_DC, LOW); + digitalWrite(PIN_EINK_CS, LOW); + + // write cmd + uint8_t cmd = 0x2F; + pinMode(PIN_EINK_MOSI, OUTPUT); + digitalWrite(PIN_EINK_SCLK, LOW); + for (int i = 0; i < 8; i++) + { + digitalWrite(PIN_EINK_MOSI, (cmd & 0x80) ? HIGH : LOW); + cmd <<= 1; + digitalWrite(PIN_EINK_SCLK, HIGH); + delayMicroseconds(1); + digitalWrite(PIN_EINK_SCLK, LOW); + delayMicroseconds(1); + } + delay(10); + + digitalWrite(PIN_EINK_DC, HIGH); + pinMode(PIN_EINK_MOSI, INPUT_PULLUP); + + // read chip ID + uint8_t chipId = 0; + for (int8_t b = 7; b >= 0; b--) + { + digitalWrite(PIN_EINK_SCLK, LOW); + delayMicroseconds(1); + digitalWrite(PIN_EINK_SCLK, HIGH); + delayMicroseconds(1); + if (digitalRead(PIN_EINK_MOSI)) chipId |= (1 << b); + } + digitalWrite(PIN_EINK_CS, HIGH); + LOG_INFO("eink chipId: %02X", chipId); + model = ((chipId&0x03) !=0x01) ? 1 : 2; + + // Start HSPI + hspi = new SPIClass(HSPI); + hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS + // VExt already enabled in setup() + // RTC GPIO hold disabled in setup() + + // Create GxEPD2 objects + adafruitDisplay = new EInkMultiWrapper(model, PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi); + // Init GxEPD2 adafruitDisplay->init(); adafruitDisplay->setRotation(3); diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index 93be197b0..965a3307a 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -4,6 +4,7 @@ #include "GxEPD2_BW.h" #include +#include "EinkMultiWrapper.h" /** * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation. @@ -64,8 +65,11 @@ class EInkDisplay : public OLEDDisplay virtual bool connect() override; // AdafruitGFX display object - instantiated in connect(), variant specific +#if defined(HELTEC_WIRELESS_PAPER) + EInkMultiWrapper *adafruitDisplay; +#else GxEPD2_BW *adafruitDisplay = NULL; - +#endif // If display uses HSPI #if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \ defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \ diff --git a/src/graphics/EInkMultiWrapper.h b/src/graphics/EInkMultiWrapper.h new file mode 100644 index 000000000..3ac0b194d --- /dev/null +++ b/src/graphics/EInkMultiWrapper.h @@ -0,0 +1,327 @@ +// Wrapper class for GxEPD2_BW + +// Generic signature at build time, allowing display model to be detected at run-time +// Workaround for issue of GxEPD2_BW objects not having a shared base class +// Only exposes methods which we are actually using +#ifndef _EINKMULTIWRAPPER_H_ +#define _EINKMULTIWRAPPER_H_ + +#include "GxEPD2_BW.h" +#include "GxEPD2_EPD.h" + +template +class EInkMultiWrapper +{ +public: + void drawPixel(int16_t x, int16_t y, uint16_t color) + { + if (model == 1) + model1->drawPixel(x, y, color); + else + model2->drawPixel(x, y, color); + } + void init(uint32_t serial_diag_bitrate = 0) // = 0 : disabled + { + if (model == 1) + model1->init(serial_diag_bitrate); + else + model2->init(serial_diag_bitrate); + } + + void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 20, bool pulldown_rst_mode = false) + { + if (model == 1) + model1->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode); + else + model2->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode); + } + void fillScreen(uint16_t color) // 0x0 black, >0x0 white, to buffer + { + if (model == 1) + model1->fillScreen(color); + else + model2->fillScreen(color); + } + void display(bool partial_update_mode = false) + { + if (model == 1) + model1->display(partial_update_mode); + else + model2->display(partial_update_mode); + } + void displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) + { + if (model == 1) + model1->displayWindow(x, y, w, h); + else + model2->displayWindow(x, y, w, h); + } + + void setFullWindow() + { + if (model == 1) + model1->setFullWindow(); + else + model2->setFullWindow(); + } + + void setPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) + { + if (model == 1) + model1->setPartialWindow(x, y, w, h); + else + model2->setPartialWindow(x, y, w, h); + } + + void firstPage() + { + if (model == 1) + model1->firstPage(); + else + model2->firstPage(); + } + void endAsyncFull() + { + if (model == 1) + model1->endAsyncFull(); + else + model2->endAsyncFull(); + } + + bool nextPage() + { + if (model == 1) + return model1->nextPage(); + else + return model2->nextPage(); + } + void drawPaged(void (*drawCallback)(const void*), const void* pv) + { + if (model == 1) + model1->drawPaged(drawCallback, pv); + else + model2->drawPaged(drawCallback, pv); + } + + void drawInvertedBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, int16_t h, uint16_t color) + { + if (model == 1) + model1->drawInvertedBitmap(x, y, bitmap, w, h, color); + else + model2->drawInvertedBitmap(x, y, bitmap, w, h, color); + } + + void clearScreen(uint8_t value = 0xFF) // init controller memory and screen (default white) + { + if (model == 1) + model1->clearScreen(value); + else + model2->clearScreen(value); + } + void writeScreenBuffer(uint8_t value = 0xFF) // init controller memory (default white) + { + if (model == 1) + model1->writeScreenBuffer(value); + else + model2->writeScreenBuffer(value); + } + // 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) + { + if (model == 1) + model1->writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm); + else + model2->writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm); + } + 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) + { + if (model == 1) + model1->writeImagePart(x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); + else + model2->writeImagePart(x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); + } + void 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 (model == 1) + model1->writeImage(black, color, x, y, w, h, invert, mirror_y, pgm); + else + model2->writeImage(black, color, x, y, w, h, invert, mirror_y, pgm); + } + void writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h) + { + if (model == 1) + model1->writeImage(black, color, x, y, w, h); + else + model2->writeImage(black, color, x, y, w, h); + } + 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, bool mirror_y, bool pgm) + { + if (model == 1) + model1->writeImagePart( black, color, x_part, y_part, w_bitmap, h_bitmap,x, y, w, h, invert, mirror_y, pgm); + else + model2->writeImagePart( black, color, x_part, y_part, w_bitmap, h_bitmap,x, y, w, h, invert, mirror_y, pgm); + } + + 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) + { + if (model == 1) + model1->writeImagePart(black, color, x_part, y_part, w_bitmap, h_bitmap,x, y, w, h); + else + model2->writeImagePart(black, color, x_part, y_part, w_bitmap, h_bitmap,x, y, w, h); + } + // 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, bool mirror_y, bool pgm) + { + if (model == 1) + model1->writeNative(data1, data2, x, y, w, h, invert, mirror_y, pgm); + else + model2->writeNative(data1, data2, x, y, w, h, invert, mirror_y, pgm); + } + // 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) + { + if (model == 1) + model1->drawImage(bitmap, x, y, w, h, invert, mirror_y, pgm); + else + model2->drawImage(bitmap, x, y, w, h, invert, mirror_y, pgm); + } + 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) + { + if (model == 1) + model1->drawImagePart(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); + else + model2->drawImagePart(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); + } + void 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) + { + if (model == 1) + model1->drawImage(black, color, x, y, w, h, invert, mirror_y, pgm); + else + model2->drawImage(black, color, x, y, w, h, invert, mirror_y, pgm); + } + void drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h) + { + if (model == 1) + model1->drawImage(black, color, x, y, w, h); + else + model2->drawImage(black, color, x, y, w, h); + } + 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, bool mirror_y, bool pgm) + { + if (model == 1) + model1->drawImagePart(black, color, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); + else + model2->drawImagePart(black, color, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); + } + 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) + { + if (model == 1) + model1->drawImagePart( black, color, x_part, y_part, w_bitmap, h_bitmap,x, y, w, h); + else + model2->drawImagePart( black, color, x_part, y_part, w_bitmap, h_bitmap,x, y, w, h); + } + // 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, bool mirror_y, bool pgm) + { + if (model == 1) + model1->drawNative(data1, data2, x, y, w, h, invert, mirror_y, pgm); + else + model2->drawNative(data1, data2, x, y, w, h, invert, mirror_y, pgm); + } + void refresh(bool partial_update_mode = false) // screen refresh from controller memory to full screen + { + if (model == 1) + model1->refresh(partial_update_mode); + else + model2->refresh(partial_update_mode); + } + void refresh(int16_t x, int16_t y, int16_t w, int16_t h) // screen refresh from controller memory, partial screen + { + if (model == 1) + model1->refresh(x, y, w, h); + else + model2->refresh(x, y, w, h); + } + // turns off generation of panel driving voltages, avoids screen fading over time + void powerOff() + { + if (model == 1) + model1->powerOff(); + else + model2->powerOff(); + } + // turns powerOff() and sets controller to deep sleep for minimum power use, ONLY if wakeable by RST (rst >= 0) + void hibernate() + { + if (model == 1) + model1->hibernate(); + else + model2->hibernate(); + } + + void setRotation(uint8_t x) + { + if (model == 1) + model1->setRotation(x); + else + model2->setRotation(x); + } + + int16_t width() + { + if (model == 1) + return model1->width(); + else + return model2->width(); + } + + int16_t height() + { + if (model == 1) + return model1->height(); + else + return model2->height(); + } + + + // Exposes methods of the GxEPD2_EPD object which is usually available as GxEPD2_BW::epd + class Epd2Wrapper + { + public: + bool isBusy() { return m_epd2->isBusy(); } + GxEPD2_EPD *m_epd2; + } epd2; + + // Constructor + // Select driver by passing whichModel as 1 or 2 + EInkMultiWrapper(uint8_t whichModel, int16_t cs, int16_t dc, int16_t rst, int16_t busy, SPIClass &spi) + { + assert(whichModel == 1 || whichModel == 2); + model = whichModel; + // LOG_DEBUG("GxEPD2_BW_MultiWrapper using driver %d", model); + + if (model == 1) + { + model1 = new GxEPD2_BW(DISPLAY_MODEL_1(cs, dc, rst, busy, spi)); + epd2.m_epd2 = &(model1->epd2); + } + else if (model == 2) + { + model2 = new GxEPD2_BW(DISPLAY_MODEL_2(cs, dc, rst, busy, spi)); + epd2.m_epd2 = &(model2->epd2); + } + } + +private: + uint8_t model; + GxEPD2_BW *model1; + GxEPD2_BW *model2; +}; + +#endif \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/E0213A367.cpp b/src/graphics/niche/Drivers/EInk/E0213A367.cpp new file mode 100644 index 000000000..4f2a50ba7 --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/E0213A367.cpp @@ -0,0 +1,126 @@ +#include "./E0213A367.h" + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +using namespace NicheGraphics::Drivers; + +// Map the display controller IC's output to the connected panel +void E0213A367::configScanning() +{ + // "Driver output control" + sendCommand(0x01); + sendData(0xF9); + sendData(0x00); + // Values set here might be redundant: F9, 00 seems to be default +} + +// Specify which information is used to control the sequence of voltages applied to move the pixels +// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from +// the controller IC's OTP memory, when the update procedure begins. +void E0213A367::configWaveform() +{ + sendCommand(0x37); // Waveform ID register + sendData(0x40); // ByteA + sendData(0x80); // ByteB DM[7:0] + sendData(0x03); // ByteC DM[[15:8] + sendData(0x0E); // ByteD DM[[23:16] + + switch (updateType) { + case FAST: + sendCommand(0x3C); // Border waveform: + sendData(0x81); + break; + case FULL: + sendCommand(0x3C); // Border waveform: + sendData(0x01); + default: + // From OTP memory + break; + } +} + +void E0213A367::configUpdateSequence() +{ + switch (updateType) { + case FAST: + sendCommand(0x22); + sendData(0xFF); + break; + case FULL: + default: + sendCommand(0x22); // Set "update sequence" + sendData(0xF7); + break; + } +} + +// Once the refresh operation has been started, +// begin periodically polling the display to check for completion, using the normal Meshtastic threading code +// Only used when refresh is "async" +void E0213A367::detachFromUpdate() +{ + switch (updateType) { + case FAST: + return beginPolling(50, 500); // At least 500ms for fast refresh + case FULL: + default: + return beginPolling(100, 2000); // At least 2 seconds for full refresh + } +} +void E0213A367::configFullscreen() +{ + // Placing this code in a separate method because it's probably pretty consistent between displays + // Should make it tidier to override SSD16XX::configure + + // Define the boundaries of the "fullscreen" region, for the controller IC + static const uint16_t sx = bufferOffsetX; // Notice the offset + static const uint16_t sy = 0; + static const uint16_t ex = bufferRowSize + bufferOffsetX - 1; // End is "max index", not "count". Minus 1 handles this + static const uint16_t ey = height; + + // Split into bytes + static const uint8_t sy1 = sy & 0xFF; + static const uint8_t ey1 = ey & 0xFF; + + // Data entry mode - Left to Right, Top to Bottom + sendCommand(0x11); + sendData(0x03); + + // Select controller IC memory region to display a fullscreen image + sendCommand(0x44); // Memory X start - end + sendData(sx); + sendData(ex); + sendCommand(0x45); // Memory Y start - end + sendData(sy1); + sendData(ey1); + + // Place the cursor at the start of this memory region, ready to send image data x=0 y=0 + sendCommand(0x4E); // Memory cursor X + sendData(sx); + sendCommand(0x4F); // Memory cursor y + sendData(sy1); +} +void E0213A367::finalizeUpdate() +{ + // Put a copy of the image into the "old memory". + // Used with differential refreshes (e.g. FAST update), to determine which px need to move, and which can remain in place + // We need to keep the "old memory" up to date, because don't know whether next refresh will be FULL or FAST etc. + if (updateType != FULL) { + writeNewImage(); // Only required by some controller variants. Todo: Override just for GDEY0154D678? + writeOldImage(); + sendCommand(0x7F); // Terminate image write without update + wait(); + } + + //After waking up from sleep mode, the local refresh is abnormal, which may be due to the loss of data in RAM. + // if ((pin_rst != 0xFF) && (updateType ==FULL)) + // deepSleep(); +} + +void E0213A367::deepSleep() +{ + sendCommand(0x10); // Enter deep sleep + sendData(0x03); // Will not retain image RAM +} + +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/E0213A367.h b/src/graphics/niche/Drivers/EInk/E0213A367.h new file mode 100644 index 000000000..c00d73378 --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/E0213A367.h @@ -0,0 +1,44 @@ +/* + +E-Ink display driver + - SSD1682 + - Manufacturer: WISEVAST + - Size: 2.13 inch + - Resolution: 122px x 255px + - Flex connector marking: HINK-E0213A162-FPC-A0 (Hidden, printed on back-side) + +*/ + +#pragma once + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +#include "configuration.h" + +#include "./SSD16XX.h" + +namespace NicheGraphics::Drivers +{ +class E0213A367 : public SSD16XX +{ + // Display properties + private: + static constexpr uint32_t width = 122; + static constexpr uint32_t height = 250; + static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST); + + public: + E0213A367() : SSD16XX(width, height, supported, 0) {} + + protected: + virtual void configScanning() override; + virtual void configWaveform() override; + virtual void configUpdateSequence() override; + virtual void detachFromUpdate() override; + virtual void configFullscreen() override; + virtual void deepSleep() override; + virtual void finalizeUpdate() override; +}; + +} // namespace NicheGraphics::Drivers +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/variants/heltec_wireless_paper/nicheGraphics.h b/variants/heltec_wireless_paper/nicheGraphics.h index c8994b7f1..e42a4df85 100644 --- a/variants/heltec_wireless_paper/nicheGraphics.h +++ b/variants/heltec_wireless_paper/nicheGraphics.h @@ -19,12 +19,58 @@ // Shared NicheGraphics components // -------------------------------- #include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h" +#include "graphics/niche/Drivers/EInk/E0213A367.h" #include "graphics/niche/Inputs/TwoButton.h" void setupNicheGraphics() { using namespace NicheGraphics; + pinMode(PIN_EINK_SCLK, OUTPUT); + pinMode(PIN_EINK_DC, OUTPUT); + pinMode(PIN_EINK_CS, OUTPUT); + pinMode(PIN_EINK_RES, OUTPUT); + + //rest e-ink + digitalWrite(PIN_EINK_RES, LOW); + delay(20); + digitalWrite(PIN_EINK_RES, HIGH); + delay(20); + + digitalWrite(PIN_EINK_DC, LOW); + digitalWrite(PIN_EINK_CS, LOW); + + // write cmd + uint8_t cmd = 0x2F; + pinMode(PIN_EINK_MOSI, OUTPUT); + digitalWrite(PIN_EINK_SCLK, LOW); + for (int i = 0; i < 8; i++) + { + digitalWrite(PIN_EINK_MOSI, (cmd & 0x80) ? HIGH : LOW); + cmd <<= 1; + digitalWrite(PIN_EINK_SCLK, HIGH); + delayMicroseconds(1); + digitalWrite(PIN_EINK_SCLK, LOW); + delayMicroseconds(1); + } + delay(10); + + digitalWrite(PIN_EINK_DC, HIGH); + pinMode(PIN_EINK_MOSI, INPUT_PULLUP); + + // read chip ID + uint8_t chipId = 0; + for (int8_t b = 7; b >= 0; b--) + { + digitalWrite(PIN_EINK_SCLK, LOW); + delayMicroseconds(1); + digitalWrite(PIN_EINK_SCLK, HIGH); + delayMicroseconds(1); + if (digitalRead(PIN_EINK_MOSI)) chipId |= (1 << b); + } + digitalWrite(PIN_EINK_CS, HIGH); + LOG_INFO("eink chipId: %02X", chipId); + // SPI // ----------------------------- @@ -34,8 +80,15 @@ void setupNicheGraphics() // E-Ink Driver // ----------------------------- - - Drivers::EInk *driver = new Drivers::LCMEN213EFC1; + Drivers::EInk *driver; + if((chipId &0x03) !=0x01) + { + driver = new Drivers::LCMEN213EFC1; + } + else + { + driver = new Drivers::E0213A367; + } driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES); // InkHUD diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 51430ebff..762b793cc 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -8,6 +8,8 @@ build_flags = -I variants/heltec_wireless_paper -D HELTEC_WIRELESS_PAPER -D EINK_DISPLAY_MODEL=GxEPD2_213_FC1 + -D EINK_DISPLAY_MODEL1=GxEPD2_213_FC1 + -D EINK_DISPLAY_MODEL2=GxEPD2_213_E0213A367 -D EINK_WIDTH=250 -D EINK_HEIGHT=122 -D USE_EINK @@ -17,9 +19,9 @@ build_flags = -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2/archive/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip + https://github.com/Quency-D/GxEPD2/archive/0513405847b281d9dea400488714643ef84507ec.zip lewisxhe/PCF8563_Library@^1.0.1 -upload_speed = 115200 +upload_speed = 921600 [env:heltec-wireless-paper-inkhud] extends = esp32s3_base, inkhud