LVGL嵌入式GUI零基础入门:从上手Demo到MVP架构工程实战
导读:LVGL是嵌入式领域最流行的开源轻量GUI库,最低64KB Flash、16KB RAM就能跑起来,C语言开发,支持按钮、图表、动画等丰富控件。本文从零教你跑通第一个Demo,再过渡到MVP分层架构,最后搞定图片资源的工程化处理,完整走通全流程。
一、LVGL原理简析
LVGL(Light and Versatile Graphics Library)是一个用纯C写的开源嵌入式图形库,MIT协议,完全免费。它的核心设计思路是"低内存、高可移植、零依赖"——不绑定任何操作系统、芯片或显示屏,裸机、FreeRTOS、RT-Thread上都能跑。
核心抽象只有四层:
- 对象(lv_obj_t):按钮、文字、图片、滑块等所有UI元素,本质上都是对象。每个对象有坐标、尺寸、样式、父子关系、事件回调队列,形成一棵对象树
- 显示设备(lv_disp_t):通过
lv_disp_drv_t注册的逻辑输出通道,支持多屏、分屏、离屏渲染 - 输入设备(lv_indev_t):统一抽象触摸屏、按键、编码器、鼠标等输入,底层差异被封装为标准化上报接口
- 主循环(lv_timer_handler):LVGL的生命线,按毫秒级精度调度定时任务、动画帧更新、输入去抖、屏幕刷新,不是简单的
while(1)轮询
二、实操步骤:从零跑通第一个Demo
2.1 环境准备
- 硬件:任意支持LVGL的开发板(STM32、ESP32均可),带一块TFT屏幕
- 软件:移植好
lv_port_disp.c(显示驱动)和lv_port_indev.c(触摸驱动) - 参考:LVGL官方移植文档 docs.lvgl.io
2.2 核心Demo代码
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
// 按钮点击回调
static void btn_event_cb(lv_event_t *e)
{
lv_obj_t *label = lv_event_get_user_data(e);
lv_label_set_text(label, "已点击!");
}
int main(void)
{
// 1. 初始化LVGL核心
lv_init();
// 2. 初始化屏幕和触摸驱动
lv_port_disp_init();
lv_port_indev_init();
// 3. 创建居中文字
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "Hello LVGL!");
lv_obj_align(label, LV_ALIGN_CENTER, 0, -30);
// 4. 创建按钮
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 120, 50);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 30);
// 5. 按钮文字
lv_obj_t *btn_label = lv_label_create(btn);
lv_label_set_text(btn_label, "Click Me");
// 6. 绑定点击事件
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, label);
// 7. LVGL主循环
while (1) {
lv_timer_handler();
usleep(5000);
}
return 0;
}
2.3 运行效果
屏幕中央显示"Hello LVGL!"文字,下方有一个"Click Me"按钮。点击按钮后,上方文字变为"已点击!"。这就是LVGL最基础的界面交互逻辑——创建对象、绑定事件、刷新显示。
三、原生开发 vs MVP架构对比
| 对比项 | 原生LVGL开发 | MVP架构开发 |
|---|---|---|
| 代码结构 | UI和业务代码混在一个文件,改一处牵全身 | View/Model/Presenter三层分离,各司其职 |
| 可测试性 | 无法单独测试UI或业务逻辑 | 三层独立,可分别编写单元测试 |
| 可复用性 | 耦合度高,换硬件或改功能成本极大 | 界面和业务完全分离,跨平台复用方便 |
| 多人协作 | 分工不清,极易代码冲突 | 目录清晰,前端/后端/调度独立开发 |
| 维护成本 | 项目越大越难维护 | 结构规范,新人上手快,bug定位快 |
| 适用规模 | 简单Demo、单一功能小项目 | 中大型项目、需要长期迭代的产品 |
四、核心流程图
4.1 LVGL对象模型层级
4.2 MVP架构执行流程
五、MVP架构在LVGL中的落地
5.1 三层职责一句话
| 层级 | 职责 | 不做什么 |
|---|---|---|
| View | 画控件、排布局、调样式、播动画 | 不碰任何业务逻辑,不调用传感器/网络 |
| Model | 传感器读取、网络通信、数据存储、协议解析 | 不依赖LVGL,不含任何UI代码 |
| Presenter | 接收View事件 → 调Model处理 → 通知View刷新 | 不直接操作硬件,不直接画控件 |
5.2 项目目录结构
project/
├── view/ # 界面布局代码(lv_obj_t声明 + lv_event_cb_t实现)
│ ├── main_page.c
│ └── setting_page.c
├── model/ # 业务功能代码(HAL驱动 + 协议解析 + 存储)
│ ├── sensor.c
│ └── storage.c
├── presenter/ # 中间调度代码(协调逻辑 + 状态映射)
│ ├── main_presenter.c
│ └── setting_presenter.c
└── lvgl/ # LVGL图形库
5.3 实战案例:X-TRACK开源项目
X-TRACK(GitHub: FASTSHIFT/X-TRACK)是一个基于LVGL + MVP架构的开源GPS自行车码表项目,4.5k+ stars。它的架构设计是MVP在嵌入式领域的经典参考:
- 4层架构:硬件层 → HAL抽象层 → 应用逻辑层 → LVGL UI层
- DataCenter发布订阅机制:组件间通过消息总线解耦,PingPongBuffer实现无锁核间通信
- HAL接口:同一套应用代码,不修改即可在MCU硬件和PC模拟器上编译运行
- 单内存池:所有动态内存分配统一走LVGL内存池,避免碎片化,方便调试
六、帧动画实现 + 图片资源工程化
6.1 逐帧动画原理
把动画拆成多张连续静态图片,用定时器按固定间隔切换 lv_img_set_src() 的图片源,就形成流畅动画。LVGL原生支持两种动画方式:
- 属性动画(lv_anim_t):自动对对象属性(坐标、透明度、大小)做补间插值
- 帧动画(lv_img_set_src + 定时器):手动切换图片数组,适合表情、加载动画等
6.2 图片转C数组:工程化方案
嵌入式开发中,PNG/JPG图片无法直接使用,必须转成C数组。LVGL官方提供了两个工具:
方案一:LVGL在线转换器
地址:https://lvgl.io/tools/imageconverter
上传图片 → 选择颜色格式(推荐RGB565节省空间)→ 点击Convert → 下载 .c 文件。在代码中声明即可使用:
LV_IMG_DECLARE(my_icon);
lv_obj_t *img = lv_img_create(lv_scr_act());
lv_img_set_src(img, &my_icon);
方案二:Python脚本批量转换(推荐工程使用)
安装 lvgl-imgconverter 包,写脚本遍历资源目录,一键批量转换:
import os
import subprocess
image_dir = "./assets/images/"
output_dir = "./src/images/"
for filename in os.listdir(image_dir):
if filename.endswith(('.png', '.jpg')):
name = os.path.splitext(filename)[0]
subprocess.run([
"lv_img_converter",
os.path.join(image_dir, filename),
"-o", os.path.join(output_dir, f"{name}.c"),
"-f", "RGB565"
])
脚本优势:
- 自动生成合法C变量名,不会语法报错
- 标准化输出LVGL兼容格式,直接
#include即可 - 支持增量更新,图片没变就不重复生成,省编译时间
- 批量处理比手动写数组快几十倍,零人为失误
6.3 帧动画播放代码
// 帧数组
static const lv_img_dsc_t *anim_frames[] = {
&img_frame_01, &img_frame_02, &img_frame_03,
&img_frame_04, &img_frame_05, &img_frame_06,
};
#define FRAME_COUNT 6
// 定时器回调:切换帧
static void anim_timer_cb(lv_timer_t *timer)
{
lv_obj_t *img = lv_timer_get_user_data(timer);
static int frame_idx = 0;
lv_img_set_src(img, anim_frames[frame_idx]);
frame_idx = (frame_idx + 1) % FRAME_COUNT;
}
// 启动动画
lv_timer_t *anim = lv_timer_create(anim_timer_cb, 100, img_obj);
七、常见问题解决
问题一:LVGL界面卡顿、不流畅
检查 lv_timer_handler() 的调用间隔,建议5ms一次。如果主循环里有耗时操作(如传感器轮询),把耗时操作移到单独的任务或中断中,别阻塞主循环。另外,确认 lv_conf.h 中 LV_MEM_SIZE 分配足够(建议至少32KB)。
问题二:图片显示花屏或颜色不对
LVGL的颜色格式(RGB565 vs ARGB8888)和屏幕驱动的颜色格式必须一致。检查 lv_conf.h 中的 LV_COLOR_DEPTH 配置,以及图片转换时选择的颜色格式。如果屏幕显示颜色反了,检查 lv_port_disp.c 中 flush_cb 函数的字节序。
问题三:MVP架构中View和Presenter如何通信
View通过 lv_event_cb_t 回调通知Presenter,Presenter通过函数指针或消息队列通知View刷新。不要在View回调中直接调Model,也不要在Model中直接调LVGL API——所有跨层调用都经过Presenter。
八、总结
LVGL用64KB Flash、16KB RAM就能跑出专业级嵌入式GUI,是目前嵌入式图形开发的首选方案。
- Demo阶段:直接写就行,代码短、上手快,适合验证原型
- 项目阶段:上MVP架构,View/Model/Presenter三层分离,改UI不动业务、改业务不动UI,维护成本跳水
- 资源处理:用官方在线转换器或Python脚本批量转图片,告别手写数组的噩梦
这套LVGL + MVP + 脚本工具链的组合,从简单Demo到带动画的产品级项目,都能应对。