3、LVGL框架初探:架构概览、源码结构与配置详解
好,咱们正式开始接触LVGL了。说实话,我第一次打开LVGL源码的时候,也被它的目录结构吓了一跳——文件夹一层套一层,光看名字根本不知道从哪下手。但后来摸透了它的设计思路,发现其实挺清晰的。
这一节,我带你把LVGL的骨架摸一遍。搞懂了架构和配置,后面移植起来就顺风顺水了。
3.1 LVGL整体架构:分层设计
LVGL本质上是一个分层设计的图形框架。你可以把它想象成一个三层蛋糕:
- 底层(硬件抽象层):负责跟你的硬件打交道——屏幕怎么刷、触摸屏怎么读、定时器怎么跑。
- 中间层(核心库):这是LVGL的心脏,包含对象管理、事件处理、样式系统、动画引擎等。
- 上层(组件层):按钮、标签、滑块、图表……你看到的那些现成控件都在这里。
我习惯把这种结构叫做「依赖倒置」——上层依赖中间层,中间层依赖底层,但底层反过来通过回调函数被上层调用。这样做的好处很明显:换硬件平台时,你只需要重写底层那几行代码,上层完全不用动。
核心思想:LVGL的架构设计让你「一次编写,到处运行」。换屏幕、换MCU,改的只是底层那几行驱动代码。
3.2 源码结构分析:别被文件夹吓到
下载LVGL源码后,你会看到这样一个目录结构(以LVGL v8.x为例):
lvgl/
├── lv_conf_template.h # 配置文件模板
├── lvgl.h # 主头文件,包含所有API
├── src/
│ ├── lv_core/ # 核心:对象、事件、样式
│ ├── lv_draw/ # 绘图引擎:软件渲染、GPU加速
│ ├── lv_extra/ # 扩展控件:图表、仪表盘等
│ ├── lv_font/ # 字体引擎:内置字体、自定义字体
│ ├── lv_hal/ # 硬件抽象层:显示、输入、定时器
│ ├── lv_misc/ # 杂项:内存管理、日志、数学工具
│ ├── lv_themes/ # 主题:默认主题、深色主题
│ └── lv_widgets/ # 基础控件:按钮、标签、滑块……
└── examples/ # 示例代码
我个人建议你重点关注三个目录:
- lv_hal/:移植的核心战场,你主要改这里面的文件。
- lv_core/:理解LVGL的运行机制,对象树、事件传播都在这里。
- lv_conf_template.h:配置开关,决定LVGL的功能集和内存占用。
我记得第一次移植时,傻乎乎地把整个源码都看了一遍,结果越看越懵。后来才明白——你不需要理解每一行代码,只需要知道「哪个文件管什么事」就够了。
我的习惯:把 lv_conf_template.h 复制一份改名为 lv_conf.h,放在项目根目录。然后从 lv_hal/ 开始读,其他目录用到什么再看什么。
3.3 配置选项详解:lv_conf.h 是总开关
lv_conf.h 是LVGL的「总控制台」。你想想看,一个图形库要适配从几百KB内存的MCU到几百MB内存的MPU,没有配置开关根本不可能。这个文件里定义了上百个宏,我挑几个最关键的给你讲透。
3.3.1 基础开关:启用/禁用LVGL
#if LV_CONF_H
/* 如果定义了 LV_CONF_H,表示使用外部配置 */
#else
/* 否则使用 lv_conf.h 中的默认配置 */
#endif
/* 最重要的开关:启用LVGL */
#define LV_USE_LVGL_H 1
这个没什么好说的,必须打开。
3.3.2 颜色深度:决定显存占用
/* 颜色深度:16位(RGB565)最常见 */
#define LV_COLOR_DEPTH 16
/* 可选值:8、16、32 */
/* 8位:颜色少,省内存,适合低端MCU */
/* 16位:平衡之选,大多数场景够用 */
/* 32位:颜色丰富,但显存翻倍 */
我在一个STMF103项目上用过8位色,说实话,颜色过渡有明显的色块感。如果你的屏幕支持16位,我建议直接用16位,性价比最高。
3.3.3 内存管理:别让LVGL吃掉你的RAM
/* LVGL动态内存大小(单位:字节) */
#define LV_MEM_SIZE (32 * 1024) /* 32KB */
/* 内存管理方式 */
#define LV_MEM_CUSTOM 0 /* 0=使用LVGL内置内存管理器 */
/* 1=使用标准库malloc/free */
这里有个坑:LV_MEM_SIZE 不是越大越好。我见过有人给一个只有64KB RAM的芯片分配了48KB给LVGL,结果系统直接跑不起来。一般来说,给LVGL分配总RAM的30%~50%比较合理。
避坑指南:我曾经在一个项目里把 LV_MEM_SIZE 设得太大,导致 lv_init() 直接卡死。后来用调试器一看,内存分配失败,系统进入硬件错误中断。建议先设小一点,跑起来后再慢慢调大。
3.3.4 显示缓冲区:影响刷新流畅度
/* 显示缓冲区大小(单位:像素数) */
#define LV_HOR_RES_MAX 240 /* 屏幕宽度 */
#define LV_VER_RES_MAX 320 /* 屏幕高度 */
#define LV_DPI_DEF 100 /* 每英寸点数,用于字体缩放 */
/* 缓冲区行数:1、2、10…… */
#define LV_BUFFER_HEIGHT 10 /* 建议10~60行 */
缓冲区越大,刷新越流畅,但占用的RAM也越多。我一般先用10行测试,如果屏幕刷新有撕裂感,再逐步增加。
3.3.5 控件开关:按需裁剪
/* 基础控件 */
#define LV_USE_BTN 1 /* 按钮 */
#define LV_USE_LABEL 1 /* 标签 */
#define LV_USE_SLIDER 1 /* 滑块 */
/* 高级控件(按需开启) */
#define LV_USE_CHART 0 /* 图表:占用较大,不用就关掉 */
#define LV_USE_TABLE 0 /* 表格 */
#define LV_USE_CALENDAR 0 /* 日历 */
这个很好理解——用不到的控件就关掉,能省不少Flash和RAM。我习惯先把所有控件都关了,用到哪个再开哪个。
3.3.6 其他重要配置
| 配置项 | 说明 | 推荐值 |
|---|---|---|
LV_USE_LOG |
日志输出,调试时打开 | 1(调试)/ 0(发布) |
LV_TICK_PERIOD |
LVGL心跳周期(毫秒) | 1~5 |
LV_USE_ANIMATION |
动画引擎 | 1(建议开启) |
LV_USE_FILESYSTEM |
文件系统支持(如需要加载图片) | 0(无文件系统)/ 1(有) |
LV_FONT_DEFAULT |
默认字体 | &lv_font_montserrat_14 |
小技巧:发布版本记得把 LV_USE_LOG 关掉,否则串口会被日志刷爆,影响性能。我吃过这个亏,调试时开着日志没问题,一发布忘了关,客户反馈界面卡顿……
3.4 配置实战:一个典型的STM32F4配置
给你看一个我实际用过的配置片段,跑在STM32F429上,屏幕是480x272的RGB接口屏:
/* 颜色深度 */
#define LV_COLOR_DEPTH 16
/* 内存 */
#define LV_MEM_SIZE (64 * 1024) /* 64KB */
#define LV_MEM_CUSTOM 0
/* 显示参数 */
#define LV_HOR_RES_MAX 480
#define LV_VER_RES_MAX 272
#define LV_DPI_DEF 100
#define LV_BUFFER_HEIGHT 20
/* 开启的控件 */
#define LV_USE_BTN 1
#define LV_USE_LABEL 1
#define LV_USE_SLIDER 1
#define LV_USE_BAR 1
#define LV_USE_IMG 1
/* 关闭的控件 */
#define LV_USE_CHART 0
#define LV_USE_TABLE 0
#define LV_USE_CALENDAR 0
#define LV_USE_MSGBOX 0
/* 其他 */
#define LV_USE_LOG 1 /* 调试阶段开着 */
#define LV_TICK_PERIOD 1
#define LV_USE_ANIMATION 1
这个配置跑下来,Flash占用大约120KB,RAM占用约80KB(含显存缓冲区)。对于F429来说绰绰有余。
3.5 小结:你现在该做什么
嗯,这一节内容不少,但核心就三件事:
- 理解架构:LVGL是三层结构,移植只改底层。
- 熟悉源码:重点看 lv_hal/、lv_core/ 和 lv_conf.h。
- 配置裁剪:根据你的硬件资源,合理设置颜色深度、内存大小和控件开关。