diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 3a5151682f..81afc159a5 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -120,7 +120,7 @@ MODELS = { "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": ("b", GDEW029T5), + "gdew029t5": ("c", GDEW029T5), "2.70in": ("b", WaveshareEPaper2P7In), "2.70in-b": ("b", WaveshareEPaper2P7InB), "2.70in-bv2": ("b", WaveshareEPaper2P7InBV2), diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index fb9e8ff6e5..2ff271f999 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1596,15 +1596,108 @@ void WaveshareEPaper2P9InV2R2::set_full_update_every(uint32_t full_update_every) // 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? // ======================================================== -void GDEW029T5::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_(); +// 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(0x00); + this->command(0x01); this->data(0x03); this->data(0x00); this->data(0x2b); @@ -1617,40 +1710,122 @@ void GDEW029T5::initialize() { this->data(0x17); this->data(0x17); - // COMMAND POWER ON - this->command(0x04); - this->wait_until_idle_(); - - // Not sure what this does but it's in the Adafruit EPD library - this->command(0xFF); - this->wait_until_idle_(); + this->power_on_(); // COMMAND PANEL SETTING this->command(0x00); // 128x296 resolution: 10 - // LUT from OTP: 0 + // 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(0b10011111); + 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); - // 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 + 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 + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + 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); @@ -1660,10 +1835,28 @@ void HOT GDEW029T5::display() { delay(2); this->wait_until_idle_(); - // COMMAND POWER OFF - // NOTE: power off < deep sleep - this->command(0x02); + 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() { @@ -1672,6 +1865,7 @@ void GDEW029T5::dump_config() { 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); } diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 0fc1051268..e0c8001a50 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -254,16 +254,27 @@ class GDEW029T5 : public WaveshareEPaper { void dump_config() override; - void deep_sleep() override { - // COMMAND DEEP SLEEP - this->command(0x07); - this->data(0xA5); // check byte - } + 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 WaveshareEPaper2P7InV2 : public WaveshareEPaper { diff --git a/tests/components/waveshare_epaper/common.yaml b/tests/components/waveshare_epaper/common.yaml index ff9ddb955f..1d77e0cf38 100644 --- a/tests/components/waveshare_epaper/common.yaml +++ b/tests/components/waveshare_epaper/common.yaml @@ -459,6 +459,7 @@ display: reset_pin: allow_other_uses: true number: ${reset_pin} + full_update_every: 30 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height());