From d9a2651a5a949a8436fd24092e0a596585ee50b9 Mon Sep 17 00:00:00 2001 From: David Zovko Date: Sat, 16 Jan 2021 01:19:35 +0100 Subject: [PATCH] Inkplate 6 support for ESPHome (#1283) * Add Inkplate 6 support Inkplate 6 is e-paper display based on ESP32. This commit adds support for integrating Inkplate 6 into the ESPHome. Find more info here: inkplate.io * Greyscale working * Update inkplate.h * Fix formatting * Formatting * Update esphome/components/inkplate6/display.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Update esphome/components/inkplate6/display.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Fix some lint errors Ignore some lint errors Only allow on ESP32 * Update the codeowners file Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/inkplate6/__init__.py | 1 + esphome/components/inkplate6/display.py | 141 +++++ esphome/components/inkplate6/inkplate.cpp | 630 ++++++++++++++++++++++ esphome/components/inkplate6/inkplate.h | 157 ++++++ 5 files changed, 930 insertions(+) create mode 100644 esphome/components/inkplate6/__init__.py create mode 100644 esphome/components/inkplate6/display.py create mode 100644 esphome/components/inkplate6/inkplate.cpp create mode 100644 esphome/components/inkplate6/inkplate.h diff --git a/CODEOWNERS b/CODEOWNERS index 5cf0f591d2..4c24586bb6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -37,6 +37,7 @@ esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/homeassistant/* @OttoWinter esphome/components/i2c/* @esphome/core +esphome/components/inkplate6/* @jesserockz esphome/components/integration/* @OttoWinter esphome/components/interval/* @esphome/core esphome/components/json/* @OttoWinter diff --git a/esphome/components/inkplate6/__init__.py b/esphome/components/inkplate6/__init__.py new file mode 100644 index 0000000000..ba7653988b --- /dev/null +++ b/esphome/components/inkplate6/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ['@jesserockz'] diff --git a/esphome/components/inkplate6/display.py b/esphome/components/inkplate6/display.py new file mode 100644 index 0000000000..4f35901bd4 --- /dev/null +++ b/esphome/components/inkplate6/display.py @@ -0,0 +1,141 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import pins +from esphome.components import display, i2c +from esphome.const import CONF_FULL_UPDATE_EVERY, CONF_ID, CONF_LAMBDA, CONF_PAGES, \ + CONF_WAKEUP_PIN, ESP_PLATFORM_ESP32 + +DEPENDENCIES = ['i2c'] +ESP_PLATFORMS = [ESP_PLATFORM_ESP32] + +CONF_DISPLAY_DATA_0_PIN = 'display_data_0_pin' +CONF_DISPLAY_DATA_1_PIN = 'display_data_1_pin' +CONF_DISPLAY_DATA_2_PIN = 'display_data_2_pin' +CONF_DISPLAY_DATA_3_PIN = 'display_data_3_pin' +CONF_DISPLAY_DATA_4_PIN = 'display_data_4_pin' +CONF_DISPLAY_DATA_5_PIN = 'display_data_5_pin' +CONF_DISPLAY_DATA_6_PIN = 'display_data_6_pin' +CONF_DISPLAY_DATA_7_PIN = 'display_data_7_pin' + +CONF_CL_PIN = 'cl_pin' +CONF_CKV_PIN = 'ckv_pin' +CONF_GREYSCALE = 'greyscale' +CONF_GMOD_PIN = 'gmod_pin' +CONF_GPIO0_ENABLE_PIN = 'gpio0_enable_pin' +CONF_LE_PIN = 'le_pin' +CONF_OE_PIN = 'oe_pin' +CONF_PARTIAL_UPDATING = 'partial_updating' +CONF_POWERUP_PIN = 'powerup_pin' +CONF_SPH_PIN = 'sph_pin' +CONF_SPV_PIN = 'spv_pin' +CONF_VCOM_PIN = 'vcom_pin' + + +inkplate6_ns = cg.esphome_ns.namespace('inkplate6') +Inkplate6 = inkplate6_ns.class_('Inkplate6', cg.PollingComponent, i2c.I2CDevice, + display.DisplayBuffer) + +CONFIG_SCHEMA = cv.All(display.FULL_DISPLAY_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(Inkplate6), + cv.Optional(CONF_GREYSCALE, default=False): cv.boolean, + cv.Optional(CONF_PARTIAL_UPDATING, default=True): cv.boolean, + cv.Optional(CONF_FULL_UPDATE_EVERY, default=10): cv.uint32_t, + # Control pins + cv.Required(CONF_CKV_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_GMOD_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_GPIO0_ENABLE_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_OE_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_POWERUP_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_SPH_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_SPV_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_VCOM_PIN): pins.gpio_output_pin_schema, + cv.Required(CONF_WAKEUP_PIN): pins.gpio_output_pin_schema, + cv.Optional(CONF_CL_PIN, default=0): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_LE_PIN, default=2): pins.internal_gpio_output_pin_schema, + # Data pins + cv.Optional(CONF_DISPLAY_DATA_0_PIN, default=4): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_1_PIN, default=5): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_2_PIN, default=18): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_3_PIN, default=19): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_4_PIN, default=23): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_5_PIN, default=25): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_6_PIN, default=26): pins.internal_gpio_output_pin_schema, + cv.Optional(CONF_DISPLAY_DATA_7_PIN, default=27): pins.internal_gpio_output_pin_schema, +}).extend(cv.polling_component_schema('5s').extend(i2c.i2c_device_schema(0x48))), + cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA)) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + yield cg.register_component(var, config) + yield display.register_display(var, config) + yield i2c.register_i2c_device(var, config) + + if CONF_LAMBDA in config: + lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(display.DisplayBufferRef, 'it')], + return_type=cg.void) + cg.add(var.set_writer(lambda_)) + + cg.add(var.set_greyscale(config[CONF_GREYSCALE])) + cg.add(var.set_partial_updating(config[CONF_PARTIAL_UPDATING])) + cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY])) + + ckv = yield cg.gpio_pin_expression(config[CONF_CKV_PIN]) + cg.add(var.set_ckv_pin(ckv)) + + gmod = yield cg.gpio_pin_expression(config[CONF_GMOD_PIN]) + cg.add(var.set_gmod_pin(gmod)) + + gpio0_enable = yield cg.gpio_pin_expression(config[CONF_GPIO0_ENABLE_PIN]) + cg.add(var.set_gpio0_enable_pin(gpio0_enable)) + + oe = yield cg.gpio_pin_expression(config[CONF_OE_PIN]) + cg.add(var.set_oe_pin(oe)) + + powerup = yield cg.gpio_pin_expression(config[CONF_POWERUP_PIN]) + cg.add(var.set_powerup_pin(powerup)) + + sph = yield cg.gpio_pin_expression(config[CONF_SPH_PIN]) + cg.add(var.set_sph_pin(sph)) + + spv = yield cg.gpio_pin_expression(config[CONF_SPV_PIN]) + cg.add(var.set_spv_pin(spv)) + + vcom = yield cg.gpio_pin_expression(config[CONF_VCOM_PIN]) + cg.add(var.set_vcom_pin(vcom)) + + wakeup = yield cg.gpio_pin_expression(config[CONF_WAKEUP_PIN]) + cg.add(var.set_wakeup_pin(wakeup)) + + cl = yield cg.gpio_pin_expression(config[CONF_CL_PIN]) + cg.add(var.set_cl_pin(cl)) + + le = yield cg.gpio_pin_expression(config[CONF_LE_PIN]) + cg.add(var.set_le_pin(le)) + + display_data_0 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_0_PIN]) + cg.add(var.set_display_data_0_pin(display_data_0)) + + display_data_1 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_1_PIN]) + cg.add(var.set_display_data_1_pin(display_data_1)) + + display_data_2 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_2_PIN]) + cg.add(var.set_display_data_2_pin(display_data_2)) + + display_data_3 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_3_PIN]) + cg.add(var.set_display_data_3_pin(display_data_3)) + + display_data_4 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_4_PIN]) + cg.add(var.set_display_data_4_pin(display_data_4)) + + display_data_5 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_5_PIN]) + cg.add(var.set_display_data_5_pin(display_data_5)) + + display_data_6 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_6_PIN]) + cg.add(var.set_display_data_6_pin(display_data_6)) + + display_data_7 = yield cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_7_PIN]) + cg.add(var.set_display_data_7_pin(display_data_7)) + + cg.add_build_flag('-DBOARD_HAS_PSRAM') diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp new file mode 100644 index 0000000000..3b17ba8f52 --- /dev/null +++ b/esphome/components/inkplate6/inkplate.cpp @@ -0,0 +1,630 @@ +#include "inkplate.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace inkplate6 { + +static const char *TAG = "inkplate"; + +void Inkplate6::setup() { + this->initialize_(); + + this->vcom_pin_->setup(); + this->powerup_pin_->setup(); + this->wakeup_pin_->setup(); + this->gpio0_enable_pin_->setup(); + this->gpio0_enable_pin_->digital_write(true); + + this->cl_pin_->setup(); + this->le_pin_->setup(); + this->ckv_pin_->setup(); + this->gmod_pin_->setup(); + this->oe_pin_->setup(); + this->sph_pin_->setup(); + this->spv_pin_->setup(); + + this->display_data_0_pin_->setup(); + this->display_data_1_pin_->setup(); + this->display_data_2_pin_->setup(); + this->display_data_3_pin_->setup(); + this->display_data_4_pin_->setup(); + this->display_data_5_pin_->setup(); + this->display_data_6_pin_->setup(); + this->display_data_7_pin_->setup(); + + this->clean(); + this->display(); +} +void Inkplate6::initialize_() { + uint32_t buffer_size = this->get_buffer_length_(); + + if (this->partial_buffer_ != nullptr) { + free(this->partial_buffer_); // NOLINT + } + if (this->partial_buffer_2_ != nullptr) { + free(this->partial_buffer_2_); // NOLINT + } + if (this->buffer_ != nullptr) { + free(this->buffer_); // NOLINT + } + + this->buffer_ = (uint8_t *) ps_malloc(buffer_size); + if (this->buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate buffer for display!"); + this->mark_failed(); + return; + } + if (!this->greyscale_) { + this->partial_buffer_ = (uint8_t *) ps_malloc(buffer_size); + if (this->partial_buffer_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate partial buffer for display!"); + this->mark_failed(); + return; + } + this->partial_buffer_2_ = (uint8_t *) ps_malloc(buffer_size * 2); + if (this->partial_buffer_2_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate partial buffer 2 for display!"); + this->mark_failed(); + return; + } + memset(this->partial_buffer_, 0, buffer_size); + memset(this->partial_buffer_2_, 0, buffer_size * 2); + } + + memset(this->buffer_, 0, buffer_size); +} +float Inkplate6::get_setup_priority() const { return setup_priority::PROCESSOR; } +size_t Inkplate6::get_buffer_length_() { + if (this->greyscale_) { + return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 2u; + } else { + return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u; + } +} +void Inkplate6::update() { + this->do_update_(); + + if (this->full_update_every_ > 0 && this->partial_updates_ >= this->full_update_every_) { + this->block_partial_ = true; + } + + this->display(); +} +void HOT Inkplate6::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; + + if (this->greyscale_) { + int x1 = x / 2; + int x_sub = x % 2; + uint32_t pos = (x1 + y * (this->get_width_internal() / 2)); + uint8_t current = this->buffer_[pos]; + + // float px = (0.2126 * (color.red / 255.0)) + (0.7152 * (color.green / 255.0)) + (0.0722 * (color.blue / 255.0)); + // px = pow(px, 1.5); + // uint8_t gs = (uint8_t)(px*7); + + uint8_t gs = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5; + this->buffer_[pos] = (pixelMaskGLUT[x_sub] & current) | (x_sub ? gs : gs << 4); + + } else { + int x1 = x / 8; + int x_sub = x % 8; + uint32_t pos = (x1 + y * (this->get_width_internal() / 8)); + uint8_t current = this->partial_buffer_[pos]; + this->partial_buffer_[pos] = (~pixelMaskLUT[x_sub] & current) | (color.is_on() ? 0 : pixelMaskLUT[x_sub]); + } +} +void Inkplate6::dump_config() { + LOG_DISPLAY("", "Inkplate", this); + ESP_LOGCONFIG(TAG, " Greyscale: %s", YESNO(this->greyscale_)); + ESP_LOGCONFIG(TAG, " Partial Updating: %s", YESNO(this->partial_updating_)); + ESP_LOGCONFIG(TAG, " Full Update Every: %d", this->full_update_every_); + // Log pins + LOG_PIN(" CKV Pin: ", this->ckv_pin_); + LOG_PIN(" CL Pin: ", this->cl_pin_); + LOG_PIN(" GPIO0 Enable Pin: ", this->gpio0_enable_pin_); + LOG_PIN(" GMOD Pin: ", this->gmod_pin_); + LOG_PIN(" LE Pin: ", this->le_pin_); + LOG_PIN(" OE Pin: ", this->oe_pin_); + LOG_PIN(" POWERUP Pin: ", this->powerup_pin_); + LOG_PIN(" SPH Pin: ", this->sph_pin_); + LOG_PIN(" SPV Pin: ", this->spv_pin_); + LOG_PIN(" VCOM Pin: ", this->vcom_pin_); + LOG_PIN(" WAKEUP Pin: ", this->wakeup_pin_); + + LOG_PIN(" Data 0 Pin: ", this->display_data_0_pin_); + LOG_PIN(" Data 1 Pin: ", this->display_data_1_pin_); + LOG_PIN(" Data 2 Pin: ", this->display_data_2_pin_); + LOG_PIN(" Data 3 Pin: ", this->display_data_3_pin_); + LOG_PIN(" Data 4 Pin: ", this->display_data_4_pin_); + LOG_PIN(" Data 5 Pin: ", this->display_data_5_pin_); + LOG_PIN(" Data 6 Pin: ", this->display_data_6_pin_); + LOG_PIN(" Data 7 Pin: ", this->display_data_7_pin_); + + LOG_UPDATE_INTERVAL(this); +} +void Inkplate6::eink_off_() { + ESP_LOGV(TAG, "Eink off called"); + unsigned long start_time = millis(); + if (panel_on_ == 0) + return; + panel_on_ = 0; + this->gmod_pin_->digital_write(false); + this->oe_pin_->digital_write(false); + + GPIO.out &= ~(get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin())); + + this->sph_pin_->digital_write(false); + this->spv_pin_->digital_write(false); + + this->powerup_pin_->digital_write(false); + this->wakeup_pin_->digital_write(false); + this->vcom_pin_->digital_write(false); + + pins_z_state_(); +} +void Inkplate6::eink_on_() { + ESP_LOGV(TAG, "Eink on called"); + unsigned long start_time = millis(); + if (panel_on_ == 1) + return; + panel_on_ = 1; + pins_as_outputs_(); + this->wakeup_pin_->digital_write(true); + this->powerup_pin_->digital_write(true); + this->vcom_pin_->digital_write(true); + + this->write_byte(0x01, 0x3F); + + delay(40); + + this->write_byte(0x0D, 0x80); + + delay(2); + + this->read_byte(0x00, &temperature_, 0); + + this->le_pin_->digital_write(false); + this->oe_pin_->digital_write(false); + this->cl_pin_->digital_write(false); + this->sph_pin_->digital_write(true); + this->gmod_pin_->digital_write(true); + this->spv_pin_->digital_write(true); + this->ckv_pin_->digital_write(false); + this->oe_pin_->digital_write(true); +} +void Inkplate6::fill(Color color) { + ESP_LOGV(TAG, "Fill called"); + unsigned long start_time = millis(); + + if (this->greyscale_) { + uint8_t fill = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5; + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) + this->buffer_[i] = (fill << 4) | fill; + } else { + uint8_t fill = color.is_on() ? 0x00 : 0xFF; + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) + this->partial_buffer_[i] = fill; + } + + ESP_LOGV(TAG, "Fill finished (%lums)", millis() - start_time); +} +void Inkplate6::display() { + ESP_LOGV(TAG, "Display called"); + unsigned long start_time = millis(); + + if (this->greyscale_) { + this->display3b_(); + } else { + if (this->partial_updating_ && this->partial_update_()) { + ESP_LOGV(TAG, "Display finished (partial) (%lums)", millis() - start_time); + return; + } + this->display1b_(); + } + ESP_LOGV(TAG, "Display finished (full) (%lums)", millis() - start_time); +} +void Inkplate6::display1b_() { + ESP_LOGV(TAG, "Display1b called"); + unsigned long start_time = millis(); + + for (int i = 0; i < this->get_buffer_length_(); i++) { + this->buffer_[i] &= this->partial_buffer_[i]; + this->buffer_[i] |= this->partial_buffer_[i]; + } + + uint16_t pos; + uint32_t send; + uint8_t data; + uint8_t buffer_value; + eink_on_(); + clean_fast_(0, 1); + clean_fast_(1, 5); + clean_fast_(2, 1); + clean_fast_(0, 5); + clean_fast_(2, 1); + clean_fast_(1, 12); + clean_fast_(2, 1); + clean_fast_(0, 11); + + ESP_LOGV(TAG, "Display1b start loops (%lums)", millis() - start_time); + for (int k = 0; k < 3; k++) { + pos = this->get_buffer_length_() - 1; + vscan_start_(); + for (int i = 0; i < this->get_height_internal(); i++) { + buffer_value = this->buffer_[pos]; + data = LUTB[(buffer_value >> 4) & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + hscan_start_(send); + data = LUTB[buffer_value & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + pos--; + + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { + buffer_value = this->buffer_[pos]; + data = LUTB[(buffer_value >> 4) & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + data = LUTB[buffer_value & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + pos--; + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + } + ESP_LOGV(TAG, "Display1b first loop x %d (%lums)", 3, millis() - start_time); + + pos = this->get_buffer_length_() - 1; + vscan_start_(); + for (int i = 0; i < this->get_height_internal(); i++) { + buffer_value = this->buffer_[pos]; + data = LUT2[(buffer_value >> 4) & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + hscan_start_(send); + data = LUT2[buffer_value & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + pos--; + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { + buffer_value = this->buffer_[pos]; + data = LUT2[(buffer_value >> 4) & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + data = LUT2[buffer_value & 0x0F]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + pos--; + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + ESP_LOGV(TAG, "Display1b second loop (%lums)", millis() - start_time); + + vscan_start_(); + for (int i = 0; i < this->get_height_internal(); i++) { + buffer_value = this->buffer_[pos]; + data = 0b00000000; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + hscan_start_(send); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + ESP_LOGV(TAG, "Display1b third loop (%lums)", millis() - start_time); + + vscan_start_(); + eink_off_(); + this->block_partial_ = false; + this->partial_updates_ = 0; + ESP_LOGV(TAG, "Display1b finished (%lums)", millis() - start_time); +} +void Inkplate6::display3b_() { + ESP_LOGV(TAG, "Display3b called"); + unsigned long start_time = millis(); + + eink_on_(); + clean_fast_(0, 1); + clean_fast_(1, 12); + clean_fast_(2, 1); + clean_fast_(0, 11); + clean_fast_(2, 1); + clean_fast_(1, 12); + clean_fast_(2, 1); + clean_fast_(0, 11); + + for (int k = 0; k < 8; k++) { + uint32_t pos = this->get_buffer_length_() - 1; + uint32_t send; + uint8_t pix1; + uint8_t pix2; + uint8_t pix3; + uint8_t pix4; + uint8_t pixel; + uint8_t pixel2; + + vscan_start_(); + for (int i = 0; i < this->get_height_internal(); i++) { + pix1 = this->buffer_[pos--]; + pix2 = this->buffer_[pos--]; + pix3 = this->buffer_[pos--]; + pix4 = this->buffer_[pos--]; + pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) | + (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0); + pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | + (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); + + send = ((pixel & B00000011) << 4) | (((pixel & B00001100) >> 2) << 18) | (((pixel & B00010000) >> 4) << 23) | + (((pixel & B11100000) >> 5) << 25); + hscan_start_(send); + send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) | + (((pixel2 & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { + pix1 = this->buffer_[pos--]; + pix2 = this->buffer_[pos--]; + pix3 = this->buffer_[pos--]; + pix4 = this->buffer_[pos--]; + pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) | + (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0); + pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | + (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); + + send = ((pixel & B00000011) << 4) | (((pixel & B00001100) >> 2) << 18) | (((pixel & B00010000) >> 4) << 23) | + (((pixel & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + + send = ((pixel2 & B00000011) << 4) | (((pixel2 & B00001100) >> 2) << 18) | (((pixel2 & B00010000) >> 4) << 23) | + (((pixel2 & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + } + clean_fast_(2, 1); + clean_fast_(3, 1); + vscan_start_(); + eink_off_(); + ESP_LOGV(TAG, "Display3b finished (%lums)", millis() - start_time); +} +bool Inkplate6::partial_update_() { + ESP_LOGV(TAG, "Partial update called"); + unsigned long start_time = millis(); + if (this->greyscale_) + return false; + if (this->block_partial_) + return false; + + this->partial_updates_++; + + uint16_t pos = this->get_buffer_length_() - 1; + uint32_t send; + uint8_t data; + uint8_t diffw, diffb; + uint32_t n = (this->get_buffer_length_() * 2) - 1; + + for (int i = 0; i < this->get_height_internal(); i++) { + for (int j = 0; j < (this->get_width_internal() / 8); j++) { + diffw = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & ~(this->partial_buffer_[pos]); + diffb = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & this->partial_buffer_[pos]; + pos--; + this->partial_buffer_2_[n--] = LUTW[diffw >> 4] & LUTB[diffb >> 4]; + this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F]; + } + } + ESP_LOGV(TAG, "Partial update buffer built after (%lums)", millis() - start_time); + + eink_on_(); + for (int k = 0; k < 5; k++) { + vscan_start_(); + n = (this->get_buffer_length_() * 2) - 1; + for (int i = 0; i < this->get_height_internal(); i++) { + data = this->partial_buffer_2_[n--]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + hscan_start_(send); + for (int j = 0; j < (this->get_width_internal() / 4) - 1; j++) { + data = this->partial_buffer_2_[n--]; + send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + ESP_LOGV(TAG, "Partial update loop k=%d (%lums)", k, millis() - start_time); + } + clean_fast_(2, 2); + clean_fast_(3, 1); + vscan_start_(); + eink_off_(); + + for (int i = 0; i < this->get_buffer_length_(); i++) { + this->buffer_[i] = this->partial_buffer_[i]; + } + ESP_LOGV(TAG, "Partial update finished (%lums)", millis() - start_time); + return true; +} +void Inkplate6::vscan_start_() { + this->ckv_pin_->digital_write(true); + delayMicroseconds(7); + this->spv_pin_->digital_write(false); + delayMicroseconds(10); + this->ckv_pin_->digital_write(false); + delayMicroseconds(0); + this->ckv_pin_->digital_write(true); + delayMicroseconds(8); + this->spv_pin_->digital_write(true); + delayMicroseconds(10); + this->ckv_pin_->digital_write(false); + delayMicroseconds(0); + this->ckv_pin_->digital_write(true); + delayMicroseconds(18); + this->ckv_pin_->digital_write(false); + delayMicroseconds(0); + this->ckv_pin_->digital_write(true); + delayMicroseconds(18); + this->ckv_pin_->digital_write(false); + delayMicroseconds(0); + this->ckv_pin_->digital_write(true); +} +void Inkplate6::vscan_write_() { + this->ckv_pin_->digital_write(false); + this->le_pin_->digital_write(true); + this->le_pin_->digital_write(false); + delayMicroseconds(0); + this->sph_pin_->digital_write(false); + this->cl_pin_->digital_write(true); + this->cl_pin_->digital_write(false); + this->sph_pin_->digital_write(true); + this->ckv_pin_->digital_write(true); +} +void Inkplate6::hscan_start_(uint32_t d) { + this->sph_pin_->digital_write(false); + GPIO.out_w1ts = (d) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + this->sph_pin_->digital_write(true); +} +void Inkplate6::vscan_end_() { + this->ckv_pin_->digital_write(false); + this->le_pin_->digital_write(true); + this->le_pin_->digital_write(false); + delayMicroseconds(1); + this->ckv_pin_->digital_write(true); +} +void Inkplate6::clean() { + ESP_LOGV(TAG, "Clean called"); + unsigned long start_time = millis(); + + eink_on_(); + clean_fast_(0, 1); // White + clean_fast_(0, 8); // White to White + clean_fast_(0, 1); // White to Black + clean_fast_(0, 8); // Black to Black + clean_fast_(2, 1); // Black to White + clean_fast_(1, 10); // White to White + ESP_LOGV(TAG, "Clean finished (%lums)", millis() - start_time); +} +void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { + ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep); + unsigned long start_time = millis(); + + eink_on_(); + uint8_t data = 0; + if (c == 0) // White + data = B10101010; + else if (c == 1) // Black + data = B01010101; + else if (c == 2) // Discharge + data = B00000000; + else if (c == 3) // Skip + data = B11111111; + + uint32_t send = ((data & B00000011) << 4) | (((data & B00001100) >> 2) << 18) | (((data & B00010000) >> 4) << 23) | + (((data & B11100000) >> 5) << 25); + + for (int k = 0; k < rep; k++) { + vscan_start_(); + for (int i = 0; i < this->get_height_internal(); i++) { + hscan_start_(send); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + for (int j = 0; j < this->get_width_internal() / 8; j++) { + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + } + GPIO.out_w1ts = (send) | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + vscan_end_(); + } + delayMicroseconds(230); + ESP_LOGV(TAG, "Clean fast rep loop %d finished (%lums)", k, millis() - start_time); + } + ESP_LOGV(TAG, "Clean fast finished (%lums)", millis() - start_time); +} +void Inkplate6::pins_z_state_() { + this->ckv_pin_->pin_mode(INPUT); + this->sph_pin_->pin_mode(INPUT); + + this->oe_pin_->pin_mode(INPUT); + this->gmod_pin_->pin_mode(INPUT); + this->spv_pin_->pin_mode(INPUT); + + this->display_data_0_pin_->pin_mode(INPUT); + this->display_data_1_pin_->pin_mode(INPUT); + this->display_data_2_pin_->pin_mode(INPUT); + this->display_data_3_pin_->pin_mode(INPUT); + this->display_data_4_pin_->pin_mode(INPUT); + this->display_data_5_pin_->pin_mode(INPUT); + this->display_data_6_pin_->pin_mode(INPUT); + this->display_data_7_pin_->pin_mode(INPUT); +} +void Inkplate6::pins_as_outputs_() { + this->ckv_pin_->pin_mode(OUTPUT); + this->sph_pin_->pin_mode(OUTPUT); + + this->oe_pin_->pin_mode(OUTPUT); + this->gmod_pin_->pin_mode(OUTPUT); + this->spv_pin_->pin_mode(OUTPUT); + + this->display_data_0_pin_->pin_mode(OUTPUT); + this->display_data_1_pin_->pin_mode(OUTPUT); + this->display_data_2_pin_->pin_mode(OUTPUT); + this->display_data_3_pin_->pin_mode(OUTPUT); + this->display_data_4_pin_->pin_mode(OUTPUT); + this->display_data_5_pin_->pin_mode(OUTPUT); + this->display_data_6_pin_->pin_mode(OUTPUT); + this->display_data_7_pin_->pin_mode(OUTPUT); +} + +} // namespace inkplate6 +} // namespace esphome + +#endif diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h new file mode 100644 index 0000000000..94d14b4f6e --- /dev/null +++ b/esphome/components/inkplate6/inkplate.h @@ -0,0 +1,157 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/display/display_buffer.h" + +#ifdef ARDUINO_ARCH_ESP32 + +namespace esphome { +namespace inkplate6 { + +class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice { + public: + const uint8_t LUT2[16] = {B10101010, B10101001, B10100110, B10100101, B10011010, B10011001, B10010110, B10010101, + B01101010, B01101001, B01100110, B01100101, B01011010, B01011001, B01010110, B01010101}; + const uint8_t LUTW[16] = {B11111111, B11111110, B11111011, B11111010, B11101111, B11101110, B11101011, B11101010, + B10111111, B10111110, B10111011, B10111010, B10101111, B10101110, B10101011, B10101010}; + const uint8_t LUTB[16] = {B11111111, B11111101, B11110111, B11110101, B11011111, B11011101, B11010111, B11010101, + B01111111, B01111101, B01110111, B01110101, B01011111, B01011101, B01010111, B01010101}; + const uint8_t pixelMaskLUT[8] = {B00000001, B00000010, B00000100, B00001000, + B00010000, B00100000, B01000000, B10000000}; + const uint8_t pixelMaskGLUT[2] = {B00001111, B11110000}; + const uint8_t waveform3Bit[8][8] = {{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, + {0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, + {1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0}}; + const uint32_t waveform[50] = { + 0x00000008, 0x00000008, 0x00200408, 0x80281888, 0x60a81898, 0x60a8a8a8, 0x60a8a8a8, 0x6068a868, 0x6868a868, + 0x6868a868, 0x68686868, 0x6a686868, 0x5a686868, 0x5a686868, 0x5a586a68, 0x5a5a6a68, 0x5a5a6a68, 0x55566a68, + 0x55565a64, 0x55555654, 0x55555556, 0x55555556, 0x55555556, 0x55555516, 0x55555596, 0x15555595, 0x95955595, + 0x95959595, 0x95949495, 0x94949495, 0x94949495, 0xa4949494, 0x9494a4a4, 0x84a49494, 0x84948484, 0x84848484, + 0x84848484, 0x84848484, 0xa5a48484, 0xa9a4a4a8, 0xa9a8a8a8, 0xa5a9a9a4, 0xa5a5a5a4, 0xa1a5a5a1, 0xa9a9a9a9, + 0xa9a9a9a9, 0xa9a9a9a9, 0xa9a9a9a9, 0x15151515, 0x11111111}; + + void set_greyscale(bool greyscale) { + this->greyscale_ = greyscale; + this->initialize_(); + this->block_partial_ = true; + } + void set_partial_updating(bool partial_updating) { this->partial_updating_ = partial_updating; } + void set_full_update_every(uint32_t full_update_every) { this->full_update_every_ = full_update_every; } + + void set_display_data_0_pin(GPIOPin *data) { this->display_data_0_pin_ = data; } + void set_display_data_1_pin(GPIOPin *data) { this->display_data_1_pin_ = data; } + void set_display_data_2_pin(GPIOPin *data) { this->display_data_2_pin_ = data; } + void set_display_data_3_pin(GPIOPin *data) { this->display_data_3_pin_ = data; } + void set_display_data_4_pin(GPIOPin *data) { this->display_data_4_pin_ = data; } + void set_display_data_5_pin(GPIOPin *data) { this->display_data_5_pin_ = data; } + void set_display_data_6_pin(GPIOPin *data) { this->display_data_6_pin_ = data; } + void set_display_data_7_pin(GPIOPin *data) { this->display_data_7_pin_ = data; } + + void set_ckv_pin(GPIOPin *ckv) { this->ckv_pin_ = ckv; } + void set_cl_pin(GPIOPin *cl) { this->cl_pin_ = cl; } + void set_gpio0_enable_pin(GPIOPin *gpio0_enable) { this->gpio0_enable_pin_ = gpio0_enable; } + void set_gmod_pin(GPIOPin *gmod) { this->gmod_pin_ = gmod; } + void set_le_pin(GPIOPin *le) { this->le_pin_ = le; } + void set_oe_pin(GPIOPin *oe) { this->oe_pin_ = oe; } + void set_powerup_pin(GPIOPin *powerup) { this->powerup_pin_ = powerup; } + void set_sph_pin(GPIOPin *sph) { this->sph_pin_ = sph; } + void set_spv_pin(GPIOPin *spv) { this->spv_pin_ = spv; } + void set_vcom_pin(GPIOPin *vcom) { this->vcom_pin_ = vcom; } + void set_wakeup_pin(GPIOPin *wakeup) { this->wakeup_pin_ = wakeup; } + + float get_setup_priority() const override; + + void dump_config() override; + + void display(); + void clean(); + void fill(Color color) override; + + void update() override; + + void setup() override; + + uint8_t get_panel_state() { return this->panel_on_; } + bool get_greyscale() { return this->greyscale_; } + bool get_partial_updating() { return this->partial_updating_; } + uint8_t get_temperature() { return this->temperature_; } + + protected: + void draw_absolute_pixel_internal(int x, int y, Color color) override; + void display1b_(); + void display3b_(); + void initialize_(); + bool partial_update_(); + void clean_fast_(uint8_t c, uint8_t rep); + + void hscan_start_(uint32_t d); + void vscan_end_(); + void vscan_start_(); + void vscan_write_(); + + void eink_off_(); + void eink_on_(); + + void setup_pins_(); + void pins_z_state_(); + void pins_as_outputs_(); + + int get_width_internal() override { return 800; } + + int get_height_internal() override { return 600; } + + size_t get_buffer_length_(); + + int get_data_pin_mask_() { + int data = 0; + data |= (1 << this->display_data_0_pin_->get_pin()); + data |= (1 << this->display_data_1_pin_->get_pin()); + data |= (1 << this->display_data_2_pin_->get_pin()); + data |= (1 << this->display_data_3_pin_->get_pin()); + data |= (1 << this->display_data_4_pin_->get_pin()); + data |= (1 << this->display_data_5_pin_->get_pin()); + data |= (1 << this->display_data_6_pin_->get_pin()); + data |= (1 << this->display_data_7_pin_->get_pin()); + return data; + } + + uint8_t panel_on_ = 0; + uint8_t temperature_; + + uint8_t *partial_buffer_{nullptr}; + uint8_t *partial_buffer_2_{nullptr}; + + uint32_t full_update_every_; + uint32_t partial_updates_{0}; + + bool block_partial_; + bool greyscale_; + bool partial_updating_; + + GPIOPin *display_data_0_pin_; + GPIOPin *display_data_1_pin_; + GPIOPin *display_data_2_pin_; + GPIOPin *display_data_3_pin_; + GPIOPin *display_data_4_pin_; + GPIOPin *display_data_5_pin_; + GPIOPin *display_data_6_pin_; + GPIOPin *display_data_7_pin_; + + GPIOPin *ckv_pin_; + GPIOPin *cl_pin_; + GPIOPin *gpio0_enable_pin_; + GPIOPin *gmod_pin_; + GPIOPin *le_pin_; + GPIOPin *oe_pin_; + GPIOPin *powerup_pin_; + GPIOPin *sph_pin_; + GPIOPin *spv_pin_; + GPIOPin *vcom_pin_; + GPIOPin *wakeup_pin_; +}; + +} // namespace inkplate6 +} // namespace esphome + +#endif