diff --git a/software/firmware/source/SoftRF/src/driver/EPD.cpp b/software/firmware/source/SoftRF/src/driver/EPD.cpp index 37a1d520e..2c9929e89 100644 --- a/software/firmware/source/SoftRF/src/driver/EPD.cpp +++ b/software/firmware/source/SoftRF/src/driver/EPD.cpp @@ -546,6 +546,10 @@ void EPD_fini(int reason, bool screen_saver) uint16_t tbw, tbh; uint16_t x, y; + uint16_t display_width; + uint16_t display_height; + int16_t dy = 0; + SoC->ADB_ops && SoC->ADB_ops->fini(); const char *msg = (reason == SOFTRF_SHUTDOWN_LOWBAT ? @@ -553,12 +557,27 @@ void EPD_fini(int reason, bool screen_saver) switch (hw_info.display) { -#if defined(EPD_ASPECT_RATIO_1C1) +#if defined(EPD_ASPECT_RATIO_1C1) || defined(EPD_ASPECT_RATIO_2C1) case DISPLAY_EPD_1_54: + case DISPLAY_EPD_2_13: #if defined(USE_EPD_TASK) while (EPD_update_in_progress != EPD_UPDATE_NONE) delay(100); // while (!SoC->Display_lock()) { delay(10); } #endif + display_width = display->width(); + display_height = display->height(); + +#if defined(EPD_ASPECT_RATIO_2C1) + + if (display->epd2.panel == GxEPD2::DEPG0213BN) { + if (display_width == 128) display_width = 122; + if (display_height == 128) { + display_height = 122; + if (display->getRotation() == ROTATE_90 ) { dy = 6; } + } + } +#endif /* EPD_ASPECT_RATIO_2C1 */ + if (screen_saver) { const char *msg_line; @@ -568,16 +587,16 @@ void EPD_fini(int reason, bool screen_saver) msg_line = "POWER OFF"; display->getTextBounds(msg_line, 0, 0, &tbx, &tby, &tbw, &tbh); - x = (display->width() - tbw) / 2; - y = display->height() / 3; + x = (display_width - tbw) / 2; + y = display_height / 3; display->setCursor(x, y); display->print(msg_line); msg_line = "SCREEN SAVER"; display->getTextBounds(msg_line, 0, 0, &tbx, &tby, &tbw, &tbh); - x = (display->width() - tbw) / 2; - y = (2 * display->height()) / 3; + x = (display_width - tbw) / 2; + y = (2 * display_height) / 3; display->setCursor(x, y); display->print(msg_line); @@ -608,19 +627,28 @@ void EPD_fini(int reason, bool screen_saver) display->setFont(&FreeMonoBold12pt7b); display->getTextBounds(msg, 0, 0, &tbx, &tby, &tbw, &tbh); - x = (display->width() - tbw) / 2; + x = (display_width - tbw) / 2; y = tbh + tbh / 2; display->setCursor(x, y); display->print(msg); - x = (display->width() - 128) / 2; - y = (display->height() - 128) / 2 - tbh / 2; +#if defined(EPD_ASPECT_RATIO_1C1) + x = (display_width - 128) / 2; + y = (display_height - 128) / 2 - tbh / 2; display->drawBitmap(x, y, sleep_icon_128x128, 128, 128, GxEPD_BLACK); +#endif /* EPD_ASPECT_RATIO_1C1 */ display->setFont(&Org_01); display->getTextBounds(EPD_SoftRF_text4, 0, 0, &tbx, &tby, &tbw, &tbh); + +#if defined(EPD_ASPECT_RATIO_1C1) x = 5; y += 128 + 17; +#endif /* EPD_ASPECT_RATIO_1C1 */ +#if defined(EPD_ASPECT_RATIO_2C1) + x = 30; + y = (3 * display_height) / 4; +#endif /* EPD_ASPECT_RATIO_2C1 */ display->setCursor(x, y); display->print(EPD_SoftRF_text4); @@ -629,7 +657,7 @@ void EPD_fini(int reason, bool screen_saver) display->setFont(&FreeSerif9pt7b); display->getTextBounds(EPD_SoftRF_text6, 0, 0, &tbx, &tby, &tbw, &tbh); - x = (display->width() - tbw) / 2; + x = (display_width - tbw) / 2; y += 21; display->setCursor(x, y); display->print(EPD_SoftRF_text6); @@ -652,7 +680,7 @@ void EPD_fini(int reason, bool screen_saver) // SoC->Display_unlock(); break; -#endif /* EPD_ASPECT_RATIO_1C1 */ +#endif /* EPD_ASPECT_RATIO_1C1 || EPD_ASPECT_RATIO_2C1 */ case DISPLAY_NONE: default: diff --git a/software/firmware/source/SoftRF/src/driver/EPD.h b/software/firmware/source/SoftRF/src/driver/EPD.h index e13e22fba..7e533192b 100644 --- a/software/firmware/source/SoftRF/src/driver/EPD.h +++ b/software/firmware/source/SoftRF/src/driver/EPD.h @@ -22,6 +22,13 @@ #if defined(USE_EPAPER) #define ENABLE_GxEPD2_GFX 1 #include + +extern const GFXfont FreeMono9pt7b PROGMEM; +extern const GFXfont FreeMonoBold9pt7b PROGMEM; +extern const GFXfont FreeMonoBold12pt7b PROGMEM; +extern const GFXfont FreeMono18pt7b PROGMEM; +extern const GFXfont FreeMonoBold18pt7b PROGMEM; +extern const GFXfont FreeMonoBold24pt7b PROGMEM; #endif /* USE_EPAPER */ #define EPD_EXPIRATION_TIME 5 /* seconds */ diff --git a/software/firmware/source/SoftRF/src/platform/nRF52.cpp b/software/firmware/source/SoftRF/src/platform/nRF52.cpp index 913fabdf6..7544e103f 100644 --- a/software/firmware/source/SoftRF/src/platform/nRF52.cpp +++ b/software/firmware/source/SoftRF/src/platform/nRF52.cpp @@ -193,7 +193,7 @@ GxEPD2_BW epd_bn (GxEPD2_150_BN( SOC_GPIO_PIN_EPD_DC, SOC_GPIO_PIN_EPD_RST, SOC_GPIO_PIN_EPD_BUSY)); -GxEPD2_BW epd_w7 (GxEPD2_371( +GxEPD2_BW epd_t3 (GxEPD2_371_T03( SOC_GPIO_PIN_EPD_TULTIMA_SS, SOC_GPIO_PIN_EPD_TULTIMA_DC, SOC_GPIO_PIN_EPD_TULTIMA_RST, @@ -2141,7 +2141,7 @@ static byte nRF52_Display_setup() SPI1.setPins(SOC_GPIO_PIN_EPD_TULTIMA_MISO, SOC_GPIO_PIN_EPD_TULTIMA_SCK, SOC_GPIO_PIN_EPD_TULTIMA_MOSI); - nRF52_display = EP_GDEW0371W7; + nRF52_display = EP_GDEY037T03; break; case NRF52_LILYGO_TECHO_REV_0: case NRF52_LILYGO_TECHO_REV_1: @@ -2167,8 +2167,8 @@ static byte nRF52_Display_setup() case EP_DEPG0150BN: display = &epd_bn; break; - case EP_GDEW0371W7: - display = &epd_w7; + case EP_GDEY037T03: + display = &epd_t3; break; case EP_GDEH0154D67: default: diff --git a/software/firmware/source/SoftRF/src/platform/nRF52.h b/software/firmware/source/SoftRF/src/platform/nRF52.h index 51efc2901..3e9d883ef 100644 --- a/software/firmware/source/SoftRF/src/platform/nRF52.h +++ b/software/firmware/source/SoftRF/src/platform/nRF52.h @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + #if defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_ARCH_NRF52840) #ifndef PLATFORM_NRF52_H @@ -76,7 +77,7 @@ enum nRF52_display_id { EP_GDEH0154D67, EP_GDEP015OC1, EP_DEPG0150BN, - EP_GDEW0371W7, + EP_GDEY037T03, }; typedef struct { diff --git a/software/firmware/source/SoftRF/src/ui/Baro_EPD.cpp b/software/firmware/source/SoftRF/src/ui/Baro_EPD.cpp index 6841dbe32..9cf5ae04a 100644 --- a/software/firmware/source/SoftRF/src/ui/Baro_EPD.cpp +++ b/software/firmware/source/SoftRF/src/ui/Baro_EPD.cpp @@ -23,9 +23,7 @@ #include "../driver/EPD.h" #include "../driver/Baro.h" -#include #include -#include #include const char Altitude_text[] = "ALTITUDE, "; diff --git a/software/firmware/source/SoftRF/src/ui/IMU_EPD.cpp b/software/firmware/source/SoftRF/src/ui/IMU_EPD.cpp index bdcd4dfb7..3fe00c091 100644 --- a/software/firmware/source/SoftRF/src/ui/IMU_EPD.cpp +++ b/software/firmware/source/SoftRF/src/ui/IMU_EPD.cpp @@ -18,15 +18,14 @@ #include "../system/SoC.h" +#if !defined(EXCLUDE_IMU) +float IMU_g = 0; +#endif /* EXCLUDE_IMU */ + #if defined(USE_EPAPER) #include "../driver/EPD.h" -#include -#include - -float IMU_g = 0; - static const char G_load_text[] = "G-load"; void EPD_imu_setup() diff --git a/software/firmware/source/SoftRF/src/ui/Radar_EPD.cpp b/software/firmware/source/SoftRF/src/ui/Radar_EPD.cpp index bc23614b3..05b194398 100644 --- a/software/firmware/source/SoftRF/src/ui/Radar_EPD.cpp +++ b/software/firmware/source/SoftRF/src/ui/Radar_EPD.cpp @@ -31,9 +31,7 @@ #include "../driver/LED.h" #include "../driver/RF.h" -#include #include -#include #include static int EPD_zoom = ZOOM_MEDIUM; diff --git a/software/firmware/source/SoftRF/src/ui/Status_EPD.cpp b/software/firmware/source/SoftRF/src/ui/Status_EPD.cpp index 29b717a59..4880ed1df 100644 --- a/software/firmware/source/SoftRF/src/ui/Status_EPD.cpp +++ b/software/firmware/source/SoftRF/src/ui/Status_EPD.cpp @@ -29,7 +29,6 @@ #include #include -#include const char ID_text[] = "ID"; const char PROTOCOL_text[] = "PROTOCOL"; diff --git a/software/firmware/source/SoftRF/src/ui/Text_EPD.cpp b/software/firmware/source/SoftRF/src/ui/Text_EPD.cpp index 99822be0b..3394309b9 100644 --- a/software/firmware/source/SoftRF/src/ui/Text_EPD.cpp +++ b/software/firmware/source/SoftRF/src/ui/Text_EPD.cpp @@ -34,8 +34,6 @@ #include #include "../protocol/radio/Legacy.h" -#include - const char *EPD_Aircraft_Type[] = { [AIRCRAFT_TYPE_UNKNOWN] = "Unknown", [AIRCRAFT_TYPE_GLIDER] = "Glider", diff --git a/software/firmware/source/SoftRF/src/ui/Time_EPD.cpp b/software/firmware/source/SoftRF/src/ui/Time_EPD.cpp index 70ecadaa6..88fc8881e 100644 --- a/software/firmware/source/SoftRF/src/ui/Time_EPD.cpp +++ b/software/firmware/source/SoftRF/src/ui/Time_EPD.cpp @@ -30,9 +30,6 @@ extern RTC_Date fw_build_date_time; #endif /* ARDUINO_ARCH_NRF52 */ -#include -#include -#include #include "U8g2_for_Adafruit_GFX.h" static const char TZ_text[] = "UTC"; diff --git a/software/firmware/source/libraries/GxEPD2/src/GxEPD2.h b/software/firmware/source/libraries/GxEPD2/src/GxEPD2.h index a862bf5ce..85c8fc512 100644 --- a/software/firmware/source/libraries/GxEPD2/src/GxEPD2.h +++ b/software/firmware/source/libraries/GxEPD2/src/GxEPD2.h @@ -47,6 +47,7 @@ class GxEPD2 GDEW027W3, Waveshare_2_7_bw = GDEW027W3, GDEY027T91, GDEW0371W7, Waveshare_3_7_bw = GDEW0371W7, + GDEY037T03, GDEW042T2, Waveshare_4_2_bw = GDEW042T2, GDEW0583T7, Waveshare_5_83_bw = GDEW0583T7, GDEW075T8, Waveshare_7_5_bw = GDEW075T8, diff --git a/software/firmware/source/libraries/GxEPD2/src/GxEPD2_BW.h b/software/firmware/source/libraries/GxEPD2/src/GxEPD2_BW.h index 43732aa4b..12b386b4b 100644 --- a/software/firmware/source/libraries/GxEPD2/src/GxEPD2_BW.h +++ b/software/firmware/source/libraries/GxEPD2/src/GxEPD2_BW.h @@ -28,6 +28,7 @@ #include "epd/GxEPD2_270.h" #include "epd/GxEPD2_270_T91.h" #include "epd/GxEPD2_371.h" +#include "epd/GxEPD2_371_T03.h" #include "epd/GxEPD2_420.h" #include "epd/GxEPD2_583.h" #include "epd/GxEPD2_750.h" diff --git a/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.cpp b/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.cpp index 69ba80e76..8ad5f0f47 100644 --- a/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.cpp +++ b/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.cpp @@ -33,6 +33,7 @@ GxEPD2_EPD::GxEPD2_EPD(int8_t cs, int8_t dc, int8_t rst, int8_t busy, int8_t bus _power_is_on = false; _using_partial_mode = false; _hibernating = false; + _init_display_done = false; _timeout_expired = false; _busy_callback = 0; _busy_callback_parameter = 0; @@ -51,6 +52,7 @@ void GxEPD2_EPD::init(uint32_t serial_diag_bitrate, bool initial, bool pulldown_ _power_is_on = false; _using_partial_mode = false; _hibernating = false; + _init_display_done = false; _timeout_expired = false; if (serial_diag_bitrate > 0) { @@ -269,3 +271,20 @@ void GxEPD2_EPD::_writeCommandDataPGM(const uint8_t* pCommandData, uint8_t datal if (_cs >= 0) digitalWrite(_cs, HIGH); _pSPIx->endTransaction(); } + +void GxEPD2_EPD::_startTransfer() +{ + _pSPIx->beginTransaction(_spi_settings); + if (_cs >= 0) digitalWrite(_cs, LOW); +} + +void GxEPD2_EPD::_transfer(uint8_t value) +{ + _pSPIx->transfer(value); +} + +void GxEPD2_EPD::_endTransfer() +{ + if (_cs >= 0) digitalWrite(_cs, HIGH); + _pSPIx->endTransaction(); +} diff --git a/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.h b/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.h index 04d97e8f1..cf81171a3 100644 --- a/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.h +++ b/software/firmware/source/libraries/GxEPD2/src/GxEPD2_EPD.h @@ -112,6 +112,9 @@ class GxEPD2_EPD void _writeDataPGM_sCS(const uint8_t* data, uint16_t n, int16_t fill_with_zeroes = 0); void _writeCommandData(const uint8_t* pCommandData, uint8_t datalen); void _writeCommandDataPGM(const uint8_t* pCommandData, uint8_t datalen); + void _startTransfer(); + void _transfer(uint8_t value); + void _endTransfer(); protected: int8_t _cs, _dc, _rst, _busy, _busy_level; uint32_t _busy_timeout; @@ -120,6 +123,7 @@ class GxEPD2_EPD SPISettings _spi_settings; bool _initial_write, _initial_refresh; bool _power_is_on, _using_partial_mode, _hibernating; + bool _init_display_done; bool _timeout_expired; void (*_busy_callback)(const void*); const void* _busy_callback_parameter; diff --git a/software/firmware/source/libraries/GxEPD2/src/epd/GxEPD2_371_T03.cpp b/software/firmware/source/libraries/GxEPD2/src/epd/GxEPD2_371_T03.cpp new file mode 100644 index 000000000..ffd08b69b --- /dev/null +++ b/software/firmware/source/libraries/GxEPD2/src/epd/GxEPD2_371_T03.cpp @@ -0,0 +1,396 @@ +// Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare. +// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! +// +// based on Demo Example from Good Display: https://www.good-display.com/product/437.html +// Panel: GDEQ031T10 : https://www.good-display.com/product/437.html +// Controller: UC8253 : https://www.good-display.com/public/html/pdfjs/viewer/viewernew.html?file=https://v4.cecdn.yun300.cn/100001_1909185148/UC8253.pdf +// +// Author: Jean-Marc Zingg +// +// Version: see library.properties +// +// Library: https://github.com/ZinggJM/GxEPD2 + +#include "GxEPD2_371_T03.h" + +GxEPD2_371_T03::GxEPD2_371_T03(int16_t cs, int16_t dc, int16_t rst, int16_t busy) : + GxEPD2_EPD(cs, dc, rst, busy, LOW, 10000000, WIDTH, HEIGHT, panel, hasColor, hasPartialUpdate, hasFastPartialUpdate) +{ +} + +void GxEPD2_371_T03::clearScreen(uint8_t value) +{ + // full refresh needed for all cases (previous != screen) + _writeScreenBuffer(0x10, value); // set previous + _writeScreenBuffer(0x13, value); // set current + refresh(false); // full refresh + _initial_write = false; +} + +void GxEPD2_371_T03::writeScreenBuffer(uint8_t value) +{ + if (_initial_write) return clearScreen(value); + _writeScreenBuffer(0x13, value); // set current +} + +void GxEPD2_371_T03::writeScreenBufferAgain(uint8_t value) +{ + _writeScreenBuffer(0x10, value); // set previous + //_writeScreenBuffer(0x13, value); // set current, not needed +} + +void GxEPD2_371_T03::_writeScreenBuffer(uint8_t command, uint8_t value) +{ + if (!_init_display_done) _InitDisplay(); + _writeCommand(command); + _startTransfer(); + for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++) + { + _transfer(value); + } + _endTransfer(); +} + +void GxEPD2_371_T03::writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) +{ + _writeImage(0x13, bitmap, x, y, w, h, invert, mirror_y, pgm); +} + +void GxEPD2_371_T03::writeImageForFullRefresh(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) +{ + _writeImage(0x10, bitmap, x, y, w, h, invert, mirror_y, pgm); // set previous + _writeImage(0x13, bitmap, x, y, w, h, invert, mirror_y, pgm); // set current +} + +void GxEPD2_371_T03::writeImageAgain(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) +{ + _writeImage(0x10, bitmap, x, y, w, h, invert, mirror_y, pgm); // set previous + //_writeImage(0x13, bitmap, x, y, w, h, invert, mirror_y, pgm); // set current, not needed +} + +void GxEPD2_371_T03::_writeImage(uint8_t command, const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) +{ + delay(1); // yield() to avoid WDT on ESP8266 and ESP32 + uint16_t wb = (w + 7) / 8; // width bytes, bitmaps are padded + x -= x % 8; // byte boundary + w = wb * 8; // byte boundary + int16_t x1 = x < 0 ? 0 : x; // limit + int16_t y1 = y < 0 ? 0 : y; // limit + int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit + int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit + int16_t dx = x1 - x; + int16_t dy = y1 - y; + w1 -= dx; + h1 -= dy; + if ((w1 <= 0) || (h1 <= 0)) return; + if (!_init_display_done) _InitDisplay(); + if (_initial_write) writeScreenBuffer(); // initial full screen buffer clean + _writeCommand(0x91); // partial in + _setPartialRamArea(x1, y1, w1, h1); + _writeCommand(command); + _startTransfer(); + for (int16_t i = 0; i < h1; i++) + { + for (int16_t j = 0; j < w1 / 8; j++) + { + uint8_t data; + // use wb, h of bitmap for index! + uint16_t idx = mirror_y ? j + dx / 8 + uint16_t((h - 1 - (i + dy))) * wb : j + dx / 8 + uint16_t(i + dy) * wb; + if (pgm) + { +#if defined(__AVR) || defined(ESP8266) || defined(ESP32) + data = pgm_read_byte(&bitmap[idx]); +#else + data = bitmap[idx]; +#endif + } + else + { + data = bitmap[idx]; + } + if (invert) data = ~data; + _transfer(data); + } + } + _endTransfer(); + _writeCommand(0x92); // partial out + delay(1); // yield() to avoid WDT on ESP8266 and ESP32 +} + +void GxEPD2_371_T03::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, bool mirror_y, bool pgm) +{ + _writeImagePart(0x13, bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); +} + +void GxEPD2_371_T03::writeImagePartAgain(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, bool mirror_y, bool pgm) +{ + _writeImagePart(0x10, bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); // set previous + //_writeImagePart(0x13, bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); // set current, not needed +} + +void GxEPD2_371_T03::_writeImagePart(uint8_t command, 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, bool mirror_y, bool pgm) +{ + delay(1); // yield() to avoid WDT on ESP8266 and ESP32 + if ((w_bitmap < 0) || (h_bitmap < 0) || (w < 0) || (h < 0)) return; + if ((x_part < 0) || (x_part >= w_bitmap)) return; + if ((y_part < 0) || (y_part >= h_bitmap)) return; + uint16_t wb_bitmap = (w_bitmap + 7) / 8; // width bytes, bitmaps are padded + x_part -= x_part % 8; // byte boundary + w = w_bitmap - x_part < w ? w_bitmap - x_part : w; // limit + h = h_bitmap - y_part < h ? h_bitmap - y_part : h; // limit + x -= x % 8; // byte boundary + w = 8 * ((w + 7) / 8); // byte boundary, bitmaps are padded + int16_t x1 = x < 0 ? 0 : x; // limit + int16_t y1 = y < 0 ? 0 : y; // limit + int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit + int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit + int16_t dx = x1 - x; + int16_t dy = y1 - y; + w1 -= dx; + h1 -= dy; + if ((w1 <= 0) || (h1 <= 0)) return; + if (!_init_display_done) _InitDisplay(); + if (_initial_write) writeScreenBuffer(); // initial full screen buffer clean + _writeCommand(0x91); // partial in + _setPartialRamArea(x1, y1, w1, h1); + _writeCommand(command); + _startTransfer(); + for (int16_t i = 0; i < h1; i++) + { + for (int16_t j = 0; j < w1 / 8; j++) + { + uint8_t data; + // use wb_bitmap, h_bitmap of bitmap for index! + uint16_t idx = mirror_y ? x_part / 8 + j + dx / 8 + uint16_t((h_bitmap - 1 - (y_part + i + dy))) * wb_bitmap : x_part / 8 + j + dx / 8 + uint16_t(y_part + i + dy) * wb_bitmap; + if (pgm) + { +#if defined(__AVR) || defined(ESP8266) || defined(ESP32) + data = pgm_read_byte(&bitmap[idx]); +#else + data = bitmap[idx]; +#endif + } + else + { + data = bitmap[idx]; + } + if (invert) data = ~data; + _transfer(data); + } + } + _endTransfer(); + _writeCommand(0x92); // partial out + delay(1); // yield() to avoid WDT on ESP8266 and ESP32 +} + +void GxEPD2_371_T03::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 (black) + { + writeImage(black, x, y, w, h, invert, mirror_y, pgm); + } +} + +void GxEPD2_371_T03::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 (black) + { + writeImagePart(black, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); + } +} + +void GxEPD2_371_T03::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 (data1) + { + writeImage(data1, x, y, w, h, invert, mirror_y, pgm); + } +} + +void GxEPD2_371_T03::drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) +{ + writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm); + refresh(x, y, w, h); + writeImageAgain(bitmap, x, y, w, h, invert, mirror_y, pgm); +} + +void GxEPD2_371_T03::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, bool mirror_y, bool pgm) +{ + writeImagePart(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); + refresh(x, y, w, h); + writeImagePartAgain(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); +} + +void GxEPD2_371_T03::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 (black) + { + drawImage(black, x, y, w, h, invert, mirror_y, pgm); + } +} + +void GxEPD2_371_T03::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 (black) + { + drawImagePart(black, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); + } +} + +void GxEPD2_371_T03::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 (data1) + { + drawImage(data1, x, y, w, h, invert, mirror_y, pgm); + } +} + +void GxEPD2_371_T03::refresh(bool partial_update_mode) +{ + if (partial_update_mode) refresh(0, 0, WIDTH, HEIGHT); + else + { + _Update_Full(); + _initial_refresh = false; // initial full update done + } +} + +void GxEPD2_371_T03::refresh(int16_t x, int16_t y, int16_t w, int16_t h) +{ + if (_initial_refresh) return refresh(false); // initial update needs be full update + // intersection with screen + int16_t w1 = x < 0 ? w + x : w; // reduce + int16_t h1 = y < 0 ? h + y : h; // reduce + int16_t x1 = x < 0 ? 0 : x; // limit + int16_t y1 = y < 0 ? 0 : y; // limit + w1 = x1 + w1 < int16_t(WIDTH) ? w1 : int16_t(WIDTH) - x1; // limit + h1 = y1 + h1 < int16_t(HEIGHT) ? h1 : int16_t(HEIGHT) - y1; // limit + if ((w1 <= 0) || (h1 <= 0)) return; + // make x1, w1 multiple of 8 + w1 += x1 % 8; + if (w1 % 8 > 0) w1 += 8 - w1 % 8; + x1 -= x1 % 8; + if (usePartialUpdateWindow) _writeCommand(0x91); // partial in + _setPartialRamArea(x1, y1, w1, h1); + _Update_Part(); + if (usePartialUpdateWindow) _writeCommand(0x92); // partial out +} + +void GxEPD2_371_T03::powerOff(void) +{ + _PowerOff(); +} + +void GxEPD2_371_T03::hibernate() +{ + _PowerOff(); + if (_rst >= 0) + { + _writeCommand(0x07); // deep sleep + _writeData(0xA5); // check code + _hibernating = true; + _init_display_done = false; + } +} + +void GxEPD2_371_T03::_setPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h) +{ + uint16_t xe = (x + w - 1) | 0x0007; // byte boundary inclusive (last byte) + uint16_t ye = y + h - 1; + x &= 0xFFF8; // byte boundary + _writeCommand(0x90); // partial window + _writeData(x); + _writeData(xe); + _writeData(y / 256); + _writeData(y % 256); + _writeData(ye / 256); + _writeData(ye % 256); + _writeData(0x01); +} + +void GxEPD2_371_T03::_PowerOn() +{ + if (!_power_is_on) + { + _writeCommand(0x04); + _waitWhileBusy("_PowerOn", power_on_time); + } + _power_is_on = true; +} + +void GxEPD2_371_T03::_PowerOff() +{ + if (_power_is_on) + { + _writeCommand(0x02); // power off + _waitWhileBusy("_PowerOff", power_off_time); + } + _power_is_on = false; +} + +void GxEPD2_371_T03::_InitDisplay() +{ + if (_hibernating) _reset(); + else + { + _writeCommand(0x00); // PANEL SETTING + _writeData(0x1e); // soft reset + _writeData(0x0d); + delay(1); + } + _power_is_on = false; + _writeCommand(0x00); // PANEL SETTING + _writeData(0x1f); // KW: 3f, KWR: 2F, BWROTP: 0f, BWOTP: 1f + _writeData(0x0d); + _init_display_done = true; +} + +void GxEPD2_371_T03::_Update_Full() +{ + if (useFastFullUpdate) + { + _writeCommand(0xE0); // Cascade Setting (CCSET) + _writeData(0x02); // TSFIX + _writeCommand(0xE5); // Force Temperature (TSSET) + _writeData(0x5A); // 90, 1015000us + //_writeData(0x6E); // 110, 1542001 + } + _writeCommand(0x50); + _writeData(0x97); + _PowerOn(); + _writeCommand(0x12); //display refresh + _waitWhileBusy("_Update_Full", full_refresh_time); + _init_display_done = false; // needed, reason unknown +} + +void GxEPD2_371_T03::_Update_Part() +{ + if (hasFastPartialUpdate) + { + _writeCommand(0xE0); // Cascade Setting (CCSET) + _writeData(0x02); // TSFIX + _writeCommand(0xE5); // Force Temperature (TSSET) + _writeData(0x79); // 121 + } + _writeCommand(0x50); + _writeData(0xD7); + _PowerOn(); + _writeCommand(0x12); //display refresh + _waitWhileBusy("_Update_Part", partial_refresh_time); + _init_display_done = false; // needed, reason unknown +} + +bool GxEPD2_371_T03::probe() +{ + if (_timeout_expired) { + return false; + } else { + return true; + } +} diff --git a/software/firmware/source/libraries/GxEPD2/src/epd/GxEPD2_371_T03.h b/software/firmware/source/libraries/GxEPD2/src/epd/GxEPD2_371_T03.h new file mode 100644 index 000000000..df320fd24 --- /dev/null +++ b/software/firmware/source/libraries/GxEPD2/src/epd/GxEPD2_371_T03.h @@ -0,0 +1,84 @@ +// Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare. +// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! +// +// based on Demo Example from Good Display: https://www.good-display.com/product/437.html +// Panel: GDEQ031T10 : https://www.good-display.com/product/437.html +// Controller: UC8253 : https://www.good-display.com/public/html/pdfjs/viewer/viewernew.html?file=https://v4.cecdn.yun300.cn/100001_1909185148/UC8253.pdf +// +// Author: Jean-Marc Zingg +// +// Version: see library.properties +// +// Library: https://github.com/ZinggJM/GxEPD2 + +#ifndef _GxEPD2_371_T03_H_ +#define _GxEPD2_371_T03_H_ + +#include "../GxEPD2_EPD.h" + +class GxEPD2_371_T03 : public GxEPD2_EPD +{ + public: + // attributes + static const uint16_t WIDTH = 240; + static const uint16_t WIDTH_VISIBLE = WIDTH; + static const uint16_t HEIGHT = 416; + static const GxEPD2::Panel panel = GxEPD2::GDEY037T03; + static const bool hasColor = false; + static const bool hasPartialUpdate = true; + static const bool usePartialUpdateWindow = true; // set false for better image + static const bool hasFastPartialUpdate = true; // set this false to force full refresh always + static const bool useFastFullUpdate = true; // set false for extended (low) temperature range, 1015000us vs 3082001us + static const uint16_t power_on_time = 50; // ms, e.g. 45000us + static const uint16_t power_off_time = 50; // ms, e.g. 45000us + static const uint16_t full_refresh_time = 1100; // ms, e.g. 1015000us + static const uint16_t partial_refresh_time = 700; // ms, e.g. 650000us + // constructor + GxEPD2_371_T03(int16_t cs, int16_t dc, int16_t rst, int16_t busy); + // methods (virtual) + // Support for Bitmaps (Sprites) to Controller Buffer and to Screen + void clearScreen(uint8_t value = 0xFF); // init controller memory and screen (default white) + void writeScreenBuffer(uint8_t value = 0xFF); // init controller memory (default white) + void writeScreenBufferAgain(uint8_t value = 0xFF); // init previous buffer controller memory (default white) + // 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); + void writeImageForFullRefresh(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); + 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); + void writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); + 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 = false, bool mirror_y = false, bool pgm = false); + // for differential update: set current and previous buffers equal (for fast partial update to work correctly) + void writeImageAgain(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); + void writeImagePartAgain(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); + // 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 = false, bool mirror_y = false, bool pgm = false); + // 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); + 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); + void drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false); + 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 = false, bool mirror_y = false, bool pgm = false); + // 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 = false, bool mirror_y = false, bool pgm = false); + void refresh(bool partial_update_mode = false); // screen refresh from controller memory to full screen + void refresh(int16_t x, int16_t y, int16_t w, int16_t h); // screen refresh from controller memory, partial screen + void powerOff(); // turns off generation of panel driving voltages, avoids screen fading over time + void hibernate(); // turns powerOff() and sets controller to deep sleep for minimum power use, ONLY if wakeable by RST (rst >= 0) + bool probe(); + private: + void _writeScreenBuffer(uint8_t command, uint8_t value); + void _writeImage(uint8_t command, 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); + void _writeImagePart(uint8_t command, 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); + void _setPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h); + void _PowerOn(); + void _PowerOff(); + void _InitDisplay(); + void _Update_Full(); + void _Update_Part(); +}; + +#endif