mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	[ili9xxx] Add 18bit mode selection and custom init sequence (#6745)
This commit is contained in:
		| @@ -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, <data>] | ||||
|     """ | ||||
|     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: | ||||
|   | ||||
| @@ -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,22 +312,52 @@ 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. | ||||
|   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 { | ||||
|     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); | ||||
|       } | ||||
|     } | ||||
|   } else { | ||||
|     // 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); | ||||
|   | ||||
| @@ -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<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, | ||||
|                                              spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_40MHZ> { | ||||
| @@ -52,6 +58,7 @@ class ILI9XXXDisplay : public display::DisplayBuffer, | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void add_init_sequence(const std::vector<uint8_t> &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<uint8_t> 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_{}; | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user