Skip to content

Commit 18d6f14

Browse files
bitcoin3usclaude
andcommitted
lcd_bus: DSIBus rotation through the PPA (ESP32-P4)
Panels on the DSI video interface cannot swap rows and columns, so a landscape UI on a portrait panel needs the picture rotated on the way to the frame buffer. DSIBus(rotation=90|180|270) does that with the ESP32-P4's PPA (pixel processing accelerator): LVGL renders the rotated (logical) picture into the bus's own screen-sized buffers, and on the last flush of an update the whole picture is rotated by the PPA into the panel's back buffer, which is then swapped in at the next frame boundary (the refresh-done interrupt reports completion as before). The panel side is always double-buffered in this mode; intermediate areas of a multi-area update complete immediately. The angle follows LVGL's convention, so pass the same rotation to lv_display. Frame buffers are now allocated cache-line aligned (the PPA and the DPI DMA both need it), the PPA driver's include path is added where the chip has one, and rotation is refused on chips without a PPA. Verified on the Waveshare ESP32-P4-WIFI6-Touch-LCD-4.3 (480x800 panel, rotation=90): LVGL runs at 800x480, a forced full-screen refresh takes ~29 ms (render + PPA rotation + vsync) against ~15 ms unrotated. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 838f276 commit 18d6f14

3 files changed

Lines changed: 214 additions & 14 deletions

File tree

‎ext_mod/lcd_bus/esp32_include/dsi_bus.h‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
#include "esp_lcd_mipi_dsi.h"
2424
#include "esp_ldo_regulator.h"
2525

26+
#if SOC_PPA_SUPPORTED
27+
#include "driver/ppa.h"
28+
#endif
29+
2630

2731
typedef struct _mp_lcd_dsi_bus_obj_t {
2832
// The first members mirror mp_lcd_bus_obj_t (lcd_types.h): the
@@ -55,6 +59,20 @@
5559
int phy_ldo_voltage_mv;
5660
esp_ldo_channel_handle_t phy_ldo_handle;
5761

62+
// rotation (0/90/180/270 degrees, LVGL's sense): LVGL renders the
63+
// rotated (logical) picture into its own buffers and every update
64+
// is rotated into the panel's frame buffers by the PPA
65+
int rotation;
66+
uint8_t bpp;
67+
uint32_t lvgl_width;
68+
uint32_t lvgl_height;
69+
uint8_t *panel_fbs[2];
70+
uint8_t back_fb_index;
71+
uint32_t fb_size_aligned;
72+
#if SOC_PPA_SUPPORTED
73+
ppa_client_handle_t ppa_client;
74+
#endif
75+
5876
} mp_lcd_dsi_bus_obj_t;
5977

6078
extern const mp_obj_type_t mp_lcd_dsi_bus_type;

‎ext_mod/lcd_bus/esp32_src/dsi_bus.c‎

Lines changed: 185 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@
3838
#include "hal/lcd_types.h"
3939
#include "esp_lcd_mipi_dsi.h"
4040
#include "esp_ldo_regulator.h"
41+
#include "hal/cache_hal.h"
42+
#include "hal/cache_ll.h"
43+
44+
#if SOC_PPA_SUPPORTED
45+
#include "driver/ppa.h"
46+
#endif
4147

4248

4349
// Prefix of ESP-IDF's private esp_lcd_dpi_panel_t (esp_lcd/dsi/esp_lcd_panel_dpi.c),
@@ -71,6 +77,14 @@
7177
}
7278

7379

80+
// frame buffers (and the PPA's output) have to be cache-line aligned
81+
static uint32_t dsi_cache_line_size(void)
82+
{
83+
uint32_t line = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA);
84+
return line ? line : 64;
85+
}
86+
87+
7488
// Runs from the DPI panel's end-of-frame interrupt: the DMA has just
7589
// restarted its scan-out from fbs[cur_fb_index]. Once that is the buffer
7690
// LVGL last flushed, the other buffer is free to be drawn into.
@@ -107,7 +121,8 @@
107121
ARG_vsync_pulse_width,
108122
ARG_dpi_clock_freq,
109123
ARG_phy_ldo_channel,
110-
ARG_phy_ldo_voltage_mv
124+
ARG_phy_ldo_voltage_mv,
125+
ARG_rotation
111126
};
112127

