display_gif_embedded: 15.4 -> 20.6 fps

- FPS/refresh/decode counter via LVGL monitor_cb (printed once per second)
- LVGL logging off, per-loop memory prints removed, loop delay 5 -> 1 ms
- double draw buffer (render while previous chunk is flushed)
- compile with -O2 instead of -Os

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
patriandClaude Opus 5.5 committed 2026-10-23 00:51:23 +02:00
1 parent 4e63fb65c6
commit 70114f73aa
5 files changed
+41 -10

No files matched your search

@@ -201,7 +201,7 @@
*-----------*/ *-----------*/
/*Enable the log module*/ /*Enable the log module*/
#define LV_USE_LOG 1 #define LV_USE_LOG 0
#if LV_USE_LOG #if LV_USE_LOG
/*How important log should be added: /*How important log should be added:
@@ -10,6 +10,7 @@
#define SCREEN_RES_VER 360 #define SCREEN_RES_VER 360
static lv_color_t *disp_draw_buf; static lv_color_t *disp_draw_buf;
static lv_color_t *disp_draw_buf2; // second buffer: LVGL renders into one while the other is sent to the LCD
static lv_disp_draw_buf_t draw_buf; static lv_disp_draw_buf_t draw_buf;
static lv_disp_drv_t disp_drv; static lv_disp_drv_t disp_drv;
static lv_indev_t *indev_touchpad; static lv_indev_t *indev_touchpad;
@@ -418,8 +419,9 @@ void scr_lvgl_init()
size_t lv_cache_rows = 72; size_t lv_cache_rows = 72;
disp_draw_buf = (lv_color_t *)heap_caps_malloc(lv_cache_rows * SCREEN_RES_HOR * 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); disp_draw_buf = (lv_color_t *)heap_caps_malloc(lv_cache_rows * SCREEN_RES_HOR * 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
disp_draw_buf2 = (lv_color_t *)heap_caps_malloc(lv_cache_rows * SCREEN_RES_HOR * 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
lv_init(); lv_init();
lv_disp_draw_buf_init(&draw_buf, disp_draw_buf, NULL, SCREEN_RES_HOR * lv_cache_rows); lv_disp_draw_buf_init(&draw_buf, disp_draw_buf, disp_draw_buf2, SCREEN_RES_HOR * lv_cache_rows);
lv_disp_drv_init(&disp_drv); lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = SCREEN_RES_HOR; disp_drv.hor_res = SCREEN_RES_HOR;
@@ -13,7 +13,10 @@ board_upload.flash_size = 16MB
; GIF compiled into firmware -> 3.4 MB, too big for huge_app (3 MB); 6.25 MB app slots ; GIF compiled into firmware -> 3.4 MB, too big for huge_app (3 MB); 6.25 MB app slots
board_build.partitions = default_16MB.csv board_build.partitions = default_16MB.csv
; optimise for speed instead of size (faster GIF decoding)
build_unflags = -Os
build_flags = build_flags =
-O2
-DBOARD_HAS_PSRAM -DBOARD_HAS_PSRAM
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1
; LVGL (also when compiled as a library) picks up include/lv_conf.h of this project ; LVGL (also when compiled as a library) picks up include/lv_conf.h of this project
@@ -12,6 +12,30 @@ const char* password = "123456789";
String httpGETRequest(const char* serverName); String httpGETRequest(const char* serverName);
// FPS measurement: LVGL calls this after every screen refresh (= one GIF frame here)
static volatile uint32_t refr_count = 0;
static volatile uint32_t refr_time_ms = 0;
static uint32_t busy_us = 0; // time spent inside lv_timer_handler()
static void refr_monitor_cb(lv_disp_drv_t* drv, uint32_t time, uint32_t px) {
refr_count++;
refr_time_ms += time;
}
void print_fps() {
static uint32_t last_ms = 0;
uint32_t now = millis();
if (now - last_ms < 1000) return;
// decode = busy time not spent refreshing the screen (mostly GIF decoding)
Serial.printf("FPS: %.1f avg refresh: %lu ms avg decode: %lu ms\n", refr_count * 1000.0f / (now - last_ms),
refr_count ? refr_time_ms / refr_count : 0,
refr_count ? (busy_us / 1000 - refr_time_ms) / refr_count : 0);
refr_count = 0;
refr_time_ms = 0;
busy_us = 0;
last_ms = now;
}
void my_print(const char* buf) { void my_print(const char* buf) {
Serial.println(buf); // Print logs to the serial monitor Serial.println(buf); // Print logs to the serial monitor
@@ -106,6 +130,7 @@ void setup() {
delay(200); delay(200);
Serial.begin(115200); Serial.begin(115200);
scr_lvgl_init(); scr_lvgl_init();
lv_disp_get_default()->driver->monitor_cb = refr_monitor_cb;
lv_init(); lv_init();
@@ -143,15 +168,11 @@ void setup() {
} }
void loop() { void loop() {
uint32_t t0 = micros();
lv_timer_handler(); lv_timer_handler();
vTaskDelay(5); busy_us += micros() - t0;
vTaskDelay(1);
lv_mem_monitor_t mem_mon; print_fps();
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
Serial.print("Available Heap: ");
Serial.println(esp_get_free_heap_size());
// if (WiFi.status() == WL_CONNECTED) { // if (WiFi.status() == WL_CONNECTED) {
+5
View File
@@ -96,6 +96,11 @@ Cheap dual-mode clones typically expose BLE service `FFF0` (notify `FFF1`, write
## Gotchas / observations ## Gotchas / observations
- **LVGL GIF bug (patched locally):** LVGL 8.3.11 `gifdec.c` (`gd_open_gif`) filled the canvas *opaque* with the GIF's background color. GIFs with a transparent background (e.g. `loligod.gif`, bg = RGB 28,29,49) showed that dark gray except where "restore to background" frames had cleared to transparent (→ screen black). **Patched** in the vendor copy `2-Program Example/Demo_Arduino/library/lvgl/src/extra/libs/gif/gifdec.c`: initial canvas alpha 0x00 (marked `LOCAL PATCH`). Re-apply if LVGL is ever replaced/upgraded. - **LVGL GIF bug (patched locally):** LVGL 8.3.11 `gifdec.c` (`gd_open_gif`) filled the canvas *opaque* with the GIF's background color. GIFs with a transparent background (e.g. `loligod.gif`, bg = RGB 28,29,49) showed that dark gray except where "restore to background" frames had cleared to transparent (→ screen black). **Patched** in the vendor copy `2-Program Example/Demo_Arduino/library/lvgl/src/extra/libs/gif/gifdec.c`: initial canvas alpha 0x00 (marked `LOCAL PATCH`). Re-apply if LVGL is ever replaced/upgraded.
- Most GIFs in `assets/gifs` are optimized (transparent unchanged pixels, disposal 1/2); `loligod.gif` has a fully transparent background (~84 % of each frame). - Most GIFs in `assets/gifs` are optimized (transparent unchanged pixels, disposal 1/2); `loligod.gif` has a fully transparent background (~84 % of each frame).
- **GIF performance** (360×360 loligod, embedded, measured with a `monitor_cb` FPS counter in `display_gif_embedded`):
baseline 15.4 fps (decode 27 ms + refresh 31 ms) → no LVGL log / no per-loop prints: no change → `vTaskDelay(1)`: 16.5 → **double draw buffer** (2×72 rows internal RAM): 19.5 (refresh 21 ms) → **`-O2`** instead of `-Os`: **20.6 fps** (refresh 19 ms, decode ~24 ms). GIF's own rate is 23.7 fps. From SD card it was only ~1.4 fps.
Remaining limit is LVGL's single-threaded GIF decode; going further would need decoding on the other core or a lighter format (e.g. pre-converted frames/MJPEG).
- **SD card (user's 487 MB SDSC)** intermittently fails to init (`sdmmc_init_ocr ... 0x107`) after resets; once stuck only a power cycle helps (card VDD is hard-wired to 3V3). Recommend a newer SDHC card.
- Reading boot logs: USB-CDC — reset via DTR=0/RTS pulse, then reopen the port (it re-enumerates).
- LVGL version: all JC3636W518 projects use the vendor-bundled **LVGL 8.3.11**. LVGL 9 would require porting `scr_st77916.h` (display/touch driver glue). - LVGL version: all JC3636W518 projects use the vendor-bundled **LVGL 8.3.11**. LVGL 9 would require porting `scr_st77916.h` (display/touch driver glue).
- Display is round: corners of the 360×360 framebuffer are not visible — design UI within a 360 px circle. - Display is round: corners of the 360×360 framebuffer are not visible — design UI within a 360 px circle.
- Compiling a 13.9 MB GIF into the firmware will not fit "Huge APP" (3 MB); large media should live on SD (or in a custom partition). - Compiling a 13.9 MB GIF into the firmware will not fit "Huge APP" (3 MB); large media should live on SD (or in a custom partition).