feat(m5stack-tab5): Update tab5 video and example to have larger display buffers and use the PPU for faster refresh and display rotation#675
Conversation
…lay buffers and use the PPU for faster refresh and display rotation
|
✅Static analysis result - no issues found! ✅ |
There was a problem hiding this comment.
Pull request overview
This PR updates the M5Stack Tab5 display/video path to improve refresh performance and enable faster display rotation by moving LVGL draw buffers into PSRAM and using the ESP32-P4 PPA (Pixel Processing Accelerator) for hardware rotation.
Changes:
- Allocate LVGL draw buffers in PSRAM and add a PSRAM-aligned scratch buffer for rotation output.
- Add PPA-based hardware rotation in the LVGL flush path with a software-rotation fallback.
- Update the Tab5 example to use a full-frame LVGL pixel buffer for single-pass full-screen flushes.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| components/m5stack-tab5/src/video.cpp | Moves draw buffers to PSRAM, adds PPA client + aligned rotation scratch buffer, and performs rotation in flush() via PPA with SW fallback. |
| components/m5stack-tab5/example/main/m5stack_tab5_example.cpp | Switches the example to a full-frame LVGL pixel buffer to avoid progressive “striped” redraws on full-screen updates. |
| components/m5stack-tab5/CMakeLists.txt | Adds esp_driver_ppa dependency required for PPA rotation. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
components/m5stack-tab5/src/video.cpp:449
- The comment says the PPA rotation direction is counter-clockwise vs LVGL’s clockwise rotation (LVGL 90 -> PPA 270 and LVGL 270 -> PPA 90), but the current mapping uses the same angles (90->90, 270->270). This mismatch will either rotate the image the wrong way or leave misleading documentation; align the mapping with the documented behavior.
// Hardware rotation via the PPA. NOTE: the PPA rotates COUNTER-clockwise
// while LVGL's rotation is clockwise, so LVGL 90 -> PPA 270 and LVGL 270
// -> PPA 90 (180 is the same). If the rotated image comes out turned the
// wrong way, swap the 90/270 mapping here. For 90/270 the output picture
// width/height are swapped.
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;
Review feedback on the PPA rotation in video.cpp: - initialize_display() reused/overwrote the rotation scratch buffer on every call, leaking it on re-init. Reuse it when the size matches, otherwise free it before reallocating. - If the scratch-buffer allocation failed, initialize_display() still returned true, leaving rotation silently broken (flush would skip rotation). The buffer is required for both the PPA and the software rotation, so fail init instead. - The PPA-rotation comment claimed LVGL 90 -> PPA 270 / 270 -> PPA 90 while the code (verified on hardware) maps 90 -> 90 and 270 -> 270. Correct the comment. - ppa_do_scale_rotate_mirror()'s return value was ignored, so a failed rotation would flush stale/partial scratch-buffer data to the panel. Check it and fall back to the software rotation (which fully overwrites the buffer) on failure. - flush() was marked IRAM_ATTR and commented as possibly ISR-invoked, which conflicts with its blocking PPA op. It is actually LVGL's flush callback and runs in the LVGL task context (the DPI transfer-done ISR is the separate notify_lvgl_flush_ready), so drop IRAM_ATTR and correct the comment; blocking work is safe there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
components/m5stack-tab5/src/video.cpp:288
- initialize_display() accepts pixel_buffer_size as a public API, but a value of 0 will currently produce required_bytes==0 and can lead to a 0-byte aligned allocation (implementation-defined) and unsafe downstream use in flush/rotation. Reject 0 early (or map it to a sensible default) before any allocations.
bool M5StackTab5::initialize_display(size_t pixel_buffer_size) {
logger_.info("Initializing LVGL display with pixel buffer size: {} pixels", pixel_buffer_size);
if (!display_) {
components/m5stack-tab5/src/video.cpp:315
- initialize_display() can now fail (rotation scratch buffer allocation) after the LVGL Display has already been constructed and its large draw buffers allocated. This makes it hard/impossible to recover by retrying with a smaller pixel_buffer_size (since display_ is already initialized), and it also means a failing call has significant side effects. Consider allocating/validating the scratch buffer (and optionally registering the PPA client) before constructing display_, so the function is effectively transactional on failure.
// 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) {
Description
Improves the M5Stack Tab5 display/video path in two related ways:
allocated in PSRAM and the example sizes them to the full panel
(1280×720) instead of a 10-line strip. A full-screen redraw (e.g. a tab
switch or a rotation) now flushes 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).
Accelerator).
flush()rotates the frame with the PPA into acache-line-aligned PSRAM scratch buffer, replacing the CPU rotation
(
lv_draw_sw_rotate). A software-rotation fallback is retained for when thePPA client can't be registered or a PPA op fails.
Also folds in the PR review feedback: reuse/free the rotation scratch buffer
across
initialize_display()calls (no leak), fail init if it can't beallocated (rather than coming up with rotation silently broken), check the PPA
op's return and fall back to software rotation on failure, correct the
rotation-direction comment to match the hardware-verified mapping, and drop the
stale
IRAM_ATTR/ISR annotation fromflush()(it runs in the LVGL taskcontext, not an ISR — the DPI transfer-done ISR is handled separately).
Motivation and Context
Full-screen updates previously flushed as many small strips, producing a
visible progressive "wipe" on redraws. And CPU rotation of a full-frame PSRAM
buffer is slow and, because the transpose strides PSRAM, asymmetric between
90° and 270° (one direction noticeably slower than the other). Moving the draw
buffers to PSRAM + using the PPA makes full-screen refreshes repaint in one pass
and makes rotation fast and symmetric across all four orientations, offloading
the work from the CPU to dedicated hardware.
How has this been tested?
m5stack-tab5example with ESP-IDF for the ESP32-P4 — clean build,no warnings.
pass instead of wiping in strips.
and correctly oriented; the previous CPU path was slow and asymmetric
(90° fast, 270° slow).
the PPA client is unavailable.
Screenshots (if appropriate, e.g. schematic, board, console logs, lab pictures):
Types of changes
Checklist:
Software
.github/workflows/build.ymlfile to add my new test to the automated cloud build