mirror of
https://github.com/meshtastic/firmware.git
synced 2025-08-03 04:15:53 +00:00
Heltec Wireless Paper, VM-E213 Hardware Revisions (#7258)
* Tests to identify display model * (InkHUD) SSD1682 controller IC Has a few quirks, gets its own base class * (InkHUD) E0213A367 Display For Heltec Wireless Paper V1.1.1, V1.2 For Heltec VM-E213 V1.1 * (InkHUD) Select display model at boot * (BaseUI) Wrapper to combine multiple GxEPD2 drivers Workaround for issue of GxEPD2_BW objects not having a shared base class. Allows us to select a driver at runtime. https://github.com/meshtastic/firmware/issues/6851#issuecomment-2905353447 * (BaseUI) Select E-Ink model at boot * (InkHUD) SSD1682 deep sleep * (InkHUD) No deep sleep for SSD1682 * (InkHUD) Fully no-op deep sleep for SSD1682
This commit is contained in:
parent
9c08220d24
commit
db4e4e6e53
@ -6,6 +6,10 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
|
|
||||||
|
#ifdef GXEPD2_DRIVER_0
|
||||||
|
#include "einkDetect.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini
|
The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini
|
||||||
Previously, these macros were defined at the top of this file.
|
Previously, these macros were defined at the top of this file.
|
||||||
@ -174,9 +178,8 @@ 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_E290) || defined(TLORA_T3S3_EPAPER) || \
|
||||||
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \
|
defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
|
||||||
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
|
|
||||||
{
|
{
|
||||||
// Start HSPI
|
// Start HSPI
|
||||||
hspi = new SPIClass(HSPI);
|
hspi = new SPIClass(HSPI);
|
||||||
@ -232,6 +235,23 @@ bool EInkDisplay::connect()
|
|||||||
adafruitDisplay->init();
|
adafruitDisplay->init();
|
||||||
adafruitDisplay->setRotation(3);
|
adafruitDisplay->setRotation(3);
|
||||||
}
|
}
|
||||||
|
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
|
||||||
|
|
||||||
|
// Detect display model, before starting SPI
|
||||||
|
EInkDetectionResult displayModel = detectEInk();
|
||||||
|
|
||||||
|
// Start HSPI
|
||||||
|
hspi = new SPIClass(HSPI);
|
||||||
|
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
|
||||||
|
|
||||||
|
// Create GxEPD2 object
|
||||||
|
adafruitDisplay = new GxEPD2_Multi<GXEPD2_DRIVER_0, GXEPD2_DRIVER_1>((uint8_t)displayModel, PIN_EINK_CS, PIN_EINK_DC,
|
||||||
|
PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
|
||||||
|
|
||||||
|
// Init GxEPD2
|
||||||
|
adafruitDisplay->init();
|
||||||
|
adafruitDisplay->setRotation(3);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -5,6 +5,10 @@
|
|||||||
#include "GxEPD2_BW.h"
|
#include "GxEPD2_BW.h"
|
||||||
#include <OLEDDisplay.h>
|
#include <OLEDDisplay.h>
|
||||||
|
|
||||||
|
#ifdef GXEPD2_DRIVER_0 // If variant has multiple possible display models
|
||||||
|
#include "GxEPD2Multi.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
|
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
|
||||||
*
|
*
|
||||||
@ -63,8 +67,15 @@ class EInkDisplay : public OLEDDisplay
|
|||||||
// Connect to the display
|
// Connect to the display
|
||||||
virtual bool connect() override;
|
virtual bool connect() override;
|
||||||
|
|
||||||
// AdafruitGFX display object - instantiated in connect(), variant specific
|
#ifdef GXEPD2_DRIVER_0
|
||||||
|
// AdafruitGFX display object - wrapper for multiple drivers
|
||||||
|
// Allows runtime detection of multiple displays
|
||||||
|
// Avoid this situation if possible!
|
||||||
|
GxEPD2_Multi<GXEPD2_DRIVER_0, GXEPD2_DRIVER_1> *adafruitDisplay = NULL;
|
||||||
|
#else
|
||||||
|
// AdafruitGFX display object (for single display model) - instantiated in connect(), variant specific
|
||||||
GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> *adafruitDisplay = NULL;
|
GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> *adafruitDisplay = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
// If display uses HSPI
|
// If display uses HSPI
|
||||||
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \
|
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \
|
||||||
|
135
src/graphics/GxEPD2Multi.h
Normal file
135
src/graphics/GxEPD2Multi.h
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// Wrapper class for GxEPD2_BW
|
||||||
|
|
||||||
|
// Generic signature at build-time, so that we can detect display model at run-time
|
||||||
|
// Workaround for issue of GxEPD2_BW objects not having a shared base class
|
||||||
|
// Only exposes methods which we are actually using
|
||||||
|
|
||||||
|
template <typename Driver0, typename Driver1> class GxEPD2_Multi
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void drawPixel(int16_t x, int16_t y, uint16_t color)
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
driver0->drawPixel(x, y, color);
|
||||||
|
else
|
||||||
|
driver1->drawPixel(x, y, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool nextPage()
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
return driver0->nextPage();
|
||||||
|
else
|
||||||
|
return driver1->nextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void hibernate()
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
driver0->hibernate();
|
||||||
|
else
|
||||||
|
driver1->hibernate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(uint32_t serial_diag_bitrate = 0)
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
driver0->init(serial_diag_bitrate);
|
||||||
|
else
|
||||||
|
driver1->init(serial_diag_bitrate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 20, bool pulldown_rst_mode = false)
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
driver0->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
|
||||||
|
else
|
||||||
|
driver1->init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRotation(uint8_t x)
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
driver0->setRotation(x);
|
||||||
|
else
|
||||||
|
driver1->setRotation(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
driver0->setPartialWindow(x, y, w, h);
|
||||||
|
else
|
||||||
|
driver1->setPartialWindow(x, y, w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFullWindow()
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
driver0->setFullWindow();
|
||||||
|
else
|
||||||
|
driver1->setFullWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t width()
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
return driver0->width();
|
||||||
|
else
|
||||||
|
return driver1->width();
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t height()
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
return driver0->height();
|
||||||
|
else
|
||||||
|
return driver1->height();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearScreen(uint8_t value = 0xFF)
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
driver0->clearScreen();
|
||||||
|
else
|
||||||
|
driver1->clearScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
void endAsyncFull()
|
||||||
|
{
|
||||||
|
if (which == 0)
|
||||||
|
driver0->endAsyncFull();
|
||||||
|
else
|
||||||
|
driver1->endAsyncFull();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 whichDriver as 0 or 1
|
||||||
|
GxEPD2_Multi(uint8_t whichDriver, int16_t cs, int16_t dc, int16_t rst, int16_t busy, SPIClass &spi)
|
||||||
|
{
|
||||||
|
assert(whichDriver == 0 || whichDriver == 1);
|
||||||
|
which = whichDriver;
|
||||||
|
LOG_DEBUG("GxEPD2_Multi driver: %d", which);
|
||||||
|
|
||||||
|
if (which == 0) {
|
||||||
|
driver0 = new GxEPD2_BW<Driver0, Driver0::HEIGHT>(Driver0(cs, dc, rst, busy, spi));
|
||||||
|
epd2.m_epd2 = &(driver0->epd2);
|
||||||
|
} else if (which == 1) {
|
||||||
|
driver1 = new GxEPD2_BW<Driver1, Driver1::HEIGHT>(Driver1(cs, dc, rst, busy, spi));
|
||||||
|
epd2.m_epd2 = &(driver1->epd2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t which;
|
||||||
|
GxEPD2_BW<Driver0, Driver0::HEIGHT> *driver0;
|
||||||
|
GxEPD2_BW<Driver1, Driver1::HEIGHT> *driver1;
|
||||||
|
};
|
84
src/graphics/niche/Drivers/EInk/E0213A367.cpp
Normal file
84
src/graphics/niche/Drivers/EInk/E0213A367.cpp
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#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"
|
||||||
|
// Scan gates from 0 to 249 (vertical resolution 250px)
|
||||||
|
sendCommand(0x01);
|
||||||
|
sendData(0xF9);
|
||||||
|
sendData(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify which information is used to control the sequence of voltages applied to move the pixels
|
||||||
|
void E0213A367::configWaveform()
|
||||||
|
{
|
||||||
|
// This command (0x37) is poorly documented
|
||||||
|
// As of July 2025, the datasheet for this display's controller IC is unavailable
|
||||||
|
// The values are supplied by Heltec, who presumably have privileged access to information from the display manufacturer
|
||||||
|
// Datasheet for the similar SSD1680 IC hints at the function of this command:
|
||||||
|
|
||||||
|
// "Spare VCOM OTP selection":
|
||||||
|
// Unclear why 0x40 is set. Sane values for related SSD1680 seem to be 0x80 or 0x00.
|
||||||
|
// Maybe value is redundant? No noticeable impact when set to 0x00.
|
||||||
|
// We'll leave it set to 0x40, following Heltec's lead, just in case.
|
||||||
|
|
||||||
|
// "Display Mode"
|
||||||
|
// Seems to specify whether a waveform stored in OTP should use display mode 1 or 2 (full refresh or differential refresh)
|
||||||
|
|
||||||
|
// Unusual that waveforms are programmed to OTP, but this meta information is not ..?
|
||||||
|
|
||||||
|
sendCommand(0x37); // "Write Register for Display Option" ?
|
||||||
|
sendData(0x40); // "Spare VCOM OTP selection" ?
|
||||||
|
sendData(0x80); // "Display Mode for WS[7:0]" ?
|
||||||
|
sendData(0x03); // "Display Mode for WS[15:8]" ?
|
||||||
|
sendData(0x0E); // "Display Mode [23:16]" ?
|
||||||
|
|
||||||
|
switch (updateType) {
|
||||||
|
case FAST:
|
||||||
|
sendCommand(0x3C); // Border waveform:
|
||||||
|
sendData(0x81); // As specified by Heltec. Actually VCOM (0x80)?. Bit 0 seems redundant here.
|
||||||
|
break;
|
||||||
|
case FULL:
|
||||||
|
default:
|
||||||
|
sendCommand(0x3C); // Border waveform:
|
||||||
|
sendData(0x01); // Follow LUT 1 (blink same as white pixels)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell controller IC which operations to run
|
||||||
|
void E0213A367::configUpdateSequence()
|
||||||
|
{
|
||||||
|
switch (updateType) {
|
||||||
|
case FAST:
|
||||||
|
sendCommand(0x22); // Set "update sequence"
|
||||||
|
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
|
||||||
|
break;
|
||||||
|
case FULL:
|
||||||
|
default:
|
||||||
|
sendCommand(0x22); // Set "update sequence"
|
||||||
|
sendData(0xF7); // Will load LUT from OTP memory, Display mode 1 "full refresh"
|
||||||
|
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, 1500); // At least 1.5 seconds for full refresh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
41
src/graphics/niche/Drivers/EInk/E0213A367.h
Normal file
41
src/graphics/niche/Drivers/EInk/E0213A367.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
E-Ink display driver
|
||||||
|
- SSD1682
|
||||||
|
- Manufacturer: SEEKINK
|
||||||
|
- Size: 2.13 inch
|
||||||
|
- Resolution: 122px x 255px
|
||||||
|
- Flex connector marking: HINK-E0213A162-A1 (hidden, printed on reverse)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#include "./SSD1682.h"
|
||||||
|
|
||||||
|
namespace NicheGraphics::Drivers
|
||||||
|
{
|
||||||
|
class E0213A367 : public SSD1682
|
||||||
|
{
|
||||||
|
// Display properties
|
||||||
|
private:
|
||||||
|
static constexpr uint32_t width = 122;
|
||||||
|
static constexpr uint32_t height = 250;
|
||||||
|
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
|
||||||
|
|
||||||
|
public:
|
||||||
|
E0213A367() : SSD1682(width, height, supported, 0) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void configScanning() override;
|
||||||
|
void configWaveform() override;
|
||||||
|
void configUpdateSequence() override;
|
||||||
|
void detachFromUpdate() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace NicheGraphics::Drivers
|
||||||
|
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
41
src/graphics/niche/Drivers/EInk/SSD1682.cpp
Normal file
41
src/graphics/niche/Drivers/EInk/SSD1682.cpp
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#include "./SSD1682.h"
|
||||||
|
|
||||||
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
|
|
||||||
|
using namespace NicheGraphics::Drivers;
|
||||||
|
|
||||||
|
SSD1682::SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX)
|
||||||
|
: SSD16XX(width, height, supported, bufferOffsetX)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSD1682 only accepts single-byte x and y values
|
||||||
|
// This causes an incompatibility with the default SSD16XX::configFullscreen
|
||||||
|
void SSD1682::configFullscreen()
|
||||||
|
{
|
||||||
|
// Define the boundaries of the "fullscreen" region, for the controller IC
|
||||||
|
static const uint8_t sx = bufferOffsetX; // Notice the offset
|
||||||
|
static const uint8_t sy = 0;
|
||||||
|
static const uint8_t ex = bufferRowSize + bufferOffsetX - 1; // End is "max index", not "count". Minus 1 handles this
|
||||||
|
static const uint8_t ey = height;
|
||||||
|
|
||||||
|
// 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(sy);
|
||||||
|
sendData(ey);
|
||||||
|
|
||||||
|
// 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(sy);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
31
src/graphics/niche/Drivers/EInk/SSD1682.h
Normal file
31
src/graphics/niche/Drivers/EInk/SSD1682.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
E-Ink base class for displays based on SSD1682
|
||||||
|
|
||||||
|
SSD1682 has a few quirks. We're implementing them here in a new base class,
|
||||||
|
to avoid re-implementing them every time we need to add a new SSD1682-based display.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#include "./SSD16XX.h"
|
||||||
|
|
||||||
|
namespace NicheGraphics::Drivers
|
||||||
|
{
|
||||||
|
|
||||||
|
class SSD1682 : public SSD16XX
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SSD1682(uint16_t width, uint16_t height, EInk::UpdateTypes supported, uint8_t bufferOffsetX = 0);
|
||||||
|
virtual void configFullscreen(); // Select memory region on controller IC
|
||||||
|
virtual void deepSleep() {} // Not usable (image memory not retained)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace NicheGraphics::Drivers
|
||||||
|
|
||||||
|
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
35
variants/heltec_vision_master_e213/einkDetect.h
Normal file
35
variants/heltec_vision_master_e213/einkDetect.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
enum class EInkDetectionResult : uint8_t {
|
||||||
|
LCMEN213EFC1 = 0, // Initial version
|
||||||
|
E0213A367 = 1, // E213 PCB marked V1.1 (Mid 2025)
|
||||||
|
};
|
||||||
|
|
||||||
|
EInkDetectionResult detectEInk()
|
||||||
|
{
|
||||||
|
// Test 1: Logic of BUSY pin
|
||||||
|
|
||||||
|
// Determines controller IC manufacturer
|
||||||
|
// Fitipower: busy when LOW
|
||||||
|
// Solomon Systech: busy when HIGH
|
||||||
|
|
||||||
|
// Force display BUSY by holding reset pin active
|
||||||
|
pinMode(PIN_EINK_RES, OUTPUT);
|
||||||
|
digitalWrite(PIN_EINK_RES, LOW);
|
||||||
|
|
||||||
|
delay(10);
|
||||||
|
|
||||||
|
// Read whether pin is HIGH or LOW while busy
|
||||||
|
pinMode(PIN_EINK_BUSY, INPUT);
|
||||||
|
bool busyLogic = digitalRead(PIN_EINK_BUSY);
|
||||||
|
|
||||||
|
// Test complete. Release pin
|
||||||
|
pinMode(PIN_EINK_RES, INPUT);
|
||||||
|
|
||||||
|
if (busyLogic == LOW)
|
||||||
|
return EInkDetectionResult::LCMEN213EFC1;
|
||||||
|
else // busy HIGH
|
||||||
|
return EInkDetectionResult::E0213A367;
|
||||||
|
}
|
@ -18,16 +18,22 @@
|
|||||||
|
|
||||||
// Shared NicheGraphics components
|
// Shared NicheGraphics components
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
#include "graphics/niche/Drivers/EInk/E0213A367.h"
|
||||||
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
|
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
|
||||||
#include "graphics/niche/Inputs/TwoButton.h"
|
#include "graphics/niche/Inputs/TwoButton.h"
|
||||||
|
|
||||||
// Button feedback
|
#include "buzz.h" // Button feedback
|
||||||
#include "buzz.h"
|
#include "einkDetect.h" // Detect display model at runtime
|
||||||
|
|
||||||
void setupNicheGraphics()
|
void setupNicheGraphics()
|
||||||
{
|
{
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
|
// Detect E-Ink Model
|
||||||
|
// -------------------
|
||||||
|
|
||||||
|
EInkDetectionResult displayModel = detectEInk();
|
||||||
|
|
||||||
// SPI
|
// SPI
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
||||||
@ -38,7 +44,13 @@ void setupNicheGraphics()
|
|||||||
// E-Ink Driver
|
// E-Ink Driver
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
||||||
Drivers::EInk *driver = new Drivers::LCMEN213EFC1;
|
Drivers::EInk *driver;
|
||||||
|
|
||||||
|
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked)
|
||||||
|
driver = new Drivers::LCMEN213EFC1;
|
||||||
|
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1
|
||||||
|
driver = new Drivers::E0213A367;
|
||||||
|
|
||||||
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
|
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
|
||||||
|
|
||||||
// InkHUD
|
// InkHUD
|
||||||
@ -51,7 +63,11 @@ void setupNicheGraphics()
|
|||||||
|
|
||||||
// Set how many FAST updates per FULL update
|
// Set how many FAST updates per FULL update
|
||||||
// Set how unhealthy additional FAST updates beyond this number are
|
// Set how unhealthy additional FAST updates beyond this number are
|
||||||
inkhud->setDisplayResilience(10, 1.5);
|
|
||||||
|
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1 (unmarked)
|
||||||
|
inkhud->setDisplayResilience(10, 1.5);
|
||||||
|
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1
|
||||||
|
inkhud->setDisplayResilience(15, 3);
|
||||||
|
|
||||||
// Select fonts
|
// Select fonts
|
||||||
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
|
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
|
||||||
|
@ -7,7 +7,8 @@ build_flags =
|
|||||||
-Ivariants/heltec_vision_master_e213
|
-Ivariants/heltec_vision_master_e213
|
||||||
-DHELTEC_VISION_MASTER_E213
|
-DHELTEC_VISION_MASTER_E213
|
||||||
-DUSE_EINK
|
-DUSE_EINK
|
||||||
-DEINK_DISPLAY_MODEL=GxEPD2_213_FC1
|
-DGXEPD2_DRIVER_0=GxEPD2_213_FC1
|
||||||
|
-DGXEPD2_DRIVER_1=GxEPD2_213_E0213A367
|
||||||
-DEINK_WIDTH=250
|
-DEINK_WIDTH=250
|
||||||
-DEINK_HEIGHT=122
|
-DEINK_HEIGHT=122
|
||||||
-DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
|
-DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk
|
||||||
@ -16,7 +17,7 @@ build_flags =
|
|||||||
-DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
|
-DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${esp32s3_base.lib_deps}
|
${esp32s3_base.lib_deps}
|
||||||
https://github.com/meshtastic/GxEPD2/archive/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip
|
https://github.com/meshtastic/GxEPD2/archive/1655054ba298e0e29fc2044741940f927f9c2a43.zip
|
||||||
lewisxhe/PCF8563_Library@^1.0.1
|
lewisxhe/PCF8563_Library@^1.0.1
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
|
|
||||||
|
35
variants/heltec_wireless_paper/einkDetect.h
Normal file
35
variants/heltec_wireless_paper/einkDetect.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
enum class EInkDetectionResult : uint8_t {
|
||||||
|
LCMEN213EFC1 = 0, // V1.1
|
||||||
|
E0213A367 = 1, // V1.1.1, V1.2
|
||||||
|
};
|
||||||
|
|
||||||
|
EInkDetectionResult detectEInk()
|
||||||
|
{
|
||||||
|
// Test 1: Logic of BUSY pin
|
||||||
|
|
||||||
|
// Determines controller IC manufacturer
|
||||||
|
// Fitipower: busy when LOW
|
||||||
|
// Solomon Systech: busy when HIGH
|
||||||
|
|
||||||
|
// Force display BUSY by holding reset pin active
|
||||||
|
pinMode(PIN_EINK_RES, OUTPUT);
|
||||||
|
digitalWrite(PIN_EINK_RES, LOW);
|
||||||
|
|
||||||
|
delay(10);
|
||||||
|
|
||||||
|
// Read whether pin is HIGH or LOW while busy
|
||||||
|
pinMode(PIN_EINK_BUSY, INPUT);
|
||||||
|
bool busyLogic = digitalRead(PIN_EINK_BUSY);
|
||||||
|
|
||||||
|
// Test complete. Release pin
|
||||||
|
pinMode(PIN_EINK_RES, INPUT);
|
||||||
|
|
||||||
|
if (busyLogic == LOW)
|
||||||
|
return EInkDetectionResult::LCMEN213EFC1;
|
||||||
|
else // busy HIGH
|
||||||
|
return EInkDetectionResult::E0213A367;
|
||||||
|
}
|
@ -18,13 +18,21 @@
|
|||||||
|
|
||||||
// Shared NicheGraphics components
|
// Shared NicheGraphics components
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
|
#include "graphics/niche/Drivers/EInk/E0213A367.h"
|
||||||
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
|
#include "graphics/niche/Drivers/EInk/LCMEN2R13EFC1.h"
|
||||||
#include "graphics/niche/Inputs/TwoButton.h"
|
#include "graphics/niche/Inputs/TwoButton.h"
|
||||||
|
|
||||||
|
#include "einkDetect.h" // Detect display model at runtime
|
||||||
|
|
||||||
void setupNicheGraphics()
|
void setupNicheGraphics()
|
||||||
{
|
{
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
|
// Detect E-Ink Model
|
||||||
|
// -------------------
|
||||||
|
|
||||||
|
EInkDetectionResult displayModel = detectEInk();
|
||||||
|
|
||||||
// SPI
|
// SPI
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
||||||
@ -35,7 +43,13 @@ void setupNicheGraphics()
|
|||||||
// E-Ink Driver
|
// E-Ink Driver
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
||||||
Drivers::EInk *driver = new Drivers::LCMEN213EFC1;
|
Drivers::EInk *driver;
|
||||||
|
|
||||||
|
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1.1
|
||||||
|
driver = new Drivers::LCMEN213EFC1;
|
||||||
|
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1.1, V1.2
|
||||||
|
driver = new Drivers::E0213A367;
|
||||||
|
|
||||||
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
|
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
|
||||||
|
|
||||||
// InkHUD
|
// InkHUD
|
||||||
@ -48,7 +62,11 @@ void setupNicheGraphics()
|
|||||||
|
|
||||||
// Set how many FAST updates per FULL update
|
// Set how many FAST updates per FULL update
|
||||||
// Set how unhealthy additional FAST updates beyond this number are
|
// Set how unhealthy additional FAST updates beyond this number are
|
||||||
inkhud->setDisplayResilience(10, 1.5);
|
|
||||||
|
if (displayModel == EInkDetectionResult::LCMEN213EFC1) // V1.1 (unmarked)
|
||||||
|
inkhud->setDisplayResilience(10, 1.5);
|
||||||
|
else if (displayModel == EInkDetectionResult::E0213A367) // V1.1.1, V1.2
|
||||||
|
inkhud->setDisplayResilience(15, 3);
|
||||||
|
|
||||||
// Select fonts
|
// Select fonts
|
||||||
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
|
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
|
||||||
|
@ -7,7 +7,8 @@ build_flags =
|
|||||||
${esp32s3_base.build_flags}
|
${esp32s3_base.build_flags}
|
||||||
-I variants/heltec_wireless_paper
|
-I variants/heltec_wireless_paper
|
||||||
-D HELTEC_WIRELESS_PAPER
|
-D HELTEC_WIRELESS_PAPER
|
||||||
-D EINK_DISPLAY_MODEL=GxEPD2_213_FC1
|
-D GXEPD2_DRIVER_0=GxEPD2_213_FC1
|
||||||
|
-D GXEPD2_DRIVER_1=GxEPD2_213_E0213A367
|
||||||
-D EINK_WIDTH=250
|
-D EINK_WIDTH=250
|
||||||
-D EINK_HEIGHT=122
|
-D EINK_HEIGHT=122
|
||||||
-D USE_EINK
|
-D USE_EINK
|
||||||
@ -17,7 +18,7 @@ build_flags =
|
|||||||
-D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
|
-D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${esp32s3_base.lib_deps}
|
${esp32s3_base.lib_deps}
|
||||||
https://github.com/meshtastic/GxEPD2/archive/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip
|
https://github.com/meshtastic/GxEPD2/archive/1655054ba298e0e29fc2044741940f927f9c2a43.zip
|
||||||
lewisxhe/PCF8563_Library@^1.0.1
|
lewisxhe/PCF8563_Library@^1.0.1
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user