3、内存调试基础:LVGL内存分配机制、内存泄漏检测方法、内存池配置检查

内存问题,是嵌入式LVGL开发里最让人头疼的坑之一。我见过太多项目,界面画到一半突然卡死,或者运行几小时后莫名其妙花屏。十有八九,都是内存惹的祸。

这一章,咱们就把LVGL的内存机制掰开揉碎。你搞懂了它,调试起来心里就有底了。

3.1 LVGL的内存分配机制

LVGL默认使用动态内存分配。说白了,就是它自己管着一块内存池,你需要控件、图片、字体时,就从池子里拿。用完再还回去。

这个池子有多大?由你决定。在 lv_conf.h 里有个关键宏:

#define LV_MEM_SIZE (32U * 1024U)    /* 32KB内存池 */

我个人习惯,先给个保守值。比如屏幕是320x240,16位色,一个全屏缓冲区就占150KB。那内存池至少得256KB起步。你想想看,如果只给32KB,随便加载一张图片就爆了。

LVGL内部用了几种分配策略:

  • 标准malloc/free:如果你没定义自定义分配器,LVGL就用C库的malloc。这在PC上没问题,但在单片机上,malloc容易产生碎片。
  • LVGL内置分配器:推荐用这个。它用了一个简单的内存块链表,分配速度快,碎片少。在 lv_conf.h 里开启:
#define LV_MEM_CUSTOM 0   /* 使用LVGL内置分配器 */

我在项目中遇到过,用标准malloc跑了一个月,内存碎片导致分配失败。换成内置分配器后,稳如老狗。

3.2 内存泄漏检测方法

内存泄漏,就是申请了内存没释放。LVGL里最常见的泄漏点:

  • 创建了控件,但忘记删除
  • 加载了图片,但没调用 lv_img_cache_invalidate_src()
  • 动态分配的字符串或样式,没释放

怎么检测?LVGL自带了一个神器:lv_mem_monitor_t

lv_mem_monitor_t mon;
lv_mem_monitor(&mon);

LV_LOG_USER("已使用: %d bytes, 空闲: %d bytes, 碎片: %d%%",
            mon.total_size - mon.free_size,
            mon.free_size,
            mon.frag_pct);

把这个打印放在主循环里,每隔几秒输出一次。如果已使用内存持续增长,恭喜你,泄漏了。

关键指标:碎片率超过20%就要警惕了。我见过一个项目,碎片率到了35%,分配一个100字节的控件都失败。

还有一个更粗暴的方法:在 lv_conf.h 里开启内存统计:

#define LV_MEMCPY_MEMSET_STD 1
#define LV_MEM_LOG 1

这样每次分配和释放都会打印日志。你可以在串口里搜 allocfree,看看数量是否匹配。

我的小技巧:写一个测试函数,反复创建和删除同一个控件。如果内存只增不减,那肯定有泄漏。我曾经用这个方法,半天就揪出了三个泄漏点。

3.3 内存池配置检查

配置内存池,不是拍脑袋定个大小就完事了。你得检查几个关键点:

检查项 说明 常见问题
内存池大小 LV_MEM_SIZE 设太小,运行中分配失败
对齐方式 LV_MEM_ATTR 和 LV_MEM_ADR 不对齐会导致硬件异常
自定义分配器 LV_MEM_CUSTOM 用标准malloc容易碎片
缓冲区大小 LV_VER_RES_MAX * LV_HOR_RES_MAX * 2 太小会频繁刷新,性能下降

嗯,这里要注意。内存池的起始地址,最好4字节对齐。有些MCU要求8字节对齐。不对齐的话,访问内存会触发硬件错误。我踩过这个坑,查了两天才发现是地址没对齐。

怎么检查对齐?加一行代码:

if ((uint32_t)lv_mem_buf & 0x3) {
    LV_LOG_ERROR("内存池未4字节对齐!");
}

还有一个容易被忽略的点:LVGL的显示缓冲区。它不占用内存池,但占用RAM。如果你用双缓冲,每个缓冲区大小是 LV_HOR_RES_MAX * LV_VER_RES_MAX * 2 字节。320x240的屏幕,16位色,一个缓冲区就是150KB。两个就是300KB。加上内存池,总RAM消耗轻松超过512KB。

警告:别把内存池和显示缓冲区设得太大,超过MCU的RAM总量。否则程序直接跑不起来。我曾经在STM32F407上试过,RAM只有192KB,我设了256KB的内存池,结果上电就死。

最后,给你一个检查清单。每次移植LVGL时,对着过一遍:

  1. 确认 LV_MEM_SIZE 不超过MCU可用RAM
  2. 确认内存池地址对齐(4字节或8字节)
  3. 开启 LV_MEM_LOG 观察分配释放是否平衡
  4. 运行压力测试:反复创建/删除控件,监控内存变化
  5. 检查碎片率,超过20%考虑增大内存池或改用内存管理算法

做到这几点,内存问题基本就能防住八成。剩下的两成,靠经验和日志慢慢排查。别急,调试本身就是个手艺活。