113128
const mp_arg_t make_new_args[] = {
@@ -123,7 +138,8 @@
123138
{ MP_QSTR_vsync_pulse_width, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 1 } },
124139
{ MP_QSTR_dpi_clock_freq, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 0 } }, // DPI pixel clock, MHz; 0 = same number as freq
125140
{ MP_QSTR_phy_ldo_channel, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = -1 } }, // internal LDO channel powering the DSI PHY (ESP32-P4 boards: 3), -1 = not managed here
126-
{ MP_QSTR_phy_ldo_voltage_mv, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 2500 } }
141+
{ MP_QSTR_phy_ldo_voltage_mv, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 2500 } },
142+
{ MP_QSTR_rotation, MP_ARG_INT | MP_ARG_KW_ONLY, { .u_int = 0 } } // 0/90/180/270 degrees (LVGL's sense); the PPA rotates every frame into the panel
127143
};
128144

129145
mp_arg_val_t args[MP_ARRAY_SIZE(make_new_args)];
@@ -166,6 +182,18 @@
166182
self->phy_ldo_channel = (int)args[ARG_phy_ldo_channel].u_int;
167183
self->phy_ldo_voltage_mv = (int)args[ARG_phy_ldo_voltage_mv].u_int;
168184

185+
self->rotation = (int)args[ARG_rotation].u_int;
186+
if (self->rotation != 0 && self->rotation != 90 && self->rotation != 180 && self->rotation != 270) {
187+
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("rotation must be 0, 90, 180 or 270 (%d)"), self->rotation);
188+
return mp_const_none;
189+
}
190+
#if !SOC_PPA_SUPPORTED
191+
if (self->rotation != 0) {
192+
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("rotation needs a PPA (pixel processing accelerator), this chip has none"));
193+
return mp_const_none;
194+
}
195+
#endif
196+
169197
LCD_DEBUG_PRINT("bus_id=%d\n", self->bus_config.bus_id)
170198
LCD_DEBUG_PRINT("num_data_lanes=%d\n", self->bus_config.num_data_lanes)
171199
LCD_DEBUG_PRINT("lane_bit_rate_mbps=%d\n",self->bus_config.lane_bit_rate_mbps)
@@ -178,6 +206,7 @@
178206
LCD_DEBUG_PRINT("vsync_back_porch=%d\n", self->panel_config.video_timing.vsync_back_porch)
179207
LCD_DEBUG_PRINT("vsync_pulse_width=%d\n", self->panel_config.video_timing.vsync_pulse_width)
180208
LCD_DEBUG_PRINT("phy_ldo_channel=%d\n", self->phy_ldo_channel)
209+
LCD_DEBUG_PRINT("rotation=%d\n", self->rotation)
181210

182211
self->panel_io_handle.get_lane_count = &dsi_get_lane_count;
183212
self->panel_io_handle.del = &dsi_del;
@@ -234,6 +263,25 @@
234263

235264
// LVGL renders in the frame buffers' own byte order; nothing here to swap.
236265
self->rgb565_byte_swap = false;
266+
self->bpp = bpp;
267+
268+
if (self->rotation == 90 || self->rotation == 270) {
269+
self->lvgl_width = (uint32_t)height;
270+
self->lvgl_height = (uint32_t)width;
271+
} else {
272+
self->lvgl_width = (uint32_t)width;
273+
self->lvgl_height = (uint32_t)height;
274+
}
275+
276+
if (self->rotation != 0) {
277+
if (bpp == 18) {
278+
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("rotation works with 16 and 24 bits per pixel, not 18"));
279+
return LCD_ERR_INVALID_ARG;
280+
}
281+
// the rotated frame goes into the panel's back buffer while the
282+
// front one is scanned out, whatever LVGL's own buffering is
283+
self->panel_config.num_fbs = 2;
284+
}
237285

238286
self->panel_config.video_timing.h_size = (uint32_t)width;
239287
self->panel_config.video_timing.v_size = (uint32_t)height;
@@ -302,21 +350,45 @@
302350
return ret;
303351
}
304352

