Speed up (and fix) ili9xxx display component. (#5406)

This commit is contained in:
Clyde Stubbs 2023-11-28 11:42:03 +11:00 committed by GitHub
parent ab1cc0ed6e
commit 993cd55b1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 317 additions and 280 deletions

View file

@ -2,6 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import core, pins from esphome import core, pins
from esphome.components import display, spi, font from esphome.components import display, spi, font
from esphome.components.display import validate_rotation
from esphome.core import CORE, HexInt from esphome.core import CORE, HexInt
from esphome.const import ( from esphome.const import (
CONF_COLOR_PALETTE, CONF_COLOR_PALETTE,
@ -13,6 +14,9 @@ from esphome.const import (
CONF_PAGES, CONF_PAGES,
CONF_RESET_PIN, CONF_RESET_PIN,
CONF_DIMENSIONS, CONF_DIMENSIONS,
CONF_WIDTH,
CONF_HEIGHT,
CONF_ROTATION,
) )
DEPENDENCIES = ["spi"] DEPENDENCIES = ["spi"]
@ -26,28 +30,35 @@ def AUTO_LOAD():
CODEOWNERS = ["@nielsnl68", "@clydebarrow"] CODEOWNERS = ["@nielsnl68", "@clydebarrow"]
ili9XXX_ns = cg.esphome_ns.namespace("ili9xxx") ili9xxx_ns = cg.esphome_ns.namespace("ili9xxx")
ili9XXXSPI = ili9XXX_ns.class_( ILI9XXXDisplay = ili9xxx_ns.class_(
"ILI9XXXDisplay", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer "ILI9XXXDisplay", cg.PollingComponent, spi.SPIDevice, display.DisplayBuffer
) )
ILI9XXXColorMode = ili9XXX_ns.enum("ILI9XXXColorMode") ILI9XXXColorMode = ili9xxx_ns.enum("ILI9XXXColorMode")
ColorOrder = display.display_ns.enum("ColorMode")
MODELS = { MODELS = {
"M5STACK": ili9XXX_ns.class_("ILI9XXXM5Stack", ili9XXXSPI), "M5STACK": ili9xxx_ns.class_("ILI9XXXM5Stack", ILI9XXXDisplay),
"M5CORE": ili9XXX_ns.class_("ILI9XXXM5CORE", ili9XXXSPI), "M5CORE": ili9xxx_ns.class_("ILI9XXXM5CORE", ILI9XXXDisplay),
"TFT_2.4": ili9XXX_ns.class_("ILI9XXXILI9341", ili9XXXSPI), "TFT_2.4": ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay),
"TFT_2.4R": ili9XXX_ns.class_("ILI9XXXILI9342", ili9XXXSPI), "TFT_2.4R": ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay),
"ILI9341": ili9XXX_ns.class_("ILI9XXXILI9341", ili9XXXSPI), "ILI9341": ili9xxx_ns.class_("ILI9XXXILI9341", ILI9XXXDisplay),
"ILI9342": ili9XXX_ns.class_("ILI9XXXILI9342", ili9XXXSPI), "ILI9342": ili9xxx_ns.class_("ILI9XXXILI9342", ILI9XXXDisplay),
"ILI9481": ili9XXX_ns.class_("ILI9XXXILI9481", ili9XXXSPI), "ILI9481": ili9xxx_ns.class_("ILI9XXXILI9481", ILI9XXXDisplay),
"ILI9481-18": ili9XXX_ns.class_("ILI9XXXILI948118", ili9XXXSPI), "ILI9481-18": ili9xxx_ns.class_("ILI9XXXILI948118", ILI9XXXDisplay),
"ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI), "ILI9486": ili9xxx_ns.class_("ILI9XXXILI9486", ILI9XXXDisplay),
"ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI), "ILI9488": ili9xxx_ns.class_("ILI9XXXILI9488", ILI9XXXDisplay),
"ILI9488_A": ili9XXX_ns.class_("ILI9XXXILI9488A", ili9XXXSPI), "ILI9488_A": ili9xxx_ns.class_("ILI9XXXILI9488A", ILI9XXXDisplay),
"ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI), "ST7796": ili9xxx_ns.class_("ILI9XXXST7796", ILI9XXXDisplay),
"S3BOX": ili9XXX_ns.class_("ILI9XXXS3Box", ili9XXXSPI), "ST7789V": ili9xxx_ns.class_("ILI9XXXST7789V", ILI9XXXDisplay),
"S3BOX_LITE": ili9XXX_ns.class_("ILI9XXXS3BoxLite", ili9XXXSPI), "S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay),
"S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay),
}
COLOR_ORDERS = {
"RGB": ColorOrder.COLOR_ORDER_RGB,
"BGR": ColorOrder.COLOR_ORDER_BGR,
} }
COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE")
@ -55,6 +66,14 @@ COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE")
CONF_LED_PIN = "led_pin" CONF_LED_PIN = "led_pin"
CONF_COLOR_PALETTE_IMAGES = "color_palette_images" CONF_COLOR_PALETTE_IMAGES = "color_palette_images"
CONF_INVERT_DISPLAY = "invert_display" CONF_INVERT_DISPLAY = "invert_display"
CONF_INVERT_COLORS = "invert_colors"
CONF_MIRROR_X = "mirror_x"
CONF_MIRROR_Y = "mirror_y"
CONF_SWAP_XY = "swap_xy"
CONF_COLOR_ORDER = "color_order"
CONF_OFFSET_HEIGHT = "offset_height"
CONF_OFFSET_WIDTH = "offset_width"
CONF_TRANSFORM = "transform"
def _validate(config): def _validate(config):
@ -77,6 +96,7 @@ def _validate(config):
"TFT_2.4R", "TFT_2.4R",
"ILI9341", "ILI9341",
"ILI9342", "ILI9342",
"ST7789V",
]: ]:
raise cv.Invalid( raise cv.Invalid(
"Provided model can't run on ESP8266. Use an ESP32 with PSRAM onboard" "Provided model can't run on ESP8266. Use an ESP32 with PSRAM onboard"
@ -88,9 +108,19 @@ CONFIG_SCHEMA = cv.All(
font.validate_pillow_installed, font.validate_pillow_installed,
display.FULL_DISPLAY_SCHEMA.extend( display.FULL_DISPLAY_SCHEMA.extend(
{ {
cv.GenerateID(): cv.declare_id(ili9XXXSPI), cv.GenerateID(): cv.declare_id(ILI9XXXDisplay),
cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True, space="_"), cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True, space="_"),
cv.Optional(CONF_DIMENSIONS): cv.dimensions, cv.Optional(CONF_DIMENSIONS): cv.Any(
cv.dimensions,
cv.Schema(
{
cv.Required(CONF_WIDTH): cv.int_,
cv.Required(CONF_HEIGHT): cv.int_,
cv.Optional(CONF_OFFSET_HEIGHT, default=0): cv.int_,
cv.Optional(CONF_OFFSET_WIDTH, default=0): cv.int_,
}
),
),
cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_DC_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_LED_PIN): cv.invalid( cv.Optional(CONF_LED_PIN): cv.invalid(
@ -101,7 +131,19 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list( cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list(
cv.file_ cv.file_
), ),
cv.Optional(CONF_INVERT_DISPLAY): cv.boolean, cv.Optional(CONF_INVERT_DISPLAY): cv.invalid(
"'invert_display' has been replaced by 'invert_colors'"
),
cv.Optional(CONF_INVERT_COLORS): cv.boolean,
cv.Optional(CONF_COLOR_ORDER): cv.one_of(*COLOR_ORDERS.keys(), upper=True),
cv.Exclusive(CONF_ROTATION, CONF_ROTATION): validate_rotation,
cv.Exclusive(CONF_TRANSFORM, CONF_ROTATION): cv.Schema(
{
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
}
),
} }
) )
.extend(cv.polling_component_schema("1s")) .extend(cv.polling_component_schema("1s"))
@ -119,6 +161,13 @@ async def to_code(config):
await spi.register_spi_device(var, config) await spi.register_spi_device(var, config)
dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) dc = await cg.gpio_pin_expression(config[CONF_DC_PIN])
cg.add(var.set_dc_pin(dc)) cg.add(var.set_dc_pin(dc))
if CONF_COLOR_ORDER in config:
cg.add(var.set_color_order(COLOR_ORDERS[config[CONF_COLOR_ORDER]]))
if CONF_TRANSFORM in config:
transform = config[CONF_TRANSFORM]
cg.add(var.set_swap_xy(transform[CONF_SWAP_XY]))
cg.add(var.set_mirror_x(transform[CONF_MIRROR_X]))
cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y]))
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( lambda_ = await cg.process_lambda(
@ -131,9 +180,17 @@ async def to_code(config):
cg.add(var.set_reset_pin(reset)) cg.add(var.set_reset_pin(reset))
if CONF_DIMENSIONS in config: if CONF_DIMENSIONS in config:
cg.add( dimensions = config[CONF_DIMENSIONS]
var.set_dimentions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1]) if isinstance(dimensions, dict):
) cg.add(var.set_dimensions(dimensions[CONF_WIDTH], dimensions[CONF_HEIGHT]))
cg.add(
var.set_offsets(
dimensions[CONF_OFFSET_WIDTH], dimensions[CONF_OFFSET_HEIGHT]
)
)
else:
(width, height) = dimensions
cg.add(var.set_dimensions(width, height))
rhs = None rhs = None
if config[CONF_COLOR_PALETTE] == "GRAYSCALE": if config[CONF_COLOR_PALETTE] == "GRAYSCALE":
@ -178,5 +235,5 @@ async def to_code(config):
prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs)
cg.add(var.set_palette(prog_arr)) cg.add(var.set_palette(prog_arr))
if CONF_INVERT_DISPLAY in config: if CONF_INVERT_COLORS in config:
cg.add(var.invert_display(config[CONF_INVERT_DISPLAY])) cg.add(var.invert_colors(config[CONF_INVERT_COLORS]))

