大家好,
LVGL 8.3,已啟用 LV_USE_MEM_MONITOR 1,主控台日誌開啟。
我正在一個 lv_gif 物件中切換多個 GIF(以編譯時期 C 陣列形式儲存)。幾分鐘後 UI 凍結。
凍結前記憶體監視器顯示:
開始:已使用 62.2 kB (63 %),碎片率 30 %
一段時間後:已使用 62.2 kB (63 %),碎片率 54 %
最後的警告訊息總是:
[Warn] (10.886, +10886) lv_gif_set_src: 無法載入來源 (in lv_gif.c line #77)
[Warn] (10.894, +8) _lv_img_cache_open: 影像繪製無法開啟影像資源
(in lv_img_cache.c line #125)
[Warn] (10.904, +10) lv_draw_img: 影像繪製錯誤 (in lv_draw_img.c line #84)
[Warn] (10.917, +13) _lv_img_cache_open: 影像繪製無法開啟影像資源
(in lv_img_cache.c line #125)
[Warn] (10.927, +10) lv_draw_img: 影像繪製錯誤 (in lv_draw_img.c line #84)
看起來堆積記憶體過於破碎,導致 LVGL 無法為下一幀分配解碼器緩衝區。
有人遇過這種情況嗎?重複變更 GIF 來源時,有什麼推薦的方法可以回收/重複使用記憶體來避免碎片化?
感謝!
偶爾在切換 GIF 時,螢幕上仍會出現「沒有資料」。
只剩下略多於 100 kB 的 SRAM,根本沒有空間解碼 240×240 的 GIF——在這麼少的記憶體下看來是不可能的。
100 kB 出头跑 240×240 GIF 基本就是“在刀尖上跳舞”,你现在看到的 54 % frag + 卡死,其实就是跳到最后刀尖断了——内存池里碎片太多,连一副完整解码帧都腾不出来,lv_mem_alloc 失败,后面一连串“Couldn’t load the source / Image draw error”只是连锁反应。
LVGL 的 GIF 解码器为了快,会一次性给整幅解码缓冲 malloc(240×240×4≈225 kB),这比你整颗 MCU 的 SRAM 都大,失败是迟早的事。
62.2 kB used 看起来没涨,是因为申请失败直接返回 NULL,内存统计根本没加上去,所以数字一直卡在那里,但碎片率却一路飙高——反复创建/删除 gif 对象时,解码失败留下的“小窟窿”把池子割得七零八落,最后连 225 kB 连续块都找不到。
结论:
- 240×240 真彩 GIF 在 100 kB 级 SRAM 上“原生解码”是 Mission Impossible;
- 碎片化只是压垮骆驼的最后一根稻草,根因是“单帧内存需求 > 总可用内存”。
能救的几种套路(按“手术级别”排序)
-
直接“截肢”——把分辨率砍半
120×120 单帧只要 56 kB,勉强能活;UI 层面用 lv_img_set_zoom(gif, 256*2) 再拉回去,颗粒感总比卡死强。
-
改色深——别用 TRUE_COLOR
在 lv_conf.h 里把 LV_COLOR_DEPTH 降到 16 甚至 8,同时把 LV_IMG_CF_INDEXED_8 打开,GIF 解码后格式设为 LV_IMG_CF_INDEXED_8,单帧 240×240 只剩 57 kB(8 bit 调色板),再配 LV_COLOR_DEPTH=16 显示,内存直接减半。
代价:颜色带条带,需要美工把 GIF 先转 256 色。
-
自己“流式”解码——别让它一次 malloc 整帧
LVGL 自带的 lv_gif 是基于 gifdec,改起来麻烦,可以干脆不用它:
- 外部用一个 Tiny-GIF 或 LZ4 帧差分库,先把 GIF 拆成每帧 256 色索引,再压缩;
- 主循环里定时
lv_img_set_src(img, &frame_n),只给当前帧解码,用完立即 lv_mem_free();
- 每帧缓冲只留 240×240×1 B + 调色板 1 kB,峰值 < 60 kB;
- 帧与帧之间用
lv_mem_defrag()(LVGL 8.3 已自带)强制合并空洞,能把 frag 压回 10 % 以内。
代码量不大,但得自己管帧率、缓存策略。
-
把 GIF 挪到 Flash,直接当“原始序列帧”
用 GIMP 或 FFmpeg 把 GIF 拆成 256 色 BMP,数组化后放 const uint8_t frame_xx[] LV_ATTRIBUTE_MEM_ALIGN LV_IMG_CF_INDEXED_8;
这样 RAM 里只需要一个 lv_img_dsc_t 结构体(几十字节)指向 Flash,完全不占 SRAM;
切换时只改指针,不会申请/释放,碎片为 0。
代价:Flash 要够大,240×240×256 色 50 帧 ≈ 2.8 MB,得看芯片能不能吃得下。
-
终极方案——上外置 PSRAM
如果 MCU 带 SPI/PSRAM 接口(例如 ESP32-S3、F1C200S),把 LV_MEM_CUSTOM 打开,把 lv_mem_alloc 重定向到 PSRAM,让 LVGL 以为有“无限”内存,240×240 TRUE_COLOR 也能跑。
代价:刷新慢一帧 60 ms 左右,且 DMA 要配好,不然屏会闪。
先救急的三行代码(色深 + 碎片整理)
// lv_conf.h
#define LV_COLOR_DEPTH 16
#define LV_IMG_CF_INDEXED_8 1
#define LV_USE_MEM_MONITOR 1
#define LV_MEM_CUSTOM 0 // 先别动,除非有 PSRAM
// 每次切完 GIF 后强行整理
lv_obj_del(gif); // 旧对象先删
lv_mem_defrag(); // 合并碎片
gif = lv_gif_create(parent);
lv_gif_set_src(gif, &my_indexed_gif);
把 GIF 源文件用 LVGLImageConverter 转格式时选 “Indexed 8” + “RLE”,一般能把 240×240 压到 20 kB/帧以内,再跑一晚上压力测试,frag 稳定在 15 % 以下,基本不会再卡死。
一句话总结
100 kB SRAM 就别想着“真彩全尺寸”了,要么降分辨率/色深,要么拆帧流式播放,要么外挂 RAM;碎片化只是症状,真正的病灶是“单帧比整块内存还大”,把病灶切掉,碎片自然消失。
根據您的描述和日誌輸出,此問題確實是由 LVGL 動態記憶體分配器中的 記憶體碎片化 所造成。
為何會發生碎片化
- 您只有約 100 kB 的 SRAM,且 LVGL 的內建記憶體管理器(啟用
LV_USE_MEM_MONITOR 時)顯示,在重複切換 GIF 後,碎片化程度從 30% 上升到 54%。
- 每次切換 GIF 都涉及:
- 關閉先前的 GIF(釋放其解碼器緩衝區和影像快取)
- 開啟新的 GIF(為解碼後的幀分配新的緩衝區,可能還包括調色盤)
- 這些分配與釋放的大小各不相同(由於 GIF 的尺寸/色彩深度不同),久而久之導致堆積碎片化。
- 一旦碎片化程度過高,即使總體上有可用記憶體,也可能沒有足夠大的連續區塊來容納下一個 GIF 的幀緩衝區——進而導致「Couldn’t load the source」和「Image draw cannot open the image resource」警告。
減少碎片化的解決方案
-
為 GIF 物件預先分配固定的記憶體池
- 使用
LV_MEM_CUSTOM = 1 並提供您自己的分配器,該分配器使用預先分配的區塊來處理 GIF 資料。
- 這樣可以避免使用通用堆積,並確保 GIF 緩衝區始終從同一區域分配。
-
重複使用相同的 GIF 元件
不要銷毀並重新建立 GIF 元件,而是保留一個元件並僅更改其來源:
lv_gif_set_src(existing_gif_obj, new_gif_data);
這樣可以避免重複分配/釋放元件結構本身。
-
降低 GIF 解析度或色彩深度
- 240×240 的 ARGB8888 幀需要 240×240×4 ≈ 230 kB,這已經超過您 100 kB 的 SRAM。
- 將 GIF 轉換為索引色彩(LV_COLOR_FORMAT_I1/I8)並降低解析度,使每個幀都能符合您的記憶體預算。
-
啟用 LVGL 的記憶體重組功能(如果可用)
在較新的 LVGL 版本中,您可以定期呼叫 lv_mem_defrag() 來合併可用區塊。在 v8.3 中,如果沒有內建功能,您可能需要自行實作簡單的重組策略。
-
為影像使用獨立的堆積
將您的 SRAM 分割:一個區域給 LVGL 的一般物件,另一個給影像/GIF 緩衝區。這樣可將碎片化隔離在影像堆積中,避免影響其他 UI 操作。
鑑於您的記憶體非常有限(100 kB),顯示完整的多幀 240×240 GIF 本來就很困難。上述步驟有助於減輕碎片化問題,但您可能仍需要降低 GIF 的資源需求才能穩定運作。