LVGL的移植思路
LVGL 移植与使用完全指南
从源码获取、目录结构、显示/输入设备移植、任务集成到常用控件与性能调优,系统化掌握 LVGL 在嵌入式平台上的落地方法。
- 面向嵌入式开发者
- 基于 LVGL v8 / v9
- 支持裸机 / RTOS / Linux
目录
1. LVGL 是什么
LVGL(Light and Versatile Graphics Library) 是一个用 C 语言编写的轻量级嵌入式图形库,可以在资源极其有限的 MCU 上提供丰富的 UI 能力。
- 跨平台: 支持裸机、FreeRTOS、RT-Thread、ThreadX、Linux 等,无 OS 依赖。
- 资源占用低: 最小
~64KBFlash、~8KBRAM 即可运行。 - 控件丰富: 30+ 内置控件(按钮、列表、图表、键盘、动画等)。
- 图形能力: 支持抗锯齿、透明度、渐变、动画、图片解码(BMP/PNG/JPG/SVG)。
- 国际化: UTF-8、阿拉伯/希伯来语渲染、CJK 字库支持。
典型应用场景:智能家居面板、工业 HMI、医疗设备、穿戴设备、汽车仪表、消费电子等。
2. 源码获取与目录结构
2.1 获取源码
推荐从 GitHub 拉取稳定版本:
# 拉取 v8.3 最新稳定版
git clone -b release/v8.3 https://github.com/lvgl/lvgl.git
# 或下载压缩包
wget https://github.com/lvgl/lvgl/archive/refs/tags/v8.3.10.zip
LVGL v8 与 v9 在 API 上有少量差异,本文示例基于 v8.x 编写(更通用),v9 差异点会单独标注。
2.2 关键目录
| 目录 | 作用 |
|---|---|
src/ |
核心源码,所有 .c 文件 |
src/core |
对象系统、事件、布局、刷新机制 |
src/widgets |
按钮、标签、滑块等所有控件 |
src/draw |
渲染后端(SW / GPU / DMA2D) |
src/font |
内置字体文件(lv_font_montserrat_* 等) |
src/extra |
主题、布局、文件系统、图片解码等扩展 |
examples/ |
官方示例代码(必看) |
demos/ |
完整功能演示(包含多页面、动画) |
lv_conf_template.h |
配置文件模板,需重命名为 lv_conf.h |
lvgl.h |
主头文件(include path 需包含 src) |
2.3 配置 lv_conf.h
将 lv_conf_template.h 复制为 lv_conf.h,核心宏:
// 颜色深度,根据屏幕与 RAM 选择
#define LV_COLOR_DEPTH 16 // 16 = RGB565, 32 = ARGB8888
// 水平/垂直分辨率,仅影响缓冲区分配,运行时可改
#define LV_HOR_RES_MAX 480
#define LV_VER_RES_MAX 320
// 缓冲区大小(像素),推荐屏幕宽度的 1/10
#define LV_MEM_SIZE (32 * 1024U)
// 使能 FreeRTOS 支持(若使用 RTOS)
#define LV_USE_OS LV_OS_FREERTOS
// 使能/关闭不需要的控件减小体积
#define LV_USE_BTN 1
#define LV_USE_LABEL 1
#define LV_USE_CHART 0 // 不需要就关掉
提示: 关闭未使用的控件、字体、动画、主题可以显著减小 bin 体积。生产版本建议在
lv_conf.h中把调试宏全部关闭。
3. 移植三要素
LVGL 与硬件解耦,移植时只需向它"喂"三样东西:
- 显示设备:实现
flush_cb,把 LVGL 渲染的像素刷到 LCD。 - 输入设备:实现
read_cb,周期性读取触摸/按键上报给 LVGL。 - 心跳时钟:周期调用
lv_tick_inc(ms)提供时间基准。
4. 显示设备移植
4.1 注册一个显示设备
// 全局缓冲区(双缓冲可提高并行效率)
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf1[LV_HOR_RES_MAX * 40];
static lv_color_t buf2[LV_HOR_RES_MAX * 40]; // 可选第二缓冲
// flush 回调:LCD 刷屏函数
static void my_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p)
{
int32_t x1 = area->x1, y1 = area->y1;
int32_t x2 = area->x2, y2 = area->y2;
lcd_set_window(x1, y1, x2, y2); // 设置 LCD 写入窗口
lcd_write_pixels((uint16_t *)color_p, (x2 - x1 + 1) * (y2 - y1 + 1));
lv_disp_flush_ready(drv); // 通知 LVGL 刷新完成
}
void lv_port_disp_init(void)
{
lv_disp_drv_t disp_drv;
lv_disp_draw_buf_init(&draw_buf, buf1, buf2, LV_HOR_RES_MAX * 40);
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = 480;
disp_drv.ver_res = 320;
disp_drv.flush_cb = my_flush_cb;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
}
关键点:
lv_disp_flush_ready()必须在 LCD 真正刷完或 DMA 发起后才调用,否则会出现"重影"或画面错乱。若使用 DMA 异步刷屏,应在 DMA 完成中断里调用。
4.2 flush 加速策略
| 方式 | 做法 | 适用场景 |
|---|---|---|
| SPI 轮询 | flush 中直接 SPI 写,完成后调用 flush_ready |
小屏、刷新率要求低 |
| SPI DMA | flush 启动 DMA,在 DMA 完成中断调用 flush_ready |
中等尺寸 SPI 屏(主流方案) |
| RGB 并口 | LVGL 写入图层 buffer,LTDC 硬件自动扫描 | STM32F4/H7 + 800x480 |
| GPU 加速 | 实现 gpu_blend_cb / gpu_fill_cb |
Chromium GPU、ESP32-S3 DMA2D |
5. 输入设备移植
5.1 触摸屏(以电阻/电容屏为例)
static void touch_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
static int16_t last_x, last_y;
bool pressed = tp_get_state(&last_x, &last_y); // 读 IC 寄存器
data->point.x = last_x;
data->point.y = last_y;
data->state = pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
}
void lv_port_indev_init(void)
{
lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touch_read_cb;
lv_indev_drv_register(&indev_drv);
}
5.2 多输入设备(触摸 + 编码器 + 按键)
LVGL 支持同时注册多个输入设备,只需在 indev_drv.type 区分:
| type | 典型硬件 | LVGL 行为 |
|---|---|---|
LV_INDEV_TYPE_POINTER |
触摸屏、鼠标 | 坐标定位 |
LV_INDEV_TYPE_KEYPAD |
实体按键矩阵、遥控器 | 焦点切换,需配合 group |
LV_INDEV_TYPE_ENCODER |
旋转编码器(带按键) | 上下/确认,需配合 group |
LV_INDEV_TYPE_BUTTON |
独立 GPIO 按键 | 屏幕外按钮,可触发指定区域点击 |
5.3 焦点组(无触摸场景必须)
// 创建焦点组并把控件加进来
lv_group_t *g = lv_group_create();
lv_group_add_obj(g, btn1);
lv_group_add_obj(g, btn2);
lv_indev_set_group(my_indev_keypad, g); // 绑定到 keypad/encoder 设备
6. 心跳与任务调度
LVGL 是单线程协作式的,它本身不会创建任何任务或定时器,需要主循环周期性"喂"它时间。
6.1 裸机主循环
int main(void)
{
hal_init(); // 硬件初始化
lv_init();
lv_port_disp_init();
lv_port_indev_init();
lv_user_gui_create(); // 用户 UI
while (1) {
lv_timer_handler(); // 处理 LVGL 任务(约 5ms 一次)
__WFI(); // 空闲休眠
}
}
6.2 SysTick 提供心跳
// 在 SysTick 中断中调用(每 1ms)
void SysTick_Handler(void)
{
lv_tick_inc(1);
HAL_IncTick();
}
6.3 RTOS 下独立任务
void lvgl_task(void *p)
{
while (1) {
lv_timer_handler();
vTaskDelay(pdMS_TO_TICKS(5));
}
}
// 单独定时器提供 tick
void tick_task(void *p)
{
while (1) {
lv_tick_inc(5);
vTaskDelay(pdMS_TO_TICKS(5));
}
}
7. 第一个 UI 程序
void lv_user_gui_create(void)
{
lv_obj_t *scr = lv_scr_act();
// 背景
lv_obj_set_style_bg_color(scr, lv_color_hex(0x003a57), 0);
// 标签
lv_obj_t *label = lv_label_create(scr);
lv_label_set_text(label, "Hello, LVGL!");
lv_obj_set_style_text_color(label, lv_color_white(), 0);
lv_obj_align(label, LV_ALIGN_CENTER, 0, -30);
// 按钮
lv_obj_t *btn = lv_btn_create(scr);
lv_obj_set_size(btn, 120, 50);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 40);
lv_obj_t *btn_lbl = lv_label_create(btn);
lv_label_set_text(btn_lbl, "Click me");
lv_obj_center(btn_lbl);
// 事件回调
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);
}
static void btn_event_cb(lv_event_t *e)
{
lv_obj_t *btn = lv_event_get_target(e);
lv_obj_t *label = lv_obj_get_child(btn, 0);
lv_label_set_text(label, "Clicked!");
}
编译下载,即可在屏幕上看到带按钮的界面,点击按钮文字会变化,这就是一个完整的 LVGL 应用。
8. 常用控件速查
| 控件 | 创建函数 | 典型用途 |
|---|---|---|
| Label | lv_label_create() |
文字显示、字体颜色 |
| Button | lv_btn_create() |
交互按钮 |
| Arc | lv_arc_create() |
进度圆环、旋钮 |
| Slider | lv_slider_create() |
音量、亮度滑条 |
| Switch | lv_switch_create() |
开关切换 |
| Dropdown | lv_dropdown_create() |
下拉选择 |
| List | lv_list_create() |
菜单列表 |
| Table | lv_table_create() |
表格 |
| Chart | lv_chart_create() |
折线/柱状图 |
| Keyboard | lv_keyboard_create() |
软键盘 |
| Canvas | lv_canvas_create() |
自定义绘图(类似 framebuffer) |
| Image | lv_img_create() |
显示 PNG/JPG/BMP/SVG |
| Textarea | lv_textarea_create() |
多行输入框 |
| Tabview | lv_tabview_create() |
多页签容器 |
| Tileview | lv_tileview_create() |
可滑动的卡片视图 |
9. 样式与主题
LVGL v8 引入样式表(样式状态机)机制,比 v7 的纯函数链式 API 更易复用。
9.1 基础样式
static lv_style_t style_btn;
lv_style_init(&style_btn);
lv_style_set_bg_color(&style_btn, lv_color_hex(0x0e7c66));
lv_style_set_bg_opa(&style_btn, LV_OPA_COVER);
lv_style_set_radius(&style_btn, 10);
lv_style_set_pad_all(&style_btn, 12);
lv_obj_add_style(btn, &style_btn, 0); // 默认状态
lv_obj_add_style(btn, &style_btn_pressed, LV_STATE_PRESSED);
9.2 切换主题
lv_theme_t *th = lv_theme_default_init(
NULL, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED),
true, LV_FONT_DEFAULT);
lv_disp_set_theme(NULL, th);
LVGL 还内置了 lv_theme_material、lv_theme_mono、lv_theme_zen 等可直接使用。
10. 性能优化与踩坑
10.1 渲染性能
- 缓冲区越大越好? 缓冲区越大,单次 flush 数据量越大,SPI 屏反而会拖慢。经验值取屏幕宽度的
1/10 ~ 1/4行。 - 双缓冲(DMA):一个缓冲给 LVGL 渲染,另一个给 DMA 传输,充分利用总线带宽。
- 裁剪:LVGL 自动只刷新变化区域,避免调用
lv_obj_invalidate()全屏重绘。 - 关闭动画:
lv_anim_delete_all()或减少动画时长。 - 颜色深度:
LV_COLOR_DEPTH 16比32快且省 RAM,但渐变过渡会变差。
10.2 内存优化
| 配置项 | 作用 | 建议 |
|---|---|---|
LV_MEM_SIZE |
动态内存池大小 | 先用默认值,实测峰值后再调小 |
LV_USE_BIG_ENDIAN |
大小端配置 | 按 MCU 选错会乱码 |
| 字体 | 每多一种字号都增加 Flash | 生成工具 lv_font_conv 按需生成 |
| 图片 | 用 lv_img_dsc_t 内置数组 |
在线转换 img2lvgl 工具 |
10.3 常见踩坑
踩坑 1: 触摸坐标上下颠倒 — 多数是 LCD 旋转与 TP 扫描方向不一致,通过
lv_disp_set_rotation()或 TP 驱动里加翻转系数解决。
踩坑 2: 控件不响应点击 — 检查是否启用了
LV_USE_GROUP与indev.set_group(无触摸场景),或触摸坐标是否被裁剪。
踩坑 3: 屏幕刷新"卡死" — 大概率
lv_disp_flush_ready()在中断外被调用太晚,或 SPI 时序不对。
踩坑 4: 多任务下 UI 错乱 — LVGL 不是线程安全的,所有 UI 操作必须放在同一任务,或使用
lvgl_mutex(v8.3+ 提供lv_lock/ lv_unlock)。
11. PC 模拟器调试
强烈推荐先用 PC 模拟器把 UI 调好,再烧到板子。官方仓库提供 SDL2 模拟器:
# Linux
sudo apt install -y build-essential cmake libsdl2-dev
git clone https://github.com/lvgl/lv_port_pc_eclipse.git
cd lv_port_pc_eclipse
cmake -B build && cmake --build build -j
./build/main
Windows 下可用 lv_port_win_codeblocks(Code::Blocks 工程)或 VSCode + CMake,直接双击运行就能看到窗口化的 LVGL 界面,断点、打印均可用。
调试技巧:
- 在
lv_conf.h中打开LV_USE_LOG并设置LV_LOG_LEVEL = LV_LOG_LEVEL_TRACE。 - 使用
lv_obj_dump_tree()打印对象树,排查"为什么控件没显示"。 - 使用
lv_mem_monitor()查看内存占用与泄漏。
12. 常见问题 FAQ
| 问题 | 可能原因与解法 |
|---|---|
| 屏幕全白 / 全黑 | 检查 LCD 复位与背光;flush_cb 是否被调用;SPI 时序/线序 |
| 画面撕裂 | 启用 DMA 双缓冲,确认 flush_ready 在 DMA 完成中断调用 |
| 触摸不灵敏 | 触摸 IC 初始化时序;触摸校准参数;坐标方向 |
| 中文显示成方块 | 未启用 CJK 字库,使用 lv_font_conv 生成包含中文的字体 |
| 占用 Flash 太大 | 关闭未用控件/主题/动画,启用 LTO,字体子集化 |
| 多任务崩溃 | UI 操作未在同一个任务;未使用 LVGL 互斥锁 |
| 动画卡顿 | 缓冲区过小 / 刷屏速度慢;降低动画时长或减少同时动画数 |
浙公网安备 33010602011771号