View file

@ -8,11 +8,31 @@ namespace esphome {
namespace ili9xxx { namespace ili9xxx {
static const char *const TAG = "ili9xxx"; static const char *const TAG = "ili9xxx";
static const uint16_t SPI_SETUP_US = 100; // estimated fixed overhead in microseconds for an SPI write
static const uint16_t SPI_MAX_BLOCK_SIZE = 4092; // Max size of continuous SPI transfer
// store a 16 bit value in a buffer, big endian.
static inline void put16_be(uint8_t *buf, uint16_t value) {
buf[0] = value >> 8;
buf[1] = value;
}
void ILI9XXXDisplay::setup() { void ILI9XXXDisplay::setup() {
ESP_LOGD(TAG, "Setting up ILI9xxx");
this->setup_pins_(); this->setup_pins_();
this->initialize(); this->init_lcd_();
this->command(this->pre_invertdisplay_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
// custom x/y transform and color order
uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
if (this->swap_xy_)
mad |= MADCTL_MV;
if (this->mirror_x_)
mad |= MADCTL_MX;
if (this->mirror_y_)
mad |= MADCTL_MY;
this->send_command(ILI9XXX_MADCTL, &mad, 1);
this->x_low_ = this->width_; this->x_low_ = this->width_;
this->y_low_ = this->height_; this->y_low_ = this->height_;
@ -47,6 +67,8 @@ void ILI9XXXDisplay::setup_pins_() {
void ILI9XXXDisplay::dump_config() { void ILI9XXXDisplay::dump_config() {
LOG_DISPLAY("", "ili9xxx", this); LOG_DISPLAY("", "ili9xxx", this);
ESP_LOGCONFIG(TAG, " Width Offset: %u", this->offset_x_);
ESP_LOGCONFIG(TAG, " Height Offset: %u", this->offset_y_);
switch (this->buffer_color_mode_) { switch (this->buffer_color_mode_) {
case BITS_8_INDEXED: case BITS_8_INDEXED:
ESP_LOGCONFIG(TAG, " Color mode: 8bit Indexed"); ESP_LOGCONFIG(TAG, " Color mode: 8bit Indexed");
@ -64,8 +86,12 @@ void ILI9XXXDisplay::dump_config() {
ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000)); ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));
LOG_PIN(" Reset Pin: ", this->reset_pin_); LOG_PIN(" Reset Pin: ", this->reset_pin_);
LOG_PIN(" CS Pin: ", this->cs_);
LOG_PIN(" DC Pin: ", this->dc_pin_); LOG_PIN(" DC Pin: ", this->dc_pin_);
LOG_PIN(" Busy Pin: ", this->busy_pin_); LOG_PIN(" Busy Pin: ", this->busy_pin_);
ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_));
ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_));
ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_));
if (this->is_failed()) { if (this->is_failed()) {
ESP_LOGCONFIG(TAG, " => Failed to init Memory: YES!"); ESP_LOGCONFIG(TAG, " => Failed to init Memory: YES!");
@ -141,12 +167,14 @@ void HOT ILI9XXXDisplay::draw_absolute_pixel_internal(int x, int y, Color color)
} }
if (updated) { if (updated) {
// low and high watermark may speed up drawing from buffer // low and high watermark may speed up drawing from buffer
this->x_low_ = (x < this->x_low_) ? x : this->x_low_; if (x < this->x_low_)
this->y_low_ = (y < this->y_low_) ? y : this->y_low_; this->x_low_ = x;
this->x_high_ = (x > this->x_high_) ? x : this->x_high_; if (y < this->y_low_)
this->y_high_ = (y > this->y_high_) ? y : this->y_high_; this->y_low_ = y;
// ESP_LOGVV(TAG, "=>>> pixel (x:%d, y:%d) (xl:%d, xh:%d, yl:%d, yh:%d", x, y, this->x_low_, this->x_high_, if (x > this->x_high_)
// this->y_low_, this->y_high_); this->x_high_ = x;
if (y > this->y_high_)
this->y_high_ = y;
} }
} }
@ -165,59 +193,82 @@ void ILI9XXXDisplay::update() {
} }
void ILI9XXXDisplay::display_() { void ILI9XXXDisplay::display_() {
// we will only update the changed window to the display uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
uint16_t w = this->x_high_ - this->x_low_ + 1; // NOLINT
uint16_t h = this->y_high_ - this->y_low_ + 1; // NOLINT
uint32_t start_pos = ((this->y_low_ * this->width_) + x_low_);
// check if something was displayed // check if something was displayed
if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) { if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
ESP_LOGV(TAG, "Nothing to display"); ESP_LOGV(TAG, "Nothing to display");
return; return;
} }
set_addr_window_(this->x_low_, this->y_low_, w, h); // we will only update the changed rows to the display
size_t const w = this->x_high_ - this->x_low_ + 1;
size_t const h = this->y_high_ - this->y_low_ + 1;
size_t mhz = this->data_rate_ / 1000000;
// estimate time for a single write
size_t sw_time = this->width_ * h * 16 / mhz + this->width_ * h * 2 / SPI_MAX_BLOCK_SIZE * SPI_SETUP_US * 2;
// estimate time for multiple writes
size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US;
ESP_LOGV(TAG, ESP_LOGV(TAG,
"Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, " "Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
"heigth:%d, start_pos:%" PRId32 ")", "height:%d, mode=%d, 18bit=%d, sw_time=%dus, mw_time=%dus)",
this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, start_pos); this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_,
this->is_18bitdisplay_, sw_time, mw_time);
this->start_data_(); auto now = millis();
for (uint16_t row = 0; row < h; row++) { this->enable();
uint32_t pos = start_pos + (row * width_); if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) {
uint32_t rem = w; // 16 bit mode maps directly to display format
ESP_LOGV(TAG, "Doing single write of %d bytes", this->width_ * h * 2);
while (rem > 0) { set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_);
uint32_t sz = std::min(rem, ILI9XXX_TRANSFER_BUFFER_SIZE); this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
// ESP_LOGVV(TAG, "Send to display(pos:%d, rem:%d, zs:%d)", pos, rem, sz); } else {
buffer_to_transfer_(pos, sz); ESP_LOGV(TAG, "Doing multiple write");
if (this->is_18bitdisplay_) { size_t rem = h * w; // remaining number of pixels to write
for (uint32_t i = 0; i < sz; ++i) { set_addr_window_(this->x_low_, this->y_low_, this->x_high_, this->y_high_);
uint16_t color_val = transfer_buffer_[i]; size_t idx = 0; // index into transfer_buffer
size_t pixel = 0; // pixel number offset
uint8_t red = color_val & 0x1F; size_t pos = this->y_low_ * this->width_ + this->x_low_;
uint8_t green = (color_val & 0x7E0) >> 5; while (rem-- != 0) {
uint8_t blue = (color_val & 0xF800) >> 11; uint16_t color_val;
switch (this->buffer_color_mode_) {
uint8_t pass_buff[3]; case BITS_8:
color_val = display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(this->buffer_[pos++]));
pass_buff[2] = (uint8_t) ((red / 32.0) * 64) << 2; break;
pass_buff[1] = (uint8_t) green << 2; case BITS_8_INDEXED:
pass_buff[0] = (uint8_t) ((blue / 32.0) * 64) << 2; color_val = display::ColorUtil::color_to_565(
display::ColorUtil::index8_to_color_palette888(this->buffer_[pos++], this->palette_));
this->write_array(pass_buff, sizeof(pass_buff)); break;
} default: // case BITS_16:
} else { color_val = (buffer_[pos * 2] << 8) + buffer_[pos * 2 + 1];
this->write_array16(transfer_buffer_, sz); pos++;
break;
}
if (this->is_18bitdisplay_) {
transfer_buffer[idx++] = (uint8_t) ((color_val & 0xF800) >> 8); // Blue
transfer_buffer[idx++] = (uint8_t) ((color_val & 0x7E0) >> 3); // Green
transfer_buffer[idx++] = (uint8_t) (color_val << 3); // Red
} else {
put16_be(transfer_buffer + idx, color_val);
idx += 2;
}
if (idx == ILI9XXX_TRANSFER_BUFFER_SIZE) {
this->write_array(transfer_buffer, idx);
idx = 0;
App.feed_wdt();
}
// end of line? Skip to the next.
if (++pixel == w) {
pixel = 0;
pos += this->width_ - w;
} }
pos += sz;
rem -= sz;
} }
App.feed_wdt(); // flush any balance.
if (idx != 0) {
this->write_array(transfer_buffer, idx);
}
} }
this->end_data_(); this->disable();
ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now));
// invalidate watermarks // invalidate watermarks
this->x_low_ = this->width_; this->x_low_ = this->width_;
this->y_low_ = this->height_; this->y_low_ = this->height_;
@ -225,26 +276,6 @@ void ILI9XXXDisplay::display_() {
this->y_high_ = 0; this->y_high_ = 0;
} }
uint32_t ILI9XXXDisplay::buffer_to_transfer_(uint32_t pos, uint32_t sz) {
for (uint32_t i = 0; i < sz; ++i) {
switch (this->buffer_color_mode_) {
case BITS_8_INDEXED:
transfer_buffer_[i] = display::ColorUtil::color_to_565(
display::ColorUtil::index8_to_color_palette888(this->buffer_[pos + i], this->palette_));
break;
case BITS_16:
transfer_buffer_[i] = ((uint16_t) this->buffer_[(pos + i) * 2] << 8) | this->buffer_[((pos + i) * 2) + 1];
continue;
break;
default:
transfer_buffer_[i] =
display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(this->buffer_[pos + i]));
break;
}
}
return sz;
}
// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
// values per bit is huge // values per bit is huge
uint32_t ILI9XXXDisplay::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); } uint32_t ILI9XXXDisplay::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal(); }
@ -303,11 +334,11 @@ void ILI9XXXDisplay::reset_() {
} }
} }
void ILI9XXXDisplay::init_lcd_(const uint8_t *init_cmd) { void ILI9XXXDisplay::init_lcd_() {
uint8_t cmd, x, num_args; uint8_t cmd, x, num_args;
const uint8_t *addr = init_cmd; const uint8_t *addr = this->init_sequence_;
while ((cmd = progmem_read_byte(addr++)) > 0) { while ((cmd = *addr++) > 0) {
x = progmem_read_byte(addr++); x = *addr++;
num_args = x & 0x7F; num_args = x & 0x7F;
send_command(cmd, addr, num_args); send_command(cmd, addr, num_args);
addr += num_args; addr += num_args;
@ -316,27 +347,29 @@ void ILI9XXXDisplay::init_lcd_(const uint8_t *init_cmd) {
} }
} }
void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t w, uint16_t h) { // Tell the display controller where we want to draw pixels.
uint16_t x2 = (x1 + w - 1), y2 = (y1 + h - 1); // when called, the SPI should have already been enabled, only the D/C pin will be toggled here.
this->command(ILI9XXX_CASET); // Column address set void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
this->start_data_(); uint8_t buf[4];
this->write_byte(x1 >> 8); this->dc_pin_->digital_write(false);
this->write_byte(x1); this->write_byte(ILI9XXX_CASET); // Column address set
this->write_byte(x2 >> 8); put16_be(buf, x1 + this->offset_x_);
this->write_byte(x2); put16_be(buf + 2, x2 + this->offset_x_);
this->end_data_(); this->dc_pin_->digital_write(true);
this->command(ILI9XXX_PASET); // Row address set this->write_array(buf, sizeof buf);
this->start_data_(); this->dc_pin_->digital_write(false);
this->write_byte(y1 >> 8); this->write_byte(ILI9XXX_PASET); // Row address set
this->write_byte(y1); put16_be(buf, y1 + this->offset_y_);
this->write_byte(y2 >> 8); put16_be(buf + 2, y2 + this->offset_y_);
this->write_byte(y2); this->dc_pin_->digital_write(true);
this->end_data_(); this->write_array(buf, sizeof buf);
this->command(ILI9XXX_RAMWR); // Write to RAM this->dc_pin_->digital_write(false);
this->write_byte(ILI9XXX_RAMWR); // Write to RAM
this->dc_pin_->digital_write(true);
} }
void ILI9XXXDisplay::invert_display(bool invert) { void ILI9XXXDisplay::invert_colors(bool invert) {
this->pre_invertdisplay_ = invert; this->pre_invertcolors_ = invert;
if (is_ready()) { if (is_ready()) {
this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF); this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF);
} }
@ -345,132 +378,5 @@ void ILI9XXXDisplay::invert_display(bool invert) {
int ILI9XXXDisplay::get_width_internal() { return this->width_; } int ILI9XXXDisplay::get_width_internal() { return this->width_; }
int ILI9XXXDisplay::get_height_internal() { return this->height_; } int ILI9XXXDisplay::get_height_internal() { return this->height_; }
// M5Stack display
void ILI9XXXM5Stack::initialize() {
this->init_lcd_(INITCMD_M5STACK);
if (this->width_ == 0)
this->width_ = 320;
if (this->height_ == 0)
this->height_ = 240;
this->pre_invertdisplay_ = true;
}
// M5CORE display // Based on the configuration settings of M5stact's M5GFX code.
void ILI9XXXM5CORE::initialize() {
this->init_lcd_(INITCMD_M5CORE);
if (this->width_ == 0)
this->width_ = 320;
if (this->height_ == 0)
this->height_ = 240;
this->pre_invertdisplay_ = true;
}
// 24_TFT display
void ILI9XXXILI9341::initialize() {
this->init_lcd_(INITCMD_ILI9341);
if (this->width_ == 0)
this->width_ = 240;
if (this->height_ == 0)
this->height_ = 320;
}
// 24_TFT rotated display
void ILI9XXXILI9342::initialize() {
this->init_lcd_(INITCMD_ILI9341);
if (this->width_ == 0) {
this->width_ = 320;
}
if (this->height_ == 0) {
this->height_ = 240;
}
}
// 35_TFT display
void ILI9XXXILI9481::initialize() {
this->init_lcd_(INITCMD_ILI9481);
if (this->width_ == 0) {
this->width_ = 480;
}
if (this->height_ == 0) {
this->height_ = 320;
}
}
void ILI9XXXILI948118::initialize() {
this->init_lcd_(INITCMD_ILI9481_18);
if (this->width_ == 0) {
this->width_ = 320;
}
if (this->height_ == 0) {
this->height_ = 480;
}
this->is_18bitdisplay_ = true;
}
// 35_TFT display
void ILI9XXXILI9486::initialize() {
this->init_lcd_(INITCMD_ILI9486);
if (this->width_ == 0) {
this->width_ = 480;
}
if (this->height_ == 0) {
this->height_ = 320;
}
}
// 40_TFT display
void ILI9XXXILI9488::initialize() {
this->init_lcd_(INITCMD_ILI9488);
if (this->width_ == 0) {
this->width_ = 480;
}
if (this->height_ == 0) {
this->height_ = 320;
}
this->is_18bitdisplay_ = true;
}
// 40_TFT display
void ILI9XXXILI9488A::initialize() {
this->init_lcd_(INITCMD_ILI9488_A);
if (this->width_ == 0) {
this->width_ = 480;
}
if (this->height_ == 0) {
this->height_ = 320;
}
this->is_18bitdisplay_ = true;
}
// 40_TFT display
void ILI9XXXST7796::initialize() {
this->init_lcd_(INITCMD_ST7796);
if (this->width_ == 0) {
this->width_ = 320;
}
if (this->height_ == 0) {
this->height_ = 480;
}
}
// 24_TFT rotated display
void ILI9XXXS3Box::initialize() {
this->init_lcd_(INITCMD_S3BOX);
if (this->width_ == 0) {
this->width_ = 320;
}
if (this->height_ == 0) {
this->height_ = 240;
}
}
// 24_TFT rotated display
void ILI9XXXS3BoxLite::initialize() {
this->init_lcd_(INITCMD_S3BOXLITE);
if (this->width_ == 0) {
this->width_ = 320;
}
if (this->height_ == 0) {
this->height_ = 240;
}
this->pre_invertdisplay_ = true;
}
} // namespace ili9xxx } // namespace ili9xxx
} // namespace esphome } // namespace esphome

