1. LVGL 内存模型全景:从 SRAM 到 SDRAM
做嵌入式 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 里,保证访问速度。
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,不推荐新手用。
分配流程大致是这样:
- LVGL 内部对象(如按钮、标签)创建时,调用
lv_mem_alloc。 - 分配器在空闲链表中查找合适大小的块。
- 找到后分割或直接返回,更新链表。
- 释放时调用
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 内存池是核心枢纽,应用对象是最终使用者。帧缓冲虽然也占内存,但它通常独立于 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 分配。