ESPHome 2026.2.1
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 "esp_lcd_panel_rgb.h"
8#include <span>
9
10namespace esphome {
11namespace mipi_rgb {
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,
121 " SPI Data rate: %uMHz"
122 "\n Mirror X: %s"
123 "\n Mirror Y: %s"
124 "\n Swap X/Y: %s"
125 "\n Color Order: %s",
126 (unsigned) (this->data_rate_ / 1000000), YESNO(this->madctl_ & (MADCTL_XFLIP | MADCTL_MX)),
127 YESNO(this->madctl_ & (MADCTL_YFLIP | MADCTL_MY | MADCTL_ML)), YESNO(this->madctl_ & MADCTL_MV),
128 this->madctl_ & MADCTL_BGR ? "BGR" : "RGB");
129}
130
131#endif // USE_SPI
132
134 this->setup_enables_();
135 this->common_setup_();
136}
137
139 esp_lcd_rgb_panel_config_t config{};
140 config.flags.fb_in_psram = 1;
141 config.bounce_buffer_size_px = this->width_ * 10;
142 config.num_fbs = 1;
143 config.timings.h_res = this->width_;
144 config.timings.v_res = this->height_;
145 config.timings.hsync_pulse_width = this->hsync_pulse_width_;
146 config.timings.hsync_back_porch = this->hsync_back_porch_;
147 config.timings.hsync_front_porch = this->hsync_front_porch_;
148 config.timings.vsync_pulse_width = this->vsync_pulse_width_;
149 config.timings.vsync_back_porch = this->vsync_back_porch_;
150 config.timings.vsync_front_porch = this->vsync_front_porch_;
151 config.timings.flags.pclk_active_neg = this->pclk_inverted_;
152 config.timings.pclk_hz = this->pclk_frequency_;
153 config.clk_src = LCD_CLK_SRC_PLL160M;
154 size_t data_pin_count = sizeof(this->data_pins_) / sizeof(this->data_pins_[0]);
155 for (size_t i = 0; i != data_pin_count; i++) {
156 config.data_gpio_nums[i] = this->data_pins_[i]->get_pin();
157 }
158 config.data_width = data_pin_count;
159 config.disp_gpio_num = -1;
160 config.hsync_gpio_num = this->hsync_pin_->get_pin();
161 config.vsync_gpio_num = this->vsync_pin_->get_pin();
162 if (this->de_pin_) {
163 config.de_gpio_num = this->de_pin_->get_pin();
164 } else {
165 config.de_gpio_num = -1;
166 }
167 config.pclk_gpio_num = 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->handle_ != nullptr)
182 esp_lcd_rgb_panel_restart(this->handle_);
183}
184
186 if (this->is_failed())
187 return;
188 if (this->auto_clear_enabled_) {
189 this->clear();
190 }
191 if (this->show_test_card_) {
192 this->test_card();
193 } else if (this->page_ != nullptr) {
194 this->page_->get_writer()(*this);
195 } else if (this->writer_.has_value()) {
196 (*this->writer_)(*this);
197 } else {
198 this->stop_poller();
199 }
200 if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
201 return;
202 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_);
203 int w = this->x_high_ - this->x_low_ + 1;
204 int h = this->y_high_ - this->y_low_ + 1;
205 this->write_to_display_(this->x_low_, this->y_low_, w, h, reinterpret_cast<const uint8_t *>(this->buffer_),
206 this->x_low_, this->y_low_, this->width_ - w - this->x_low_);
207 // invalidate watermarks
208 this->x_low_ = this->width_;
209 this->y_low_ = this->height_;
210 this->x_high_ = 0;
211 this->y_high_ = 0;
212}
213
214void MipiRgb::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) {
216 if (w <= 0 || h <= 0 || this->is_failed())
217 return;
218 // if color mapping is required, pass the buck.
219 // note that endianness is not considered here - it is assumed to match!
220 if (bitness != display::COLOR_BITNESS_565) {
221 Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad);
222 this->write_to_display_(x_start, y_start, w, h, reinterpret_cast<const uint8_t *>(this->buffer_), x_start, y_start,
223 this->width_ - w - x_start);
224 } else {
225 this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
226 }
227}
228
229void MipiRgb::write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
230 int x_pad) {
231 esp_err_t err = ESP_OK;
232 auto stride = (x_offset + w + x_pad) * 2;
233 ptr += y_offset * stride + x_offset * 2; // skip to the first pixel
234 // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
235 if (x_offset == 0 && x_pad == 0) {
236 err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr);
237 } else {
238 // draw line by line
239 for (int y = 0; y != h; y++) {
240 err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, ptr);
241 if (err != ESP_OK)
242 break;
243 ptr += stride; // next line
244 }
245 }
246 if (err != ESP_OK)
247 ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
248}
249
251 if (this->is_failed())
252 return false;
253 if (this->buffer_ != nullptr)
254 return true;
255 // this is dependent on the enum values.
256 RAMAllocator<uint16_t> allocator;
257 this->buffer_ = allocator.allocate(this->height_ * this->width_);
258 if (this->buffer_ == nullptr) {
259 this->mark_failed(LOG_STR("Could not allocate buffer for display!"));
260 return false;
261 }
262 return true;
263}
264
265void MipiRgb::draw_pixel_at(int x, int y, Color color) {
266 if (!this->get_clipping().inside(x, y) || this->is_failed())
267 return;
268
269 switch (this->rotation_) {
271 break;
273 std::swap(x, y);
274 x = this->width_ - x - 1;
275 break;
277 x = this->width_ - x - 1;
278 y = this->height_ - y - 1;
279 break;
281 std::swap(x, y);
282 y = this->height_ - y - 1;
283 break;
284 }
285 if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
286 return;
287 }
288 if (!this->check_buffer_())
289 return;
290 size_t pos = (y * this->width_) + x;
291 uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
292 uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
293 uint16_t new_color = hi_byte | (lo_byte << 8); // big endian
294 if (this->buffer_[pos] == new_color)
295 return;
296 this->buffer_[pos] = new_color;
297 // low and high watermark may speed up drawing from buffer
299 this->x_low_ = x;
301 this->y_low_ = y;
302 if (x > this->x_high_)
303 this->x_high_ = x;
304 if (y > this->y_high_)
305 this->y_high_ = y;
306}
307void MipiRgb::fill(Color color) {
308 if (!this->check_buffer_())
309 return;
310
311 // If clipping is active, fall back to base implementation
312 if (this->get_clipping().is_set()) {
313 Display::fill(color);
314 return;
315 }
316
317 auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_);
318 uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
319 uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
320 uint16_t new_color = lo_byte | (hi_byte << 8); // little endian
321 std::fill_n(ptr_16, this->width_ * this->height_, new_color);
322}
323
335
347
348static const char *get_pin_name(GPIOPin *pin, std::span<char, GPIO_SUMMARY_MAX_LEN> buffer) {
349 if (pin == nullptr)
350 return "None";
351 pin->dump_summary(buffer.data(), buffer.size());
352 return buffer.data();
353}
354
355void MipiRgb::dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset) {
356 char pin_summary[GPIO_SUMMARY_MAX_LEN];
357 for (uint8_t i = start; i != end; i++) {
358 this->data_pins_[i]->dump_summary(pin_summary, sizeof(pin_summary));
359 ESP_LOGCONFIG(TAG, " %s pin %d: %s", name, offset++, pin_summary);
360 }
361}
362
364 char reset_buf[GPIO_SUMMARY_MAX_LEN];
365 char de_buf[GPIO_SUMMARY_MAX_LEN];
366 char pclk_buf[GPIO_SUMMARY_MAX_LEN];
367 char hsync_buf[GPIO_SUMMARY_MAX_LEN];
368 char vsync_buf[GPIO_SUMMARY_MAX_LEN];
369 ESP_LOGCONFIG(TAG,
370 "MIPI_RGB LCD"
371 "\n Model: %s"
372 "\n Width: %u"
373 "\n Height: %u"
374 "\n Rotation: %d degrees"
375 "\n PCLK Inverted: %s"
376 "\n HSync Pulse Width: %u"
377 "\n HSync Back Porch: %u"
378 "\n HSync Front Porch: %u"
379 "\n VSync Pulse Width: %u"
380 "\n VSync Back Porch: %u"
381 "\n VSync Front Porch: %u"
382 "\n Invert Colors: %s"
383 "\n Pixel Clock: %uMHz"
384 "\n Reset Pin: %s"
385 "\n DE Pin: %s"
386 "\n PCLK Pin: %s"
387 "\n HSYNC Pin: %s"
388 "\n VSYNC Pin: %s",
389 this->model_, this->width_, this->height_, this->rotation_, YESNO(this->pclk_inverted_),
391 this->vsync_back_porch_, this->vsync_front_porch_, YESNO(this->invert_colors_),
392 (unsigned) (this->pclk_frequency_ / 1000000), get_pin_name(this->reset_pin_, reset_buf),
393 get_pin_name(this->de_pin_, de_buf), get_pin_name(this->pclk_pin_, pclk_buf),
394 get_pin_name(this->hsync_pin_, hsync_buf), get_pin_name(this->vsync_pin_, vsync_buf));
395
396 this->dump_pins_(8, 13, "Blue", 0);
397 this->dump_pins_(13, 16, "Green", 0);
398 this->dump_pins_(0, 3, "Green", 3);
399 this->dump_pins_(3, 8, "Red", 0);
400}
401
402} // namespace mipi_rgb
403} // namespace esphome
404#endif // defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32P4)
uint8_t h
Definition bl0906.h:2
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
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:1647
T * allocate(size_t n)
Definition helpers.h:1667
display_writer_t writer_
Definition display.h:791
virtual void clear()
Clear the entire screen by filling it with OFF pixels.
Definition display.cpp:16
DisplayPage * page_
Definition display.h:792
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:764
DisplayRotation rotation_
Definition display.h:790
const display_writer_t & get_writer() const
Definition display.cpp:896
InternalGPIOPin * de_pin_
Definition mipi_rgb.h:73
std::vector< GPIOPin * > enable_pins_
Definition mipi_rgb.h:94
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:214
esp_lcd_panel_handle_t handle_
Definition mipi_rgb.h:100
int get_height() override
Definition mipi_rgb.cpp:336
InternalGPIOPin * hsync_pin_
Definition mipi_rgb.h:75
void dump_config() override
Definition mipi_rgb.cpp:363
int get_height_internal() override
Definition mipi_rgb.h:64
InternalGPIOPin * data_pins_[16]
Definition mipi_rgb.h:78
void draw_pixel_at(int x, int y, Color color) override
Definition mipi_rgb.cpp:265
InternalGPIOPin * vsync_pin_
Definition mipi_rgb.h:76
void dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset)
Definition mipi_rgb.cpp:355
InternalGPIOPin * pclk_pin_
Definition mipi_rgb.h:74
void fill(Color color) override
Definition mipi_rgb.cpp:307
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:229
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:121
uint32_t data_rate_
Definition spi.h:410
@ 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
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:353
size_t size_t pos
Definition helpers.h:729
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:978
constexpr size_t GPIO_SUMMARY_MAX_LEN
Maximum buffer size for dump_summary output.
Definition gpio.h:13
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:26
uint8_t g
Definition color.h:34
uint8_t b
Definition color.h:38
uint8_t r
Definition color.h:30
uint8_t end[39]
Definition sun_gtil2.cpp:17
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6