305-
// Point the buffers handed out by allocate_framebuffer() at the DPI
306-
// panel's frame buffers and drop the interim allocations.
307353
dpi_panel_t *dpi_panel = __containerof((esp_lcd_panel_t *)self->panel_handle, dpi_panel_t, base);
354+
self->panel_fbs[0] = dpi_panel->fbs[0];
355+
self->panel_fbs[1] = self->panel_config.num_fbs == 2 ? dpi_panel->fbs[1] : NULL;
356+
357+
if (self->rotation == 0) {
358+
// Point the buffers handed out by allocate_framebuffer() at the DPI
359+
// panel's frame buffers and drop the interim allocations: LVGL
360+
// renders straight into what the panel scans out.
361+
heap_caps_free(self->view1->items);
362+
self->view1->items = (void *)self->panel_fbs[0];
363+
self->view1->len = self->buffer_size;
364+
365+
if (self->view2 != NULL) {
366+
heap_caps_free(self->view2->items);
367+
self->view2->items = (void *)self->panel_fbs[1];
368+
self->view2->len = self->buffer_size;
369+
}
370+
} else {
371+
#if SOC_PPA_SUPPORTED
372+
// LVGL keeps its own (logical, rotated) buffers; each finished
373+
// update is rotated into the panel's back buffer by the PPA.
374+
ppa_client_config_t ppa_cfg = {
375+
.oper_type = PPA_OPERATION_SRM,
376+
.max_pending_trans_num = 1,
377+
};
378+
ret = ppa_register_client(&ppa_cfg, &self->ppa_client);
308379

309-
heap_caps_free(self->view1->items);
310-
self->view1->items = (void *)dpi_panel->fbs[0];
311-
self->view1->len = self->buffer_size;
380+
if (ret != 0) {
381+
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("%d(ppa_register_client)"), ret);
382+
return ret;
383+
}
312384

313-
if (self->view2 != NULL) {
314-
heap_caps_free(self->view2->items);
315-
self->view2->items = (void *)dpi_panel->fbs[1];
316-
self->view2->len = self->buffer_size;
385+
uint32_t line = dsi_cache_line_size();
386+
self->fb_size_aligned = (full_frame_size + line - 1) / line * line;
387+
self->back_fb_index = 1;
388+
#endif
317389
}
318390

319-
LCD_DEBUG_PRINT("fb1=%p fb2=%p\n", self->view1->items, self->view2 != NULL ? self->view2->items : NULL)
391+
LCD_DEBUG_PRINT("fb1=%p fb2=%p rotation=%d\n", self->view1->items, self->view2 != NULL ? self->view2->items : NULL, self->rotation)
320392

321393
return LCD_OK;
322394
}
@@ -337,20 +409,35 @@
337409
}
338410
self->panel_handle = NULL;
339411
self->panel_started = false;
412+
self->panel_fbs[0] = NULL;
413+
self->panel_fbs[1] = NULL;
340414

341-
// the frame buffers went with the panel
415+
// unrotated: the frame buffers went with the panel; rotated: they are ours
342416
if (self->view1 != NULL) {
417+
if (self->rotation != 0) {
418+
heap_caps_free(self->view1->items);
419+
}
343420
self->view1->items = NULL;
344421
self->view1->len = 0;
345422
self->view1 = NULL;
346423
}
347424
if (self->view2 != NULL) {
425+
if (self->rotation != 0) {
426+
heap_caps_free(self->view2->items);
427+
}
348428
self->view2->items = NULL;
349429
self->view2->len = 0;
350430
self->view2 = NULL;
351431
}
352432
}
353433

