2. 源码获取与工程结构:从GitHub拉取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
小提示: 我建议你拉取时指定一个稳定版本号,而不是直接拉master分支。master分支可能包含实验性代码,我在项目里吃过这个亏,半夜调试到崩溃,结果发现是master分支的一个bug。

拉取完成后,你会看到这样的目录结构:

lvgl/
├── docs/          # 文档
├── examples/      # 示例代码
├── scripts/       # 构建脚本
├── src/           # 核心源码
├── tests/         # 测试用例
├── library.json   # 平台IO配置
├── lv_conf_template.h  # 配置文件模板
└── lvgl.h         # 主头文件

嗯,这里要注意,lv_conf_template.h 是你移植时必须要处理的文件。我见过有人直接把它改名就用了,结果一堆功能没打开,折腾半天。

2.2 源码目录结构深度解析

LVGL的源码组织得相当清晰,咱们重点看 src/ 目录。它分为四个核心部分:核心库、组件、移植层和示例。我画了一张图帮你理清关系:

LVGL 源码结构总览 核心库 (core) 组件 (widgets) 移植层 (porting) 示例 (examples) 核心库子模块 lv_obj:对象管理 lv_disp:显示驱动 lv_indev:输入设备 lv_refr:刷新机制 常用组件 lv_btn:按钮 lv_label:标签 lv_slider:滑块 lv_list:列表 移植层文件 lv_port_disp.c/h lv_port_indev.c/h lv_port_fs.c/h 示例代码 lv_example_btn lv_example_label lv_example_slider 关键依赖关系 移植层 → 核心库 → 组件 → 示例 lv_conf.h 控制所有模块的开关与配置

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)

创建步骤很简单:

  1. 复制 lv_conf_template.h 到工程根目录,重命名为 lv_conf.h
  2. 在 lv_conf.h 中,将 #if 0 改为 #if 1 来启用配置
  3. 根据你的硬件调整配置项(比如颜色深度、缓冲区大小)
  4. 创建 lv_port/ 目录,从 src/porting/ 复制模板文件并修改
  5. 编写 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就像盖房子,地基打好了,上面怎么盖都稳当。