在STM32H743上部署LVGL教程
在 STM32H743 上部署 LVGL:源码移植、显示触摸适配与 UI 设计
本文以 野火 STM32H743XI EVK Pro 为硬件平台,以 LVGL v8.3.11 + Keil MDK 为软件环境,记录从准备 LVGL 源码、加入工程,到适配显示、触摸和系统时基的完整过程,并介绍使用 AI 与 SquareLine Studio 1.6.1 设计个人 UI 的两种路线。
文章目录
一、硬件平台与移植前提
本文使用的硬件配置如下:
| 项目 | 配置 |
|---|---|
| 开发板 | 野火 STM32H743XI EVK Pro |
| 显示屏 | 7 英寸 LCD |
| 分辨率 | 800 × 480 |
| 显示接口 | 24 bit RGB 接口 |
| 触摸方式 | 电容触摸,支持 5 点触控 |
| 供电要求 | 开发板外接 DC 12 V 电源 |
供电提示: 使用 7 英寸屏时,应给开发板接入 DC 12 V 电源。供电不足可能造成屏幕显示异常,也可能导致 MCU 无法与调试器稳定通信。
在移植 LVGL 之前,建议先确认现有裸机工程已经满足以下条件:
- LCD 初始化正常,可以稳定显示点、线、圆或色块等基本图形。
- 触摸驱动正常,可以读取按下状态和触摸坐标。
- 屏幕分辨率、像素格式和显存地址已经在 BSP 中正确配置。
LVGL 的显示和输入设备最终都要建立在现有 LCD、触摸驱动之上。如果底层驱动尚未跑通,后续很难判断问题来自 BSP 还是 LVGL 适配层。
二、下载并整理 LVGL v8.3.11 源码
2.1 选择 LVGL 版本
在 GitHub 中搜索 LVGL,进入官方源码仓库:

图 1:LVGL GitHub 源码仓库
点击仓库右侧的 Releases,选择需要的版本下载。本文使用 LVGL v8.3.11,主要考虑到 v8 系列资料较多、移植接口成熟,并且与本文使用的 lv_port_disp.c、lv_port_indev.c 模板一致。
版本说明: 本文代码基于 LVGL v8。LVGL v9 的显示、输入设备注册 API 已发生变化,不能直接照搬本文接口。如果准备使用 LVGL Pro 等新工具,应先确认工具支持的 LVGL 主版本。
2.2 提取移植所需文件
下载并解压源码包后,本文保留以下内容:
srcexamplesdemoslvgl.hlv_conf_template.h
源码包的原始目录如下:

图 2:LVGL Release 解压后的目录
新建一个名为 LVGL 的文件夹,将上述文件和目录复制进去。整理完成后的顶层目录如下:

图 3:整理后的 LVGL 顶层目录
随后进入 examples 目录,只保留 porting 文件夹。将 porting 中 6 个模板文件名里的 _template 去掉,例如:
lv_port_disp_template.c→lv_port_disp.clv_port_disp_template.h→lv_port_disp.hlv_port_indev_template.c→lv_port_indev.clv_port_indev_template.h→lv_port_indev.hlv_port_fs_template.c→lv_port_fs.clv_port_fs_template.h→lv_port_fs.h
处理后的 porting 目录如下:

图 4:移除 _template 后的 porting 文件
最后,将 lv_conf_template.h 重命名为 lv_conf.h。
demos并不是 LVGL 内核运行的必需项。如果不使用官方 Demo,不要把其中的源文件和大体积资源加入 Keil 工程,以免增加编译时间和 Flash 占用。
三、将 LVGL 加入 STM32 工程
3.1 放置源码目录
将整理好的 LVGL 文件夹复制到 STM32 工程根目录。对于 CubeMX 生成的工程,可以将它放在与 .ioc 文件同级的位置:

图 5:将 LVGL 文件夹放入工程根目录
3.2 在 Keil 中创建分组
打开 Keil,在工程目标上右键,选择 Manage Project Items:

图 6:打开 Manage Project Items
在弹出的窗口中创建用于管理 LVGL 代码的 Group:

图 7:在 Keil 中创建 LVGL 分组
本文按功能划分为以下 4 个逻辑分组:
LVGL_conf:LVGL 配置文件和总头文件。LVGL_porting:显示、触摸等硬件适配代码。LVGL_src:LVGL 内核源码。LVGL_MyGUI:用户界面代码,可按实际项目选用。
创建完成后的工程分组如下:

图 8:LVGL 工程分组
Keil 中的 Group 是逻辑分组,并不等同于磁盘上的真实文件夹。
3.3 添加源码文件
选中 LVGL_conf,点击 Add Files:

图 9:通过 Add Files 添加 LVGL 文件
将 lv_conf.h 和 lvgl.h 添加到 LVGL_conf。随后采用同样的方法:
- 将
examples/porting中使用到的.c文件加入LVGL_porting。 - 将
src及其所有子目录中的.c文件加入LVGL_src。 - 如果有自己的 UI 源码,将其加入
LVGL_MyGUI。
src 下包含 100 多个源文件,需要逐个子目录确认,避免漏加 .c 文件。
3.4 配置头文件搜索路径
打开 Options for Target:

图 10:打开 Options for Target
进入 C/C++ → Include Paths,点击右侧的省略号按钮添加路径:

图 11:配置 Keil Include Paths
至少加入以下目录,具体相对路径应以工程结构为准:
LVGL
LVGL\src
LVGL\examples\porting
同时不要忘记加入 LCD BSP 和触摸驱动头文件所在目录。
四、配置 lv_conf.h
打开重命名后的 lv_conf.h,先将文件开头用于启用配置内容的条件编译从 0 改为 1:
#if 1 /* Set it to "1" to enable content */
随后检查颜色深度:
#define LV_COLOR_DEPTH 16
这里的 16 仅适用于 LVGL 使用 RGB565 的情况。屏幕物理接口是 24 bit RGB,并不代表 LVGL 缓冲区一定要配置成 24/32 bit;最终应以 LTDC 图层像素格式、BSP 画点函数参数和显存格式为准。三者不一致时,通常会出现颜色异常或图像错位。
五、适配 LVGL 显示接口
5.1 启用 lv_port_disp.c
打开 lv_port_disp.c,将文件顶部的:
#if 0
修改为:
#if 1
然后加入项目 LCD 驱动头文件:

图 12:启用 lv_port_disp.c 并包含 LCD 头文件
5.2 设置屏幕分辨率
在 DEFINES 区域将水平、垂直分辨率改成实际值。本文为 800 × 480:
#define MY_DISP_HOR_RES 800
#define MY_DISP_VER_RES 480

图 13:设置 LVGL 屏幕分辨率
5.3 选择显示缓冲区方案
继续向下找到 lv_port_disp_init()。模板提供了 3 种缓冲区示例:

图 14:LVGL 显示缓冲区示例
- Example 1:单个局部缓冲区。 内存占用最小,适合先完成移植验证。
- Example 2:两个局部缓冲区。 可以配合 DMA/DMA2D 在刷新时并行绘制,性能通常更好。
- Example 3:两个全屏缓冲区。 刷新效率高,但内存开销最大。
以 RGB565 为例,800 × 480 的单个全屏缓冲区约占:
800 × 480 × 2 Byte = 768000 Byte ≈ 750 KiB
两个全屏缓冲区约占 1.5 MiB,通常需要外部 SDRAM。本文先采用 Example 1 跑通显示链路。
5.4 实现 disp_flush()
找到 disp_flush(),在模板循环中调用现有 LCD 画点函数。下面代码仅展示适配思路,LCD_DrawPixel() 的函数名和颜色参数必须根据实际 BSP 修改:
static void disp_flush(lv_disp_drv_t *disp_drv,
const lv_area_t *area,
lv_color_t *color_p)
{
for (int32_t y = area->y1; y <= area->y2; y++) {
for (int32_t x = area->x1; x <= area->x2; x++) {
/* 仅当 LV_COLOR_DEPTH == 16 且 BSP 接收 RGB565 时可直接使用 full */
LCD_DrawPixel(x, y, color_p->full);
color_p++;
}
}
/* 无论采用哪种刷新方式,完成后都必须通知 LVGL */
lv_disp_flush_ready(disp_drv);
}
性能提示: 逐像素调用 BSP 函数适合验证显示链路,但 800 × 480 屏幕上会比较慢。正式项目建议让
disp_flush()直接复制矩形像素块到帧缓冲区,或使用 DMA2D 加速。若启用了 STM32H7 的 D-Cache,还要处理帧缓冲区的 Cache 一致性。
至此,LVGL 显示设备的基础适配完成。
六、适配 LVGL 触摸输入
6.1 启用 lv_port_indev.c
打开 lv_port_indev.c,同样将顶部的 #if 0 改为 #if 1,并包含项目触摸驱动头文件。
本文只使用触摸屏作为 Pointer 输入设备,因此可以在 lv_port_indev_init() 中保留触摸相关注册,关闭模板中未使用的 Mouse、Keypad、Encoder 和 Button 相关代码:

图 15:关闭未使用的输入设备模板
6.2 接入按下状态与坐标读取函数
继续找到以下两个触摸适配函数:

图 16:触摸按下状态与坐标适配函数
本文的 Goodix 触摸驱动对外提供:
GTP_Get_Is_Pressed_For_LVGL():返回当前是否按下。GTP_Get_XY_For_LVGL():读取当前触摸坐标。
因此可以按下面的方式接入:
static bool touchpad_is_pressed(void)
{
return GTP_Get_Is_Pressed_For_LVGL();
}
static void touchpad_get_xy(lv_coord_t *x, lv_coord_t *y)
{
int16_t temp_x;
int16_t temp_y;
GTP_Get_XY_For_LVGL(&temp_x, &temp_y);
*x = (lv_coord_t)temp_x;
*y = (lv_coord_t)temp_y;
}
触摸读取回调只负责上报状态与坐标,不建议在这里调用 LCD_DrawPixel() 等绘图函数,否则可能与 LVGL 的刷新过程互相干扰。
6.3 从触摸中断定位底层处理链路
不同屏幕使用的触摸控制器和 BSP 不同,函数名不会完全一致。可以从外部中断入口开始查找触摸处理逻辑。本文驱动中的调用关系为:
GTP_IRQHandler()
└── GTP_TouchProcess()
└── Goodix_TS_Work_Func()
触摸中断处理函数如下图所示:

图 17:Goodix 触摸中断处理函数
继续进入 GTP_TouchProcess(),即可找到更深一层的 Goodix 数据处理函数:

图 18:Goodix 触摸数据处理函数
如果 LVGL 能检测到按下但坐标不正确,应重点检查屏幕旋转、X/Y 交换、坐标镜像和分辨率映射。
七、在 main.c 中初始化 LVGL
7.1 包含头文件
在 main.c 中加入:
#include "lvgl.h" /* LVGL 核心接口 */
#include "lv_port_disp.h" /* 显示设备适配 */
#include "lv_port_indev.h" /* 输入设备适配 */
7.2 按顺序完成初始化
先完成时钟、GPIO、SDRAM、LTDC、LCD 和触摸等底层外设初始化,再初始化 LVGL:
/* LCD、触摸等底层硬件初始化完成后 */
lv_init();
lv_port_disp_init();
lv_port_indev_init();
推荐的初始化顺序可以概括为:
HAL/系统时钟
→ SDRAM、LTDC、LCD
→ 触摸驱动
→ lv_init()
→ lv_port_disp_init()
→ lv_port_indev_init()
→ 创建 UI
八、为 LVGL 提供 1 ms 系统时基
LVGL 需要一个持续递增的系统时基,用于动画、输入设备长按、定时器等内部功能。本文使用 1 ms 定时器中断,在中断中调用:
lv_tick_inc(1);
以 TIM6 为例:
void TIM6_DAC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim6);
lv_tick_inc(1);
}

图 19:在 TIM6 中断中调用 lv_tick_inc(1)
注意事项:
- TIM6 的实际中断周期必须是 1 ms;如果周期不是 1 ms,传给
lv_tick_inc()的数值也要相应调整。 - CubeMX 工程应将自定义代码放入
USER CODE区域,防止重新生成工程时被覆盖。 - 如果工程已经通过其他可靠方式向 LVGL 提供 Tick,不要重复调用。
九、创建测试控件并运行任务处理器
9.1 创建按钮和开关
在进入 while 循环之前、完成 LVGL 初始化之后,加入以下测试代码:
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 160, 80);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, -50);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Touch Me!");
lv_obj_center(label);
/* 创建开关。触摸正常时,点击后状态会自动切换 */
lv_obj_t *sw = lv_switch_create(lv_scr_act());
lv_obj_align(sw, LV_ALIGN_CENTER, 0, 50);
9.2 周期调用 lv_timer_handler()
在主循环中持续调用 LVGL 任务处理器:
while (1) {
lv_timer_handler();
HAL_Delay(5);
}
lv_timer_handler() 负责处理界面刷新、动画和输入事件。裸机工程中加入一个短延时可以避免 CPU 一直空转;如果使用 FreeRTOS,可以在 LVGL 任务中改用 osDelay()。
十、编译、烧录与触摸验证
完成以上配置后重新编译并烧录程序。界面应显示一个按钮和一个开关:

图 20:LVGL 测试界面的初始状态
点击开关后,其状态发生切换,说明显示刷新和触摸输入链路均已正常工作:

图 21:触摸后开关状态发生切换
十一、进一步设计个人 UI
完成 LVGL 的基础移植后,就可以开始构建实际项目界面。本文新增两条常用路线:
- 使用 AI 根据需求和 HTML 原型生成原生 LVGL C 代码。
- 使用 SquareLine Studio 完成可视化设计并导出 LVGL 工程代码。
无论采用哪种方式,都不能改变 LVGL 的基础运行链路:lv_tick_inc() 仍然负责系统时基,lv_timer_handler() 仍然需要在主循环或 RTOS 任务中周期执行。
11.1 路线一:使用 AI 生成 LVGL UI
得益于 LVGL 的开源生态和 C API,可以先与 AI 沟通界面布局、交互逻辑和视觉风格,让 AI 生成一个 HTML 原型;确认原型满足需求后,再要求代码能力较强的 AI 将原型转换为 LVGL 的 .c 和 .h 文件。
推荐向 AI 明确提供以下约束:
- LVGL 版本:本文为 v8.3.11。
- 屏幕分辨率:800 × 480。
- 颜色深度:与
lv_conf.h中的LV_COLOR_DEPTH保持一致。 - 运行环境:STM32H743,不存在浏览器、HTML 或 CSS 运行时。
- 输入方式:触摸屏,交互控件应直接显示在界面上。
- 资源限制:说明可用 Flash、内部 SRAM 和外部 SDRAM 容量。
HTML 在这里是界面和交互原型,不会直接运行在 STM32 上。最终代码必须转换为真实的 LVGL 控件、样式、事件回调和资源声明。
将生成的 .c、.h 文件放入工程目录,并添加到 Keil 的 UI 分组中:

图 22:将 AI 生成的 UI 源文件加入工程
在 main.c 中包含生成的头文件,并在 lv_init()、显示端口和输入端口初始化完成后,调用生成代码提供的 UI 初始化函数:
#include "AI_Code_UI.h"
/* LCD、触摸和 LVGL 初始化完成后 */
lv_init();
lv_port_disp_init();
lv_port_indev_init();
AI_Code_UI_Init(); /* 以生成文件中的实际初始化函数名为准 */
如果生成代码中的函数名不是 AI_Code_UI_Init(),应以头文件声明为准进行替换。UI 初始化完成后,原有 lv_tick_inc() 和 lv_timer_handler() 的位置保持不变。
11.2 在 AI 生成的 UI 中加入真实图片
如果 UI 需要显示 Logo、图标或背景图,应提前要求 AI 在代码中预留对应图片资源接口。随后使用 LVGL 官方 Image Converter 将图片转换为 C 数组:
- 选择与项目
LV_COLOR_DEPTH相匹配的颜色格式。 - 将图片导出为
.c数组文件。 - 把生成的
.c文件加入 Keil 工程。 - 在使用图片的源文件中通过生成的图片描述符或
LV_IMG_DECLARE()进行引用。
如果只保留了图片声明,却没有把转换后的 .c 文件加入工程,链接阶段通常会出现图片符号未定义的问题。
11.3 路线二:使用 SquareLine Studio 设计 UI
本文使用 SquareLine Studio 1.6.1。打开软件后可以看到项目和示例选择界面:

图 23:SquareLine Studio 1.6.1 主界面
进入 Examples,选择一个分辨率和界面结构接近目标屏幕的示例并双击打开:

图 24:选择 SquareLine Studio 示例工程
示例分辨率只作为起点。打开工程后,进入 File → Project Settings:

图 25:配置 SquareLine Studio Project Settings
重点检查以下项目:
- LVGL 主版本与目标工程一致,本文应选择 LVGL v8 对应配置。
- Horizontal Resolution 设置为
800。 - Vertical Resolution 设置为
480。 - 导出路径指向一个便于管理的独立目录。
- 图片、字体和颜色格式与嵌入式工程配置一致。
设置完成后点击 APPLY CHANGES。软件会根据新配置重新生成界面,然后通过导出菜单生成 UI 文件:

图 26:从 SquareLine Studio 导出 UI 文件
导出的目录通常包含:
componentsfontsimagesscreensui.c、ui.hui_events.c、ui_events.hui_helpers.c、ui_helpers.h

