diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 4d3965449f..b123ce4fcc 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -24,6 +24,9 @@ WaveshareEPaper = waveshare_epaper_ns.class_("WaveshareEPaper", WaveshareEPaperB WaveshareEPaperBWR = waveshare_epaper_ns.class_( "WaveshareEPaperBWR", WaveshareEPaperBase ) +WaveshareEPaperPolled = waveshare_epaper_ns.class_( + "WaveshareEPaperPolled", WaveshareEPaper +) WaveshareEPaperTypeA = waveshare_epaper_ns.class_( "WaveshareEPaperTypeA", WaveshareEPaper ) @@ -98,6 +101,9 @@ WaveshareEPaper13P3InK = waveshare_epaper_ns.class_( "WaveshareEPaper13P3InK", WaveshareEPaper ) GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper) +WaveshareEPaper7In5BV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper7In5BV2", WaveshareEPaperPolled +) WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel") WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel") @@ -128,6 +134,7 @@ MODELS = { "5.83inv2": ("b", WaveshareEPaper5P8InV2), "7.50in": ("b", WaveshareEPaper7P5In), "7.50in-bv2": ("b", WaveshareEPaper7P5InBV2), + "7.50in-bv2-rb": ("b", WaveshareEPaper7In5BV2), "7.50in-bv3": ("b", WaveshareEPaper7P5InBV3), "7.50in-bc": ("b", WaveshareEPaper7P5InBC), "7.50inv2": ("b", WaveshareEPaper7P5InV2), diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 24df428e6f..a4e1ba6465 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -110,7 +110,7 @@ static const uint8_t PARTIAL_UPD_2IN9_LUT[PARTIAL_UPD_2IN9_LUT_SIZE] = }; // clang-format on -void WaveshareEPaperBase::setup_pins_() { +void WaveshareEPaper::setup_pins_() { this->init_internal_(this->get_buffer_length_()); this->dc_pin_->setup(); // OUTPUT this->dc_pin_->digital_write(false); @@ -125,13 +125,13 @@ void WaveshareEPaperBase::setup_pins_() { this->reset_(); } -float WaveshareEPaperBase::get_setup_priority() const { return setup_priority::PROCESSOR; } -void WaveshareEPaperBase::command(uint8_t value) { +float WaveshareEPaper::get_setup_priority() const { return setup_priority::PROCESSOR; } +void WaveshareEPaper::command(uint8_t value) { this->start_command_(); this->write_byte(value); this->end_command_(); } -void WaveshareEPaperBase::data(uint8_t value) { +void WaveshareEPaper::data(uint8_t value) { this->start_data_(); this->write_byte(value); this->end_data_(); @@ -139,7 +139,7 @@ void WaveshareEPaperBase::data(uint8_t value) { // 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) { +void WaveshareEPaper::cmd_data(const uint8_t *c_data, size_t length) { this->dc_pin_->digital_write(false); this->enable(); this->write_byte(c_data[0]); @@ -148,7 +148,7 @@ void WaveshareEPaperBase::cmd_data(const uint8_t *c_data, size_t length) { this->disable(); } -bool WaveshareEPaperBase::wait_until_idle_() { +bool WaveshareEPaper::wait_until_idle_() { if (this->busy_pin_ == nullptr || !this->busy_pin_->digital_read()) { return true; } @@ -163,15 +163,23 @@ bool WaveshareEPaperBase::wait_until_idle_() { } return true; } -void WaveshareEPaperBase::update() { +void WaveshareEPaper::update() { this->do_update_(); this->display(); } void WaveshareEPaper::fill(Color color) { - // 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; + uint32_t offset = 0; + for (const auto& buf_color: this->get_supported_colors()) { + // A bit set to 1 means color is off + uint8_t value = 0x00; + if (color != buf_color) { + value = 0xFF; + } + for (uint32_t i = 0; i < (this->get_width_internal() * this->get_height_internal()) / 8u; i++) { + this->buffer_[offset+i] = value; + } + offset+= (this->get_width_internal() * this->get_height_internal()) / 8u; + } } 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) @@ -179,58 +187,40 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color 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 offset = 0; + for (const auto& buf_color: this->get_supported_colors()) { + // A bit set to 1 means color is off + this->buffer_[offset+pos] &= ~(0x80 >> subpos); + if (color != buf_color) { + this->buffer_[offset+pos] |= 0x80 >> subpos; + } + offset+= (this->get_width_internal() * this->get_height_internal()) / 8u; } } 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 - -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); - } + return (this->get_width_controller() * this->get_height_internal() * this->get_supported_colors().size()) / 8u; } -void WaveshareEPaperBase::start_command_() { +void WaveshareEPaper::start_command_() { this->dc_pin_->digital_write(false); this->enable(); } -void WaveshareEPaperBase::end_command_() { this->disable(); } -void WaveshareEPaperBase::start_data_() { +void WaveshareEPaper::end_command_() { this->disable(); } +void WaveshareEPaper::start_data_() { this->dc_pin_->digital_write(true); this->enable(); } -void WaveshareEPaperBase::end_data_() { this->disable(); } -void WaveshareEPaperBase::on_safe_shutdown() { this->deep_sleep(); } +void WaveshareEPaper::end_data_() { this->disable(); } +void WaveshareEPaper::on_safe_shutdown() { this->deep_sleep(); } + +display::DisplayType WaveshareEPaper::get_display_type() { + if (this->get_supported_colors().size() == 1) { + return display::DisplayType::DISPLAY_TYPE_BINARY; + } else { + return display::DisplayType::DISPLAY_TYPE_COLOR; + } +} // ======================================================== // Type A @@ -602,7 +592,7 @@ uint32_t WaveshareEPaperTypeA::idle_timeout_() { case TTGO_EPAPER_2_13_IN_B1: return 2500; default: - return WaveshareEPaperBase::idle_timeout_(); + return WaveshareEPaper::idle_timeout_(); } } @@ -3040,11 +3030,166 @@ uint32_t WaveshareEPaper13P3InK::idle_timeout_() { return 10000; } void WaveshareEPaper13P3InK::dump_config() { LOG_DISPLAY("", "Waveshare E-Paper", this); ESP_LOGCONFIG(TAG, " Model: 13.3inK"); +} + +void WaveshareEPaperPolled::update() { + this->do_update_(); + if (this->state_ == State::sleeping) { + this->set_state_(State::update_requested); + } +} + +void WaveshareEPaperPolled::loop() { + switch (this->state_) { + case State::sleeping: + break; + case State::update_requested: + this->reset_pin_->digital_write(false); + this->set_state_(State::resetting); + break; + case State::resetting: + if (millis() - this->last_state_change_ >= this->reset_duration_) { + this->reset_pin_->digital_write(true); + this->set_state_(State::initializing); + } + break; + case State::initializing: + if (millis() - this->last_state_change_ >= 200) { + this->power_on(); + this->set_state_(State::powering_on); + } + break; + case State::powering_on: + if (millis() - this->last_state_change_ >= 100 && (!this->busy_pin_ || !this->busy_pin_->digital_read())) { + this->configure(); + this->set_state_(State::configuring); + } + break; + case State::configuring: + this->display(); + this->set_state_(State::displaying); + break; + case State::displaying: + if (millis() - this->last_state_change_ >= 200 && (!this->busy_pin_ || !this->busy_pin_->digital_read())) { + this->power_off(); + this->set_state_(State::powering_off); + } + break; + case State::powering_off: + if (!this->busy_pin_ || !this->busy_pin_->digital_read()) { + this->deep_sleep(); + this->set_state_(State::sleeping); + } + break; + } +} + +void WaveshareEPaperPolled::set_state_(State state) { + this->state_ = state; + this->last_state_change_ = millis(); + switch (this->state_) { + case State::sleeping: + ESP_LOGD(TAG, "sleeping"); + break; + case State::update_requested: + ESP_LOGD(TAG, "update_requested"); + break; + case State::resetting: + ESP_LOGD(TAG, "resetting"); + break; + case State::initializing: + ESP_LOGD(TAG, "initializing"); + break; + case State::powering_on: + ESP_LOGD(TAG, "powering_on"); + break; + case State::configuring: + ESP_LOGD(TAG, "configuring"); + break; + case State::displaying: + ESP_LOGD(TAG, "displaying"); + break; + case State::powering_off: + ESP_LOGD(TAG, "powering_off"); + break; + } +} + +void WaveshareEPaper7In5BV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in V3 Black/Red"); 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 WaveshareEPaper7In5BV2::power_on() { + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x07); + this->data(0x17); + this->data(0x3F); + this->data(0x3F); + // POWER ON + this->command(0x04); +} + +void WaveshareEPaper7In5BV2::configure() { + // 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); + this->data(0x20); + this->data(0x01); + this->data(0xE0); + // COMMAND DUAL SPI MODE + 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); + // COMMAND RESOLUTION GATE SETTING + this->command(0x65); + this->data(0x00); + this->data(0x00); + this->data(0x00); + this->data(0x00); +} + +void HOT WaveshareEPaper7In5BV2::display() { + uint32_t buf_len = this->get_buffer_length_(); + // COMMAND DATA START TRANSMISSION NEW DATA + this->command(0x10); + for (uint32_t i = 0; i < buf_len/2; i++) { + this->data(this->buffer_[i]); + } + this->command(0x92); + + // COMMAND DATA START TRANSMISSION NEW DATA + this->command(0x13); + for (uint32_t i = buf_len/2; i < buf_len; i++) { + this->data(~this->buffer_[i]); + } + + // COMMAND DISPLAY REFRESH + this->command(0x12); +} + +void WaveshareEPaper7In5BV2::power_off() { + this->command(0x02); // POWER OFF +} + +void WaveshareEPaper7In5BV2::deep_sleep() { + this->command(0x07); // SLEEP + this->data(0xA5); +} + } // namespace waveshare_epaper } // namespace esphome diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 7572982a20..81acf91c8b 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -7,7 +7,7 @@ namespace esphome { namespace waveshare_epaper { -class WaveshareEPaperBase : public display::DisplayBuffer, +class WaveshareEPaper : public display::DisplayBuffer, public spi::SPIDevice { public: @@ -34,6 +34,10 @@ class WaveshareEPaperBase : public display::DisplayBuffer, void on_safe_shutdown() override; + display::DisplayType get_display_type() override; + + void fill(Color color) override; + protected: bool wait_until_idle_(); @@ -50,9 +54,14 @@ class WaveshareEPaperBase : public display::DisplayBuffer, virtual int get_width_controller() { return this->get_width_internal(); }; - virtual uint32_t get_buffer_length_() = 0; // NOLINT(readability-identifier-naming) + virtual uint32_t get_buffer_length_(); uint32_t reset_duration_{200}; + // Return the list of colors supported by the device + virtual std::vector get_supported_colors() { return {display::COLOR_ON}; } + + void draw_absolute_pixel_internal(int x, int y, Color color) override; + void start_command_(); void end_command_(); void start_data_(); @@ -64,26 +73,9 @@ class WaveshareEPaperBase : public display::DisplayBuffer, virtual uint32_t idle_timeout_() { return 1000u; } // NOLINT(readability-identifier-naming) }; -class WaveshareEPaper : public WaveshareEPaperBase { +class WaveshareEPaperBWR : public WaveshareEPaper { 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; + virtual std::vector get_supported_colors() override { return {display::COLOR_ON, Color(255, 0, 0, 0)}; } }; enum WaveshareEPaperTypeAModel { @@ -791,6 +783,83 @@ class WaveshareEPaper13P3InK : public WaveshareEPaper { int get_height_internal() override; uint32_t idle_timeout_() override; + +}; + +// Generic Waveshare e-paper component that +// avoids using any blocking wait to be able to support +// big screens that are slow to update +class WaveshareEPaperPolled : public WaveshareEPaper { + // Will request a display refresh + void update() override; + + // Will move the state machine one state at a time + // to refresh the display when requested by display() + void loop() override; + + // Unused method from parent + void initialize() override {} + +protected: + + // Below are display steps, called one after the other by loop() + // Just implement these to support a new device. + // Never sleep or wait in a step, the state machine will + // handle it. + + // Just after reset, set the power mode and power the driver on + virtual void power_on() = 0; + + // Send all the configuration required to display + virtual void configure() = 0; + + // Send image data and refresh the display + virtual void display() = 0; + + // Power off the driver + virtual void power_off() = 0; + + // Set the screen to deep sleep + void deep_sleep() override = 0; + +private: + enum class State: uint8_t { + sleeping, + update_requested, + resetting, + initializing, + powering_on, + configuring, + displaying, + powering_off, + }; + + // Set the current state of the display + void set_state_(State state); + + // Current state of the display + State state_{State::sleeping}; + // Timestamp of last state changed, used to wait between states + uint32_t last_state_change_{0}; +}; + +// 7.5 inches screen supporting black and red color with +// a v3 label on the back. Called EDP_7in5b_V2 in WaveShare examples. +class WaveshareEPaper7In5BV2 : public WaveshareEPaperPolled { + public: + void dump_config() override; + + void power_on() override; + void configure() override; + void display() override; + void power_off() override; + void deep_sleep() override; + + virtual std::vector get_supported_colors() override { return {display::COLOR_ON, Color(255, 0, 0, 0)}; } + + protected: + int get_width_internal() override { return 800; } + int get_height_internal() override { return 480; } }; } // namespace waveshare_epaper