ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
mipi_spi.h
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cstring>
5#include <utility>
6
10#include "esphome/core/hal.h"
12#include "esphome/core/log.h"
13
14namespace esphome::mipi_spi {
15
16ESPHOME_LOG_TAG(TAG, "display.mipi_spi");
17
18// Maximum bytes to log for commands (truncated if larger)
19static constexpr size_t MIPI_SPI_MAX_CMD_LOG_BYTES = 64;
20
21// Command codes for MIPI SPI displays. Not all currently used, kept here for reference.
22static constexpr uint8_t SW_RESET_CMD = 0x01;
23static constexpr uint8_t SLEEP_OUT = 0x11;
24static constexpr uint8_t NORON = 0x13;
25static constexpr uint8_t INVERT_OFF = 0x20;
26static constexpr uint8_t INVERT_ON = 0x21;
27static constexpr uint8_t ALL_ON = 0x23;
28static constexpr uint8_t WRAM = 0x24;
29static constexpr uint8_t MIPI = 0x26;
30static constexpr uint8_t DISPLAY_ON = 0x29;
31static constexpr uint8_t RASET = 0x2B;
32static constexpr uint8_t CASET = 0x2A;
33static constexpr uint8_t WDATA = 0x2C;
34static constexpr uint8_t TEON = 0x35;
35static constexpr uint8_t MADCTL_CMD = 0x36;
36static constexpr uint8_t PIXFMT = 0x3A;
37static constexpr uint8_t BRIGHTNESS = 0x51;
38static constexpr uint8_t SWIRE1 = 0x5A;
39static constexpr uint8_t SWIRE2 = 0x5B;
40static constexpr uint8_t PAGESEL = 0xFE;
41
42static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top
43static constexpr uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left
44static constexpr uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes
45static constexpr uint8_t MADCTL_RGB = 0x00; // Bit 3 Red-Green-Blue pixel order
46static constexpr uint8_t MADCTL_BGR = 0x08; // Bit 3 Blue-Green-Red pixel order
47static constexpr uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horizontally
48static constexpr uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically
49static constexpr uint16_t MADCTL_FLIP_FLAG = 0x100; // controller uses axis flip bits
50
51static constexpr uint8_t DELAY_FLAG = 0xFF;
52// store a 16 bit value in a buffer, big endian.
53static inline void put16_be(uint8_t *buf, uint16_t value) {
54 buf[0] = value >> 8;
55 buf[1] = value;
56}
57
58// Buffer mode, conveniently also the number of bytes in a pixel
64
65enum BusType {
69 BUS_TYPE_SINGLE_16 = 16, // Single bit bus, but 16 bits per transfer
70};
71
72// Helper function for dump_config - defined in mipi_spi.cpp to allow use of LOG_PIN macro
73void internal_dump_config(const char *model, int width, int height, int offset_width, int offset_height, uint8_t madctl,
74 bool invert_colors, int display_bits, bool is_big_endian, const optional<uint8_t> &brightness,
75 GPIOPin *cs, GPIOPin *reset, GPIOPin *dc, int spi_mode, uint32_t data_rate, int bus_width,
76 bool has_hardware_rotation);
77
78// Lets a light set the display brightness without knowing the display's template parameters.
80 public:
81 virtual void set_brightness(uint8_t brightness) = 0;
82};
83
102template<typename BUFFERTYPE, PixelMode BUFFERPIXEL, bool IS_BIG_ENDIAN, PixelMode DISPLAYPIXEL, BusType BUS_TYPE,
103 int WIDTH, int HEIGHT, int OFFSET_WIDTH, int OFFSET_HEIGHT, int PAD_WIDTH, int PAD_HEIGHT, uint16_t MADCTL,
104 bool HAS_HARDWARE_ROTATION>
106 public MipiSpiBrightness,
107 public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
108 spi::DATA_RATE_1MHZ> {
109 public:
110 MipiSpi() = default;
111 void update() override { this->stop_poller(); }
112 void draw_pixel_at(int x, int y, Color color) override {}
113 void set_model(const char *model) { this->model_ = model; }
114 void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
115 void set_enable_pins(std::vector<GPIOPin *> enable_pins) { this->enable_pins_ = std::move(enable_pins); }
116 void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; }
117 void set_invert_colors(bool invert_colors) {
118 this->invert_colors_ = invert_colors;
119 this->reset_params_();
120 }
121 void set_brightness(uint8_t brightness) override {
122 this->brightness_ = brightness;
123 // Before setup the stored value is applied by reset_params_()
124 if (this->is_ready())
125 this->write_command_(BRIGHTNESS, brightness);
126 }
127 void set_rotation(display::DisplayRotation rotation) override {
128 this->rotation_ = rotation;
129 if constexpr (HAS_HARDWARE_ROTATION) {
130 this->reset_params_();
131 }
132 }
134
135 int get_width() override { return display::rotation_swaps_axes(this->rotation_) ? HEIGHT : WIDTH; }
136 int get_height() override { return display::rotation_swaps_axes(this->rotation_) ? WIDTH : HEIGHT; }
137
138 void set_init_sequence(const uint8_t *sequence, size_t len) {
139 this->init_sequence_ = sequence;
140 this->init_sequence_len_ = len;
141 }
142
143 // reset the display, and write the init sequence
144 void setup() override {
145 this->spi_setup();
146 if (this->dc_pin_ != nullptr) {
147 this->dc_pin_->setup();
148 this->dc_pin_->digital_write(false);
149 }
150 for (auto *pin : this->enable_pins_) {
151 pin->setup();
152 pin->digital_write(true);
153 }
154 if (this->reset_pin_ != nullptr) {
155 this->reset_pin_->setup();
156 this->reset_pin_->digital_write(true);
157 delay(5);
158 this->reset_pin_->digital_write(false);
159 delay(5);
160 this->reset_pin_->digital_write(true);
161 // required delay after reset is already in the init sequence, don't duplicate
162 }
163
164 // need to know when the display is ready for SLPOUT command - will be 120ms after reset
165 auto when = millis() + 120;
166 size_t index = 0;
167 const uint8_t *seq = this->init_sequence_;
168 const size_t len = this->init_sequence_len_;
169 while (index != len) {
170 if (len - index < 2) {
171 esph_log_e(TAG, "Malformed init sequence");
172 this->mark_failed();
173 return;
174 }
175 uint8_t cmd = progmem_read_byte(seq + index++);
176 uint8_t x = progmem_read_byte(seq + index++);
177 if (x == DELAY_FLAG) {
178 if (cmd == 0) {
179 cmd = clamp_at_least((int) (when - millis()), 0);
180 }
181 esph_log_d(TAG, "Delay %dms", cmd);
182 delay(cmd);
183 } else {
184 uint8_t num_args = x & 0x7F;
185 if (len - index < num_args) {
186 esph_log_e(TAG, "Malformed init sequence");
187 this->mark_failed();
188 return;
189 }
190 // The sequence is in flash, which SPI DMA (and ESP8266 byte loads) cannot read
191 uint8_t args[0x80];
192 progmem_memcpy(args, seq + index, num_args);
193 this->write_command_(cmd, args, num_args);
194 index += num_args;
195 }
196 }
197 this->reset_params_();
198 // Marks init as done, so later commands log at verbose level instead of debug
199 this->init_sequence_len_ = 0;
200 }
201
202 void dump_config() override {
203 internal_dump_config(this->model_, this->get_width(), this->get_height(), this->get_offset_width_(),
204 this->get_offset_height_(), (uint8_t) MADCTL, this->invert_colors_, DISPLAYPIXEL * 8,
205 IS_BIG_ENDIAN, this->brightness_, this->cs_, this->reset_pin_, this->dc_pin_, this->mode_,
206 this->data_rate_, BUS_TYPE, HAS_HARDWARE_ROTATION);
207 }
208
209 protected:
210 // Drawing operations
211
212 void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness,
213 bool big_endian, int x_offset, int y_offset, int x_pad) override {
214 if (this->is_failed())
215 return;
216 if (w <= 0 || h <= 0)
217 return;
218 if (!this->is_native_block_(bitness, big_endian)) {
220 return;
221 }
222 this->write_to_display_(x_start, y_start, w, h, reinterpret_cast<const BUFFERTYPE *>(ptr), x_offset, y_offset,
223 x_pad);
224 }
225
226 /* METHODS */
227 // If hardware rotation is in use, the actual display width/height changes with rotation
228 int get_width_internal() override {
229 if constexpr (HAS_HARDWARE_ROTATION)
230 return get_width();
231 return WIDTH;
232 }
233 int get_height_internal() override {
234 if constexpr (HAS_HARDWARE_ROTATION)
235 return get_height();
236 return HEIGHT;
237 }
238 // convenience functions to write commands with or without data
239 void write_command_(uint8_t cmd, uint8_t data) { this->write_command_(cmd, &data, 1); }
240 void write_command_(uint8_t cmd) { this->write_command_(cmd, &cmd, 0); }
241
242 // Writes a command to the display, with the given bytes.
243 void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) {
244 char hex_buf[format_hex_pretty_size(MIPI_SPI_MAX_CMD_LOG_BYTES)];
245 // Don't spam the log after setup
246 if (this->init_sequence_len_ == 0) {
247 esph_log_v(TAG, "Command %02X, length %zu, bytes %s", cmd, len, format_hex_pretty_to(hex_buf, bytes, len));
248 } else {
249 esph_log_d(TAG, "Command %02X, length %zu, bytes %s", cmd, len, format_hex_pretty_to(hex_buf, bytes, len));
250 }
251 if constexpr (BUS_TYPE == BUS_TYPE_QUAD) {
252 this->enable();
253 this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len);
254 this->disable();
255 } else if constexpr (BUS_TYPE == BUS_TYPE_OCTAL) {
256 this->enable();
257 this->dc_pin_->digital_write(false);
258 this->write_cmd_addr_data(0, 0, 0, 0, &cmd, 1, 8);
259 this->dc_pin_->digital_write(true);
260 // hold the bus between command and data to avoid a glitch on the D/C line
261 if (len != 0) {
262 this->write_cmd_addr_data(0, 0, 0, 0, bytes, len, 8);
263 }
264 this->disable();
265 } else if constexpr (BUS_TYPE == BUS_TYPE_SINGLE) {
266 // Toggle D/C only while holding the bus; works around a quirk in the CoreS3 and W5500 ethernet combination.
267 // See https://github.com/esphome/esphome/pull/18529
268 this->enable();
269 this->dc_pin_->digital_write(false);
270 this->write_byte(cmd);
271 this->dc_pin_->digital_write(true);
272 this->disable();
273 if (len != 0) {
274 this->enable();
275 this->write_array(bytes, len);
276 this->disable();
277 }
278 } else if constexpr (BUS_TYPE == BUS_TYPE_SINGLE_16) {
279 // DC must be stable before CS as the clock is gated by CS
280 this->dc_pin_->digital_write(false);
281 this->enable();
282 this->write_byte(cmd);
283 this->disable();
284 this->dc_pin_->digital_write(true);
285 for (size_t i = 0; i != len; i++) {
286 // must enable and disable for each byte based on empirical testing
287 this->enable();
288 this->write_byte(0);
289 this->write_byte(bytes[i]);
290 this->disable();
291 }
292 }
293 }
294
295 // write changed parameters to the display
297 if (!this->is_ready())
298 return;
299 this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF);
300 if (this->brightness_.has_value())
301 this->write_command_(BRIGHTNESS, this->brightness_.value());
302
303 // calculate new madctl value from base value adjusted for rotation
304 uint8_t madctl = (uint8_t) MADCTL; // lower 8 bits only
305 constexpr bool use_flips = (MADCTL & MADCTL_FLIP_FLAG) != 0;
306 constexpr uint8_t x_mask = use_flips ? MADCTL_XFLIP : MADCTL_MX;
307 constexpr uint8_t y_mask = use_flips ? MADCTL_YFLIP : MADCTL_MY;
308 if constexpr (HAS_HARDWARE_ROTATION) {
309 switch (this->rotation_) {
310 default:
311 break;
313 madctl ^= x_mask; // flip X axis
314 madctl ^= MADCTL_MV; // swap X and Y axes
315 break;
317 madctl ^= x_mask; // flip X axis
318 madctl ^= y_mask; // flip Y axis
319 break;
321 madctl ^= y_mask; // flip Y axis
322 madctl ^= MADCTL_MV; // swap X and Y axes
323 break;
324 }
325 }
326 esph_log_d(TAG, "Setting MADCTL for rotation %d, value %X", this->rotation_, madctl);
327 this->write_command_(MADCTL_CMD, madctl);
328 }
329
330 uint16_t get_offset_width_() const {
331 if constexpr (HAS_HARDWARE_ROTATION) {
332 switch (this->rotation_) {
334 return OFFSET_HEIGHT;
336 return PAD_WIDTH;
338 return PAD_HEIGHT;
339 default:
340 break;
341 }
342 }
343 return OFFSET_WIDTH;
344 }
345
346 uint16_t get_offset_height_() const {
347 if constexpr (HAS_HARDWARE_ROTATION) {
348 switch (this->rotation_) {
350 return PAD_WIDTH;
352 return PAD_HEIGHT;
354 return OFFSET_WIDTH;
355 default:
356 break;
357 }
358 }
359 return OFFSET_HEIGHT;
360 }
361
362 // set the address window for the next data write
363 void set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
364 esph_log_v(TAG, "Set addr %d/%d, %d/%d", x1, y1, x2, y2);
365 uint8_t buf[4];
366 x1 += get_offset_width_();
367 x2 += get_offset_width_();
368 y1 += get_offset_height_();
369 y2 += get_offset_height_();
370 put16_be(buf, y1);
371 put16_be(buf + 2, y2);
372 this->write_command_(RASET, buf, sizeof buf);
373 put16_be(buf, x1);
374 put16_be(buf + 2, x2);
375 this->write_command_(CASET, buf, sizeof buf);
376 if constexpr (BUS_TYPE != BUS_TYPE_QUAD) {
377 this->write_command_(WDATA);
378 }
379 }
380
381 // map the display color bitness to the pixel mode
383 switch (bitness) {
385 return PIXEL_MODE_18; // 18 bits per pixel
387 return PIXEL_MODE_16; // 16 bits per pixel
388 default:
389 return PIXEL_MODE_8; // Default to 8 bits per pixel
390 }
391 }
392
400 void write_display_data_(const uint8_t *ptr, size_t w, size_t h, size_t stride) {
401 if (stride == w) {
402 if constexpr (BUS_TYPE == BUS_TYPE_SINGLE || BUS_TYPE == BUS_TYPE_SINGLE_16) {
403 this->write_array(ptr, w * h);
404 } else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) {
405 this->write_cmd_addr_data(8, 0x32, 24, WDATA << 8, ptr, w * h, 4);
406 } else if constexpr (BUS_TYPE == BUS_TYPE_OCTAL) {
407 this->write_cmd_addr_data(0, 0, 0, 0, ptr, w * h, 8);
408 }
409 } else {
410 for (size_t y = 0; y != h; y++) {
411 if constexpr (BUS_TYPE == BUS_TYPE_SINGLE || BUS_TYPE == BUS_TYPE_SINGLE_16) {
412 this->write_array(ptr, w);
413 } else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) {
414 this->write_cmd_addr_data(8, 0x32, 24, WDATA << 8, ptr, w, 4);
415 } else if constexpr (BUS_TYPE == BUS_TYPE_OCTAL) {
416 this->write_cmd_addr_data(0, 0, 0, 0, ptr, w, 8);
417 }
418 ptr += stride;
419 }
420 }
421 }
422
423 // True when the block is already in the buffer pixel format and can be copied without per pixel work
425 return get_pixel_mode(bitness) == BUFFERPIXEL && big_endian == IS_BIG_ENDIAN &&
426 (HAS_HARDWARE_ROTATION || this->rotation_ == display::DISPLAY_ROTATION_0_DEGREES) && !this->is_clipping();
427 }
428
429 // Without a buffer there is nothing to convert a foreign block into, so it is dropped
430 virtual void draw_pixels_slow(int x_start, int y_start, int w, int h, const uint8_t *ptr,
432 esph_log_e(TAG, "Unsupported color depth, bit order or rotation");
433 }
434
441 void write_to_display_(int x_start, int y_start, int w, int h, const BUFFERTYPE *ptr, int x_offset, int y_offset,
442 int x_pad) {
443 this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
444 this->enable();
445 ptr += y_offset * (x_offset + w + x_pad) + x_offset;
446 if constexpr (BUFFERPIXEL == DISPLAYPIXEL) {
447 this->write_display_data_(reinterpret_cast<const uint8_t *>(ptr), w * sizeof(BUFFERTYPE), h,
448 (x_offset + w + x_pad) * sizeof(BUFFERTYPE));
449 } else {
450 // type conversion required, do it in chunks
451 uint8_t dbuffer[DISPLAYPIXEL * 48];
452 uint8_t *dptr = dbuffer;
453 auto stride = x_offset + w + x_pad; // stride in pixels
454 for (size_t y = 0; y != static_cast<size_t>(h); y++) {
455 for (size_t x = 0; x != static_cast<size_t>(w); x++) {
456 auto color_val = ptr[y * stride + x];
457 if constexpr (DISPLAYPIXEL == PIXEL_MODE_18 && BUFFERPIXEL == PIXEL_MODE_16) {
458 // 16 to 18 bit conversion
459 if constexpr (IS_BIG_ENDIAN) {
460 *dptr++ = color_val & 0xF8;
461 *dptr++ = ((color_val & 0x7) << 5) | (color_val & 0xE000) >> 11;
462 *dptr++ = (color_val >> 5) & 0xF8;
463 } else {
464 *dptr++ = (color_val >> 8) & 0xF8; // Blue
465 *dptr++ = (color_val & 0x7E0) >> 3;
466 *dptr++ = color_val << 3;
467 }
468 } else if constexpr (DISPLAYPIXEL == PIXEL_MODE_18 && BUFFERPIXEL == PIXEL_MODE_8) {
469 // 8 bit to 18 bit conversion
470 *dptr++ = color_val << 6; // Blue
471 *dptr++ = (color_val & 0x1C) << 3; // Green
472 *dptr++ = (color_val & 0xE0); // Red
473 } else if constexpr (DISPLAYPIXEL == PIXEL_MODE_16 && BUFFERPIXEL == PIXEL_MODE_8) {
474 if constexpr (IS_BIG_ENDIAN) {
475 *dptr++ = (color_val & 0xE0) | ((color_val & 0x1C) >> 2);
476 *dptr++ = (color_val & 3) << 3;
477 } else {
478 *dptr++ = (color_val & 3) << 3;
479 *dptr++ = (color_val & 0xE0) | ((color_val & 0x1C) >> 2);
480 }
481 }
482 // buffer full? Flush.
483 if (dptr == dbuffer + sizeof(dbuffer)) {
484 this->write_display_data_(dbuffer, sizeof(dbuffer), 1, sizeof(dbuffer));
485 dptr = dbuffer;
486 }
487 }
488 }
489 // flush any remaining data
490 if (dptr != dbuffer) {
491 this->write_display_data_(dbuffer, dptr - dbuffer, 1, dptr - dbuffer);
492 }
493 }
494 this->disable();
495 }
496
497 /* PROPERTIES */
498
499 // GPIO pins
501 std::vector<GPIOPin *> enable_pins_{};
502 GPIOPin *dc_pin_{nullptr};
503
504 // other properties set by configuration
506 optional<uint8_t> brightness_{};
507 const char *model_{"Unknown"};
508 // Shared PROGMEM table
509 const uint8_t *init_sequence_{nullptr};
511};
512
531template<typename BUFFERTYPE, PixelMode BUFFERPIXEL, bool IS_BIG_ENDIAN, PixelMode DISPLAYPIXEL, BusType BUS_TYPE,
532 uint16_t WIDTH, uint16_t HEIGHT, int OFFSET_WIDTH, int OFFSET_HEIGHT, int PAD_WIDTH, int PAD_HEIGHT,
533 uint16_t MADCTL, bool HAS_HARDWARE_ROTATION, int FRACTION, unsigned ROUNDING>
535 : public MipiSpi<BUFFERTYPE, BUFFERPIXEL, IS_BIG_ENDIAN, DISPLAYPIXEL, BUS_TYPE, WIDTH, HEIGHT, OFFSET_WIDTH,
536 OFFSET_HEIGHT, PAD_WIDTH, PAD_HEIGHT, MADCTL, HAS_HARDWARE_ROTATION> {
537 public:
538 // these values define the buffer size needed to write in accordance with the chip pixel alignment
539 // requirements. If the required rounding does not divide the width and height, we round up to the next multiple and
540 // ignore the extra columns and rows when drawing, but use them to write to the display.
541 static constexpr size_t round_buffer(size_t size) { return (size + ROUNDING - 1) / ROUNDING * ROUNDING; }
542
543 MipiSpiBuffer() = default;
544
545 void dump_config() override {
546 MipiSpi<BUFFERTYPE, BUFFERPIXEL, IS_BIG_ENDIAN, DISPLAYPIXEL, BUS_TYPE, WIDTH, HEIGHT, OFFSET_WIDTH, OFFSET_HEIGHT,
547 PAD_WIDTH, PAD_HEIGHT, MADCTL, HAS_HARDWARE_ROTATION>::dump_config();
548 esph_log_config(TAG,
549 " Rotation: %d°\n"
550 " Buffer pixels: %d bits\n"
551 " Buffer fraction: 1/%d\n"
552 " Buffer bytes: %zu\n"
553 " Draw rounding: %u",
554 this->rotation_, BUFFERPIXEL * 8, FRACTION,
555 sizeof(BUFFERTYPE) * round_buffer(WIDTH) * round_buffer(HEIGHT) / FRACTION, ROUNDING);
556 }
557
558 void setup() override {
559 MipiSpi<BUFFERTYPE, BUFFERPIXEL, IS_BIG_ENDIAN, DISPLAYPIXEL, BUS_TYPE, WIDTH, HEIGHT, OFFSET_WIDTH, OFFSET_HEIGHT,
560 PAD_WIDTH, PAD_HEIGHT, MADCTL, HAS_HARDWARE_ROTATION>::setup();
561 RAMAllocator<BUFFERTYPE> allocator{};
562 this->buffer_ = allocator.allocate(round_buffer(WIDTH) * round_buffer(HEIGHT) / FRACTION);
563 if (this->buffer_ == nullptr) {
564 this->mark_failed(LOG_STR("Buffer allocation failed"));
565 }
566 }
567
568 void update() override {
569#if ESPHOME_LOG_LEVEL == ESPHOME_LOG_LEVEL_VERBOSE
570 auto now = millis();
571#endif
572 if (this->is_failed()) {
573 return;
574 }
575 // for updates with a small buffer, we repeatedly call the writer_ function, clipping the height to a fraction of
576 // the display height,
577 auto increment = (this->get_height_internal() / FRACTION / ROUNDING) * ROUNDING;
578 for (this->start_line_ = 0; this->start_line_ < this->get_height_internal(); this->start_line_ = this->end_line_) {
579#if ESPHOME_LOG_LEVEL == ESPHOME_LOG_LEVEL_VERBOSE
580 auto lap = millis();
581#endif
582 this->end_line_ = clamp_at_most(this->start_line_ + increment, this->get_height_internal());
583 if (this->auto_clear_enabled_) {
584 this->clear();
585 }
586 this->updating_ = true;
587 if (this->show_test_card_) {
588 this->test_card();
589 } else if (this->page_ != nullptr) {
590 this->page_->get_writer()(*this);
591 } else if (this->writer_.has_value()) {
592 (*this->writer_)(*this);
593 }
594 this->updating_ = false;
595#if ESPHOME_LOG_LEVEL == ESPHOME_LOG_LEVEL_VERBOSE
596 esph_log_v(TAG, "Drawing from line %d took %dms", this->start_line_, millis() - lap);
597 lap = millis();
598#endif
599 if (this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
600 return;
601 esph_log_v(TAG, "x_low %d, y_low %d, x_high %d, y_high %d", this->x_low_, this->y_low_, this->x_high_,
602 this->y_high_);
603 // Some chips require that the drawing window be aligned on certain boundaries
604 this->x_low_ = this->x_low_ / ROUNDING * ROUNDING;
605 this->y_low_ = this->y_low_ / ROUNDING * ROUNDING;
606 this->x_high_ = round_buffer(this->x_high_ + 1) - 1;
607 this->y_high_ = clamp_at_most(round_buffer(this->y_high_ + 1) - 1, this->end_line_ - 1);
608 int w = this->x_high_ - this->x_low_ + 1;
609 int h = this->y_high_ - this->y_low_ + 1;
610 this->write_to_display_(this->x_low_, this->y_low_, w, h, this->buffer_, this->x_low_,
611 this->y_low_ - this->start_line_,
612 round_buffer(this->get_width_internal()) - w - this->x_low_);
613 // invalidate watermarks
614 this->x_low_ = this->get_width_internal();
615 this->y_low_ = this->get_height_internal();
616 this->x_high_ = 0;
617 this->y_high_ = 0;
618#if ESPHOME_LOG_LEVEL == ESPHOME_LOG_LEVEL_VERBOSE
619 esph_log_v(TAG, "Write to display took %dms", millis() - lap);
620 lap = millis();
621#endif
622 }
623#if ESPHOME_LOG_LEVEL == ESPHOME_LOG_LEVEL_VERBOSE
624 esph_log_v(TAG, "Total update took %dms", millis() - now);
625#endif
626 }
627
628 // With hardware rotation the buffer is written in drawn coordinates.
629 bool pixel_axes_swapped() const override { return !HAS_HARDWARE_ROTATION && display::Display::pixel_axes_swapped(); }
630
631 // Draw a pixel at the given coordinates.
632 void draw_pixel_at(int x, int y, Color color) override {
633 if (this->is_point_clipped(x, y))
634 return;
635 if constexpr (not HAS_HARDWARE_ROTATION) {
637 x = WIDTH - x - 1;
638 y = HEIGHT - y - 1;
640 auto tmp = x;
641 x = WIDTH - y - 1;
642 y = tmp;
644 auto tmp = y;
645 y = HEIGHT - x - 1;
646 x = tmp;
647 }
648 }
650 return;
651 this->buffer_[(y - this->start_line_) * round_buffer(this->get_width_internal()) + x] = convert_color(color);
652 if (x < this->x_low_) {
653 this->x_low_ = x;
654 }
655 if (x > this->x_high_) {
656 this->x_high_ = x;
657 }
658 if (y < this->y_low_) {
659 this->y_low_ = y;
660 }
661 if (y > this->y_high_) {
662 this->y_high_ = y;
663 }
664 }
665
666 // Fills the display with a color.
667 void fill(Color color) override {
668 // If clipping is active, fall back to base implementation
669 if (this->get_clipping().is_set()) {
671 return;
672 }
673
674 this->x_low_ = 0;
675 this->y_low_ = this->start_line_;
676 this->x_high_ = this->get_width_internal() - 1;
677 this->y_high_ = this->end_line_ - 1;
678 std::fill_n(this->buffer_, (this->end_line_ - this->start_line_) * round_buffer(this->get_width_internal()),
679 convert_color(color));
680 }
681
682 protected:
683 // While update() runs the writer, a block goes into the buffer slice, or the slice flush would paint over it.
684 // Outside update() (e.g. LVGL) it is written straight to the display.
685 void draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness,
686 bool big_endian, int x_offset, int y_offset, int x_pad) override {
687 if (!this->updating_) {
688 MipiSpiBuffer::MipiSpi::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset,
689 x_pad);
690 return;
691 }
692 if (this->is_failed() || w <= 0 || h <= 0)
693 return;
694 if (!this->is_native_block_(bitness, big_endian)) {
696 x_pad);
697 return;
698 }
699 // Only the rows inside the current slice are kept
700 const int first_col = std::max(0, -x_start);
701 const int end_col = std::min(w, this->get_width_internal() - x_start);
702 const int first_row = std::max<int>(0, this->start_line_ - y_start);
703 const int end_row = std::min<int>(h, this->end_line_ - y_start);
704 if (first_col >= end_col || first_row >= end_row)
705 return;
706 const size_t stride = x_offset + w + x_pad;
707 const size_t buffer_stride = round_buffer(this->get_width_internal());
708 for (int y = first_row; y != end_row; y++) {
709 memcpy(this->buffer_ + (y_start + y - this->start_line_) * buffer_stride + x_start + first_col,
710 ptr + ((y_offset + y) * stride + x_offset + first_col) * sizeof(BUFFERTYPE),
711 (end_col - first_col) * sizeof(BUFFERTYPE));
712 }
713 this->x_low_ = std::min<int>(this->x_low_, x_start + first_col);
714 this->y_low_ = std::min<int>(this->y_low_, y_start + first_row);
715 this->x_high_ = std::max<int>(this->x_high_, x_start + end_col - 1);
716 this->y_high_ = std::max<int>(this->y_high_, y_start + end_row - 1);
717 }
718
719 void draw_pixels_slow(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorBitness bitness,
720 bool big_endian, int x_offset, int y_offset, int x_pad) override {
722 }
723 // Convert a color to the buffer pixel format.
724 static BUFFERTYPE convert_color(const Color &color) {
725 if constexpr (BUFFERPIXEL == PIXEL_MODE_8) {
726 return (color.red & 0xE0) | (color.g & 0xE0) >> 3 | color.b >> 6;
727 } else if constexpr (BUFFERPIXEL == PIXEL_MODE_16) {
728 if constexpr (IS_BIG_ENDIAN) {
729 return (color.r & 0xF8) | color.g >> 5 | (color.g & 0x1C) << 11 | (color.b & 0xF8) << 5;
730 } else {
731 return (color.r & 0xF8) << 8 | (color.g & 0xFC) << 3 | color.b >> 3;
732 }
733 }
734 return static_cast<BUFFERTYPE>(0);
735 }
736
737 BUFFERTYPE *buffer_{};
738 uint16_t x_low_{WIDTH};
739 uint16_t y_low_{HEIGHT};
740 uint16_t x_high_{0};
741 uint16_t y_high_{0};
742 uint16_t start_line_{0};
743 uint16_t end_line_{1};
744 bool updating_{false};
745};
746
747} // namespace esphome::mipi_spi
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
bool is_ready() const
virtual void setup()=0
virtual void digital_write(bool value)=0
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2298
T * allocate(size_t n)
Definition helpers.h:2325
virtual bool pixel_axes_swapped() const
True when draw_pixel_at() swaps x and y before writing, so pixels that are neighbours along x land a ...
Definition display.h:757
display_writer_t writer_
Definition display.h:864
int int int const uint8_t ColorOrder ColorBitness bitness
Definition display.h:392
virtual void clear()
Clear the entire screen by filling it with OFF pixels.
Definition display.cpp:17
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
virtual void fill(Color color)
Fill the entire screen with the given color.
Definition display.cpp:16
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
bool ESPHOME_ALWAYS_INLINE is_point_clipped(int x, int y) const
Whether (x, y) falls outside the active clipping rectangle.
Definition display.h:810
DisplayPage * page_
Definition display.h:865
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
const display_writer_t & get_writer() const
Definition display.cpp:956
virtual void set_brightness(uint8_t brightness)=0
Class for MIPI SPI displays with a buffer.
Definition mipi_spi.h:536
void draw_pixel_at(int x, int y, Color color) override
Definition mipi_spi.h:632
static constexpr size_t round_buffer(size_t size)
Definition mipi_spi.h:541
void draw_pixels_slow(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
Definition mipi_spi.h:719
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
Definition mipi_spi.h:685
bool pixel_axes_swapped() const override
Definition mipi_spi.h:629
void fill(Color color) override
Definition mipi_spi.h:667
static BUFFERTYPE convert_color(const Color &color)
Definition mipi_spi.h:724
Base class for MIPI SPI displays.
Definition mipi_spi.h:108
void update() override
Definition mipi_spi.h:111
uint16_t get_offset_height_() const
Definition mipi_spi.h:346
void write_to_display_(int x_start, int y_start, int w, int h, const BUFFERTYPE *ptr, int x_offset, int y_offset, int x_pad)
Writes a buffer to the display.
Definition mipi_spi.h:441
int get_width_internal() override
Definition mipi_spi.h:228
void set_init_sequence(const uint8_t *sequence, size_t len)
Definition mipi_spi.h:138
const uint8_t * init_sequence_
Definition mipi_spi.h:509
void set_brightness(uint8_t brightness) override
Definition mipi_spi.h:121
static PixelMode get_pixel_mode(display::ColorBitness bitness)
Definition mipi_spi.h:382
void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len)
Definition mipi_spi.h:243
void set_reset_pin(GPIOPin *reset_pin)
Definition mipi_spi.h:114
int get_width() override
Definition mipi_spi.h:135
optional< uint8_t > brightness_
Definition mipi_spi.h:506
void set_invert_colors(bool invert_colors)
Definition mipi_spi.h:117
int get_height_internal() override
Definition mipi_spi.h:233
void dump_config() override
Definition mipi_spi.h:202
uint16_t get_offset_width_() const
Definition mipi_spi.h:330
int get_height() override
Definition mipi_spi.h:136
void draw_pixel_at(int x, int y, Color color) override
Definition mipi_spi.h:112
void write_command_(uint8_t cmd)
Definition mipi_spi.h:240
void write_display_data_(const uint8_t *ptr, size_t w, size_t h, size_t stride)
Writes a buffer to the display.
Definition mipi_spi.h:400
display::DisplayType get_display_type() override
Definition mipi_spi.h:133
void set_dc_pin(GPIOPin *dc_pin)
Definition mipi_spi.h:116
void set_rotation(display::DisplayRotation rotation) override
Definition mipi_spi.h:127
void write_command_(uint8_t cmd, uint8_t data)
Definition mipi_spi.h:239
void set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
Definition mipi_spi.h:363
bool is_native_block_(display::ColorBitness bitness, bool big_endian) const
Definition mipi_spi.h:424
void setup() override
Definition mipi_spi.h:144
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
Definition mipi_spi.h:212
void set_model(const char *model)
Definition mipi_spi.h:113
virtual void draw_pixels_slow(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)
Definition mipi_spi.h:430
std::vector< GPIOPin * > enable_pins_
Definition mipi_spi.h:501
void set_enable_pins(std::vector< GPIOPin * > enable_pins)
Definition mipi_spi.h:115
uint32_t data_rate_
Definition spi.h:436
The SPIDevice is what components using the SPI will create.
Definition spi.h:456
void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, const uint8_t *data, size_t length, uint8_t bus_width=1)
Definition spi.h:500
uint16_t reset
Definition ina226.h:5
@ DISPLAY_ROTATION_0_DEGREES
Definition display.h:136
@ DISPLAY_ROTATION_270_DEGREES
Definition display.h:139
@ DISPLAY_ROTATION_180_DEGREES
Definition display.h:138
@ DISPLAY_ROTATION_90_DEGREES
Definition display.h:137
constexpr bool rotation_swaps_axes(DisplayRotation rotation)
Whether a rotation swaps the x and y axes.
Definition display.h:143
void internal_dump_config(const char *model, int width, int height, int offset_width, int offset_height, uint8_t madctl, bool invert_colors, int display_bits, bool is_big_endian, const optional< uint8_t > &brightness, GPIOPin *cs, GPIOPin *reset, GPIOPin *dc, int spi_mode, uint32_t data_rate, int bus_width, bool has_hardware_rotation)
Definition mipi_spi.cpp:6
ESPHOME_LOG_TAG(TAG, "display.mipi_spi")
T clamp_at_most(T value, U max)
Definition helpers.h:2478
void progmem_memcpy(void *dst, const void *src, size_t len)
Definition hal.h:56
const void size_t len
Definition hal.h:68
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:498
uint16_t size
Definition helpers.cpp:34
T clamp_at_least(T value, U min)
Definition helpers.h:2473
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1609
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
uint8_t progmem_read_byte(const uint8_t *addr)
Definition hal.h:51
static void uint32_t
uint8_t red
Definition color.h:31
uint8_t g
Definition color.h:34
uint8_t b
Definition color.h:38
uint8_t r
Definition color.h:30
uint16_t seq
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6