ESPHome  2024.7.2
ili9xxx_display.cpp
Go to the documentation of this file.
1 #include "ili9xxx_display.h"
3 #include "esphome/core/hal.h"
4 #include "esphome/core/helpers.h"
5 #include "esphome/core/log.h"
6 
7 namespace esphome {
8 namespace ili9xxx {
9 
10 static const uint16_t SPI_SETUP_US = 100; // estimated fixed overhead in microseconds for an SPI write
11 static 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.
14 static 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  if (this->buffer_ != nullptr) {
70  return;
71  }
72  this->buffer_color_mode_ = BITS_8;
73  }
74  this->init_internal_(this->get_buffer_length_());
75  if (this->buffer_ == nullptr) {
76  this->mark_failed();
77  }
78 }
79 
81  this->dc_pin_->setup(); // OUTPUT
82  this->dc_pin_->digital_write(false);
83  if (this->reset_pin_ != nullptr) {
84  this->reset_pin_->setup(); // OUTPUT
85  this->reset_pin_->digital_write(true);
86  }
87 
88  this->spi_setup();
89 
90  this->reset_();
91 }
92 
94  LOG_DISPLAY("", "ili9xxx", this);
95  ESP_LOGCONFIG(TAG, " Width Offset: %u", this->offset_x_);
96  ESP_LOGCONFIG(TAG, " Height Offset: %u", this->offset_y_);
97  switch (this->buffer_color_mode_) {
98  case BITS_8_INDEXED:
99  ESP_LOGCONFIG(TAG, " Color mode: 8bit Indexed");
100  break;
101  case BITS_16:
102  ESP_LOGCONFIG(TAG, " Color mode: 16bit");
103  break;
104  default:
105  ESP_LOGCONFIG(TAG, " Color mode: 8bit 332 mode");
106  break;
107  }
108  if (this->is_18bitdisplay_) {
109  ESP_LOGCONFIG(TAG, " 18-Bit Mode: YES");
110  }
111  ESP_LOGCONFIG(TAG, " Data rate: %dMHz", (unsigned) (this->data_rate_ / 1000000));
112 
113  LOG_PIN(" Reset Pin: ", this->reset_pin_);
114  LOG_PIN(" CS Pin: ", this->cs_);
115  LOG_PIN(" DC Pin: ", this->dc_pin_);
116  LOG_PIN(" Busy Pin: ", this->busy_pin_);
117  ESP_LOGCONFIG(TAG, " Color order: %s", this->color_order_ == display::COLOR_ORDER_BGR ? "BGR" : "RGB");
118  ESP_LOGCONFIG(TAG, " Swap_xy: %s", YESNO(this->swap_xy_));
119  ESP_LOGCONFIG(TAG, " Mirror_x: %s", YESNO(this->mirror_x_));
120  ESP_LOGCONFIG(TAG, " Mirror_y: %s", YESNO(this->mirror_y_));
121 
122  if (this->is_failed()) {
123  ESP_LOGCONFIG(TAG, " => Failed to init Memory: YES!");
124  }
125  LOG_UPDATE_INTERVAL(this);
126 }
127 
129 
131  if (!this->check_buffer_())
132  return;
133  uint16_t new_color = 0;
134  this->x_low_ = 0;
135  this->y_low_ = 0;
136  this->x_high_ = this->get_width_internal() - 1;
137  this->y_high_ = this->get_height_internal() - 1;
138  switch (this->buffer_color_mode_) {
139  case BITS_8_INDEXED:
141  break;
142  case BITS_16:
143  new_color = display::ColorUtil::color_to_565(color);
144  {
145  const uint32_t buffer_length_16_bits = this->get_buffer_length_() * 2;
146  if (((uint8_t) (new_color >> 8)) == ((uint8_t) new_color)) {
147  // Upper and lower is equal can use quicker memset operation. Takes ~20ms.
148  memset(this->buffer_, (uint8_t) new_color, buffer_length_16_bits);
149  } else {
150  for (uint32_t i = 0; i < buffer_length_16_bits; i = i + 2) {
151  this->buffer_[i] = (uint8_t) (new_color >> 8);
152  this->buffer_[i + 1] = (uint8_t) new_color;
153  }
154  }
155  }
156  return;
157  break;
158  default:
160  break;
161  }
162  memset(this->buffer_, (uint8_t) new_color, this->get_buffer_length_());
163 }
164 
166  if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
167  return;
168  }
169  if (!this->check_buffer_())
170  return;
171  uint32_t pos = (y * width_) + x;
172  uint16_t new_color;
173  bool updated = false;
174  switch (this->buffer_color_mode_) {
175  case BITS_8_INDEXED:
177  break;
178  case BITS_16:
179  pos = pos * 2;
181  if (this->buffer_[pos] != (uint8_t) (new_color >> 8)) {
182  this->buffer_[pos] = (uint8_t) (new_color >> 8);
183  updated = true;
184  }
185  pos = pos + 1;
186  new_color = new_color & 0xFF;
187  break;
188  default:
190  break;
191  }
192 
193  if (this->buffer_[pos] != new_color) {
194  this->buffer_[pos] = new_color;
195  updated = true;
196  }
197  if (updated) {
198  // low and high watermark may speed up drawing from buffer
199  if (x < this->x_low_)
200  this->x_low_ = x;
201  if (y < this->y_low_)
202  this->y_low_ = y;
203  if (x > this->x_high_)
204  this->x_high_ = x;
205  if (y > this->y_high_)
206  this->y_high_ = y;
207  }
208 }
209 
211  if (this->prossing_update_) {
212  this->need_update_ = true;
213  return;
214  }
215  this->prossing_update_ = true;
216  do {
217  this->need_update_ = false;
218  this->do_update_();
219  } while (this->need_update_);
220  this->prossing_update_ = false;
221  this->display_();
222 }
223 
225  // check if something was displayed
226  if ((this->x_high_ < this->x_low_) || (this->y_high_ < this->y_low_)) {
227  return;
228  }
229 
230  // we will only update the changed rows to the display
231  size_t const w = this->x_high_ - this->x_low_ + 1;
232  size_t const h = this->y_high_ - this->y_low_ + 1;
233 
234  size_t mhz = this->data_rate_ / 1000000;
235  // estimate time for a single write
236  size_t sw_time = this->width_ * h * 16 / mhz + this->width_ * h * 2 / SPI_MAX_BLOCK_SIZE * SPI_SETUP_US * 2;
237  // estimate time for multiple writes
238  size_t mw_time = (w * h * 16) / mhz + w * h * 2 / ILI9XXX_TRANSFER_BUFFER_SIZE * SPI_SETUP_US;
239  ESP_LOGV(TAG,
240  "Start display(xlow:%d, ylow:%d, xhigh:%d, yhigh:%d, width:%d, "
241  "height:%zu, mode=%d, 18bit=%d, sw_time=%zuus, mw_time=%zuus)",
242  this->x_low_, this->y_low_, this->x_high_, this->y_high_, w, h, this->buffer_color_mode_,
243  this->is_18bitdisplay_, sw_time, mw_time);
244  auto now = millis();
245  if (this->buffer_color_mode_ == BITS_16 && !this->is_18bitdisplay_ && sw_time < mw_time) {
246  // 16 bit mode maps directly to display format
247  ESP_LOGV(TAG, "Doing single write of %zu bytes", this->width_ * h * 2);
248  set_addr_window_(0, this->y_low_, this->width_ - 1, this->y_high_);
249  this->write_array(this->buffer_ + this->y_low_ * this->width_ * 2, h * this->width_ * 2);
250  } else {
251  ESP_LOGV(TAG, "Doing multiple write");
252  uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE];
253  size_t rem = h * w; // remaining number of pixels to write
254  set_addr_window_(this->x_low_, this->y_low_, this->x_high_, this->y_high_);
255  size_t idx = 0; // index into transfer_buffer
256  size_t pixel = 0; // pixel number offset
257  size_t pos = this->y_low_ * this->width_ + this->x_low_;
258  while (rem-- != 0) {
259  uint16_t color_val;
260  switch (this->buffer_color_mode_) {
261  case BITS_8:
263  break;
264  case BITS_8_INDEXED:
267  break;
268  default: // case BITS_16:
269  color_val = (this->buffer_[pos * 2] << 8) + this->buffer_[pos * 2 + 1];
270  pos++;
271  break;
272  }
273  if (this->is_18bitdisplay_) {
274  transfer_buffer[idx++] = (uint8_t) ((color_val & 0xF800) >> 8); // Blue
275  transfer_buffer[idx++] = (uint8_t) ((color_val & 0x7E0) >> 3); // Green
276  transfer_buffer[idx++] = (uint8_t) (color_val << 3); // Red
277  } else {
278  put16_be(transfer_buffer + idx, color_val);
279  idx += 2;
280  }
281  if (idx == sizeof(transfer_buffer)) {
282  this->write_array(transfer_buffer, idx);
283  idx = 0;
284  App.feed_wdt();
285  }
286  // end of line? Skip to the next.
287  if (++pixel == w) {
288  pixel = 0;
289  pos += this->width_ - w;
290  }
291  }
292  // flush any balance.
293  if (idx != 0) {
294  this->write_array(transfer_buffer, idx);
295  }
296  }
297  this->end_data_();
298  ESP_LOGV(TAG, "Data write took %dms", (unsigned) (millis() - now));
299  // invalidate watermarks
300  this->x_low_ = this->width_;
301  this->y_low_ = this->height_;
302  this->x_high_ = 0;
303  this->y_high_ = 0;
304 }
305 
306 // note that this bypasses the buffer and writes directly to the display.
307 void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr,
308  display::ColorOrder order, display::ColorBitness bitness, bool big_endian,
309  int x_offset, int y_offset, int x_pad) {
310  if (w <= 0 || h <= 0)
311  return;
312  // if color mapping or software rotation is required, hand this off to the parent implementation. This will
313  // do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not
314  // configured the renderer well.
315  if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian) {
316  return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
317  x_pad);
318  }
319  this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
320  // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
321  auto stride = x_offset + w + x_pad;
322  if (!this->is_18bitdisplay_) {
323  if (x_offset == 0 && x_pad == 0 && y_offset == 0) {
324  // 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
325  this->write_array(ptr, w * h * 2);
326  } else {
327  for (size_t y = 0; y != h; y++) {
328  this->write_array(ptr + (y + y_offset) * stride + x_offset, w * 2);
329  }
330  }
331  } else {
332  // 18 bit mode
333  uint8_t transfer_buffer[ILI9XXX_TRANSFER_BUFFER_SIZE * 4];
334  ESP_LOGV(TAG, "Doing multiple write");
335  size_t rem = h * w; // remaining number of pixels to write
336  size_t idx = 0; // index into transfer_buffer
337  size_t pixel = 0; // pixel number offset
338  ptr += (y_offset * stride + x_offset) * 2;
339  while (rem-- != 0) {
340  uint8_t hi_byte = *ptr++;
341  uint8_t lo_byte = *ptr++;
342  transfer_buffer[idx++] = hi_byte & 0xF8; // Blue
343  transfer_buffer[idx++] = ((hi_byte << 5) | (lo_byte) >> 5); // Green
344  transfer_buffer[idx++] = lo_byte << 3; // Red
345  if (idx == sizeof(transfer_buffer)) {
346  this->write_array(transfer_buffer, idx);
347  idx = 0;
348  App.feed_wdt();
349  }
350  // end of line? Skip to the next.
351  if (++pixel == w) {
352  pixel = 0;
353  ptr += (x_pad + x_offset) * 2;
354  }
355  }
356  // flush any balance.
357  if (idx != 0) {
358  this->write_array(transfer_buffer, idx);
359  }
360  }
361  this->end_data_();
362 }
363 
364 // should return the total size: return this->get_width_internal() * this->get_height_internal() * 2 // 16bit color
365 // values per bit is huge
367 
368 void ILI9XXXDisplay::command(uint8_t value) {
369  this->start_command_();
370  this->write_byte(value);
371  this->end_command_();
372 }
373 
374 void ILI9XXXDisplay::data(uint8_t value) {
375  this->start_data_();
376  this->write_byte(value);
377  this->end_data_();
378 }
379 
380 void ILI9XXXDisplay::send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes) {
381  this->command(command_byte); // Send the command byte
382  this->start_data_();
383  this->write_array(data_bytes, num_data_bytes);
384  this->end_data_();
385 }
386 
388  this->dc_pin_->digital_write(false);
389  this->enable();
390 }
392  this->dc_pin_->digital_write(true);
393  this->enable();
394 }
395 
398 
400  if (this->reset_pin_ != nullptr) {
401  this->reset_pin_->digital_write(false);
402  delay(20);
403  this->reset_pin_->digital_write(true);
404  delay(20);
405  }
406 }
407 
408 void ILI9XXXDisplay::init_lcd(const uint8_t *addr) {
409  if (addr == nullptr)
410  return;
411  uint8_t cmd, x, num_args;
412  while ((cmd = *addr++) != 0) {
413  x = *addr++;
414  if (cmd == ILI9XXX_DELAY) {
415  ESP_LOGD(TAG, "Delay %dms", x);
416  delay(x);
417  } else {
418  num_args = x & 0x7F;
419  ESP_LOGD(TAG, "Command %02X, length %d, bits %02X", cmd, num_args, *addr);
420  this->send_command(cmd, addr, num_args);
421  addr += num_args;
422  if (x & 0x80) {
423  ESP_LOGD(TAG, "Delay 150ms");
424  delay(150); // NOLINT
425  }
426  }
427  }
428 }
429 
430 void ILI9XXXGC9A01A::init_lcd(const uint8_t *addr) {
431  if (addr == nullptr)
432  return;
433  uint8_t cmd, x, num_args;
434  while ((cmd = *addr++) != 0) {
435  x = *addr++;
436  num_args = x & 0x7F;
437  this->send_command(cmd, addr, num_args);
438  addr += num_args;
439  if (x & 0x80)
440  delay(150); // NOLINT
441  }
442 }
443 
444 // Tell the display controller where we want to draw pixels.
445 void ILI9XXXDisplay::set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
446  x1 += this->offset_x_;
447  x2 += this->offset_x_;
448  y1 += this->offset_y_;
449  y2 += this->offset_y_;
450  this->command(ILI9XXX_CASET);
451  this->data(x1 >> 8);
452  this->data(x1 & 0xFF);
453  this->data(x2 >> 8);
454  this->data(x2 & 0xFF);
455  this->command(ILI9XXX_PASET); // Page address set
456  this->data(y1 >> 8);
457  this->data(y1 & 0xFF);
458  this->data(y2 >> 8);
459  this->data(y2 & 0xFF);
460  this->command(ILI9XXX_RAMWR); // Write to RAM
461  this->start_data_();
462 }
463 
464 void ILI9XXXDisplay::invert_colors(bool invert) {
465  this->pre_invertcolors_ = invert;
466  if (is_ready()) {
467  this->command(invert ? ILI9XXX_INVON : ILI9XXX_INVOFF);
468  }
469 }
470 
473 
474 } // namespace ili9xxx
475 } // namespace esphome
void send_command(uint8_t command_byte, const uint8_t *data_bytes, uint8_t num_data_bytes)
virtual void digital_write(bool value)=0
static uint16_t color_to_565(Color color, ColorOrder color_order=ColorOrder::COLOR_ORDER_RGB)
void set_addr_window_(uint16_t x, uint16_t y, uint16_t x2, uint16_t y2)
bool is_failed() const
Definition: component.cpp:143
uint16_t x
Definition: tt21100.cpp:17
GPIOPin * cs_
Definition: spi.h:395
virtual void setup()=0
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
uint16_t y
Definition: tt21100.cpp:18
int16_t width_
Display width as modified by current rotation.
bool is_ready() const
Definition: component.cpp:144
void init_internal_(uint32_t buffer_length)
void draw_absolute_pixel_internal(int x, int y, Color color) override
int16_t height_
Display height as modified by current rotation.
static uint8_t color_to_332(Color color, ColorOrder color_order=ColorOrder::COLOR_ORDER_RGB)
float get_setup_priority() const override
static Color index8_to_color_palette888(uint8_t index, const uint8_t *palette)
const size_t ILI9XXX_TRANSFER_BUFFER_SIZE
Application App
Global storage of Application pointer - only one Application can exist.
virtual void data(uint8_t value)
virtual void init_lcd(const uint8_t *addr)
uint32_t data_rate_
Definition: spi.h:393
DisplayRotation rotation_
Definition: display.h:658
const float HARDWARE
For components that deal with hardware and are very important like GPIO switch.
Definition: component.cpp:18
static uint8_t color_to_index8_palette888(Color color, const uint8_t *palette)
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
uint8_t h
Definition: bl0939.h:21
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
void init_lcd(const uint8_t *addr) override
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
void fill(Color color) override
static Color rgb332_to_color(uint8_t rgb332_color)
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&#39;s buffer...
Definition: display.cpp:54
stm32_cmd_t * cmd
Definition: stm32flash.h:96
virtual void command(uint8_t value)
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26