Fix/dsi dma2d psram buffers (#24949)

* Fix silently-dropped LVGL flush chunks on ESP32-P4 DSI panels

esp_lcd_panel_draw_bitmap()'s DMA2D path takes an internal draw
semaphore with a 0 timeout and returns ESP_ERR_INVALID_STATE (without
drawing anything) if the previous async transfer hasn't completed yet.
DSIPanel::pushColors() ignored that return value, and lv_flush_callback()
always calls lv_disp_flush_ready() regardless, so LVGL believed the
flush succeeded even when the chunk was silently dropped - leaving
stale framebuffer content on screen for that region.

Retry instead of dropping the chunk. The transfer typically completes
in well under 1ms, so poll in short bursts rather than 1ms FreeRTOS
ticks; a bounded retry count still catches a genuinely stuck transfer
and logs it instead of looping forever.

* Allow LVGL DSI draw buffers to use PSRAM on ESP32-P4

lv_pixel_buf/lv_pixel_buf2 previously preferred MALLOC_CAP_INTERNAL,
costing ~120KB of scarce internal SRAM on boards where PSRAM is
otherwise nearly idle (e.g. a 1024x600 DSI panel at 60 flushlines).
Prefer MALLOC_CAP_SPIRAM instead, falling back to internal RAM if
PSRAM is unavailable - safe now that the previous commit fixes the
DMA2D transfer race that PSRAM's slower read speed was exposing.

Also switch the flush-timing debug log from millis() to micros() and
drop its DMA-path exclusion: at a ~60KB/transfer chunk size the whole
operation completes in a few hundred microseconds, so millisecond
resolution just read back 0 or 1 and the DMA path - the one we
actually needed data from - was never logged at all.

* Remove defaulting to PSRAM for now

---------

Co-authored-by: s-hadinger <49731213+s-hadinger@users.noreply.github.com>
This commit is contained in:
LesNewellands-hadinger authored and GitHub committed 2026-10-09 15:22:37 +02:00
1 parent af56a31050
commit 53dbdb72dc
2 files changed
+24 -5

No files matched your search

@@ -10,6 +10,7 @@
#include "esp_ldo_regulator.h"
#include "driver/gpio.h"
#include <rom/cache.h>
#include "esp_rom_sys.h"
extern void AddLog(uint32_t loglevel, const char* formatP, ...);
@@ -215,7 +216,25 @@ bool DSIPanel::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
}
bool DSIPanel::pushColors(uint16_t *data, uint32_t len, bool not_swapped) {
esp_err_t ret = esp_lcd_panel_draw_bitmap(panel_handle, window_x0, window_y0, window_x1, window_y1, data);
esp_err_t ret;
uint16_t retries = 0;
// esp_lcd_panel_draw_bitmap's DMA2D path takes the draw semaphore with a 0 timeout and
// returns ESP_ERR_INVALID_STATE (without drawing anything) if the previous async transfer
// hasn't completed yet - retry instead of silently dropping this chunk, which would leave
// stale framebuffer content on screen for that region. A ~60KB chunk transfer typically
// completes in well under 1ms, so poll in short bursts rather than in 1ms FreeRTOS ticks -
// the completion ISR isn't blocked by this busy-wait, so it doesn't delay noticing success.
const uint16_t max_retries = 300;
do {
ret = esp_lcd_panel_draw_bitmap(panel_handle, window_x0, window_y0, window_x1, window_y1, data);
if (ret == ESP_ERR_INVALID_STATE) {
esp_rom_delay_us(50);
retries++;
}
} while (ret == ESP_ERR_INVALID_STATE && retries < max_retries);
if (ret != ESP_OK) {
AddLog(3, "DSI: draw_bitmap failed after %d retries: %d", retries, ret);
}
return (ret == ESP_OK);
}
+4 -4
View File
@@ -95,19 +95,19 @@ void lv_flush_callback(lv_display_t *disp, const lv_area_t *area, uint8_t *color
}
uint32_t pixels_len = width * height;
uint32_t chrono_start = millis();
uint32_t chrono_start = micros();
renderer->setAddrWindow(area->x1, area->y1, area->x1+width, area->y1+height);
renderer->pushColors((uint16_t *)color_p, pixels_len, true);
renderer->setAddrWindow(0,0,0,0);
renderer->Updateframe();
uint32_t chrono_time = millis() - chrono_start;
uint32_t chrono_time = micros() - chrono_start;
lv_disp_flush_ready(disp);
if (pixels_len >= 10000 && (!renderer->lvgl_param.use_dma)) {
if (HighestLogLevel() >= LOG_LEVEL_DEBUG_MORE) {
AddLog(LOG_LEVEL_DEBUG_MORE, D_LOG_LVGL "Refreshed %d pixels in %d ms (%i pix/ms)", pixels_len, chrono_time,
chrono_time > 0 ? pixels_len / chrono_time : -1);
AddLog(LOG_LEVEL_DEBUG_MORE, D_LOG_LVGL "Refreshed %d pixels in %d us (%i pix/ms)", pixels_len, chrono_time,
chrono_time > 0 ? (pixels_len * 1000 / chrono_time) : -1);
}
}
// if there is a display callback, call it