ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
mipi_rgb.cpp
Go to the documentation of this file.
1#if defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) || defined(USE_ESP32_VARIANT_ESP32S31)
2#include "mipi_rgb.h"
3#include "esphome/core/gpio.h"
4#include "esphome/core/hal.h"
6#include "esphome/core/log.h"
7
8#include <algorithm>
9#include <cstring>
10#include <driver/gpio.h>
11#include <esp_lcd_panel_ops.h>
12#include <span>
13
15
16static const uint8_t DELAY_FLAG = 0xFF;
17
18// Maximum bytes to log for init commands (truncated if larger)
19static constexpr size_t MIPI_RGB_MAX_CMD_LOG_BYTES = 64;
20static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top
21static constexpr uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left
22static constexpr uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes
23static constexpr uint8_t MADCTL_ML = 0x10; // Bit 4 Refresh bottom to top
24static constexpr uint8_t MADCTL_BGR = 0x08; // Bit 3 Blue-Green-Red pixel order
25static constexpr uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horizontally
26static constexpr uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically
27
29 if (!this->enable_pins_.empty()) {
30 for (auto *pin : this->enable_pins_) {
31 pin->setup();
32 pin->digital_write(true);
33 }
34 delay(10);
35 }
36 if (this->reset_pin_ != nullptr) {
37 this->reset_pin_->setup();
38 this->reset_pin_->digital_write(true);
39 delay(5);
40 this->reset_pin_->digital_write(false);
41 delay(5);
42 this->reset_pin_->digital_write(true);
43 }
44}
45
46#ifdef USE_SPI
48 this->setup_enables_();
49 this->spi_setup();
50 this->common_setup_();
51 if (this->is_failed())
52 return;
54}
55void MipiRgbSpi::write_command_(uint8_t value) {
56 this->enable();
57 if (this->dc_pin_ == nullptr) {
58 this->write(value, 9);
59 } else {
60 this->dc_pin_->digital_write(false);
61 this->write_byte(value);
62 this->dc_pin_->digital_write(true);
63 }
64 this->disable();
65}
66
67void MipiRgbSpi::write_data_(uint8_t value) {
68 this->enable();
69 if (this->dc_pin_ == nullptr) {
70 this->write(value | 0x100, 9);
71 } else {
72 this->dc_pin_->digital_write(true);
73 this->write_byte(value);
74 }
75 this->disable();
76}
77
83 size_t index = 0;
84 const uint8_t *seq = this->init_sequence_;
85 const size_t len = this->init_sequence_len_;
86 while (index != len) {
87 if (len - index < 2) {
88 this->mark_failed(LOG_STR("Malformed init sequence"));
89 return;
90 }
91 uint8_t cmd = seq[index++];
92 uint8_t x = seq[index++];
93 if (x == DELAY_FLAG) {
94 ESP_LOGD(TAG, "Delay %dms", cmd);
95 delay(cmd);
96 } else {
97 uint8_t num_args = x & 0x7F;
98 if (len - index < num_args) {
99 this->mark_failed(LOG_STR("Malformed init sequence"));
100 return;
101 }
102 if (cmd == SLEEP_OUT) {
103 delay(120); // NOLINT
104 }
105 const auto *ptr = seq + index;
106 char hex_buf[format_hex_pretty_size(MIPI_RGB_MAX_CMD_LOG_BYTES)];
107 ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, num_args,
108 format_hex_pretty_to(hex_buf, ptr, num_args, '.'));
109 index += num_args;
110 this->write_command_(cmd);
111 while (num_args-- != 0)
112 this->write_data_(*ptr++);
113 if (cmd == SLEEP_OUT)
114 delay(10);
115 }
116 }
117 // this->spi_teardown(); // SPI not needed after this
118 delay(10);
119}
120
123 LOG_PIN(" CS Pin: ", this->cs_);
124 LOG_PIN(" DC Pin: ", this->dc_pin_);
125 ESP_LOGCONFIG(TAG, " SPI Data rate: %uMHz", (unsigned) (this->data_rate_ / 1000000));
126}
127
128#endif // USE_SPI
129
131 this->setup_enables_();
132 this->common_setup_();
133}
134
136 esp_lcd_rgb_panel_config_t config{};
137 config.flags.fb_in_psram = 1;
138 config.bounce_buffer_size_px = this->width_ * 10;
139 config.num_fbs = 1;
140 config.timings.h_res = this->width_;
141 config.timings.v_res = this->height_;
142 config.timings.hsync_pulse_width = this->hsync_pulse_width_;
143 config.timings.hsync_back_porch = this->hsync_back_porch_;
144 config.timings.hsync_front_porch = this->hsync_front_porch_;
145 config.timings.vsync_pulse_width = this->vsync_pulse_width_;
146 config.timings.vsync_back_porch = this->vsync_back_porch_;
147 config.timings.vsync_front_porch = this->vsync_front_porch_;
148 config.timings.flags.pclk_active_neg = this->pclk_inverted_;
149 config.timings.pclk_hz = this->pclk_frequency_;
150 config.clk_src = LCD_CLK_SRC_PLL160M;
151 size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
152 for (size_t i = 0; i != data_pin_count; i++) {
153 config.data_gpio_nums[i] = static_cast<gpio_num_t>(this->data_pins_[i]->get_pin());
154 }
155 config.data_width = data_pin_count;
156 config.disp_gpio_num = GPIO_NUM_NC;
157 if (this->hsync_pin_) {
158 config.hsync_gpio_num = static_cast<gpio_num_t>(this->hsync_pin_->get_pin());
159 } else {
160 config.hsync_gpio_num = GPIO_NUM_NC;
161 }
162 if (this->vsync_pin_) {
163 config.vsync_gpio_num = static_cast<gpio_num_t>(this->vsync_pin_->get_pin());
164 } else {
165 config.vsync_gpio_num = GPIO_NUM_NC;
166 }
167 if (this->de_pin_) {
168 config.de_gpio_num = static_cast<gpio_num_t>(this->de_pin_->get_pin());
169 } else {
170 config.de_gpio_num = GPIO_NUM_NC;
171 }
172 config.pclk_gpio_num = static_cast<gpio_num_t>(this->pclk_pin_->get_pin());
173 esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
174 if (err == ESP_OK)
175 err = esp_lcd_panel_reset(this->handle_);
176 if (err == ESP_OK)
177 err = esp_lcd_panel_init(this->handle_);
178 if (err == ESP_OK) {
179 void *fb = nullptr;
180 err = esp_lcd_rgb_panel_get_frame_buffer(this->handle_, 1, &fb);
181 if (err == ESP_OK && fb != nullptr) {
182 this->fb_ = reinterpret_cast<uint16_t *>(fb);
183 } else if (err == ESP_OK) {
184 err = ESP_ERR_NO_MEM;
185 }
186 }
187 if (err != ESP_OK) {
188 ESP_LOGE(TAG, "lcd setup failed: %s", esp_err_to_name(err));
189 this->mark_failed(LOG_STR("lcd setup failed"));
190 }
191 ESP_LOGCONFIG(TAG, "MipiRgb setup complete");
192}
193
195 if (this->is_failed())
196 return;
197 if (this->auto_clear_enabled_) {
198 this->clear();
199 }
200 this->updating_ = true;
201 if (this->show_test_card_) {
202 this->test_card();
203 } else if (this->page_ != nullptr) {
204 this->page_->get_writer()(*this);
205 } else if (this->writer_.has_value()) {
206 (*this->writer_)(*this);
207 } else {
208 this->stop_poller();
209 }
210 this->updating_ = false;
211 this->flush_dirty_();
212}
213
215 // Inside update() the one flush at its end sends everything
216 if (this->updating_ || this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
217 return;
218 ESP_LOGV(TAG, "x_low %d, y_low %d, x_high %d, y_high %d", this->x_low_, this->y_low_, this->x_high_, this->y_high_);
219 int w = this->x_high_ - this->x_low_ + 1;
220 int h = this->y_high_ - this->y_low_ + 1;
221 this->write_to_display_(this->x_low_, this->y_low_, w, h, reinterpret_cast<const uint8_t *>(this->buffer_),
222 this->x_low_, this->y_low_, this->width_ - w - this->x_low_);
223 // invalidate watermarks
224 this->x_low_ = this->width_;
225 this->y_low_ = this->height_;
226 this->x_high_ = 0;
227 this->y_high_ = 0;
228}
229
230void MipiRgb::draw_pixels_internal(int x_start, int y_start, int w, int h, const uint8_t *ptr,
231 display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset,
232 int x_pad) {
233 if (w <= 0 || h <= 0 || this->is_failed())
234 return;
236 Display::draw_pixels_internal(x_start, y_start, w, h, ptr, bitness, big_endian, x_offset, y_offset, x_pad);
237 this->mark_dirty_(x_start, y_start, w, h);
238 this->flush_dirty_();
239 return;
240 }
241
242 const int log_w = this->get_width();
243 const int log_h = this->get_height();
244
245 if (x_start < 0 || y_start < 0 || x_start + w > log_w || y_start + h > log_h) {
246 return;
247 }
248
249 uint16_t *dst = this->buffer_ != nullptr ? this->buffer_ : this->fb_;
250 const size_t stride = static_cast<size_t>(x_offset) + w + x_pad;
251 const auto *src_origin =
252 reinterpret_cast<const uint16_t *>(ptr) + (static_cast<size_t>(y_offset) * stride + x_offset);
253
254 blit_rotated_565(dst, this->width_, this->height_, this->rotation_, src_origin, x_start, y_start, w, h, stride);
255 if (dst == this->buffer_) {
256 this->mark_dirty_(x_start, y_start, w, h);
257 if (!this->updating_) {
258 this->flush_dirty_();
259 }
260 }
261}
262
263void MipiRgb::write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
264 int x_pad) {
265 esp_err_t err = ESP_OK;
266 auto stride = (x_offset + w + x_pad) * 2;
267 ptr += y_offset * stride + x_offset * 2; // skip to the first pixel
268 // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
269 if (x_offset == 0 && x_pad == 0) {
270 err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr);
271 } else {
272 // draw line by line
273 for (int y = 0; y != h; y++) {
274 err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, ptr);
275 if (err != ESP_OK)
276 break;
277 ptr += stride; // next line
278 }
279 }
280 if (err != ESP_OK) {
281 ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
282 }
283}
284
286 if (this->is_failed())
287 return false;
288 if (this->buffer_ != nullptr)
289 return true;
290 // this is dependent on the enum values.
291 RAMAllocator<uint16_t> allocator;
292 this->buffer_ = allocator.allocate(this->height_ * this->width_);
293 if (this->buffer_ == nullptr) {
294 this->mark_failed(LOG_STR("Could not allocate buffer for display!"));
295 return false;
296 }
297 return true;
298}
299
300// Widen the dirty box to the drawn rectangle, even where the buffer already held the same values.
301void MipiRgb::mark_dirty_(int x_start, int y_start, int w, int h) {
302 int x1 = x_start, y1 = y_start;
303 int x2 = x_start + w - 1, y2 = y_start + h - 1;
304 this->rotate_(x1, y1);
305 this->rotate_(x2, y2);
306 const int right = static_cast<int>(this->width_) - 1;
307 const int bottom = static_cast<int>(this->height_) - 1;
308 const int left = std::clamp(std::min(x1, x2), 0, right);
309 const int top = std::clamp(std::min(y1, y2), 0, bottom);
310 x2 = std::clamp(std::max(x1, x2), 0, right);
311 y2 = std::clamp(std::max(y1, y2), 0, bottom);
312 // An empty box is low above high, so it is replaced rather than widened
313 if (this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_) {
314 this->x_low_ = left;
315 this->x_high_ = x2;
316 this->y_low_ = top;
317 this->y_high_ = y2;
318 return;
319 }
320 this->x_low_ = std::min<int>(this->x_low_, left);
321 this->x_high_ = std::max<int>(this->x_high_, x2);
322 this->y_low_ = std::min<int>(this->y_low_, top);
323 this->y_high_ = std::max<int>(this->y_high_, y2);
324}
325
326void MipiRgb::draw_pixel_at(int x, int y, Color color) {
327 if (this->is_point_clipped(x, y) || this->is_failed())
328 return;
329
330 this->rotate_(x, y);
331 if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
332 return;
333 }
334 if (!this->check_buffer_())
335 return;
336 size_t pos = (y * this->width_) + x;
337 uint16_t new_color = this->to_panel_565_(color);
338 if (this->buffer_[pos] == new_color)
339 return;
340 this->buffer_[pos] = new_color;
341 // low and high watermark may speed up drawing from buffer
343 this->x_low_ = x;
345 this->y_low_ = y;
346 if (x > this->x_high_)
347 this->x_high_ = x;
348 if (y > this->y_high_)
349 this->y_high_ = y;
350}
351void MipiRgb::fill(Color color) {
352 if (!this->check_buffer_())
353 return;
354
355 // If clipping is active, fall back to base implementation
356 if (this->get_clipping().is_set()) {
357 Display::fill(color);
358 return;
359 }
360
361 auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_);
362 uint16_t new_color = this->to_panel_565_(color);
363 std::fill_n(ptr_16, this->width_ * this->height_, new_color);
364 this->x_low_ = 0;
365 this->y_low_ = 0;
366 this->x_high_ = this->width_ - 1;
367 this->y_high_ = this->height_ - 1;
368}
369
381
393
394[[maybe_unused]] static const char *get_pin_name(GPIOPin *pin, std::span<char, GPIO_SUMMARY_MAX_LEN> buffer) {
395 if (pin == nullptr)
396 return "None";
397 pin->dump_summary(buffer.data(), buffer.size());
398 return buffer.data();
399}
400
401void MipiRgb::dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset) {
402 char pin_summary[GPIO_SUMMARY_MAX_LEN];
403 for (uint8_t i = start; i != end; i++) {
404 this->data_pins_[i]->dump_summary(pin_summary, sizeof(pin_summary));
405 ESP_LOGCONFIG(TAG, " %s pin %d: %s", name, offset++, pin_summary);
406 }
407}
408
410 char reset_buf[GPIO_SUMMARY_MAX_LEN];
411 char de_buf[GPIO_SUMMARY_MAX_LEN];
412 char pclk_buf[GPIO_SUMMARY_MAX_LEN];
413 char hsync_buf[GPIO_SUMMARY_MAX_LEN];
414 char vsync_buf[GPIO_SUMMARY_MAX_LEN];
415 ESP_LOGCONFIG(TAG,
416 "MIPI_RGB LCD"
417 "\n Model: %s"
418 "\n Width: %u"
419 "\n Height: %u"
420 "\n Rotation: %d degrees"
421 "\n PCLK Inverted: %s"
422 "\n HSync Pulse Width: %u"
423 "\n HSync Back Porch: %u"
424 "\n HSync Front Porch: %u"
425 "\n VSync Pulse Width: %u"
426 "\n VSync Back Porch: %u"
427 "\n VSync Front Porch: %u"
428 "\n Invert Colors: %s"
429 "\n Pixel Clock: %uMHz"
430 "\n Reset Pin: %s"
431 "\n DE Pin: %s"
432 "\n PCLK Pin: %s"
433 "\n HSYNC Pin: %s"
434 "\n VSYNC Pin: %s",
435 this->model_, this->width_, this->height_, this->rotation_, YESNO(this->pclk_inverted_),
437 this->vsync_back_porch_, this->vsync_front_porch_, YESNO(this->invert_colors_),
438 (unsigned) (this->pclk_frequency_ / 1000000), get_pin_name(this->reset_pin_, reset_buf),
439 get_pin_name(this->de_pin_, de_buf), get_pin_name(this->pclk_pin_, pclk_buf),
440 get_pin_name(this->hsync_pin_, hsync_buf), get_pin_name(this->vsync_pin_, vsync_buf));
441
442 this->dump_pins_(8, 13, "Blue", 0);
443 this->dump_pins_(13, 16, "Green", 0);
444 this->dump_pins_(0, 3, "Green", 3);
445 this->dump_pins_(3, 8, "Red", 0);
446}
447
448} // namespace esphome::mipi_rgb
449#endif // defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4) ||
450 // defined(USE_ESP32_VARIANT_ESP32S31)
uint8_t h
Definition bl0906.h:2
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
virtual void setup()=0
virtual void digital_write(bool value)=0
virtual size_t dump_summary(char *buffer, size_t len) const
Write a summary of this pin to the provided buffer.
Definition gpio_pin.h:126
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2298
T * allocate(size_t n)
Definition helpers.h:2325
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
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
const display_writer_t & get_writer() const
Definition display.cpp:956
size_t dump_summary(char *buffer, size_t len) const override
Definition gpio.cpp:139
InternalGPIOPin * de_pin_
Definition mipi_rgb.h:92
std::vector< GPIOPin * > enable_pins_
Definition mipi_rgb.h:114
void flush_dirty_()
Send the region touched since the last flush to the panel.
Definition mipi_rgb.cpp:214
esp_lcd_panel_handle_t handle_
Definition mipi_rgb.h:120
int get_height() override
Definition mipi_rgb.cpp:382
InternalGPIOPin * hsync_pin_
Definition mipi_rgb.h:94
void dump_config() override
Definition mipi_rgb.cpp:409
int get_height_internal() override
Definition mipi_rgb.h:67
InternalGPIOPin * data_pins_[16]
Definition mipi_rgb.h:97
void draw_pixel_at(int x, int y, Color color) override
Definition mipi_rgb.cpp:326
void rotate_(int &x, int &y) const
Definition mipi_rgb.h:87
uint16_t to_panel_565_(Color color) const
Pack a colour as the 565 value the panel's byte order expects in memory.
Definition mipi_rgb.h:80
InternalGPIOPin * vsync_pin_
Definition mipi_rgb.h:95
void dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset)
Definition mipi_rgb.cpp:401
void mark_dirty_(int x_start, int y_start, int w, int h)
Definition mipi_rgb.cpp:301
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_rgb.cpp:230
InternalGPIOPin * pclk_pin_
Definition mipi_rgb.h:93
void fill(Color color) override
Definition mipi_rgb.cpp:351
int get_width_internal() override
Definition mipi_rgb.h:66
void write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset, int x_pad)
Definition mipi_rgb.cpp:263
void write_data_(uint8_t value)
Definition mipi_rgb.cpp:67
void write_init_sequence_()
this relies upon the init sequence being well-formed, which is guaranteed by the Python init code.
Definition mipi_rgb.cpp:82
void write_command_(uint8_t value)
Definition mipi_rgb.cpp:55
const uint8_t * init_sequence_
Definition mipi_rgb.h:146
uint32_t data_rate_
Definition spi.h:436
uint32_t buffer
Definition gif.cpp:50
@ 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
const uint8_t SLEEP_OUT
Definition mipi_rgb.h:19
void blit_rotated_565(uint16_t *fb, int fb_width, int fb_height, display::DisplayRotation rotation, const uint16_t *src_origin, int x_start, int y_start, int w, int h, size_t stride)
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
size_t size_t pos
Definition helpers.h:1255
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
constexpr size_t GPIO_SUMMARY_MAX_LEN
Maximum buffer size for dump_summary output.
Definition gpio_pin.h:14
void HOT delay(uint32_t ms)
Definition hal.cpp:85
uint8_t end[39]
Definition sun_gtil2.cpp:17
uint16_t seq
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6