434+
#if SOC_PPA_SUPPORTED
435+
if (self->ppa_client != NULL) {
436+
ppa_unregister_client(self->ppa_client);
437+
self->ppa_client = NULL;
438+
}
439+
#endif
440+
354441
if (self->panel_io_handle.panel_io != NULL) {
355442
ret = esp_lcd_panel_io_del(self->panel_io_handle.panel_io);
356443
if (ret != 0) {
@@ -450,7 +537,7 @@
450537
// A real buffer for now, so the memoryview is always valid; once the
451538
// bus is initialized it is repointed at the frame buffer the DPI panel
452539
// driver allocated itself and this one is freed again (dsi_init).
453-
void *buf = heap_caps_calloc(1, size, caps);
540+
void *buf = heap_caps_aligned_calloc(dsi_cache_line_size(), 1, size, caps);
454541

455542
if (buf == NULL) {
456543
mp_raise_msg_varg(&mp_type_MemoryError, MP_ERROR_TEXT("Not enough memory available (%lu)"), size);
@@ -502,6 +589,90 @@
502589
self->panel_started = true;
503590
}
504591

592+
#if SOC_PPA_SUPPORTED
593+
if (self->rotation != 0) {
594+
if (!last_update) {
595+
// more areas of this update follow; LVGL's buffer is complete
596+
// only after the last one, nothing to rotate yet
597+
self->trans_done = true;
598+
599+
if (self->callback != mp_const_none && mp_obj_is_callable(self->callback)) {
600+
mp_call_function_n_kw(self->callback, 0, 0, NULL);
601+
}
602+
return LCD_OK;
603+
}
604+
605+
// LVGL's buffer (the one this area lives in) holds the whole
606+
// logical picture: rotate all of it into the panel's back buffer.
607+
const uint8_t *base = NULL;
608+
if (self->view1 != NULL && dsi_buf_in_fb(self, (const uint8_t *)self->view1->items, color)) {
609+
base = (const uint8_t *)self->view1->items;
610+
} else if (self->view2 != NULL && dsi_buf_in_fb(self, (const uint8_t *)self->view2->items, color)) {
611+
base = (const uint8_t *)self->view2->items;
612+
} else {
613+
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("flushed buffer is not a DSIBus frame buffer"));
614+
return LCD_ERR_INVALID_ARG;
615+
}
616+
617+
uint8_t *dst = self->panel_fbs[self->back_fb_index];
618+
ppa_srm_color_mode_t cm = self->bpp == 16 ? PPA_SRM_COLOR_MODE_RGB565 : PPA_SRM_COLOR_MODE_RGB888;
619+
620+
ppa_srm_oper_config_t op = { 0 };
621+
op.in.buffer = base;
622+
op.in.pic_w = self->lvgl_width;
623+
op.in.pic_h = self->lvgl_height;
624+
op.in.block_w = self->lvgl_width;
625+
op.in.block_h = self->lvgl_height;
626+
op.in.block_offset_x = 0;
627+
op.in.block_offset_y = 0;
628+
op.in.srm_cm = cm;
629+
op.out.buffer = dst;
630+
op.out.buffer_size = self->fb_size_aligned;
631+
op.out.pic_w = self->panel_config.video_timing.h_size;
632+
op.out.pic_h = self->panel_config.video_timing.v_size;
633+
op.out.block_offset_x = 0;
634+
op.out.block_offset_y = 0;
635+
op.out.srm_cm = cm;
636+
// LVGL's rotation and the PPA's are both counter-clockwise
637+
op.rotation_angle = self->rotation == 90 ? PPA_SRM_ROTATION_ANGLE_90 :
638+
self->rotation == 180 ? PPA_SRM_ROTATION_ANGLE_180 :
639+
PPA_SRM_ROTATION_ANGLE_270;
640+
op.scale_x = 1.0f;
641+
op.scale_y = 1.0f;
642+
op.mode = PPA_TRANS_MODE_BLOCKING;
643+
644+
ret = ppa_do_scale_rotate_mirror(self->ppa_client, &op);
645+
646+
if (ret != 0) {
647+
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("%d(ppa_do_scale_rotate_mirror)"), ret);
648+
return ret;
649+
}
650+
651+
self->trans_done = false;
652+
self->transmitting_buf = NULL;
653+
654+
// whole-buffer "draw": cache write-back plus the swap to this buffer
655+
// at the next frame boundary; the refresh-done interrupt reports it
656+
ret = esp_lcd_panel_draw_bitmap(
657+
self->panel_handle,
658+
0,
659+
0,
660+
(int)self->panel_config.video_timing.h_size,
661+
(int)self->panel_config.video_timing.v_size,
662+
dst
663+
);
664+
665+
if (ret != 0) {
666+
mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("%d(esp_lcd_panel_draw_bitmap)"), ret);
667+
return ret;
668+
}
669+
670+
self->transmitting_buf = dst;
671+
self->back_fb_index ^= 1;
672+
return LCD_OK;
673+
}
674+
#endif
675+
505676
self->trans_done = false;
506677
self->transmitting_buf = NULL;
507678

‎ext_mod/lcd_bus/micropython.cmake‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ if(ESP_PLATFORM)
3232
list(APPEND LCD_INCLUDES ${ESP_LCD_INCLUDES})
3333
endif()
3434

35+
# the DSI bus rotates with the PPA where the chip has one (ESP32-P4)
36+
if(CONFIG_SOC_PPA_SUPPORTED)
37+
idf_component_get_property(ESP_PPA_INCLUDES esp_driver_ppa INCLUDE_DIRS)
38+
idf_component_get_property(ESP_PPA_DIR esp_driver_ppa COMPONENT_DIR)
39+
40+
if(ESP_PPA_INCLUDES)
41+
list(TRANSFORM ESP_PPA_INCLUDES PREPEND ${ESP_PPA_DIR}/)
42+
list(APPEND LCD_INCLUDES ${ESP_PPA_INCLUDES})
43+
endif()
44+
endif()
45+
3546
else()
3647
set(LCD_INCLUDES
3748
${CMAKE_CURRENT_LIST_DIR}

0 commit comments

Comments
 (0)