Запуск 240×240 GIF чуть более чем на 100 кБ — это буквально «танцевать на острие ножа». То, что вы сейчас видите: 54 % frag + зависание — это и есть момент, когда к концу танца острие ломается: в памяти слишком много фрагментов, не удаётся выделить место даже для одного полного декодированного кадра, lv_mem_alloc завершается ошибкой, а вся последующая цепочка «Couldn’t load the source / Image draw error» — лишь цепная реакция.
Декодер GIF в LVGL для скорости выделяет весь буфер декодирования одним malloc(240×240×4≈225 кБ), что больше, чем вся SRAM вашего MCU, и сбой — лишь вопрос времени.
62,2 кБ used кажется не растёт, потому что при неудаче выделения возвращается NULL, и статистика памяти вообще не учитывает этот запрос, поэтому цифра застревает на месте, но уровень фрагментации растёт: при повторном создании/удалении gif-объектов «маленькие дырки», оставленные неудачными попытками декодирования, разрезают пул на куски, и в итоге не удаётся найти даже непрерывный блок в 225 кБ.
Вывод:
- «Нативное декодирование» 240×240 true-color GIF на SRAM уровня 100 кБ — это Mission Impossible;
- Фрагментация — лишь последняя соломинка, переломившая хребет верблюду; корневая причина — «память под один кадр > общая доступная память».
Несколько спасительных приёмов (в порядке «хирургической сложности)**
-
Прямая «ампутация» — урезать разрешение вдвое
120×120 требует всего 56 кБ на кадр, можно выжить; на уровне 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 кБ (8-битная палитра), при отображении с LV_COLOR_DEPTH=16 память сокращается вдвое.
Цена: появляются полосы цвета, дизайнеру нужно предварительно конвертировать GIF в 256 цветов.
-
Собственное «потоковое» декодирование — не давать ему malloc-ить целый кадр сразу
Встроенный lv_gif в LVGL основан на gifdec, его непросто модифицировать, можно просто не использовать его:
- Снаружи применить библиотеку Tiny-GIF или LZ4 для дельты кадров, предварительно разбить GIF на индексированные 256-цветные кадры и сжать;
- В главном цикле периодически вызывать
lv_img_set_src(img, &frame_n), декодировать только текущий кадр, сразу после использования lv_mem_free();
- Оставлять буфер кадра только 240×240×1 Б + палитра 1 кБ, пик < 60 кБ;
- Между кадрами принудительно объединять дырки с помощью
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 не занимается вообще;
При переключении меняется только указатель, нет alloc/free, фрагментация 0.
Цена: Flash должен быть достаточно большим, 240×240×256 цветов 50 кадров ≈ 2,8 МБ, нужно смотреть, сможет ли чип переварить.
-
Ультимативное решение — подключить внешний PSRAM
Если MCU имеет интерфейс SPI/PSRAM (например, ESP32-S3, F1C200S), включить LV_MEM_CUSTOM, перенаправить lv_mem_alloc в PSRAM, заставив LVGL думать, что памяти «бесконечно», тогда сможет работать и 240×240 TRUE_COLOR.
Цена: замедление обновления примерно на 60 мс за кадр, и 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 кБ/кадр, после чего запустите ночной стресс-тест — frag стабилизируется ниже 15 %, и зависаний больше не будет.
Итог одним предложением
С 100 кБ SRAM не стоит мечтать о «true-color полном размере» — либо снижайте разрешение/глубину цвета, либо разбивайте кадры для потокового воспроизведения, либо подключайте внешнюю RAM; фрагментация — лишь симптом, источник проблемы в том, что «кадр больше всего доступного блока памяти», устраните источник — и фрагментация исчезнет сама собой.