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