diff --git a/src/graphics/niche/Drivers/EInk/DEPG0154BNS800.cpp b/src/graphics/niche/Drivers/EInk/DEPG0154BNS800.cpp deleted file mode 100644 index b8715ed1d..000000000 --- a/src/graphics/niche/Drivers/EInk/DEPG0154BNS800.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "./DEPG0154BNS800.h" \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/DEPG0154BNS800.h b/src/graphics/niche/Drivers/EInk/DEPG0154BNS800.h deleted file mode 100644 index 62d42ef57..000000000 --- a/src/graphics/niche/Drivers/EInk/DEPG0154BNS800.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - -E-Ink display driver - - DEPG0154BNS800 - - Manufacturer: DKE - - Size: 1.54 inch - - Resolution: 152px x 152px - - Flex connector marking: FPC7525 - -*/ - -#pragma once - -#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS -#include "configuration.h" - -#include "./SSD16XX.h" - -namespace NicheGraphics::Drivers -{ -class DEPG0154BNS800 : public SSD16XX -{ - // Display properties - private: - static constexpr uint32_t width = 152; - static constexpr uint32_t height = 152; - static constexpr UpdateTypes supported = (UpdateTypes)(FULL); - - public: - DEPG0154BNS800() : SSD16XX(width, height, supported, 1) {} // Note: left edge of this display is offset by 1 byte -}; - -} // namespace NicheGraphics::Drivers -#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.cpp b/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.cpp new file mode 100644 index 000000000..2c8df96ed --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.cpp @@ -0,0 +1,132 @@ +#include "./DEPG0213BNS800.h" + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +using namespace NicheGraphics::Drivers; + +// Describes the operation performed when a "fast refresh" is performed +// Source: Modified from GxEPD2 (GxEPD2_213_BN) +static const uint8_t LUT_FAST[] = { + // 1 2 3 + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // B2B (Existing black pixels) + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // B2W (New white pixels) + 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // W2B (New black pixels) + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // W2W (Existing white pixels) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // VCOM + + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // 1. Any pixels changing W2B or B2W. Two medium taps. + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2. All pixels. One short tap. + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 3. Cooldown + 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, // + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, // +}; + +// How strongly the pixels are pulled and pushed +void DEPG0213BNS800::configVoltages() +{ + switch (updateType) { + case FAST: + // Reference: display datasheet, GxEPD1 + sendCommand(0x03); // Gate voltage + sendData(0x17); // VGH: 20V + + // Reference: display datasheet, GxEPD1 + sendCommand(0x04); // Source voltage + sendData(0x41); // VSH1: 15V + sendData(0x00); // VSH2: NA + sendData(0x32); // VSL: -15V + + // GxEPD1 sets this at -1.2V, but that seems to be drive the pixels very hard + sendCommand(0x2C); // VCOM voltage + sendData(0x08); // VCOM: -0.2V + break; + + case FULL: + default: + // From OTP memory + break; + } +} + +// Load settings about how the pixels are moved from old state to new state during a refresh +// - manually specified, +// - or with stored values from displays OTP memory +void DEPG0213BNS800::configWaveform() +{ + switch (updateType) { + case FAST: + sendCommand(0x3C); // Border waveform: + sendData(0x80); // VSS + + sendCommand(0x32); // Write LUT register from MCU: + sendData(LUT_FAST, sizeof(LUT_FAST)); // (describes operation for a FAST refresh) + break; + + case FULL: + default: + // From OTP memory + break; + } +} + +// Describes the sequence of events performed by the displays controller IC during a refresh +// Includes "power up", "load settings from memory", "update the pixels", etc +void DEPG0213BNS800::configUpdateSequence() +{ + switch (updateType) { + case FAST: + sendCommand(0x22); // Set "update sequence" + sendData(0xCF); // Differential, use manually loaded waveform + break; + + case FULL: + default: + sendCommand(0x22); // Set "update sequence" + sendData(0xF7); // Non-differential, load waveform from OTP + 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 DEPG0213BNS800::detachFromUpdate() +{ + switch (updateType) { + case FAST: + return beginPolling(50, 500); // At least 500ms, then poll every 50ms + case FULL: + default: + return beginPolling(100, 3500); // At least 3500ms, then poll every 100ms + } +} + +// For this display, we do not need to re-write the new image. +// We're overriding SSD16XX::finalizeUpdate to make this small optimization. +// The display does also work just fine with the generic SSD16XX method, though. +void DEPG0213BNS800::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(); // Not required for this display + writeOldImage(); + sendCommand(0x7F); // Terminate image write without update + wait(); + } + + // Enter deep-sleep to save a few µA + // Waking from this requires that display's reset pin is broken out + if (pin_rst != 0xFF) + deepSleep(); +} +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.h b/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.h new file mode 100644 index 000000000..e1bb96450 --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/DEPG0213BNS800.h @@ -0,0 +1,44 @@ +/* + +E-Ink display driver + - DEPG0213BNS800 + - Manufacturer: DKE + - Size: 2.13 inch + - Resolution: 122px x 250px + - Flex connector marking: FPC-7528B + + Note: this is from an older generation of DKE panels, which still used Solomon Systech controller ICs. + DKE's website suggests that the latest DEPG0213BN displays may use Fitipower controllers instead. +*/ + +#pragma once + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +#include "configuration.h" + +#include "./SSD16XX.h" + +namespace NicheGraphics::Drivers +{ +class DEPG0213BNS800 : 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: + DEPG0213BNS800() : SSD16XX(width, height, supported, 1) {} // Note: left edge of this display is offset by 1 byte + + protected: + void configVoltages() override; + void configWaveform() override; + void configUpdateSequence() override; + void detachFromUpdate() override; + void finalizeUpdate() override; // Only overriden for a slight optimization +}; + +} // namespace NicheGraphics::Drivers +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/DEPG0290BNS800.cpp b/src/graphics/niche/Drivers/EInk/DEPG0290BNS800.cpp index 5f3a05670..15134d5ad 100644 --- a/src/graphics/niche/Drivers/EInk/DEPG0290BNS800.cpp +++ b/src/graphics/niche/Drivers/EInk/DEPG0290BNS800.cpp @@ -116,5 +116,10 @@ void DEPG0290BNS800::finalizeUpdate() sendCommand(0x7F); // Terminate image write without update wait(); } + + // Enter deep-sleep to save a few µA + // Waking from this requires that display's reset pin is broken out + if (pin_rst != 0xFF) + deepSleep(); } #endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/SSD16XX.cpp b/src/graphics/niche/Drivers/EInk/SSD16XX.cpp index 5a5397dbd..a2357a80b 100644 --- a/src/graphics/niche/Drivers/EInk/SSD16XX.cpp +++ b/src/graphics/niche/Drivers/EInk/SSD16XX.cpp @@ -242,5 +242,18 @@ void SSD16XX::finalizeUpdate() sendCommand(0x7F); // Terminate image write without update wait(); } + + // Enter deep-sleep to save a few µA + // Waking from this requires that display's reset pin is broken out + if (pin_rst != 0xFF) + deepSleep(); +} + +// Enter a lower-power state +// May only save a few µA.. +void SSD16XX::deepSleep() +{ + sendCommand(0x10); // Enter deep sleep + sendData(0x01); // Mode 1: preserve image RAM } #endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/SSD16XX.h b/src/graphics/niche/Drivers/EInk/SSD16XX.h index 799a378c0..3f92818ce 100644 --- a/src/graphics/niche/Drivers/EInk/SSD16XX.h +++ b/src/graphics/niche/Drivers/EInk/SSD16XX.h @@ -44,6 +44,7 @@ class SSD16XX : public EInk virtual void detachFromUpdate(); virtual bool isUpdateDone() override; virtual void finalizeUpdate() override; + virtual void deepSleep(); protected: uint8_t bufferOffsetX = 0; // In bytes. Panel x=0 does not always align with controller x=0. Quirky internal wiring? diff --git a/src/graphics/niche/Inputs/TwoButton.cpp b/src/graphics/niche/Inputs/TwoButton.cpp index b270d56cf..1e91d9080 100644 --- a/src/graphics/niche/Inputs/TwoButton.cpp +++ b/src/graphics/niche/Inputs/TwoButton.cpp @@ -98,9 +98,8 @@ void TwoButton::setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup) assert(whichButton < 2); buttons[whichButton].pin = pin; buttons[whichButton].activeLogic = LOW; // Unimplemented - buttons[whichButton].mode = internalPullup ? INPUT_PULLUP : INPUT; - pinMode(buttons[whichButton].pin, buttons[whichButton].mode); + pinMode(buttons[whichButton].pin, internalPullup ? INPUT_PULLUP : INPUT); } void TwoButton::setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs) @@ -299,7 +298,9 @@ int TwoButton::afterLightSleep(esp_sleep_wakeup_cause_t cause) // Manually trigger the button-down ISR // - during light sleep, our ISR is disabled // - if light sleep ends by button press, pretend our own ISR caught it - if (cause == ESP_SLEEP_WAKEUP_GPIO) + // - need to manually confirm by reading pin ourselves, to avoid occasional false positives + // (false positive only when using internal pullup resistors?) + if (cause == ESP_SLEEP_WAKEUP_GPIO && digitalRead(buttons[0].pin) == buttons[0].activeLogic) isrPrimary(); return 0; // Indicates success diff --git a/src/graphics/niche/Inputs/TwoButton.h b/src/graphics/niche/Inputs/TwoButton.h index f1e18dd89..ae66adf96 100644 --- a/src/graphics/niche/Inputs/TwoButton.h +++ b/src/graphics/niche/Inputs/TwoButton.h @@ -35,7 +35,7 @@ class TwoButton : protected concurrency::OSThread static TwoButton *getInstance(); // Create or get the singleton instance void start(); // Start handling button input void stop(); // Stop handling button input (disconnect ISRs for sleep) - void setWiring(uint8_t whichButton, uint8_t pin, bool internalPulldown = false); + void setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup = false); void setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs); void setHandlerDown(uint8_t whichButton, Callback onDown); void setHandlerUp(uint8_t whichButton, Callback onUp); @@ -65,7 +65,6 @@ class TwoButton : protected concurrency::OSThread // Per-button config uint8_t pin = 0xFF; // 0xFF: unset bool activeLogic = LOW; // Active LOW by default. Currently unimplemented. - uint8_t mode = INPUT; // Whether to use internal pull up / pull down resistors uint32_t debounceLength = 50; // Minimum length for shortpress, in ms uint32_t longpressLength = 500; // How long after button down to fire longpress, in ms volatile State state = State::REST; // Internal state diff --git a/variants/t-echo/nicheGraphics.h b/variants/t-echo/nicheGraphics.h index f5dde6b19..5862dcdfb 100644 --- a/variants/t-echo/nicheGraphics.h +++ b/variants/t-echo/nicheGraphics.h @@ -112,7 +112,7 @@ void setupNicheGraphics() // Setup the capacitive touch button // - short: momentary backlight // - long: latch backlight on - buttons->setWiring(TOUCH_BUTTON, PIN_BUTTON_TOUCH, LOW); + buttons->setWiring(TOUCH_BUTTON, PIN_BUTTON_TOUCH); buttons->setTiming(TOUCH_BUTTON, 50, 5000); // 5 seconds before latch - limited by T-Echo's capacitive touch IC buttons->setHandlerDown(TOUCH_BUTTON, [backlight]() { backlight->peek(); diff --git a/variants/tlora_t3s3_epaper/nicheGraphics.h b/variants/tlora_t3s3_epaper/nicheGraphics.h new file mode 100644 index 000000000..55bb9a203 --- /dev/null +++ b/variants/tlora_t3s3_epaper/nicheGraphics.h @@ -0,0 +1,102 @@ +#pragma once + +#include "configuration.h" + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +// InkHUD-specific components +// --------------------------- +#include "graphics/niche/InkHUD/InkHUD.h" + +// Applets +#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h" +#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h" +#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h" +#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h" +#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h" +#include "graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.h" + +// #include "graphics/niche/InkHUD/Applets/Examples/BasicExample/BasicExampleApplet.h" +// #include "graphics/niche/InkHUD/Applets/Examples/NewMsgExample/NewMsgExampleApplet.h" + +// Shared NicheGraphics components +// -------------------------------- +#include "graphics/niche/Drivers/EInk/DEPG0213BNS800.h" +#include "graphics/niche/Inputs/TwoButton.h" + +#include "graphics/niche/Fonts/FreeSans6pt7b.h" +#include "graphics/niche/Fonts/FreeSans6pt8bCyrillic.h" +#include + +void setupNicheGraphics() +{ + using namespace NicheGraphics; + + // SPI + // ----------------------------- + + // Display is connected to HSPI + SPIClass *hspi = new SPIClass(HSPI); + hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); + + // E-Ink Driver + // ----------------------------- + + // Use E-Ink driver + Drivers::EInk *driver = new Drivers::DEPG0213BNS800; + driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES); + + // InkHUD + // ---------------------------- + + InkHUD::InkHUD *inkhud = InkHUD::InkHUD::getInstance(); + + // Set the driver + inkhud->setDriver(driver); + + // Set how many FAST updates per FULL update + // Set how unhealthy additional FAST updates beyond this number are + inkhud->setDisplayResilience(15, 1.5); + + // Prepare fonts + InkHUD::Applet::fontLarge = InkHUD::AppletFont(FreeSans9pt7b); + InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt7b); + /* + // Font localization demo: Cyrillic + InkHUD::Applet::fontSmall = InkHUD::AppletFont(FreeSans6pt8bCyrillic); + InkHUD::Applet::fontSmall.addSubstitutionsWin1251(); + */ + + // Customize default settings + inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle? + inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise + inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users + + // Pick applets + inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown + inkhud->addApplet("DMs", new InkHUD::DMApplet); // Inactive + inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0)); // Inactive + inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1)); // Inactive + inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated + inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // Inactive + inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0 + // inkhud->addApplet("Basic", new InkHUD::BasicExampleApplet); + // inkhud->addApplet("NewMsg", new InkHUD::NewMsgExampleApplet); + + // Start running InkHUD + inkhud->begin(); + + // Buttons + // -------------------------- + + Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // Shared NicheGraphics component + + // Setup the main user button + buttons->setWiring(0, Inputs::TwoButton::getUserButtonPin(), true); + buttons->setHandlerShortPress(0, []() { InkHUD::InkHUD::getInstance()->shortpress(); }); + buttons->setHandlerLongPress(0, []() { InkHUD::InkHUD::getInstance()->longpress(); }); + + buttons->start(); +} + +#endif \ No newline at end of file diff --git a/variants/tlora_t3s3_epaper/platformio.ini b/variants/tlora_t3s3_epaper/platformio.ini index 87351e586..957c37b95 100644 --- a/variants/tlora_t3s3_epaper/platformio.ini +++ b/variants/tlora_t3s3_epaper/platformio.ini @@ -7,6 +7,7 @@ upload_protocol = esptool build_flags = ${esp32_base.build_flags} -D TLORA_T3S3_EPAPER -I variants/tlora_t3s3_epaper -DGPS_POWER_TOGGLE + -DUSE_EINK -DEINK_DISPLAY_MODEL=GxEPD2_213_BN -DEINK_WIDTH=250 -DEINK_HEIGHT=122 @@ -16,3 +17,21 @@ build_flags = lib_deps = ${esp32s3_base.lib_deps} https://github.com/meshtastic/GxEPD2/archive/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip + +[env:tlora-t3s3-epaper-inkhud] +extends = esp32s3_base, inkhud +board = tlora-t3s3-v1 +board_check = true +upload_protocol = esptool +build_src_filter = + ${esp32_base.build_src_filter} + ${inkhud.build_src_filter} +build_flags = + ${esp32s3_base.build_flags} + ${inkhud.build_flags} + -I variants/tlora_t3s3_epaper + -D TLORA_T3S3_EPAPER + -D MAX_THREADS=40 ; Required if used with WiFi +lib_deps = + ${inkhud.lib_deps} ; InkHUD libs first, so we get GFXRoot instead of AdafruitGFX + ${esp32s3_base.lib_deps} \ No newline at end of file diff --git a/variants/tlora_t3s3_epaper/variant.h b/variants/tlora_t3s3_epaper/variant.h index 732869b20..1ed505420 100644 --- a/variants/tlora_t3s3_epaper/variant.h +++ b/variants/tlora_t3s3_epaper/variant.h @@ -2,7 +2,6 @@ #define SDCARD_USE_SPI1 // Display (E-Ink) -#define USE_EINK #define PIN_EINK_CS 15 #define PIN_EINK_BUSY 48 #define PIN_EINK_DC 16