From 1e6e121c8555e68e892ad924652c8245b75ac381 Mon Sep 17 00:00:00 2001 From: Kaj Schittecat Date: Fri, 31 Jul 2026 11:40:40 +0200 Subject: [PATCH] p4: blank the framebuffer before display-on; widen LVGL bands (#167) Two changes against the whole-screen colour flash. 1. Real fix for the flash testers see BEFORE the boot splash. The panel was switched on and only then was the framebuffer cleared, so the DSI streamed a freshly-allocated (garbage) PSRAM buffer to the screen until the clear caught up. Blank it with a memset + cache flush while the panel is still dark. That also removes 39 back-to-back draw_bitmap transfers into a buffer the panel is scanning out. 2. Diagnostic, P4 only: LVGL bands 24 -> 64 lines. This panel is DSI video mode with a SINGLE framebuffer, so every band is copied into the buffer being actively scanned. At 24 lines a full-screen repaint is ~26 back-to-back transfers into that live buffer. The reported triggers (message arrives, opening a chat with unread, adding/deleting a contact) all rebuild the whole screen; panning the map repaints only its own region and never flashes, which is the shape this predicts. Wider bands cut the same repaint to ~10 transfers. Sized so the 2x upscale scratch still fits internal SRAM, so burst count is the only variable that moves. Supersedes the write-path theory: the flash still occurs with contacts, history and prefs all on SD, and the pre-splash case involves no write at all. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ui-touch/UITask.cpp | 12 +++++++++++ variants/tdisplay_p4/RM69A10Display.cpp | 28 ++++++++++++------------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/ui-touch/UITask.cpp b/src/ui-touch/UITask.cpp index 736df7e..f95e4b0 100644 --- a/src/ui-touch/UITask.cpp +++ b/src/ui-touch/UITask.cpp @@ -1950,7 +1950,19 @@ static bool g_cap_touch_hw_started = false; // build). 24 × 240 × 2 B = 11.5 KB. Flush time is bound by SPI clock // (80 MHz), not memcpy speed, so the throughput delta is small — // LVGL just calls flush_cb more often. +#if defined(HAS_TDISPLAY_P4) +// DIAGNOSTIC (#167): the T-Display P4 is a DSI video-mode panel with a SINGLE framebuffer, so every +// LVGL band is copied into the buffer the panel is actively scanning out. At 24 lines a full-screen +// repaint is ~26 back-to-back DMA transfers into that live buffer -- and the events testers report +// (a message arriving, opening a chat with unread, adding/deleting a contact) are exactly the ones +// that rebuild the whole screen, while panning the map only repaints its own region and never +// flashes. Wider bands make the same repaint ~10 transfers instead of ~26. Kept just small enough +// that the 2x upscale scratch still fits internal SRAM (568*128*2 = ~145 KB), so this changes ONLY +// the burst count and not where the scratch lives. +static constexpr int LV_DRAW_BUF_LINES = 64; +#else static constexpr int LV_DRAW_BUF_LINES = 24; +#endif static lv_color_t* g_draw_buffer = nullptr; static uint32_t g_draw_buf_px = 240 * LV_DRAW_BUF_LINES; // actual buffer size in px; shrinks if the full alloc fails at boot #if CAP_LARGE_SCREEN diff --git a/variants/tdisplay_p4/RM69A10Display.cpp b/variants/tdisplay_p4/RM69A10Display.cpp index cdcad22..ee5d922 100644 --- a/variants/tdisplay_p4/RM69A10Display.cpp +++ b/variants/tdisplay_p4/RM69A10Display.cpp @@ -3,6 +3,7 @@ #include "RM69A10Display.h" #include #include "esp_heap_caps.h" +#include "esp_cache.h" // esp_cache_msync — flush the memset framebuffer out to the DSI DMA #include "esp_ldo_regulator.h" #include "Xl9535.h" // SCREEN_RST lives on the expander (reset_gpio_num = -1) #include "esp_lcd_mipi_dsi.h" @@ -109,6 +110,19 @@ bool RM69A10Display::begin() { esp_lcd_panel_reset(_panel); if (esp_lcd_panel_init(_panel) != ESP_OK) { Serial.println("[RM69A10] panel_init fail"); return false; } + + // Blank the framebuffer BEFORE switching the panel on. It is freshly allocated PSRAM, so it holds + // garbage; turning the panel on first meant the DSI streamed that garbage to the screen until the + // clear below finished -- the colour flash testers see before the boot splash (#167). Write the + // buffer directly (memset + a cache flush to make it visible to the DSI's DMA) instead of pushing + // strips through draw_bitmap: it is one pass with the panel still dark, and it avoids a burst of + // 39 back-to-back DMA transfers into a buffer the panel is scanning out. + void* fb = nullptr; + if (esp_lcd_dpi_panel_get_frame_buffer(_panel, 1, &fb) == ESP_OK && fb) { + const size_t fb_bytes = (size_t)RM_W * RM_H * 2; + memset(fb, 0, fb_bytes); + esp_cache_msync(fb, fb_bytes, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + } esp_lcd_panel_disp_on_off(_panel, true); // Brightness: the init array ships 0x51=0 (placeholder), and RM69A10 brightness is driven at @@ -125,20 +139,6 @@ bool RM69A10Display::begin() { cbs.on_color_trans_done = rm69a10TransDone; esp_lcd_dpi_panel_register_event_callbacks(_panel, &cbs, nullptr); - // Clear the panel framebuffer to black once, so nothing stale shows before LVGL's first flush. - { - const int SH = 32; - uint16_t* strip = (uint16_t*)heap_caps_calloc((size_t)RM_W * SH, 2, MALLOC_CAP_SPIRAM); - if (strip) { - for (int y0 = 0; y0 < RM_H; y0 += SH) { - int hh = (y0 + SH <= RM_H) ? SH : (RM_H - y0); - esp_lcd_panel_draw_bitmap(_panel, 0, y0, RM_W, y0 + hh, strip); - if (s_flush_sem) xSemaphoreTake(s_flush_sem, pdMS_TO_TICKS(100)); // let the copy finish before reusing/freeing strip - } - free(strip); - } - } - Serial.printf("[RM69A10] up %dx%d\n", RM_W, RM_H); return true; }