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