ESPHome 2026.6.2
Loading...
Searching...
No Matches
mipi_spi.h
Go to the documentation of this file.
1#pragma once
2
3#include <utility>
4
9
10namespace esphome::mipi_spi {
11
12constexpr static const char *const TAG = "display.mipi_spi";
13
14// Maximum bytes to log for commands (truncated if larger)
15static constexpr size_t MIPI_SPI_MAX_CMD_LOG_BYTES = 64;
16static constexpr uint8_t SW_RESET_CMD = 0x01;
17static constexpr uint8_t SLEEP_OUT = 0x11;
18static constexpr uint8_t NORON = 0x13;
19static constexpr uint8_t INVERT_OFF = 0x20;
20static constexpr uint8_t INVERT_ON = 0x21;
21static constexpr uint8_t ALL_ON = 0x23;
22static constexpr uint8_t WRAM = 0x24;
23static constexpr uint8_t MIPI = 0x26;
24static constexpr uint8_t DISPLAY_ON = 0x29;
25static constexpr uint8_t RASET = 0x2B;
26static constexpr uint8_t CASET = 0x2A;
27static constexpr uint8_t WDATA = 0x2C;
28static constexpr uint8_t TEON = 0x35;
29static constexpr uint8_t MADCTL_CMD = 0x36;
30static constexpr uint8_t PIXFMT = 0x3A;
31static constexpr uint8_t BRIGHTNESS = 0x51;
32static constexpr uint8_t SWIRE1 = 0x5A;
33static constexpr uint8_t SWIRE2 = 0x5B;
34static constexpr uint8_t PAGESEL = 0xFE;
35
36static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top
37static constexpr uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left
38static constexpr uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes
39static constexpr uint8_t MADCTL_RGB = 0x00; // Bit 3 Red-Green-Blue pixel order
40static constexpr uint8_t MADCTL_BGR = 0x08; // Bit 3 Blue-Green-Red pixel order
41static constexpr uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horizontally
42static constexpr uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically
43static constexpr uint16_t MADCTL_FLIP_FLAG = 0x100; // controller uses axis flip bits
44
45static constexpr uint8_t DELAY_FLAG = 0xFF;
46// store a 16 bit value in a buffer, big endian.
47static inline void put16_be(uint8_t *buf, uint16_t value) {
48 buf[0] = value >> 8;
49 buf[1] = value;
50}
51
52// Buffer mode, conveniently also the number of bytes in a pixel
58
59enum BusType {
63 BUS_TYPE_SINGLE_16 = 16, // Single bit bus, but 16 bits per transfer
64};
65
66// Helper function for dump_config - defined in mipi_spi.cpp to allow use of LOG_PIN macro
67void internal_dump_config(const char *model, int width, int height, int offset_width, int offset_height, uint8_t madctl,
68 bool invert_colors, int display_bits, bool is_big_endian, const optional<uint8_t> &brightness,
69 GPIOPin *cs, GPIOPin *reset, GPIOPin *dc, int spi_mode, uint32_t data_rate, int bus_width,
70 bool has_hardware_rotation);
71
90template<typename BUFFERTYPE, PixelMode BUFFERPIXEL, bool IS_BIG_ENDIAN, PixelMode DISPLAYPIXEL, BusType BUS_TYPE,
91 int WIDTH, int HEIGHT, int OFFSET_WIDTH, int OFFSET_HEIGHT, int PAD_WIDTH, int PAD_HEIGHT, uint16_t MADCTL,
92 bool HAS_HARDWARE_ROTATION>
94 public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
95 spi::DATA_RATE_1MHZ> {
96 public:
97 MipiSpi() = default;
98 void update() override { this->stop_poller(); }
99 void draw_pixel_at(int x, int y, Color color) override {}
100 void set_model(const char *model) { this->model_ = model; }
101 void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
102 void set_enable_pins(std::vector<GPIOPin *> enable_pins) { this->enable_pins_ = std::move(enable_pins); }
103 void set_dc_pin(GPIOPin *dc_pin) { this->dc_pin_ = dc_pin; }
104 void set_invert_colors(bool invert_colors) {
105 this->invert_colors_ = invert_colors;
106 this->reset_params_();
107 }
108 void set_brightness(uint8_t brightness) {
109 this->brightness_ = brightness;
110 this->reset_params_();
111 }
112 void set_rotation(display::DisplayRotation rotation) override {
113 this->rotation_ = rotation;
114 if constexpr (HAS_HARDWARE_ROTATION) {
115 this->reset_params_();
116 }
117 }
119
120 int get_width() override {
123 return HEIGHT;
124 return WIDTH;
125 }
126
127 int get_height() override {
130 return WIDTH;
131 return HEIGHT;
132 }
133
134 void set_init_sequence(const std::vector<uint8_t> &sequence) { this->init_sequence_ = sequence; }
135
136 // reset the display, and write the init sequence
137 void setup() override {
138 this->spi_setup();
139 if (this->dc_pin_ != nullptr) {
140 this->dc_pin_->setup();
141 this->dc_pin_->digital_write(false);
142 }
143 for (auto *pin : this->enable_pins_) {
144 pin->setup();
145 pin->digital_write(true);
146 }
147 if (this->reset_pin_ != nullptr) {
148 this->reset_pin_->setup();
149 this->reset_pin_->digital_write(true);
150 delay(5);
151 this->reset_pin_->digital_write(false);
152 delay(5);
153 this->reset_pin_->digital_write(true);
154 }
155
156 // need to know when the display is ready for SLPOUT command - will be 120ms after reset
157 auto when = millis() + 120;
158 delay(10);
159 size_t index = 0;
160 auto &vec = this->init_sequence_;
161 while (index != vec.size()) {
162 if (vec.size() - index < 2) {
163 esph_log_e(TAG, "Malformed init sequence");
164 this->mark_failed();
165 return;
166 }
167 uint8_t cmd = vec[index++];
168 uint8_t x = vec[index++];
169 if (x == DELAY_FLAG) {
170 esph_log_d(TAG, "Delay %dms", cmd);
171 delay(cmd);
172 } else {
173 uint8_t num_args = x & 0x7F;
174 if (vec.size() - index < num_args) {
175 esph_log_e(TAG, "Malformed init sequence");
176 this->mark_failed();
177 return;
178 }
179 auto arg_byte = vec[index];
180 switch (cmd) {
181 case SLEEP_OUT: {
182 // are we ready, boots?
183 int duration = when - millis();
184 if (duration > 0) {
185 esph_log_d(TAG, "Sleep %dms", duration);
187 }
188 } break;
189
190 case INVERT_ON:
191 this->invert_colors_ = true;
192 break;
193 case BRIGHTNESS:
194 this->brightness_ = arg_byte;
195 break;
196
197 default:
198 break;
199 }
200 const auto *ptr = vec.data() + index;
201 this->write_command_(cmd, ptr, num_args);
202 index += num_args;
203 if (cmd == SLEEP_OUT)
204 delay(10);
205 }
206 }
207 this->reset_params_();
208 // init sequence no longer needed
209 this->init_sequence_.clear();
210 }
211
212 // Drawing operations
213
214 void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
215 display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) override {
216 if (this->is_failed())
217 return;
218 if (w <= 0 || h <= 0)
219 return;
220 if (get_pixel_mode(bitness) != BUFFERPIXEL || big_endian != IS_BIG_ENDIAN) {
221 // note that the usual logging macros are banned in header files, so use their replacement
222 esph_log_e(TAG, "Unsupported color depth or bit order");
223 return;
224 }
225 this->write_to_display_(x_start, y_start, w, h, reinterpret_cast<const BUFFERTYPE *>(ptr), x_offset, y_offset,
226 x_pad);
227 }
228
229 void dump_config() override {
230 internal_dump_config(this->model_, this->get_width(), this->get_height(), this->get_offset_width_(),
231 this->get_offset_height_(), (uint8_t) MADCTL, this->invert_colors_, DISPLAYPIXEL * 8,
232 IS_BIG_ENDIAN, this->brightness_, this->cs_, this->reset_pin_, this->dc_pin_, this->mode_,
233 this->data_rate_, BUS_TYPE, HAS_HARDWARE_ROTATION);
234 }
235
236 protected:
237 /* METHODS */
238 // If hardware rotation is in use, the actual display width/height changes with rotation
239 int get_width_internal() override {
240 if constexpr (HAS_HARDWARE_ROTATION)
241 return get_width();
242 return WIDTH;
243 }
244 int get_height_internal() override {
245 if constexpr (HAS_HARDWARE_ROTATION)
246 return get_height();
247 return HEIGHT;
248 }
249 // convenience functions to write commands with or without data
250 void write_command_(uint8_t cmd, uint8_t data) { this->write_command_(cmd, &data, 1); }
251 void write_command_(uint8_t cmd) { this->write_command_(cmd, &cmd, 0); }
252
253 // Writes a command to the display, with the given bytes.
254 void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len) {
255 char hex_buf[format_hex_pretty_size(MIPI_SPI_MAX_CMD_LOG_BYTES)];
256 // Don't spam the log after setup
257 if (this->init_sequence_.empty()) {
258 esph_log_v(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty_to(hex_buf, bytes, len));
259 } else {
260 esph_log_d(TAG, "Command %02X, length %d, bytes %s", cmd, len, format_hex_pretty_to(hex_buf, bytes, len));
261 }
262 if constexpr (BUS_TYPE == BUS_TYPE_QUAD) {
263 this->enable();
264 this->write_cmd_addr_data(8, 0x02, 24, cmd << 8, bytes, len);
265 this->disable();
266 } else if constexpr (BUS_TYPE == BUS_TYPE_OCTAL) {
267 this->dc_pin_->digital_write(false);
268 this->enable();
269 this->write_cmd_addr_data(0, 0, 0, 0, &cmd, 1, 8);
270 this->disable();
271 this->dc_pin_->digital_write(true);
272 if (len != 0) {
273 this->enable();
274 this->write_cmd_addr_data(0, 0, 0, 0, bytes, len, 8);
275 this->disable();
276 }
277 } else if constexpr (BUS_TYPE == BUS_TYPE_SINGLE) {
278 this->dc_pin_->digital_write(false);
279 this->enable();
280 this->write_byte(cmd);
281 this->disable();
282 this->dc_pin_->digital_write(true);
283 if (len != 0) {
284 this->enable();
285 this->write_array(bytes, len);
286 this->disable();
287 }
288 } else if constexpr (BUS_TYPE == BUS_TYPE_SINGLE_16) {
289 this->dc_pin_->digital_write(false);
290 this->enable();
291 this->write_byte(cmd);
292 this->disable();
293 this->dc_pin_->digital_write(true);
294 for (size_t i = 0; i != len; i++) {
295 this->enable();
296 this->write_byte(0);
297 this->write_byte(bytes[i]);
298 this->disable();
299 }
300 }
301 }
302
303 // write changed parameters to the display
305 if (!this->is_ready())
306 return;
307 this->write_command_(this->invert_colors_ ? INVERT_ON : INVERT_OFF);
308 if (this->brightness_.has_value())
309 this->write_command_(BRIGHTNESS, this->brightness_.value());
310
311 // calculate new madctl value from base value adjusted for rotation
312 uint8_t madctl = (uint8_t) MADCTL; // lower 8 bits only
313 constexpr bool use_flips = (MADCTL & MADCTL_FLIP_FLAG) != 0;
314 constexpr uint8_t x_mask = use_flips ? MADCTL_XFLIP : MADCTL_MX;
315 constexpr uint8_t y_mask = use_flips ? MADCTL_YFLIP : MADCTL_MY;
316 if constexpr (HAS_HARDWARE_ROTATION) {
317 switch (this->rotation_) {
318 default:
319 break;
321 madctl ^= x_mask; // flip X axis
322 madctl ^= MADCTL_MV; // swap X and Y axes
323 break;
325 madctl ^= x_mask; // flip X axis
326 madctl ^= y_mask; // flip Y axis
327 break;
329 madctl ^= y_mask; // flip Y axis
330 madctl ^= MADCTL_MV; // swap X and Y axes
331 break;
332 }
333 }
334 esph_log_d(TAG, "Setting MADCTL for rotation %d, value %X", this->rotation_, madctl);
335 this->write_command_(MADCTL_CMD, madctl);
336 }
337
338 uint16_t get_offset_width_() const {
339 if constexpr (HAS_HARDWARE_ROTATION) {
340 switch (this->rotation_) {
342 return OFFSET_HEIGHT;
344 return PAD_WIDTH;
346 return PAD_HEIGHT;
347 default:
348 break;
349 }
350 }
351 return OFFSET_WIDTH;
352 }
353
354 uint16_t get_offset_height_() const {
355 if constexpr (HAS_HARDWARE_ROTATION) {
356 switch (this->rotation_) {
358 return PAD_WIDTH;
360 return PAD_HEIGHT;
362 return OFFSET_WIDTH;
363 default:
364 break;
365 }
366 }
367 return OFFSET_HEIGHT;
368 }
369
370 // set the address window for the next data write
371 void set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
372 esph_log_v(TAG, "Set addr %d/%d, %d/%d", x1, y1, x2, y2);
373 uint8_t buf[4];
374 x1 += get_offset_width_();
375 x2 += get_offset_width_();
376 y1 += get_offset_height_();
377 y2 += get_offset_height_();
378 put16_be(buf, y1);
379 put16_be(buf + 2, y2);
380 this->write_command_(RASET, buf, sizeof buf);
381 put16_be(buf, x1);
382 put16_be(buf + 2, x2);
383 this->write_command_(CASET, buf, sizeof buf);
384 if constexpr (BUS_TYPE != BUS_TYPE_QUAD) {
385 this->write_command_(WDATA);
386 }
387 }
388
389 // map the display color bitness to the pixel mode
391 switch (bitness) {
393 return PIXEL_MODE_18; // 18 bits per pixel
395 return PIXEL_MODE_16; // 16 bits per pixel
396 default:
397 return PIXEL_MODE_8; // Default to 8 bits per pixel
398 }
399 }
400
408 void write_display_data_(const uint8_t *ptr, size_t w, size_t h, size_t pad) {
409 if (pad == 0) {
410 if constexpr (BUS_TYPE == BUS_TYPE_SINGLE || BUS_TYPE == BUS_TYPE_SINGLE_16) {
411 this->write_array(ptr, w * h);
412 } else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) {
413 this->write_cmd_addr_data(8, 0x32, 24, WDATA << 8, ptr, w * h, 4);
414 } else if constexpr (BUS_TYPE == BUS_TYPE_OCTAL) {
415 this->write_cmd_addr_data(0, 0, 0, 0, ptr, w * h, 8);
416 }
417 } else {
418 for (size_t y = 0; y != h; y++) {
419 if constexpr (BUS_TYPE == BUS_TYPE_SINGLE || BUS_TYPE == BUS_TYPE_SINGLE_16) {
420 this->write_array(ptr, w);
421 } else if constexpr (BUS_TYPE == BUS_TYPE_QUAD) {
422 this->write_cmd_addr_data(8, 0x32, 24, WDATA << 8, ptr, w, 4);
423 } else if constexpr (BUS_TYPE == BUS_TYPE_OCTAL) {
424 this->write_cmd_addr_data(0, 0, 0, 0, ptr, w, 8);
425 }
426 ptr += w + pad;
427 }
428 }
429 }
430
437 void write_to_display_(int x_start, int y_start, int w, int h, const BUFFERTYPE *ptr, int x_offset, int y_offset,
438 int x_pad) {
439 this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
440 this->enable();
441 ptr += y_offset * (x_offset + w + x_pad) + x_offset;
442 if constexpr (BUFFERPIXEL == DISPLAYPIXEL) {
443 this->write_display_data_(reinterpret_cast<const uint8_t *>(ptr), w * sizeof(BUFFERTYPE), h,
444 x_pad * sizeof(BUFFERTYPE));
445 } else {
446 // type conversion required, do it in chunks
447 uint8_t dbuffer[DISPLAYPIXEL * 48];
448 uint8_t *dptr = dbuffer;
449 auto stride = x_offset + w + x_pad; // stride in pixels
450 for (size_t y = 0; y != static_cast<size_t>(h); y++) {
451 for (size_t x = 0; x != static_cast<size_t>(w); x++) {
452 auto color_val = ptr[y * stride + x];
453 if constexpr (DISPLAYPIXEL == PIXEL_MODE_18 && BUFFERPIXEL == PIXEL_MODE_16) {
454 // 16 to 18 bit conversion
455 if constexpr (IS_BIG_ENDIAN) {
456 *dptr++ = color_val & 0xF8;
457 *dptr++ = ((color_val & 0x7) << 5) | (color_val & 0xE000) >> 11;
458 *dptr++ = (color_val >> 5) & 0xF8;
459 } else {
460 *dptr++ = (color_val >> 8) & 0xF8; // Blue
461 *dptr++ = (color_val & 0x7E0) >> 3;
462 *dptr++ = color_val << 3;
463 }
464 } else if constexpr (DISPLAYPIXEL == PIXEL_MODE_18 && BUFFERPIXEL == PIXEL_MODE_8) {
465 // 8 bit to 18 bit conversion
466 *dptr++ = color_val << 6; // Blue
467 *dptr++ = (color_val & 0x1C) << 3; // Green
468 *dptr++ = (color_val & 0xE0); // Red
469 } else if constexpr (DISPLAYPIXEL == PIXEL_MODE_16 && BUFFERPIXEL == PIXEL_MODE_8) {
470 if constexpr (IS_BIG_ENDIAN) {
471 *dptr++ = (color_val & 0xE0) | ((color_val & 0x1C) >> 2);
472 *dptr++ = (color_val & 3) << 3;
473 } else {
474 *dptr++ = (color_val & 3) << 3;
475 *dptr++ = (color_val & 0xE0) | ((color_val & 0x1C) >> 2);
476 }
477 }
478 // buffer full? Flush.
479 if (dptr == dbuffer + sizeof(dbuffer)) {
480 this->write_display_data_(dbuffer, sizeof(dbuffer), 1, 0);
481 dptr = dbuffer;
482 }
483 }
484 }
485 // flush any remaining data
486 if (dptr != dbuffer) {
487 this->write_display_data_(dbuffer, dptr - dbuffer, 1, 0);
488 }
489 }
490 this->disable();
491 }
492
493 /* PROPERTIES */
494
495 // GPIO pins
497 std::vector<GPIOPin *> enable_pins_{};
498 GPIOPin *dc_pin_{nullptr};
499
500 // other properties set by configuration
502 optional<uint8_t> brightness_{};
503 const char *model_{"Unknown"};
504 std::vector<uint8_t> init_sequence_{};
505};
506
525template<typename BUFFERTYPE, PixelMode BUFFERPIXEL, bool IS_BIG_ENDIAN, PixelMode DISPLAYPIXEL, BusType BUS_TYPE,
526 uint16_t WIDTH, uint16_t HEIGHT, int OFFSET_WIDTH, int OFFSET_HEIGHT, int PAD_WIDTH, int PAD_HEIGHT,
527 uint16_t MADCTL, bool HAS_HARDWARE_ROTATION, int FRACTION, unsigned ROUNDING>
529 : public MipiSpi<BUFFERTYPE, BUFFERPIXEL, IS_BIG_ENDIAN, DISPLAYPIXEL, BUS_TYPE, WIDTH, HEIGHT, OFFSET_WIDTH,
530 OFFSET_HEIGHT, PAD_WIDTH, PAD_HEIGHT, MADCTL, HAS_HARDWARE_ROTATION> {
531 public:
532 // these values define the buffer size needed to write in accordance with the chip pixel alignment
533 // requirements. If the required rounding does not divide the width and height, we round up to the next multiple and
534 // ignore the extra columns and rows when drawing, but use them to write to the display.
535 static constexpr size_t round_buffer(size_t size) { return (size + ROUNDING - 1) / ROUNDING * ROUNDING; }
536
537 MipiSpiBuffer() = default;
538
539 void dump_config() override {
540 MipiSpi<BUFFERTYPE, BUFFERPIXEL, IS_BIG_ENDIAN, DISPLAYPIXEL, BUS_TYPE, WIDTH, HEIGHT, OFFSET_WIDTH, OFFSET_HEIGHT,
541 PAD_WIDTH, PAD_HEIGHT, MADCTL, HAS_HARDWARE_ROTATION>::dump_config();
542 esph_log_config(TAG,
543 " Rotation: %d°\n"
544 " Buffer pixels: %d bits\n"
545 " Buffer fraction: 1/%d\n"
546 " Buffer bytes: %zu\n"
547 " Draw rounding: %u",
548 this->rotation_, BUFFERPIXEL * 8, FRACTION,
549 sizeof(BUFFERTYPE) * round_buffer(WIDTH) * round_buffer(HEIGHT) / FRACTION, ROUNDING);
550 }
551
552 void setup() override {
553 MipiSpi<BUFFERTYPE, BUFFERPIXEL, IS_BIG_ENDIAN, DISPLAYPIXEL, BUS_TYPE, WIDTH, HEIGHT, OFFSET_WIDTH, OFFSET_HEIGHT,
554 PAD_WIDTH, PAD_HEIGHT, MADCTL, HAS_HARDWARE_ROTATION>::setup();
555 RAMAllocator<BUFFERTYPE> allocator{};
556 this->buffer_ = allocator.allocate(round_buffer(WIDTH) * round_buffer(HEIGHT) / FRACTION);
557 if (this->buffer_ == nullptr) {
558 this->mark_failed(LOG_STR("Buffer allocation failed"));
559 }
560 }
561
562 void update() override {
563#if ESPHOME_LOG_LEVEL == ESPHOME_LOG_LEVEL_VERBOSE
564 auto now = millis();
565#endif
566 if (this->is_failed()) {
567 return;
568 }
569 // for updates with a small buffer, we repeatedly call the writer_ function, clipping the height to a fraction of
570 // the display height,
571 auto increment = (this->get_height_internal() / FRACTION / ROUNDING) * ROUNDING;
572 for (this->start_line_ = 0; this->start_line_ < this->get_height_internal(); this->start_line_ = this->end_line_) {
573#if ESPHOME_LOG_LEVEL == ESPHOME_LOG_LEVEL_VERBOSE
574 auto lap = millis();
575#endif
576 this->end_line_ = clamp_at_most(this->start_line_ + increment, this->get_height_internal());
577 if (this->auto_clear_enabled_) {
578 this->clear();
579 }
580 if (this->page_ != nullptr) {
581 this->page_->get_writer()(*this);
582 } else if (this->writer_.has_value()) {
583 (*this->writer_)(*this);
584 } else {
585 this->test_card();
586 }
587#if ESPHOME_LOG_LEVEL == ESPHOME_LOG_LEVEL_VERBOSE
588 esph_log_v(TAG, "Drawing from line %d took %dms", this->start_line_, millis() - lap);
589 lap = millis();
590#endif
591 if (this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
592 return;
593 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_,
594 this->y_high_);
595 // Some chips require that the drawing window be aligned on certain boundaries
596 this->x_low_ = this->x_low_ / ROUNDING * ROUNDING;
597 this->y_low_ = this->y_low_ / ROUNDING * ROUNDING;
598 this->x_high_ = round_buffer(this->x_high_ + 1) - 1;
599 this->y_high_ = clamp_at_most(round_buffer(this->y_high_ + 1) - 1, this->end_line_ - 1);
600 int w = this->x_high_ - this->x_low_ + 1;
601 int h = this->y_high_ - this->y_low_ + 1;
602 this->write_to_display_(this->x_low_, this->y_low_, w, h, this->buffer_, this->x_low_,
603 this->y_low_ - this->start_line_,
604 round_buffer(this->get_width_internal()) - w - this->x_low_);
605 // invalidate watermarks
606 this->x_low_ = this->get_width_internal();
607 this->y_low_ = this->get_height_internal();
608 this->x_high_ = 0;
609 this->y_high_ = 0;
610#if ESPHOME_LOG_LEVEL == ESPHOME_LOG_LEVEL_VERBOSE
611 esph_log_v(TAG, "Write to display took %dms", millis() - lap);
612 lap = millis();
613#endif
614 }
615#if ESPHOME_LOG_LEVEL == ESPHOME_LOG_LEVEL_VERBOSE
616 esph_log_v(TAG, "Total update took %dms", millis() - now);
617#endif
618 }
619
620 // Draw a pixel at the given coordinates.
621 void draw_pixel_at(int x, int y, Color color) override {
622 if (!this->get_clipping().inside(x, y))
623 return;
624 if constexpr (not HAS_HARDWARE_ROTATION) {
626 x = WIDTH - x - 1;
627 y = HEIGHT - y - 1;
629 auto tmp = x;
630 x = WIDTH - y - 1;
631 y = tmp;
633 auto tmp = y;
634 y = HEIGHT - x - 1;
635 x = tmp;
636 }
637 }
639 return;
640 this->buffer_[(y - this->start_line_) * round_buffer(this->get_width_internal()) + x] = convert_color(color);
641 if (x < this->x_low_) {
642 this->x_low_ = x;
643 }
644 if (x > this->x_high_) {
645 this->x_high_ = x;
646 }
647 if (y < this->y_low_) {
648 this->y_low_ = y;
649 }
650 if (y > this->y_high_) {
651 this->y_high_ = y;
652 }
653 }
654
655 // Fills the display with a color.
656 void fill(Color color) override {
657 // If clipping is active, fall back to base implementation
658 if (this->get_clipping().is_set()) {
660 return;
661 }
662
663 this->x_low_ = 0;
664 this->y_low_ = this->start_line_;
665 this->x_high_ = this->get_width_internal() - 1;
666 this->y_high_ = this->end_line_ - 1;
667 std::fill_n(this->buffer_, (this->end_line_ - this->start_line_) * round_buffer(this->get_width_internal()),
668 convert_color(color));
669 }
670
671 protected:
672 // Rotate the coordinates to match the display orientation.
673
674 // Convert a color to the buffer pixel format.
675 static BUFFERTYPE convert_color(const Color &color) {
676 if constexpr (BUFFERPIXEL == PIXEL_MODE_8) {
677 return (color.red & 0xE0) | (color.g & 0xE0) >> 3 | color.b >> 6;
678 } else if constexpr (BUFFERPIXEL == PIXEL_MODE_16) {
679 if constexpr (IS_BIG_ENDIAN) {
680 return (color.r & 0xF8) | color.g >> 5 | (color.g & 0x1C) << 11 | (color.b & 0xF8) << 5;
681 } else {
682 return (color.r & 0xF8) << 8 | (color.g & 0xFC) << 3 | color.b >> 3;
683 }
684 }
685 return static_cast<BUFFERTYPE>(0);
686 }
687
688 BUFFERTYPE *buffer_{};
689 uint16_t x_low_{WIDTH};
690 uint16_t y_low_{HEIGHT};
691 uint16_t x_high_{0};
692 uint16_t y_high_{0};
693 uint16_t start_line_{0};
694 uint16_t end_line_{1};
695};
696
697} // namespace esphome::mipi_spi
uint8_t h
Definition bl0906.h:2
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:272
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:2053
T * allocate(size_t n)
Definition helpers.h:2080
display_writer_t writer_
Definition display.h:790
virtual void clear()
Clear the entire screen by filling it with OFF pixels.
Definition display.cpp:15
virtual void fill(Color color)
Fill the entire screen with the given color.
Definition display.cpp:14
DisplayPage * page_
Definition display.h:791
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:766
DisplayRotation rotation_
Definition display.h:789
const display_writer_t & get_writer() const
Definition display.cpp:898
Class for MIPI SPI displays with a buffer.
Definition mipi_spi.h:530
void draw_pixel_at(int x, int y, Color color) override
Definition mipi_spi.h:621
static constexpr size_t round_buffer(size_t size)
Definition mipi_spi.h:535
void fill(Color color) override
Definition mipi_spi.h:656
static BUFFERTYPE convert_color(const Color &color)
Definition mipi_spi.h:675
Base class for MIPI SPI displays.
Definition mipi_spi.h:95
void update() override
Definition mipi_spi.h:98
uint16_t get_offset_height_() const
Definition mipi_spi.h:354
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:437
int get_width_internal() override
Definition mipi_spi.h:239
static PixelMode get_pixel_mode(display::ColorBitness bitness)
Definition mipi_spi.h:390
void write_command_(uint8_t cmd, const uint8_t *bytes, size_t len)
Definition mipi_spi.h:254
void set_reset_pin(GPIOPin *reset_pin)
Definition mipi_spi.h:101
int get_width() override
Definition mipi_spi.h:120
optional< uint8_t > brightness_
Definition mipi_spi.h:502
void set_invert_colors(bool invert_colors)
Definition mipi_spi.h:104
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
Definition mipi_spi.h:214
int get_height_internal() override
Definition mipi_spi.h:244
void write_display_data_(const uint8_t *ptr, size_t w, size_t h, size_t pad)
Writes a buffer to the display.
Definition mipi_spi.h:408
void dump_config() override
Definition mipi_spi.h:229
uint16_t get_offset_width_() const
Definition mipi_spi.h:338
void set_brightness(uint8_t brightness)
Definition mipi_spi.h:108
int get_height() override
Definition mipi_spi.h:127
void draw_pixel_at(int x, int y, Color color) override
Definition mipi_spi.h:99
void write_command_(uint8_t cmd)
Definition mipi_spi.h:251
display::DisplayType get_display_type() override
Definition mipi_spi.h:118
std::vector< uint8_t > init_sequence_
Definition mipi_spi.h:504
void set_dc_pin(GPIOPin *dc_pin)
Definition mipi_spi.h:103
void set_rotation(display::DisplayRotation rotation) override
Definition mipi_spi.h:112
void write_command_(uint8_t cmd, uint8_t data)
Definition mipi_spi.h:250
void set_addr_window_(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
Definition mipi_spi.h:371
void setup() override
Definition mipi_spi.h:137
void set_init_sequence(const std::vector< uint8_t > &sequence)
Definition mipi_spi.h:134
void set_model(const char *model)
Definition mipi_spi.h:100
std::vector< GPIOPin * > enable_pins_
Definition mipi_spi.h:497
void set_enable_pins(std::vector< GPIOPin * > enable_pins)
Definition mipi_spi.h:102
uint32_t data_rate_
Definition spi.h:412
The SPIDevice is what components using the SPI will create.
Definition spi.h:429
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:473
uint16_t reset
Definition ina226.h:5
uint8_t duration
Definition msa3xx.h:0
@ DISPLAY_ROTATION_270_DEGREES
Definition display.h:137
@ DISPLAY_ROTATION_180_DEGREES
Definition display.h:136
@ DISPLAY_ROTATION_90_DEGREES
Definition display.h:135
const uint8_t MADCTL_CMD
Definition mipi_dsi.h:25
const uint8_t MADCTL_YFLIP
Definition mipi_dsi.h:36
const uint8_t INVERT_ON
Definition mipi_dsi.h:27
const uint8_t DISPLAY_ON
Definition mipi_dsi.h:28
const uint8_t MADCTL_MV
Definition mipi_dsi.h:34
const uint8_t MADCTL_MX
Definition mipi_dsi.h:32
const uint8_t MADCTL_MY
Definition mipi_dsi.h:33
const uint8_t MADCTL_XFLIP
Definition mipi_dsi.h:35
const uint8_t INVERT_OFF
Definition mipi_dsi.h:26
const uint8_t DELAY_FLAG
Definition mipi_dsi.h:30
const uint8_t SLEEP_OUT
Definition mipi_dsi.h:23
const uint8_t SW_RESET_CMD
Definition mipi_dsi.h:22
const uint8_t MADCTL_BGR
Definition mipi_dsi.h:31
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
T clamp_at_most(T value, U max)
Definition helpers.h:2191
const void size_t len
Definition hal.h:64
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:340
uint16_t size
Definition helpers.cpp:25
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:1386
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:28
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
uint8_t pad
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6