From f972b62d89420abdac781113747b2c40ac873b41 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 09:06:52 -0500 Subject: [PATCH 1/4] Upgrade trunk to 1.24.0 (#6915) Co-authored-by: sachaw <11172820+sachaw@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 162fdfd2d..3a02034a6 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -1,6 +1,6 @@ version: 0.1 cli: - version: 1.22.15 + version: 1.24.0 plugins: sources: - id: trunk @@ -9,7 +9,7 @@ plugins: lint: enabled: - checkov@3.2.435 - - renovate@40.32.7 + - renovate@40.33.8 - prettier@3.5.3 - trufflehog@3.88.34 - yamllint@1.37.1 From cb9429e83e06b29679b5c85272b62615ce583bed Mon Sep 17 00:00:00 2001 From: dmarman Date: Thu, 29 May 2025 16:09:33 +0200 Subject: [PATCH 2/4] Added full support for LTR390UV readings of UV and Lux (#6872) * Added full support for LTR390UV readings of UV and Lux * Trunk formatting * Added full support for LTR390UV readings of UV and Lux * Trunk formatting * fix library check and unnecessary bit resolution change * fixed log info messages and removed unused dependency * Hopefully fixes git mess * fix variable scope and getMetrics returns * set metrics flags bavk to false in case something wrong happens while reading LTR390UV mode --------- Co-authored-by: Domingo Co-authored-by: Ben Meadors --- platformio.ini | 4 +- .../Telemetry/EnvironmentTelemetry.cpp | 18 +++++ .../Telemetry/Sensor/LTR390UVSensor.cpp | 73 +++++++++++++++++++ src/modules/Telemetry/Sensor/LTR390UVSensor.h | 25 +++++++ 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/modules/Telemetry/Sensor/LTR390UVSensor.cpp create mode 100644 src/modules/Telemetry/Sensor/LTR390UVSensor.h diff --git a/platformio.ini b/platformio.ini index e5d36d862..125dfb573 100644 --- a/platformio.ini +++ b/platformio.ini @@ -161,6 +161,8 @@ lib_deps = sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@1.1.2 # renovate: datasource=custom.pio depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/library/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@1.3.2 + # renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library + adafruit/Adafruit LTR390 Library@1.1.2 # renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/Adafruit PCT2075 adafruit/Adafruit PCT2075@1.0.5 @@ -190,4 +192,4 @@ lib_deps = # renovate: datasource=custom.pio depName=Bosch BME68x packageName=boschsensortec/library/BME68x Sensor Library boschsensortec/BME68x Sensor Library@1.3.40408 # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master - https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip + https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip \ No newline at end of file diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index 51f076552..6d29fecb2 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -52,6 +52,13 @@ BMP280Sensor bmp280Sensor; NullSensor bme280Sensor; #endif +#if __has_include() +#include "Sensor/LTR390UVSensor.h" +LTR390UVSensor ltr390uvSensor; +#else +NullSensor ltr390uvSensor; +#endif + #if __has_include() #include "Sensor/BME680Sensor.h" BME680Sensor bme680Sensor; @@ -231,6 +238,8 @@ int32_t EnvironmentTelemetryModule::runOnce() #endif if (bme280Sensor.hasSensor()) result = bme280Sensor.runOnce(); + if (ltr390uvSensor.hasSensor()) + result = ltr390uvSensor.runOnce(); if (bmp3xxSensor.hasSensor()) result = bmp3xxSensor.runOnce(); if (bme680Sensor.hasSensor()) @@ -524,6 +533,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m valid = valid && bme280Sensor.getMetrics(m); hasSensor = true; } + if (ltr390uvSensor.hasSensor()) { + valid = valid && ltr390uvSensor.getMetrics(m); + hasSensor = true; + } if (bmp3xxSensor.hasSensor()) { valid = valid && bmp3xxSensor.getMetrics(m); hasSensor = true; @@ -752,6 +765,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } + if (ltr390uvSensor.hasSensor()) { + result = ltr390uvSensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } if (bmp3xxSensor.hasSensor()) { result = bmp3xxSensor.handleAdminMessage(mp, request, response); if (result != AdminMessageHandleResult::NOT_HANDLED) diff --git a/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp b/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp new file mode 100644 index 000000000..fb84700c4 --- /dev/null +++ b/src/modules/Telemetry/Sensor/LTR390UVSensor.cpp @@ -0,0 +1,73 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "LTR390UVSensor.h" +#include "TelemetrySensor.h" +#include + +LTR390UVSensor::LTR390UVSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_LTR390UV, "LTR390UV") {} + +int32_t LTR390UVSensor::runOnce() +{ + LOG_INFO("Init sensor: %s", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + status = ltr390uv.begin(nodeTelemetrySensorsMap[sensorType].second); + ltr390uv.setMode(LTR390_MODE_UVS); + ltr390uv.setGain(LTR390_GAIN_18); // Datasheet default + ltr390uv.setResolution(LTR390_RESOLUTION_20BIT); // Datasheet default + + return initI2CSensor(); +} + +void LTR390UVSensor::setup() {} + +bool LTR390UVSensor::getMetrics(meshtastic_Telemetry *measurement) +{ + LOG_DEBUG("LTR390UV getMetrics"); + + // Because the sensor does not measure Lux and UV at the same time, we need to read them in two passes. + if (ltr390uv.newDataAvailable()) { + measurement->variant.environment_metrics.has_lux = true; + measurement->variant.environment_metrics.has_uv_lux = true; + + if (ltr390uv.getMode() == LTR390_MODE_ALS) { + lastLuxReading = 0.6 * ltr390uv.readALS() / (1 * 4); // Datasheet page 23 for gain x1 and 20bit resolution + LOG_DEBUG("LTR390UV Lux reading: %f", lastLuxReading); + + measurement->variant.environment_metrics.lux = lastLuxReading; + measurement->variant.environment_metrics.uv_lux = lastUVReading; + + ltr390uv.setGain( + LTR390_GAIN_18); // Recommended for UVI - x18. Do not change, 2300 UV Sensitivity only specified for x18 gain + ltr390uv.setMode(LTR390_MODE_UVS); + + return true; + + } else if (ltr390uv.getMode() == LTR390_MODE_UVS) { + lastUVReading = ltr390uv.readUVS() / + 2300.f; // Datasheet page 23 and page 6, only characterisation for gain x18 and 20bit resolution + LOG_DEBUG("LTR390UV UV reading: %f", lastUVReading); + + measurement->variant.environment_metrics.lux = lastLuxReading; + measurement->variant.environment_metrics.uv_lux = lastUVReading; + + ltr390uv.setGain( + LTR390_GAIN_1); // x1 gain will already max out the sensor at direct sunlight, so no need to increase it + ltr390uv.setMode(LTR390_MODE_ALS); + + return true; + } + } + + // In case we fail to read the sensor mode, set the has_lux and has_uv_lux back to false + measurement->variant.environment_metrics.has_lux = false; + measurement->variant.environment_metrics.has_uv_lux = false; + + return false; +} +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/LTR390UVSensor.h b/src/modules/Telemetry/Sensor/LTR390UVSensor.h new file mode 100644 index 000000000..40206bce8 --- /dev/null +++ b/src/modules/Telemetry/Sensor/LTR390UVSensor.h @@ -0,0 +1,25 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +class LTR390UVSensor : public TelemetrySensor +{ + private: + Adafruit_LTR390 ltr390uv = Adafruit_LTR390(); + float lastLuxReading = 0; + float lastUVReading = 0; + + protected: + virtual void setup() override; + + public: + LTR390UVSensor(); + virtual int32_t runOnce() override; + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; +}; + +#endif \ No newline at end of file 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 3/4] 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 From 7a7166d57555f42156eb15a8923667e63fdb6a95 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 29 May 2025 10:17:20 -0500 Subject: [PATCH 4/4] Revert "Add a new screen for heltec_wireless_paper. (#6894)" (#6918) This reverts commit ba535433543f40b0e15280c7beb9e0afabe56142. --- 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, 6 insertions(+), 624 deletions(-) delete mode 100644 src/graphics/EInkMultiWrapper.h delete mode 100644 src/graphics/niche/Drivers/EInk/E0213A367.cpp delete mode 100644 src/graphics/niche/Drivers/EInk/E0213A367.h diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index b518299f7..5a2749482 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_VISION_MASTER_E213) || \ +#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER) || 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,68 +228,6 @@ 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 965a3307a..93be197b0 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -4,7 +4,6 @@ #include "GxEPD2_BW.h" #include -#include "EinkMultiWrapper.h" /** * An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation. @@ -65,11 +64,8 @@ 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 deleted file mode 100644 index 3ac0b194d..000000000 --- a/src/graphics/EInkMultiWrapper.h +++ /dev/null @@ -1,327 +0,0 @@ -// 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 deleted file mode 100644 index 4f2a50ba7..000000000 --- a/src/graphics/niche/Drivers/EInk/E0213A367.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#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 deleted file mode 100644 index c00d73378..000000000 --- a/src/graphics/niche/Drivers/EInk/E0213A367.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - -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 e42a4df85..c8994b7f1 100644 --- a/variants/heltec_wireless_paper/nicheGraphics.h +++ b/variants/heltec_wireless_paper/nicheGraphics.h @@ -19,58 +19,12 @@ // 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 // ----------------------------- @@ -80,15 +34,8 @@ void setupNicheGraphics() // E-Ink Driver // ----------------------------- - Drivers::EInk *driver; - if((chipId &0x03) !=0x01) - { - driver = new Drivers::LCMEN213EFC1; - } - else - { - driver = new Drivers::E0213A367; - } + + Drivers::EInk *driver = new Drivers::LCMEN213EFC1; 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 762b793cc..51430ebff 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -8,8 +8,6 @@ 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 @@ -19,9 +17,9 @@ build_flags = -D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" lib_deps = ${esp32s3_base.lib_deps} - https://github.com/Quency-D/GxEPD2/archive/0513405847b281d9dea400488714643ef84507ec.zip + https://github.com/meshtastic/GxEPD2/archive/b202ebfec6a4821e098cf7a625ba0f6f2400292d.zip lewisxhe/PCF8563_Library@^1.0.1 -upload_speed = 921600 +upload_speed = 115200 [env:heltec-wireless-paper-inkhud] extends = esp32s3_base, inkhud