Chạy GIF 240×240 với hơn 100 kB về cơ bản là “nhảy múa trên đầu mũi dao”, 54% frag + treo máy bạn đang thấy, thực ra là đến cuối đầu mũi dao gãy——quá nhiều mảnh vỡ trong memory pool, không thể giải phóng được một khung giải mã hoàn chỉnh, lv_mem_alloc thất bại, chuỗi “Couldn’t load the source / Image draw error” sau đó chỉ là phản ứng dây chuyền.
Bộ giải mã GIF của LVGL vì muốn nhanh, sẽ malloc(240×240×4≈225 kB) một lần cho toàn bộ decode buffer, lớn hơn cả SRAM của MCU, thất bại chỉ là vấn đề thời gian.
62.2 kB used có vẻ không tăng, là vì khi xin cấp phát thất bại trả về NULL luôn, thống kê bộ nhớ không hề cộng thêm, nên con số cứ mãi đứng yên, nhưng tỷ lệ phân mảnh lại tăng vùn vụt——khi tạo/xóa đối tượng gif liên tục, những “lỗ nhỏ” do giải mã thất bại cắt pool thành từng mảnh, cuối cùng không tìm được khối liên tục 225 kB nào.
Kết luận:
- GIF 240×240 true color “giải mã native” trên SRAM cấp 100 kB là Mission Impossible;
- Phân mảnh chỉ là giọt nước tràn ly, nguyên nhân gốc rễ là “nhu cầu bộ nhớ một khung > tổng bộ nhớ khả dụng”.
Một số cách cứu (sắp xếp theo “cấp độ phẫu thuật”)
-
“Cắt cụt” trực tiếp——giảm độ phân giải một nửa
120×120 chỉ cần 56 kB một khung, vừa đủ sống sót; ở tầng UI dùng lv_img_set_zoom(gif, 256*2) kéo lại, răng cưa còn hơn treo máy.
-
Thay đổi độ sâu màu——đừng dùng TRUE_COLOR
Trong lv_conf.h hãy giảm LV_COLOR_DEPTH xuống 16 thậm chí 8, đồng thời bật LV_IMG_CF_INDEXED_8, đặt format sau giải mã GIF là LV_IMG_CF_INDEXED_8, 240×240 chỉ còn 57 kB một khung (bảng màu 8 bit), kết hợp hiển thị LV_COLOR_DEPTH=16, bộ nhớ giảm một nửa ngay.
Cái giá phải trả: có băng màu, cần designer chuyển GIF sang 256 màu trước.
-
Tự “stream” decode——đừng để nó malloc cả khung một lần
lv_gif tích hợp sẵn của LVGL dựa trên gifdec, sửa khó, có thể bỏ luôn:
- Dùng thư viện Tiny-GIF hoặc LZ4 frame diff bên ngoài, tách GIF thành từng khung 256 màu index rồi nén;
- Trong main loop định kỳ
lv_img_set_src(img, &frame_n), chỉ giải mã khung hiện tại, xong ngay lv_mem_free();
- Buffer mỗi khung chỉ giữ 240×240×1 B + bảng màu 1 kB, peak < 60 kB;
- Giữa các khung dùng
lv_mem_defrag() (LVGL 8.3 đã tích hợp) ép gộp lỗ rỗng, đưa frag xuống < 10%.
Lượng code không nhiều, nhưng phải tự quản frame rate, cache policy.
-
Chuyển GIF sang Flash, dùng luôn làm “raw frame sequence”
Dùng GIMP hoặc FFmpeg tách GIF thành BMP 256 màu, array hóa rồi đặt const uint8_t frame_xx[] LV_ATTRIBUTE_MEM_ALIGN LV_IMG_CF_INDEXED_8;
Như vậy RAM chỉ cần một struct lv_img_dsc_t (vài chục byte) trỏ vào Flash, không chiếm SRAM chút nào;
Khi chuyển khung chỉ đổi con trỏ, không cấp phát/giải phóng, frag = 0.
Cái giá: Flash phải đủ lớn, 240×240×256 màu 50 khung ≈ 2.8 MB, xem chip có nuốt nổi không.
-
Giải pháp tối thượng——gắn PSRAM ngoài
Nếu MCU có giao diện SPI/PSRAM (ví dụ ESP32-S3, F1C200S), bật LV_MEM_CUSTOM, redirect lv_mem_alloc sang PSRAM, để LVGL tưởng có bộ nhớ “vô hạn”, 240×240 TRUE COLOR cũng chạy được.
Cái giá: refresh chậm khoảng 60 ms một khung, và phải config DMA tốt không màn hình sẽ nhấp nháy.
Ba dòng code cấp cứu trước (độ sâu màu + defrag)
// 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 // đừng động, trừ khi có PSRAM
// Sau mỗi lần chuyển GIF ép defrag
lv_obj_del(gif); // xóa object cũ trước
lv_mem_defrag(); // gộp mảnh vỡ
gif = lv_gif_create(parent);
lv_gif_set_src(gif, &my_indexed_gif);
Khi chuyển file GIF gốc bằng LVGLImageConverter chọn “Indexed 8” + “RLE”, thường có thể nén 240×240 xuống < 20 kB/khung, chạy stress test cả đêm, frag ổn định < 15%, cơ bản không còn treo máy.
Tóm tắt một câu
Đừng nghĩ đến “true color full size” với 100 kB SRAM, hoặc giảm độ phân giải/độ sâu màu, hoặc phát từng khung stream, hoặc gắn RAM ngoài; phân mảnh chỉ là triệu chứng, bệnh gốc là “một khung lớn hơn cả khối bộ nhớ”, cắt bỏ bệnh gốc thì mảnh vỡ tự biến mất.