ESPHome 2026.5.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)
2#include "mipi_rgb.h"
3#include "esphome/core/gpio.h"
4#include "esphome/core/hal.h"
6#include "esphome/core/log.h"
7#include <driver/gpio.h>
8#include <esp_lcd_panel_rgb.h>
9#include <span>
10
11namespace esphome {
12namespace mipi_rgb {
13
14static const uint8_t DELAY_FLAG = 0xFF;
15
16// Maximum bytes to log for init commands (truncated if larger)
17static constexpr size_t MIPI_RGB_MAX_CMD_LOG_BYTES = 64;
18static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top
19static constexpr uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left
20static constexpr uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes
21static constexpr uint8_t MADCTL_ML = 0x10; // Bit 4 Refresh bottom to top
22static constexpr uint8_t MADCTL_BGR = 0x08; // Bit 3 Blue-Green-Red pixel order
23static constexpr uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horizontally
24static constexpr uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically
25
27 if (!this->enable_pins_.empty()) {
28 for (auto *pin : this->enable_pins_) {
29 pin->setup();
30 pin->digital_write(true);
31 }
32 delay(10);
33 }
34 if (this->reset_pin_ != nullptr) {
35 this->reset_pin_->setup();
36 this->reset_pin_->digital_write(true);
37 delay(5);
38 this->reset_pin_->digital_write(false);
39 delay(5);
40 this->reset_pin_->digital_write(true);
41 }
42}
43
44#ifdef USE_SPI
46 this->setup_enables_();
47 this->spi_setup();
49 this->common_setup_();
50}
51void MipiRgbSpi::write_command_(uint8_t value) {
52 this->enable();
53 if (this->dc_pin_ == nullptr) {
54 this->write(value, 9);
55 } else {
56 this->dc_pin_->digital_write(false);
57 this->write_byte(value);
58 this->dc_pin_->digital_write(true);
59 }
60 this->disable();
61}
62
63void MipiRgbSpi::write_data_(uint8_t value) {
64 this->enable();
65 if (this->dc_pin_ == nullptr) {
66 this->write(value | 0x100, 9);
67 } else {
68 this->dc_pin_->digital_write(true);
69 this->write_byte(value);
70 }
71 this->disable();
72}
73
79 size_t index = 0;
80 auto &vec = this->init_sequence_;
81 while (index != vec.size()) {
82 if (vec.size() - index < 2) {
83 this->mark_failed(LOG_STR("Malformed init sequence"));
84 return;
85 }
86 uint8_t cmd = vec[index++];
87 uint8_t x = vec[index++];
88 if (x == DELAY_FLAG) {
89 ESP_LOGD(TAG, "Delay %dms", cmd);
90 delay(cmd);
91 } else {
92 uint8_t num_args = x & 0x7F;
93 if (vec.size() - index < num_args) {
94 this->mark_failed(LOG_STR("Malformed init sequence"));
95 return;
96 }
97 if (cmd == SLEEP_OUT) {
98 delay(120); // NOLINT
99 }
100 const auto *ptr = vec.data() + index;
101 char hex_buf[format_hex_pretty_size(MIPI_RGB_MAX_CMD_LOG_BYTES)];
102 ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, num_args,
103 format_hex_pretty_to(hex_buf, ptr, num_args, '.'));
104 index += num_args;
105 this->write_command_(cmd);
106 while (num_args-- != 0)
107 this->write_data_(*ptr++);
108 if (cmd == SLEEP_OUT)
109 delay(10);
110 }
111 }
112 // this->spi_teardown(); // SPI not needed after this
113 this->init_sequence_.clear();
114 delay(10);
115}
116
119 LOG_PIN(" CS Pin: ", this->cs_);
120 LOG_PIN(" DC Pin: ", this->dc_pin_);
121 ESP_LOGCONFIG(TAG, " SPI Data rate: %uMHz", (unsigned) (this->data_rate_ / 1000000));
122}
123
124#endif // USE_SPI
125
127 this->setup_enables_();
128 this->common_setup_();
129}
130
132 esp_lcd_rgb_panel_config_t config{};
133 config.flags.fb_in_psram = 1;
134 config.bounce_buffer_size_px = this->width_ * 10;
135 config.num_fbs = 1;
136 config.timings.h_res = this->width_;
137 config.timings.v_res = this->height_;
138 config.timings.hsync_pulse_width = this->hsync_pulse_width_;
139 config.timings.hsync_back_porch = this->hsync_back_porch_;
140 config.timings.hsync_front_porch = this->hsync_front_porch_;
141 config.timings.vsync_pulse_width = this->vsync_pulse_width_;
142 config.timings.vsync_back_porch = this->vsync_back_porch_;
143 config.timings.vsync_front_porch = this->vsync_front_porch_;
144 config.timings.flags.pclk_active_neg = this->pclk_inverted_;
145 config.timings.pclk_hz = this->pclk_frequency_;
146 config.clk_src = LCD_CLK_SRC_PLL160M;
147 size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
148 for (size_t i = 0; i != data_pin_count; i++) {
149 config.data_gpio_nums[i] = static_cast<gpio_num_t>(this->data_pins_[i]->get_pin());
150 }
151 config.data_width = data_pin_count;
152 config.disp_gpio_num = GPIO_NUM_NC;
153 if (this->hsync_pin_) {
154 config.hsync_gpio_num = static_cast<gpio_num_t>(this->hsync_pin_->get_pin());
155 } else {
156 config.hsync_gpio_num = GPIO_NUM_NC;
157 }
158 if (this->vsync_pin_) {
159 config.vsync_gpio_num = static_cast<gpio_num_t>(this->vsync_pin_->get_pin());
160 } else {
161 config.vsync_gpio_num = GPIO_NUM_NC;
162 }
163 if (this->de_pin_) {
164 config.de_gpio_num = static_cast<gpio_num_t>(this->de_pin_->get_pin());
165 } else {
166 config.de_gpio_num = GPIO_NUM_NC;
167 }
168 config.pclk_gpio_num = static_cast<gpio_num_t>(this->pclk_pin_->get_pin());
169 esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
170 if (err == ESP_OK)
171 err = esp_lcd_panel_reset(this->handle_);
172 if (err == ESP_OK)
173 err = esp_lcd_panel_init(this->handle_);
174 if (err != ESP_OK) {
175 ESP_LOGE(TAG, "lcd setup failed: %s", esp_err_to_name(err));
176 this->mark_failed(LOG_STR("lcd setup failed"));
177 }
178 ESP_LOGCONFIG(TAG, "MipiRgb setup complete");
179}
180
182 if (this->handle_ != nullptr)
183 esp_lcd_rgb_panel_restart(this->handle_);
184}
185
187 if (this->is_failed())
188 return;
189 if (this->auto_clear_enabled_) {
190 this->clear();
191 }
192 if (this->show_test_card_) {
193 this->test_card();
194 } else if (this->page_ != nullptr) {
195 this->page_->get_writer()(*this);
196 } else if (this->writer_.has_value()) {
197 (*this->writer_)(*this);
198 } else {
199 this->stop_poller();
200 }
201 if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
202 return;
203 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_);
204 int w = this->x_high_ - this->x_low_ + 1;
205 int h = this->y_high_ - this->y_low_ + 1;
206 this->write_to_display_(this->x_low_, this->y_low_, w, h, reinterpret_cast<const uint8_t *>(this->buffer_),
207 this->x_low_, this->y_low_, this->width_ - w - this->x_low_);
208 // invalidate watermarks
209 this->x_low_ = this->width_;
210 this->y_low_ = this->height_;
211 this->x_high_ = 0;
212 this->y_high_ = 0;
213}
214
215void MipiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
216 display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
217 if (w <= 0 || h <= 0 || this->is_failed())
218 return;
219 // if color mapping is required, pass the buck.
220 // note that endianness is not considered here - it is assumed to match!
221 if (bitness != display::COLOR_BITNESS_565) {
222 Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad);
223 this->write_to_display_(x_start, y_start, w, h, reinterpret_cast<const uint8_t *>(this->buffer_), x_start, y_start,
224 this->width_ - w - x_start);
225 } else {
226 this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
227 }
228}
229
230void MipiRgb::write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
231 int x_pad) {
232 esp_err_t err = ESP_OK;
233 auto stride = (x_offset + w + x_pad) * 2;
234 ptr += y_offset * stride + x_offset * 2; // skip to the first pixel
235 // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
236 if (x_offset == 0 && x_pad == 0) {
237 err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr);
238 } else {
239 // draw line by line
240 for (int y = 0; y != h; y++) {
241 err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, ptr);
242 if (err != ESP_OK)
243 break;
244 ptr += stride; // next line
245 }
246 }
247 if (err != ESP_OK)
248 ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
249}
250
252 if (this->is_failed())
253 return false;
254 if (this->buffer_ != nullptr)
255 return true;
256 // this is dependent on the enum values.
257 RAMAllocator<uint16_t> allocator;
258 this->buffer_ = allocator.allocate(this->height_ * this->width_);
259 if (this->buffer_ == nullptr) {
260 this->mark_failed(LOG_STR("Could not allocate buffer for display!"));
261 return false;
262 }
263 return true;
264}
265
266void MipiRgb::draw_pixel_at(int x, int y, Color color) {
267 if (!this->get_clipping().inside(x, y) || this->is_failed())
268 return;
269
270 switch (this->rotation_) {
272 break;
274 std::swap(x, y);
275 x = this->width_ - x - 1;
276 break;
278 x = this->width_ - x - 1;
279 y = this->height_ - y - 1;
280 break;
282 std::swap(x, y);
283 y = this->height_ - y - 1;
284 break;
285 }
286 if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
287 return;
288 }
289 if (!this->check_buffer_())
290 return;
291 size_t pos = (y * this->width_) + x;
292 uint16_t new_color = convert_big_endian(display::ColorUtil::color_to_565(color));
293 if (this->buffer_[pos] == new_color)
294 return;
295 this->buffer_[pos] = new_color;
296 // low and high watermark may speed up drawing from buffer
298 this->x_low_ = x;
300 this->y_low_ = y;
301 if (x > this->x_high_)
302 this->x_high_ = x;
303 if (y > this->y_high_)
304 this->y_high_ = y;
305}
306void MipiRgb::fill(Color color) {
307 if (!this->check_buffer_())
308 return;
309
310 // If clipping is active, fall back to base implementation
311 if (this->get_clipping().is_set()) {
312 Display::fill(color);
313 return;
314 }
315
316 auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_);
317 uint16_t new_color = convert_big_endian(display::ColorUtil::color_to_565(color));
318 std::fill_n(ptr_16, this->width_ * this->height_, new_color);
319 this->x_low_ = 0;
320 this->y_low_ = 0;
321 this->x_high_ = this->width_ - 1;
322 this->y_high_ = this->height_ - 1;
323}
324
336
348
349static const char *get_pin_name(GPIOPin *pin, std::span<char, GPIO_SUMMARY_MAX_LEN> buffer) {
350 if (pin == nullptr)
351 return "None";
352 pin->dump_summary(buffer.data(), buffer.size());
353 return buffer.data();
354}
355
356void MipiRgb::dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset) {
357 char pin_summary[GPIO_SUMMARY_MAX_LEN];
358 for (uint8_t i = start; i != end; i++) {
359 this->data_pins_[i]->dump_summary(pin_summary, sizeof(pin_summary));
360 ESP_LOGCONFIG(TAG, " %s pin %d: %s", name, offset++, pin_summary);
361 }
362}
363
365 char reset_buf[GPIO_SUMMARY_MAX_LEN];
366 char de_buf[GPIO_SUMMARY_MAX_LEN];
367 char pclk_buf[GPIO_SUMMARY_MAX_LEN];
368 char hsync_buf[GPIO_SUMMARY_MAX_LEN];
369 char vsync_buf[GPIO_SUMMARY_MAX_LEN];
370 ESP_LOGCONFIG(TAG,
371 "MIPI_RGB LCD"
372 "\n Model: %s"
373 "\n Width: %u"
374 "\n Height: %u"
375 "\n Rotation: %d degrees"
376 "\n PCLK Inverted: %s"
377 "\n HSync Pulse Width: %u"
378 "\n HSync Back Porch: %u"
379 "\n HSync Front Porch: %u"
380 "\n VSync Pulse Width: %u"
381 "\n VSync Back Porch: %u"
382 "\n VSync Front Porch: %u"
383 "\n Invert Colors: %s"
384 "\n Pixel Clock: %uMHz"
385 "\n Reset Pin: %s"
386 "\n DE Pin: %s"
387 "\n PCLK Pin: %s"
388 "\n HSYNC Pin: %s"
389 "\n VSYNC Pin: %s",
390 this->model_, this->width_, this->height_, this->rotation_, YESNO(this->pclk_inverted_),
392 this->vsync_back_porch_, this->vsync_front_porch_, YESNO(this->invert_colors_),
393 (unsigned) (this->pclk_frequency_ / 1000000), get_pin_name(this->reset_pin_, reset_buf),
394 get_pin_name(this->de_pin_, de_buf), get_pin_name(this->pclk_pin_, pclk_buf),
395 get_pin_name(this->hsync_pin_, hsync_buf), get_pin_name(this->vsync_pin_, vsync_buf));
396
397 this->dump_pins_(8, 13, "Blue", 0);
398 this->dump_pins_(13, 16, "Green", 0);
399 this->dump_pins_(0, 3, "Green", 3);
400 this->dump_pins_(3, 8, "Red", 0);
401}
402
403} // namespace mipi_rgb
404} // namespace esphome
405#endif // defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
uint8_t h
Definition bl0906.h:2
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:284
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.h:129
virtual uint8_t get_pin() const =0
An STL allocator that uses SPI or internal RAM.
Definition helpers.h:2212
T * allocate(size_t n)
Definition helpers.h:2229
static uint16_t color_to_565(Color color, ColorOrder color_order=ColorOrder::COLOR_ORDER_RGB)
display_writer_t writer_
Definition display.h:791
virtual void clear()
Clear the entire screen by filling it with OFF pixels.
Definition display.cpp:15
DisplayPage * page_
Definition display.h:792
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:766
DisplayRotation rotation_
Definition display.h:790
const display_writer_t & get_writer() const
Definition display.cpp:898
InternalGPIOPin * de_pin_
Definition mipi_rgb.h:72
std::vector< GPIOPin * > enable_pins_
Definition mipi_rgb.h:92
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_rgb.cpp:215
esp_lcd_panel_handle_t handle_
Definition mipi_rgb.h:98
int get_height() override
Definition mipi_rgb.cpp:337
InternalGPIOPin * hsync_pin_
Definition mipi_rgb.h:74
void dump_config() override
Definition mipi_rgb.cpp:364
int get_height_internal() override
Definition mipi_rgb.h:63
InternalGPIOPin * data_pins_[16]
Definition mipi_rgb.h:77
void draw_pixel_at(int x, int y, Color color) override
Definition mipi_rgb.cpp:266
InternalGPIOPin * vsync_pin_
Definition mipi_rgb.h:75
void dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset)
Definition mipi_rgb.cpp:356
InternalGPIOPin * pclk_pin_
Definition mipi_rgb.h:73
void fill(Color color) override
Definition mipi_rgb.cpp:306
int get_width_internal() override
Definition mipi_rgb.h:62
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:230
void write_data_(uint8_t value)
Definition mipi_rgb.cpp:63
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:78
void write_command_(uint8_t value)
Definition mipi_rgb.cpp:51
std::vector< uint8_t > init_sequence_
Definition mipi_rgb.h:119
uint32_t data_rate_
Definition spi.h:412
@ DISPLAY_ROTATION_0_DEGREES
Definition display.h:135
@ DISPLAY_ROTATION_270_DEGREES
Definition display.h:138
@ DISPLAY_ROTATION_180_DEGREES
Definition display.h:137
@ DISPLAY_ROTATION_90_DEGREES
Definition display.h:136
const uint8_t SLEEP_OUT
Definition mipi_rgb.h:16
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
constexpr T convert_big_endian(T val)
Convert a value between host byte order and big endian (most significant byte first) order.
Definition helpers.h:937
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:409
size_t size_t pos
Definition helpers.h:1082
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:1368
constexpr size_t GPIO_SUMMARY_MAX_LEN
Maximum buffer size for dump_summary output.
Definition gpio.h:13
void HOT delay(uint32_t ms)
Definition core.cpp:28
uint8_t end[39]
Definition sun_gtil2.cpp:17
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6