Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/m5stack-tab5/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
REQUIRES driver esp_driver_i2s esp_driver_sdmmc esp_driver_spi esp_lcd fatfs base_component bmi270 codec display display_drivers gt911 i2c ina226 input_drivers interrupt pi4ioe5v rx8130ce st7123touch task touch
REQUIRES driver esp_driver_i2s esp_driver_ppa esp_driver_sdmmc esp_driver_spi esp_lcd fatfs base_component bmi270 codec display display_drivers gt911 i2c ina226 input_drivers interrupt pi4ioe5v rx8130ce st7123touch task touch
REQUIRED_IDF_TARGETS "esp32p4"
)
10 changes: 8 additions & 2 deletions components/m5stack-tab5/example/main/m5stack_tab5_example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,15 @@ extern "C" void app_main(void) {
return;
}

// initialize the display with a pixel buffer (Tab5 is 1280x720 with 2 bytes per pixel)
// initialize the display with a full-frame pixel buffer (Tab5 is 1280x720 with
// 2 bytes per pixel). A full-frame LVGL draw buffer (allocated in PSRAM, see
// initialize_display) lets a full-screen redraw - e.g. a rotation change -
// flush in a single pass instead of ~72 ten-line strips, so the screen
// repaints at once rather than wiping progressively. Partial updates still
// only render/flush their dirty area, so the larger buffer costs nothing
// there (just PSRAM).
logger.info("Initializing display...");
auto pixel_buffer_size = tab5.display_width() * 10; // tab5.display_height();
auto pixel_buffer_size = tab5.display_width() * tab5.display_height();
if (!tab5.initialize_display(pixel_buffer_size)) {
logger.error("Failed to initialize display!");
return;
Expand Down
143 changes: 118 additions & 25 deletions components/m5stack-tab5/src/video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <algorithm>
#include <cstdlib>

#include <driver/ppa.h>
#include <esp_lcd_mipi_dsi.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
Expand Down Expand Up @@ -271,7 +272,16 @@ bool M5StackTab5::initialize_lcd() {
return true;
}

// Scratch buffer that holds the hardware-rotated frame produced by the PPA
// before it is handed to the panel (see flush()). Kept in PSRAM and aligned to
// the data-cache line size, which the PPA requires for its output buffer.
static uint16_t *third_buffer = nullptr;
static size_t third_buffer_bytes = 0;
// PPA (Pixel Processing Accelerator) client used to rotate the frame in
// hardware instead of on the CPU (lv_draw_sw_rotate). CPU rotation of a
// full-frame PSRAM buffer is slow and, because the transpose strides PSRAM,
// asymmetric between 90 and 270 degrees; the PPA is fast and symmetric.
static ppa_client_handle_t g_ppa_client = nullptr;

bool M5StackTab5::initialize_display(size_t pixel_buffer_size) {
logger_.info("Initializing LVGL display with pixel buffer size: {} pixels", pixel_buffer_size);
Expand All @@ -289,13 +299,57 @@ bool M5StackTab5::initialize_display(size_t pixel_buffer_size) {
Display<Pixel>::DynamicMemoryConfig{
.pixel_buffer_size = pixel_buffer_size,
.double_buffered = true,
.allocation_flags = MALLOC_CAP_8BIT | MALLOC_CAP_DMA,
// Allocate the LVGL draw buffers in PSRAM: a full-frame double
// buffer is ~3.7 MB, far larger than internal RAM, and the DPI
// panel's draw_bitmap reads the source straight from PSRAM (the
// esp32-p4-function-ev-board uses the same PSRAM buffers).
.allocation_flags = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT,
},
Logger::Verbosity::WARN);
}

third_buffer = (uint16_t *)heap_caps_malloc(pixel_buffer_size * sizeof(uint16_t),
MALLOC_CAP_8BIT | MALLOC_CAP_DMA);
// Register a PPA client for hardware rotation, and allocate the rotation
// scratch buffer that the PPA writes into. The PPA output buffer (external /
// PSRAM memory) must be aligned to the data cache line size, and both the
// pointer and the size must be a multiple of it.
if (g_ppa_client == nullptr) {
ppa_client_config_t ppa_cfg = {};
ppa_cfg.oper_type = PPA_OPERATION_SRM;
esp_err_t perr = ppa_register_client(&ppa_cfg, &g_ppa_client);
if (perr != ESP_OK) {
logger_.warn("Could not register PPA client ({}); rotation will fall back to software",
esp_err_to_name(perr));
g_ppa_client = nullptr;
}
}
// Align to 128 bytes: the PPA output buffer in external (PSRAM) memory must
// be aligned to the L1 and L2 cache line size, and the ESP32-P4's L2 line is
// 128 bytes. Both the pointer and the size must be a multiple of it.
static constexpr size_t kCacheAlign = 128;
size_t required_bytes = pixel_buffer_size * sizeof(uint16_t);
required_bytes = (required_bytes + kCacheAlign - 1) / kCacheAlign * kCacheAlign;

// Reuse the existing scratch buffer if it is already the right size; otherwise
// free it first so a repeated initialize_display() call (e.g. re-init with a
// different pixel buffer size) cannot leak the previous allocation.
if (third_buffer == nullptr || third_buffer_bytes != required_bytes) {
if (third_buffer != nullptr) {
heap_caps_free(third_buffer);
third_buffer = nullptr;
}
third_buffer_bytes = required_bytes;
third_buffer = (uint16_t *)heap_caps_aligned_alloc(kCacheAlign, third_buffer_bytes,
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (third_buffer == nullptr) {
// The scratch buffer is required for display rotation - both the PPA path
// and the software fallback rotate into it - so without it a non-zero
// rotation (settable at runtime) would silently flush incorrect output.
// Fail initialization rather than come up with rotation quietly broken.
logger_.error("Could not allocate the rotation scratch buffer ({} bytes)", required_bytes);
third_buffer_bytes = 0;
return false;
}
Comment thread
Copilot marked this conversation as resolved.
}
Comment thread
Copilot marked this conversation as resolved.

logger_.info("LVGL display initialized");
return true;
Expand Down Expand Up @@ -366,9 +420,11 @@ float M5StackTab5::brightness() const {
// DSI write helpers
// -----------------

void IRAM_ATTR M5StackTab5::flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) {
// Note: This function may be called from ISR context via DPI callback
// Avoid using floating-point operations, logging, or other coprocessor functions
void M5StackTab5::flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) {
// This is LVGL's flush callback; it runs in the LVGL task context (from
// lv_display_flush / the LVGL timer), not from an ISR - the DPI transfer-done
// interrupt is handled separately in notify_lvgl_flush_ready(). Blocking work
// (the PPA rotation and esp_lcd_panel_draw_bitmap) is therefore safe here.

if (lcd_handles_.panel == nullptr) {
lv_display_flush_ready(disp);
Expand All @@ -381,28 +437,65 @@ void IRAM_ATTR M5StackTab5::flush(lv_display_t *disp, const lv_area_t *area, uin
int offsety2 = area->y2;

auto rotation = lv_display_get_rotation(lv_display_get_default());
if (rotation > LV_DISPLAY_ROTATION_0) {
/* SW rotation */
if (rotation > LV_DISPLAY_ROTATION_0 && third_buffer != nullptr) {
int32_t ww = lv_area_get_width(area);
int32_t hh = lv_area_get_height(area);
lv_color_format_t cf = lv_display_get_color_format(disp);
uint32_t w_stride = lv_draw_buf_width_to_stride(ww, cf);
uint32_t h_stride = lv_draw_buf_width_to_stride(hh, cf);
if (rotation == LV_DISPLAY_ROTATION_180) {
lv_draw_sw_rotate(px_map, third_buffer, hh, ww, h_stride, h_stride, LV_DISPLAY_ROTATION_180,
cf);
} else if (rotation == LV_DISPLAY_ROTATION_90) {
// printf("%ld %ld\n", w_stride, h_stride);
lv_draw_sw_rotate(px_map, third_buffer, ww, hh, w_stride, h_stride, LV_DISPLAY_ROTATION_90,
cf);
// // rotate_copy_pixel((uint16_t*)color_map, (uint16_t*)third_buffer, offsetx1, offsety1,
// // offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, 270);
// rotate_copy_pixel((uint16_t*)color_map, (uint16_t*)third_buffer, 0, 0, offsetx2 - offsetx1,
// offsety2 - offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1,
// 270);
} else if (rotation == LV_DISPLAY_ROTATION_270) {
lv_draw_sw_rotate(px_map, third_buffer, ww, hh, w_stride, h_stride, LV_DISPLAY_ROTATION_270,
cf);
bool rotated = false;
if (g_ppa_client != nullptr) {
// Hardware rotation via the PPA. The LVGL rotation maps directly onto the
// PPA rotation angle (LVGL 90 -> PPA 90, 180 -> 180, 270 -> 270); this is
// the mapping verified on hardware. For 90/270 the output picture
// width/height are swapped. If a different panel comes out turned the
// wrong way, swap the 90 and 270 cases here.
ppa_srm_rotation_angle_t angle = PPA_SRM_ROTATION_ANGLE_0;
uint32_t out_w = ww, out_h = hh;
if (rotation == LV_DISPLAY_ROTATION_90) {
angle = PPA_SRM_ROTATION_ANGLE_90;
out_w = hh;
out_h = ww;
} else if (rotation == LV_DISPLAY_ROTATION_180) {
angle = PPA_SRM_ROTATION_ANGLE_180;
} else if (rotation == LV_DISPLAY_ROTATION_270) {
angle = PPA_SRM_ROTATION_ANGLE_270;
out_w = hh;
out_h = ww;
}
ppa_srm_oper_config_t srm = {};
srm.in.buffer = px_map;
srm.in.pic_w = ww;
srm.in.pic_h = hh;
srm.in.block_w = ww;
srm.in.block_h = hh;
srm.in.srm_cm = PPA_SRM_COLOR_MODE_RGB565;
srm.out.buffer = third_buffer;
srm.out.buffer_size = third_buffer_bytes;
srm.out.pic_w = out_w;
srm.out.pic_h = out_h;
srm.out.srm_cm = PPA_SRM_COLOR_MODE_RGB565;
srm.rotation_angle = angle;
srm.scale_x = 1.0f;
srm.scale_y = 1.0f;
srm.mode = PPA_TRANS_MODE_BLOCKING;
// On failure third_buffer holds stale/partial data; leave rotated=false so
// we fall through to the software rotation below rather than flushing it.
rotated = (ppa_do_scale_rotate_mirror(g_ppa_client, &srm) == ESP_OK);
}
if (!rotated) {
// Software fallback: the PPA client failed to register or the PPA
// operation failed. Rotates into third_buffer, fully overwriting it.
uint32_t w_stride = lv_draw_buf_width_to_stride(ww, cf);
uint32_t h_stride = lv_draw_buf_width_to_stride(hh, cf);
if (rotation == LV_DISPLAY_ROTATION_180) {
lv_draw_sw_rotate(px_map, third_buffer, hh, ww, h_stride, h_stride, LV_DISPLAY_ROTATION_180,
cf);
} else if (rotation == LV_DISPLAY_ROTATION_90) {
lv_draw_sw_rotate(px_map, third_buffer, ww, hh, w_stride, h_stride, LV_DISPLAY_ROTATION_90,
cf);
} else if (rotation == LV_DISPLAY_ROTATION_270) {
lv_draw_sw_rotate(px_map, third_buffer, ww, hh, w_stride, h_stride, LV_DISPLAY_ROTATION_270,
cf);
}
}
px_map = reinterpret_cast<uint8_t *>(third_buffer);
lv_display_rotate_area(disp, const_cast<lv_area_t *>(area));
Expand Down
Loading