diff --git a/src/Display.cpp b/src/Display.cpp new file mode 100644 index 0000000..b19ce94 --- /dev/null +++ b/src/Display.cpp @@ -0,0 +1,416 @@ +// 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, available here: http://www.e-paper-display.com/download_detail/downloadsId=806.html +// Panel: GDEH0154D67 : http://www.e-paper-display.com/products_detail/productId=455.html +// Controller : SSD1681 : http://www.e-paper-display.com/download_detail/downloadsId=825.html +// +// Author: Jean-Marc Zingg +// +// Version: see library.properties +// +// Library: https://github.com/ZinggJM/GxEPD2 +// +// The original code from the author has been slightly modified to improve the performance for Watchy Project: +// Link: https://github.com/sqfmi/Watchy + +#include "Display.h" + +WatchyDisplay::WatchyDisplay(int16_t cs, int16_t dc, int16_t rst, int16_t busy) : + GxEPD2_EPD(cs, dc, rst, busy, HIGH, 10000000, WIDTH, HEIGHT, panel, hasColor, hasPartialUpdate, hasFastPartialUpdate) +{ + selectSPI(SPI, SPISettings(20000000, MSBFIRST, SPI_MODE0)); // Set SPI to 20Mhz (default is 4Mhz) +} + +void WatchyDisplay::clearScreen(uint8_t value) +{ + writeScreenBuffer(value); + refresh(true); + writeScreenBufferAgain(value); +} + +void WatchyDisplay::writeScreenBuffer(uint8_t value) +{ + if (!_using_partial_mode) _Init_Part(); + if (_initial_write) _writeScreenBuffer(0x26, value); // set previous + _writeScreenBuffer(0x24, value); // set current + _initial_write = false; // initial full screen buffer clean done +} + +void WatchyDisplay::writeScreenBufferAgain(uint8_t value) +{ + if (!_using_partial_mode) _Init_Part(); + _writeScreenBuffer(0x24, value); // set current +} + +void WatchyDisplay::_writeScreenBuffer(uint8_t command, uint8_t value) +{ + _startTransfer(); + _transferCommand(command); + for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++) + { + _transfer(value); + } + _endTransfer(); +} + +void WatchyDisplay::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(0x24, bitmap, x, y, w, h, invert, mirror_y, pgm); +} + +void WatchyDisplay::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(0x26, bitmap, x, y, w, h, invert, mirror_y, pgm); + _writeImage(0x24, bitmap, x, y, w, h, invert, mirror_y, pgm); +} + +void WatchyDisplay::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(0x24, bitmap, x, y, w, h, invert, mirror_y, pgm); +} + +void WatchyDisplay::_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) +{ + if (_initial_write) writeScreenBuffer(); // initial full screen buffer clean +#if defined(ESP8266) || defined(ESP32) + yield(); // avoid wdt +#endif + int16_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 (!_using_partial_mode) _Init_Part(); + _setPartialRamArea(x1, y1, w1, h1); + _startTransfer(); + _transferCommand(command); + 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! + int16_t idx = mirror_y ? j + dx / 8 + ((h - 1 - (i + dy))) * wb : j + dx / 8 + (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(); +#if defined(ESP8266) || defined(ESP32) + yield(); // avoid wdt +#endif +} + +void WatchyDisplay::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(0x24, bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); +} + +void WatchyDisplay::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(0x24, bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm); +} + +void WatchyDisplay::_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) +{ + if (_initial_write) writeScreenBuffer(); // initial full screen buffer clean +#if defined(ESP8266) || defined(ESP32) + yield(); // avoid wdt +#endif + 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; + int16_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 (!_using_partial_mode) _Init_Part(); + _setPartialRamArea(x1, y1, w1, h1); + _startTransfer(); + _transferCommand(command); + 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! + int16_t idx = mirror_y ? x_part / 8 + j + dx / 8 + ((h_bitmap - 1 - (y_part + i + dy))) * wb_bitmap : x_part / 8 + j + dx / 8 + (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(); +#if defined(ESP8266) || defined(ESP32) + yield(); // avoid wdt +#endif +} + +void WatchyDisplay::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 WatchyDisplay::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 WatchyDisplay::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 WatchyDisplay::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 WatchyDisplay::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 WatchyDisplay::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 WatchyDisplay::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 WatchyDisplay::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 WatchyDisplay::refresh(bool partial_update_mode) +{ + if (partial_update_mode) refresh(0, 0, WIDTH, HEIGHT); + else + { + if (_using_partial_mode) _Init_Full(); + _Update_Full(); + _initial_refresh = false; // initial full update done + } +} + +void WatchyDisplay::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 (!_using_partial_mode) _Init_Part(); + _setPartialRamArea(x1, y1, w1, h1); + _Update_Part(); +} + +void WatchyDisplay::powerOff() +{ + _PowerOff(); +} + +void WatchyDisplay::hibernate() +{ + //_PowerOff(); // Not needed before entering deep sleep + if (_rst >= 0) + { + _writeCommand(0x10); // deep sleep mode + _writeData(0x1); // enter deep sleep + _hibernating = true; + } +} + +void WatchyDisplay::_setPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h) +{ + _startTransfer(); + _transferCommand(0x11); // set ram entry mode + _transfer(0x03); // x increase, y increase : normal mode + _transferCommand(0x44); + _transfer(x / 8); + _transfer((x + w - 1) / 8); + _transferCommand(0x45); + _transfer(y % 256); + _transfer(y / 256); + _transfer((y + h - 1) % 256); + _transfer((y + h - 1) / 256); + _transferCommand(0x4e); + _transfer(x / 8); + _transferCommand(0x4f); + _transfer(y % 256); + _transfer(y / 256); + _endTransfer(); +} + +void WatchyDisplay::_PowerOn() +{ + if (!_power_is_on) + { + _startTransfer(); + _transferCommand(0x22); + _transfer(0xf8); + _transferCommand(0x20); + _endTransfer(); + _waitWhileBusy("_PowerOn", power_on_time); + } + _power_is_on = true; +} + +void WatchyDisplay::_PowerOff() +{ + if (_power_is_on) + { + _startTransfer(); + _transferCommand(0x22); + _transfer(0x83); + _transferCommand(0x20); + _endTransfer(); + _waitWhileBusy("_PowerOff", power_off_time); + } + _power_is_on = false; + _using_partial_mode = false; +} + +void WatchyDisplay::_InitDisplay() +{ + if (_hibernating) _reset(); + _writeCommand(0x12); // soft reset + _waitWhileBusy("_SoftReset", 10); // 10ms max according to specs + + _startTransfer(); + _transferCommand(0x01); // Driver output control + _transfer(0xC7); + _transfer(0x00); + _transfer(0x00); + _transferCommand(0x3C); // BorderWavefrom + _transfer(darkBorder ? 0x02 : 0x05); + _transferCommand(0x18); // Read built-in temperature sensor + _transfer(0x80); + _endTransfer(); + + _setPartialRamArea(0, 0, WIDTH, HEIGHT); +} + +void WatchyDisplay::_Init_Full() +{ + _InitDisplay(); + _PowerOn(); + _using_partial_mode = false; +} + +void WatchyDisplay::_Init_Part() +{ + _InitDisplay(); + _PowerOn(); + _using_partial_mode = true; +} + +void WatchyDisplay::_Update_Full() +{ + _startTransfer(); + _transferCommand(0x22); + _transfer(0xf4); + _transferCommand(0x20); + _endTransfer(); + _waitWhileBusy("_Update_Full", full_refresh_time); +} + +void WatchyDisplay::_Update_Part() +{ + _startTransfer(); + _transferCommand(0x22); + //_transfer(0xcc); // skip temperature load (-5ms) + _transfer(0xfc); + _transferCommand(0x20); + _endTransfer(); + _waitWhileBusy("_Update_Part", partial_refresh_time); +} + +void WatchyDisplay::_transferCommand(uint8_t value) +{ + if (_dc >= 0) digitalWrite(_dc, LOW); + SPI.transfer(value); + if (_dc >= 0) digitalWrite(_dc, HIGH); +} diff --git a/src/Display.h b/src/Display.h new file mode 100644 index 0000000..d142459 --- /dev/null +++ b/src/Display.h @@ -0,0 +1,86 @@ +// 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, available here: http://www.e-paper-display.com/download_detail/downloadsId=806.html +// Panel: GDEH0154D67 : http://www.e-paper-display.com/products_detail/productId=455.html +// Controller : SSD1681 : http://www.e-paper-display.com/download_detail/downloadsId=825.html +// +// Author: Jean-Marc Zingg +// +// Version: see library.properties +// +// Library: https://github.com/ZinggJM/GxEPD2 +// +// The original code from the author has been slightly modified to improve the performance for Watchy Project: +// Link: https://github.com/sqfmi/Watchy + +#pragma once + +#include + +class WatchyDisplay : public GxEPD2_EPD +{ + public: + // attributes + static const uint16_t WIDTH = 200; + static const uint16_t HEIGHT = 200; + static const GxEPD2::Panel panel = GxEPD2::GDEH0154D67; + static const bool hasColor = false; + static const bool hasPartialUpdate = true; + static const bool hasFastPartialUpdate = true; + static const uint16_t power_on_time = 100; // ms, e.g. 95583us + static const uint16_t power_off_time = 150; // ms, e.g. 140621us + static const uint16_t full_refresh_time = 2600; // ms, e.g. 2509602us + static const uint16_t partial_refresh_time = 500; // ms, e.g. 457282us + // constructor + WatchyDisplay(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 darkBorder = false; // adds a dark border outside the normal screen area + 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 _Init_Full(); + void _Init_Part(); + void _Update_Full(); + void _Update_Part(); + + void _transferCommand(uint8_t command); +}; diff --git a/src/Watchy.cpp b/src/Watchy.cpp index 98e94e3..6c7a12e 100644 --- a/src/Watchy.cpp +++ b/src/Watchy.cpp @@ -1,8 +1,8 @@ #include "Watchy.h" WatchyRTC Watchy::RTC; -GxEPD2_BW Watchy::display( - GxEPD2_154_D67(DISPLAY_CS, DISPLAY_DC, DISPLAY_RES, DISPLAY_BUSY)); +GxEPD2_BW Watchy::display( + WatchyDisplay(DISPLAY_CS, DISPLAY_DC, DISPLAY_RES, DISPLAY_BUSY)); RTC_DATA_ATTR int guiState; RTC_DATA_ATTR int menuIndex; diff --git a/src/Watchy.h b/src/Watchy.h index 43fa9ae..265e22c 100644 --- a/src/Watchy.h +++ b/src/Watchy.h @@ -11,6 +11,7 @@ #include #include #include "DSEG7_Classic_Bold_53.h" +#include "Display.h" #include "WatchyRTC.h" #include "BLE.h" #include "bma.h" @@ -40,7 +41,7 @@ typedef struct watchySettings { class Watchy { public: static WatchyRTC RTC; - static GxEPD2_BW display; + static GxEPD2_BW display; tmElements_t currentTime; watchySettings settings;