View file

@ -7,7 +7,7 @@
namespace esphome { namespace esphome {
namespace ili9xxx { namespace ili9xxx {
const uint32_t ILI9XXX_TRANSFER_BUFFER_SIZE = 64; const size_t ILI9XXX_TRANSFER_BUFFER_SIZE = 126; // ensure this is divisible by 6
enum ILI9XXXColorMode { enum ILI9XXXColorMode {
BITS_8 = 0x08, BITS_8 = 0x08,
@ -23,20 +23,47 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, ILI9XXXDisplay_DATA_RATE> { spi::CLOCK_PHASE_LEADING, ILI9XXXDisplay_DATA_RATE> {
public: public:
ILI9XXXDisplay() = default;
ILI9XXXDisplay(uint8_t const *init_sequence, int16_t width, int16_t height, bool invert_colors)
: init_sequence_{init_sequence}, width_{width}, height_{height}, pre_invertcolors_{invert_colors} {
uint8_t cmd, num_args, bits;
const uint8_t *addr = init_sequence;
while ((cmd = *addr++) != 0) {
num_args = *addr++ & 0x7F;
if (cmd == ILI9XXX_MADCTL) {
bits = *addr;
this->swap_xy_ = (bits & MADCTL_MV) != 0;
this->mirror_x_ = (bits & MADCTL_MX) != 0;
this->mirror_y_ = (bits & MADCTL_MY) != 0;
this->color_order_ = (bits & MADCTL_BGR) ? display::COLOR_ORDER_BGR : display::COLOR_ORDER_RGB;
break;
}
addr += num_args;
}
}
void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; }
float get_setup_priority() const override; float get_setup_priority() const override;
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
void set_palette(const uint8_t *palette) { this->palette_ = palette; } void set_palette(const uint8_t *palette) { this->palette_ = palette; }
void set_buffer_color_mode(ILI9XXXColorMode color_mode) { this->buffer_color_mode_ = color_mode; } void set_buffer_color_mode(ILI9XXXColorMode color_mode) { this->buffer_color_mode_ = color_mode; }
void set_dimentions(int16_t width, int16_t height) { void set_dimensions(int16_t width, int16_t height) {
this->height_ = height; this->height_ = height;
this->width_ = width; this->width_ = width;
} }
void invert_display(bool invert); void set_offsets(int16_t offset_x, int16_t offset_y) {
this->offset_x_ = offset_x;
this->offset_y_ = offset_y;
}
void invert_colors(bool invert);
void command(uint8_t value); void command(uint8_t value);
void data(uint8_t value); void data(uint8_t value);
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes); void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes);
uint8_t read_command(uint8_t command_byte, uint8_t index); uint8_t read_command(uint8_t command_byte, uint8_t index);
void set_color_order(display::ColorOrder color_order) { this->color_order_ = color_order; }
void set_swap_xy(bool swap_xy) { this->swap_xy_ = swap_xy; }
void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
void set_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; }
void update() override; void update() override;
@ -50,16 +77,17 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
protected: protected:
void draw_absolute_pixel_internal(int x, int y, Color color) override; void draw_absolute_pixel_internal(int x, int y, Color color) override;
void setup_pins_(); void setup_pins_();
virtual void initialize() = 0;
void display_(); void display_();
void init_lcd_(const uint8_t *init_cmd); void init_lcd_();
void set_addr_window_(uint16_t x, uint16_t y, uint16_t w, uint16_t h); void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2);
void reset_(); void reset_();
uint8_t const *init_sequence_{};
int16_t width_{0}; ///< Display width as modified by current rotation int16_t width_{0}; ///< Display width as modified by current rotation
int16_t height_{0}; ///< Display height as modified by current rotation int16_t height_{0}; ///< Display height as modified by current rotation
int16_t offset_x_{0};
int16_t offset_y_{0};
uint16_t x_low_{0}; uint16_t x_low_{0};
uint16_t y_low_{0}; uint16_t y_low_{0};
uint16_t x_high_{0}; uint16_t x_high_{0};
@ -77,10 +105,6 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
void start_data_(); void start_data_();
void end_data_(); void end_data_();
uint16_t transfer_buffer_[ILI9XXX_TRANSFER_BUFFER_SIZE];
uint32_t buffer_to_transfer_(uint32_t pos, uint32_t sz);
GPIOPin *reset_pin_{nullptr}; GPIOPin *reset_pin_{nullptr};
GPIOPin *dc_pin_{nullptr}; GPIOPin *dc_pin_{nullptr};
GPIOPin *busy_pin_{nullptr}; GPIOPin *busy_pin_{nullptr};
@ -88,77 +112,87 @@ class ILI9XXXDisplay : public display::DisplayBuffer,
bool prossing_update_ = false; bool prossing_update_ = false;
bool need_update_ = false; bool need_update_ = false;
bool is_18bitdisplay_ = false; bool is_18bitdisplay_ = false;
bool pre_invertdisplay_ = false; bool pre_invertcolors_ = false;
display::ColorOrder color_order_{};
bool swap_xy_{};
bool mirror_x_{};
bool mirror_y_{};
}; };
//----------- M5Stack display -------------- //----------- M5Stack display --------------
class ILI9XXXM5Stack : public ILI9XXXDisplay { class ILI9XXXM5Stack : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXM5Stack() : ILI9XXXDisplay(INITCMD_M5STACK, 320, 240, true) {}
}; };
//----------- M5Stack display -------------- //----------- M5Stack display --------------
class ILI9XXXM5CORE : public ILI9XXXDisplay { class ILI9XXXM5CORE : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXM5CORE() : ILI9XXXDisplay(INITCMD_M5CORE, 320, 240, true) {}
};
//----------- ST7789V display --------------
class ILI9XXXST7789V : public ILI9XXXDisplay {
public:
ILI9XXXST7789V() : ILI9XXXDisplay(INITCMD_ST7789V, 240, 320, false) {}
}; };
//----------- ILI9XXX_24_TFT display -------------- //----------- ILI9XXX_24_TFT display --------------
class ILI9XXXILI9341 : public ILI9XXXDisplay { class ILI9XXXILI9341 : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXILI9341() : ILI9XXXDisplay(INITCMD_ILI9341, 240, 320, false) {}
}; };
//----------- ILI9XXX_24_TFT rotated display -------------- //----------- ILI9XXX_24_TFT rotated display --------------
class ILI9XXXILI9342 : public ILI9XXXDisplay { class ILI9XXXILI9342 : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXILI9342() : ILI9XXXDisplay(INITCMD_ILI9341, 320, 240, false) {}
}; };
//----------- ILI9XXX_??_TFT rotated display -------------- //----------- ILI9XXX_??_TFT rotated display --------------
class ILI9XXXILI9481 : public ILI9XXXDisplay { class ILI9XXXILI9481 : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXILI9481() : ILI9XXXDisplay(INITCMD_ILI9481, 480, 320, false) {}
}; };
//----------- ILI9481 in 18 bit mode -------------- //----------- ILI9481 in 18 bit mode --------------
class ILI9XXXILI948118 : public ILI9XXXDisplay { class ILI9XXXILI948118 : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXILI948118() : ILI9XXXDisplay(INITCMD_ILI9481_18, 320, 480, true) {}
}; };
//----------- ILI9XXX_35_TFT rotated display -------------- //----------- ILI9XXX_35_TFT rotated display --------------
class ILI9XXXILI9486 : public ILI9XXXDisplay { class ILI9XXXILI9486 : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXILI9486() : ILI9XXXDisplay(INITCMD_ILI9486, 480, 320, false) {}
}; };
//----------- ILI9XXX_35_TFT rotated display -------------- //----------- ILI9XXX_35_TFT rotated display --------------
class ILI9XXXILI9488 : public ILI9XXXDisplay { class ILI9XXXILI9488 : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXILI9488() : ILI9XXXDisplay(INITCMD_ILI9488, 480, 320, true) {}
}; };
//----------- ILI9XXX_35_TFT origin colors rotated display -------------- //----------- ILI9XXX_35_TFT origin colors rotated display --------------
class ILI9XXXILI9488A : public ILI9XXXDisplay { class ILI9XXXILI9488A : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXILI9488A() : ILI9XXXDisplay(INITCMD_ILI9488_A, 480, 320, true) {}
}; };
//----------- ILI9XXX_35_TFT rotated display -------------- //----------- ILI9XXX_35_TFT rotated display --------------
class ILI9XXXST7796 : public ILI9XXXDisplay { class ILI9XXXST7796 : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXST7796() : ILI9XXXDisplay(INITCMD_ST7796, 320, 480, false) {}
}; };
class ILI9XXXS3Box : public ILI9XXXDisplay { class ILI9XXXS3Box : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXS3Box() : ILI9XXXDisplay(INITCMD_S3BOX, 320, 240, false) {}
}; };
class ILI9XXXS3BoxLite : public ILI9XXXDisplay { class ILI9XXXS3BoxLite : public ILI9XXXDisplay {
protected: public:
void initialize() override; ILI9XXXS3BoxLite() : ILI9XXXDisplay(INITCMD_S3BOXLITE, 320, 240, true) {}
}; };
} // namespace ili9xxx } // namespace ili9xxx

