100 kB of SRAM to run 240×240 GIF is basically “dancing on the tip of a knife”. The 54% frag + freeze you’re seeing now is essentially the knife tip breaking at the end of the dance—too many fragments in the memory pool, can’t even free up a complete decoded frame, lv_mem_alloc fails, and the subsequent series of “Couldn’t load the source / Image draw error” is just a chain reaction.
For speed, LVGL’s GIF decoder allocates the entire decode buffer at once with malloc(240×240×4≈225 kB), which is larger than your entire MCU’s SRAM—failure is just a matter of time.
The 62.2 kB used appears unchanged because the allocation failure returns NULL directly, and the memory statistics never get updated, so the number stays stuck there, but the fragmentation rate keeps climbing—when repeatedly creating/deleting gif objects, the “small holes” left by failed decoding slice the pool into pieces, until eventually not even a contiguous 225 kB block can be found.
Conclusion:
- “Native decoding” of 240×240 true-color GIF on 100 kB-class SRAM is Mission Impossible;
- Fragmentation is just the straw that breaks the camel’s back—the root cause is “single frame memory demand > total available memory”.
Several Workable Solutions (Ranked by “Surgical Level”)
-
Direct “Amputation”—Cut Resolution in Half
A 120×120 single frame only needs 56 kB, barely survivable; at the UI level use lv_img_set_zoom(gif, 256*2) to scale it back up—pixelation is better than freezing.
-
Change Color Depth—Don’t Use TRUE_COLOR
In lv_conf.h, lower LV_COLOR_DEPTH to 16 or even 8, and enable LV_IMG_CF_INDEXED_8, set the GIF decoded format to LV_IMG_CF_INDEXED_8, a single 240×240 frame drops to just 57 kB (8-bit palette), combined with LV_COLOR_DEPTH=16 for display, memory is halved directly.
Cost: Color banding, requires the designer to convert the GIF to 256 colors first.
-
DIY “Streaming” Decode—Don’t Let It malloc the Whole Frame at Once
LVGL’s built-in lv_gif is based on gifdec, modifying it is troublesome, so you might as well not use it:
- Externally use a Tiny-GIF or LZ4 frame-difference library to first split the GIF into per-frame 256-color indexes, then compress;
- In the main loop, periodically call
lv_img_set_src(img, &frame_n), only decode the current frame, immediately lv_mem_free() after use;
- Keep only 240×240×1 B + 1 kB palette per frame buffer, peak < 60 kB;
- Between frames use
lv_mem_defrag() (built into LVGL 8.3) to forcibly merge holes, can push frag back under 10%.
Not much code, but you have to manage frame rate and cache strategy yourself.
-
Move GIF to Flash, Treat as “Raw Frame Sequence”
Use GIMP or FFmpeg to split the GIF into 256-color BMPs, array-ize them into const uint8_t frame_xx[] LV_ATTRIBUTE_MEM_ALIGN LV_IMG_CF_INDEXED_8;
This way RAM only needs an lv_img_dsc_t structure (a few dozen bytes) pointing to Flash, occupying zero SRAM;
When switching just change the pointer, no alloc/free, fragmentation is 0.
Cost: Flash must be large enough, 240×240×256-color 50 frames ≈ 2.8 MB, depends on whether the chip can handle it.
-
Ultimate Solution—Add External PSRAM
If the MCU has SPI/PSRAM interface (e.g., ESP32-S3, F1C200S), enable LV_MEM_CUSTOM, redirect lv_mem_alloc to PSRAM, let LVGL think it has “unlimited” memory, 240×240 TRUE_COLOR can run.
Cost: Refresh slows by about 60 ms per frame, and DMA must be configured properly, otherwise the screen will flicker.
Quick Emergency Fix Code (Color Depth + Defragmentation)
// 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 // Don't touch this unless you have PSRAM
// Force defragmentation after each GIF switch
lv_obj_del(gif); // Delete old object first
lv_mem_defrag(); // Merge fragments
gif = lv_gif_create(parent);
lv_gif_set_src(gif, &my_indexed_gif);
When converting the GIF source file with LVGLImageConverter, select “Indexed 8” + “RLE”, this can generally compress 240×240 to under 20 kB/frame, then run an overnight stress test, frag will stabilize below 15%, and it basically won’t freeze again.
One-Sentence Summary
With 100 kB SRAM, don’t even think about “true-color full-size”—either reduce resolution/color depth, or split frames for streaming playback, or add external RAM; fragmentation is just a symptom, the real disease is “single frame larger than entire memory block”, cut out the disease and the fragments disappear naturally.