commit d91fb653815621fff7190685c421128fa12e75d8 Author: Andrew Villeneuve Date: Wed May 6 13:53:55 2026 -0700 Proof of concept: 2bpp greyscale LVGL display diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..0a37a01 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "WebSearch" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..275defe --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# ESPHome build cache and secrets +.esphome/ +secrets.yaml + +# Python bytecode from custom components +__pycache__/ +*.pyc diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b8dc2d7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,59 @@ +# Waveshare 2.9" E-Paper LVGL Project + +## Goal Summary +- Extend the native waveshare_epaper component of esphome to support 2-bit grayscale +- Only concerned with this one target board: `2.90inv2-r2` + +## Hardware +- ESP32-WROOM-32 (nodemcu-32s board) +- Waveshare 2.9inch Touch ePaper HAT (296x128, 2-bit grayscale) +- Display model string: `2.90inv2-r2` (SSD1680 controller) +- Pin mapping: SCLK=GPIO23, MOSI=GPIO22, CS=GPIO19, DC=GPIO18, BUSY=GPIO4, RST=GPIO5 + +## Software +- ESPHome 2026.1.0 +- Framework: esp-idf +- Project directory: ~/Projects/waveshare-panel + +## Current Status +- Display working correctly via ESPHome's native display library +- LVGL rendering confirmed working (black background visible) +- Custom component `waveshare_epaper_2bit` created and flashed, working identically to built-in driver +- Custom model name: `2.90inv2-r2-2bpp` + +## Known Issues / Quirks +- LVGL color depth is hardcoded to 16-bit (RGB565) in ESPHome — only supported value +- Display renders in 1-bit monochrome despite panel supporting 2-bit grayscale +- Colors are inverted by default (bg_color: 0x000000 produces white background) +- Resolved: original timeout errors were caused by on_draw_end hammering the display + +## Working LVGL Config +- refer to the file waveshare-test.yaml + +## Goal: 2-bit Grayscale Support +The SSD1680 controller supports 4-shade grayscale via two bitplanes: +- Register 0x24: bitplane 1 +- Register 0x26: bitplane 2 + +| 0x24 | 0x26 | Result | +|------|------|------------| +| 1 | 1 | White | +| 1 | 0 | Light grey | +| 0 | 1 | Dark grey | +| 0 | 0 | Black | + +The ESPHome driver only writes to 0x24, discarding grayscale information. +The real problem is upstream — RGB565 is converted to 1-bit before display() is +called, so grayscale data is lost before the driver even sees it. Next step is to +find where this conversion happens in the ESPHome display pipeline so the patch +can preserve grayscale information through to the two-bitplane write. + +## Relevant Files +- Custom component: `custom_components/waveshare_epaper_2bit/` + - `__init__.py` — CODEOWNERS only + - `display.py` — component schema and model registration + - `waveshare_epaper_2bit.cpp` — driver implementation + - `waveshare_epaper_2bit.h` — header +- Build cache: `.esphome/build/waveshare-epaper-test/src/esphome/components/` +- ESPHome source (venv): ~/Code/esphome-2026.1.0/ +- Applicable ESPHome source is copied into .esphome/build by the build system at compile time diff --git a/FreeSans.ttf b/FreeSans.ttf new file mode 100644 index 0000000..ec00c45 Binary files /dev/null and b/FreeSans.ttf differ diff --git a/custom_components/waveshare_epaper_2bit/__init__.py b/custom_components/waveshare_epaper_2bit/__init__.py new file mode 100644 index 0000000..c58ce8a --- /dev/null +++ b/custom_components/waveshare_epaper_2bit/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@clydebarrow"] diff --git a/custom_components/waveshare_epaper_2bit/display.py b/custom_components/waveshare_epaper_2bit/display.py new file mode 100644 index 0000000..d67bee5 --- /dev/null +++ b/custom_components/waveshare_epaper_2bit/display.py @@ -0,0 +1,265 @@ +from esphome import core, pins +import esphome.codegen as cg +from esphome.components import display, spi +import esphome.config_validation as cv +from esphome.const import ( + CONF_BUSY_PIN, + CONF_DC_PIN, + CONF_FULL_UPDATE_EVERY, + CONF_ID, + CONF_LAMBDA, + CONF_MODEL, + CONF_PAGES, + CONF_RESET_DURATION, + CONF_RESET_PIN, +) + +DEPENDENCIES = ["spi"] + +waveshare_epaper_ns = cg.esphome_ns.namespace("waveshare_epaper_2bit") +WaveshareEPaperBase = waveshare_epaper_ns.class_( + "WaveshareEPaperBase", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer +) +WaveshareEPaper = waveshare_epaper_ns.class_("WaveshareEPaper", WaveshareEPaperBase) +WaveshareEPaperBWR = waveshare_epaper_ns.class_( + "WaveshareEPaperBWR", WaveshareEPaperBase +) +WaveshareEPaper7C = waveshare_epaper_ns.class_("WaveshareEPaper7C", WaveshareEPaperBase) +WaveshareEPaperTypeA = waveshare_epaper_ns.class_( + "WaveshareEPaperTypeA", WaveshareEPaper +) +WaveshareEpaper1P54INBV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper1P54InBV2", WaveshareEPaperBWR +) +WaveshareEPaper2P7In = waveshare_epaper_ns.class_( + "WaveshareEPaper2P7In", WaveshareEPaper +) +WaveshareEPaper2P7InB = waveshare_epaper_ns.class_( + "WaveshareEPaper2P7InB", WaveshareEPaperBWR +) +WaveshareEPaper2P7InBV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P7InBV2", WaveshareEPaperBWR +) +WaveshareEPaper2P7InV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P7InV2", WaveshareEPaper +) +WaveshareEPaper2P9InB = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InB", WaveshareEPaper +) +WaveshareEPaper2P9InBV3 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InBV3", WaveshareEPaper +) +WaveshareEPaper2P9InV2R2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InV2R2", WaveshareEPaper +) +WaveshareEPaper2P9InV2R22Bpp = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InV2R22Bpp", WaveshareEPaper +) +GDEW029T5 = waveshare_epaper_ns.class_("GDEW029T5", WaveshareEPaper) +GDEY029T94 = waveshare_epaper_ns.class_("GDEY029T94", WaveshareEPaper) +WaveshareEPaper2P9InDKE = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InDKE", WaveshareEPaper +) +GDEY042T81 = waveshare_epaper_ns.class_("GDEY042T81", WaveshareEPaper) +WaveshareEPaper2P9InD = waveshare_epaper_ns.class_( + "WaveshareEPaper2P9InD", WaveshareEPaper +) +WaveshareEPaper4P2In = waveshare_epaper_ns.class_( + "WaveshareEPaper4P2In", WaveshareEPaper +) +WaveshareEPaper4P2InBV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper4P2InBV2", WaveshareEPaper +) +WaveshareEPaper4P2InBV2BWR = waveshare_epaper_ns.class_( + "WaveshareEPaper4P2InBV2BWR", WaveshareEPaperBWR +) +WaveshareEPaper5P65InF = waveshare_epaper_ns.class_( + "WaveshareEPaper5P65InF", WaveshareEPaper7C +) +WaveshareEPaper5P8In = waveshare_epaper_ns.class_( + "WaveshareEPaper5P8In", WaveshareEPaper +) +WaveshareEPaper5P8InV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper5P8InV2", WaveshareEPaper +) +GDEY0583T81 = waveshare_epaper_ns.class_("GDEY0583T81", WaveshareEPaper) +WaveshareEPaper7P3InF = waveshare_epaper_ns.class_( + "WaveshareEPaper7P3InF", WaveshareEPaper7C +) +WaveshareEPaper7P5In = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5In", WaveshareEPaper +) +WaveshareEPaper7P5InBC = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InBC", WaveshareEPaper +) +WaveshareEPaper7P5InBV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InBV2", WaveshareEPaper +) +WaveshareEPaper7P5InBV3 = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InBV3", WaveshareEPaper +) +WaveshareEPaper7P5InBV3BWR = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InBV3BWR", WaveshareEPaperBWR +) +WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InV2", WaveshareEPaper +) +WaveshareEPaper7P5InV2alt = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InV2alt", WaveshareEPaper +) +WaveshareEPaper7P5InV2P = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InV2P", WaveshareEPaper +) +WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InHDB", WaveshareEPaper +) +WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_( + "WaveshareEPaper2P13InDKE", WaveshareEPaper +) +WaveshareEPaper2P13InV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P13InV2", WaveshareEPaper +) +WaveshareEPaper2P13InV3 = waveshare_epaper_ns.class_( + "WaveshareEPaper2P13InV3", WaveshareEPaper +) +WaveshareEPaper13P3InK = waveshare_epaper_ns.class_( + "WaveshareEPaper13P3InK", WaveshareEPaper +) +GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper) + +WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel") +WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel") + +MODELS = { + "1.54in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN), + "1.54inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_1_54_IN_V2), + "1.54inv2-b": ("b", WaveshareEpaper1P54INBV2), + "2.13in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN), + "2.13inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_13_IN_V2), + "2.13in-ttgo": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN), + "2.13in-ttgo-b1": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B1), + "2.13in-ttgo-b73": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B73), + "2.13in-ttgo-b74": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B74), + "2.90in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN), + "2.90inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2), + "gdew029t5": ("c", GDEW029T5), + "2.70in": ("b", WaveshareEPaper2P7In), + "2.70in-b": ("b", WaveshareEPaper2P7InB), + "2.70in-bv2": ("b", WaveshareEPaper2P7InBV2), + "2.70inv2": ("b", WaveshareEPaper2P7InV2), + "2.90in-b": ("b", WaveshareEPaper2P9InB), + "2.90in-bv3": ("b", WaveshareEPaper2P9InBV3), + "gdey029t94": ("c", GDEY029T94), + "2.90inv2-r2": ("c", WaveshareEPaper2P9InV2R2), + "2.90inv2-r2-2bpp": ("c", WaveshareEPaper2P9InV2R22Bpp), + "2.90in-d": ("b", WaveshareEPaper2P9InD), + "2.90in-dke": ("c", WaveshareEPaper2P9InDKE), + "gdey042t81": ("c", GDEY042T81), + "4.20in": ("b", WaveshareEPaper4P2In), + "4.20in-bv2": ("b", WaveshareEPaper4P2InBV2), + "4.20in-bv2-bwr": ("b", WaveshareEPaper4P2InBV2BWR), + "5.65in-f": ("b", WaveshareEPaper5P65InF), + "5.83in": ("b", WaveshareEPaper5P8In), + "5.83inv2": ("b", WaveshareEPaper5P8InV2), + "gdey0583t81": ("c", GDEY0583T81), + "7.30in-f": ("b", WaveshareEPaper7P3InF), + "7.50in": ("b", WaveshareEPaper7P5In), + "7.50in-bv2": ("b", WaveshareEPaper7P5InBV2), + "7.50in-bv3": ("b", WaveshareEPaper7P5InBV3), + "7.50in-bv3-bwr": ("b", WaveshareEPaper7P5InBV3BWR), + "7.50in-bc": ("b", WaveshareEPaper7P5InBC), + "7.50inv2": ("b", WaveshareEPaper7P5InV2), + "7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt), + "7.50inv2p": ("c", WaveshareEPaper7P5InV2P), + "7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB), + "2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), + "2.13inv3": ("c", WaveshareEPaper2P13InV3), + "1.54in-m5coreink-m09": ("b", GDEW0154M09), + "13.3in-k": ("b", WaveshareEPaper13P3InK), +} + +RESET_PIN_REQUIRED_MODELS = ("2.13inv2", "2.13in-ttgo-b74") + + +def validate_full_update_every_only_types_ac(value): + if CONF_FULL_UPDATE_EVERY not in value: + return value + if MODELS[value[CONF_MODEL]][0] == "b": + full_models = [] + for key, val in sorted(MODELS.items()): + if val[0] != "b": + full_models.append(key) + raise cv.Invalid( + "The 'full_update_every' option is only available for models " + + ", ".join(full_models) + ) + return value + + +def validate_reset_pin_required(config): + if config[CONF_MODEL] in RESET_PIN_REQUIRED_MODELS and CONF_RESET_PIN not in config: + raise cv.Invalid( + f"'{CONF_RESET_PIN}' is required for model {config[CONF_MODEL]}" + ) + return config + + +CONFIG_SCHEMA = cv.All( + display.FULL_DISPLAY_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(WaveshareEPaperBase), + cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_MODEL): cv.one_of(*MODELS, lower=True), + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_BUSY_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_FULL_UPDATE_EVERY): cv.int_range(min=1, max=4294967295), + cv.Optional(CONF_RESET_DURATION): cv.All( + cv.positive_time_period_milliseconds, + cv.Range(max=core.TimePeriod(milliseconds=500)), + ), + } + ) + .extend(cv.polling_component_schema("1s")) + .extend(spi.spi_device_schema()), + validate_full_update_every_only_types_ac, + validate_reset_pin_required, + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA), +) + +FINAL_VALIDATE_SCHEMA = spi.final_validate_device_schema( + "waveshare_epaper_2bit", require_miso=False, require_mosi=True +) + + +async def to_code(config): + model_type, model = MODELS[config[CONF_MODEL]] + if model_type == "a": + rhs = WaveshareEPaperTypeA.new(model) + var = cg.Pvariable(config[CONF_ID], rhs, WaveshareEPaperTypeA) + elif model_type in ("b", "c"): + rhs = model.new() + var = cg.Pvariable(config[CONF_ID], rhs, model) + else: + raise NotImplementedError() + + await display.register_display(var, config) + await spi.register_spi_device(var, config) + + dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) + cg.add(var.set_dc_pin(dc)) + + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void + ) + cg.add(var.set_writer(lambda_)) + if CONF_RESET_PIN in config: + reset = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) + cg.add(var.set_reset_pin(reset)) + if CONF_BUSY_PIN in config: + reset = await cg.gpio_pin_expression(config[CONF_BUSY_PIN]) + cg.add(var.set_busy_pin(reset)) + if CONF_FULL_UPDATE_EVERY in config: + cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY])) + if CONF_RESET_DURATION in config: + cg.add(var.set_reset_duration(config[CONF_RESET_DURATION])) diff --git a/custom_components/waveshare_epaper_2bit/waveshare_213v3_2bit.cpp b/custom_components/waveshare_epaper_2bit/waveshare_213v3_2bit.cpp new file mode 100644 index 0000000..7aa8d3f --- /dev/null +++ b/custom_components/waveshare_epaper_2bit/waveshare_213v3_2bit.cpp @@ -0,0 +1,192 @@ +#include "waveshare_epaper_2bit.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace waveshare_epaper_2bit { + +static const char *const TAG = "waveshare_2.13v3"; + +static const uint8_t PARTIAL_LUT[] = { + 0x32, // cmd + 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, +}; + +static const uint8_t FULL_LUT[] = { + 0x32, // CMD + 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x80, 0x4A, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x4A, 0x80, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, 0x0, 0x0, 0xF, 0x0, 0x0, 0x2, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, +}; + +static const uint8_t SW_RESET = 0x12; +static const uint8_t ACTIVATE = 0x20; +static const uint8_t WRITE_BUFFER = 0x24; +static const uint8_t WRITE_BASE = 0x26; + +static const uint8_t DRV_OUT_CTL[] = {0x01, 0x27, 0x01, 0x00}; // driver output control +static const uint8_t GATEV[] = {0x03, 0x17}; +static const uint8_t SRCV[] = {0x04, 0x41, 0x0C, 0x32}; +static const uint8_t SLEEP[] = {0x10, 0x01}; +static const uint8_t DATA_ENTRY[] = {0x11, 0x03}; // data entry mode +static const uint8_t TEMP_SENS[] = {0x18, 0x80}; // Temp sensor +static const uint8_t DISPLAY_UPDATE[] = {0x21, 0x00, 0x80}; // Display update control +static const uint8_t UPSEQ[] = {0x22, 0xC0}; +static const uint8_t ON_FULL[] = {0x22, 0xC7}; +static const uint8_t ON_PARTIAL[] = {0x22, 0x0F}; +static const uint8_t VCOM[] = {0x2C, 0x36}; +static const uint8_t CMD5[] = {0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00}; +static const uint8_t BORDER_PART[] = {0x3C, 0x80}; // border waveform +static const uint8_t BORDER_FULL[] = {0x3C, 0x05}; // border waveform +static const uint8_t CMD1[] = {0x3F, 0x22}; +static const uint8_t RAM_X_START[] = {0x44, 0x00, 121 / 8}; // set ram_x_address_start_end +static const uint8_t RAM_Y_START[] = {0x45, 0x00, 0x00, 250 - 1, 0}; // set ram_y_address_start_end +static const uint8_t RAM_X_POS[] = {0x4E, 0x00}; // set ram_x_address_counter +// static const uint8_t RAM_Y_POS[] = {0x4F, 0x00, 0x00}; // set ram_y_address_counter +#define SEND(x) this->cmd_data(x, sizeof(x)) + +void WaveshareEPaper2P13InV3::write_lut_(const uint8_t *lut) { + this->wait_until_idle_(); + this->cmd_data(lut, sizeof(PARTIAL_LUT)); + SEND(CMD1); + SEND(GATEV); + SEND(SRCV); + SEND(VCOM); +} + +// write the buffer starting on line top, up to line bottom. +void WaveshareEPaper2P13InV3::write_buffer_(uint8_t cmd, int top, int bottom) { + this->wait_until_idle_(); + this->set_window_(top, bottom); + this->command(cmd); + this->start_data_(); + + auto width_bytes = this->get_width_controller() / 8; + this->write_array(this->buffer_ + top * width_bytes, (bottom - top) * width_bytes); + this->end_data_(); +} + +void WaveshareEPaper2P13InV3::send_reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(2); + this->reset_pin_->digital_write(true); + } +} + +void WaveshareEPaper2P13InV3::setup() { + this->init_internal_(this->get_buffer_length_()); + this->setup_pins_(); + this->spi_setup(); + this->reset_(); + + delay(20); + this->send_reset_(); + // as a one-off delay this is not worth working around. + delay(100); // NOLINT + this->wait_until_idle_(); + this->command(SW_RESET); + this->wait_until_idle_(); + + SEND(DRV_OUT_CTL); + SEND(DATA_ENTRY); + SEND(CMD5); + this->set_window_(0, this->get_height_internal()); + SEND(BORDER_FULL); + SEND(DISPLAY_UPDATE); + SEND(TEMP_SENS); + this->wait_until_idle_(); + this->write_lut_(FULL_LUT); +} + +// t and b are y positions, i.e. line numbers. +void WaveshareEPaper2P13InV3::set_window_(int t, int b) { + uint8_t buffer[3]; + + SEND(RAM_X_START); + SEND(RAM_Y_START); + SEND(RAM_X_POS); + buffer[0] = 0x4F; + buffer[1] = (uint8_t) t; + buffer[2] = (uint8_t) (t >> 8); + SEND(buffer); +} + +// must implement, but we override setup to have more control +void WaveshareEPaper2P13InV3::initialize() {} + +void WaveshareEPaper2P13InV3::partial_update_() { + this->send_reset_(); + this->set_timeout(100, [this] { + this->write_lut_(PARTIAL_LUT); + SEND(BORDER_PART); + SEND(UPSEQ); + this->command(ACTIVATE); + this->set_timeout(100, [this] { + this->wait_until_idle_(); + this->write_buffer_(WRITE_BUFFER, 0, this->get_height_internal()); + SEND(ON_PARTIAL); + this->command(ACTIVATE); // Activate Display Update Sequence + this->is_busy_ = false; + }); + }); +} + +void WaveshareEPaper2P13InV3::full_update_() { + ESP_LOGI(TAG, "Performing full e-paper update."); + this->write_lut_(FULL_LUT); + this->write_buffer_(WRITE_BUFFER, 0, this->get_height_internal()); + this->write_buffer_(WRITE_BASE, 0, this->get_height_internal()); + SEND(ON_FULL); + this->command(ACTIVATE); // don't wait here + this->is_busy_ = false; +} + +void WaveshareEPaper2P13InV3::display() { + if (this->is_busy_ || (this->busy_pin_ != nullptr && this->busy_pin_->digital_read())) + return; + this->is_busy_ = true; + const bool partial = this->at_update_ != 0; + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; + if (partial) { + this->partial_update_(); + } else { + this->full_update_(); + } +} + +int WaveshareEPaper2P13InV3::get_width_controller() { return 128; } +int WaveshareEPaper2P13InV3::get_width_internal() { return 122; } + +int WaveshareEPaper2P13InV3::get_height_internal() { return 250; } + +uint32_t WaveshareEPaper2P13InV3::idle_timeout_() { return 5000; } + +void WaveshareEPaper2P13InV3::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this) + ESP_LOGCONFIG(TAG, " Model: 2.13inV3"); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper2P13InV3::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + +} // namespace waveshare_epaper +} // namespace esphome diff --git a/custom_components/waveshare_epaper_2bit/waveshare_epaper_2bit.cpp b/custom_components/waveshare_epaper_2bit/waveshare_epaper_2bit.cpp new file mode 100644 index 0000000..787c5a6 --- /dev/null +++ b/custom_components/waveshare_epaper_2bit/waveshare_epaper_2bit.cpp @@ -0,0 +1,5025 @@ +#include "waveshare_epaper_2bit.h" +#include +#include +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace waveshare_epaper_2bit { + +static const char *const TAG = "waveshare_epaper"; + +static const uint8_t LUT_SIZE_WAVESHARE = 30; + +static const uint8_t FULL_UPDATE_LUT[LUT_SIZE_WAVESHARE] = {0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, 0x66, 0x69, + 0x69, 0x59, 0x58, 0x99, 0x99, 0x88, 0x00, 0x00, 0x00, 0x00, + 0xF8, 0xB4, 0x13, 0x51, 0x35, 0x51, 0x51, 0x19, 0x01, 0x00}; + +static const uint8_t PARTIAL_UPDATE_LUT[LUT_SIZE_WAVESHARE] = { + 0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +static const uint8_t LUT_SIZE_TTGO = 70; + +static const uint8_t FULL_UPDATE_LUT_TTGO[LUT_SIZE_TTGO] = { + 0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, // LUT0: BB: VS 0 ~7 + 0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, // LUT1: BW: VS 0 ~7 + 0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, // LUT2: WB: VS 0 ~7 + 0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, // LUT3: WW: VS 0 ~7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT4: VCOM: VS 0 ~7 + 0x03, 0x03, 0x00, 0x00, 0x02, // TP0 A~D RP0 + 0x09, 0x09, 0x00, 0x00, 0x02, // TP1 A~D RP1 + 0x03, 0x03, 0x00, 0x00, 0x02, // TP2 A~D RP2 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP3 A~D RP3 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP4 A~D RP4 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP5 A~D RP5 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP6 A~D RP6 +}; + +static const uint8_t PARTIAL_UPDATE_LUT_TTGO[LUT_SIZE_TTGO] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT0: BB: VS 0 ~7 + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT1: BW: VS 0 ~7 + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT2: WB: VS 0 ~7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT3: WW: VS 0 ~7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUT4: VCOM: VS 0 ~7 + 0x0A, 0x00, 0x00, 0x00, 0x00, // TP0 A~D RP0 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP1 A~D RP1 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP2 A~D RP2 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP3 A~D RP3 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP4 A~D RP4 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP5 A~D RP5 + 0x00, 0x00, 0x00, 0x00, 0x00, // TP6 A~D RP6 +}; + +static const uint8_t LUT_SIZE_TTGO_B73 = 100; + +static const uint8_t FULL_UPDATE_LUT_TTGO_B73[LUT_SIZE_TTGO_B73] = { + 0xA0, 0x90, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x90, 0xA0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xA0, 0x90, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x90, 0xA0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x03, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t PARTIAL_UPDATE_LUT_TTGO_B73[LUT_SIZE_TTGO_B73] = { + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_SIZE_TTGO_B1 = 29; + +static const uint8_t FULL_UPDATE_LUT_TTGO_B1[LUT_SIZE_TTGO_B1] = { + 0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x01, 0x00, 0x00, 0x00, 0x00}; + +static const uint8_t PARTIAL_UPDATE_LUT_TTGO_B1[LUT_SIZE_TTGO_B1] = { + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +// clang-format off +// Disable formatting to preserve the same look as in Waveshare examples +static const uint8_t PARTIAL_UPD_2IN9_LUT_SIZE = 159; +static const uint8_t PARTIAL_UPD_2IN9_LUT[PARTIAL_UPD_2IN9_LUT_SIZE] = +{ + 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x00, 0x00, 0x00, + 0x22, 0x17, 0x41, 0xB0, 0x32, 0x36, +}; +// clang-format on + +void WaveshareEPaperBase::setup() { + this->init_internal_(this->get_buffer_length_()); + this->setup_pins_(); + this->spi_setup(); + this->reset_(); + this->initialize(); +} +void WaveshareEPaperBase::setup_pins_() { + this->dc_pin_->setup(); // OUTPUT + this->dc_pin_->digital_write(false); + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); // OUTPUT + this->reset_pin_->digital_write(true); + } + if (this->busy_pin_ != nullptr) { + this->busy_pin_->setup(); // INPUT + } +} +float WaveshareEPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; } +void WaveshareEPaperBase::command(uint8_t value) { + this->start_command_(); + this->write_byte(value); + this->end_command_(); +} +void WaveshareEPaperBase::data(uint8_t value) { + this->start_data_(); + this->write_byte(value); + this->end_data_(); +} + +// write a command followed by one or more bytes of data. +// The command is the first byte, length is the total including cmd. +void WaveshareEPaperBase::cmd_data(const uint8_t *c_data, size_t length) { + this->dc_pin_->digital_write(false); + this->enable(); + this->write_byte(c_data[0]); + this->dc_pin_->digital_write(true); + this->write_array(c_data + 1, length - 1); + this->disable(); +} + +bool WaveshareEPaperBase::wait_until_idle_() { + if (this->busy_pin_ == nullptr || !this->busy_pin_->digital_read()) { + return true; + } + + const uint32_t start = millis(); + while (this->busy_pin_->digital_read()) { + if (millis() - start > this->idle_timeout_()) { + ESP_LOGE(TAG, "Timeout while displaying image!"); + return false; + } + delay(1); + } + return true; +} +void WaveshareEPaperBase::update() { + this->do_update_(); + this->display(); +} +void WaveshareEPaper::fill(Color color) { + // If clipping is active, fall back to base implementation + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + + // flip logic + const uint8_t fill = color.is_on() ? 0x00 : 0xFF; + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) + this->buffer_[i] = fill; +} +void WaveshareEPaper7C::setup() { + this->init_internal_7c_(this->get_buffer_length_()); + this->setup_pins_(); + this->spi_setup(); + this->reset_(); + this->initialize(); +} +void WaveshareEPaper7C::init_internal_7c_(uint32_t buffer_length) { + RAMAllocator allocator; + uint32_t small_buffer_length = buffer_length / NUM_BUFFERS; + + for (int i = 0; i < NUM_BUFFERS; i++) { + this->buffers_[i] = allocator.allocate(small_buffer_length); + if (this->buffers_[i] == nullptr) { + ESP_LOGE(TAG, "Could not allocate buffer %d for display!", i); + for (auto &buffer : this->buffers_) { + allocator.deallocate(buffer, small_buffer_length); + buffer = nullptr; + } + return; + } + } + this->clear(); +} +uint8_t WaveshareEPaper7C::color_to_hex(Color color) { + uint8_t hex_code; + if (color.red > 127) { + if (color.green > 170) { + if (color.blue > 127) { + hex_code = 0x1; // White + } else { + hex_code = 0x5; // Yellow + } + } else if (color.green > 85) { + hex_code = 0x6; // Orange + } else { + hex_code = 0x4; // Red (or Magenta) + } + } else { + if (color.green > 127) { + if (color.blue > 127) { + hex_code = 0x3; // Cyan -> Blue + } else { + hex_code = 0x2; // Green + } + } else { + if (color.blue > 127) { + hex_code = 0x3; // Blue + } else { + hex_code = 0x0; // Black + } + } + } + + return hex_code; +} +void WaveshareEPaper7C::fill(Color color) { + // If clipping is active, use base class (3-bit packing is complex for partial fills) + if (this->get_clipping().is_set()) { + display::Display::fill(color); + return; + } + + uint8_t pixel_color; + if (color.is_on()) { + pixel_color = this->color_to_hex(color); + } else { + pixel_color = 0x1; + } + + if (this->buffers_[0] == nullptr) { + ESP_LOGE(TAG, "Buffer unavailable!"); + } else { + uint32_t small_buffer_length = this->get_buffer_length_() / NUM_BUFFERS; + for (auto &buffer : this->buffers_) { + for (uint32_t buffer_pos = 0; buffer_pos < small_buffer_length; buffer_pos += 3) { + // We store 8 bitset<3> in 3 bytes + // | byte 1 | byte 2 | byte 3 | + // |aaabbbaa|abbbaaab|bbaaabbb| + buffer[buffer_pos + 0] = pixel_color << 5 | pixel_color << 2 | pixel_color >> 1; + buffer[buffer_pos + 1] = pixel_color << 7 | pixel_color << 4 | pixel_color << 1 | pixel_color >> 2; + buffer[buffer_pos + 2] = pixel_color << 6 | pixel_color << 3 | pixel_color << 0; + } + App.feed_wdt(); + } + } +} +void WaveshareEPaper7C::send_buffers_() { + if (this->buffers_[0] == nullptr) { + ESP_LOGE(TAG, "Buffer unavailable!"); + return; + } + + uint32_t small_buffer_length = this->get_buffer_length_() / NUM_BUFFERS; + uint8_t byte_to_send; + for (auto &buffer : this->buffers_) { + for (uint32_t buffer_pos = 0; buffer_pos < small_buffer_length; buffer_pos += 3) { + std::bitset<24> triplet = + buffer[buffer_pos + 0] << 16 | buffer[buffer_pos + 1] << 8 | buffer[buffer_pos + 2] << 0; + // 8 bitset<3> are stored in 3 bytes + // |aaabbbaa|abbbaaab|bbaaabbb| + // | byte 1 | byte 2 | byte 3 | + byte_to_send = ((triplet >> 17).to_ulong() & 0b01110000) | ((triplet >> 18).to_ulong() & 0b00000111); + this->data(byte_to_send); + + byte_to_send = ((triplet >> 11).to_ulong() & 0b01110000) | ((triplet >> 12).to_ulong() & 0b00000111); + this->data(byte_to_send); + + byte_to_send = ((triplet >> 5).to_ulong() & 0b01110000) | ((triplet >> 6).to_ulong() & 0b00000111); + this->data(byte_to_send); + + byte_to_send = ((triplet << 1).to_ulong() & 0b01110000) | ((triplet << 0).to_ulong() & 0b00000111); + this->data(byte_to_send); + } + App.feed_wdt(); + } +} +void WaveshareEPaper7C::reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(true); + delay(20); + this->reset_pin_->digital_write(false); + delay(1); + this->reset_pin_->digital_write(true); + delay(20); + } +} + +void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) + return; + + const uint32_t pos = (x + y * this->get_width_controller()) / 8u; + const uint8_t subpos = x & 0x07; + // flip logic + if (!color.is_on()) { + this->buffer_[pos] |= 0x80 >> subpos; + } else { + this->buffer_[pos] &= ~(0x80 >> subpos); + } +} + +uint32_t WaveshareEPaper::get_buffer_length_() { + return this->get_width_controller() * this->get_height_internal() / 8u; +} // just a black buffer +uint32_t WaveshareEPaperBWR::get_buffer_length_() { + return this->get_width_controller() * this->get_height_internal() / 4u; +} // black and red buffer +uint32_t WaveshareEPaper7C::get_buffer_length_() { + return this->get_width_controller() * this->get_height_internal() / 8u * 3u; +} // 7 colors buffer, 1 pixel = 3 bits, we will store 8 pixels in 24 bits = 3 bytes + +void WaveshareEPaperBWR::fill(Color color) { + this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); +} +void HOT WaveshareEPaperBWR::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) + return; + + const uint32_t buf_half_len = this->get_buffer_length_() / 2u; + + const uint32_t pos = (x + y * this->get_width_internal()) / 8u; + const uint8_t subpos = x & 0x07; + // flip logic + if (color.is_on()) { + this->buffer_[pos] |= 0x80 >> subpos; + } else { + this->buffer_[pos] &= ~(0x80 >> subpos); + } + + // draw red pixels only, if the color contains red only + if (((color.red > 0) && (color.green == 0) && (color.blue == 0))) { + this->buffer_[pos + buf_half_len] |= 0x80 >> subpos; + } else { + this->buffer_[pos + buf_half_len] &= ~(0x80 >> subpos); + } +} +void HOT WaveshareEPaper7C::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) + return; + + uint8_t pixel_bits = this->color_to_hex(color); + uint32_t small_buffer_length = this->get_buffer_length_() / NUM_BUFFERS; + uint32_t pixel_position = x + y * this->get_width_controller(); + uint32_t first_bit_position = pixel_position * 3; + uint32_t byte_position = first_bit_position / 8u; + uint32_t byte_subposition = first_bit_position % 8u; + uint32_t buffer_position = byte_position / small_buffer_length; + uint32_t buffer_subposition = byte_position % small_buffer_length; + + if (byte_subposition <= 5) { + this->buffers_[buffer_position][buffer_subposition] = + (this->buffers_[buffer_position][buffer_subposition] & (0xFF ^ (0b111 << (5 - byte_subposition)))) | + (pixel_bits << (5 - byte_subposition)); + } else { + this->buffers_[buffer_position][buffer_subposition + 0] = + (this->buffers_[buffer_position][buffer_subposition + 0] & (0xFF ^ (0b111 >> (byte_subposition - 5)))) | + (pixel_bits >> (byte_subposition - 5)); + + this->buffers_[buffer_position][buffer_subposition + 1] = (this->buffers_[buffer_position][buffer_subposition + 1] & + (0xFF ^ (0xFF & (0b111 << (13 - byte_subposition))))) | + (pixel_bits << (13 - byte_subposition)); + } +} +void WaveshareEPaperBase::start_command_() { + this->dc_pin_->digital_write(false); + this->enable(); +} +void WaveshareEPaperBase::end_command_() { this->disable(); } +void WaveshareEPaperBase::start_data_() { + this->dc_pin_->digital_write(true); + this->enable(); +} +void WaveshareEPaperBase::end_data_() { this->disable(); } +void WaveshareEPaperBase::on_safe_shutdown() { this->deep_sleep(); } + +// ======================================================== +// Type A +// ======================================================== + +void WaveshareEPaperTypeA::initialize() { + // Achieve display intialization + this->init_display_(); + // If a reset pin is configured, eligible displays can be set to deep sleep + // between updates, as recommended by the hardware provider + if (this->reset_pin_ != nullptr) { + switch (this->model_) { + // More models can be added here to enable deep sleep if eligible + case WAVESHARE_EPAPER_1_54_IN: + case WAVESHARE_EPAPER_1_54_IN_V2: + this->deep_sleep_between_updates_ = true; + ESP_LOGI(TAG, "Set the display to deep sleep"); + this->deep_sleep(); + break; + default: + break; + } + } +} +void WaveshareEPaperTypeA::init_display_() { + if (this->model_ == TTGO_EPAPER_2_13_IN_B74 || this->model_ == WAVESHARE_EPAPER_2_13_IN_V2) { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(10); + this->reset_pin_->digital_write(true); + delay(10); + this->wait_until_idle_(); + } + + this->command(0x12); // SWRESET + this->wait_until_idle_(); + } + + // COMMAND DRIVER OUTPUT CONTROL + this->command(0x01); + this->data(this->get_height_internal() - 1); + this->data((this->get_height_internal() - 1) >> 8); + this->data(0x00); // ? GD = 0, SM = 0, TB = 0 + + // COMMAND BOOSTER SOFT START CONTROL + this->command(0x0C); + this->data(0xD7); + this->data(0xD6); + this->data(0x9D); + + // COMMAND WRITE VCOM REGISTER + this->command(0x2C); + this->data(0xA8); + + // COMMAND SET DUMMY LINE PERIOD + this->command(0x3A); + this->data(0x1A); + + // COMMAND SET GATE TIME + this->command(0x3B); + this->data(0x08); // 2µs per row + + // COMMAND DATA ENTRY MODE SETTING + this->command(0x11); + switch (this->model_) { + case TTGO_EPAPER_2_13_IN_B1: + this->data(0x01); // x increase, y decrease : as in demo code + break; + case TTGO_EPAPER_2_13_IN_B74: + case WAVESHARE_EPAPER_2_9_IN_V2: + this->data(0x03); // from top left to bottom right + // RAM content option for Display Update + this->command(0x21); + this->data(0x00); + this->data(0x80); + break; + default: + this->data(0x03); // from top left to bottom right + } +} +void WaveshareEPaperTypeA::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + switch (this->model_) { + case WAVESHARE_EPAPER_1_54_IN: + ESP_LOGCONFIG(TAG, " Model: 1.54in"); + break; + case WAVESHARE_EPAPER_1_54_IN_V2: + ESP_LOGCONFIG(TAG, " Model: 1.54inV2"); + break; + case WAVESHARE_EPAPER_2_13_IN: + ESP_LOGCONFIG(TAG, " Model: 2.13in"); + break; + case WAVESHARE_EPAPER_2_13_IN_V2: + ESP_LOGCONFIG(TAG, " Model: 2.13inV2"); + break; + case TTGO_EPAPER_2_13_IN: + ESP_LOGCONFIG(TAG, " Model: 2.13in (TTGO)"); + break; + case TTGO_EPAPER_2_13_IN_B73: + ESP_LOGCONFIG(TAG, " Model: 2.13in (TTGO B73)"); + break; + case TTGO_EPAPER_2_13_IN_B74: + ESP_LOGCONFIG(TAG, " Model: 2.13in (TTGO B74)"); + break; + case TTGO_EPAPER_2_13_IN_B1: + ESP_LOGCONFIG(TAG, " Model: 2.13in (TTGO B1)"); + break; + case WAVESHARE_EPAPER_2_9_IN: + ESP_LOGCONFIG(TAG, " Model: 2.9in"); + break; + case WAVESHARE_EPAPER_2_9_IN_V2: + ESP_LOGCONFIG(TAG, " Model: 2.9inV2"); + break; + } + ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} +void HOT WaveshareEPaperTypeA::display() { + bool full_update = this->at_update_ == 0; + bool prev_full_update = this->at_update_ == 1; + + if (this->deep_sleep_between_updates_) { + ESP_LOGI(TAG, "Wake up the display"); + this->reset_(); + this->wait_until_idle_(); + this->init_display_(); + } + + if (!this->wait_until_idle_()) { + this->status_set_warning(); + return; + } + + if (this->full_update_every_ >= 1) { + if (full_update != prev_full_update) { + switch (this->model_) { + case TTGO_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: + // Waveshare 2.13" V2 uses the same LUTs as TTGO + this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO : PARTIAL_UPDATE_LUT_TTGO, LUT_SIZE_TTGO); + break; + case TTGO_EPAPER_2_13_IN_B73: + this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO_B73 : PARTIAL_UPDATE_LUT_TTGO_B73, LUT_SIZE_TTGO_B73); + break; + case TTGO_EPAPER_2_13_IN_B74: + // there is no LUT + break; + case TTGO_EPAPER_2_13_IN_B1: + this->write_lut_(full_update ? FULL_UPDATE_LUT_TTGO_B1 : PARTIAL_UPDATE_LUT_TTGO_B1, LUT_SIZE_TTGO_B1); + break; + default: + this->write_lut_(full_update ? FULL_UPDATE_LUT : PARTIAL_UPDATE_LUT, LUT_SIZE_WAVESHARE); + } + } + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; + } + + if (this->model_ == WAVESHARE_EPAPER_2_13_IN_V2) { + // Set VCOM for full or partial update + this->command(0x2C); + this->data(full_update ? 0x55 : 0x26); + + if (!full_update) { + // Enable "ping-pong" + this->command(0x37); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x40); + this->data(0x00); + this->data(0x00); + this->command(0x22); + this->data(0xc0); + this->command(0x20); + } + } + + // Border waveform + switch (this->model_) { + case TTGO_EPAPER_2_13_IN_B74: + this->command(0x3C); + this->data(full_update ? 0x05 : 0x80); + break; + case WAVESHARE_EPAPER_2_13_IN_V2: + this->command(0x3C); + this->data(full_update ? 0x03 : 0x01); + break; + default: + break; + } + + // Set x & y regions we want to write to (full) + switch (this->model_) { + case TTGO_EPAPER_2_13_IN_B1: + // COMMAND SET RAM X ADDRESS START END POSITION + this->command(0x44); + this->data(0x00); + this->data((this->get_width_controller() - 1) >> 3); + // COMMAND SET RAM Y ADDRESS START END POSITION + this->command(0x45); + this->data(this->get_height_internal() - 1); + this->data((this->get_height_internal() - 1) >> 8); + this->data(0x00); + this->data(0x00); + + // COMMAND SET RAM X ADDRESS COUNTER + this->command(0x4E); + this->data(0x00); + // COMMAND SET RAM Y ADDRESS COUNTER + this->command(0x4F); + this->data(this->get_height_internal() - 1); + this->data((this->get_height_internal() - 1) >> 8); + + break; + default: + // COMMAND SET RAM X ADDRESS START END POSITION + this->command(0x44); + this->data(0x00); + this->data((this->get_width_internal() - 1) >> 3); + // COMMAND SET RAM Y ADDRESS START END POSITION + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data(this->get_height_internal() - 1); + this->data((this->get_height_internal() - 1) >> 8); + + // COMMAND SET RAM X ADDRESS COUNTER + this->command(0x4E); + this->data(0x00); + // COMMAND SET RAM Y ADDRESS COUNTER + this->command(0x4F); + this->data(0x00); + this->data(0x00); + } + + if (!this->wait_until_idle_()) { + this->status_set_warning(); + return; + } + + // COMMAND WRITE RAM + this->command(0x24); + this->start_data_(); + switch (this->model_) { + case TTGO_EPAPER_2_13_IN_B1: { // block needed because of variable initializations + int16_t wb = ((this->get_width_controller()) >> 3); + for (int i = 0; i < this->get_height_internal(); i++) { + for (int j = 0; j < wb; j++) { + int idx = j + (this->get_height_internal() - 1 - i) * wb; + this->write_byte(this->buffer_[idx]); + } + } + break; + } + default: + this->write_array(this->buffer_, this->get_buffer_length_()); + } + this->end_data_(); + + if (this->model_ == WAVESHARE_EPAPER_2_13_IN_V2 && full_update) { + // Write base image again on full refresh + this->command(0x26); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + } + + // COMMAND DISPLAY UPDATE CONTROL 2 + this->command(0x22); + switch (this->model_) { + case WAVESHARE_EPAPER_2_9_IN_V2: + case WAVESHARE_EPAPER_1_54_IN_V2: + case TTGO_EPAPER_2_13_IN_B74: + this->data(full_update ? 0xF7 : 0xFF); + break; + case TTGO_EPAPER_2_13_IN_B73: + this->data(0xC7); + break; + case WAVESHARE_EPAPER_2_13_IN_V2: + this->data(full_update ? 0xC7 : 0x0C); + break; + default: + this->data(0xC4); + break; + } + + // COMMAND MASTER ACTIVATION + this->command(0x20); + // COMMAND TERMINATE FRAME READ WRITE + this->command(0xFF); + + this->status_clear_warning(); + + if (this->deep_sleep_between_updates_) { + ESP_LOGI(TAG, "Set the display back to deep sleep"); + this->deep_sleep(); + } +} +int WaveshareEPaperTypeA::get_width_internal() { + switch (this->model_) { + case WAVESHARE_EPAPER_1_54_IN: + case WAVESHARE_EPAPER_1_54_IN_V2: + return 200; + case WAVESHARE_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: + case TTGO_EPAPER_2_13_IN: + case TTGO_EPAPER_2_13_IN_B73: + case TTGO_EPAPER_2_13_IN_B74: + case TTGO_EPAPER_2_13_IN_B1: + return 122; + case WAVESHARE_EPAPER_2_9_IN: + case WAVESHARE_EPAPER_2_9_IN_V2: + return 128; + } + return 0; +} +// The controller of the 2.13" displays has a buffer larger than screen size +int WaveshareEPaperTypeA::get_width_controller() { + switch (this->model_) { + case WAVESHARE_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: + case TTGO_EPAPER_2_13_IN: + case TTGO_EPAPER_2_13_IN_B73: + case TTGO_EPAPER_2_13_IN_B74: + case TTGO_EPAPER_2_13_IN_B1: + return 128; + default: + return this->get_width_internal(); + } +} +int WaveshareEPaperTypeA::get_height_internal() { + switch (this->model_) { + case WAVESHARE_EPAPER_1_54_IN: + case WAVESHARE_EPAPER_1_54_IN_V2: + return 200; + case WAVESHARE_EPAPER_2_13_IN: + case WAVESHARE_EPAPER_2_13_IN_V2: + case TTGO_EPAPER_2_13_IN: + case TTGO_EPAPER_2_13_IN_B73: + case TTGO_EPAPER_2_13_IN_B74: + case TTGO_EPAPER_2_13_IN_B1: + return 250; + case WAVESHARE_EPAPER_2_9_IN: + case WAVESHARE_EPAPER_2_9_IN_V2: + return 296; + } + return 0; +} +void WaveshareEPaperTypeA::write_lut_(const uint8_t *lut, const uint8_t size) { + // COMMAND WRITE LUT REGISTER + this->command(0x32); + for (uint8_t i = 0; i < size; i++) + this->data(lut[i]); +} +WaveshareEPaperTypeA::WaveshareEPaperTypeA(WaveshareEPaperTypeAModel model) : model_(model) {} +void WaveshareEPaperTypeA::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + +uint32_t WaveshareEPaperTypeA::idle_timeout_() { + switch (this->model_) { + case WAVESHARE_EPAPER_1_54_IN: + case WAVESHARE_EPAPER_1_54_IN_V2: + case WAVESHARE_EPAPER_2_13_IN_V2: + case TTGO_EPAPER_2_13_IN_B1: + return 2500; + default: + return WaveshareEPaperBase::idle_timeout_(); + } +} + +// ======================================================== +// Type B +// ======================================================== +// Datasheet: +// - https://www.waveshare.com/w/upload/7/7f/4.2inch-e-paper-b-specification.pdf +// - https://github.com/soonuse/epd-library-arduino/blob/master/4.2inch_e-paper/epd4in2/ + +static const uint8_t LUT_VCOM_DC_2_7[44] = { + 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x05, 0x00, 0x32, 0x32, 0x00, 0x00, 0x02, 0x00, + 0x0F, 0x0F, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_WHITE_TO_WHITE_2_7[42] = { + 0x50, 0x0F, 0x0F, 0x00, 0x00, 0x05, 0x60, 0x32, 0x32, 0x00, 0x00, 0x02, 0xA0, 0x0F, + 0x0F, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_BLACK_TO_WHITE_2_7[42] = { + 0x50, 0x0F, 0x0F, 0x00, 0x00, 0x05, 0x60, 0x32, 0x32, 0x00, 0x00, 0x02, 0xA0, 0x0F, + 0x0F, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_WHITE_TO_BLACK_2_7[] = { + 0xA0, 0x0F, 0x0F, 0x00, 0x00, 0x05, 0x60, 0x32, 0x32, 0x00, 0x00, 0x02, 0x50, 0x0F, + 0x0F, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_BLACK_TO_BLACK_2_7[42] = { + 0xA0, 0x0F, 0x0F, 0x00, 0x00, 0x05, 0x60, 0x32, 0x32, 0x00, 0x00, 0x02, 0x50, 0x0F, + 0x0F, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +void WaveshareEPaper2P7In::initialize() { + // command power setting + this->command(0x01); + this->data(0x03); // VDS_EN, VDG_EN + this->data(0x00); // VCOM_HV, VGHL_LV[1], VGHL_LV[0] + this->data(0x2B); // VDH + this->data(0x2B); // VDL + this->data(0x09); // VDHR + + // command booster soft start + this->command(0x06); + this->data(0x07); + this->data(0x07); + this->data(0x17); + + // Power optimization - ??? + this->command(0xF8); + this->data(0x60); + this->data(0xA5); + this->command(0xF8); + this->data(0x89); + this->data(0xA5); + this->command(0xF8); + this->data(0x90); + this->data(0x00); + this->command(0xF8); + this->data(0x93); + this->data(0x2A); + this->command(0xF8); + this->data(0xA0); + this->data(0xA5); + this->command(0xF8); + this->data(0xA1); + this->data(0x00); + this->command(0xF8); + this->data(0x73); + this->data(0x41); + + // command partial display refresh + this->command(0x16); + this->data(0x00); + + // command power on + this->command(0x04); + this->wait_until_idle_(); + delay(10); + + // Command panel setting + this->command(0x00); + this->data(0xAF); // KW-BF KWR-AF BWROTP 0f + // command pll control + this->command(0x30); + this->data(0x3A); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ + // COMMAND VCM DC SETTING + this->command(0x82); + this->data(0x12); + + delay(2); + // COMMAND LUT FOR VCOM + this->command(0x20); + for (uint8_t i : LUT_VCOM_DC_2_7) + this->data(i); + + // COMMAND LUT WHITE TO WHITE + this->command(0x21); + for (uint8_t i : LUT_WHITE_TO_WHITE_2_7) + this->data(i); + // COMMAND LUT BLACK TO WHITE + this->command(0x22); + for (uint8_t i : LUT_BLACK_TO_WHITE_2_7) + this->data(i); + // COMMAND LUT WHITE TO BLACK + this->command(0x23); + for (uint8_t i : LUT_WHITE_TO_BLACK_2_7) + this->data(i); + // COMMAND LUT BLACK TO BLACK + this->command(0x24); + for (uint8_t i : LUT_BLACK_TO_BLACK_2_7) + this->data(i); +} +void HOT WaveshareEPaper2P7In::display() { + uint32_t buf_len = this->get_buffer_length_(); + + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + delay(2); + + // COMMAND DATA START TRANSMISSION 2 + this->command(0x13); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + + // COMMAND DISPLAY REFRESH + this->command(0x12); +} +int WaveshareEPaper2P7In::get_width_internal() { return 176; } +int WaveshareEPaper2P7In::get_height_internal() { return 264; } +void WaveshareEPaper2P7In::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.7in"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper2P7InV2::initialize() { + this->reset_(); + this->wait_until_idle_(); + + this->command(0x12); // SWRESET + this->wait_until_idle_(); + + // SET WINDOWS + // XRAM_START_AND_END_POSITION + this->command(0x44); + this->data(0x00); + this->data(((this->get_width_controller() - 1) >> 3) & 0xFF); + // YRAM_START_AND_END_POSITION + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data((get_height_internal() - 1) & 0xFF); + this->data(((get_height_internal() - 1) >> 8) & 0xFF); + + // SET CURSOR + // XRAM_ADDRESS + this->command(0x4E); + this->data(0x00); + // YRAM_ADDRESS + this->command(0x4F); + this->data(0x00); + this->data(0x00); + + this->command(0x11); // data entry mode + this->data(0x03); +} +void HOT WaveshareEPaper2P7InV2::display() { + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // COMMAND DISPLAY REFRESH + this->command(0x22); + this->data(0xF7); + this->command(0x20); +} +int WaveshareEPaper2P7InV2::get_width_internal() { return 176; } +int WaveshareEPaper2P7InV2::get_height_internal() { return 264; } +void WaveshareEPaper2P7InV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.7in V2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// 1.54inch_v2_e-paper_b +// ======================================================== +// Datasheet: +// - https://files.waveshare.com/upload/9/9e/1.54inch-e-paper-b-v2-specification.pdf +// - https://www.waveshare.com/wiki/1.54inch_e-Paper_Module_(B)_Manual + +void WaveshareEPaper1P54InBV2::initialize() { + this->reset_(); + + this->wait_until_idle_(); + + this->command(0x12); + this->wait_until_idle_(); + + this->command(0x01); + this->data(0xC7); + this->data(0x00); + this->data(0x01); + + this->command(0x11); // data entry mode + this->data(0x01); + + this->command(0x44); // set Ram-X address start/end position + this->data(0x00); + this->data(0x18); // 0x18-->(24+1)*8=200 + + this->command(0x45); // set Ram-Y address start/end position + this->data(0xC7); // 0xC7-->(199+1)=200 + this->data(0x00); + this->data(0x00); + this->data(0x00); + + this->command(0x3C); // BorderWavefrom + this->data(0x05); + + this->command(0x18); // Read built-in temperature sensor + this->data(0x80); + + this->command(0x4E); // set RAM x address count to 0; + this->data(0x00); + this->command(0x4F); // set RAM y address count to 0x199; + this->data(0xC7); + this->data(0x00); + + this->wait_until_idle_(); +} + +void HOT WaveshareEPaper1P54InBV2::display() { + uint32_t buf_len_half = this->get_buffer_length_() >> 1; + this->initialize(); + + // COMMAND DATA START TRANSMISSION 1 (BLACK) + this->command(0x24); + delay(2); + for (uint32_t i = 0; i < buf_len_half; i++) { + this->data(~this->buffer_[i]); + } + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED) + this->command(0x26); + delay(2); + for (uint32_t i = buf_len_half; i < buf_len_half * 2u; i++) { + this->data(this->buffer_[i]); + } + this->command(0x22); + this->data(0xf7); + this->command(0x20); + this->wait_until_idle_(); + + this->deep_sleep(); +} +int WaveshareEPaper1P54InBV2::get_height_internal() { return 200; } +int WaveshareEPaper1P54InBV2::get_width_internal() { return 200; } +void WaveshareEPaper1P54InBV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 1.54in V2 B"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// 2.7inch_e-paper_b +// ======================================================== +// Datasheet: +// - https://www.waveshare.com/w/upload/d/d8/2.7inch-e-paper-b-specification.pdf +// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in7b.c + +static const uint8_t LUT_VCOM_DC_2_7B[44] = {0x00, 0x00, 0x00, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x0A, + 0x00, 0x00, 0x08, 0x00, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x00, 0x0A, + 0x0A, 0x00, 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, + 0x03, 0x0E, 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_WHITE_TO_WHITE_2_7B[42] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x80, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_BLACK_TO_WHITE_2_7B[42] = {0xA0, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x90, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0xB0, 0x04, 0x10, 0x00, 0x00, 0x05, 0xB0, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0xC0, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_WHITE_TO_BLACK_2_7B[] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x20, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x10, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +static const uint8_t LUT_BLACK_TO_BLACK_2_7B[42] = {0x90, 0x1A, 0x1A, 0x00, 0x00, 0x01, 0x40, 0x0A, 0x0A, 0x00, 0x00, + 0x08, 0x84, 0x0E, 0x01, 0x0E, 0x01, 0x10, 0x80, 0x0A, 0x0A, 0x00, + 0x00, 0x08, 0x00, 0x04, 0x10, 0x00, 0x00, 0x05, 0x00, 0x03, 0x0E, + 0x00, 0x00, 0x0A, 0x00, 0x23, 0x00, 0x00, 0x00, 0x01}; + +void WaveshareEPaper2P7InB::initialize() { + this->reset_(); + + // command power on + this->command(0x04); + this->wait_until_idle_(); + delay(10); + + // Command panel setting + this->command(0x00); + this->data(0xAF); // KW-BF KWR-AF BWROTP 0f + // command pll control + this->command(0x30); + this->data(0x3A); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ + + // command power setting + this->command(0x01); + this->data(0x03); // VDS_EN, VDG_EN + this->data(0x00); // VCOM_HV, VGHL_LV[1], VGHL_LV[0] + this->data(0x2B); // VDH + this->data(0x2B); // VDL + this->data(0x09); // VDHR + + // command booster soft start + this->command(0x06); + this->data(0x07); + this->data(0x07); + this->data(0x17); + + // Power optimization - ??? + this->command(0xF8); + this->data(0x60); + this->data(0xA5); + this->command(0xF8); + this->data(0x89); + this->data(0xA5); + this->command(0xF8); + this->data(0x90); + this->data(0x00); + this->command(0xF8); + this->data(0x93); + this->data(0x2A); + this->command(0xF8); + this->data(0x73); + this->data(0x41); + + // COMMAND VCM DC SETTING + this->command(0x82); + this->data(0x12); + + // VCOM_AND_DATA_INTERVAL_SETTING + this->command(0x50); + this->data(0x87); // define by OTP + + delay(2); + // COMMAND LUT FOR VCOM + this->command(0x20); + for (uint8_t i : LUT_VCOM_DC_2_7B) + this->data(i); + // COMMAND LUT WHITE TO WHITE + this->command(0x21); + for (uint8_t i : LUT_WHITE_TO_WHITE_2_7B) + this->data(i); + // COMMAND LUT BLACK TO WHITE + this->command(0x22); + for (uint8_t i : LUT_BLACK_TO_WHITE_2_7B) + this->data(i); + // COMMAND LUT WHITE TO BLACK + this->command(0x23); + for (uint8_t i : LUT_WHITE_TO_BLACK_2_7B) { + this->data(i); + } + // COMMAND LUT BLACK TO BLACK + this->command(0x24); + + for (uint8_t i : LUT_BLACK_TO_BLACK_2_7B) { + this->data(i); + } + + delay(2); +} + +void HOT WaveshareEPaper2P7InB::display() { + uint32_t buf_len_half = this->get_buffer_length_() >> 1; + this->initialize(); + + // TCON_RESOLUTION + this->command(0x61); + this->data(this->get_width_controller() >> 8); + this->data(this->get_width_controller() & 0xff); // 176 + this->data(this->get_height_internal() >> 8); + this->data(this->get_height_internal() & 0xff); // 264 + + // COMMAND DATA START TRANSMISSION 1 (BLACK) + this->command(0x10); + delay(2); + for (uint32_t i = 0; i < buf_len_half; i++) { + this->data(this->buffer_[i]); + } + this->command(0x11); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED) + this->command(0x13); + delay(2); + for (uint32_t i = buf_len_half; i < buf_len_half * 2u; i++) { + this->data(this->buffer_[i]); + } + this->command(0x11); + + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + this->wait_until_idle_(); + + this->deep_sleep(); +} +int WaveshareEPaper2P7InB::get_width_internal() { return 176; } +int WaveshareEPaper2P7InB::get_height_internal() { return 264; } +void WaveshareEPaper2P7InB::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.7in B"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// 2.7inch_e-paper_b_v2 +// ======================================================== +// Datasheet: +// - https://www.waveshare.com/w/upload/7/7b/2.7inch-e-paper-b-v2-specification.pdf +// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_2in7b_V2.c + +void WaveshareEPaper2P7InBV2::initialize() { + this->reset_(); + + this->wait_until_idle_(); + this->command(0x12); + this->wait_until_idle_(); + + this->command(0x00); + this->data(0x27); + this->data(0x01); + this->data(0x00); + + this->command(0x11); + this->data(0x03); + + // self.SetWindows(0, 0, self.width-1, self.height-1) + // SetWindows(self, Xstart, Ystart, Xend, Yend): + + uint32_t xend = this->get_width_controller() - 1; + uint32_t yend = this->get_height_internal() - 1; + this->command(0x44); + this->data(0x00); + this->data((xend >> 3) & 0xff); + + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data(yend & 0xff); + this->data((yend >> 8) & 0xff); + + // SetCursor(self, Xstart, Ystart): + this->command(0x4E); + this->data(0x00); + this->command(0x4F); + this->data(0x00); + this->data(0x00); +} + +void HOT WaveshareEPaper2P7InBV2::display() { + uint32_t buf_len = this->get_buffer_length_(); + // COMMAND DATA START TRANSMISSION 1 (BLACK) + this->command(0x24); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED) + this->command(0x26); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + + delay(2); + + this->command(0x20); + + this->wait_until_idle_(); +} +int WaveshareEPaper2P7InBV2::get_width_internal() { return 176; } +int WaveshareEPaper2P7InBV2::get_height_internal() { return 264; } +void WaveshareEPaper2P7InBV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.7in B V2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// 2.90in Type B (LUT from OTP) +// Datasheet: +// - https://www.waveshare.com/w/upload/b/bb/2.9inch-e-paper-b-specification.pdf +// - https://github.com/soonuse/epd-library-arduino/blob/master/2.9inch_e-paper_b/epd2in9b/epd2in9b.cpp +// ======================================================== + +void WaveshareEPaper2P9InB::initialize() { + // from https://www.waveshare.com/w/upload/b/bb/2.9inch-e-paper-b-specification.pdf, page 37 + // EPD hardware init start + this->reset_(); + + // COMMAND BOOSTER SOFT START + this->command(0x06); + this->data(0x17); + this->data(0x17); + this->data(0x17); + + // COMMAND POWER ON + this->command(0x04); + this->wait_until_idle_(); + + // COMMAND PANEL SETTING + this->command(0x00); + // 128x296 resolution: 10 + // LUT from OTP: 0 + // B/W mode (doesn't work): 1 + // scan-up: 1 + // shift-right: 1 + // booster ON: 1 + // no soft reset: 1 + this->data(0x9F); + + // COMMAND RESOLUTION SETTING + // set to 128x296 by COMMAND PANEL SETTING + + // COMMAND VCOM AND DATA INTERVAL SETTING + // use defaults for white border and ESPHome image polarity + + // EPD hardware init end +} +void HOT WaveshareEPaper2P9InB::display() { + // COMMAND DATA START TRANSMISSION 1 (B/W data) + this->command(0x10); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED data) + this->command(0x13); + delay(2); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0x00); + this->end_data_(); + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + delay(2); + this->wait_until_idle_(); + + // COMMAND POWER OFF + // NOTE: power off < deep sleep + this->command(0x02); +} +int WaveshareEPaper2P9InB::get_width_internal() { return 128; } +int WaveshareEPaper2P9InB::get_height_internal() { return 296; } +void WaveshareEPaper2P9InB::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.9in (B)"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// Waveshare 2.9-inch E-Paper (Type D) +// Waveshare WIKI: https://www.waveshare.com/wiki/Pico-ePaper-2.9-D +// Datasheet: https://www.waveshare.com/w/upload/b/b5/2.9inch_e-Paper_(D)_Specification.pdf +// ======================================================== + +void WaveshareEPaper2P9InD::initialize() { + // EPD hardware init start + this->reset_(); + + // Booster Soft Start + this->command(0x06); // Command: BTST + this->data(0x17); // Soft start configuration Phase A + this->data(0x17); // Soft start configuration Phase B + this->data(0x17); // Soft start configuration Phase C + + // Power Setting + this->command(0x01); // Command: PWR + this->data(0x03); // Intern DC/DC for VDH/VDL and VGH/VGL + this->data(0x00); // Default configuration VCOM_HV and VGHL_LV + this->data(0x2b); // VDH = 10.8 V + this->data(0x2b); // VDL = -10.8 V + + // Power ON + this->command(0x04); // Command: PON + this->wait_until_idle_(); + + // Panel settings + this->command(0x00); // Command: PSR + this->data(0x1F); // LUT from OTP, black and white mode, default scan + + // PLL Control + this->command(0x30); // Command: PLL + this->data(0x3A); // Default PLL frequency + + // Resolution settings + this->command(0x61); // Command: TRES + this->data(0x80); // Width: 128 + this->data(0x01); // Height MSB: 296 + this->data(0x28); // Height LSB: 296 + + // VCOM and data interval settings + this->command(0x50); // Command: CDI + this->data(0x77); + + // VCOM_DC settings + this->command(0x82); // Command: VDCS + this->data(0x12); // Dafault VCOM_DC +} + +void WaveshareEPaper2P9InD::display() { + // Start transmitting old data (clearing buffer) + this->command(0x10); // Command: DTM1 (OLD frame data) + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // Start transmitting new data (updated content) + this->command(0x13); // Command: DTM2 (NEW frame data) + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // Refresh Display + this->command(0x12); // Command: DRF + this->wait_until_idle_(); + + // Enter Power Off + this->command(0x02); // Command: POF + this->wait_until_idle_(); + + // Enter Deep Sleep + this->command(0x07); // Command: DSLP + this->data(0xA5); +} + +int WaveshareEPaper2P9InD::get_width_internal() { return 128; } +int WaveshareEPaper2P9InD::get_height_internal() { return 296; } +void WaveshareEPaper2P9InD::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.9in (D)"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// DKE 2.9 +// https://www.badge.team/docs/badges/sha2017/hardware/#e-ink-display-the-dke-group-depg0290b1 +// https://www.badge.team/docs/badges/sha2017/hardware/DEPG0290B01V3.0.pdf +static const uint8_t LUT_SIZE_DKE = 70; +static const uint8_t UPDATE_LUT_DKE[LUT_SIZE_DKE] = { + 0xA0, 0x90, 0x50, 0x0, 0x0, 0x0, 0x0, 0x50, 0x90, 0xA0, 0x0, 0x0, 0x0, 0x0, 0xA0, 0x90, 0x50, 0x0, + 0x0, 0x0, 0x0, 0x50, 0x90, 0xA0, 0x0, 0x0, 0x0, 0x0, 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, 0xF, + 0xF, 0x0, 0x0, 0x0, 0xF, 0xF, 0x0, 0x0, 0x02, 0xF, 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +}; +static const uint8_t PART_UPDATE_LUT_DKE[LUT_SIZE_DKE] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xa0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x50, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x05, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +static const uint8_t FULL_UPDATE_LUT_DKE[LUT_SIZE_DKE] = { + 0x90, 0x50, 0xa0, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x10, 0xa0, 0xa0, 0x80, 0x00, 0x90, 0x50, 0xa0, 0x50, + 0x50, 0x00, 0x00, 0x00, 0x00, 0x10, 0xa0, 0xa0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, + 0x04, 0x00, 0x00, 0x00, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x06, 0x05, 0x00, 0x00, 0x00, 0x04, 0x05, 0x00, 0x00, + 0x00, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +void WaveshareEPaper2P9InDKE::initialize() { + // Hardware reset + delay(10); + this->reset_pin_->digital_write(false); + delayMicroseconds(200); + this->reset_pin_->digital_write(true); + delayMicroseconds(200); + // Wait for busy low + this->wait_until_idle_(); + // Software reset + this->command(0x12); + // Wait for busy low + this->wait_until_idle_(); + // Set Analog Block Control + this->command(0x74); + this->data(0x54); + // Set Digital Block Control + this->command(0x7E); + this->data(0x3B); + // Set display size and driver output control + this->command(0x01); + // this->data(0x27); + // this->data(0x01); + // this->data(0x00); + this->data(this->get_height_internal() - 1); + this->data((this->get_height_internal() - 1) >> 8); + this->data(0x00); // ? GD = 0, SM = 0, TB = 0 + // Ram data entry mode + this->command(0x11); + this->data(0x03); + // Set Ram X address + this->command(0x44); + this->data(0x00); + this->data(0x0F); + // Set Ram Y address + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data(0x27); + this->data(0x01); + // Set border + this->command(0x3C); + // this->data(0x80); + this->data(0x01); + // Set VCOM value + this->command(0x2C); + this->data(0x26); + // Gate voltage setting + this->command(0x03); + this->data(0x17); + // Source voltage setting + this->command(0x04); + this->data(0x41); + this->data(0x00); + this->data(0x32); + // Frame setting 50hz + this->command(0x3A); + this->data(0x30); + this->command(0x3B); + this->data(0x0A); + // Load LUT + this->command(0x32); + for (uint8_t v : FULL_UPDATE_LUT_DKE) + this->data(v); +} + +void HOT WaveshareEPaper2P9InDKE::display() { + ESP_LOGI(TAG, "Performing e-paper update."); + // Set Ram X address counter + this->command(0x4e); + this->data(0); + // Set Ram Y address counter + this->command(0x4f); + this->data(0); + this->data(0); + // Load image (128/8*296) + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + // Image update + this->command(0x22); + this->data(0xC7); + this->command(0x20); + // Wait for busy low + this->wait_until_idle_(); + // Enter deep sleep mode + this->command(0x10); + this->data(0x01); +} +int WaveshareEPaper2P9InDKE::get_width_internal() { return 128; } +int WaveshareEPaper2P9InDKE::get_height_internal() { return 296; } +void WaveshareEPaper2P9InDKE::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.9in DKE"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} +void WaveshareEPaper2P9InDKE::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + +// ======================================================== +// 2.90in Type B (LUT from OTP) +// Datasheet: +// - https://files.waveshare.com/upload/a/af/2.9inch-e-paper-b-v3-specification.pdf +// ======================================================== + +void WaveshareEPaper2P9InBV3::initialize() { + // from https://github.com/waveshareteam/e-Paper/blob/master/Arduino/epd2in9b_V3/epd2in9b_V3.cpp + this->reset_(); + + // COMMAND POWER ON + this->command(0x04); + this->wait_until_idle_(); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x0F); + this->data(0x89); + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x80); + this->data(0x01); + this->data(0x28); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x77); +} +void HOT WaveshareEPaper2P9InBV3::display() { + // COMMAND DATA START TRANSMISSION 1 (B/W data) + this->command(0x10); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + this->command(0x92); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED data) + this->command(0x13); + delay(2); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0xFF); + this->end_data_(); + this->command(0x92); + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + delay(2); + this->wait_until_idle_(); + + // COMMAND POWER OFF + // NOTE: power off < deep sleep + this->command(0x02); +} +int WaveshareEPaper2P9InBV3::get_width_internal() { return 128; } +int WaveshareEPaper2P9InBV3::get_height_internal() { return 296; } +void WaveshareEPaper2P9InBV3::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.9in (B) V3"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// 2.90in v2 rev2 +// based on SDK and examples in ZIP file from: +// https://www.waveshare.com/pico-epaper-2.9.htm +// ======================================================== + +void WaveshareEPaper2P9InV2R2::initialize() { + this->reset_(); + this->wait_until_idle_(); + + this->command(0x12); // SWRESET + this->wait_until_idle_(); + + this->command(0x01); + this->data(0x27); + this->data(0x01); + this->data(0x00); + + this->command(0x11); + this->data(0x03); + + // SetWindows(0, 0, w, h) + this->command(0x44); + this->data(0x00); + this->data(((this->get_width_controller() - 1) >> 3) & 0xFF); + + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data((this->get_height_internal() - 1) & 0xFF); + this->data(((this->get_height_internal() - 1) >> 8) & 0xFF); + + this->command(0x21); + this->data(0x00); + this->data(0x80); + + // SetCursor(0, 0) + this->command(0x4E); + this->data(0x00); + this->command(0x4f); + this->data(0x00); + this->data(0x00); + + this->wait_until_idle_(); +} + +WaveshareEPaper2P9InV2R2::WaveshareEPaper2P9InV2R2() { this->reset_duration_ = 10; } + +void WaveshareEPaper2P9InV2R2::reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(reset_duration_); // NOLINT + this->reset_pin_->digital_write(true); + delay(reset_duration_); // NOLINT + } +} + +void WaveshareEPaper2P9InV2R2::display() { + if (!this->wait_until_idle_()) { + this->status_set_warning(); + ESP_LOGE(TAG, "fail idle 1"); + return; + } + + if (this->full_update_every_ == 1) { + // do single full update + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplay + this->command(0x22); + this->data(0xF7); + this->command(0x20); + return; + } + + // if (this->full_update_every_ == 1 || + if (this->at_update_ == 0) { + // do base update + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + this->command(0x26); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplay + this->command(0x22); + this->data(0xF7); + this->command(0x20); + } else { + // do partial update + this->reset_(); + + this->write_lut_(PARTIAL_UPD_2IN9_LUT, PARTIAL_UPD_2IN9_LUT_SIZE); + + this->command(0x37); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x40); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + + this->command(0x3C); + this->data(0x80); + + this->command(0x22); + this->data(0xC0); + this->command(0x20); + + if (!this->wait_until_idle_()) { + ESP_LOGE(TAG, "fail idle 2"); + } + + // SetWindows(0, 0, w, h) + this->command(0x44); + this->data(0x00); + this->data(((this->get_width_controller() - 1) >> 3) & 0xFF); + + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data((this->get_height_internal() - 1) & 0xFF); + this->data(((this->get_height_internal() - 1) >> 8) & 0xFF); + + // SetCursor(0, 0) + this->command(0x4E); + this->data(0x00); + this->command(0x4f); + this->data(0x00); + this->data(0x00); + + // write b/w + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplayPartial + this->command(0x22); + this->data(0x0F); + this->command(0x20); + } + + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; +} + +void WaveshareEPaper2P9InV2R2::write_lut_(const uint8_t *lut, const uint8_t size) { + // COMMAND WRITE LUT REGISTER + this->command(0x32); + for (uint8_t i = 0; i < size; i++) + this->data(lut[i]); +} + +void WaveshareEPaper2P9InV2R2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, + " Model: 2.9inV2R2\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper2P9InV2R2::deep_sleep() { + this->command(0x10); + this->data(0x01); +} + +int WaveshareEPaper2P9InV2R2::get_width_internal() { return 128; } +int WaveshareEPaper2P9InV2R2::get_height_internal() { return 296; } +int WaveshareEPaper2P9InV2R2::get_width_controller() { return this->get_width_internal(); } +void WaveshareEPaper2P9InV2R2::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + + +// ======================================================== +// 2.90in v2 rev2 2bpp +// based on SDK and examples in ZIP file from: +// https://www.waveshare.com/pico-epaper-2.9.htm +// ======================================================== + +// 4-level grayscale waveform LUT for SSD1680, sourced from: +// https://github.com/waveshareteam/e-Paper/blob/master/Arduino/epd2in9_V2/epd2in9_V2.cpp +// Bit convention with this LUT: bit=0 → white, bit=1 → black (opposite of OTP LUT). +// Bytes 0-152: waveform written to register 0x32 (153 bytes) +// Bytes 153-158: voltage parameters for 0x3F, 0x03, 0x04(×3), 0x2C +static const uint8_t GRAY4_LUT_2IN9_SIZE = 153; +static const uint8_t GRAY4_LUT_2IN9[159] = { + // voltage phase data (5 rows × 12 bytes = 60) + 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x60, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x60, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2A, 0x60, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // frame timing (12 rows × 7 bytes = 84) + 0x00, 0x02, 0x00, 0x05, 0x14, 0x00, 0x00, + 0x1E, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x02, 0x00, 0x05, 0x14, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // end sequence (9 bytes) + 0x24, 0x22, 0x22, 0x22, 0x23, 0x32, 0x00, 0x00, 0x00, + // voltage register values: 0x3F, 0x03, 0x04(VSH VSH2 VSL), 0x2C + 0x22, 0x17, 0x41, 0xAE, 0x32, 0x28, +}; + +void WaveshareEPaper2P9InV2R22Bpp::initialize() { + if (this->buffer2_ == nullptr) { + RAMAllocator allocator; + this->buffer2_ = allocator.allocate(this->get_buffer_length_()); + if (this->buffer2_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate buffer2 for 2bpp display!"); + return; + } + } + // With Gray4 LUT, bit=0 = white. Init both buffers to white. + memset(this->buffer_, 0x00, this->get_buffer_length_()); + memset(this->buffer2_, 0x00, this->get_buffer_length_()); + + this->reset_(); + this->wait_until_idle_(); + + this->command(0x12); // SWRESET + this->wait_until_idle_(); + + this->command(0x01); + this->data(0x27); + this->data(0x01); + this->data(0x00); + + this->command(0x11); + this->data(0x03); + + // SetWindows(0, 0, w, h) + this->command(0x44); + this->data(0x00); + this->data(((this->get_width_controller() - 1) >> 3) & 0xFF); + + this->command(0x45); + this->data(0x00); + this->data(0x00); + this->data((this->get_height_internal() - 1) & 0xFF); + this->data(((this->get_height_internal() - 1) >> 8) & 0xFF); + + this->command(0x21); + this->data(0x00); + this->data(0x80); + + this->command(0x3C); // border waveform: follow output data during VCOM + this->data(0x04); + + // SetCursor(0, 0) + this->command(0x4E); + this->data(0x00); + this->command(0x4f); + this->data(0x00); + this->data(0x00); + + this->wait_until_idle_(); + this->write_gray4_lut_(); +} + +WaveshareEPaper2P9InV2R22Bpp::WaveshareEPaper2P9InV2R22Bpp() { this->reset_duration_ = 10; } + +void WaveshareEPaper2P9InV2R22Bpp::reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(reset_duration_); // NOLINT + this->reset_pin_->digital_write(true); + delay(reset_duration_); // NOLINT + } +} + +void WaveshareEPaper2P9InV2R22Bpp::display() { + if (!this->wait_until_idle_()) { + this->status_set_warning(); + ESP_LOGE(TAG, "fail idle"); + return; + } + + // Reload Gray4 LUT each frame — partial updates with grayscale are not + // supported on SSD1680, so always do a full grayscale update. + this->write_gray4_lut_(); + + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + this->command(0x26); + this->start_data_(); + this->write_array(this->buffer2_ != nullptr ? this->buffer2_ : this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplay using custom LUT (0xC7, not 0xF7 which would reload OTP LUT) + this->command(0x22); + this->data(0xC7); + this->command(0x20); +} + +void WaveshareEPaper2P9InV2R22Bpp::write_lut_(const uint8_t *lut, const uint8_t size) { + this->command(0x32); + for (uint8_t i = 0; i < size; i++) + this->data(lut[i]); +} + +void WaveshareEPaper2P9InV2R22Bpp::write_gray4_lut_() { + this->command(0x32); + for (uint8_t i = 0; i < GRAY4_LUT_2IN9_SIZE; i++) + this->data(GRAY4_LUT_2IN9[i]); + this->command(0x3F); + this->data(GRAY4_LUT_2IN9[153]); + this->command(0x03); + this->data(GRAY4_LUT_2IN9[154]); + this->command(0x04); + this->data(GRAY4_LUT_2IN9[155]); + this->data(GRAY4_LUT_2IN9[156]); + this->data(GRAY4_LUT_2IN9[157]); + this->command(0x2C); + this->data(GRAY4_LUT_2IN9[158]); +} + +// 4x4 Bayer ordered dither matrix, values 0-15 +static const uint8_t BAYER4[4][4] = { + { 0, 8, 2, 10}, + {12, 4, 14, 6}, + { 3, 11, 1, 9}, + {15, 7, 13, 5}, +}; + +void HOT WaveshareEPaper2P9InV2R22Bpp::draw_absolute_pixel_internal(int x, int y, Color color) { + if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) + return; + + const uint32_t pos = (x + y * this->get_width_controller()) / 8u; + const uint8_t subpos = x & 0x07; + const uint8_t mask = 0x80 >> subpos; + + // BT.601 luma, 0=black 255=white + const uint8_t lum = (77u * color.red + 150u * color.green + 29u * color.blue) >> 8; + + // Ordered dithering: perturb lum by ±30 based on pixel position so that + // color transitions blend across a ~60-lum-unit band instead of hard-cutting. + const int16_t bias = (int16_t)(BAYER4[y & 3][x & 3] * 2) - 15; + int16_t lum_d = (int16_t)lum + bias; + if (lum_d < 0) lum_d = 0; + if (lum_d > 255) lum_d = 255; + + // Gray4 LUT bit convention: bit=0 → white, bit=1 → black. + // | 0x24 (buffer_) | 0x26 (buffer2_) | shade | + // | 0 | 0 | white | + // | 1 | 0 | light grey | + // | 0 | 1 | dark grey | + // | 1 | 1 | black | + if (lum_d >= 192) { // white + this->buffer_[pos] &= ~mask; + if (this->buffer2_ != nullptr) this->buffer2_[pos] &= ~mask; + } else if (lum_d >= 128) { // light grey + this->buffer_[pos] |= mask; + if (this->buffer2_ != nullptr) this->buffer2_[pos] &= ~mask; + } else if (lum_d >= 64) { // dark grey + this->buffer_[pos] &= ~mask; + if (this->buffer2_ != nullptr) this->buffer2_[pos] |= mask; + } else { // black + this->buffer_[pos] |= mask; + if (this->buffer2_ != nullptr) this->buffer2_[pos] |= mask; + } +} + +void WaveshareEPaper2P9InV2R22Bpp::fill(Color color) { + if (this->get_clipping().is_set()) { + Display::fill(color); + return; + } + + const uint8_t lum = (77u * color.red + 150u * color.green + 29u * color.blue) >> 8; + uint8_t b1, b2; + if (lum >= 192) { // white + b1 = 0x00; b2 = 0x00; + } else if (lum >= 128) { // light grey + b1 = 0xFF; b2 = 0x00; + } else if (lum >= 64) { // dark grey + b1 = 0x00; b2 = 0xFF; + } else { // black + b1 = 0xFF; b2 = 0xFF; + } + + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { + this->buffer_[i] = b1; + if (this->buffer2_ != nullptr) this->buffer2_[i] = b2; + } +} + +void WaveshareEPaper2P9InV2R22Bpp::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, + " Model: 2.9inV2R2greyscale\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper2P9InV2R22Bpp::deep_sleep() { + this->command(0x10); + this->data(0x01); +} + +int WaveshareEPaper2P9InV2R22Bpp::get_width_internal() { return 128; } +int WaveshareEPaper2P9InV2R22Bpp::get_height_internal() { return 296; } +int WaveshareEPaper2P9InV2R22Bpp::get_width_controller() { return this->get_width_internal(); } +void WaveshareEPaper2P9InV2R22Bpp::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + +// ======================================================== +// Good Display 2.9in black/white +// Datasheet: +// - https://files.seeedstudio.com/wiki/Other_Display/29-epaper/GDEY029T94.pdf +// - +// https://github.com/Allen-Kuang/e-ink_Demo/blob/main/2.9%20inch%20E-paper%20-%20monocolor%20128x296/example/Display_EPD_W21.cpp +// ======================================================== + +void GDEY029T94::initialize() { + // EPD hardware init start + this->reset_(); + + this->wait_until_idle_(); + this->command(0x12); // SWRESET + this->wait_until_idle_(); + + this->command(0x01); // Driver output control + this->data((this->get_height_internal() - 1) % 256); + this->data((this->get_height_internal() - 1) / 256); + this->data(0x00); + + this->command(0x11); // data entry mode + this->data(0x03); + + this->command(0x44); // set Ram-X address start/end position + this->data(0x00); + this->data(this->get_width_internal() / 8 - 1); + + this->command(0x45); // set Ram-Y address start/end position + this->data(0x00); + this->data(0x00); + this->data((this->get_height_internal() - 1) % 256); + this->data((this->get_height_internal() - 1) / 256); + + this->command(0x3C); // BorderWavefrom + this->data(0x05); + + this->command(0x21); // Display update control + this->data(0x00); + this->data(0x80); + + this->command(0x18); // Read built-in temperature sensor + this->data(0x80); + + this->command(0x4E); // set RAM x address count to 0; + this->data(0x00); + this->command(0x4F); // set RAM y address count to 0x199; + this->command(0x00); + this->command(0x00); + this->wait_until_idle_(); +} +void HOT GDEY029T94::display() { + this->command(0x24); // write RAM for black(0)/white (1) + this->start_data_(); + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { + this->write_byte(this->buffer_[i]); + } + this->end_data_(); + this->command(0x22); // Display Update Control + this->data(0xF7); + this->command(0x20); // Activate Display Update Sequence + this->wait_until_idle_(); +} +int GDEY029T94::get_width_internal() { return 128; } +int GDEY029T94::get_height_internal() { return 296; } +void GDEY029T94::dump_config() { + LOG_DISPLAY("", "E-Paper (Good Display)", this); + ESP_LOGCONFIG(TAG, " Model: 2.9in GDEY029T94"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// Good Display 2.9in black/white +// Datasheet: +// - https://v4.cecdn.yun300.cn/100001_1909185148/SSD1680.pdf +// - https://github.com/adafruit/Adafruit_EPD/blob/master/src/panels/ThinkInk_290_Grayscale4_T5.h +// - https://github.com/ZinggJM/GxEPD2/blob/master/src/epd/GxEPD2_290_T5.cpp +// - http://www.e-paper-display.com/GDEW029T5%20V3.1%20Specification5c22.pdf? +// ======================================================== + +// full screen update LUT +static const uint8_t LUT_20_VCOMDC_29_5[] = { + 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x60, 0x28, 0x28, 0x00, 0x00, 0x01, 0x00, 0x14, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x12, 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_21_WW_29_5[] = { + 0x40, 0x08, 0x00, 0x00, 0x00, 0x02, 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, 0x40, 0x14, + 0x00, 0x00, 0x00, 0x01, 0xA0, 0x12, 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_22_BW_29_5[] = { + 0x40, 0x08, 0x00, 0x00, 0x00, 0x02, 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, 0x40, 0x14, + 0x00, 0x00, 0x00, 0x01, 0xA0, 0x12, 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_23_WB_29_5[] = { + 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, 0x80, 0x14, + 0x00, 0x00, 0x00, 0x01, 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_24_BB_29_5[] = { + 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, 0x90, 0x28, 0x28, 0x00, 0x00, 0x01, 0x80, 0x14, + 0x00, 0x00, 0x00, 0x01, 0x50, 0x12, 0x12, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +// partial screen update LUT +static const uint8_t LUT_20_VCOMDC_PARTIAL_29_5[] = { + 0x00, 0x20, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_21_WW_PARTIAL_29_5[] = { + 0x00, 0x20, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_22_BW_PARTIAL_29_5[] = { + 0x80, 0x20, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_23_WB_PARTIAL_29_5[] = { + 0x40, 0x20, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_24_BB_PARTIAL_29_5[] = { + 0x00, 0x20, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +void GDEW029T5::power_on_() { + if (!this->power_is_on_) { + this->command(0x04); + this->wait_until_idle_(); + } + this->power_is_on_ = true; +} + +void GDEW029T5::power_off_() { + this->command(0x02); + this->wait_until_idle_(); + this->power_is_on_ = false; +} + +void GDEW029T5::deep_sleep() { + this->power_off_(); + if (this->deep_sleep_between_updates_) { + this->command(0x07); // deep sleep + this->data(0xA5); // check code + ESP_LOGD(TAG, "go to deep sleep"); + this->is_deep_sleep_ = true; + } +} + +void GDEW029T5::init_display_() { + // from https://github.com/ZinggJM/GxEPD2/blob/master/src/epd/GxEPD2_290_T5.cpp + + // Hardware Initialization + if (this->deep_sleep_between_updates_ && this->is_deep_sleep_) { + ESP_LOGI(TAG, "wake up from deep sleep"); + this->reset_(); + this->is_deep_sleep_ = false; + } + + // COMMAND POWER SETTINGS + this->command(0x01); + this->data(0x03); + this->data(0x00); + this->data(0x2b); + this->data(0x2b); + this->data(0x03); /* for b/w */ + + // COMMAND BOOSTER SOFT START + this->command(0x06); + this->data(0x17); + this->data(0x17); + this->data(0x17); + + this->power_on_(); + + // COMMAND PANEL SETTING + this->command(0x00); + // 128x296 resolution: 10 + // LUT from register: 1 + // B/W mode (doesn't work): 1 + // scan-up: 1 + // shift-right: 1 + // booster ON: 1 + // no soft reset: 1 + this->data(0b10111111); + this->data(0x0d); // VCOM to 0V fast + this->command(0x30); // PLL setting + this->data(0x3a); // 3a 100HZ 29 150Hz 39 200HZ 31 171HZ + this->command(0x61); // resolution setting + this->data(this->get_width_internal()); + this->data(this->get_height_internal() >> 8); + this->data(this->get_height_internal() & 0xFF); + + ESP_LOGD(TAG, "panel setting done"); +} + +void GDEW029T5::initialize() { + // from https://www.waveshare.com/w/upload/b/bb/2.9inch-e-paper-b-specification.pdf, page 37 + if (this->reset_pin_ != nullptr) + this->deep_sleep_between_updates_ = true; + + // old buffer for partial update + RAMAllocator allocator; + this->old_buffer_ = allocator.allocate(this->get_buffer_length_()); + if (this->old_buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate old buffer for display!"); + return; + } + for (size_t i = 0; i < this->get_buffer_length_(); i++) { + this->old_buffer_[i] = 0xFF; + } +} + +// initialize for full(normal) update +void GDEW029T5::init_full_() { + this->init_display_(); + this->command(0x82); // vcom_DC setting + this->data(0x08); + this->command(0x50); // VCOM AND DATA INTERVAL SETTING + this->data(0x97); // WBmode:VBDF 17|D7 VBDW 97 VBDB 57 WBRmode:VBDF F7 VBDW 77 VBDB 37 VBDR B7 + this->command(0x20); + this->write_lut_(LUT_20_VCOMDC_29_5, sizeof(LUT_20_VCOMDC_29_5)); + this->command(0x21); + this->write_lut_(LUT_21_WW_29_5, sizeof(LUT_21_WW_29_5)); + this->command(0x22); + this->write_lut_(LUT_22_BW_29_5, sizeof(LUT_22_BW_29_5)); + this->command(0x23); + this->write_lut_(LUT_23_WB_29_5, sizeof(LUT_23_WB_29_5)); + this->command(0x24); + this->write_lut_(LUT_24_BB_29_5, sizeof(LUT_24_BB_29_5)); + ESP_LOGD(TAG, "initialized full update"); +} + +// initialzie for partial update +void GDEW029T5::init_partial_() { + this->init_display_(); + this->command(0x82); // vcom_DC setting + this->data(0x08); + this->command(0x50); // VCOM AND DATA INTERVAL SETTING + this->data(0x17); // WBmode:VBDF 17|D7 VBDW 97 VBDB 57 WBRmode:VBDF F7 VBDW 77 VBDB 37 VBDR B7 + this->command(0x20); + this->write_lut_(LUT_20_VCOMDC_PARTIAL_29_5, sizeof(LUT_20_VCOMDC_PARTIAL_29_5)); + this->command(0x21); + this->write_lut_(LUT_21_WW_PARTIAL_29_5, sizeof(LUT_21_WW_PARTIAL_29_5)); + this->command(0x22); + this->write_lut_(LUT_22_BW_PARTIAL_29_5, sizeof(LUT_22_BW_PARTIAL_29_5)); + this->command(0x23); + this->write_lut_(LUT_23_WB_PARTIAL_29_5, sizeof(LUT_23_WB_PARTIAL_29_5)); + this->command(0x24); + this->write_lut_(LUT_24_BB_PARTIAL_29_5, sizeof(LUT_24_BB_PARTIAL_29_5)); + ESP_LOGD(TAG, "initialized partial update"); +} + +void HOT GDEW029T5::display() { + bool full_update = this->at_update_ == 0; + if (full_update) { + this->init_full_(); + } else { + this->init_partial_(); + this->command(0x91); // partial in + // set partial window + this->command(0x90); + // this->data(0); + this->data(0); + // this->data(0); + this->data((this->get_width_internal() - 1) % 256); + this->data(0); + this->data(0); + this->data(((this->get_height_internal() - 1)) / 256); + this->data(((this->get_height_internal() - 1)) % 256); + this->data(0x01); + } + // input old buffer data + this->command(0x10); + delay(2); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) { + this->write_byte(this->old_buffer_[i]); + } + this->end_data_(); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (B/W only) + this->command(0x13); + delay(2); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) { + this->write_byte(this->buffer_[i]); + this->old_buffer_[i] = this->buffer_[i]; + } + this->end_data_(); + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + delay(2); + this->wait_until_idle_(); + + if (full_update) { + ESP_LOGD(TAG, "full update done"); + } else { + this->command(0x92); // partial out + ESP_LOGD(TAG, "partial update done"); + } + + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; + // COMMAND deep sleep + this->deep_sleep(); +} + +void GDEW029T5::write_lut_(const uint8_t *lut, const uint8_t size) { + // COMMAND WRITE LUT REGISTER + this->start_data_(); + for (uint8_t i = 0; i < size; i++) + this->write_byte(lut[i]); + this->end_data_(); +} + +void GDEW029T5::set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } + +int GDEW029T5::get_width_internal() { return 128; } +int GDEW029T5::get_height_internal() { return 296; } +void GDEW029T5::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper (Good Display)", this); + ESP_LOGCONFIG(TAG, " Model: 2.9in Greyscale GDEW029T5"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + ESP_LOGCONFIG(TAG, " Full Update Every: %" PRIu32, this->full_update_every_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// Good Display 1.54in black/white/grey GDEW0154M09 +// As used in M5Stack Core Ink +// Datasheet: +// - https://v4.cecdn.yun300.cn/100001_1909185148/GDEW0154M09-200709.pdf +// - https://github.com/m5stack/M5Core-Ink +// Reference code from GoodDisplay: +// - https://github.com/GoodDisplay/E-paper-Display-Library-of-GoodDisplay/ +// -> /Monochrome_E-paper-Display/1.54inch_JD79653_GDEW0154M09_200x200/ESP32-Arduino%20IDE/GDEW0154M09_Arduino.ino +// M5Stack Core Ink spec: +// - https://docs.m5stack.com/en/core/coreink +// ======================================================== + +void GDEW0154M09::initialize() { + this->init_internal_(); + RAMAllocator allocator; + this->lastbuff_ = allocator.allocate(this->get_buffer_length_()); + if (this->lastbuff_ != nullptr) { + memset(this->lastbuff_, 0xff, sizeof(uint8_t) * this->get_buffer_length_()); + } + this->clear_(); +} + +void GDEW0154M09::reset_() { + // RST is inverse from other einks in this project + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(10); + this->reset_pin_->digital_write(true); + delay(10); + } +} + +void GDEW0154M09::init_internal_() { + this->reset_(); + + // clang-format off + // 200x200 resolution: 11 + // LUT from OTP: 0 + // B/W mode (doesn't work): 1 + // scan-up: 1 + // shift-right: 1 + // booster ON: 1 + // no soft reset: 1 + const uint8_t panel_setting_1 = 0b11011111; + + // VCOM status off 0 + // Temp sensing default 1 + // VGL Power Off Floating 1 + // NORG expect refresh 1 + // VCOM Off on displ off 0 + const uint8_t panel_setting_2 = 0b01110; + + const uint8_t wf_t0154_cz_b3_list[] = { + 11, // 11 commands in list + CMD_PSR_PANEL_SETTING, 2, panel_setting_1, panel_setting_2, + CMD_UNDOCUMENTED_0x4D, 1, 0x55, + CMD_UNDOCUMENTED_0xAA, 1, 0x0f, + CMD_UNDOCUMENTED_0xE9, 1, 0x02, + CMD_UNDOCUMENTED_0xB6, 1, 0x11, + CMD_UNDOCUMENTED_0xF3, 1, 0x0a, + CMD_TRES_RESOLUTION_SETTING, 3, 0xc8, 0x00, 0xc8, + CMD_TCON_TCONSETTING, 1, 0x00, + CMD_CDI_VCOM_DATA_INTERVAL, 1, 0xd7, + CMD_PWS_POWER_SAVING, 1, 0x00, + CMD_PON_POWER_ON, 0 + }; + // clang-format on + + this->write_init_list_(wf_t0154_cz_b3_list); + delay(100); // NOLINT + this->wait_until_idle_(); +} + +void GDEW0154M09::write_init_list_(const uint8_t *list) { + uint8_t list_limit = list[0]; + uint8_t *start_ptr = ((uint8_t *) list + 1); + for (uint8_t i = 0; i < list_limit; i++) { + this->command(*(start_ptr + 0)); + for (uint8_t dnum = 0; dnum < *(start_ptr + 1); dnum++) { + this->data(*(start_ptr + 2 + dnum)); + } + start_ptr += (*(start_ptr + 1) + 2); + } +} + +void GDEW0154M09::clear_() { + uint32_t pixsize = this->get_buffer_length_(); + for (uint8_t j = 0; j < 2; j++) { + this->command(CMD_DTM1_DATA_START_TRANS); + for (uint32_t count = 0; count < pixsize; count++) { + this->data(0x00); + } + this->command(CMD_DTM2_DATA_START_TRANS2); + for (uint32_t count = 0; count < pixsize; count++) { + this->data(0xff); + } + this->command(CMD_DISPLAY_REFRESH); + delay(10); + this->wait_until_idle_(); + } +} + +void HOT GDEW0154M09::display() { + this->init_internal_(); + // "Mode 0 display" for now + this->command(CMD_DTM1_DATA_START_TRANS); + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { + this->data(0xff); + } + this->command(CMD_DTM2_DATA_START_TRANS2); // write 'new' data to SRAM + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { + this->data(this->buffer_[i]); + } + this->command(CMD_DISPLAY_REFRESH); + delay(10); + this->wait_until_idle_(); + this->deep_sleep(); +} + +void GDEW0154M09::deep_sleep() { + // COMMAND DEEP SLEEP + this->command(CMD_POF_POWER_OFF); + this->wait_until_idle_(); + delay(1000); // NOLINT + this->command(CMD_DSLP_DEEP_SLEEP); + this->data(DATA_DSLP_DEEP_SLEEP); +} + +int GDEW0154M09::get_width_internal() { return 200; } +int GDEW0154M09::get_height_internal() { return 200; } +void GDEW0154M09::dump_config() { + LOG_DISPLAY("", "M5Stack CoreInk E-Paper (Good Display)", this); + ESP_LOGCONFIG(TAG, " Model: 1.54in Greyscale GDEW0154M09"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// Good Display 4.2in black/white GDEY042T81 (SSD1683) +// Product page: +// - https://www.good-display.com/product/386.html +// Datasheet: +// - https://v4.cecdn.yun300.cn/100001_1909185148/GDEY042T81.pdf +// - https://v4.cecdn.yun300.cn/100001_1909185148/SSD1683.PDF +// Reference code from GoodDisplay: +// - https://www.good-display.com/companyfile/1572.html (2024-08-01 15:40:41) +// Other reference code: +// - https://github.com/ZinggJM/GxEPD2/blob/03d8e7a533c1493f762e392ead12f1bcb7fab8f9/src/gdey/GxEPD2_420_GDEY042T81.cpp +// ======================================================== + +void GDEY042T81::initialize() { + this->init_display_(); + ESP_LOGD(TAG, "Initialization complete, set the display to deep sleep"); + this->deep_sleep(); +} + +// conflicting documentation / examples regarding reset timings +// https://v4.cecdn.yun300.cn/100001_1909185148/SSD1683.PDF -> 10ms +// GD sample code (Display_EPD_W21.cpp, see above) -> 10 ms +// https://v4.cecdn.yun300.cn/100001_1909185148/GDEY042T81.pdf (section 14.2) -> 0.2ms (200us) +// https://github.com/ZinggJM/GxEPD2/blob/03d8e7a533c1493f762e392ead12f1bcb7fab8f9/src/gdey/GxEPD2_420_GDEY042T81.cpp#L351 +// -> 10ms +// 10 ms seems to work, so we use this +GDEY042T81::GDEY042T81() { this->reset_duration_ = 10; } + +void GDEY042T81::reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(reset_duration_); // NOLINT + this->reset_pin_->digital_write(true); + delay(reset_duration_); // NOLINT + } +} + +void GDEY042T81::init_display_() { + this->reset_(); + + this->wait_until_idle_(); + this->command(0x12); // SWRESET + this->wait_until_idle_(); + + // Specify number of lines for the driver: 300 (MUX 300) + // https://v4.cecdn.yun300.cn/100001_1909185148/SSD1683.PDF (section 8.1) + // https://github.com/ZinggJM/GxEPD2/blob/03d8e7a533c1493f762e392ead12f1bcb7fab8f9/src/gdey/GxEPD2_420_GDEY042T81.cpp#L354 + this->command(0x01); // driver output control + this->data(0x2B); // (height - 1) % 256 + this->data(0x01); // (height - 1) / 256 + this->data(0x00); + + // https://github.com/ZinggJM/GxEPD2/blob/03d8e7a533c1493f762e392ead12f1bcb7fab8f9/src/gdey/GxEPD2_420_GDEY042T81.cpp#L360 + this->command(0x3C); // BorderWaveform + this->data(0x01); + this->command(0x18); // Read built-in temperature sensor + this->data(0x80); + + // GD sample code (Display_EPD_W21.cpp@90ff) + this->command(0x11); // data entry mode + this->data(0x03); + // set windows (0,0,400,300) + this->command(0x44); // set Ram-X address start/end position + this->data(0); + this->data(0x31); // (width / 8 -1) + + this->command(0x45); // set Ram-y address start/end position + this->data(0); + this->data(0); + this->data(0x2B); // (height - 1) % 256 + this->data(0x01); // (height - 1) / 256 + + // set cursor (0,0) + this->command(0x4E); // set RAM x address count to 0; + this->data(0); + this->command(0x4F); // set RAM y address count to 0; + this->data(0); + this->data(0); + + this->wait_until_idle_(); +} + +// https://github.com/ZinggJM/GxEPD2/blob/03d8e7a533c1493f762e392ead12f1bcb7fab8f9/src/gdey/GxEPD2_420_GDEY042T81.cpp#L366 +void GDEY042T81::update_full_() { + this->command(0x21); // display update control + this->data(0x40); // bypass RED as 0 + this->data(0x00); // single chip application + + // only ever do a fast update because slow updates are only relevant + // for lower operating temperatures + // see + // https://github.com/ZinggJM/GxEPD2/blob/03d8e7a533c1493f762e392ead12f1bcb7fab8f9/src/gdey/GxEPD2_290_GDEY029T94.h#L30 + // + // Should slow/fast updates be made configurable similar to how GxEPD2 does it? No idea if anyone would need it... + this->command(0x1A); // Write to temperature register + this->data(0x6E); + this->command(0x22); + this->data(0xd7); + + this->command(0x20); + this->wait_until_idle_(); +} + +// https://github.com/ZinggJM/GxEPD2/blob/03d8e7a533c1493f762e392ead12f1bcb7fab8f9/src/gdey/GxEPD2_420_GDEY042T81.cpp#L389 +void GDEY042T81::update_part_() { + this->command(0x21); // display update control + this->data(0x00); // RED normal + this->data(0x00); // single chip application + + this->command(0x22); + this->data(0xfc); + + this->command(0x20); + this->wait_until_idle_(); +} + +void HOT GDEY042T81::display() { + ESP_LOGD(TAG, "Wake up the display"); + this->init_display_(); + + if (!this->wait_until_idle_()) { + this->status_set_warning(); + ESP_LOGE(TAG, "Failed to perform update, display is busy"); + return; + } + + // basic code structure copied from WaveshareEPaper2P9InV2R2 + if (this->full_update_every_ == 1) { + ESP_LOGD(TAG, "Full update"); + // do single full update + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplay + this->update_full_(); + return; + } + + // if (this->full_update_every_ == 1 || + if (this->at_update_ == 0) { + ESP_LOGD(TAG, "Update"); + // do base update + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + this->command(0x26); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplay; + this->update_full_(); + } else { + // do partial update (full screen) + // no need to load a LUT for GoodDisplays as they seem to have the LUT onboard + // GD example code (Display_EPD_W21.cpp@283ff) + // + // not setting the BorderWaveform here again (contrary to the GD example) because according to + // https://github.com/ZinggJM/GxEPD2/blob/03d8e7a533c1493f762e392ead12f1bcb7fab8f9/src/gdey/GxEPD2_420_GDEY042T81.cpp#L358 + // it seems to be enough to set it during display initialization + ESP_LOGD(TAG, "Partial update"); + this->reset_(); + if (!this->wait_until_idle_()) { + this->status_set_warning(); + ESP_LOGE(TAG, "Failed to perform partial update, display is busy"); + return; + } + + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // TurnOnDisplay + this->update_part_(); + } + + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; + this->wait_until_idle_(); + ESP_LOGD(TAG, "Set the display back to deep sleep"); + this->deep_sleep(); +} +void GDEY042T81::set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } +int GDEY042T81::get_width_internal() { return 400; } +int GDEY042T81::get_height_internal() { return 300; } +uint32_t GDEY042T81::idle_timeout_() { return 5000; } +void GDEY042T81::dump_config() { + LOG_DISPLAY("", "GoodDisplay E-Paper", this); + ESP_LOGCONFIG(TAG, + " Model: 4.2in B/W GDEY042T81\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +static const uint8_t LUT_VCOM_DC_4_2[] = { + 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00, 0x17, 0x17, 0x00, 0x00, 0x02, 0x00, 0x0A, 0x01, + 0x00, 0x00, 0x01, 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +static const uint8_t LUT_WHITE_TO_WHITE_4_2[] = { + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, 0x40, 0x0A, + 0x01, 0x00, 0x00, 0x01, 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; +static const uint8_t LUT_BLACK_TO_WHITE_4_2[] = { + 0x40, 0x17, 0x00, 0x00, 0x00, 0x02, 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, 0x40, 0x0A, + 0x01, 0x00, 0x00, 0x01, 0xA0, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_BLACK_TO_BLACK_4_2[] = { + 0x80, 0x17, 0x00, 0x00, 0x00, 0x02, 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, 0x80, 0x0A, + 0x01, 0x00, 0x00, 0x01, 0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t LUT_WHITE_TO_BLACK_4_2[] = { + 0x80, 0x17, 0x00, 0x00, 0x00, 0x02, 0x90, 0x17, 0x17, 0x00, 0x00, 0x02, 0x80, 0x0A, + 0x01, 0x00, 0x00, 0x01, 0x50, 0x0E, 0x0E, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +void WaveshareEPaper4P2In::initialize() { + // https://www.waveshare.com/w/upload/7/7f/4.2inch-e-paper-b-specification.pdf - page 8 + + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x03); // VDS_EN, VDG_EN + this->data(0x00); // VCOM_HV, VGHL_LV[1], VGHL_LV[0] + this->data(0x2B); // VDH + this->data(0x2B); // VDL + this->data(0xFF); // VDHR + + // COMMAND BOOSTER SOFT START + this->command(0x06); + this->data(0x17); // PHA + this->data(0x17); // PHB + this->data(0x17); // PHC + + // COMMAND POWER ON + this->command(0x04); + this->wait_until_idle_(); + delay(10); + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0xBF); // KW-BF KWR-AF BWROTP 0f + this->data(0x0B); + // COMMAND PLL CONTROL + this->command(0x30); + this->data(0x3C); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ + + delay(2); + // COMMAND LUT FOR VCOM + this->command(0x20); + for (uint8_t i : LUT_VCOM_DC_4_2) + this->data(i); + // COMMAND LUT WHITE TO WHITE + this->command(0x21); + for (uint8_t i : LUT_WHITE_TO_WHITE_4_2) + this->data(i); + // COMMAND LUT BLACK TO WHITE + this->command(0x22); + for (uint8_t i : LUT_BLACK_TO_WHITE_4_2) + this->data(i); + // COMMAND LUT WHITE TO BLACK + this->command(0x23); + for (uint8_t i : LUT_WHITE_TO_BLACK_4_2) + this->data(i); + // COMMAND LUT BLACK TO BLACK + this->command(0x24); + for (uint8_t i : LUT_BLACK_TO_BLACK_4_2) + this->data(i); +} +void HOT WaveshareEPaper4P2In::display() { + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x01); + this->data(0x90); + this->data(0x01); + this->data(0x2C); + + // COMMAND VCM DC SETTING REGISTER + this->command(0x82); + this->data(0x12); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x97); + + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + delay(2); + // COMMAND DATA START TRANSMISSION 2 + this->command(0x13); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + // COMMAND DISPLAY REFRESH + this->command(0x12); +} +int WaveshareEPaper4P2In::get_width_internal() { return 400; } +int WaveshareEPaper4P2In::get_height_internal() { return 300; } +void WaveshareEPaper4P2In::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 4.2in"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// 4.20in Type B (LUT from OTP) +// Datasheet: +// - https://www.waveshare.com/w/upload/2/20/4.2inch-e-paper-module-user-manual-en.pdf +// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_4in2b_V2.c +// ======================================================== +void WaveshareEPaper4P2InBV2::initialize() { + // these exact timings are required for a proper reset/init + this->reset_pin_->digital_write(false); + delay(2); + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + + // COMMAND POWER ON + this->command(0x04); + this->wait_until_idle_(); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x0f); // LUT from OTP +} + +void HOT WaveshareEPaper4P2InBV2::display() { + // COMMAND DATA START TRANSMISSION 1 (B/W data) + this->command(0x10); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // COMMAND DATA START TRANSMISSION 2 (RED data) + this->command(0x13); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0xFF); + this->end_data_(); + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + this->wait_until_idle_(); + + // COMMAND POWER OFF + // NOTE: power off < deep sleep + this->command(0x02); +} +int WaveshareEPaper4P2InBV2::get_width_internal() { return 400; } +int WaveshareEPaper4P2InBV2::get_height_internal() { return 300; } +void WaveshareEPaper4P2InBV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 4.2in (B V2)"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// 4.20in Type B With Red colour support (LUT from OTP) +// Datasheet: +// - https://www.waveshare.com/w/upload/2/20/4.2inch-e-paper-module-user-manual-en.pdf +// - https://github.com/waveshare/e-Paper/blob/master/RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_4in2b_V2.c +// The implementation is an adaptation of WaveshareEPaper4P2InBV2 class +// ======================================================== +void WaveshareEPaper4P2InBV2BWR::initialize() { + // these exact timings are required for a proper reset/init + this->reset_pin_->digital_write(false); + delay(2); + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + + // COMMAND POWER ON + this->command(0x04); + this->wait_until_idle_(); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x0f); // LUT from OTP +} + +void HOT WaveshareEPaper4P2InBV2BWR::display() { + const uint32_t buf_len = this->get_buffer_length_() / 2u; + + this->command(0x10); // Send BW data Transmission + delay(2); // Delay to prevent Watchdog error + for (uint32_t i = 0; i < buf_len; ++i) { + this->data(this->buffer_[i]); + } + + this->command(0x13); // Send red data Transmission + delay(2); // Delay to prevent Watchdog error + for (uint32_t i = 0; i < buf_len; ++i) { + // Red color need to flip bit from the buffer. Otherwise, red will conqure the screen! + this->data(~this->buffer_[buf_len + i]); + } + + // COMMAND DISPLAY REFRESH + this->command(0x12); + this->wait_until_idle_(); + + // COMMAND POWER OFF + // NOTE: power off < deep sleep + this->command(0x02); +} +int WaveshareEPaper4P2InBV2BWR::get_width_internal() { return 400; } +int WaveshareEPaper4P2InBV2BWR::get_height_internal() { return 300; } +void WaveshareEPaper4P2InBV2BWR::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 4.2in (B V2) BWR-Mode"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper5P8In::initialize() { + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x37); + this->data(0x00); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0xCF); + this->data(0x0B); + + // COMMAND BOOSTER SOFT START + this->command(0x06); + this->data(0xC7); + this->data(0xCC); + this->data(0x28); + + // COMMAND POWER ON + this->command(0x04); + this->wait_until_idle_(); + delay(10); + + // COMMAND PLL CONTROL + this->command(0x30); + this->data(0x3C); + + // COMMAND TEMPERATURE SENSOR CALIBRATION + this->command(0x41); + this->data(0x00); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x77); + + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x02); + this->data(0x58); + this->data(0x01); + this->data(0xC0); + + // COMMAND VCM DC SETTING REGISTER + this->command(0x82); + this->data(0x1E); + + this->command(0xE5); + this->data(0x03); +} +void HOT WaveshareEPaper5P8In::display() { + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); + + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) { + uint8_t temp1 = this->buffer_[i]; + for (uint8_t j = 0; j < 8; j++) { + uint8_t temp2; + if (temp1 & 0x80) { + temp2 = 0x03; + } else { + temp2 = 0x00; + } + + temp2 <<= 4; + temp1 <<= 1; + j++; + if (temp1 & 0x80) { + temp2 |= 0x03; + } else { + temp2 |= 0x00; + } + temp1 <<= 1; + this->write_byte(temp2); + } + + App.feed_wdt(); + } + this->end_data_(); + + // COMMAND DISPLAY REFRESH + this->command(0x12); +} +int WaveshareEPaper5P8In::get_width_internal() { return 600; } +int WaveshareEPaper5P8In::get_height_internal() { return 448; } +void WaveshareEPaper5P8In::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 5.83in"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// 5.83in V2 +// Datasheet/Specification/Reference: +// - https://www.waveshare.com/w/upload/3/37/5.83inch_e-Paper_V2_Specification.pdf +// - https://github.com/waveshare/e-Paper/blob/master/Arduino/epd5in83_V2/epd5in83_V2.cpp +// ======================================================== +void WaveshareEPaper5P8InV2::initialize() { + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x07); + this->data(0x07); + this->data(0x3f); + this->data(0x3f); + + // COMMAND POWER ON + this->command(0x04); + delay(10); + this->wait_until_idle_(); + + // PANNEL SETTING + this->command(0x00); + this->data(0x1F); + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x02); + this->data(0x88); + this->data(0x01); + this->data(0xE0); + + this->command(0x15); + this->data(0x00); + + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + + // Do we need this? + // COMMAND PLL CONTROL + this->command(0x30); + this->data(0x3C); // 3A 100HZ 29 150Hz 39 200HZ 31 171HZ +} +void HOT WaveshareEPaper5P8InV2::display() { + // Reuse the code from WaveshareEPaper4P2In::display() + // COMMAND VCM DC SETTING REGISTER + this->command(0x82); + this->data(0x12); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x97); + + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 + this->command(0x13); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // COMMAND DISPLAY REFRESH + this->command(0x12); +} +int WaveshareEPaper5P8InV2::get_width_internal() { return 648; } +int WaveshareEPaper5P8InV2::get_height_internal() { return 480; } +void WaveshareEPaper5P8InV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 5.83inv2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// ======================================================== +// Good Display 5.83in black/white GDEY0583T81 +// Product page: +// - https://www.good-display.com/product/440.html +// - https://www.seeedstudio.com/5-83-Monochrome-ePaper-Display-with-648x480-Pixels-p-5785.html +// Datasheet: +// - +// https://www.good-display.com/public/html/pdfjs/viewer/viewernew.html?file=https://v4.cecdn.yun300.cn/100001_1909185148/GDEY0583T81-new.pdf +// - https://v4.cecdn.yun300.cn/100001_1909185148/GDEY0583T81-new.pdf +// Reference code from GoodDisplay: +// - https://www.good-display.com/companyfile/903.html +// ======================================================== + +void GDEY0583T81::initialize() { + // Allocate buffer for old data for partial updates + RAMAllocator allocator{}; + this->old_buffer_ = allocator.allocate(this->get_buffer_length_()); + if (this->old_buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate old buffer for display!"); + return; + } + memset(this->old_buffer_, 0xFF, this->get_buffer_length_()); + + this->init_full_(); + + this->wait_until_idle_(); + + this->deep_sleep(); +} + +void GDEY0583T81::power_on_() { + if (!this->power_is_on_) { + this->command(0x04); + this->wait_until_idle_(); + } + this->power_is_on_ = true; + this->is_deep_sleep_ = false; +} + +void GDEY0583T81::power_off_() { + this->command(0x02); + this->wait_until_idle_(); + this->power_is_on_ = false; +} + +void GDEY0583T81::deep_sleep() { + if (this->is_deep_sleep_) { + return; + } + + // VCOM and data interval setting (CDI) + this->command(0x50); + this->data(0xf7); + + this->power_off_(); + delay(10); + + // Deep sleep (DSLP) + this->command(0x07); + this->data(0xA5); + this->is_deep_sleep_ = true; +} + +void GDEY0583T81::reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(10); + this->reset_pin_->digital_write(true); + delay(10); + } +} + +// Initialize for full screen update in fast mode +void GDEY0583T81::init_full_() { + this->init_display_(); + + // Based on the GD sample code + // VCOM and data interval setting (CDI) + this->command(0x50); + this->data(0x29); + this->data(0x07); + + // Cascade Setting (CCSET) + this->command(0xE0); + this->data(0x02); + + // Force Temperature (TSSET) + this->command(0xE5); + this->data(0x5A); +} + +// Initialize for a partial update of the full screen +void GDEY0583T81::init_partial_() { + this->init_display_(); + + // Cascade Setting (CCSET) + this->command(0xE0); + this->data(0x02); + + // Force Temperature (TSSET) + this->command(0xE5); + this->data(0x6E); +} + +void GDEY0583T81::init_display_() { + this->reset_(); + + // Panel Setting (PSR) + this->command(0x00); + // Sets: REG=0, LUT from OTP (set by CDI) + // KW/R=1, Sets KW mode (Black/White) + // as opposed to the default KWR mode (Black/White/Red) + // UD=1, Gate Scan Direction, 1 = up (default) + // SHL=1, Source Shift Direction, 1 = right (default) + // SHD_N=1, Booster Switch, 1 = ON (default) + // RST_N=1, Soft reset, 1 = No effect (default) + this->data(0x1F); + + // Resolution setting (TRES) + this->command(0x61); + + // Horizontal display resolution (HRES) + this->data(get_width_internal() / 256); + this->data(get_width_internal() % 256); + + // Vertical display resolution (VRES) + this->data(get_height_internal() / 256); + this->data(get_height_internal() % 256); + + this->power_on_(); +} + +void HOT GDEY0583T81::display() { + bool full_update = this->at_update_ == 0; + if (full_update) { + this->init_full_(); + } else { + this->init_partial_(); + + // VCOM and data interval setting (CDI) + this->command(0x50); + this->data(0xA9); + this->data(0x07); + + // Partial In (PTIN), makes the display enter partial mode + this->command(0x91); + + // Partial Window (PTL) + // We use the full screen as the window + this->command(0x90); + + // Horizontal start/end channel bank (HRST/HRED) + this->data(0); + this->data(0); + this->data((get_width_internal() - 1) / 256); + this->data((get_width_internal() - 1) % 256); + + // Vertical start/end line (VRST/VRED) + this->data(0); + this->data(0); + this->data((get_height_internal() - 1) / 256); + this->data((get_height_internal() - 1) % 256); + + this->data(0x01); + + // Display Start Transmission 1 (DTM1) + // in KW mode this writes "OLD" data to SRAM + this->command(0x10); + this->start_data_(); + this->write_array(this->old_buffer_, this->get_buffer_length_()); + this->end_data_(); + } + + // Display Start Transmission 2 (DTM2) + // in KW mode this writes "NEW" data to SRAM + this->command(0x13); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + for (size_t i = 0; i < this->get_buffer_length_(); i++) { + this->old_buffer_[i] = this->buffer_[i]; + } + + // Display Refresh (DRF) + this->command(0x12); + delay(10); + this->wait_until_idle_(); + + if (full_update) { + ESP_LOGD(TAG, "Full update done"); + } else { + // Partial out (PTOUT), makes the display exit partial mode + this->command(0x92); + ESP_LOGD(TAG, "Partial update done, next full update after %" PRIu32 " cycles", + this->full_update_every_ - this->at_update_ - 1); + } + + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; + + this->deep_sleep(); +} + +void GDEY0583T81::set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } +int GDEY0583T81::get_width_internal() { return 648; } +int GDEY0583T81::get_height_internal() { return 480; } +uint32_t GDEY0583T81::idle_timeout_() { return 5000; } +void GDEY0583T81::dump_config() { + LOG_DISPLAY("", "GoodDisplay E-Paper", this); + ESP_LOGCONFIG(TAG, + " Model: 5.83in B/W GDEY0583T81\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper7P5InBV2::initialize() { + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x07); + this->data(0x07); // VGH=20V,VGL=-20V + this->data(0x3f); // VDH=15V + this->data(0x3f); // VDL=-15V + // COMMAND POWER ON + this->command(0x04); + delay(100); // NOLINT + this->wait_until_idle_(); + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x0F); // KW3f, KWR-2F, BWROTP 0f, BWOTP 1f + this->command(0x61); // tres + this->data(0x03); // 800px + this->data(0x20); + this->data(0x01); // 400px + this->data(0xE0); + this->command(0x15); + this->data(0x00); + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x11); + this->data(0x07); + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + + this->command(0x82); + this->data(0x08); + this->command(0x30); + this->data(0x06); + + // COMMAND RESOLUTION SETTING + this->command(0x65); + this->data(0x00); + this->data(0x00); // 800*480 + this->data(0x00); + this->data(0x00); +} +void HOT WaveshareEPaper7P5InBV2::display() { + // COMMAND DATA START TRANSMISSION 1 (B/W data) + this->command(0x10); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED data) + this->command(0x13); + delay(2); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0x00); + this->end_data_(); + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + delay(100); // NOLINT + this->wait_until_idle_(); + this->deep_sleep(); +} +int WaveshareEPaper7P5InBV2::get_width_internal() { return 800; } +int WaveshareEPaper7P5InBV2::get_height_internal() { return 480; } +void WaveshareEPaper7P5InBV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-bv2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper7P5InBV3::initialize() { this->init_display_(); } +bool WaveshareEPaper7P5InBV3::wait_until_idle_() { + if (this->busy_pin_ == nullptr) { + return true; + } + + const uint32_t start = millis(); + while (this->busy_pin_->digital_read()) { + this->command(0x71); + if (millis() - start > this->idle_timeout_()) { + ESP_LOGI(TAG, "Timeout while displaying image!"); + return false; + } + App.feed_wdt(); + delay(10); + } + delay(200); // NOLINT + return true; +}; +void WaveshareEPaper7P5InBV3::init_display_() { + this->reset_(); + + // COMMAND POWER SETTING + this->command(0x01); + + // 1-0=11: internal power + this->data(0x07); + this->data(0x17); // VGH&VGL + this->data(0x3F); // VSH + this->data(0x26); // VSL + this->data(0x11); // VSHR + + // VCOM DC Setting + this->command(0x82); + this->data(0x24); // VCOM + + // Booster Setting + this->command(0x06); + this->data(0x27); + this->data(0x27); + this->data(0x2F); + this->data(0x17); + + // POWER ON + this->command(0x04); + + delay(100); // NOLINT + this->wait_until_idle_(); + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x3F); // KW-3f KWR-2F BWROTP 0f BWOTP 1f + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x03); // source 800 + this->data(0x20); + this->data(0x01); // gate 480 + this->data(0xE0); + // COMMAND ...? + this->command(0x15); + this->data(0x00); + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x10); + this->data(0x00); + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + // Resolution setting + this->command(0x65); + this->data(0x00); + this->data(0x00); // 800*480 + this->data(0x00); + this->data(0x00); + + uint8_t lut_vcom_7_i_n5_v2[] = { + 0x0, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0xF, 0x1, 0xF, 0x1, 0x2, 0x0, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_ww_7_i_n5_v2[] = { + 0x10, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x20, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_bw_7_i_n5_v2[] = { + 0x10, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x20, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_wb_7_i_n5_v2[] = { + 0x80, 0xF, 0xF, 0x0, 0x0, 0x3, 0x84, 0xF, 0x1, 0xF, 0x1, 0x4, 0x40, 0xF, 0xF, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_bb_7_i_n5_v2[] = { + 0x80, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x40, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t count; + this->command(0x20); // VCOM + for (count = 0; count < 42; count++) + this->data(lut_vcom_7_i_n5_v2[count]); + + this->command(0x21); // LUTBW + for (count = 0; count < 42; count++) + this->data(lut_ww_7_i_n5_v2[count]); + + this->command(0x22); // LUTBW + for (count = 0; count < 42; count++) + this->data(lut_bw_7_i_n5_v2[count]); + + this->command(0x23); // LUTWB + for (count = 0; count < 42; count++) + this->data(lut_wb_7_i_n5_v2[count]); + + this->command(0x24); // LUTBB + for (count = 0; count < 42; count++) + this->data(lut_bb_7_i_n5_v2[count]); +}; +void HOT WaveshareEPaper7P5InBV3::display() { + this->init_display_(); + uint32_t buf_len = this->get_buffer_length_(); + + this->command(0x10); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(0xFF); + } + + this->command(0x13); // Start Transmission + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(~this->buffer_[i]); + } + + this->command(0x12); // Display Refresh + delay(100); // NOLINT + this->wait_until_idle_(); + this->deep_sleep(); +} +int WaveshareEPaper7P5InBV3::get_width_internal() { return 800; } +int WaveshareEPaper7P5InBV3::get_height_internal() { return 480; } +void WaveshareEPaper7P5InBV3::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-bv3"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper7P5InBV3BWR::initialize() { this->init_display_(); } +bool WaveshareEPaper7P5InBV3BWR::wait_until_idle_() { + if (this->busy_pin_ == nullptr) { + return true; + } + + const uint32_t start = millis(); + while (this->busy_pin_->digital_read()) { + this->command(0x71); + if (millis() - start > this->idle_timeout_()) { + ESP_LOGI(TAG, "Timeout while displaying image!"); + return false; + } + App.feed_wdt(); + delay(10); + } + delay(200); // NOLINT + return true; +}; +void WaveshareEPaper7P5InBV3BWR::init_display_() { + this->reset_(); + + // COMMAND POWER SETTING + this->command(0x01); + + // 1-0=11: internal power + this->data(0x07); // VRS_EN=1, VS_EN=1, VG_EN=1 + this->data(0x17); // VGH&VGL ??? VCOM_SLEW=1 but this is fixed, VG_LVL[2:0]=111 => VGH=20V VGL=-20V, it could be 0x07 + this->data(0x3F); // VSH=15V? + this->data(0x26); // VSL=-9.4V? + this->data(0x11); // VSHR=5.8V? + + // VCOM DC Setting + this->command(0x82); + this->data(0x24); // VCOM=-1.9V + + // POWER ON + this->command(0x04); + delay(100); // NOLINT + this->wait_until_idle_(); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x0F); // KW-3f KWR-2F BWROTP 0f BWOTP 1f + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x03); // source 800 + this->data(0x20); + this->data(0x01); // gate 480 + this->data(0xE0); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x20); + this->data(0x00); + + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + + // Resolution setting + this->command(0x65); + this->data(0x00); + this->data(0x00); // 800*480 + this->data(0x00); + this->data(0x00); +}; +void HOT WaveshareEPaper7P5InBV3BWR::display() { + this->init_display_(); + const uint32_t buf_len = this->get_buffer_length_() / 2u; + + this->command(0x10); // Send BW data Transmission + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + + this->command(0x13); // Send red data Transmission + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i + buf_len]); + } + + this->command(0x12); // Display Refresh + delay(100); // NOLINT + this->wait_until_idle_(); + this->deep_sleep(); +} +int WaveshareEPaper7P5InBV3BWR::get_width_internal() { return 800; } +int WaveshareEPaper7P5InBV3BWR::get_height_internal() { return 480; } +void WaveshareEPaper7P5InBV3BWR::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-bv3 BWR-Mode"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper7P5In::initialize() { + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x37); + this->data(0x00); + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0xCF); + this->data(0x0B); + // COMMAND BOOSTER SOFT START + this->command(0x06); + this->data(0xC7); + this->data(0xCC); + this->data(0x28); + // COMMAND POWER ON + this->command(0x04); + this->wait_until_idle_(); + delay(10); + // COMMAND PLL CONTROL + this->command(0x30); + this->data(0x3C); + // COMMAND TEMPERATURE SENSOR CALIBRATION + this->command(0x41); + this->data(0x00); + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x77); + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x02); + this->data(0x80); + this->data(0x01); + this->data(0x80); + // COMMAND VCM DC SETTING REGISTER + this->command(0x82); + this->data(0x1E); + this->command(0xE5); + this->data(0x03); +} +void HOT WaveshareEPaper7P5In::display() { + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) { + uint8_t temp1 = this->buffer_[i]; + for (uint8_t j = 0; j < 8; j++) { + uint8_t temp2; + if (temp1 & 0x80) { + temp2 = 0x03; + } else { + temp2 = 0x00; + } + temp2 <<= 4; + temp1 <<= 1; + j++; + if (temp1 & 0x80) { + temp2 |= 0x03; + } else { + temp2 |= 0x00; + } + temp1 <<= 1; + this->write_byte(temp2); + } + App.feed_wdt(); + } + this->end_data_(); + // COMMAND DISPLAY REFRESH + this->command(0x12); +} +int WaveshareEPaper7P5In::get_width_internal() { return 640; } +int WaveshareEPaper7P5In::get_height_internal() { return 384; } +void WaveshareEPaper7P5In::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +// Waveshare 5.65F ======================================================== + +namespace cmddata_5P65InF { +// WaveshareEPaper5P65InF commands +// https://www.waveshare.com/wiki/5.65inch_e-Paper_Module_(F) + +// R00H (PSR): Panel setting Register +// UD(1): scan up +// SHL(1) shift right +// SHD_N(1) DC-DC on +// RST_N(1) no reset +static const uint8_t R00_CMD_PSR[] = {0x00, 0xEF, 0x08}; + +// R01H (PWR): Power setting Register +// internal DC-DC power generation +static const uint8_t R01_CMD_PWR[] = {0x01, 0x07, 0x00, 0x00, 0x00}; + +// R02H (POF): Power OFF Command +static const uint8_t R02_CMD_POF[] = {0x02}; + +// R03H (PFS): Power off sequence setting Register +// T_VDS_OFF (00) = 1 frame +static const uint8_t R03_CMD_PFS[] = {0x03, 0x00}; + +// R04H (PON): Power ON Command +static const uint8_t R04_CMD_PON[] = {0x04}; + +// R06h (BTST): Booster Soft Start +static const uint8_t R06_CMD_BTST[] = {0x06, 0xC7, 0xC7, 0x1D}; + +// R07H (DSLP): Deep sleep# +// Note Documentation @ Waveshare shows cmd code as 0x10 in table, but +// 0x10 is DTM1. +static const uint8_t R07_CMD_DSLP[] = {0x07, 0xA5}; + +// R10H (DTM1): Data Start Transmission 1 + +static const uint8_t R10_CMD_DTM1[] = {0x10}; + +// R11H (DSP): Data Stop +static const uint8_t R11_CMD_DSP[] = {0x11}; + +// R12H (DRF): Display Refresh +static const uint8_t R12_CMD_DRF[] = {0x12}; + +// R13H (IPC): Image Process Command +static const uint8_t R13_CMD_IPC[] = {0x13, 0x00}; + +// R30H (PLL): PLL Control +// 0x3C = 50Hz +static const uint8_t R30_CMD_PLL[] = {0x30, 0x3C}; + +// R41H (TSE): Temperature Sensor Enable +// TSE(0) enable, TO(0000) +0 degree offset +static const uint8_t R41_CMD_TSE[] = {0x41, 0x00}; + +// R50H (CDI) VCOM and Data interval setting +// CDI(0111) 10 +// DDX(1), VBD(001) Border output "White" +static const uint8_t R50_CMD_CDI[] = {0x50, 0x37}; + +// R60H (TCON) Gate and Source non overlap period command +// S2G(10) 12 units +// G2S(10) 12 units +static const uint8_t R60_CMD_TCON[] = {0x60, 0x22}; + +// R61H (TRES) Resolution Setting +// 0x258 = 600 +// 0x1C0 = 448 +static const uint8_t R61_CMD_TRES[] = {0x61, 0x02, 0x58, 0x01, 0xC0}; + +// RE3H (PWS) Power Savings +static const uint8_t RE3_CMD_PWS[] = {0xE3, 0xAA}; +} // namespace cmddata_5P65InF + +void WaveshareEPaper5P65InF::initialize() { + if (this->buffers_[0] == nullptr) { + ESP_LOGE(TAG, "Buffer unavailable!"); + return; + } + + this->reset_(); + delay(20); + this->wait_until_(IDLE); + + using namespace cmddata_5P65InF; + + this->cmd_data(R00_CMD_PSR, sizeof(R00_CMD_PSR)); + this->cmd_data(R01_CMD_PWR, sizeof(R01_CMD_PWR)); + this->cmd_data(R03_CMD_PFS, sizeof(R03_CMD_PFS)); + this->cmd_data(R06_CMD_BTST, sizeof(R06_CMD_BTST)); + this->cmd_data(R30_CMD_PLL, sizeof(R30_CMD_PLL)); + this->cmd_data(R41_CMD_TSE, sizeof(R41_CMD_TSE)); + this->cmd_data(R50_CMD_CDI, sizeof(R50_CMD_CDI)); + this->cmd_data(R60_CMD_TCON, sizeof(R60_CMD_TCON)); + this->cmd_data(R61_CMD_TRES, sizeof(R61_CMD_TRES)); + this->cmd_data(RE3_CMD_PWS, sizeof(RE3_CMD_PWS)); + + delay(100); // NOLINT + this->cmd_data(R50_CMD_CDI, sizeof(R50_CMD_CDI)); + + ESP_LOGI(TAG, "Display initialized successfully"); +} + +void HOT WaveshareEPaper5P65InF::display() { + // INITIALIZATION + ESP_LOGI(TAG, "Initialise the display"); + this->initialize(); + + using namespace cmddata_5P65InF; + + // COMMAND DATA START TRANSMISSION + ESP_LOGI(TAG, "Sending data to the display"); + this->cmd_data(R61_CMD_TRES, sizeof(R61_CMD_TRES)); + this->cmd_data(R10_CMD_DTM1, sizeof(R10_CMD_DTM1)); + this->send_buffers_(); + + // COMMAND POWER ON + ESP_LOGI(TAG, "Power on the display"); + this->cmd_data(R04_CMD_PON, sizeof(R04_CMD_PON)); + this->wait_until_(IDLE); + + // COMMAND REFRESH SCREEN + ESP_LOGI(TAG, "Refresh the display"); + this->cmd_data(R12_CMD_DRF, sizeof(R12_CMD_DRF)); + this->wait_until_(IDLE); + + // COMMAND POWER OFF + ESP_LOGI(TAG, "Power off the display"); + this->cmd_data(R02_CMD_POF, sizeof(R02_CMD_POF)); + this->wait_until_(BUSY); + + if (this->deep_sleep_between_updates_) { + ESP_LOGI(TAG, "Set the display to deep sleep"); + this->cmd_data(R07_CMD_DSLP, sizeof(R07_CMD_DSLP)); + } +} + +int WaveshareEPaper5P65InF::get_width_internal() { return 600; } +int WaveshareEPaper5P65InF::get_height_internal() { return 448; } +uint32_t WaveshareEPaper5P65InF::idle_timeout_() { return 35000; } + +void WaveshareEPaper5P65InF::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 5.65in-F"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +bool WaveshareEPaper5P65InF::wait_until_(WaitForState busy_state) { + if (this->busy_pin_ == nullptr) { + return true; + } + + const uint32_t start = millis(); + while (busy_state != this->busy_pin_->digital_read()) { + if (millis() - start > this->idle_timeout_()) { + ESP_LOGE(TAG, "Timeout while displaying image!"); + return false; + } + App.feed_wdt(); + delay(10); + } + return true; +} + +void WaveshareEPaper7P3InF::initialize() { + if (this->buffers_[0] == nullptr) { + ESP_LOGE(TAG, "Buffer unavailable!"); + return; + } + + this->reset_(); + delay(20); + this->wait_until_idle_(); + + // COMMAND CMDH + this->command(0xAA); + this->data(0x49); + this->data(0x55); + this->data(0x20); + this->data(0x08); + this->data(0x09); + this->data(0x18); + + this->command(0x01); + this->data(0x3F); + this->data(0x00); + this->data(0x32); + this->data(0x2A); + this->data(0x0E); + this->data(0x2A); + + this->command(0x00); + this->data(0x5F); + this->data(0x69); + + this->command(0x03); + this->data(0x00); + this->data(0x54); + this->data(0x00); + this->data(0x44); + + this->command(0x05); + this->data(0x40); + this->data(0x1F); + this->data(0x1F); + this->data(0x2C); + + this->command(0x06); + this->data(0x6F); + this->data(0x1F); + this->data(0x1F); + this->data(0x22); + + this->command(0x08); + this->data(0x6F); + this->data(0x1F); + this->data(0x1F); + this->data(0x22); + + // COMMAND IPC + this->command(0x13); + this->data(0x00); + this->data(0x04); + + this->command(0x30); + this->data(0x3C); + + // COMMAND TSE + this->command(0x41); + this->data(0x00); + + this->command(0x50); + this->data(0x3F); + + this->command(0x60); + this->data(0x02); + this->data(0x00); + + this->command(0x61); + this->data(0x03); + this->data(0x20); + this->data(0x01); + this->data(0xE0); + + this->command(0x82); + this->data(0x1E); + + this->command(0x84); + this->data(0x00); + + // COMMAND AGID + this->command(0x86); + this->data(0x00); + + this->command(0xE3); + this->data(0x2F); + + // COMMAND CCSET + this->command(0xE0); + this->data(0x00); + + // COMMAND TSSET + this->command(0xE6); + this->data(0x00); + + ESP_LOGI(TAG, "Display initialized successfully"); +} +void HOT WaveshareEPaper7P3InF::display() { + // INITIALIZATION + ESP_LOGI(TAG, "Initialise the display"); + this->initialize(); + + // COMMAND DATA START TRANSMISSION + ESP_LOGI(TAG, "Sending data to the display"); + this->command(0x10); + this->send_buffers_(); + + // COMMAND POWER ON + ESP_LOGI(TAG, "Power on the display"); + this->command(0x04); + this->wait_until_idle_(); + + // COMMAND REFRESH SCREEN + ESP_LOGI(TAG, "Refresh the display"); + this->command(0x12); + this->data(0x00); + this->wait_until_idle_(); + + // COMMAND POWER OFF + ESP_LOGI(TAG, "Power off the display"); + this->command(0x02); + this->data(0x00); + this->wait_until_idle_(); + + if (this->deep_sleep_between_updates_) { + ESP_LOGI(TAG, "Set the display to deep sleep"); + this->command(0x07); + this->data(0xA5); + } +} +int WaveshareEPaper7P3InF::get_width_internal() { return 800; } +int WaveshareEPaper7P3InF::get_height_internal() { return 480; } +uint32_t WaveshareEPaper7P3InF::idle_timeout_() { return 35000; } +void WaveshareEPaper7P3InF::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.3in-F"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +bool WaveshareEPaper7P3InF::wait_until_idle_() { + if (this->busy_pin_ == nullptr) { + return true; + } + const uint32_t start = millis(); + while (this->busy_pin_->digital_read()) { + if (millis() - start > this->idle_timeout_()) { + ESP_LOGE(TAG, "Timeout while displaying image!"); + return false; + } + App.feed_wdt(); + delay(10); + } + delay(200); // NOLINT + return true; +} +bool WaveshareEPaper7P5InV2::wait_until_idle_() { + if (this->busy_pin_ == nullptr) { + return true; + } + + const uint32_t start = millis(); + while (this->busy_pin_->digital_read()) { + this->command(0x71); + if (millis() - start > this->idle_timeout_()) { + ESP_LOGE(TAG, "Timeout while displaying image!"); + return false; + } + App.feed_wdt(); + delay(10); + } + return true; +} +void WaveshareEPaper7P5InV2::initialize() { + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x07); + this->data(0x07); + this->data(0x3f); + this->data(0x3f); + + // We don't want the display to be powered at this point + + delay(100); // NOLINT + this->wait_until_idle_(); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x10); + this->data(0x07); + + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x1F); + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x03); + this->data(0x20); + this->data(0x01); + this->data(0xE0); + + // COMMAND DUAL SPI MM_EN, DUSPI_EN + this->command(0x15); + this->data(0x00); + + // COMMAND POWER DRIVER HAT DOWN + // This command will turn off booster, controller, source driver, gate driver, VCOM, and + // temperature sensor, but register data will be kept until VDD turned OFF or Deep Sleep Mode. + // Source/Gate/Border/VCOM will be released to floating. + this->command(0x02); +} +void HOT WaveshareEPaper7P5InV2::display() { + uint32_t buf_len = this->get_buffer_length_(); + + // COMMAND POWER ON + ESP_LOGI(TAG, "Power on the display and hat"); + + // This command will turn on booster, controller, regulators, and temperature sensor will be + // activated for one-time sensing before enabling booster. When all voltages are ready, the + // BUSY_N signal will return to high. + this->command(0x04); + delay(200); // NOLINT + this->wait_until_idle_(); + + // COMMAND DATA START TRANSMISSION NEW DATA + this->command(0x13); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(~(this->buffer_[i])); + } + + delay(100); // NOLINT + this->wait_until_idle_(); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + delay(100); // NOLINT + this->wait_until_idle_(); + + ESP_LOGV(TAG, "Before command(0x02) (>> power off)"); + this->command(0x02); + this->wait_until_idle_(); + ESP_LOGV(TAG, "After command(0x02) (>> power off)"); +} + +int WaveshareEPaper7P5InV2::get_width_internal() { return 800; } +int WaveshareEPaper7P5InV2::get_height_internal() { return 480; } +uint32_t WaveshareEPaper7P5InV2::idle_timeout_() { return 10000; } +void WaveshareEPaper7P5InV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5inV2rev2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +/* 7.50inV2alt */ +bool WaveshareEPaper7P5InV2alt::wait_until_idle_() { + if (this->busy_pin_ == nullptr) { + return true; + } + + const uint32_t start = millis(); + while (this->busy_pin_->digital_read()) { + this->command(0x71); + if (millis() - start > this->idle_timeout_()) { + ESP_LOGI(TAG, "Timeout while displaying image!"); + return false; + } + delay(10); + } + return true; +} + +void WaveshareEPaper7P5InV2alt::initialize() { + this->reset_(); + + // COMMAND POWER SETTING + this->command(0x01); + + // 1-0=11: internal power + this->data(0x07); + this->data(0x17); // VGH&VGL + this->data(0x3F); // VSH + this->data(0x26); // VSL + this->data(0x11); // VSHR + + // VCOM DC Setting + this->command(0x82); + this->data(0x24); // VCOM + + // Booster Setting + this->command(0x06); + this->data(0x27); + this->data(0x27); + this->data(0x2F); + this->data(0x17); + + // POWER ON + this->command(0x04); + + delay(100); // NOLINT + this->wait_until_idle_(); + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x3F); // KW-3f KWR-2F BWROTP 0f BWOTP 1f + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x03); // source 800 + this->data(0x20); + this->data(0x01); // gate 480 + this->data(0xE0); + // COMMAND ...? + this->command(0x15); + this->data(0x00); + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x10); + this->data(0x00); + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + // Resolution setting + this->command(0x65); + this->data(0x00); + this->data(0x00); // 800*480 + this->data(0x00); + this->data(0x00); + + this->wait_until_idle_(); + + uint8_t lut_vcom_7_i_n5_v2[] = { + 0x0, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0xF, 0x1, 0xF, 0x1, 0x2, 0x0, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_ww_7_i_n5_v2[] = { + 0x10, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x20, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_bw_7_i_n5_v2[] = { + 0x10, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x20, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_wb_7_i_n5_v2[] = { + 0x80, 0xF, 0xF, 0x0, 0x0, 0x3, 0x84, 0xF, 0x1, 0xF, 0x1, 0x4, 0x40, 0xF, 0xF, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t lut_bb_7_i_n5_v2[] = { + 0x80, 0xF, 0xF, 0x0, 0x0, 0x1, 0x84, 0xF, 0x1, 0xF, 0x1, 0x2, 0x40, 0xF, 0xF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + }; + + uint8_t count; + this->command(0x20); // VCOM + for (count = 0; count < 42; count++) + this->data(lut_vcom_7_i_n5_v2[count]); + + this->command(0x21); // LUTBW + for (count = 0; count < 42; count++) + this->data(lut_ww_7_i_n5_v2[count]); + + this->command(0x22); // LUTBW + for (count = 0; count < 42; count++) + this->data(lut_bw_7_i_n5_v2[count]); + + this->command(0x23); // LUTWB + for (count = 0; count < 42; count++) + this->data(lut_wb_7_i_n5_v2[count]); + + this->command(0x24); // LUTBB + for (count = 0; count < 42; count++) + this->data(lut_bb_7_i_n5_v2[count]); +} + +void WaveshareEPaper7P5InV2alt::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5inV2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +/* 7.50inV2 with partial and fast refresh */ +bool WaveshareEPaper7P5InV2P::wait_until_idle_() { + if (this->busy_pin_ == nullptr) { + return true; + } + + const uint32_t start = millis(); + while (this->busy_pin_->digital_read()) { + this->command(0x71); + if (millis() - start > this->idle_timeout_()) { + ESP_LOGE(TAG, "Timeout while displaying image!"); + return false; + } + App.feed_wdt(); + delay(10); + } + return true; +} + +void WaveshareEPaper7P5InV2P::reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(true); + delay(20); + this->reset_pin_->digital_write(false); + delay(2); + this->reset_pin_->digital_write(true); + delay(20); + } +} + +void WaveshareEPaper7P5InV2P::turn_on_display_() { + this->command(0x12); + delay(100); // NOLINT + this->wait_until_idle_(); +} + +void WaveshareEPaper7P5InV2P::initialize() { + this->reset_(); + + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x07); + this->data(0x07); + this->data(0x3f); + this->data(0x3f); + + // COMMAND BOOSTER SOFT START + this->command(0x06); + this->data(0x17); + this->data(0x17); + this->data(0x28); + this->data(0x17); + + // COMMAND POWER DRIVER HAT UP + this->command(0x04); + delay(100); // NOLINT + this->wait_until_idle_(); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x1F); + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x03); + this->data(0x20); + this->data(0x01); + this->data(0xE0); + + // COMMAND DUAL SPI MM_EN, DUSPI_EN + this->command(0x15); + this->data(0x00); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x10); + this->data(0x07); + + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + + // COMMAND ENABLE FAST UPDATE + this->command(0xE0); + this->data(0x02); + this->command(0xE5); + this->data(0x5A); + + // COMMAND POWER DRIVER HAT DOWN + this->command(0x02); +} + +void HOT WaveshareEPaper7P5InV2P::display() { + uint32_t buf_len = this->get_buffer_length_(); + + // COMMAND POWER ON + ESP_LOGI(TAG, "Power on the display and hat"); + + this->command(0x04); + delay(200); // NOLINT + this->wait_until_idle_(); + + if (this->full_update_every_ == 1) { + this->command(0x13); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(~(this->buffer_[i])); + } + + this->turn_on_display_(); + + this->command(0x02); + this->wait_until_idle_(); + return; + } + + this->command(0x50); + this->data(0xA9); + this->data(0x07); + + if (this->at_update_ == 0) { + // Enable fast refresh + this->command(0xE5); + this->data(0x5A); + + this->command(0x92); + + this->command(0x10); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(~(this->buffer_[i])); + } + + delay(100); // NOLINT + this->wait_until_idle_(); + + this->command(0x13); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + + delay(100); // NOLINT + this->wait_until_idle_(); + + this->turn_on_display_(); + + } else { + // Enable partial refresh + this->command(0xE5); + this->data(0x6E); + + // Activate partial refresh and set window bounds + this->command(0x91); + this->command(0x90); + + this->data(0x00); + this->data(0x00); + this->data((get_width_internal() - 1) >> 8 & 0xFF); + this->data((get_width_internal() - 1) & 0xFF); + + this->data(0x00); + this->data(0x00); + this->data((get_height_internal() - 1) >> 8 & 0xFF); + this->data((get_height_internal() - 1) & 0xFF); + + this->data(0x01); + + this->command(0x13); + delay(2); + for (uint32_t i = 0; i < buf_len; i++) { + this->data(this->buffer_[i]); + } + + delay(100); // NOLINT + this->wait_until_idle_(); + + this->turn_on_display_(); + } + + ESP_LOGV(TAG, "Before command(0x02) (>> power off)"); + this->command(0x02); + this->wait_until_idle_(); + ESP_LOGV(TAG, "After command(0x02) (>> power off)"); + + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; +} + +int WaveshareEPaper7P5InV2P::get_width_internal() { return 800; } +int WaveshareEPaper7P5InV2P::get_height_internal() { return 480; } +uint32_t WaveshareEPaper7P5InV2P::idle_timeout_() { return 10000; } +void WaveshareEPaper7P5InV2P::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, + " Model: 7.50inv2p\n" + " Full Update Every: %" PRIu32, + this->full_update_every_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} +void WaveshareEPaper7P5InV2P::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + +/* 7.50in-bc */ +void WaveshareEPaper7P5InBC::initialize() { + /* The command sequence is similar to the 7P5In display but differs in subtle ways + to allow for faster updates. */ + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x37); + this->data(0x00); + + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0xCF); + this->data(0x08); + + // COMMAND PLL CONTROL + this->command(0x30); + this->data(0x3A); + + // COMMAND VCM_DC_SETTING: all temperature range + this->command(0x82); + this->data(0x28); + + // COMMAND BOOSTER SOFT START + this->command(0x06); + this->data(0xC7); + this->data(0xCC); + this->data(0x15); + + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x77); + + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + + // COMMAND FLASH CONTROL + this->command(0x65); + this->data(0x00); + + // COMMAND RESOLUTION SETTING + this->command(0x61); + this->data(0x02); // 640 >> 8 + this->data(0x80); + this->data(0x01); // 384 >> 8 + this->data(0x80); + + // COMMAND FLASH MODE + this->command(0xE5); + this->data(0x03); +} + +void HOT WaveshareEPaper7P5InBC::display() { + // COMMAND DATA START TRANSMISSION 1 + this->command(0x10); + this->start_data_(); + + for (size_t i = 0; i < this->get_buffer_length_(); i++) { + // A line of eight source pixels (each a bit in this byte) + uint8_t eight_pixels = this->buffer_[i]; + + for (uint8_t j = 0; j < 8; j += 2) { + /* For bichromatic displays, each byte represents two pixels. Each nibble encodes a pixel: 0=white, 3=black, + 4=color. Therefore, e.g. 0x44 = two adjacent color pixels, 0x33 is two adjacent black pixels, etc. If you want + to draw using the color pixels, change '0x30' with '0x40' and '0x03' with '0x04' below. */ + uint8_t left_nibble = (eight_pixels & 0x80) ? 0x30 : 0x00; + eight_pixels <<= 1; + uint8_t right_nibble = (eight_pixels & 0x80) ? 0x03 : 0x00; + eight_pixels <<= 1; + this->write_byte(left_nibble | right_nibble); + } + App.feed_wdt(); + } + this->end_data_(); + + // Unlike the 7P5In display, we send the "power on" command here rather than during initialization + // COMMAND POWER ON + this->command(0x04); + + // COMMAND DISPLAY REFRESH + this->command(0x12); +} + +int WaveshareEPaper7P5InBC::get_width_internal() { return 640; } + +int WaveshareEPaper7P5InBC::get_height_internal() { return 384; } + +void WaveshareEPaper7P5InBC::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-bc"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper7P5InHDB::initialize() { + this->command(0x12); // SWRESET + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x46); // Auto Write RAM + this->data(0xF7); + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x47); // Auto Write RAM + this->data(0xF7); + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x0C); // Soft start setting + this->data(0xAE); + this->data(0xC7); + this->data(0xC3); + this->data(0xC0); + this->data(0x40); + + this->command(0x01); // Set MUX as 527 + this->data(0xAF); + this->data(0x02); + this->data(0x01); + + this->command(0x11); // Data entry mode + this->data(0x01); + + this->command(0x44); + this->data(0x00); // RAM x address start at 0 + this->data(0x00); + this->data(0x6F); // RAM x address end at 36Fh -> 879 + this->data(0x03); + + this->command(0x45); + this->data(0xAF); // RAM y address start at 20Fh; + this->data(0x02); + this->data(0x00); // RAM y address end at 00h; + this->data(0x00); + + this->command(0x3C); // VBD + this->data(0x01); // LUT1, for white + + this->command(0x18); + this->data(0x80); + + this->command(0x22); + this->data(0xB1); // Load Temperature and waveform setting. + + this->command(0x20); + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x4E); + this->data(0x00); + this->data(0x00); + + this->command(0x4F); + this->data(0xAF); + this->data(0x02); +} + +void HOT WaveshareEPaper7P5InHDB::display() { + this->command(0x4F); + this->data(0xAf); + this->data(0x02); + + // BLACK + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // RED + this->command(0x26); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0x00); + this->end_data_(); + + this->command(0x22); + this->data(0xC7); + this->command(0x20); + delay(100); // NOLINT +} + +int WaveshareEPaper7P5InHDB::get_width_internal() { return 880; } + +int WaveshareEPaper7P5InHDB::get_height_internal() { return 528; } + +void WaveshareEPaper7P5InHDB::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-HD-b"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +static const uint8_t LUT_SIZE_TTGO_DKE_PART = 153; + +static const uint8_t PART_UPDATE_LUT_TTGO_DKE[LUT_SIZE_TTGO_DKE_PART] = { + 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0xF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0, 0x0, 0x0, + // 0x22, 0x17, 0x41, 0x0, 0x32, 0x32 +}; + +void WaveshareEPaper2P13InDKE::initialize() {} +void HOT WaveshareEPaper2P13InDKE::display() { + bool partial = this->at_update_ != 0; + this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; + + if (partial) { + ESP_LOGI(TAG, "Performing partial e-paper update."); + } else { + ESP_LOGI(TAG, "Performing full e-paper update."); + } + + // start and set up data format + this->command(0x12); + this->wait_until_idle_(); + + this->command(0x11); + this->data(0x03); + this->command(0x44); + this->data(1); + this->data(this->get_width_internal() / 8); + this->command(0x45); + this->data(0); + this->data(0); + this->data(this->get_height_internal()); + this->data(0); + this->command(0x4e); + this->data(1); + this->command(0x4f); + this->data(0); + this->data(0); + + if (!partial) { + // send data + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // commit + this->command(0x20); + this->wait_until_idle_(); + } else { + // set up partial update + this->command(0x32); + this->start_data_(); + this->write_array(PART_UPDATE_LUT_TTGO_DKE, sizeof(PART_UPDATE_LUT_TTGO_DKE)); + this->end_data_(); + this->command(0x3F); + this->data(0x22); + + this->command(0x03); + this->data(0x17); + this->command(0x04); + this->data(0x41); + this->data(0x00); + this->data(0x32); + this->command(0x2C); + this->data(0x32); + + this->command(0x37); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x40); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + + this->command(0x3C); + this->data(0x80); + this->command(0x22); + this->data(0xC0); + this->command(0x20); + this->wait_until_idle_(); + + // send data + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // commit as partial + this->command(0x22); + this->data(0xCF); + this->command(0x20); + this->wait_until_idle_(); + + // data must be sent again on partial update + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + } + + ESP_LOGI(TAG, "Completed e-paper update."); +} + +int WaveshareEPaper2P13InDKE::get_width_internal() { return 128; } +int WaveshareEPaper2P13InDKE::get_height_internal() { return 250; } +uint32_t WaveshareEPaper2P13InDKE::idle_timeout_() { return 5000; } +void WaveshareEPaper2P13InDKE::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 2.13inDKE"); + LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +void WaveshareEPaper2P13InDKE::set_full_update_every(uint32_t full_update_every) { + this->full_update_every_ = full_update_every; +} + +// ======================================================== +// 13.3in (K version) +// Datasheet/Specification/Reference: +// - https://files.waveshare.com/wiki/13.3inch-e-Paper-HAT-(K)/13.3-inch-e-Paper-(K)-user-manual.pdf +// - https://github.com/waveshareteam/e-Paper/tree/master/Arduino/epd13in3k +// ======================================================== + +// using default wait_until_idle_() function +void WaveshareEPaper13P3InK::initialize() { + this->wait_until_idle_(); + this->command(0x12); // SWRESET + this->wait_until_idle_(); + + this->command(0x0c); // set soft start + this->data(0xae); + this->data(0xc7); + this->data(0xc3); + this->data(0xc0); + this->data(0x80); + + this->command(0x01); // driver output control + this->data((get_height_internal() - 1) % 256); // Y + this->data((get_height_internal() - 1) / 256); // Y + this->data(0x00); + + this->command(0x11); // data entry mode + this->data(0x03); + + // SET WINDOWS + // XRAM_START_AND_END_POSITION + this->command(0x44); + this->data(0 & 0xFF); + this->data((0 >> 8) & 0x03); + this->data((get_width_internal() - 1) & 0xFF); + this->data(((get_width_internal() - 1) >> 8) & 0x03); + // YRAM_START_AND_END_POSITION + this->command(0x45); + this->data(0 & 0xFF); + this->data((0 >> 8) & 0x03); + this->data((get_height_internal() - 1) & 0xFF); + this->data(((get_height_internal() - 1) >> 8) & 0x03); + + this->command(0x3C); // Border setting + this->data(0x01); + + this->command(0x18); // use the internal temperature sensor + this->data(0x80); + + // SET CURSOR + // XRAM_ADDRESS + this->command(0x4E); + this->data(0 & 0xFF); + this->data((0 >> 8) & 0x03); + // YRAM_ADDRESS + this->command(0x4F); + this->data(0 & 0xFF); + this->data((0 >> 8) & 0x03); +} +void HOT WaveshareEPaper13P3InK::display() { + // do single full update + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // COMMAND DISPLAY REFRESH + this->command(0x22); + this->data(0xF7); + this->command(0x20); +} + +int WaveshareEPaper13P3InK::get_width_internal() { return 960; } +int WaveshareEPaper13P3InK::get_height_internal() { return 680; } +uint32_t WaveshareEPaper13P3InK::idle_timeout_() { return 10000; } +void WaveshareEPaper13P3InK::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 13.3inK"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + +} // namespace waveshare_epaper +} // namespace esphome diff --git a/custom_components/waveshare_epaper_2bit/waveshare_epaper_2bit.h b/custom_components/waveshare_epaper_2bit/waveshare_epaper_2bit.h new file mode 100644 index 0000000..248a28d --- /dev/null +++ b/custom_components/waveshare_epaper_2bit/waveshare_epaper_2bit.h @@ -0,0 +1,1139 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/spi/spi.h" +#include "esphome/components/display/display_buffer.h" + +namespace esphome { +namespace waveshare_epaper_2bit { + +class WaveshareEPaperBase : public display::DisplayBuffer, + public spi::SPIDevice { + public: + void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } + float get_setup_priority() const override; + void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } + void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; } + void set_reset_duration(uint32_t reset_duration) { this->reset_duration_ = reset_duration; } + + void command(uint8_t value); + void data(uint8_t value); + void cmd_data(const uint8_t *data, size_t length); + + virtual void display() = 0; + virtual void initialize() = 0; + virtual void deep_sleep() = 0; + + void update() override; + + void setup() override; + + void on_safe_shutdown() override; + + protected: + bool wait_until_idle_(); + + void setup_pins_(); + + void reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(reset_duration_); // NOLINT + this->reset_pin_->digital_write(true); + delay(20); + } + } + + virtual int get_width_controller() { return this->get_width_internal(); }; + + virtual uint32_t get_buffer_length_() = 0; // NOLINT(readability-identifier-naming) + uint32_t reset_duration_{200}; + + void start_command_(); + void end_command_(); + void start_data_(); + void end_data_(); + + GPIOPin *reset_pin_{nullptr}; + GPIOPin *dc_pin_; + GPIOPin *busy_pin_{nullptr}; + virtual uint32_t idle_timeout_() { return 1000u; } // NOLINT(readability-identifier-naming) +}; + +class WaveshareEPaper : public WaveshareEPaperBase { + public: + void fill(Color color) override; + + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_BINARY; } + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + uint32_t get_buffer_length_() override; +}; + +class WaveshareEPaperBWR : public WaveshareEPaperBase { + public: + void fill(Color color) override; + + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + uint32_t get_buffer_length_() override; +}; + +class WaveshareEPaper7C : public WaveshareEPaperBase { + public: + uint8_t color_to_hex(Color color); + void fill(Color color) override; + + display::DisplayType get_display_type() override { return display::DisplayType::DISPLAY_TYPE_COLOR; } + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + uint32_t get_buffer_length_() override; + void setup() override; + + void init_internal_7c_(uint32_t buffer_length); + void send_buffers_(); + void reset_(); + + static const int NUM_BUFFERS = 10; + uint8_t *buffers_[NUM_BUFFERS]; +}; + +enum WaveshareEPaperTypeAModel { + WAVESHARE_EPAPER_1_54_IN = 0, + WAVESHARE_EPAPER_1_54_IN_V2, + WAVESHARE_EPAPER_2_13_IN, + WAVESHARE_EPAPER_2_13_IN_V2, + WAVESHARE_EPAPER_2_9_IN, + WAVESHARE_EPAPER_2_9_IN_V2, + TTGO_EPAPER_2_13_IN, + TTGO_EPAPER_2_13_IN_B73, + TTGO_EPAPER_2_13_IN_B1, + TTGO_EPAPER_2_13_IN_B74, +}; + +class WaveshareEPaperTypeA : public WaveshareEPaper { + public: + WaveshareEPaperTypeA(WaveshareEPaperTypeAModel model); + + void initialize() override; + + void dump_config() override; + + void display() override; + + void deep_sleep() override { + switch (this->model_) { + // Models with specific deep sleep command and data + case WAVESHARE_EPAPER_1_54_IN: + case WAVESHARE_EPAPER_1_54_IN_V2: + case WAVESHARE_EPAPER_2_9_IN_V2: + case WAVESHARE_EPAPER_2_13_IN_V2: + // COMMAND DEEP SLEEP MODE + this->command(0x10); + this->data(0x01); + break; + // Other models default to simple deep sleep command + default: + // COMMAND DEEP SLEEP + this->command(0x10); + break; + } + if (this->model_ != WAVESHARE_EPAPER_2_13_IN_V2) { + // From panel specification: + // "After this command initiated, the chip will enter Deep Sleep Mode, BUSY pad will keep output high." + this->wait_until_idle_(); + } + } + + void set_full_update_every(uint32_t full_update_every); + + protected: + void write_lut_(const uint8_t *lut, uint8_t size); + + void init_display_(); + + int get_width_internal() override; + + int get_height_internal() override; + + int get_width_controller() override; + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + WaveshareEPaperTypeAModel model_; + uint32_t idle_timeout_() override; + + bool deep_sleep_between_updates_{false}; +}; + +enum WaveshareEPaperTypeBModel { + WAVESHARE_EPAPER_2_7_IN = 0, + WAVESHARE_EPAPER_2_7_IN_B, + WAVESHARE_EPAPER_2_7_IN_B_V2, + WAVESHARE_EPAPER_4_2_IN, + WAVESHARE_EPAPER_4_2_IN_B_V2, + WAVESHARE_EPAPER_7_3_IN_F, + WAVESHARE_EPAPER_7_5_IN, + WAVESHARE_EPAPER_7_5_INV2, + WAVESHARE_EPAPER_7_5_IN_B_V2, + WAVESHARE_EPAPER_13_3_IN_K, +}; + +class WaveshareEPaper1P54InBV2 : public WaveshareEPaperBWR { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + this->command(0x10); + this->data(0x01); + } + + protected: + int get_width_internal() override; + int get_height_internal() override; +}; + +class WaveshareEPaper2P7In : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + int get_height_internal() override; +}; + +class WaveshareEPaper2P7InB : public WaveshareEPaperBWR { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND VCOM_AND_DATA_INTERVAL_SETTING + this->command(0x50); + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); // deep sleep + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + int get_height_internal() override; +}; + +class WaveshareEPaper2P7InBV2 : public WaveshareEPaperBWR { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x10); + this->data(0x01); + } + + protected: + int get_width_internal() override; + int get_height_internal() override; +}; + +class GDEW029T5 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override; + void set_full_update_every(uint32_t full_update_every); + + protected: + void init_display_(); + void init_full_(); + void init_partial_(); + void write_lut_(const uint8_t *lut, uint8_t size); + void power_off_(); + void power_on_(); + int get_width_internal() override; + + int get_height_internal() override; + + private: + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + bool deep_sleep_between_updates_{false}; + bool power_is_on_{false}; + bool is_deep_sleep_{false}; + uint8_t *old_buffer_{nullptr}; +}; + +class GDEY029T94 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + this->command(0x10); // Enter deep sleep + this->data(0x01); + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper2P7InV2 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { ; } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class GDEW0154M09 : public WaveshareEPaper { + public: + void initialize() override; + void display() override; + void dump_config() override; + void deep_sleep() override; + + protected: + int get_width_internal() override; + int get_height_internal() override; + + private: + static const uint8_t CMD_DTM1_DATA_START_TRANS = 0x10; + static const uint8_t CMD_DTM2_DATA_START_TRANS2 = 0x13; + static const uint8_t CMD_DISPLAY_REFRESH = 0x12; + static const uint8_t CMD_AUTO_SEQ = 0x17; + static const uint8_t DATA_AUTO_PON_DSR_POF_DSLP = 0xA7; + static const uint8_t CMD_PSR_PANEL_SETTING = 0x00; + static const uint8_t CMD_UNDOCUMENTED_0x4D = 0x4D; // NOLINT + static const uint8_t CMD_UNDOCUMENTED_0xAA = 0xaa; // NOLINT + static const uint8_t CMD_UNDOCUMENTED_0xE9 = 0xe9; // NOLINT + static const uint8_t CMD_UNDOCUMENTED_0xB6 = 0xb6; // NOLINT + static const uint8_t CMD_UNDOCUMENTED_0xF3 = 0xf3; // NOLINT + static const uint8_t CMD_TRES_RESOLUTION_SETTING = 0x61; + static const uint8_t CMD_TCON_TCONSETTING = 0x60; + static const uint8_t CMD_CDI_VCOM_DATA_INTERVAL = 0x50; + static const uint8_t CMD_POF_POWER_OFF = 0x02; + static const uint8_t CMD_DSLP_DEEP_SLEEP = 0x07; + static const uint8_t DATA_DSLP_DEEP_SLEEP = 0xA5; + static const uint8_t CMD_PWS_POWER_SAVING = 0xe3; + static const uint8_t CMD_PON_POWER_ON = 0x04; + static const uint8_t CMD_PTL_PARTIAL_WINDOW = 0x90; + + uint8_t *lastbuff_ = nullptr; + void reset_(); + void clear_(); + void write_init_list_(const uint8_t *list); + void init_internal_(); +}; + +class WaveshareEPaper2P9InB : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper2P9InBV3 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper2P9InV2R2 : public WaveshareEPaper { + public: + WaveshareEPaper2P9InV2R2(); + + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override; + + void set_full_update_every(uint32_t full_update_every); + + protected: + void write_lut_(const uint8_t *lut, uint8_t size); + + int get_width_internal() override; + + int get_height_internal() override; + + int get_width_controller() override; + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + + private: + void reset_(); +}; + +class WaveshareEPaper2P9InV2R22Bpp : public WaveshareEPaper { + public: + WaveshareEPaper2P9InV2R22Bpp(); + + void initialize() override; + + void display() override; + + void fill(Color color) override; + + void dump_config() override; + + void deep_sleep() override; + + void set_full_update_every(uint32_t full_update_every); + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + + void write_lut_(const uint8_t *lut, uint8_t size); + void write_gray4_lut_(); + + int get_width_internal() override; + + int get_height_internal() override; + + int get_width_controller() override; + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + uint8_t *buffer2_{nullptr}; + + private: + void reset_(); +}; + +class WaveshareEPaper2P9InDKE : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x10); + this->data(0x01); + } + + void set_full_update_every(uint32_t full_update_every); + + protected: + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper2P9InD : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class GDEY042T81 : public WaveshareEPaper { + public: + GDEY042T81(); + + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER OFF + this->command(0x22); + this->data(0x83); + this->command(0x20); + // COMMAND DEEP SLEEP + this->command(0x10); + this->data(0x01); + } + + void set_full_update_every(uint32_t full_update_every); + + protected: + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + + int get_width_internal() override; + int get_height_internal() override; + uint32_t idle_timeout_() override; + + private: + void reset_(); + void update_full_(); + void update_part_(); + void init_display_(); +}; + +class WaveshareEPaper4P2In : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x17); // border floating + + // COMMAND VCM DC SETTING + this->command(0x82); + // COMMAND PANEL SETTING + this->command(0x00); + + delay(100); // NOLINT + + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + delay(100); // NOLINT + + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper4P2InBV2 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0xF7); // border floating + + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check code + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper4P2InBV2BWR : public WaveshareEPaperBWR { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0xF7); // border floating + + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check code + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper5P8In : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper5P8InV2 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x17); // border floating + + // COMMAND VCM DC SETTING + this->command(0x82); + // COMMAND PANEL SETTING + this->command(0x00); + + delay(100); // NOLINT + + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); + delay(100); // NOLINT + + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class GDEY0583T81 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override; + + void set_full_update_every(uint32_t full_update_every); + + protected: + int get_width_internal() override; + int get_height_internal() override; + uint32_t idle_timeout_() override; + + private: + void power_on_(); + void power_off_(); + void reset_(); + void update_full_(); + void update_part_(); + void init_full_(); + void init_partial_(); + void init_display_(); + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + bool power_is_on_{false}; + bool is_deep_sleep_{false}; + uint8_t *old_buffer_{nullptr}; +}; + +class WaveshareEPaper5P65InF : public WaveshareEPaper7C { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + uint32_t idle_timeout_() override; + + void deep_sleep() override { ; } + + enum WaitForState { BUSY = true, IDLE = false }; + bool wait_until_(WaitForState state); + + bool deep_sleep_between_updates_{true}; +}; + +class WaveshareEPaper7P3InF : public WaveshareEPaper7C { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + uint32_t idle_timeout_() override; + + void deep_sleep() override { ; } + + bool wait_until_idle_(); + + bool deep_sleep_between_updates_{true}; +}; + +class WaveshareEPaper7P5In : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper7P5InBV2 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); // deep sleep + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper7P5InBV3 : public WaveshareEPaper { + public: + bool wait_until_idle_(); + + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + this->command(0x02); // Power off + this->wait_until_idle_(); + this->command(0x07); // Deep sleep + this->data(0xA5); + } + + void clear_screen(); + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + void reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + this->reset_pin_->digital_write(false); + delay(5); + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + } + }; + + void init_display_(); +}; + +class WaveshareEPaper7P5InBV3BWR : public WaveshareEPaperBWR { + public: + bool wait_until_idle_(); + + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + this->command(0x02); // Power off + this->wait_until_idle_(); + this->command(0x07); // Deep sleep + this->data(0xA5); + } + + void clear_screen(); + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + void reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + this->reset_pin_->digital_write(false); + delay(5); + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + } + }; + + void init_display_(); +}; + +class WaveshareEPaper7P5InBC : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper7P5InV2 : public WaveshareEPaper { + public: + bool wait_until_idle_(); + + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + uint32_t idle_timeout_() override; +}; + +class WaveshareEPaper7P5InV2alt : public WaveshareEPaper7P5InV2 { + public: + bool wait_until_idle_(); + void initialize() override; + void dump_config() override; + + protected: + void reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(true); + delay(200); // NOLINT + this->reset_pin_->digital_write(false); + delay(2); + this->reset_pin_->digital_write(true); + delay(20); + } + }; +}; + +class WaveshareEPaper7P5InV2P : public WaveshareEPaper { + public: + bool wait_until_idle_(); + + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); + this->data(0xA5); // check byte + } + + void set_full_update_every(uint32_t full_update_every); + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + uint32_t idle_timeout_() override; + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + + private: + void reset_(); + + void turn_on_display_(); +}; + +class WaveshareEPaper7P5InHDB : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // deep sleep + this->command(0x10); + this->data(0x01); + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + +class WaveshareEPaper2P13InDKE : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER DOWN + this->command(0x10); + this->data(0x01); + // cannot wait until idle here, the device no longer responds + } + + void set_full_update_every(uint32_t full_update_every); + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + uint32_t idle_timeout_() override; + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; +}; + +class WaveshareEPaper2P13InV3 : public WaveshareEPaper { + public: + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER DOWN + this->command(0x10); + this->data(0x01); + // cannot wait until idle here, the device no longer responds + } + + void set_full_update_every(uint32_t full_update_every); + + void setup() override; + void initialize() override; + + protected: + int get_width_controller() override; + int get_width_internal() override; + int get_height_internal() override; + uint32_t idle_timeout_() override; + + void write_buffer_(uint8_t cmd, int top, int bottom); + void set_window_(int t, int b); + void send_reset_(); + void partial_update_(); + void full_update_(); + + uint32_t full_update_every_{30}; + uint32_t at_update_{0}; + bool is_busy_{false}; + void write_lut_(const uint8_t *lut); +}; + +class WaveshareEPaper13P3InK : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND DEEP SLEEP + this->command(0x10); + this->data(0x01); + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; + + uint32_t idle_timeout_() override; +}; + +} // namespace waveshare_epaper +} // namespace esphome diff --git a/docs/2.9inch_Touch_e-Paper_HAT.pdf b/docs/2.9inch_Touch_e-Paper_HAT.pdf new file mode 100644 index 0000000..4583432 Binary files /dev/null and b/docs/2.9inch_Touch_e-Paper_HAT.pdf differ diff --git a/docs/IMG_2522_preview.jpeg b/docs/IMG_2522_preview.jpeg new file mode 100644 index 0000000..3904aa5 Binary files /dev/null and b/docs/IMG_2522_preview.jpeg differ diff --git a/waveshare-test.yaml b/waveshare-test.yaml new file mode 100644 index 0000000..bb9f0bb --- /dev/null +++ b/waveshare-test.yaml @@ -0,0 +1,200 @@ +# Andrew Villeneuve 2026/05 +# Waveshare 2.9inch Touch ePaper HAT integration demo +# +# Platform: https://www.waveshare.com/2.9inch-Touch-e-Paper-HAT.htm +# +# Pin mappings +# --- +# |Function |ESP Pin |HAT Pin |Wire | +# |--- |--- |--- |--- | +# |5V |VIN |Pin2/5V |Red | +# |GND |GND |Pin6/GND |Blk | +# |Display SCLK |GPIO23 |Pin23/GPIO11/SPI0_SCLK |Blue | +# |Display MOSI |GPIO22 |Pin19/GPIO10/SPI0_MOSI |Green | +# |Display CS |GPIO19 |Pin24/GPIO8/SPI0_CE0 |Mgnta | +# |Display DC |GPIO18 |Pin22/GPIO25 |White | +# |Display Busy |GPIO4 |Pin24/GPIO24 |Brown | +# |Display RST |GPIO5 |Pin11/GPIO17/SPI1_CE1 |Orange | +# +# Exposed Entities +# --- +# + +### Boilerplate ### + +esphome: + name: waveshare-epaper-test + +esp32: + board: nodemcu-32s + framework: + type: esp-idf + +# Enable logging +logger: + level: DEBUG + +# Enable Home Assistant API +api: + encryption: + key: !secret enckey + +ota: + - platform: esphome + password: !secret apipw + +wifi: + ssid: !secret wifi_ssid + password: !secret wappw + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: ${fallbackssid} + password: !secret hotspotpw + +captive_portal: + +### Setup Interfaces ### + +# For the display +spi: + clk_pin: GPIO23 + mosi_pin: GPIO22 + +# For the touchscreen +# ... + +### Okay, now the Good Stuff ### + +# Load some fonts for the display renderer (we don't neeed these with LVGL) +font: + - file: "FreeSans.ttf" + id: font1 + size: 26 + bpp: 2 + - file: "FreeSans.ttf" + id: headerfont + size: 80 + - file: "FreeSans.ttf" + id: console + size: 30 + +# Pull in some icons from the material design library +image: + - file: "mdi:home-lock-open" + id: home_lock_open + type: binary + transparency: chroma_key + invert_alpha: true + resize: 40x40 + +# Force a full display refresh when we press the "boot" button on the devboard +binary_sensor: + - platform: gpio + pin: + number: GPIO0 + mode: + input: true + pullup: true + inverted: true + id: boot_button + internal: true + on_press: + then: + - component.update: lvgl0 + - delay: 1s + - component.update: display0 + +display: + - platform: waveshare_epaper_2bit + id: display0 + cs_pin: GPIO19 + dc_pin: GPIO18 + busy_pin: GPIO4 + reset_pin: GPIO5 + model: 2.90inv2-r2-2bpp + full_update_every: 30 + rotation: 270° + auto_clear_enabled: false + update_interval: never + # show_test_card: true + # lambda: |- + # it.print(0, 0, id(headerfont), "Bleat!"); + +lvgl: + - id: lvgl0 + displays: + - display0 + # on_draw_end: + # - component.update: display0 + bg_color: 0xFFFFFF + # Black: 0x000000 + # Dark Grey: 0x555555 + # Light Grey: 0xAAAAAA + # White: 0xFFFFFF + theme: + label: + text_color: 0x000000 + # button: + # bg_color: 0x000000 + # text_color: 0xFFFFFF + obj: + bg_color: 0xFFFFFF + widgets: + - label: + text: 'Test 2bpp 1326' + align: TOP_LEFT + text_font: font1 + # - label: + # text: "0xFFFFFF" + # text_color: 0xFFFFFF + # x: 0 + # y: 15 + # - label: + # text: "0xAAAAAA" + # text_color: 0xAAAAAA + # x: 0 + # y: 30 + # - label: + # text: "0x555555" + # text_color: 0x555555 + # x: 0 + # y: 45 + # - label: + # text: "0x000000" + # text_color: 0x000000 + # x: 0 + # y: 60 + - obj: + align: BOTTOM_LEFT + width: 33% + height: 30% + bg_color: 0x000000 #Black + - obj: + align: BOTTOM_MID + width: 33% + height: 30% + bg_color: 0x555555 #Dark Grey + - obj: + align: BOTTOM_RIGHT + width: 33% + height: 30% + bg_color: 0xAAAAAA #Light Grey + - obj: + align: TOP_RIGHT + width: 33% + height: 30% + bg_color: 0xFFFFFF #White + - obj: + align: LEFT_MID + width: 66% + height: 30% + bg_color: 0x000000 + bg_grad_color: 0xFFFFFF + bg_grad_dir: HOR + - button: + id: button0 + align: TOP_RIGHT + widgets: + - image: + src: home_lock_open +