ESPHome 2025.9.1
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"
3#include "esphome/core/log.h"
4#include "esphome/core/hal.h"
5#include "esp_lcd_panel_rgb.h"
6
7namespace esphome {
8namespace mipi_rgb {
9
10static const uint8_t DELAY_FLAG = 0xFF;
11static constexpr uint8_t MADCTL_MY = 0x80; // Bit 7 Bottom to top
12static constexpr uint8_t MADCTL_MX = 0x40; // Bit 6 Right to left
13static constexpr uint8_t MADCTL_MV = 0x20; // Bit 5 Swap axes
14static constexpr uint8_t MADCTL_ML = 0x10; // Bit 4 Refresh bottom to top
15static constexpr uint8_t MADCTL_BGR = 0x08; // Bit 3 Blue-Green-Red pixel order
16static constexpr uint8_t MADCTL_XFLIP = 0x02; // Mirror the display horizontally
17static constexpr uint8_t MADCTL_YFLIP = 0x01; // Mirror the display vertically
18
20 if (!this->enable_pins_.empty()) {
21 for (auto *pin : this->enable_pins_) {
22 pin->setup();
23 pin->digital_write(true);
24 }
25 delay(10);
26 }
27 if (this->reset_pin_ != nullptr) {
28 this->reset_pin_->setup();
29 this->reset_pin_->digital_write(true);
30 delay(5);
31 this->reset_pin_->digital_write(false);
32 delay(5);
33 this->reset_pin_->digital_write(true);
34 }
35}
36
37#ifdef USE_SPI
39 this->setup_enables_();
40 this->spi_setup();
42 this->common_setup_();
43}
44void MipiRgbSpi::write_command_(uint8_t value) {
45 this->enable();
46 if (this->dc_pin_ == nullptr) {
47 this->write(value, 9);
48 } else {
49 this->dc_pin_->digital_write(false);
50 this->write_byte(value);
51 this->dc_pin_->digital_write(true);
52 }
53 this->disable();
54}
55
56void MipiRgbSpi::write_data_(uint8_t value) {
57 this->enable();
58 if (this->dc_pin_ == nullptr) {
59 this->write(value | 0x100, 9);
60 } else {
61 this->dc_pin_->digital_write(true);
62 this->write_byte(value);
63 }
64 this->disable();
65}
66
72 size_t index = 0;
73 auto &vec = this->init_sequence_;
74 while (index != vec.size()) {
75 if (vec.size() - index < 2) {
76 this->mark_failed("Malformed init sequence");
77 return;
78 }
79 uint8_t cmd = vec[index++];
80 uint8_t x = vec[index++];
81 if (x == DELAY_FLAG) {
82 ESP_LOGD(TAG, "Delay %dms", cmd);
83 delay(cmd);
84 } else {
85 uint8_t num_args = x & 0x7F;
86 if (vec.size() - index < num_args) {
87 this->mark_failed("Malformed init sequence");
88 return;
89 }
90 if (cmd == SLEEP_OUT) {
91 delay(120); // NOLINT
92 }
93 const auto *ptr = vec.data() + index;
94 ESP_LOGD(TAG, "Write command %02X, length %d, byte(s) %s", cmd, num_args,
95 format_hex_pretty(ptr, num_args, '.', false).c_str());
96 index += num_args;
97 this->write_command_(cmd);
98 while (num_args-- != 0)
99 this->write_data_(*ptr++);
100 if (cmd == SLEEP_OUT)
101 delay(10);
102 }
103 }
104 // this->spi_teardown(); // SPI not needed after this
105 this->init_sequence_.clear();
106 delay(10);
107}
108
111 LOG_PIN(" CS Pin: ", this->cs_);
112 LOG_PIN(" DC Pin: ", this->dc_pin_);
113 ESP_LOGCONFIG(TAG,
114 " SPI Data rate: %uMHz"
115 "\n Mirror X: %s"
116 "\n Mirror Y: %s"
117 "\n Swap X/Y: %s"
118 "\n Color Order: %s",
119 (unsigned) (this->data_rate_ / 1000000), YESNO(this->madctl_ & (MADCTL_XFLIP | MADCTL_MX)),
120 YESNO(this->madctl_ & (MADCTL_YFLIP | MADCTL_MY | MADCTL_ML)), YESNO(this->madctl_ & MADCTL_MV),
121 this->madctl_ & MADCTL_BGR ? "BGR" : "RGB");
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] = this->data_pins_[i]->get_pin();
150 }
151 config.data_width = data_pin_count;
152 config.disp_gpio_num = -1;
153 config.hsync_gpio_num = this->hsync_pin_->get_pin();
154 config.vsync_gpio_num = this->vsync_pin_->get_pin();
155 if (this->de_pin_) {
156 config.de_gpio_num = this->de_pin_->get_pin();
157 } else {
158 config.de_gpio_num = -1;
159 }
160 config.pclk_gpio_num = this->pclk_pin_->get_pin();
161 esp_err_t err = esp_lcd_new_rgb_panel(&config, &this->handle_);
162 if (err == ESP_OK)
163 err = esp_lcd_panel_reset(this->handle_);
164 if (err == ESP_OK)
165 err = esp_lcd_panel_init(this->handle_);
166 if (err != ESP_OK) {
167 auto msg = str_sprintf("lcd setup failed: %s", esp_err_to_name(err));
168 this->mark_failed(msg.c_str());
169 }
170 ESP_LOGCONFIG(TAG, "MipiRgb setup complete");
171}
172
174 if (this->handle_ != nullptr)
175 esp_lcd_rgb_panel_restart(this->handle_);
176}
177
179 if (this->is_failed())
180 return;
181 if (this->auto_clear_enabled_) {
182 this->clear();
183 }
184 if (this->show_test_card_) {
185 this->test_card();
186 } else if (this->page_ != nullptr) {
187 this->page_->get_writer()(*this);
188 } else if (this->writer_.has_value()) {
189 (*this->writer_)(*this);
190 } else {
191 this->stop_poller();
192 }
193 if (this->buffer_ == nullptr || this->x_low_ > this->x_high_ || this->y_low_ > this->y_high_)
194 return;
195 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_);
196 int w = this->x_high_ - this->x_low_ + 1;
197 int h = this->y_high_ - this->y_low_ + 1;
198 this->write_to_display_(this->x_low_, this->y_low_, w, h, reinterpret_cast<const uint8_t *>(this->buffer_),
199 this->x_low_, this->y_low_, this->width_ - w - this->x_low_);
200 // invalidate watermarks
201 this->x_low_ = this->width_;
202 this->y_low_ = this->height_;
203 this->x_high_ = 0;
204 this->y_high_ = 0;
205}
206
207void MipiRgb::draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, display::ColorOrder order,
208 display::ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad) {
209 if (w <= 0 || h <= 0 || this->is_failed())
210 return;
211 // if color mapping is required, pass the buck.
212 // note that endianness is not considered here - it is assumed to match!
213 if (bitness != display::COLOR_BITNESS_565) {
214 Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset, x_pad);
215 this->write_to_display_(x_start, y_start, w, h, reinterpret_cast<const uint8_t *>(this->buffer_), x_start, y_start,
216 this->width_ - w - x_start);
217 } else {
218 this->write_to_display_(x_start, y_start, w, h, ptr, x_offset, y_offset, x_pad);
219 }
220}
221
222void MipiRgb::write_to_display_(int x_start, int y_start, int w, int h, const uint8_t *ptr, int x_offset, int y_offset,
223 int x_pad) {
224 esp_err_t err = ESP_OK;
225 auto stride = (x_offset + w + x_pad) * 2;
226 ptr += y_offset * stride + x_offset * 2; // skip to the first pixel
227 // x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.
228 if (x_offset == 0 && x_pad == 0) {
229 err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y_start, x_start + w, y_start + h, ptr);
230 } else {
231 // draw line by line
232 for (int y = 0; y != h; y++) {
233 err = esp_lcd_panel_draw_bitmap(this->handle_, x_start, y + y_start, x_start + w, y + y_start + 1, ptr);
234 if (err != ESP_OK)
235 break;
236 ptr += stride; // next line
237 }
238 }
239 if (err != ESP_OK)
240 ESP_LOGE(TAG, "lcd_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
241}
242
244 if (this->is_failed())
245 return false;
246 if (this->buffer_ != nullptr)
247 return true;
248 // this is dependent on the enum values.
249 RAMAllocator<uint16_t> allocator;
250 this->buffer_ = allocator.allocate(this->height_ * this->width_);
251 if (this->buffer_ == nullptr) {
252 this->mark_failed("Could not allocate buffer for display!");
253 return false;
254 }
255 return true;
256}
257
258void MipiRgb::draw_pixel_at(int x, int y, Color color) {
259 if (!this->get_clipping().inside(x, y) || this->is_failed())
260 return;
261
262 switch (this->rotation_) {
264 break;
266 std::swap(x, y);
267 x = this->width_ - x - 1;
268 break;
270 x = this->width_ - x - 1;
271 y = this->height_ - y - 1;
272 break;
274 std::swap(x, y);
275 y = this->height_ - y - 1;
276 break;
277 }
278 if (x >= this->get_width_internal() || x < 0 || y >= this->get_height_internal() || y < 0) {
279 return;
280 }
281 if (!this->check_buffer_())
282 return;
283 size_t pos = (y * this->width_) + x;
284 uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
285 uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
286 uint16_t new_color = hi_byte | (lo_byte << 8); // big endian
287 if (this->buffer_[pos] == new_color)
288 return;
289 this->buffer_[pos] = new_color;
290 // low and high watermark may speed up drawing from buffer
292 this->x_low_ = x;
294 this->y_low_ = y;
295 if (x > this->x_high_)
296 this->x_high_ = x;
297 if (y > this->y_high_)
298 this->y_high_ = y;
299}
300void MipiRgb::fill(Color color) {
301 if (!this->check_buffer_())
302 return;
303 auto *ptr_16 = reinterpret_cast<uint16_t *>(this->buffer_);
304 uint8_t hi_byte = static_cast<uint8_t>(color.r & 0xF8) | (color.g >> 5);
305 uint8_t lo_byte = static_cast<uint8_t>((color.g & 0x1C) << 3) | (color.b >> 3);
306 uint16_t new_color = lo_byte | (hi_byte << 8); // little endian
307 std::fill_n(ptr_16, this->width_ * this->height_, new_color);
308}
309
321
333
334static std::string get_pin_name(GPIOPin *pin) {
335 if (pin == nullptr)
336 return "None";
337 return pin->dump_summary();
338}
339
340void MipiRgb::dump_pins_(uint8_t start, uint8_t end, const char *name, uint8_t offset) {
341 for (uint8_t i = start; i != end; i++) {
342 ESP_LOGCONFIG(TAG, " %s pin %d: %s", name, offset++, this->data_pins_[i]->dump_summary().c_str());
343 }
344}
345
347 ESP_LOGCONFIG(TAG,
348 "MIPI_RGB LCD"
349 "\n Model: %s"
350 "\n Width: %u"
351 "\n Height: %u"
352 "\n Rotation: %d degrees"
353 "\n HSync Pulse Width: %u"
354 "\n HSync Back Porch: %u"
355 "\n HSync Front Porch: %u"
356 "\n VSync Pulse Width: %u"
357 "\n VSync Back Porch: %u"
358 "\n VSync Front Porch: %u"
359 "\n Invert Colors: %s"
360 "\n Pixel Clock: %dMHz"
361 "\n Reset Pin: %s"
362 "\n DE Pin: %s"
363 "\n PCLK Pin: %s"
364 "\n HSYNC Pin: %s"
365 "\n VSYNC Pin: %s",
366 this->model_, this->width_, this->height_, this->rotation_, this->hsync_pulse_width_,
368 this->vsync_front_porch_, YESNO(this->invert_colors_), this->pclk_frequency_ / 1000000,
369 get_pin_name(this->reset_pin_).c_str(), get_pin_name(this->de_pin_).c_str(),
370 get_pin_name(this->pclk_pin_).c_str(), get_pin_name(this->hsync_pin_).c_str(),
371 get_pin_name(this->vsync_pin_).c_str());
372
373 if (this->madctl_ & MADCTL_BGR) {
374 this->dump_pins_(8, 13, "Blue", 0);
375 this->dump_pins_(13, 16, "Green", 0);
376 this->dump_pins_(0, 3, "Green", 3);
377 this->dump_pins_(3, 8, "Red", 0);
378 } else {
379 this->dump_pins_(8, 13, "Red", 0);
380 this->dump_pins_(13, 16, "Green", 0);
381 this->dump_pins_(0, 3, "Green", 3);
382 this->dump_pins_(3, 8, "Blue", 0);
383 }
384}
385
386} // namespace mipi_rgb
387} // namespace esphome
388#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:848
T * allocate(size_t n)
Definition helpers.h:868
void clear()
Clear the entire screen by filling it with OFF pixels.
Definition display.cpp:17
DisplayPage * page_
Definition display.h:682
optional< display_writer_t > writer_
Definition display.h:681
Rect get_clipping() const
Get the current the clipping rectangle.
Definition display.cpp:715
DisplayRotation rotation_
Definition display.h:680
const display_writer_t & get_writer() const
Definition display.cpp:836
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:207
esp_lcd_panel_handle_t handle_
Definition mipi_rgb.h:100
int get_height() override
Definition mipi_rgb.cpp:322
InternalGPIOPin * hsync_pin_
Definition mipi_rgb.h:75
void dump_config() override
Definition mipi_rgb.cpp:346
void fill(Color color)
Definition mipi_rgb.cpp:300
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:258
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:340
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:222
void write_data_(uint8_t value)
Definition mipi_rgb.cpp:56
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:71
void write_command_(uint8_t value)
Definition mipi_rgb.cpp:44
std::vector< uint8_t > init_sequence_
Definition mipi_rgb.h:121
bool has_value() const
Definition optional.h:92
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
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length)
Format a byte array in pretty-printed, human-readable hex format.
Definition helpers.cpp:292
std::string str_sprintf(const char *fmt,...)
Definition helpers.cpp:221
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
uint8_t g
Definition color.h:25
uint8_t b
Definition color.h:29
uint8_t r
Definition color.h:21
uint8_t end[39]
Definition sun_gtil2.cpp:17
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6