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:
1 parent
70114f73aa
commit
9684ab39f2
7 files changed
+2563
-613
No files matched your search
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in new issue
Block a user