ESPHome 2025.5.0
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 ESP_LOGD(TAG, "Setting up ILI9xxx");
35
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();
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, " Width Offset: %u", this->offset_x_);
93 ESP_LOGCONFIG(TAG, " Height Offset: %u", 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, " Color order: %s", this->color_order_ == display::COLOR_ORDER_BGR ? "BGR" : "RGB");
115 ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_));
116 ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_));
117 ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_));
118 ESP_LOGCONFIG(TAG, " Invert colors: %s", YESNO(this->pre_invertcolors_));
119
120 if (this->is_failed()) {
121 ESP_LOGCONFIG(TAG, " => Failed to init Memory: YES!");
122 }
123 LOG_UPDATE_INTERVAL(this);
124}
125
127
129 if (!this->check_buffer_())
130 return;
131 uint16_t new_color = 0;
132 this->x_low_ = 0;
133 this->y_low_ = 0;
134 this->x_high_ = this->get_width_internal() - 1;
135 this->y_high_ = this->get_height_internal() - 1;
136 switch (this->buffer_color_mode_) {
137 case BITS_8_INDEXED:
139 break;
140 case BITS_16:
141 new_color = display::ColorUtil::color_to_565(color);
142 {
143 const uint32_t buffer_length_16_bits = this->get_buffer_length_() * 2;
144 if (((uint8_t) (new_color >> 8)) == ((uint8_t) new_color)) {
145 // Upper and lower is equal can use quicker memset operation. Takes ~20ms.
146 memset(this->buffer_, (uint8_t) new_color, buffer_length_16_bits);
147 } else {
148 for (uint32_t i = 0; i < buffer_length_16_bits; i = i + 2) {
149 this->buffer_[i] = (uint8_t) (new_color >> 8);
150 this->buffer_[i + 1] = (uint8_t) new_color;
151 }
152 }
153 }
154 return;
155 default:
157 break;
158 }
159 memset(this->buffer_, (uint8_t) new_color, this->get_buffer_length_());
160}
161
163 if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
164 return;
165 }
166 if (!this->check_buffer_())
167 return;
168 uint32_t pos = (y * width_) + x;
169 uint16_t new_color;
170 bool updated = false;
171 switch (this->buffer_color_mode_) {
172 case BITS_8_INDEXED:
174 break;
175 case BITS_16:
176 pos = pos * 2;
178 if (this->buffer_[pos] != (uint8_t) (new_color >> 8)) {
179 this->buffer_[pos] = (uint8_t) (new_color >> 8);
180 updated = true;
181 }
182 pos = pos + 1;
183 new_color = new_color & 0xFF;
184 break;
185 default:
187 break;
188 }
189
190 if (this->buffer_[pos] != new_color) {
191 this->buffer_[pos] = new_color;
192 updated = true;
193 }
194 if (updated) {
195 // low and high watermark may speed up drawing from buffer
197 this->x_low_ = x;
199 this->y_low_ = y;
200 if (x > this->x_high_)
201 this->x_high_ = x;
202 if (y > this->y_high_)
203 this->y_high_ = y;
204 }
205}
206
208 if (this->prossing_update_) {
209 this->need_update_ = true;
210 return;
211 }
212 this->prossing_update_ = true;
213 do {
214 this->need_update_ = false;
215 this->do_update_();
216 } while (this->need_update_);
217 this->prossing_update_ = false;
218 this->display_();
219}
220
222 // check if something was displayed
223 if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
224 return;
225 }
226
227 // we will only update the changed rows to the display
228 size_t const w = this->x_high_ - this->x_low_ + 1;
229 size_t const h = this->y_high_ - this->y_low_ + 1;
230
231 size_t mhz = this->data_rate_ / 1000000;
232 // estimate time for a single write
233 size_t sw_time = this->width_ * h * 16 / mhz + this->width_ * h * 2 / SPI_MAX_BLOCK_SIZE * SPI_SETUP_US * 2;
234 // estimate time for multiple writes
235 size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US;
236 ESP_LOGV(TAG,
237 "Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
238 "height:%zu, mode=%d, 18bit=%d, sw_time=%zuus, mw_time=%zuus)",
239 this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_,
240 this->is_18bitdisplay_, sw_time, mw_time);
241 auto now = millis();
242 if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) {
243 // 16 bit mode maps directly to display format
244 ESP_LOGV(TAG, "Doing single write of %zu bytes", this->width_ * h * 2);
245 set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_);
246 this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
247 } else {
248 ESP_LOGV(TAG, "Doing multiple write");
249 uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
250 size_t rem = h * w; // remaining number of pixels to write
251 set_addr_window_(this->x_low_, this->y_low_, this->x_high_, this->y_high_);
252 size_t idx = 0; // index into transfer_buffer
253 size_t pixel = 0; // pixel number offset
254 size_t pos = this->y_low_ * this->width_ + this->x_low_;
255 while (rem-- != 0) {
256 uint16_t color_val;
257 switch (this->buffer_color_mode_) {
258 case BITS_8:
260 break;
261 case BITS_8_INDEXED:
264 break;
265 default: // case BITS_16:
266 color_val = (this->buffer_[pos * 2] << 8) + this->buffer_[pos * 2 + 1];
267 pos++;
268 break;
269 }
270 if (this->is_18bitdisplay_) {
271 transfer_buffer[idx++] = (uint8_t) ((color_val & 0xF800) >> 8); // Blue
272 transfer_buffer[idx++] = (uint8_t) ((color_val & 0x7E0) >> 3); // Green
273 transfer_buffer[idx++] = (uint8_t) (color_val << 3); // Red
274 } else {
275 put16_be(transfer_buffer + idx, color_val);
276 idx += 2;
277 }
278 if (idx == sizeof(transfer_buffer)) {
279 this->write_array(transfer_buffer, idx);
280 idx = 0;
281 App.feed_wdt();
282 }
283 // end of line? Skip to the next.
284 if (++pixel == w) {
285 pixel = 0;
286 pos += this->width_ - w;
287 }
288 }
289 // flush any balance.
290 if (idx != 0) {
291 this->write_array(transfer_buffer, idx);
292 }
293 }
294 this->end_data_();
295 ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now));
296 // invalidate watermarks
297 this->x_low_ = this->width_;
298 this->y_low_ = this->height_;
299 this->x_high_ = 0;
300 this->y_high_ = 0;
301}
302
303// note that this bypasses the buffer and writes directly to the display.
304void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr,
305 display::ColorOrder order, display::ColorBitness bitness, bool big_endian,
306 int x_offset, int y_offset, int x_pad) {
307 if (w <= 0 || h <= 0)
308 return;
309 // if color mapping or software rotation is required, hand this off to the parent implementation. This will
310 // do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not
311 // configured the renderer well.
312 if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian) {
313 display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
314 x_pad);
315 return;
316 }
317 this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
318 // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
319 auto stride = x_offset + w + x_pad;
320 if (!this->is_18bitdisplay_) {
321 if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
322 // 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
323 this->write_array(ptr, w * h * 2);
324 } else {
325 for (size_t y = 0; y != h; y++) {
326 this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
327 }
328 }
329 } else {
330 // 18 bit mode
331 uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE * 4];
332 ESP_LOGV(TAG, "Doing multiple write");
333 size_t rem = h * w; // remaining number of pixels to write
334 size_t idx = 0; // index into transfer_buffer
335 size_t pixel = 0; // pixel number offset
336 ptr += (y_offset * stride + x_offset) * 2;
337 while (rem-- != 0) {
338 uint8_t hi_byte = *ptr++;
339 uint8_t lo_byte = *ptr++;
340 transfer_buffer[idx++] = hi_byte & 0xF8; // Blue
341 transfer_buffer[idx++] = ((hi_byte << 5) | (lo_byte) >> 5); // Green
342 transfer_buffer[idx++] = lo_byte << 3; // Red
343 if (idx == sizeof(transfer_buffer)) {
344 this->write_array(transfer_buffer, idx);
345 idx = 0;
346 App.feed_wdt();
347 }
348 // end of line? Skip to the next.
349 if (++pixel == w) {
350 pixel = 0;
351 ptr += (x_pad + x_offset) * 2;
352 }
353 }
354 // flush any balance.
355 if (idx != 0) {
356 this->write_array(transfer_buffer, idx);
357 }
358 }
359 this->end_data_();
360}
361
362// should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
363// values per bit is huge
365
366void ILI9XXXDisplay::command(uint8_t value) {
367 this->start_command_();
368 this->write_byte(value);
369 this->end_command_();
370}
371
372void ILI9XXXDisplay::data(uint8_t value) {
373 this->start_data_();
374 this->write_byte(value);
375 this->end_data_();
376}
377
378void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes) {
379 this->command(command_byte); // Send the command byte
380 this->start_data_();
381 this->write_array(data_bytes, num_data_bytes);
382 this->end_data_();
383}
384
386 this->dc_pin_->digital_write(false);
387 this->enable();
388}
390 this->dc_pin_->digital_write(true);
391 this->enable();
392}
393
396
398 if (this->reset_pin_ != nullptr) {
399 this->reset_pin_->digital_write(false);
400 delay(20);
401 this->reset_pin_->digital_write(true);
402 delay(20);
403 }
404}
405
406void ILI9XXXDisplay::init_lcd_(const uint8_t *addr) {
407 if (addr == nullptr)
408 return;
409 uint8_t cmd, x, num_args;
410 while ((cmd = *addr++) != 0) {
411 x = *addr++;
412 if (x == ILI9XXX_DELAY_FLAG) {
413 cmd &= 0x7F;
414 ESP_LOGV(TAG, "Delay %dms", cmd);
415 delay(cmd);
416 } else {
417 num_args = x & 0x7F;
418 ESP_LOGV(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr);
419 this->send_command(cmd, addr, num_args);
420 addr += num_args;
421 if (x & 0x80) {
422 ESP_LOGV(TAG, "Delay 150ms");
423 delay(150); // NOLINT
424 }
425 }
426 }
427}
428
429// Tell the display controller where we want to draw pixels.
430void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
431 x1 += this->offset_x_;
432 x2 += this->offset_x_;
433 y1 += this->offset_y_;
434 y2 += this->offset_y_;
435 this->command(ILI9XXX_CASET);
436 this->data(x1 >> 8);
437 this->data(x1 & 0xFF);
438 this->data(x2 >> 8);
439 this->data(x2 & 0xFF);
440 this->command(ILI9XXX_PASET); // Page address set
441 this->data(y1 >> 8);
442 this->data(y1 & 0xFF);
443 this->data(y2 >> 8);
444 this->data(y2 & 0xFF);
445 this->command(ILI9XXX_RAMWR); // Write to RAM
446 this->start_data_();
447}
448
450 this->pre_invertcolors_ = invert;
451 if (is_ready()) {
452 this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF);
453 }
454}
455
458
459} // namespace ili9xxx
460} // namespace esphome
uint8_t h
Definition bl0906.h:2
void feed_wdt(uint32_t time=0)
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
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)
DisplayRotation rotation_
Definition display.h:680
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:406
@ DISPLAY_ROTATION_0_DEGREES
Definition display.h:135
const size_t ILI9XXX_TRANSFER_BUFFER_SIZE
const float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition component.cpp:18
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27
Application App
Global storage of Application pointer - only one Application can exist.
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6