Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
6b6b947
feat(m5stack-tab5): use display-driver (MADCTL) rotation for the ST71…
finger563 Aug 28, 2026
d16e909
fix(m5stack-tab5): address rotation review — unify rotation enums, ap…
finger563 Aug 28, 2026
33eb080
fix(m5stack-tab5): gate ST7121 panel-side rotation behind an experime…
finger563 Aug 28, 2026
513f565
fix(m5stack-tab5): reduce PSRAM contention that underruns the DSI sca…
finger563 Aug 28, 2026
0f4c38e
doc(m5stack-tab5): document thread-safety of the init-path on_display…
finger563 Aug 28, 2026
94ab211
fix(m5stack-tab5): synchronize LCD-state publication against a concur…
finger563 Aug 29, 2026
18d7e74
fix(m5stack-tab5): address PPA flush-path review feedback
finger563 Aug 29, 2026
b85dd72
fix(m5stack-tab5): gate DPI DMA2D per controller and harden the LCD p…
finger563 Aug 29, 2026
74a7e6b
fix(m5stack-tab5): replace private esp_cache_private.h with the publi…
finger563 Aug 30, 2026
60972be
fix(m5stack-tab5): suppress cppcheck config-exploration false positives
finger563 Aug 30, 2026
2d69b61
fix(m5stack-tab5): harden LCD retry state and enforce the RGB565-only…
finger563 Aug 30, 2026
df3cf9e
fix(m5stack-tab5): PPA-correct scratch allocation caps, single RGB565…
finger563 Aug 30, 2026
550999d
fix(m5stack-tab5): avoid cross-thread LVGL read when applying the ini…
finger563 Aug 31, 2026
236f42b
fix(m5stack-tab5): flush/geometry use the BSP-managed display, not th…
finger563 Aug 31, 2026
cfdbd8d
fix(m5stack-tab5): serialize panel draws so async DMA2D draw_bitmap i…
finger563 Aug 31, 2026
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
8 changes: 5 additions & 3 deletions components/esp32-p4-function-ev-board/src/video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,11 @@ bool Esp32P4FunctionEvBoard::initialize_lcd() {
// (esp_lcd_panel_draw_bitmap) through it corrupts the RGB565 channel order on
// this board — colors come out brighter/greener and alpha blends render
// wrong, while the bytes in the frame buffer are correct. The plain CPU copy
// path renders correctly and matches the m5stack-tab5 BSP, which also does
// not enable DMA2D on IDF >= 6. (An earlier "blank screen without DMA2D" was
// actually the RST_LCD/PWM jumper wiring, not DMA2D.)
// path renders correctly and matches the m5stack-tab5 BSP's ILI9881 variant,
// which skips DMA2D on IDF >= 6 for the same reason (its ST71xx variants
// enable it without issue, as does the esp32-p4-nano's JD9365). (An earlier
// "blank screen without DMA2D" was actually the RST_LCD/PWM jumper wiring,
// not DMA2D.)
}

// Send the panel controller's vendor init sequence over DBI (command mode),
Expand Down
14 changes: 14 additions & 0 deletions components/m5stack-tab5/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,18 @@ menu "M5Stack Tab5 Configuration"
help
Size of the stack used for the audio processing task.

config M5STACK_TAB5_ST7121_HW_ROTATION
bool "ST7121: apply 0/180 rotation in the panel (EXPERIMENTAL)"
default n
help
Route 0/180-degree display rotation to the ST7121 panel itself (MADCTL
GS/SS scan-direction flip) instead of the PPA/software rotation in the
flush path. EXPERIMENTAL: verified NOT working on at least some ST7121
Tab5 units - the 180-degree orientation renders corrupted, most likely
because the TDDI gate/source mux tables programmed at init (command
0xAC block) are matched to the normal scan direction and a MADCTL GS
flip alone reorders gate scanning without swapping them. Leave disabled
(the default) to keep the known-good PPA rotation for all orientations;
enable only to experiment on your unit.
Comment thread
finger563 marked this conversation as resolved.

endmenu
59 changes: 59 additions & 0 deletions components/m5stack-tab5/include/m5stack-tab5.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
#include <freertos/semphr.h>
#include <freertos/stream_buffer.h>
#include <freertos/task.h>

