3. LVGL架构与核心概念
拿到 LVGL 源码包,第一件事别急着编译。我习惯先花半小时把目录结构摸一遍。说白了,这就像拿到一张新地图,先看清主干道在哪,哪些是死胡同,心里才有底。
LVGL 的源码组织得相当规整。你打开根目录,会看到几个核心文件夹:src、examples、docs、tests。真正干活的全在 src 里。咱们今天就把它拆开揉碎了讲。
3.1 源码目录总览
src 目录下分了 8 个核心子目录。我刚开始接触时也觉得多,但用熟了就会发现,每个目录各司其职,互不干扰。
| 目录名 | 作用 | 我常用的频率 |
|---|---|---|
lv_core |
核心机制:对象树、事件、刷新 | ★★★★★ |
lv_draw |
底层绘制引擎:像素、线条、图像 | ★★★★☆ |
lv_widgets |
所有控件:按钮、标签、滑块等 | ★★★★★ |
lv_hal |
硬件抽象层:显示、输入、时钟 | ★★★★☆ |
lv_misc |
工具函数:数学、内存、字符串 | ★★★☆☆ |
lv_font |
字体引擎:加载、渲染、缓存 | ★★★☆☆ |
lv_themes |
主题系统:配色、样式预设 | ★★☆☆☆ |
lv_api_map.h |
API 映射:新旧版本兼容 | ★☆☆☆☆ |
嗯,这里要注意:lv_api_map.h 是给从 v7 升级到 v8 的人用的。如果你是全新项目,我建议直接跳过它,别给自己找麻烦。
3.2 核心组件详解
3.2.1 lv_core —— 心脏
lv_core 是整个 LVGL 的调度中心。它管三件事:对象生命周期、事件分发、屏幕刷新。
你想想看,屏幕上每个按钮、每个标签,在 LVGL 里都是一个 lv_obj_t 对象。这些对象怎么创建、怎么销毁、父子关系怎么维护,全在 lv_obj.c 里。我记得有一次调试一个界面卡死的问题,查了半天发现是对象树里出现了循环引用。从那以后,我每次创建复杂界面都会用 lv_obj_get_child_cnt() 检查一下树结构。
事件系统在 lv_event.c 里。LVGL 的事件是冒泡机制——子控件的事件会向上传递给父控件。这个设计很巧妙,但也是个坑。我曾经遇到一个情况:按钮点击事件被父容器拦截了,按钮死活没反应。后来发现是父容器注册了 LV_EVENT_CLICKED 且没有调用 lv_event_stop_bubbling()。
核心文件清单(lv_core):
lv_obj.c/lv_obj.h—— 对象基础操作lv_obj_tree.c—— 对象树管理lv_event.c—— 事件系统lv_refr.c—— 屏幕刷新机制lv_group.c—— 输入设备组管理
3.2.2 lv_draw —— 画笔
lv_draw 是 LVGL 的绘制引擎。它不直接操作硬件,而是通过 lv_draw_ctx_t 上下文把绘制命令发给底层驱动。
这里有个关键概念:绘制分两步走。第一步是 lv_draw_...() 系列函数生成绘制命令,第二步是 lv_disp_flush_ready() 把数据刷到屏幕。我刚开始做移植时,总以为绘制函数会直接写显存,结果发现屏幕一片黑。后来才明白,LVGL 用的是「脏矩形」刷新策略——只重绘变化的部分。
绘制引擎支持多种后端:SW(软件渲染)、SDL、OpenGL。默认是 SW,用 CPU 逐像素画。如果你的 MCU 没有 GPU,老老实实用 SW 就行。
个人经验: 调试绘制问题时,可以打开 LV_USE_PERF_MONITOR 和 LV_USE_REFR_DEBUG。这两个宏能帮你看到每帧的绘制区域和耗时,定位性能瓶颈特别好用。
3.2.3 lv_widgets —— 积木盒
这是你最常打交道的目录。LVGL 提供了 30+ 种控件,从基础的 lv_btn、lv_label 到复杂的 lv_table、lv_chart,应有尽有。
每个控件本质上是一个 lv_obj_t 加上一些专属属性和事件。比如 lv_btn 就是在基础对象上加了点击状态和样式。我习惯把控件分为三类:
- 基础控件: 按钮、标签、滑块、进度条 —— 90% 的界面靠它们就够了
- 容器控件: 页面、滚动条、选项卡 —— 用来做布局和导航
- 高级控件: 图表、表格、日历 —— 需要数据驱动的场景
你写代码时,尽量用基础控件组合出复杂界面。直接上高级控件虽然省事,但灵活性差,而且占内存。我在一个项目里用 lv_chart 画实时波形,结果发现刷新一快就卡。后来换成 lv_canvas 自己画点,性能直接翻倍。
3.2.4 lv_hal —— 桥梁
lv_hal 是 LVGL 和硬件之间的桥梁。它定义了三个核心接口:
lv_hal_disp.c—— 显示驱动接口lv_hal_indev.c—— 输入设备接口(触摸、按键、编码器)lv_hal_tick.c—— 系统时钟接口
移植 LVGL 时,你主要就是实现这三个接口。我见过很多人移植失败,原因就一个:时钟没配好。LVGL 需要一个 1ms 的 tick 中断来驱动内部定时器。没有这个 tick,动画不动、长按不触发、双击没反应。
避坑指南: 我曾经在 STM32 上移植时,用了 SysTick 做 tick,但忘了在中断里调用 lv_tick_inc(1)。结果界面能显示,但按钮按下去没反应。查了两天才发现是 tick 没跑。记住:tick 是 LVGL 的心跳,断了就全瘫。
3.3 配置文件 lv_conf.h 详解
lv_conf.h 是 LVGL 的「总开关」。你把它放在项目根目录或编译器搜索路径里,LVGL 编译时就会自动加载它。我每次新建项目,第一件事就是复制一份 lv_conf_template.h 改名为 lv_conf.h,然后逐项配置。
配置文件分几个大块,我挑最重要的说:
3.3.1 基础开关
/* 颜色深度:16位RGB565最常见 */
#define LV_COLOR_DEPTH 16
/* 屏幕分辨率 */
#define LV_HOR_RES_MAX 320
#define LV_VER_RES_MAX 240
/* 内存池大小 */
#define LV_MEM_SIZE (32 * 1024U) /* 32KB */
颜色深度选 16 位还是 32 位,取决于你的屏幕硬件。16 位省内存,但颜色过渡会有条纹。32 位色彩细腻,但显存翻倍。我一般用 16 位,除非做图像显示才切到 32 位。
内存池大小是个经验值。32KB 能跑基础界面,但如果你用图表、动画、多语言字体,建议至少 64KB。我有个项目用了 128KB 才跑顺。
3.3.2 功能模块开关
LVGL 把功能模块做成宏开关,用不到的可以关掉,省 ROM 省 RAM。
| 宏 | 作用 | 建议 |
|---|---|---|
LV_USE_ANIMATION |
动画引擎 | 开,除非你界面全是静态的 |
LV_USE_FILESYSTEM |
文件系统支持 | 需要读图片/字体文件时开 |
LV_USE_GPU |
GPU 加速 | 有硬件 GPU 才开 |
LV_USE_FREETYPE |
FreeType 字体渲染 | 需要矢量字体时开,很占 ROM |
LV_USE_LOG |
调试日志 | 开发时开,发布时关 |
嗯,这里有个小技巧:LV_USE_LOG 在调试阶段一定要开。LVGL 的日志输出非常详细,能告诉你哪个对象创建失败、哪个事件没注册。我调试触摸坐标不准时,就是靠日志发现坐标换算公式写反了。
3.3.3 字体配置
字体是嵌入式 GUI 的痛点。LVGL 支持三种字体方案:内置字体、外部字体、FreeType。内置字体是位图,体积小但丑。外部字体用 lv_font_conv 工具转换,质量好但占空间。
/* 内置字体开关 */
#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_16 1
#define LV_FONT_MONTSERRAT_20 1
/* 自定义字体路径 */
#define LV_FONT_CUSTOM_DECLARE \
LV_FONT_DECLARE(my_font_16)
我建议只开你实际用到的字号。全开的话,光字体就能吃掉 100KB+ ROM。我一般只开 12、16、20 三个字号,够用。
3.3.4 性能调优参数
这几个参数直接影响界面流畅度:
LV_DISP_DEF_REFR_PERIOD—— 屏幕刷新周期,默认 30ms(约 33fps)。调小到 20ms 会更流畅,但 CPU 负载上升。LV_INDEV_DEF_READ_PERIOD—— 输入设备轮询周期,默认 5ms。触摸屏用 5ms 够了,按键可以放宽到 20ms。LV_DPI_DEF—— 屏幕 DPI,默认 130。这个值影响控件的大小计算,设错了按钮会变得特别大或特别小。
我的配置模板(供参考):
#define LV_COLOR_DEPTH 16
#define LV_HOR_RES_MAX 480
#define LV_VER_RES_MAX 320
#define LV_MEM_SIZE (64 * 1024U)
#define LV_DPI_DEF 130
#define LV_DISP_DEF_REFR_PERIOD 20
#define LV_INDEV_DEF_READ_PERIOD 5
#define LV_USE_ANIMATION 1
#define LV_USE_LOG 1
#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_16 1
3.4 源码结构全景图
下面这张图帮你把整个源码结构串起来。从硬件到用户界面,LVGL 分了四层。你平时写代码主要跟 Widgets 层打交道,但出了问题往往要往下追到 Core 或 HAL 层。
这张图里,从下往上看:硬件层提供显示和输入能力,HAL 层把它们包装成 LVGL 能理解的接口,核心引擎层负责对象管理和绘制,控件层提供现成的 UI 组件,最后你的应用代码调用这些控件搭建界面。配置文件 lv_conf.h 像是一个总闸,控制着每一层哪些功能开启、哪些关闭。
3.5 总结一下
源码结构这块,你不需要背下每个文件。记住几个关键目录的职责就够了:
- 写界面找
lv_widgets - 调性能找
lv_core和lv_conf.h - 做移植找
lv_hal - 画图出问题找
lv_draw
配置文件 lv_conf.h 是你和 LVGL 沟通的第一道门。花 10 分钟把每个宏看一遍,能省下后面几天的调试时间。我每次接手新项目,都会把配置文件从头到尾过一遍,确认每个开关的状态。这个习惯帮我避免了很多低级错误。