display_gif_embedded: port to LVGL 9.6.0 (20.6 -> 23.4 fps)

- lvgl/lvgl@9.6.0 via lib_deps, lv_conf.h from the 9.6 template
- scr_st77916.h: lv_display/lv_indev API, RGB565 byte swap in flush,
  lv_tick_set_cb
- FPS counter via display refresh events
- loligod.c: lv_image_dsc_t descriptor

GIF now plays at its own frame rate; decode time halved by LVGL 9.6's
AnimatedGIF decoder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
patriandClaude Opus 5.5 committed 2026-10-23 01:00:18 +02:00
1 parent 70114f73aa
commit 9684ab39f2
7 files changed
+2567 -617

No files matched your search

+1 -1
View File
@@ -14,7 +14,7 @@ Split out of `2-Program Example/Demo_Arduino/ST77916_LVGL_DEMO/` on 2026-10-22 a
|---|---|---|---|
| `display_speed_wifi/` | JC3636W518 | GIF background from SD (`A:wallpaper.gif`) + joins AP `ESP32-Access-Point`, polls `http://192.168.4.1/speed`, shows it in a label | `ST77916_LVGL_DEMO/8.4/` |
| `display_gif_sd/` | JC3636W518 | GIF from SD (`A:wallpaper.gif`) + static text label | `ST77916_LVGL_DEMO/working version/` |
| `display_gif_embedded/` | JC3636W518 | GIF compiled into firmware (`src/loligod.c`, 2.2 MB GIF → 3.4 MB firmware, uses `default_16MB.csv` with 6.25 MB app slots). WiFi code commented out. Stops in `setup()` if no SD card | `ST77916_LVGL_DEMO/` (top level) |
| `display_gif_embedded/` | JC3636W518 | GIF compiled into firmware (`src/loligod.c`, 2.2 MB GIF → 3.4 MB firmware, uses `default_16MB.csv` with 6.25 MB app slots). **LVGL 9.6.0**, plays at full GIF speed (~23.4 fps), prints FPS once per second. GIF starts without SD card. WiFi code commented out. | `ST77916_LVGL_DEMO/` (top level) |
| `elm327_wifi_bridge/` | **classic ESP32** (not S3) | BT Classic → ELM327 named `OBDII` (PIN 1234), WiFi AP `ESP32-Access-Point` / `123456789`, serves `/speed` (kph) | `ST77916_LVGL_DEMO/esp23_elm327_to_wifi/` |
| `elmulator/` | classic ESP32 | Fake ELM327 for testing without a car. Library in `lib/ELMulator`, envs `simple`, `custom`, `wifi` (BT name `OBDII`) | `ST77916_LVGL_DEMO/ELMulator-master/` |
| `assets/gifs/` | — | GIF files (`dump/` = unused collection) | `ST77916_LVGL_DEMO/gifs/` |
File diff suppressed because it is too large. Load diff
@@ -9,10 +9,9 @@
#define SCREEN_RES_HOR 360
#define SCREEN_RES_VER 360
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_drv_t disp_drv;
static uint8_t *disp_draw_buf;
static uint8_t *disp_draw_buf2; // second buffer: LVGL renders into one while the other is sent to the LCD
static lv_display_t *disp = NULL;
static lv_indev_t *indev_touchpad;
static ESP_PanelBacklight *backlight = NULL;
static ESP_PanelLcd *lcd = NULL;
@@ -251,20 +250,25 @@ const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = {
#define TFT_SPI_FREQ_HZ (50 * 1000 * 1000)
static void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
// LVGL tick source (replaces LV_TICK_CUSTOM of LVGL 8)
static uint32_t lv_tick_millis(void)
{
ESP_PanelLcd *lcd = (ESP_PanelLcd *)disp->user_data;
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_p);
return millis();
}
static void my_disp_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
ESP_PanelLcd *lcd = (ESP_PanelLcd *)lv_display_get_user_data(disp);
const int w = lv_area_get_width(area);
const int h = lv_area_get_height(area);
// LVGL 9 renders little-endian RGB565, the ST77916 expects big-endian (LVGL 8 used LV_COLOR_16_SWAP)
lv_draw_rgb565_swap(px_map, w * h);
lcd->drawBitmap(area->x1, area->y1, w, h, (const uint8_t *)px_map);
}
IRAM_ATTR bool onRefreshFinishCallback(void *user_data)
{
lv_disp_drv_t *drv = (lv_disp_drv_t *)user_data;
lv_disp_flush_ready(drv);
lv_display_flush_ready((lv_display_t *)user_data);
return false;
}
@@ -330,9 +334,9 @@ void set_brightness(uint8_t bri)
backlight->setBrightness(bri);
}
static void touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
static void touchpad_read(lv_indev_t *indev, lv_indev_data_t *data)
{
ESP_PanelTouch *tp = (ESP_PanelTouch *)indev_drv->user_data;
ESP_PanelTouch *tp = (ESP_PanelTouch *)lv_indev_get_user_data(indev);
ESP_PanelTouchPoint point;
int read_touch_result = tp->readPoints(&point, 1);
@@ -352,12 +356,11 @@ static lv_indev_t *indev_init(ESP_PanelTouch *tp)
{
ESP_PANEL_CHECK_FALSE_RET(tp != nullptr, nullptr, "Invalid touch device");
ESP_PANEL_CHECK_FALSE_RET(tp->getHandle() != nullptr, nullptr, "Touch device is not initialized");
static lv_indev_drv_t indev_drv_tp;
lv_indev_drv_init(&indev_drv_tp);
indev_drv_tp.type = LV_INDEV_TYPE_POINTER;
indev_drv_tp.read_cb = touchpad_read;
indev_drv_tp.user_data = (void *)tp;
return lv_indev_drv_register(&indev_drv_tp);
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, touchpad_read);
lv_indev_set_user_data(indev, (void *)tp);
return indev;
}
void scr_lvgl_init()
@@ -418,22 +421,20 @@ void scr_lvgl_init()
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_buf2 = (lv_color_t *)heap_caps_malloc(lv_cache_rows * SCREEN_RES_HOR * 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
size_t buf_bytes = lv_cache_rows * SCREEN_RES_HOR * 2; // RGB565
disp_draw_buf = (uint8_t *)heap_caps_malloc(buf_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
disp_draw_buf2 = (uint8_t *)heap_caps_malloc(buf_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
lv_init();
lv_disp_draw_buf_init(&draw_buf, disp_draw_buf, disp_draw_buf2, SCREEN_RES_HOR * lv_cache_rows);
lv_tick_set_cb(lv_tick_millis);
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = SCREEN_RES_HOR;
disp_drv.ver_res = SCREEN_RES_VER;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
disp_drv.user_data = (void *)lcd;
lv_disp_t *disp = lv_disp_drv_register(&disp_drv);
disp = lv_display_create(SCREEN_RES_HOR, SCREEN_RES_VER);
lv_display_set_flush_cb(disp, my_disp_flush);
lv_display_set_buffers(disp, disp_draw_buf, disp_draw_buf2, buf_bytes, LV_DISPLAY_RENDER_MODE_PARTIAL);
lv_display_set_user_data(disp, (void *)lcd);
if (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB)
{
lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)disp->driver);
lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)disp);
}
indev_touchpad = indev_init(touch);
}
@@ -23,8 +23,10 @@ build_flags =
-DLV_CONF_INCLUDE_SIMPLE
-Iinclude
; vendor-bundled libraries: ESP32_Display_Panel 0.1.4, ESP32_IO_Expander 0.0.2, lvgl 8.3.11
; vendor-bundled libraries: ESP32_Display_Panel 0.1.4, ESP32_IO_Expander 0.0.2
; (the vendor folder also has lvgl 8.3.11 - ignored here, this project uses LVGL 9 from lib_deps)
lib_extra_dirs = ../../2-Program Example/Demo_Arduino/library
lib_deps = lvgl/lvgl@9.6.0
upload_port = COM21
monitor_port = COM21
@@ -167846,12 +167846,14 @@ const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_LOLIGOD u
};
const lv_img_dsc_t loligod = {
LV_IMG_CF_RAW,//LV_IMG_CF_RAW_CHROMA_KEYED
0,
0,
360,
360,
2181829,
loligod_map,
/* LVGL 9 image descriptor: raw GIF file data, decoded by the lv_gif widget */
const lv_image_dsc_t loligod = {
.header = {
.magic = LV_IMAGE_HEADER_MAGIC,
.cf = LV_COLOR_FORMAT_RAW,
.w = 360,
.h = 360,
},
.data_size = 2181829,
.data = loligod_map,
};
@@ -12,14 +12,31 @@ const char* password = "123456789";
String httpGETRequest(const char* serverName);
// FPS measurement: LVGL calls this after every screen refresh (= one GIF frame here)
// FPS measurement: counts screen refreshes that actually rendered something (= 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) {
static void refr_event_cb(lv_event_t* e) {
static uint32_t refr_start_ms = 0;
static bool rendered = false;
switch (lv_event_get_code(e)) {
case LV_EVENT_REFR_START:
refr_start_ms = millis();
rendered = false;
break;
case LV_EVENT_RENDER_START:
rendered = true;
break;
case LV_EVENT_REFR_READY:
if (rendered) {
refr_count++;
refr_time_ms += time;
refr_time_ms += millis() - refr_start_ms;
}
break;
default:
break;
}
}
void print_fps() {
@@ -44,9 +61,9 @@ void my_print(const char* buf) {
void display_animation() {
lv_obj_t* img;
LV_IMG_DECLARE(loligod);
LV_IMAGE_DECLARE(loligod);
img = lv_gif_create(lv_scr_act());
img = lv_gif_create(lv_screen_active());
Serial.print("Available Heap before: ");
Serial.println(esp_get_free_heap_size());
@@ -94,7 +111,7 @@ void display_text() {
// lv_style_set_shadow_ofs_x(&style_text, 0);
// lv_style_set_shadow_ofs_y(&style_text, 0);
label1 = lv_label_create(lv_scr_act());
label1 = lv_label_create(lv_screen_active());
// Apply the style to the label
lv_obj_add_style(label1, &style_text, LV_PART_MAIN);
@@ -130,14 +147,12 @@ void setup() {
delay(200);
Serial.begin(115200);
scr_lvgl_init();
lv_disp_get_default()->driver->monitor_cb = refr_monitor_cb;
lv_init();
lv_display_add_event_cb(lv_display_get_default(), refr_event_cb, LV_EVENT_ALL, NULL);
//lv_log_register_print_cb(my_print); // Register print function for LVGL logs
// Get the active screen (root object for the display)
lv_obj_t* screen = lv_scr_act();
lv_obj_t* screen = lv_screen_active();
// Set the background color of the entire screen to black
lv_obj_set_style_bg_color(screen, lv_color_black(), 0); // 0 is the part ID for the main part
+3 -1
View File
@@ -101,7 +101,9 @@ Cheap dual-mode clones typically expose BLE service `FFF0` (notify `FFF1`, write
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 9.6.0 port** (`display_gif_embedded`, 2026-10-23): **23.4 fps** (= the GIF's own 23.7 fps; decode ~12 ms, refresh ~24 ms) vs 20.6 fps tuned LVGL 8.3. LVGL 9.6 replaced gifdec with bitbank2 AnimatedGIF (2× faster decode, correct transparency — no local patch needed).
Port notes: `lib_deps = lvgl/lvgl@9.6.0` (takes priority over the vendor lvgl 8 in `lib_extra_dirs`); `lv_conf.h` generated from the 9.6 template (CLIB malloc, refresh 15 ms, GIF on, examples off); in 9.6 public headers live in `include/lvgl/` and `LV_COLOR_DEPTH` is deprecated → `LV_COLOR_FORMAT_DEFAULT` (RGB565); no LV_COLOR_16_SWAP → `lv_draw_rgb565_swap()` in the flush cb; no LV_TICK_CUSTOM → `lv_tick_set_cb(millis)`; no `monitor_cb` → display events `LV_EVENT_REFR_START/RENDER_START/REFR_READY`; images are `lv_image_dsc_t` with `LV_IMAGE_HEADER_MAGIC`. `lv_gif_set_color_format()` can switch the GIF buffer from ARGB8888 to RGB565 (untested).
- LVGL version: the other 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.
- 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).
- The top-level demo `setup()` returns early if no SD card is present, before showing anything.