2023-03-28 13:33:49 +00:00
|
|
|
#include "configuration.h"
|
|
|
|
|
|
|
|
#ifdef USE_EINK
|
|
|
|
#include "EInkDisplay2.h"
|
|
|
|
#include "SPILock.h"
|
|
|
|
#include "main.h"
|
|
|
|
#include <SPI.h>
|
|
|
|
|
2024-02-28 15:45:15 +00:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
|
|
|
|
For archival reasons, note that the following configurations had also been tested during this period:
|
|
|
|
* ifdef RAK4631
|
|
|
|
- 4.2 inch
|
|
|
|
EINK_DISPLAY_MODEL: GxEPD2_420_M01
|
|
|
|
EINK_WIDTH: 300
|
|
|
|
EINK_WIDTH: 400
|
|
|
|
|
|
|
|
- 2.9 inch
|
|
|
|
EINK_DISPLAY_MODEL: GxEPD2_290_T5D
|
|
|
|
EINK_WIDTH: 296
|
|
|
|
EINK_HEIGHT: 128
|
|
|
|
|
|
|
|
- 1.54 inch
|
|
|
|
EINK_DISPLAY_MODEL: GxEPD2_154_M09
|
|
|
|
EINK_WIDTH: 200
|
|
|
|
EINK_HEIGHT: 200
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Constructor
|
2023-03-28 13:33:49 +00:00
|
|
|
EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
|
|
|
|
{
|
2024-02-28 15:45:15 +00:00
|
|
|
// Set dimensions in OLEDDisplay base class
|
2024-02-09 01:00:13 +00:00
|
|
|
this->geometry = GEOMETRY_RAWMODE;
|
2024-02-28 15:45:15 +00:00
|
|
|
this->displayWidth = EINK_WIDTH;
|
|
|
|
this->displayHeight = EINK_HEIGHT;
|
2023-03-28 13:33:49 +00:00
|
|
|
|
2024-02-28 15:45:15 +00:00
|
|
|
// Round shortest side up to nearest byte, to prevent truncation causing an undersized buffer
|
|
|
|
uint16_t shortSide = min(EINK_WIDTH, EINK_HEIGHT);
|
|
|
|
uint16_t longSide = max(EINK_WIDTH, EINK_HEIGHT);
|
|
|
|
if (shortSide % 8 != 0)
|
|
|
|
shortSide = (shortSide | 7) + 1;
|
2023-03-28 13:33:49 +00:00
|
|
|
|
2024-02-28 15:45:15 +00:00
|
|
|
this->displayBufferSize = longSide * (shortSide / 8);
|
2023-03-28 13:33:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Force a display update if we haven't drawn within the specified msecLimit
|
|
|
|
*/
|
|
|
|
bool EInkDisplay::forceDisplay(uint32_t msecLimit)
|
|
|
|
{
|
|
|
|
// No need to grab this lock because we are on our own SPI bus
|
|
|
|
// concurrency::LockGuard g(spiLock);
|
|
|
|
|
|
|
|
uint32_t now = millis();
|
|
|
|
uint32_t sinceLast = now - lastDrawMsec;
|
|
|
|
|
2024-02-11 13:27:22 +00:00
|
|
|
if (adafruitDisplay && (sinceLast > msecLimit || lastDrawMsec == 0))
|
2023-03-28 13:33:49 +00:00
|
|
|
lastDrawMsec = now;
|
2024-02-11 13:27:22 +00:00
|
|
|
else
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
|
2024-05-13 11:42:41 +00:00
|
|
|
const bool flipped = config.display.flip_screen;
|
2024-02-11 13:27:22 +00:00
|
|
|
for (uint32_t y = 0; y < displayHeight; y++) {
|
|
|
|
for (uint32_t x = 0; x < displayWidth; x++) {
|
|
|
|
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient
|
|
|
|
auto b = buffer[x + (y / 8) * displayWidth];
|
|
|
|
auto isset = b & (1 << (y & 7));
|
2024-05-13 11:42:41 +00:00
|
|
|
|
|
|
|
// Handle flip here, rather than with setRotation(),
|
|
|
|
// Avoids issues when display width is not a multiple of 8
|
|
|
|
if (flipped)
|
|
|
|
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
|
|
|
else
|
|
|
|
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
|
2023-03-28 13:33:49 +00:00
|
|
|
}
|
2024-02-11 13:27:22 +00:00
|
|
|
}
|
2023-03-28 13:33:49 +00:00
|
|
|
|
2024-03-11 12:56:55 +00:00
|
|
|
// Trigger the refresh in GxEPD2
|
2024-02-11 13:27:22 +00:00
|
|
|
LOG_DEBUG("Updating E-Paper... ");
|
|
|
|
adafruitDisplay->nextPage();
|
2023-03-28 13:33:49 +00:00
|
|
|
|
2024-03-11 12:56:55 +00:00
|
|
|
// End the update process
|
|
|
|
endUpdate();
|
2023-03-28 13:33:49 +00:00
|
|
|
|
2024-03-03 02:07:29 +00:00
|
|
|
LOG_DEBUG("done\n");
|
2024-02-11 13:27:22 +00:00
|
|
|
return true;
|
2023-03-28 13:33:49 +00:00
|
|
|
}
|
|
|
|
|
2024-03-11 12:56:55 +00:00
|
|
|
// End the update process - virtual method, overriden in derived class
|
|
|
|
void EInkDisplay::endUpdate()
|
|
|
|
{
|
|
|
|
// Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep)
|
|
|
|
adafruitDisplay->hibernate();
|
|
|
|
}
|
|
|
|
|
2023-03-28 13:33:49 +00:00
|
|
|
// Write the buffer to the display memory
|
|
|
|
void EInkDisplay::display(void)
|
|
|
|
{
|
|
|
|
// We don't allow regular 'dumb' display() calls to draw on eink until we've shown
|
|
|
|
// at least one forceDisplay() keyframe. This prevents flashing when we should the critical
|
|
|
|
// bootscreen (that we want to look nice)
|
2024-02-11 13:27:22 +00:00
|
|
|
|
|
|
|
if (lastDrawMsec) {
|
2023-03-28 13:33:49 +00:00
|
|
|
forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower
|
2024-02-11 13:27:22 +00:00
|
|
|
}
|
2023-03-28 13:33:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Send a command to the display (low level function)
|
|
|
|
void EInkDisplay::sendCommand(uint8_t com)
|
|
|
|
{
|
|
|
|
(void)com;
|
|
|
|
// Drop all commands to device (we just update the buffer)
|
|
|
|
}
|
|
|
|
|
|
|
|
void EInkDisplay::setDetected(uint8_t detected)
|
|
|
|
{
|
|
|
|
(void)detected;
|
|
|
|
}
|
|
|
|
|
2024-02-28 15:45:15 +00:00
|
|
|
// Connect to the display - variant specific
|
2023-03-28 13:33:49 +00:00
|
|
|
bool EInkDisplay::connect()
|
|
|
|
{
|
|
|
|
LOG_INFO("Doing EInk init\n");
|
|
|
|
|
|
|
|
#ifdef PIN_EINK_EN
|
|
|
|
// backlight power, HIGH is backlight on, LOW is off
|
|
|
|
pinMode(PIN_EINK_EN, OUTPUT);
|
2023-12-02 13:40:31 +00:00
|
|
|
digitalWrite(PIN_EINK_EN, LOW);
|
2023-03-28 13:33:49 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(TTGO_T_ECHO)
|
|
|
|
{
|
2024-02-28 15:45:15 +00:00
|
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
|
2023-03-28 13:33:49 +00:00
|
|
|
|
2024-02-28 15:45:15 +00:00
|
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
2023-03-28 13:33:49 +00:00
|
|
|
adafruitDisplay->init();
|
|
|
|
adafruitDisplay->setRotation(3);
|
2024-01-19 16:28:26 +00:00
|
|
|
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
2023-03-28 13:33:49 +00:00
|
|
|
}
|
|
|
|
#elif defined(RAK4630) || defined(MAKERPYTHON)
|
|
|
|
{
|
|
|
|
if (eink_found) {
|
2024-02-28 15:45:15 +00:00
|
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
|
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
2023-03-28 13:33:49 +00:00
|
|
|
adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
2024-02-23 13:45:23 +00:00
|
|
|
// RAK14000 2.13 inch b/w 250x122 does actually now support fast refresh
|
2023-03-28 13:33:49 +00:00
|
|
|
adafruitDisplay->setRotation(3);
|
2024-02-23 13:45:23 +00:00
|
|
|
// Fast refresh support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2
|
2023-03-28 13:33:49 +00:00
|
|
|
// adafruitDisplay->setRotation(1);
|
|
|
|
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
|
|
|
|
} else {
|
|
|
|
(void)adafruitDisplay;
|
|
|
|
}
|
|
|
|
}
|
2024-02-09 01:00:13 +00:00
|
|
|
|
2024-07-09 17:16:56 +00:00
|
|
|
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) || \
|
2024-08-30 10:53:06 +00:00
|
|
|
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER)
|
2024-02-09 01:00:13 +00:00
|
|
|
{
|
|
|
|
// Start HSPI
|
|
|
|
hspi = new SPIClass(HSPI);
|
|
|
|
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
|
|
|
|
|
2024-04-03 11:59:53 +00:00
|
|
|
// VExt already enabled in setup()
|
|
|
|
// RTC GPIO hold disabled in setup()
|
2024-02-09 01:00:13 +00:00
|
|
|
|
|
|
|
// Create GxEPD2 objects
|
2024-02-28 15:45:15 +00:00
|
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
|
|
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
2024-02-09 01:00:13 +00:00
|
|
|
|
|
|
|
// Init GxEPD2
|
|
|
|
adafruitDisplay->init();
|
|
|
|
adafruitDisplay->setRotation(3);
|
|
|
|
}
|
2024-08-05 10:59:57 +00:00
|
|
|
#elif defined(PCA10059) || defined(ME25LS01)
|
2023-03-28 13:33:49 +00:00
|
|
|
{
|
2024-02-28 15:45:15 +00:00
|
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
|
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
2024-08-05 10:59:57 +00:00
|
|
|
adafruitDisplay->init(115200, true, 40, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
|
|
|
adafruitDisplay->setRotation(0);
|
|
|
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
2023-03-28 13:33:49 +00:00
|
|
|
}
|
|
|
|
#elif defined(M5_COREINK)
|
2024-02-28 15:45:15 +00:00
|
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
|
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
2024-02-21 13:45:23 +00:00
|
|
|
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
|
|
|
adafruitDisplay->setRotation(0);
|
2024-02-28 15:45:15 +00:00
|
|
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
2024-02-21 13:18:36 +00:00
|
|
|
#elif defined(my) || defined(ESP32_S3_PICO)
|
2023-03-28 13:33:49 +00:00
|
|
|
{
|
2024-02-28 15:45:15 +00:00
|
|
|
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
|
|
|
|
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
2023-03-28 13:33:49 +00:00
|
|
|
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
|
|
|
adafruitDisplay->setRotation(1);
|
2024-02-28 15:45:15 +00:00
|
|
|
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
2023-03-28 13:33:49 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-02-21 13:18:36 +00:00
|
|
|
#endif
|