View file

@ -289,6 +289,33 @@ static const uint8_t PROGMEM INITCMD_S3BOXLITE[] = {
0x00 // End of list 0x00 // End of list
}; };
static const uint8_t PROGMEM INITCMD_ST7789V[] = {
ILI9XXX_SLPOUT , 0x80, // Exit Sleep
ILI9XXX_DISPON , 0x80, // Display on
ILI9XXX_MADCTL , 1, 0x08, // Memory Access Control, BGR
ILI9XXX_DFUNCTR, 2, 0x0A, 0x82,
ILI9XXX_PIXFMT , 1, 0x55,
ILI9XXX_FRMCTR2, 5, 0x0C, 0x0C, 0x00, 0x33, 0x33,
ILI9XXX_ETMOD, 1, 0x35, 0xBB, 1, 0x28,
ILI9XXX_PWCTR1 , 1, 0x0C, // Power control VRH[5:0]
ILI9XXX_PWCTR3 , 2, 0x01, 0xFF,
ILI9XXX_PWCTR4 , 1, 0x10,
ILI9XXX_PWCTR5 , 1, 0x20,
ILI9XXX_IFCTR , 1, 0x0F,
ILI9XXX_PWSET, 2, 0xA4, 0xA1,
ILI9XXX_GMCTRP1 , 14,
0xd0, 0x00, 0x02, 0x07, 0x0a,
0x28, 0x32, 0x44, 0x42, 0x06, 0x0e,
0x12, 0x14, 0x17,
ILI9XXX_GMCTRN1 , 14,
0xd0, 0x00, 0x02, 0x07, 0x0a,
0x28, 0x31, 0x54, 0x47,
0x0e, 0x1c, 0x17, 0x1b,
0x1e,
ILI9XXX_DISPON , 0x80, // Display on
0x00 // End of list
};
// clang-format on // clang-format on
} // namespace ili9xxx } // namespace ili9xxx
} // namespace esphome } // namespace esphome

View file

@ -1554,6 +1554,8 @@ sensor:
memory_address: 0x7d memory_address: 0x7d
name: Adres sensor name: Adres sensor
psram:
esp32_touch: esp32_touch:
setup_mode: false setup_mode: false
iir_filter: 10ms iir_filter: 10ms
@ -2992,6 +2994,12 @@ display:
lambda: |- lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height()); it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: ili9xxx - platform: ili9xxx
invert_colors: true
dimensions: 320x240
transform:
swap_xy: true
mirror_x: true
mirror_y: false
model: TFT 2.4 model: TFT 2.4
cs_pin: GPIO5 cs_pin: GPIO5
dc_pin: GPIO4 dc_pin: GPIO4
@ -3000,6 +3008,11 @@ display:
lambda: |- lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height()); it.rectangle(0, 0, it.get_width(), it.get_height());
- platform: ili9xxx - platform: ili9xxx
dimensions:
width: 320
height: 240
offset_width: 20
offset_height: 10
model: TFT 2.4 model: TFT 2.4
cs_pin: GPIO5 cs_pin: GPIO5
dc_pin: GPIO4 dc_pin: GPIO4