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