diff --git a/esphome/components/epaper_spi/epaper_waveshare_bwr.cpp b/esphome/components/epaper_spi/epaper_waveshare_bwr.cpp index 004597b72bd..eecb1882613 100644 --- a/esphome/components/epaper_spi/epaper_waveshare_bwr.cpp +++ b/esphome/components/epaper_spi/epaper_waveshare_bwr.cpp @@ -20,7 +20,7 @@ static BwrState color_to_bwr(Color color) { return BwrState::BWR_BLACK; } -// UC8179 3-color display buffer layout: +// 3-color display buffer layout (UC8179 and UC8151D): // - 1 bit per pixel, 8 pixels per byte // - Buffer first half: Black/White plane (1=black, 0=white) // - Buffer second half: Red plane (1=red, 0=white) @@ -79,7 +79,7 @@ bool HOT EPaperWaveshareBWR::transfer_data() { uint8_t bytes_to_send[MAX_TRANSFER_SIZE]; // Phase 1: send Black/White plane (first half) via command 0x10 (DTM1) - // UC8179 DTM1 (0x10): inverted to get 0=black, 1=white + // DTM1 (0x10): inverted to get 0=black, 1=white if (this->current_data_index_ < half_buffer) { if (this->current_data_index_ == 0) { this->command(0x10); // DATA START TRANSMISSION 1 (black channel) @@ -101,7 +101,7 @@ bool HOT EPaperWaveshareBWR::transfer_data() { } // Phase 2: send Red plane (second half) via command 0x13 (DTM2) - // UC8179 DTM2 (0x13): 1=red, 0=white + // DTM2 (0x13): 1=red, 0=white; on the UC8151D this relies on the 0x50 value in uc8151.py if (this->current_data_index_ < buffer_length) { if (this->current_data_index_ == half_buffer) { this->command(0x13); // DATA START TRANSMISSION 2 (red channel) @@ -127,8 +127,7 @@ bool HOT EPaperWaveshareBWR::transfer_data() { } void EPaperWaveshareBWR::power_on() { - this->cmd_data(0x01, {0x07, 0x17, 0x3F, 0x3F}); // POWER SETTING - this->command(0x04); // POWER ON + this->command(0x04); // POWER ON } void EPaperWaveshareBWR::refresh_screen(bool /*partial*/) { diff --git a/esphome/components/epaper_spi/epaper_waveshare_bwr.h b/esphome/components/epaper_spi/epaper_waveshare_bwr.h index d34f05cbb2b..f5cbb5fde47 100644 --- a/esphome/components/epaper_spi/epaper_waveshare_bwr.h +++ b/esphome/components/epaper_spi/epaper_waveshare_bwr.h @@ -5,8 +5,8 @@ namespace esphome::epaper_spi { /** - * Waveshare 3-color e-paper displays (UC8179 controller). - * Supports: 7.5" V2 BWR (EDP_7in5b_V2), 800x480 pixels. + * Waveshare 3-color e-paper displays (UC8179 and UC8151D controllers). + * Supports: 7.5" V2 BWR (EPD_7in5b_V2), 800x480 pixels; 2.13" B V3 (EPD_2in13b_V3), 104x212 pixels. * * Color scheme: Black, White, Red (BWR) * Buffer layout: 1 bit per pixel, separate planes @@ -14,8 +14,8 @@ namespace esphome::epaper_spi { * - Buffer second half: Red plane (1=red, 0=no red) * - Total buffer: width * height / 4 bytes (2 * width * height / 8) * - * The init sequence (INITIALISE state) sends panel configuration only. - * Power-on (0x01 + 0x04) is sent in the POWER_ON state after data transfer; + * The init sequence (INITIALISE state) sends the power and panel configuration. + * Power-on (0x04) is sent in the POWER_ON state after data transfer; * the state machine then busy-waits before triggering REFRESH_SCREEN (0x12). */ class EPaperWaveshareBWR : public EPaperBase { diff --git a/esphome/components/epaper_spi/models/uc8151.py b/esphome/components/epaper_spi/models/uc8151.py new file mode 100644 index 00000000000..49d0d6818b4 --- /dev/null +++ b/esphome/components/epaper_spi/models/uc8151.py @@ -0,0 +1,60 @@ +"""Black/white/red e-paper displays using the UltraChip UC8151D controller. + +Supported models: +- uc8151: the controller on its own; dimensions and pins must be configured +- waveshare-2.13in-bv3: 104x212 pixels (2.13" B V3, EPD_2in13b_V3) + +The busy pin of these displays is low while busy, so configure it with ``inverted: true``. +""" + +from typing import Any + +import esphome.config_validation as cv +from esphome.const import CONF_DIMENSIONS +from esphome.types import ConfigType + +from . import EpaperModel + +MAX_SOURCES = 160 +MAX_GATES = 296 + + +class UC8151(EpaperModel): + """EpaperModel class for black/white/red displays using the UC8151D controller.""" + + def __init__(self, name: str, **defaults: Any) -> None: + super().__init__(name, "EPaperWaveshareBWR", **defaults) + + def validate_config(self, config: ConfigType) -> ConfigType: + width, height = self.get_dimensions(config) + if width > MAX_SOURCES or height > MAX_GATES: + raise cv.Invalid( + f"{self.name} supports at most {MAX_SOURCES}x{MAX_GATES} pixels (width x height)", + path=[CONF_DIMENSIONS], + ) + return config + + def get_init_sequence(self, config: dict) -> tuple: + width, height = self.get_dimensions(config) + return ( + # PANEL SETTING (black/white/red mode, LUT from OTP) + (0x00, 0x0F, 0x89), + # RESOLUTION SETTING: sources, then gates + (0x61, width, height >> 8, height & 0xFF), + # VCOM AND DATA INTERVAL SETTING; 0x57, not Waveshare's 0x77, so that 1 in DTM2 is red + (0x50, 0x57), + ) + + +uc8151 = UC8151( + "uc8151", + data_rate="10MHz", + minimum_update_interval="15s", + width_multiple=8, +) + +uc8151.extend( + "waveshare-2.13in-bv3", + width=104, + height=212, +) diff --git a/esphome/components/epaper_spi/models/waveshare_bwr.py b/esphome/components/epaper_spi/models/waveshare_bwr.py index e124ea7083b..18c673fbeee 100644 --- a/esphome/components/epaper_spi/models/waveshare_bwr.py +++ b/esphome/components/epaper_spi/models/waveshare_bwr.py @@ -1,10 +1,10 @@ """Waveshare Black/White/Red e-paper displays using UC8179 controller. Supported models: -- waveshare-7.5in-bv2-bwr: 800x480 pixels (7.5" BWR display, EDP_7in5b_V2) +- waveshare-7.5in-bv2-bwr: 800x480 pixels (7.5" BWR display, EPD_7in5b_V2) -These displays use the UC8179 controller. Panel configuration is sent during -the INITIALISE state. Power-on is handled in the POWER_ON state, after data +These displays use the UC8179 controller. Power and panel configuration are sent +during the INITIALISE state. Power-on is handled in the POWER_ON state, after data transfer, so the state machine's built-in busy wait covers the power-on delay. """ @@ -20,8 +20,8 @@ class WaveshareBWR(EpaperModel): def get_init_sequence(self, config): """Generate initialization sequence for UC8179 BWR displays. - Panel configuration only — power-on is handled separately in power_on() - after data transfer, with the state machine busy-waiting before refresh. + Power and panel configuration only — power-on is handled separately in + power_on() after data transfer, with the state machine busy-waiting before refresh. """ width, height = self.get_dimensions(config) return ( @@ -43,10 +43,12 @@ class WaveshareBWR(EpaperModel): (0x60, 0x22), # RESOLUTION GATE SETTING (0x65, 0x00, 0x00, 0x00, 0x00), + # POWER SETTING + (0x01, 0x07, 0x17, 0x3F, 0x3F), ) -# Model: Waveshare 7.5" V2 BWR (EDP_7in5b_V2) — 800x480, UC8179 controller +# Model: Waveshare 7.5" V2 BWR (EPD_7in5b_V2) — 800x480, UC8179 controller WaveshareBWR( "waveshare-7.5in-bv2-bwr", width=800, diff --git a/tests/component_tests/epaper_spi/test_init.py b/tests/component_tests/epaper_spi/test_init.py index 644a505bf82..5e9cc6d703a 100644 --- a/tests/component_tests/epaper_spi/test_init.py +++ b/tests/component_tests/epaper_spi/test_init.py @@ -143,7 +143,7 @@ def test_all_predefined_models( # Add dimensions if not provided by model if not model.get_default(CONF_WIDTH): - config[CONF_DIMENSIONS] = {CONF_HEIGHT: 240, CONF_WIDTH: 320} + config[CONF_DIMENSIONS] = {CONF_HEIGHT: 120, CONF_WIDTH: 120} # Add init sequence if model doesn't provide one if model.initsequence is None: @@ -199,7 +199,7 @@ def test_individual_models( config[CONF_DC_PIN] = 21 if not model.get_default(CONF_WIDTH): - config[CONF_DIMENSIONS] = {CONF_HEIGHT: 240, CONF_WIDTH: 320} + config[CONF_DIMENSIONS] = {CONF_HEIGHT: 120, CONF_WIDTH: 120} if model.initsequence is None: config[CONF_INIT_SEQUENCE] = [[0xA0, 0x01]] diff --git a/tests/components/epaper_spi/test.esp32-s3-idf.yaml b/tests/components/epaper_spi/test.esp32-s3-idf.yaml index 144c60dd2ff..d0d5f5a6aab 100644 --- a/tests/components/epaper_spi/test.esp32-s3-idf.yaml +++ b/tests/components/epaper_spi/test.esp32-s3-idf.yaml @@ -285,6 +285,28 @@ display: it.circle(it.get_width() / 2, it.get_height() / 2, 20, Color::BLACK); it.circle(it.get_width() / 2, it.get_height() / 2, 15, Color(255, 0, 0)); + # Waveshare 2.13" V3 B series 3-color e-paper (104x212, BWR, UC8151D) + - platform: epaper_spi + spi_id: spi_bus + model: waveshare-2.13in-bv3 + cs_pin: + allow_other_uses: true + number: GPIO5 + dc_pin: + allow_other_uses: true + number: GPIO17 + reset_pin: + allow_other_uses: true + number: GPIO16 + busy_pin: + allow_other_uses: true + number: GPIO4 + inverted: true + lambda: |- + it.filled_rectangle(0, 0, it.get_width(), it.get_height(), Color::WHITE); + it.circle(it.get_width() / 2, it.get_height() / 2, 20, Color::BLACK); + it.circle(it.get_width() / 2, it.get_height() / 2, 15, Color(255, 0, 0)); + # Waveshare 2.66" B 3-color e-paper (152x296, BWR, SSD1680) - platform: epaper_spi spi_id: spi_bus