1. 课程导入

做嵌入式 UI 开发,说白了就是在跟内存打交道。LVGL 作为一个图形框架,它的内存管理方式直接决定了你的界面能画多复杂、跑多流畅。我刚开始用 LVGL 时,就踩过不少内存分配的坑——界面画到一半突然卡死,或者刷新率掉到个位数。后来把它的内存模型摸透了,这些问题才一个个解决。

今天咱们就从硬件到软件,把 LVGL 的内存体系彻底拆开看看。

1.1 硬件内存的两大阵营:SRAM 与 SDRAM

先搞清楚物理层面的区别。嵌入式设备里最常见的就是 SRAM 和 SDRAM,它们各有各的脾气。

特性 SRAM SDRAM
速度 极快(纳秒级访问) 较快(需刷新周期)
容量 小(通常几十 KB ~ 几 MB) 大(几十 MB ~ 几百 MB)
成本 高 低
典型用途 栈、中断向量、实时数据 帧缓冲、大块缓存
掉电保持 否 否

我在一个 STM32F429 项目里,SRAM 只有 256KB,但 SDRAM 外挂了 8MB。LVGL 的帧缓冲必须放在 SDRAM 里,否则 SRAM 根本装不下 800×480 的 RGB565 画面——光一个缓冲就要 800×480×2 ≈ 768KB。但 LVGL 内部的一些关键数据结构,比如显示驱动句柄、输入设备状态,我习惯放在 SRAM 里,保证访问速度。

我的习惯: 把 LVGL 的 lv_disp_drv_t 和 lv_indev_drv_t 这些高频访问的结构体分配到 SRAM 中,帧缓冲和动态对象池放到 SDRAM。这样既保证了响应速度,又充分利用了大容量。

1.2 LVGL 内部内存池架构剖析

LVGL 自己维护了一套内存分配系统,不直接依赖 C 标准库的 malloc/free。为什么?因为标准库的分配器在嵌入式环境里表现不稳定,容易产生碎片,而且线程安全方面也靠不住。

LVGL 的内存池本质上是一个大数组,你在 lv_conf.h 里通过 LV_MEM_SIZE 定义它的大小。分配算法默认用的是 dlmalloc(Doug Lea's malloc)的简化版,支持按大小分类的空闲链表。

核心配置项:

  • LV_MEM_SIZE:内存池总大小,单位字节。建议至少 32KB,复杂 UI 可能需要 128KB+。
  • LV_MEM_ADR:内存池起始地址,可以指定到特定内存区域(比如 SDRAM 的某个地址)。
  • LV_MEM_CUSTOM:设为 1 则使用外部 malloc/free,不推荐新手用。

分配流程大致是这样:

  1. LVGL 内部对象(如按钮、标签)创建时,调用 lv_mem_alloc。
  2. 分配器在空闲链表中查找合适大小的块。
  3. 找到后分割或直接返回,更新链表。
  4. 释放时调用 lv_mem_free,合并相邻空闲块。

嗯,这里要注意:LVGL 的内存池是全局唯一的。所有控件、样式、图片缓存都从这个池子里拿。所以池子大小直接决定了你能创建多少个控件。

我曾经踩过的坑: 在一个项目里把 LV_MEM_SIZE 设成了 16KB,结果界面稍微复杂一点就分配失败,屏幕直接黑掉。后来改成 64KB 才稳定。建议先设大一点,跑起来后用 lv_mem_monitor 查看实际使用量,再调小。

1.3 动态内存 vs 静态内存:怎么选?

LVGL 支持两种对象创建方式:动态分配和静态分配。很多人搞不清什么时候该用哪个,我直接说结论。

创建方式 API 示例 内存来源 适用场景
动态 lv_btn_create(lv_scr_act()) LVGL 内存池 数量不确定、频繁创建/销毁
静态 lv_btn_create_from_ptr(&my_btn_mem) 用户指定的静态缓冲区 数量固定、对实时性要求高

动态分配的好处是灵活。你可以在运行时根据用户操作创建或删除控件,内存自动管理。但代价是分配和释放有开销,而且会产生碎片。

静态分配则完全避免了碎片问题。你把一块内存提前分配好,LVGL 直接在上面构造对象。我习惯把主界面上的固定控件——比如状态栏、导航按钮——用静态方式创建。这些控件从开机到关机都在,没必要走动态分配。

我的建议: 对于页面级的动态内容(比如列表项、弹窗),用动态分配。对于永久存在的框架控件(标题栏、底部导航),用静态分配。这样既保证了灵活性,又减少了碎片。

1.4 一张图看懂 LVGL 内存模型

下面这张图把整个内存体系串起来了。从硬件层到 LVGL 内部,再到应用层,每个环节都标清楚了。

LVGL 内存模型全景图 硬件内存层 SRAM(片上高速) SDRAM(片外大容量) Flash(代码/常量) LVGL 内存池(dlmalloc 管理) 动态分配区 静态分配区 空闲块链表 碎片区 LVGL 应用对象 lv_obj_t lv_style_t lv_img_dsc_t lv_font_t ... 帧缓冲(通常位于 SDRAM) 说明: 箭头表示数据流向或分配关系

从这张图可以清楚看到:硬件内存是基础,LVGL 内存池是核心枢纽,应用对象是最终使用者。帧缓冲虽然也占内存,但它通常独立于 LVGL 内存池,由显示驱动直接管理。

1.5 实战:如何配置内存池到 SDRAM

假设你的板子有 SDRAM,起始地址是 0xC0000000,大小 8MB。你想把 LVGL 内存池放在 SDRAM 里,同时保留 SRAM 给系统栈和中断用。配置方法如下:

// lv_conf.h 中的配置
#define LV_MEM_SIZE    (512 * 1024)    // 512KB 内存池
#define LV_MEM_ADR     0xC0000000      // SDRAM 起始地址

// 注意:确保 SDRAM 初始化在 LVGL 初始化之前完成
// 否则 LVGL 访问的内存地址是无效的

如果你不想把整个内存池都放在 SDRAM,也可以只把大块对象放过去。我个人习惯的做法是:

  • 在 lv_conf.h 里把 LV_MEM_ADR 设为 NULL,让 LVGL 使用默认的 SRAM 内存池。
  • 对于需要大内存的对象(比如图片缓存),手动用 lv_mem_alloc_custom 指定从 SDRAM 分配。
注意: SDRAM 的访问速度比 SRAM 慢一个数量级。如果你把 LVGL 的样式表、字体这类频繁访问的数据也放 SDRAM,界面刷新率可能会下降。我一般只把帧缓冲和图片缓存放 SDRAM,其他小对象留在 SRAM。