简介:这份《LVGL开发指南》中文V1.3版面向嵌入式开发者与STM32学习者,聚焦在资源有限的微控制器上构建图形用户界面这一核心问题。内容以正点原子STM32开发板为实践平台,从初识LVGL、移植要求与源码获取讲起,依次覆盖无操作系统移植、带FreeRTOS等操作系统移植、PC模拟器使用及移植相关知识,并延伸到UI元素创建、动画交互与性能优化等应用开发环节,适合具备一定单片机基础、希望系统掌握LVGL的中级读者按章节循序渐进学习。资源包共1个PDF文件,大小约9.54MB,单文件即完整承载教程正文与例程说明,便于离线阅读和随查随用。目前已有764人学习下载,可作为嵌入式GUI入门与移植实践的参考手册。
1. 从一块白屏说起:LVGL V1.3 移植到底卡在哪
上周帮一个做工业面板的朋友看板子,STM32F407 加 3.5 寸屏,代码烧进去屏幕全白,串口一行日志都没有。他以为屏坏了,换了三块屏还是白。最后发现是lv_conf.h里颜色深度填了 32,而他的屏驱动按 RGB565 走,LVGL 画出来的缓冲区格式和 DMA 送出去的数据对不上,画面自然是一片白。这种坑在嵌入式 GUI 移植里太常见了,LVGL 本身不难,难的是版本、配置、驱动接口这三样东西必须严丝合缝地对上。
这份《LVGL 开发指南》中文 V1.3 是围绕 STM32 开发板写的移植教程,2023 年 1 月更新过例程功能,内容从无操作系统移植、FreeRTOS 移植一直讲到 PC 模拟器和部件实验。适合手里有 STM32 板子、想把 LVGL 跑起来做界面的开发者,也适合先在 PC 模拟器上把界面逻辑调通再往板子上搬的人。下面按我实际装环境的顺序,把版本选择、工程搭建、配置修改、验证排错一层层拆开。
2. 装前把环境列清:LVGL V1.3 的版本与工程底座
2.1 先定 LVGL 版本,别拿最新版套旧教程
这份指南的例程是按 LVGL 8.x 的 API 写的,V1.3 修订时间在 2023 年初,那时候 8.x 还是主流。如果你直接从仓库拉最新代码,很可能碰到lv_disp_drv_register参数变化、lv_img_dsc_t结构体字段调整这类问题,教程里的代码贴进去编译不过。我的做法是先把版本锁死,再动工程。
常见做法是从 LVGL 的 release 页面找 8.3.x 的稳定包,解压后只取lvgl根目录、lvgl/src、lvgl/examples这几块。不要整个仓库拖进 Keil 工程,里面有一堆 CI 脚本和文档,编译时会拖慢索引。目录结构建议这样放:
Project/ ├── Core/ ├── Drivers/ ├── LVGL/ │ ├── lvgl/ # 库源码,含 src、examples │ ├── lv_conf.h # 从 lv_conf_template.h 复制改名 │ └── lvgl_port/ # 自己写的显示和输入驱动 └── MDK-ARM/lv_conf.h不要放在lvgl目录里面,放在外面一层,这样升级库版本时配置文件不会被覆盖。这一点教程里提过,但很多人图省事直接改模板文件,后面换版本就哭了。
2.2 硬件与工具链的最低要求
指南面向的是 STM32 全系开发板,但实际能不能跑起来,看三个硬指标:Flash 至少 128KB、RAM 至少 64KB、屏幕有可用的刷屏接口(FSMC、SPI 或并口都行)。LVGL 8.x 开基础部件加默认字体,代码体积大概 60 到 100KB,帧缓冲区按 1/10 屏幕大小算,320×240 的屏用 RGB565 需要 320×24×2 约 15KB,双缓冲翻倍。RAM 低于 64KB 的型号建议先砍部件、关动画。
工具链这边,Keil MDK 5 加 STM32CubeMX 生成底层初始化是最省事的组合。CubeMX 负责把 FSMC 或 SPI、DMA、时钟树配好,生成工程后再往里加 LVGL 源码。注意 CubeMX 生成的main.c里SystemClock_Config别被后续手改覆盖,我见过有人加 LVGL 时把时钟配置整段替换掉,结果屏刷得极慢,还以为是 LVGL 性能差。
| 项目 | 建议值 | 说明 |
|---|---|---|
| LVGL 版本 | 8.3.x | 与 V1.3 教程例程匹配 |
| 颜色深度 | 16(RGB565) | 与多数屏驱动一致 |
| 帧缓冲 | 屏幕的 1/10 | 内存紧张时再降 |
| 编译器 | Keil MDK 5 / GCC | 教程以 Keil 为主 |
| RTOS | FreeRTOS 可选 | 带系统移植章节单独讲 |
2.3 工程文件怎么加才不乱
把lvgl/src下所有.c文件加进工程时,Keil 的 Add Group 一个个点太慢。我一般按目录建 Group:lvgl/core、lvgl/draw、lvgl/font、lvgl/widgets,然后把对应目录的源文件批量加进去。lvgl/examples只在跑官方例程时加,正式项目里删掉,能省不少编译时间。
头文件路径要加三条:LVGL/lvgl、LVGL(为了找到lv_conf.h)、LVGL/lvgl/src。少加一条就是满屏的cannot open source input file "lv_conf.h"。加完之后先别急着编译,把lv_conf.h里LV_CONF_INCLUDE_SIMPLE这个宏打开,它让 LVGL 用简单路径找配置文件,省得在编译选项里绕。
3. 无操作系统移植:从加文件到点亮第一屏
3.1 lv_conf.h 里必须先改的五个参数
lv_conf.h是从lv_conf_template.h复制来的,开头有个#if 0要改成#if 1,否则整个文件不生效,这是第一个坑。改完之后重点看这几项:
#define LV_COLOR_DEPTH 16 // 与屏驱动位深一致,RGB565 填 16 #define LV_MEM_CUSTOM 0 // 用 LVGL 自带内存池 #define LV_MEM_SIZE (32U * 1024U) // 内存池大小,按 RAM 余量调 #define LV_DISP_DEF_REFR_PERIOD 30 // 刷新周期 ms #define LV_USE_LOG 1 // 调试期打开日志LV_COLOR_DEPTH填错就是开头那个白屏案例,屏是 16 位就填 16,别看着电脑显示器 32 位就跟着填。LV_MEM_SIZE给 32KB 是保守值,界面部件多、图片多的时候要往上加,加完记得看链接后的 RAM 占用,别把栈挤爆。LV_USE_LOG打开后配合LV_LOG_LEVEL设成LV_LOG_LEVEL_WARN,移植阶段能看到初始化失败的提示,比盲猜强。
3.2 显示驱动:flush_cb 是整条链路的咽喉
LVGL 不直接操作屏幕,它把画好的缓冲区交给你写的flush_cb回调,由你把数据送到屏上。这个回调写不对,前面配置全白搭。典型实现是这样:
static void disp_flush(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) { uint32_t w = area->x2 - area->x1 + 1; uint32_t h = area->y2 - area->y1 + 1; /* 按屏的坐标窗口设置,再把 color_p 里的 w*h 个像素写进去 */ LCD_SetWindow(area->x1, area->y1, area->x2, area->y2); LCD_WriteData((uint16_t *)color_p, w * h); lv_disp_flush_ready(drv); // 必须调用,否则 LVGL 一直等 }lv_disp_flush_ready这行是血泪经验,漏掉它界面只会刷第一帧然后卡死,因为 LVGL 以为上一次刷新还没完成。如果用的是 DMA 传输,要等 DMA 完成中断里再调这个函数,不能在启动 DMA 后立刻调,否则缓冲区被下一帧覆盖,画面会撕裂。注册驱动时把缓冲区地址、宽高、flush 回调填进lv_disp_drv_t,再lv_disp_drv_register。
3.3 时基和任务处理:两个函数决定界面活不活
LVGL 需要知道时间流逝,还要定期处理自己的任务。无操作系统时,这两件事放在SysTick中断和主循环里:
void SysTick_Handler(void) { HAL_IncTick(); lv_tick_inc(1); // 每 1ms 告诉 LVGL 过了一毫秒 } while (1) { lv_task_handler(); // 主循环里反复调用 HAL_Delay(5); }lv_tick_inc的调用频率要和实际毫秒数对上,放错位置或者频率不对,动画会忽快忽慢,触摸响应也会飘。lv_task_handler的调用间隔决定界面刷新节奏,5ms 一次比较稳,太频繁浪费 CPU,太稀疏按钮按下去半天没反应。这两处是移植后界面“能动但不对劲”的高发区。
3.4 输入设备注册:触摸坐标对不上先查这里
触摸屏接进来后要注册输入设备,读点函数返回原始坐标,LVGL 再按显示分辨率映射。常见问题是点左边响应右边,多半是坐标没做方向转换或者 x/y 反了。读点回调里把原始值读出来,必要时交换或取反:
static void touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data) { uint16_t x, y; TP_Read(&x, &y); >void lvgl_task(void *pvParameters) { while (1) { lv_task_handler(); vTaskDelay(pdMS_TO_TICKS(5)); } } /* 创建时优先级低于通信任务,高于空闲任务 */ xTaskCreate(lvgl_task, "lvgl", 2048, NULL, 3, NULL);vTaskDelay的 5ms 和裸机里的HAL_Delay(5)作用一样,但这里让出了 CPU,其他任务能跑。如果发现界面刷新时串口丢数据,说明 LVGL 任务占 CPU 时间太长,可以把刷新周期调大或者降低颜色深度减少绘制量。
4.2 时基改用系统 tick
带系统后lv_tick_inc不必再放 SysTick 中断里手动调,FreeRTOS 的 tick 本身就是毫秒级,可以在 LVGL 任务里按差值补,或者干脆用lv_tick_set_cb绑定一个返回xTaskGetTickCount的回调。后者更干净,省得两套时基打架。注意 FreeRTOS 的 tick 频率如果是 1000Hz 正好对应 1ms,如果是 100Hz 就要换算,否则动画速度差十倍。
4.3 内存分配交给系统还是 LVGL 自己管
LV_MEM_CUSTOM设 1 时 LVGL 用malloc/free,设 0 时用自带内存池。带 FreeRTOS 的项目里,如果系统堆够大,用自定义分配能和其他模块共享内存;但 FreeRTOS 的heap_4有碎片风险,界面反复创建删除对象时间长了可能分配失败。稳妥做法是保持LV_MEM_CUSTOM 0,给 LVGL 一块独立内存池,界面对象生命周期再乱也影响不到系统堆。这块内存从链接脚本里预留,别和任务栈抢。
5. 避坑排查:移植阶段最常见的五个翻车现场
5.1 编译报 lv_conf.h 找不到
现象是满屏cannot open source input file "lv_conf.h"。原因是头文件搜索路径没加LVGL这一层,或者LV_CONF_INCLUDE_SIMPLE没开。解决是把LVGL目录加进 Include Paths,并在lv_conf.h里确认#if 1已生效。改完 Clean 再全编译,Keil 有时缓存旧路径。
5.2 屏幕白屏或花屏
现象是烧录后全白、全黑或者彩色噪点。原因按概率排:颜色深度和屏驱动不一致、flush 回调没调lv_disp_flush_ready、缓冲区大小算错越界。解决是先确认LV_COLOR_DEPTH与屏的像素格式一致,再在 flush 回调末尾补上 ready 调用,最后检查缓冲区数组长度是否等于宽×高×每像素字节数。
5.3 界面能显示但触摸没反应
现象是画面正常,点按无响应。原因是输入设备没注册、读点回调没返回按下状态、坐标超出分辨率。解决是确认lv_indev_drv_register被调用,在回调里打印原始坐标看范围,方向不对就交换 x/y 或做镜像。
5.4 动画卡顿、刷新慢
现象是按钮切换有拖影,动画一顿一顿。原因是帧缓冲太小、刷新周期太长、或者用了单缓冲还在等 DMA。解决是把缓冲提到屏幕 1/10 以上,LV_DISP_DEF_REFR_PERIOD降到 20 到 30ms,双缓冲配合 DMA 完成中断。RAM 实在不够就减少同屏部件数量。
5.5 带系统后偶发死机
现象是跑一段时间后卡死或 HardFault。原因是 LVGL 内存池和任务栈重叠、或者在中断里调用了 LVGL 函数。解决是检查链接脚本里 LVGL 内存池地址段是否与栈区分离,并确保所有lv_*调用都在任务上下文,中断里只置标志位。
6. 装完怎么验证:从模拟器到板子的双端确认
移植完别急着写业务界面,先做两层验证。第一层在 PC 模拟器上跑,指南第四章专门讲模拟器工程,用 VS 或 CodeBlocks 把 LVGL 源码编成可执行程序,官方例程能正常显示、鼠标能点,说明库本身和配置没问题。这一步把硬件因素排除掉,后面板子上出问题就只查驱动。
第二层在板子上跑最小验证:一个标签加一个按钮,按钮按下改变标签文字。这个 demo 同时覆盖显示、输入、事件三条链路,比直接跑复杂例程更容易定位问题。验证时打开LV_USE_LOG,串口能看到初始化各阶段的日志,哪一步没打印出来就查哪一步。
| 验证项 | 通过标准 | 失败先查 |
|---|---|---|
| 模拟器例程 | 窗口正常显示可交互 | lv_conf.h 与库版本 |
| 板子显示 | 标签文字清晰无花屏 | 颜色深度与 flush 回调 |
| 板子触摸 | 按钮按下有响应 | 输入设备注册与坐标 |
| 动画 | 过渡平滑无明显拖影 | 缓冲大小与刷新周期 |
| 长时间运行 | 半小时不死机 | 内存池与任务栈 |
我自己的习惯是每次换板子或者升级 LVGL 版本,都先把这套最小验证走一遍,通过了再往里堆部件。有次偷懒直接拿旧工程改,结果新屏的驱动时序不一样,调了两天才发现是最小验证没做。从那以后我每次移植都强制先跑模拟器再跑板子最小 demo,两步都过了才动业务代码。希望这套流程能帮你少走点弯路,指南 PDF 里的例程和配置说明对着看,装环境这一步基本就稳了。
本文还有配套的精品资源,点击获取