Expand Down Expand Up @@ -285,6 +286,10 @@ class M5StackTab5 : public BaseComponent {
/// \note This method queues the panel transfer asynchronously and may return
/// before the write has completed.
void write_lcd_lines(int xs, int ys, int xe, int ye, const uint8_t *data, uint32_t user_data);
// Issue one draw_bitmap and block until the DPI copy completes (or a
// bounded timeout). Caller must hold panel_op_mutex_. Returns false if the
// draw was rejected/failed (no completion will arrive).
bool draw_and_wait(int x1, int y1, int x2, int y2, const void *data);

/////////////////////////////////////////////////////////////////////////////
// Audio System
Expand Down Expand Up @@ -944,6 +949,44 @@ class M5StackTab5 : public BaseComponent {
esp_lcd_panel_handle_t panel{nullptr}; // color handle
} lcd_handles_{};

// Publication gate for the LCD state that flush() / write_lcd_lines() /
// on_display_rotation() read from other threads (lcd_handles_,
// dpi_framebuffer_ + dpi_framebuffer_bytes_, display_driver_,
// display_controller_). Those fields are plain (non-atomic) and are written
// by initialize_lcd() on the init thread; if the LVGL display already exists
// (initialize_display() called first) the LVGL thread can be flushing
// concurrently, so the readers must not touch them until they are all
// written. initialize_lcd() only runs while this flag is false (it refuses
// to re-initialize once the gate has opened — clearing the flag would not
// wait for readers that already observed true), writes every field, applies
// the initial panel rotation, and store-releases it true as its final
// publication step; the readers load-acquire it and bail out while it is
// false. The release/acquire pair makes all of the writes happen-before any
// read that observes true, and the flag never transitions true -> false.
std::atomic<bool> lcd_initialized_{false};

// Serializes every panel draw. esp_lcd_panel_draw_bitmap() is asynchronous and
// single-flight when the DMA2D hook is enabled (a second call while one is in
// flight returns ESP_ERR_INVALID_STATE), and flush() and the public,
// cross-thread write_lcd_lines() both issue draws. Holding this mutex across
// the draw AND its completion wait means only one transfer is ever in flight,
// so a direct write cannot make an LVGL flush's draw fail (which would leave
// LVGL waiting forever) and a direct write's completion cannot be mistaken for
// an LVGL flush completion.
std::mutex panel_op_mutex_;
// Signalled from the on_color_trans_done ISR for each completed draw; the
// issuing draw (under panel_op_mutex_) waits on it, making draws synchronous.
SemaphoreHandle_t draw_done_sem_{nullptr};

// The DPI panel's (PSRAM) framebuffer, queried from esp_lcd once the panel
// is created. flush() uses it to rotate LVGL draw buffers directly into the
// scanned-out framebuffer with the PPA, skipping the intermediate scratch
// buffer + draw_bitmap copy (which doubles the PSRAM traffic and can starve
// the DSI scan-out DMA into FIFO underruns / on-screen streaking). Null when
// unavailable, in which case flush() falls back to the scratch-buffer path.
void *dpi_framebuffer_{nullptr};
size_t dpi_framebuffer_bytes_{0};

// Display controller detection
DisplayController display_controller_{DisplayController::UNKNOWN};

Expand All @@ -952,6 +995,22 @@ class M5StackTab5 : public BaseComponent {
esp_err_t (*original_panel_init_)(esp_lcd_panel_t *panel){nullptr};

void flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map);
// Called by espp::Display (LV_EVENT_RESOLUTION_CHANGED) whenever the LVGL
// display rotation changes; routes the rotation to the display driver
// (MADCTL) when the active panel can honor it in hardware.
void on_display_rotation(const DisplayRotation &rotation);
// Gate-free core of on_display_rotation(): routes the rotation to the
// display driver (MADCTL) when the active panel honors it in hardware.
// Callers must guarantee display_driver_ / display_controller_ are safe to
// read: on_display_rotation() does so via its lcd_initialized_ acquire
// load; initialize_lcd() calls this directly on the init thread (which
// wrote those fields) BEFORE opening the gate, so the initial scan
// direction is programmed before any flush() can skip the PPA rotation.
void apply_panel_rotation(const DisplayRotation &rotation);
// Whether the active display controller applies the given LVGL rotation in
// panel hardware (via the display driver's set_rotation()/MADCTL), making
// buffer rotation (PPA / software) in flush() unnecessary.
bool panel_handles_rotation(lv_display_rotation_t rotation) const;
static bool notify_lvgl_flush_ready(esp_lcd_panel_handle_t panel,
esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx);

Expand Down
6 changes: 6 additions & 0 deletions components/m5stack-tab5/src/camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,12 @@ bool M5StackTab5::initialize_camera(const camera_frame_callback_t &callback,
// hardware pass. The callback receives this preview buffer.
ppa_client_config_t ppa_cfg = {};
ppa_cfg.oper_type = PPA_OPERATION_SRM;
// Throttle the PPA's AXI bursts (default 128 bytes): full-length PPA bursts
// against PSRAM are known to starve the DSI panel's continuous framebuffer
// scan-out DMA and underrun its FIFO, streaking the display (see the display
// PPA client in video.cpp and lvgl/lvgl#9590). The camera PPA runs every
// frame concurrently with display flushes, so keep its bursts short too.
ppa_cfg.data_burst_length = PPA_DATA_BURST_LENGTH_64;
if (ppa_register_client(&ppa_cfg, &camera_ppa_client_) != ESP_OK) {
logger_.error("Could not register the camera PPA client");
stop_camera();
Expand Down
Loading
Loading