Ejecutar un GIF de 240×240 con poco más de 100 kB es básicamente “bailar sobre la punta de un cuchillo”. El 54 % de fragmentación + bloqueo que ves ahora es, en realidad, que al final la punta del cuchillo se rompió: hay demasiados fragmentos en el pool de memoria, no se puede liberar espacio ni para un solo fotograma decodificado completo, lv_mem_alloc falla, y la cadena de “Couldn´t load the source / Image draw error” que sigue no es más que una reacción en cadena.
El decodificador de GIF de LVGL, para ser rápido, asigna de una sola vez el búfer de decodificación completo con malloc(240×240×4≈225 kB), que es más grande que toda la SRAM de tu MCU; el fallo es solo cuestión de tiempo.
El “62.2 kB used” parece que no aumenta porque cuando la solicitud falla devuelve NULL directamente y las estadísticas de memoria ni siquiera se actualizan, por lo que el número se queda atascado ahí, pero la tasa de fragmentación se dispara: al crear/eliminar objetos gif repetidamente, los “agujeritos” que dejan los fallos de decodificación van cortando el pool en pedazos, hasta que no se encuentra ni un bloque contiguo de 225 kB.
Conclusión:
- La “decodificación nativa” de un GIF a color real de 240×240 en SRAM de nivel de 100 kB es Misión Imposible;
- La fragmentación es solo la gota que colma el vaso, la causa raíz es “demanda de memoria por fotograma > memoria total disponible”.
Algunas soluciones que pueden salvarlo (ordenadas por “nivel de cirugía”)
-
“Amputación” directa: reduce la resolución a la mitad
Un fotograma de 120×120 solo necesita 56 kB, apenas puede sobrevivir; a nivel UI usa lv_img_set_zoom(gif, 256*2) para escalarlo de nuevo, la pixelación es mejor que el bloqueo.
-
Cambia la profundidad de color: no uses TRUE_COLOR
En lv_conf.h baja LV_COLOR_DEPTH a 16 o incluso 8, y activa LV_IMG_CF_INDEXED_8, establece el formato después de la decodificación del GIF a LV_IMG_CF_INDEXED_8, un fotograma de 240×240 se queda en solo 57 kB (paleta de 8 bits), combinado con LV_COLOR_DEPTH=16 para mostrar, la memoria se reduce a la mitad directamente.
Costo: bandas de color, necesitas que el diseñador convierta el GIF a 256 colores primero.
-
Decodificación “streaming” por tu cuenta: no permitas que haga malloc de todo el fotograma de una vez
El lv_gif incluido en LVGL está basado en gifdec, es complicado de modificar, puedes simplemente no usarlo:
- Usa externamente una librería Tiny-GIF o LZ4 de diferencia entre fotogramas, primero descompón el GIF en índices de 256 colores por fotograma, luego comprime;
- En el bucle principal usa
lv_img_set_src(img, &frame_n) periódicamente, solo decodifica el fotograma actual, usa lv_mem_free() inmediatamente después;
- Deja solo 240×240×1 B + paleta de 1 kB como búfer por fotograma, pico < 60 kB;
- Entre fotogramas usa
lv_mem_defrag() (LVGL 8.3 ya lo incluye) para forzar la fusión de huecos, puede bajar el frag a menos del 10 %.
No es mucho código, pero tienes que gestionar el framerate y la estrategia de caché tú mismo.
-
Mueve el GIF a Flash, úsalo como “fotogramas de secuencia sin procesar”
Usa GIMP o FFmpeg para dividir el GIF en BMP de 256 colores, después conviértelos a arrays y ponlos en const uint8_t frame_xx[] LV_ATTRIBUTE_MEM_ALIGN LV_IMG_CF_INDEXED_8;
Así solo necesitas una estructura lv_img_dsc_t (decenas de bytes) en RAM apuntando a Flash, no ocupa SRAM en absoluto;
Al cambiar solo modificas el puntero, no hay solicitudes/liberaciones, fragmentación 0.
Costo: Flash debe ser lo suficientemente grande, 50 fotogramas de 240×240×256 colores ≈ 2.8 MB, depende de si el chip puede soportarlo.
-
Solución definitiva: usa PSRAM externo
Si tu MCU tiene interfaz SPI/PSRAM (por ejemplo ESP32-S3, F1C200S), activa LV_MEM_CUSTOM, redirige lv_mem_alloc a PSRAM, haz que LVGL piense que tiene memoria “infinita”, incluso 240×240 TRUE_COLOR puede funcionar.
Costo: el refresco es lento aproximadamente 60 ms por fotograma, y debes configurar bien el DMA o la pantalla parpadeará.
Tres líneas de código para salir del apuro (profundidad de color + desfragmentación)
// 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 // no lo toques a menos que tengas PSRAM
// Forzar desfragmentación después de cada cambio de GIF
lv_obj_del(gif); // primero elimina el objeto antiguo
lv_mem_defrag(); // combina fragmentos
gif = lv_gif_create(parent);
lv_gif_set_src(gif, &my_indexed_gif);
Cuando conviertas el archivo fuente GIF con LVGLImageConverter, selecciona “Indexed 8” + “RLE”, generalmente puede comprimir 240×240 a menos de 20 kB/fotograma, luego ejecuta una prueba de estrés durante toda la noche, frag se estabiliza por debajo del 15 %, básicamente no se bloqueará más.
Resumen en una frase:
Con 100 kB de SRAM no pienses en “color real a tamaño completo”, o reduces la resolución/profundidad de color, o divides los fotogramas para reproducción streaming, o añades RAM externa; la fragmentación es solo un síntoma, la verdadera enfermedad es “un fotograma es más grande que toda la memoria disponible”, elimina la enfermedad y la fragmentación desaparecerá naturalmente.