diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 3aaf76d6f8..f0ac5ba9ef 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -47,6 +47,12 @@ ILI9XXXDisplay = ili9xxx_ns.class_( display.DisplayBuffer, ) +PixelMode = ili9xxx_ns.enum("PixelMode") +PIXEL_MODES = { + "16bit": PixelMode.PIXEL_MODE_16, + "18bit": PixelMode.PIXEL_MODE_18, +} + ILI9XXXColorMode = ili9xxx_ns.enum("ILI9XXXColorMode") ColorOrder = display.display_ns.enum("ColorMode") @@ -68,6 +74,7 @@ MODELS = { "S3BOX": ili9xxx_ns.class_("ILI9XXXS3Box", ILI9XXXDisplay), "S3BOX_LITE": ili9xxx_ns.class_("ILI9XXXS3BoxLite", ILI9XXXDisplay), "WAVESHARE_RES_3_5": ili9xxx_ns.class_("WAVESHARERES35", ILI9XXXDisplay), + "CUSTOM": ILI9XXXDisplay, } COLOR_ORDERS = { @@ -80,14 +87,37 @@ COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") CONF_LED_PIN = "led_pin" CONF_COLOR_PALETTE_IMAGES = "color_palette_images" CONF_INVERT_DISPLAY = "invert_display" +CONF_PIXEL_MODE = "pixel_mode" +CONF_INIT_SEQUENCE = "init_sequence" + + +def cmd(c, *args): + """ + Create a command sequence + :param c: The command (8 bit) + :param args: zero or more arguments (8 bit values) + :return: a list with the command, the argument count and the arguments + """ + return [c, len(args)] + list(args) + + +def map_sequence(value): + """ + An initialisation sequence is a literal array of data bytes. + The format is a repeated sequence of [CMD, ] + """ + if len(value) == 0: + raise cv.Invalid("Empty sequence") + return cmd(*value) def _validate(config): - if config.get(CONF_COLOR_PALETTE) == "IMAGE_ADAPTIVE" and not config.get( - CONF_COLOR_PALETTE_IMAGES + if ( + config.get(CONF_COLOR_PALETTE) == "IMAGE_ADAPTIVE" + and CONF_COLOR_PALETTE_IMAGES not in config ): raise cv.Invalid( - "Color palette in IMAGE_ADAPTIVE mode requires at least one 'color_palette_images' entry to generate palette" + "IMAGE_ADAPTIVE palette requires at least one 'color_palette_images' entry" ) if ( config.get(CONF_COLOR_PALETTE_IMAGES) @@ -96,7 +126,8 @@ def _validate(config): raise cv.Invalid( "Providing color palette images requires palette mode to be 'IMAGE_ADAPTIVE'" ) - if CORE.is_esp8266 and config.get(CONF_MODEL) not in [ + model = config[CONF_MODEL] + if CORE.is_esp8266 and model not in [ "M5STACK", "TFT_2.4", "TFT_2.4R", @@ -104,9 +135,12 @@ def _validate(config): "ILI9342", "ST7789V", ]: - raise cv.Invalid( - "Provided model can't run on ESP8266. Use an ESP32 with PSRAM onboard" - ) + raise cv.Invalid("Selected model can't run on ESP8266.") + + if model == "CUSTOM": + if CONF_INIT_SEQUENCE not in config or CONF_DIMENSIONS not in config: + raise cv.Invalid("CUSTOM model requires init_sequence and dimensions") + return config @@ -116,6 +150,7 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(ILI9XXXDisplay), cv.Required(CONF_MODEL): cv.enum(MODELS, upper=True, space="_"), + cv.Optional(CONF_PIXEL_MODE): cv.enum(PIXEL_MODES), cv.Optional(CONF_DIMENSIONS): cv.Any( cv.dimensions, cv.Schema( @@ -150,6 +185,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean, } ), + cv.Optional(CONF_INIT_SEQUENCE): cv.ensure_list(map_sequence), } ) .extend(cv.polling_component_schema("1s")) @@ -167,6 +203,14 @@ async def to_code(config): await spi.register_spi_device(var, config) dc = await cg.gpio_pin_expression(config[CONF_DC_PIN]) cg.add(var.set_dc_pin(dc)) + if init_sequences := config.get(CONF_INIT_SEQUENCE): + sequence = [] + for seq in init_sequences: + sequence.extend(seq) + cg.add(var.add_init_sequence(sequence)) + + if pixel_mode := config.get(CONF_PIXEL_MODE): + cg.add(var.set_pixel_mode(pixel_mode)) if CONF_COLOR_ORDER in config: cg.add(var.set_color_order(COLOR_ORDERS[config[CONF_COLOR_ORDER]])) if CONF_TRANSFORM in config: diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index e292906a93..463e3dd851 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -34,7 +34,26 @@ void ILI9XXXDisplay::setup() { ESP_LOGD(TAG, "Setting up ILI9xxx"); this->setup_pins_(); - this->init_lcd_(); + this->init_lcd_(this->init_sequence_); + this->init_lcd_(this->extra_init_sequence_.data()); + switch (this->pixel_mode_) { + case PIXEL_MODE_16: + if (this->is_18bitdisplay_) { + this->command(ILI9XXX_PIXFMT); + this->data(0x55); + this->is_18bitdisplay_ = false; + } + break; + case PIXEL_MODE_18: + if (!this->is_18bitdisplay_) { + this->command(ILI9XXX_PIXFMT); + this->data(0x66); + this->is_18bitdisplay_ = true; + } + break; + default: + break; + } this->set_madctl(); this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF); @@ -203,7 +222,6 @@ void ILI9XXXDisplay::update() { } void ILI9XXXDisplay::display_() { - uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE]; // check if something was displayed if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) { return; @@ -231,6 +249,7 @@ void ILI9XXXDisplay::display_() { this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2); } else { ESP_LOGV(TAG, "Doing multiple write"); + uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE]; size_t rem = h * w; // remaining number of pixels to write set_addr_window_(this->x_low_, this->y_low_, this->x_high_, this->y_high_); size_t idx = 0; // index into transfer_buffer @@ -247,7 +266,7 @@ void ILI9XXXDisplay::display_() { display::ColorUtil::index8_to_color_palette888(this->buffer_[pos++], this->palette_)); break; default: // case BITS_16: - color_val = (buffer_[pos * 2] << 8) + buffer_[pos * 2 + 1]; + color_val = (this->buffer_[pos * 2] << 8) + this->buffer_[pos * 2 + 1]; pos++; break; } @@ -259,7 +278,7 @@ void ILI9XXXDisplay::display_() { put16_be(transfer_buffer + idx, color_val); idx += 2; } - if (idx == ILI9XXX_TRANSFER_BUFFER_SIZE) { + if (idx == sizeof(transfer_buffer)) { this->write_array(transfer_buffer, idx); idx = 0; App.feed_wdt(); @@ -293,20 +312,50 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons // if color mapping or software rotation is required, hand this off to the parent implementation. This will // do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not // configured the renderer well. - if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian || - this->is_18bitdisplay_) { + if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian) { return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad); } this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1); // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display. - if (x_offset == 0 && x_pad == 0 && y_offset == 0) { - // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother - this->write_array(ptr, w * h * 2); + auto stride = x_offset + w + x_pad; + if (!this->is_18bitdisplay_) { + if (x_offset == 0 && x_pad == 0 && y_offset == 0) { + // we could deal here with a non-zero y_offset, but if x_offset is zero, y_offset probably will be so don't bother + this->write_array(ptr, w * h * 2); + } else { + for (size_t y = 0; y != h; y++) { + this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2); + } + } } else { - auto stride = x_offset + w + x_pad; - for (size_t y = 0; y != h; y++) { - this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2); + // 18 bit mode + uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE * 4]; + ESP_LOGV(TAG, "Doing multiple write"); + size_t rem = h * w; // remaining number of pixels to write + size_t idx = 0; // index into transfer_buffer + size_t pixel = 0; // pixel number offset + ptr += (y_offset * stride + x_offset) * 2; + while (rem-- != 0) { + uint8_t hi_byte = *ptr++; + uint8_t lo_byte = *ptr++; + transfer_buffer[idx++] = hi_byte & 0xF8; // Blue + transfer_buffer[idx++] = ((hi_byte << 5) | (lo_byte) >> 5); // Green + transfer_buffer[idx++] = lo_byte << 3; // Red + if (idx == sizeof(transfer_buffer)) { + this->write_array(transfer_buffer, idx); + idx = 0; + App.feed_wdt(); + } + // end of line? Skip to the next. + if (++pixel == w) { + pixel = 0; + ptr += (x_pad + x_offset) * 2; + } + } + // flush any balance. + if (idx != 0) { + this->write_array(transfer_buffer, idx); } } this->end_data_(); @@ -356,10 +405,11 @@ void ILI9XXXDisplay::reset_() { } } -void ILI9XXXDisplay::init_lcd_() { +void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) { + if (addr == nullptr) + return; uint8_t cmd, x, num_args; - const uint8_t *addr = this->init_sequence_; - while ((cmd = *addr++) > 0) { + while ((cmd = *addr++) != 0) { x = *addr++; num_args = x & 0x7F; this->send_command(cmd, addr, num_args); diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index 11a90e142f..4446686e7b 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -17,6 +17,12 @@ enum ILI9XXXColorMode { BITS_16 = 0x10, }; +enum PixelMode { + PIXEL_MODE_UNSPECIFIED, + PIXEL_MODE_16, + PIXEL_MODE_18, +}; + class ILI9XXXDisplay : public display::DisplayBuffer, public spi::SPIDevice { @@ -52,6 +58,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, } } + void add_init_sequence(const std::vector &sequence) { this->extra_init_sequence_ = sequence; } void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } float get_setup_priority() const override; void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } @@ -73,6 +80,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, 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 set_pixel_mode(PixelMode mode) { this->pixel_mode_ = mode; } void update() override; @@ -99,11 +107,12 @@ class ILI9XXXDisplay : public display::DisplayBuffer, virtual void set_madctl(); void display_(); - void init_lcd_(); + void init_lcd_(const uint8_t *addr); void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2); void reset_(); uint8_t const *init_sequence_{}; + std::vector extra_init_sequence_; int16_t width_{0}; ///< Display width as modified by current rotation int16_t height_{0}; ///< Display height as modified by current rotation int16_t offset_x_{0}; @@ -112,7 +121,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, uint16_t y_low_{0}; uint16_t x_high_{0}; uint16_t y_high_{0}; - const uint8_t *palette_; + const uint8_t *palette_{}; ILI9XXXColorMode buffer_color_mode_{BITS_16}; @@ -133,6 +142,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, bool prossing_update_ = false; bool need_update_ = false; bool is_18bitdisplay_ = false; + PixelMode pixel_mode_{}; bool pre_invertcolors_ = false; display::ColorOrder color_order_{display::COLOR_ORDER_BGR}; bool swap_xy_{}; diff --git a/tests/components/ili9xxx/test.esp32-idf.yaml b/tests/components/ili9xxx/test.esp32-idf.yaml index 0d7bda8ac6..6da62f69d2 100644 --- a/tests/components/ili9xxx/test.esp32-idf.yaml +++ b/tests/components/ili9xxx/test.esp32-idf.yaml @@ -12,24 +12,12 @@ display: swap_xy: true mirror_x: true mirror_y: false - model: TFT 2.4 - color_palette: GRAYSCALE + model: custom cs_pin: 12 dc_pin: 13 reset_pin: 14 + init_sequence: + - [0xFF, 0x77, 0x01, 0x00, 0x00, 0x10] + lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - - platform: ili9xxx - dimensions: - width: 320 - height: 240 - offset_width: 20 - offset_height: 10 - model: TFT 2.4 - cs_pin: 25 - dc_pin: 26 - reset_pin: 27 - auto_clear_enabled: false - rotation: 90 - lambda: |- - it.fill(Color::WHITE);