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) <noreply@anthropic.com>
This commit is contained in:
Kaj Schittecat
2026-07-31 11:40:40 +02:00
co-authored by Claude Opus 4.8
parent 1bc0c7ab39
commit 1e6e121c85
2 changed files with 26 additions and 14 deletions
+12
View File
@@ -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
+14 -14
View File
@@ -3,6 +3,7 @@
#include "RM69A10Display.h"
#include <Arduino.h>
#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;
}