2. LVGL核心概念
好,咱们正式开始动手。这一章我会带你走一遍最基础但也是最关键的一步——把LVGL的源码拿到手,并且搞清楚它里面到底装了些什么东西。很多初学者一上来就急着写代码,结果连源码目录都搞不清楚,移植的时候各种报错。我刚开始做LVGL移植那会儿也踩过这个坑,所以这一章咱们把地基打牢。
2.1 从GitHub拉取LVGL源码
LVGL的官方仓库在GitHub上,地址是:https://github.com/lvgl/lvgl。我个人习惯用git命令行来拉取,这样能保证拿到的是最新的稳定版。
打开终端,找个你喜欢的目录,执行:
git clone https://github.com/lvgl/lvgl.git
cd lvgl
git checkout v8.3.11
拉取完成后,你会看到这样的目录结构:
lvgl/
├── docs/ # 文档
├── examples/ # 示例代码
├── scripts/ # 构建脚本
├── src/ # 核心源码
├── tests/ # 测试用例
├── library.json # 平台IO配置
├── lv_conf_template.h # 配置文件模板
└── lvgl.h # 主头文件
嗯,这里要注意,lv_conf_template.h 是你移植时必须要处理的文件。我见过有人直接把它改名就用了,结果一堆功能没打开,折腾半天。
2.2 源码目录结构深度解析
LVGL的源码组织得相当清晰,咱们重点看 src/ 目录。它分为四个核心部分:核心库、组件、移植层和示例。我画了一张图帮你理清关系:
2.3 核心库(core)
核心库是LVGL的心脏。它包含了对象系统、显示驱动、输入设备管理、动画引擎、样式系统等基础模块。说白了,没有核心库,LVGL就是个空壳。
核心库的主要文件都在 src/core/ 下:
| 文件/目录 | 作用 |
|---|---|
| lv_obj.c/h | 对象管理,所有UI组件的基类 |
| lv_disp.c/h | 显示驱动接口,管理多个显示设备 |
| lv_indev.c/h | 输入设备接口,支持触摸、按键、编码器 |
| lv_refr.c/h | 刷新机制,决定何时重绘屏幕 |
| lv_anim.c/h | 动画引擎,实现平滑过渡效果 |
| lv_style.c/h | 样式系统,管理UI的外观属性 |
lv_disp 和 lv_indev。我当初移植到STM32时,就是在这两个文件上花的时间最多。尤其是刷新回调函数,写不好屏幕就闪个不停。
2.4 组件(widgets)
组件就是咱们在屏幕上看到的那些UI元素——按钮、标签、滑块、进度条等等。LVGL提供了丰富的内置组件,都在 src/widgets/ 目录下。
常用的组件有:
- lv_btn:按钮,支持点击、长按、状态切换
- lv_label:文本标签,支持多行、滚动、富文本
- lv_slider:滑块,用于调节数值
- lv_list:列表,用于菜单或选项
- lv_dropdown:下拉菜单
- lv_chart:图表,支持折线图、柱状图
每个组件都继承自 lv_obj_t,所以它们都拥有对象的基本属性——位置、大小、样式、事件等。你想想看,这就像搭积木一样,基础模块有了,往上堆就行了。
2.5 移植层(porting)
移植层是连接LVGL和你硬件平台的桥梁。它位于 src/porting/ 目录下,包含三个核心文件:
| 文件 | 功能 |
|---|---|
| lv_port_disp.c/h | 显示驱动移植,告诉LVGL怎么往屏幕上画像素 |
| lv_port_indev.c/h | 输入设备移植,告诉LVGL怎么读取触摸或按键 |
| lv_port_fs.c/h | 文件系统移植,用于读取字体、图片等外部资源 |
lv_port_disp 时,没有正确实现 flush_cb 回调,结果屏幕显示全是花屏。后来发现是缓冲区大小没对齐,折腾了两天才搞定。所以移植时一定要仔细看注释,每个回调函数的参数含义都要搞清楚。
2.6 示例代码(examples)
示例代码在 examples/ 目录下,按组件分类存放。比如 examples/widgets/btn/ 里就有按钮的各种用法示例。这些示例是学习LVGL的最佳入门材料,我建议你移植成功后,先跑一遍示例,验证移植是否成功。
2.7 创建工程模板
好了,源码结构搞清楚了,咱们来创建一个工程模板。我个人习惯这样组织工程目录:
my_lvgl_project/
├── lvgl/ # LVGL源码(从GitHub拉取)
│ ├── src/
│ ├── examples/
│ └── lv_conf_template.h
├── lv_port/ # 移植层代码(自己写的)
│ ├── lv_port_disp.c
│ ├── lv_port_disp.h
│ ├── lv_port_indev.c
│ └── lv_port_indev.h
├── lv_conf.h # 配置文件(从模板复制并修改)
├── main.c # 主程序入口
├── main.h # 主程序头文件
└── Makefile # 构建脚本(或CMakeLists.txt)
创建步骤很简单:
- 复制
lv_conf_template.h到工程根目录,重命名为lv_conf.h - 在
lv_conf.h中,将#if 0改为#if 1来启用配置 - 根据你的硬件调整配置项(比如颜色深度、缓冲区大小)
- 创建
lv_port/目录,从src/porting/复制模板文件并修改 - 编写
main.c,初始化LVGL并创建第一个界面
lv_conf.h 里把 LV_MEM_SIZE 先设大一点(比如64KB),等调试稳定后再根据实际使用情况优化。这样能避免早期因为内存不足导致的奇怪问题。
最后,给你一个最简单的 main.c 骨架:
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
int main(void)
{
/* 初始化硬件 */
lv_init();
/* 初始化显示驱动 */
lv_port_disp_init();
/* 初始化输入设备 */
lv_port_indev_init();
/* 创建第一个界面 */
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 100, 50);
lv_obj_center(btn);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Hello LVGL");
lv_obj_center(label);
/* 主循环 */
while(1) {
lv_timer_handler();
/* 你的延时或任务调度 */
}
return 0;
}
这个模板虽然简单,但五脏俱全。你把它跑通了,后续的移植工作就有了坚实的基础。记住,移植LVGL就像盖房子,地基打好了,上面怎么盖都稳当。