ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
ili9xxx_display.cpp
Go to the documentation of this file.
1#include "ili9xxx_display.h"
3#include "esphome/core/hal.h"
5#include "esphome/core/log.h"
6
7#include <algorithm>
8#include <cstring>
9
10namespace esphome::ili9xxx {
11
12static const uint16_t SPI_SETUP_US = 100; // estimated fixed overhead in microseconds for an SPI write
13static const uint16_t SPI_MAX_BLOCK_SIZE = 4092; // Max size of continuous SPI transfer
14
15// store a 16 bit value in a buffer, big endian.
16static inline void put16_be(uint8_t *buf, uint16_t value) {
17 buf[0] = value >> 8;
18 buf[1] = value;
19}
20
22 // custom x/y transform and color order
23 uint8_t mad = this->color_order_ == display::COLOR_ORDER_BGR ? MADCTL_BGR : MADCTL_RGB;
24 if (this->swap_xy_)
25 mad |= MADCTL_MV;
26 if (this->mirror_x_)
27 mad |= MADCTL_MX;
28 if (this->mirror_y_)
29 mad |= MADCTL_MY;
30 this->command(ILI9XXX_MADCTL);
31 this->data(mad);
32 esph_log_d(TAG, "Wrote MADCTL 0x%02X", mad);
33}
34
36 this->setup_pins_();
37 this->init_lcd_(this->init_sequence_);
38 this->init_lcd_(this->extra_init_sequence_.data());
39 switch (this->pixel_mode_) {
40 case PIXEL_MODE_16:
41 if (this->is_18bitdisplay_) {
42 this->command(ILI9XXX_PIXFMT);
43 this->data(0x55);
44 this->is_18bitdisplay_ = false;
45 }
46 break;
47 case PIXEL_MODE_18:
48 if (!this->is_18bitdisplay_) {
49 this->command(ILI9XXX_PIXFMT);
50 this->data(0x66);
51 this->is_18bitdisplay_ = true;
52 }
53 break;
54 default:
55 break;
56 }
57
58 this->set_madctl();
59 this->command(this->pre_invertcolors_ ? ILI9XXX_INVON : ILI9XXX_INVOFF);
60 this->x_low_ = this->width_;
61 this->y_low_ = this->height_;
62 this->x_high_ = 0;
63 this->y_high_ = 0;
64}
65
67 if (this->buffer_color_mode_ == BITS_16) {
68 this->init_internal_(this->get_buffer_length_() * 2);
69 } else {
71 }
72 if (this->buffer_ == nullptr) {
73 this->mark_failed(LOG_STR("Failed to allocate the display buffer"));
74 }
75}
76
78 this->dc_pin_->setup(); // OUTPUT
79 this->dc_pin_->digital_write(false);
80 if (this->reset_pin_ != nullptr) {
81 this->reset_pin_->setup(); // OUTPUT
82 this->reset_pin_->digital_write(true);
83 }
84
85 this->spi_setup();
86
87 this->reset_();
88}
89
91 LOG_DISPLAY("", "ili9xxx", this);
92 ESP_LOGCONFIG(TAG,
93 " Width Offset: %u\n"
94 " Height Offset: %u",
95 this->offset_x_, this->offset_y_);
96 switch (this->buffer_color_mode_) {
97 case BITS_8_INDEXED:
98 ESP_LOGCONFIG(TAG, " Color mode: 8bit Indexed");
99 break;
100 case BITS_16:
101 ESP_LOGCONFIG(TAG, " Color mode: 16bit");
102 break;
103 default:
104 ESP_LOGCONFIG(TAG, " Color mode: 8bit 332 mode");
105 break;
106 }
107 if (this->is_18bitdisplay_) {
108 ESP_LOGCONFIG(TAG, " 18-Bit Mode: YES");
109 }
110 ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));
111
112 LOG_PIN(" Reset Pin: ", this->reset_pin_);
113 LOG_PIN(" CS Pin: ", this->cs_);
114 LOG_PIN(" DC Pin: ", this->dc_pin_);
115 LOG_PIN(" Busy Pin: ", this->busy_pin_);
116 ESP_LOGCONFIG(TAG,
117 " Color order: %s\n"
118 " Swap_xy: %s\n"
119 " Mirror_x: %s\n"
120 " Mirror_y: %s\n"
121 " Invert colors: %s",
122 this->color_order_ == display::COLOR_ORDER_BGR ? LOG_STR_LITERAL("BGR") : LOG_STR_LITERAL("RGB"),
123 YESNO(this->swap_xy_), YESNO(this->mirror_x_), YESNO(this->mirror_y_), YESNO(this->pre_invertcolors_));
124
125 LOG_UPDATE_INTERVAL(this);
126}
127
129
131 if (!this->check_buffer_())
132 return;
133
134 // If clipping is active, fall back to base implementation
135 if (this->get_clipping().is_set()) {
136 Display::fill(color);
137 return;
138 }
139
140 uint16_t new_color = 0;
141 this->x_low_ = 0;
142 this->y_low_ = 0;
143 this->x_high_ = this->get_width_internal() - 1;
144 this->y_high_ = this->get_height_internal() - 1;
145 switch (this->buffer_color_mode_) {
146 case BITS_8_INDEXED:
147 new_color = display::ColorUtil::color_to_index8_palette888(color, this->palette_);
148 break;
149 case BITS_16:
150 new_color = display::ColorUtil::color_to_565(color);
151 {
152 const uint32_t buffer_length_16_bits = this->get_buffer_length_() * 2;
153 if (((uint8_t) (new_color >> 8)) == ((uint8_t) new_color)) {
154 // Upper and lower is equal can use quicker memset operation. Takes ~20ms.
155 memset(this->buffer_, (uint8_t) new_color, buffer_length_16_bits);
156 } else {
157 for (uint32_t i = 0; i < buffer_length_16_bits; i = i + 2) {
158 this->buffer_[i] = (uint8_t) (new_color >> 8);
159 this->buffer_[i + 1] = (uint8_t) new_color;
160 }
161 }
162 }
163 return;
164 default:
165 new_color = display::ColorUtil::color_to_332(color);
166 break;
167 }
168 memset(this->buffer_, (uint8_t) new_color, this->get_buffer_length_());
169}
170
172 if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
173 return;
174 }
175 if (!this->check_buffer_())
176 return;
177 uint32_t pos = (y * width_) + x;
178 uint16_t new_color;
179 bool updated = false;
180 switch (this->buffer_color_mode_) {
181 case BITS_8_INDEXED:
182 new_color = display::ColorUtil::color_to_index8_palette888(color, this->palette_);
183 break;
184 case BITS_16:
185 pos = pos * 2;
186 new_color = display::ColorUtil::color_to_565(color);
187 if (this->buffer_[pos] != (uint8_t) (new_color >> 8)) {
188 this->buffer_[pos] = (uint8_t) (new_color >> 8);
189 updated = true;
190 }
191 pos = pos + 1;
192 new_color = new_color & 0xFF;
193 break;
194 default:
195 new_color = display::ColorUtil::color_to_332(color);
196 break;
197 }
198
199 if (this->buffer_[pos] != new_color) {
200 this->buffer_[pos] = new_color;
201 updated = true;
202 }
203 if (updated) {
204 // low and high watermark may speed up drawing from buffer
206 this->x_low_ = x;
208 this->y_low_ = y;
209 if (x > this->x_high_)
210 this->x_high_ = x;
211 if (y > this->y_high_)
212 this->y_high_ = y;
213 }
214}
215
217 if (this->prossing_update_) {
218 this->need_update_ = true;
219 return;
220 }
221 this->prossing_update_ = true;
222 do {
223 this->need_update_ = false;
224 this->do_update_();
225 } while (this->need_update_);
226 this->prossing_update_ = false;
227 this->display_();
228}
229
231 // buffer may be null if allocation failed
232 if (this->buffer_ == nullptr) {
233 return;
234 }
235 // check if something was displayed
236 if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
237 return;
238 }
239
240 // we will only update the changed rows to the display
241 size_t const w = this->x_high_ - this->x_low_ + 1;
242 size_t const h = this->y_high_ - this->y_low_ + 1;
243
244 size_t mhz = this->data_rate_ / 1000000;
245 // estimate time for a single write
246 size_t sw_time = this->width_ * h * 16 / mhz + this->width_ * h * 2 / SPI_MAX_BLOCK_SIZE * SPI_SETUP_US * 2;
247 // estimate time for multiple writes
248 size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US;
249 ESP_LOGV(TAG,
250 "Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
251 "height:%zu, mode=%d, 18bit=%d, sw_time=%zuus, mw_time=%zuus)",
252 this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_,
253 this->is_18bitdisplay_, sw_time, mw_time);
254 auto now = millis();
255 if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) {
256 // 16 bit mode maps directly to display format
257 ESP_LOGV(TAG, "Doing single write of %zu bytes", this->width_ * h * 2);
258 set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_);
259 this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
260 } else {
261 ESP_LOGV(TAG, "Doing multiple write");
262 uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
263 size_t rem = h * w; // remaining number of pixels to write
264 set_addr_window_(this->x_low_, this->y_low_, this->x_high_, this->y_high_);
265 size_t idx = 0; // index into transfer_buffer
266 size_t pixel = 0; // pixel number offset
267 size_t pos = this->y_low_ * this->width_ + this->x_low_;
268 while (rem-- != 0) {
269 uint16_t color_val;
270 switch (this->buffer_color_mode_) {
271 case BITS_8:
272 color_val = display::ColorUtil::color_to_565(display::ColorUtil::rgb332_to_color(this->buffer_[pos++]));
273 break;
274 case BITS_8_INDEXED:
275 color_val = display::ColorUtil::color_to_565(
276 display::ColorUtil::index8_to_color_palette888(this->buffer_[pos++], this->palette_));
277 break;
278 default: // case BITS_16:
279 color_val = (this->buffer_[pos * 2] << 8) + this->buffer_[pos * 2 + 1];
280 pos++;
281 break;
282 }
283 if (this->is_18bitdisplay_) {
284 transfer_buffer[idx++] = (uint8_t) ((color_val & 0xF800) >> 8); // Blue
285 transfer_buffer[idx++] = (uint8_t) ((color_val & 0x7E0) >> 3); // Green
286 transfer_buffer[idx++] = (uint8_t) (color_val << 3); // Red
287 } else {
288 put16_be(transfer_buffer + idx, color_val);
289 idx += 2;
290 }
291 if (idx == sizeof(transfer_buffer)) {
292 this->write_array(transfer_buffer, idx);
293 idx = 0;
294 App.feed_wdt();
295 }
296 // end of line? Skip to the next.
297 if (++pixel == w) {
298 pixel = 0;
299 pos += this->width_ - w;
300 }
301 }
302 // flush any balance.
303 if (idx != 0) {
304 this->write_array(transfer_buffer, idx);
305 }
306 }
307 this->end_data_();
308 ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now));
309 // invalidate watermarks
310 this->x_low_ = this->width_;
311 this->y_low_ = this->height_;
312 this->x_high_ = 0;
313 this->y_high_ = 0;
314}
315
316// Outside update() this bypasses the buffer and writes directly to the display. Inside it the block goes into the
317// buffer, or the flush at the end of update() would paint the old buffer contents over it.
318void ILI9XXXDisplay::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr,
319 display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset,
320 int x_pad) {
321 if (w <= 0 || h <= 0)
322 return;
323 // if color mapping or software rotation is required, hand this off to the parent implementation. This will
324 // do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not
325 // configured the renderer well.
327 this->is_clipping() || (this->prossing_update_ && this->buffer_color_mode_ != BITS_16)) {
329 return;
330 }
331 // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
332 auto stride = x_offset + w + x_pad;
333 if (this->prossing_update_) {
334 if (!this->check_buffer_())
335 return;
336 // The buffer holds big endian RGB565, the same layout as the block
337 const int first_col = std::max(0, -x_start), end_col = std::min(w, this->get_width_internal() - x_start);
338 const int first_row = std::max(0, -y_start), end_row = std::min(h, this->get_height_internal() - y_start);
339 if (first_col >= end_col || first_row >= end_row)
340 return;
341 for (int y = first_row; y != end_row; y++) {
342 memcpy(this->buffer_ + ((y_start + y) * this->width_ + x_start + first_col) * 2,
343 ptr + ((y_offset + y) * stride + x_offset + first_col) * 2, (end_col - first_col) * 2);
344 }
345 this->x_low_ = std::min<int>(this->x_low_, x_start + first_col);
346 this->y_low_ = std::min<int>(this->y_low_, y_start + first_row);
347 this->x_high_ = std::max<int>(this->x_high_, x_start + end_col - 1);
348 this->y_high_ = std::max<int>(this->y_high_, y_start + end_row - 1);
349 return;
350 }
351 this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
352 if (!this->is_18bitdisplay_) {
353 if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
354 // 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
355 this->write_array(ptr, w * h * 2);
356 } else {
357 for (size_t y = 0; y != static_cast<size_t>(h); y++) {
358 this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
359 }
360 }
361 } else {
362 // 18 bit mode
363 uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE * 4];
364 ESP_LOGV(TAG, "Doing multiple write");
365 size_t rem = h * w; // remaining number of pixels to write
366 size_t idx = 0; // index into transfer_buffer
367 size_t pixel = 0; // pixel number offset
368 ptr += (y_offset * stride + x_offset) * 2;
369 while (rem-- != 0) {
370 uint8_t hi_byte = *ptr++;
371 uint8_t lo_byte = *ptr++;
372 transfer_buffer[idx++] = hi_byte & 0xF8; // Blue
373 transfer_buffer[idx++] = ((hi_byte << 5) | (lo_byte) >> 5); // Green
374 transfer_buffer[idx++] = lo_byte << 3; // Red
375 if (idx == sizeof(transfer_buffer)) {
376 this->write_array(transfer_buffer, idx);
377 idx = 0;
378 App.feed_wdt();
379 }
380 // end of line? Skip to the next.
381 if (++pixel == static_cast<size_t>(w)) {
382 pixel = 0;
383 ptr += (x_pad + x_offset) * 2;
384 }
385 }
386 // flush any balance.
387 if (idx != 0) {
388 this->write_array(transfer_buffer, idx);
389 }
390 }
391 this->end_data_();
392}
393
394// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
395// values per bit is huge
397
398void ILI9XXXDisplay::command(uint8_t value) {
399 this->start_command_();
400 this->write_byte(value);
401 this->end_command_();
402}
403
404void ILI9XXXDisplay::data(uint8_t value) {
405 this->start_data_();
406 this->write_byte(value);
407 this->end_data_();
408}
409
410void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes) {
411 this->command(command_byte); // Send the command byte
412 this->start_data_();
413 this->write_array(data_bytes, num_data_bytes);
414 this->end_data_();
415}
416
418 this->dc_pin_->digital_write(false);
419 this->enable();
420}
422 this->dc_pin_->digital_write(true);
423 this->enable();
424}
425
428
430 if (this->reset_pin_ != nullptr) {
431 this->reset_pin_->digital_write(false);
432 delay(20);
433 this->reset_pin_->digital_write(true);
434 delay(20);
435 }
436}
437
438void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) {
439 if (addr == nullptr)
440 return;
441 uint8_t cmd, x, num_args;
442 while ((cmd = *addr++) != 0) {
443 x = *addr++;
444 if (x == ILI9XXX_DELAY_FLAG) {
445 cmd &= 0x7F;
446 ESP_LOGV(TAG, "Delay %dms", cmd);
447 delay(cmd);
448 } else {
449 num_args = x & 0x7F;
450 ESP_LOGV(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr);
451 this->send_command(cmd, addr, num_args);
452 addr += num_args;
453 if (x & 0x80) {
454 ESP_LOGV(TAG, "Delay 150ms");
455 delay(150); // NOLINT
456 }
457 }
458 }
459}
460
461// Tell the display controller where we want to draw pixels.
462void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
463 x1 += this->offset_x_;
464 x2 += this->offset_x_;
465 y1 += this->offset_y_;
466 y2 += this->offset_y_;
467 this->command(ILI9XXX_CASET);
468 this->data(x1 >> 8);
469 this->data(x1 & 0xFF);
470 this->data(x2 >> 8);
471 this->data(x2 & 0xFF);
472 this->command(ILI9XXX_PASET); // Page address set
473 this->data(y1 >> 8);
474 this->data(y1 & 0xFF);
475 this->data(y2 >> 8);
476 this->data(y2 & 0xFF);
477 this->command(ILI9XXX_RAMWR); // Write to RAM
478 this->start_data_();
479}
480
482 this->pre_invertcolors_ = invert;
483 if (is_ready()) {
484 this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF);
485 }
486}
487
490
491} // namespace esphome::ili9xxx
uint8_t h
Definition bl0906.h:2
void feed_wdt()
Feed the task watchdog.
void mark_failed()
Mark this component as failed.
bool is_ready() const
virtual void setup()=0
virtual void digital_write(bool value)=0
void init_internal_(uint32_t buffer_length)
int int int const uint8_t ColorOrder ColorBitness bitness
Definition display.h:392
bool is_clipping() const
Definition display.h:805
int int int const uint8_t ColorOrder ColorBitness bool int int y_offset
Definition display.h:392
int int int const uint8_t ColorOrder ColorBitness bool int int int x_pad
Definition display.h:392
int int int const uint8_t * ptr
Definition display.h:391
int int int const uint8_t ColorOrder ColorBitness bool big_endian
Definition display.h:392
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:826
int int int const uint8_t ColorOrder ColorBitness bool int x_offset
Definition display.h:392
DisplayRotation rotation_
Definition display.h:863
virtual void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad)
Draws a block for draw_pixels_at(); the parameters are the same.
Definition display.cpp:119
virtual void data(uint8_t value)
int16_t width_
Display width as modified by current rotation.
void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override
std::vector< uint8_t > extra_init_sequence_
float get_setup_priority() const override
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2)
void init_lcd_(const uint8_t *addr)
virtual void command(uint8_t value)
void draw_absolute_pixel_internal(int x, int y, Color color) override
void fill(Color color) override
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes)
int16_t height_
Display height as modified by current rotation.
uint32_t data_rate_
Definition spi.h:436
@ DISPLAY_ROTATION_0_DEGREES
Definition display.h:136
const size_t ILI9XXX_TRANSFER_BUFFER_SIZE
constexpr float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition component.h:43
value[LAYOUT.day_index()]
size_t size_t pos
Definition helpers.h:1255
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6