图 27:SquareLine Studio 导出的 UI 目录
11.4 将 SquareLine 导出代码加入 Keil
在 STM32 工程中创建一个独立 UI 文件夹,例如 LVGL_MyGUI,将 SquareLine 导出的全部 UI 代码复制到该目录:

图 28:将 SquareLine 导出文件放入工程目录
随后在 Keil 中完成以下配置:
- 将导出目录及
screens、images、fonts、components等子目录中的所有.c文件加入LVGL_MyGUI分组。 - 将 UI 根目录及需要的子目录加入 Include Paths。
- 在
main.c中包含ui.h。 - 在
lv_port_disp_init()和lv_port_indev_init()之后、进入主循环之前调用ui_init()。
Keil 中加入 UI 文件后的结构如下:

图 29:在 Keil 中加入 SquareLine UI 文件
初始化代码示例:
#include "ui.h"
lv_init();
lv_port_disp_init();
lv_port_indev_init();
ui_init();
while (1) {
lv_timer_handler();
HAL_Delay(5);
}
编译并烧录后,SquareLine 设计的界面即可运行在开发板上:

图 30:SquareLine UI 在 STM32H743 开发板上的运行效果
SquareLine 重新导出时可能覆盖生成文件。业务逻辑最好放在事件文件、用户代码文件或独立模块中,避免直接修改会被重复生成的代码。更深入的控件使用、动画和事件配置,可以继续参考 SquareLine Studio 专项教程。
十二、常见问题排查
12.1 编译时找不到 lvgl.h
检查 Keil 的 Include Paths 是否包含 LVGL,并确认路径是相对于工程文件的正确路径。
12.2 lv_conf.h 没有生效
确认文件已从 lv_conf_template.h 重命名为 lv_conf.h,文件顶部的配置开关已改成 #if 1,并且编译器能够从 Include Paths 找到该文件。
12.3 屏幕白屏或刷新停止
依次检查:
- DC 12 V 供电、背光和 LCD 底层初始化是否正常。
lv_init()、lv_port_disp_init()是否已经调用。disp_flush()是否真正写入屏幕或帧缓冲区。- 刷新结束时是否调用了
lv_disp_flush_ready()。
12.4 颜色异常
检查 LV_COLOR_DEPTH、LTDC 图层像素格式、LCD BSP 接收的颜色格式以及 RGB565 字节序是否一致。
12.5 触摸无响应或坐标颠倒
检查输入设备是否注册为 LV_INDEV_TYPE_POINTER,按下状态是否正确返回,以及坐标是否需要进行 X/Y 交换、镜像或旋转映射。
12.6 程序能运行但刷新很慢
逐像素调用 LCD_DrawPixel() 的开销较大。确认功能正确后,可将 disp_flush() 改为帧缓冲区块复制,并结合 DMA2D、双局部缓冲区和 Cache 维护优化性能。
12.7 UI 代码编译时报图片或字体符号未定义
确认 SquareLine 或 Image Converter 生成的资源 .c 文件已经加入 Keil,而不仅仅是添加了头文件。还要检查 images、fonts 等目录是否加入 Include Paths。
12.8 加入 SquareLine UI 后 Flash 空间不足
优先检查生成的图片和字体资源。删除未使用的资源、减少图片尺寸或颜色深度、缩小字体字符范围,并避免同时编译无关的 demos 和 examples。
十三、总结
在 STM32H743 上部署并构建 LVGL 界面的关键,不是简单地把源码加入工程,而是打通以下 5 条链路:
- 源码与配置链路:LVGL 内核源码、
lv_conf.h和编译路径正确。 - 显示链路:LVGL 输出区域能够写入 LCD 或帧缓冲区,并在完成后调用
lv_disp_flush_ready()。 - 输入链路:触摸驱动能够向 LVGL 提供稳定的按下状态和坐标。
- 运行时链路:
lv_tick_inc()提供系统时基,lv_timer_handler()周期运行。 - UI 应用链路:AI 或 SquareLine 生成的界面代码、图片和字体资源完整加入工程,并在正确的初始化阶段创建界面。
当按钮能够显示、开关能够响应触摸并正确切换状态时,LVGL 的基础移植就完成了;当 AI 或 SquareLine 生成的界面也能稳定运行时,应用层 UI 链路就已经打通。后续可以继续接入 DMA2D、双缓冲和资源优化,进一步提升刷新性能与界面复杂度。
关键词: STM32H743、LVGL、Keil、LCD、Goodix、SquareLine Studio、AI UI、嵌入式 GUI
更多推荐

所有评论(0)