diff --git a/variants/tdisplay_p4/HI8561Display.cpp b/variants/tdisplay_p4/HI8561Display.cpp index ba6d390..6fbe2fc 100644 --- a/variants/tdisplay_p4/HI8561Display.cpp +++ b/variants/tdisplay_p4/HI8561Display.cpp @@ -185,11 +185,21 @@ void HI8561Display::writePixelsRGB565(int x, int y, int w, int h, const uint16_t // Nearest-neighbour upscale the (half-res) LVGL band to the native panel: each source pixel becomes // an HI_UI_SCALE x HI_UI_SCALE block. One expanded band -> one draw_bitmap (exclusive end coords). const int S = HI_UI_SCALE, dw = w * S, dh = h * S; + // INTERNAL DMA RAM, not PSRAM — same reasoning as the AMOLED SKU (long note in + // RM69A10Display::writePixelsRGB565). This is a DPI/DSI panel whose single framebuffer must + // live in PSRAM and is streamed to the glass continuously, so keeping the upscale scratch in + // PSRAM too put three streams on one bus: the scratch read, the framebuffer write, and the + // DSI's own read. Any other PSRAM consumer — the priority-10 "lora_rx" drain task fires on + // every received packet — could then starve the DSI, and a DPI underrun shows as a + // whole-screen colour flash for one frame (#167). One band is 270x24 upscaled 2x = + // 540*48*2 = ~51 KB out of 768 KB SRAM. PSRAM stays the fallback. static uint16_t* s_up = nullptr; static size_t s_up_px = 0; size_t need = (size_t)dw * dh; if (need > s_up_px) { if (s_up) heap_caps_free(s_up); - s_up = (uint16_t*)heap_caps_malloc(need * sizeof(uint16_t), MALLOC_CAP_SPIRAM); + s_up = (uint16_t*)heap_caps_malloc(need * sizeof(uint16_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + if (!s_up) s_up = (uint16_t*)heap_caps_malloc(need * sizeof(uint16_t), MALLOC_CAP_SPIRAM); s_up_px = s_up ? need : 0; } if (s_up) { diff --git a/variants/tdisplay_p4/RM69A10Display.cpp b/variants/tdisplay_p4/RM69A10Display.cpp index 4deece1..79e2bf6 100644 --- a/variants/tdisplay_p4/RM69A10Display.cpp +++ b/variants/tdisplay_p4/RM69A10Display.cpp @@ -149,11 +149,26 @@ void RM69A10Display::writePixelsRGB565(int x, int y, int w, int h, const uint16_ // Nearest-neighbour upscale the (half-res) LVGL band to the native panel: each source pixel becomes // an RM_UI_SCALE x RM_UI_SCALE block. One expanded band -> one draw_bitmap (exclusive end coords). const int S = RM_UI_SCALE, dw = w * S, dh = h * S; + // The scratch band lives in INTERNAL DMA RAM, not PSRAM. This panel is a DPI/DSI one with a + // single ~1.4 MB framebuffer that can only live in PSRAM, and the DSI DMA streams that + // framebuffer out to the glass CONTINUOUSLY. Every draw_bitmap below is a copy INTO that + // framebuffer, so with the scratch also in PSRAM a flush put three streams on the same bus at + // once: the scratch read, the framebuffer write, and the DSI's own read. Anything else that + // wants PSRAM at that moment — notably the priority-10 "lora_rx" drain task, which wakes on + // every received packet — can then starve the DSI read, and a DPI underrun shows up as a + // whole-screen colour flash for one frame. That is the P4 "flicker on every RX" report + // (issue #167): it appears on any screen, because it is a display-bus problem and nothing to + // do with what is being drawn. Internal RAM removes the scratch read from the PSRAM bus. + // Cost is bounded and small: one LVGL band is 284x24 (LV_DRAW_BUF_LINES), so upscaled 2x this + // is 568*48*2 = ~53 KB out of the P4's 768 KB SRAM. PSRAM stays as the fallback so a + // fragmented heap degrades to the old behaviour instead of dropping to the unscaled path. static uint16_t* s_up = nullptr; static size_t s_up_px = 0; size_t need = (size_t)dw * dh; if (need > s_up_px) { if (s_up) heap_caps_free(s_up); - s_up = (uint16_t*)heap_caps_malloc(need * sizeof(uint16_t), MALLOC_CAP_SPIRAM); + s_up = (uint16_t*)heap_caps_malloc(need * sizeof(uint16_t), + MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + if (!s_up) s_up = (uint16_t*)heap_caps_malloc(need * sizeof(uint16_t), MALLOC_CAP_SPIRAM); s_up_px = s_up ? need : 0; } if (s_up) {