1. 移植前的准备:芯片选型与应用场景拆解
1.1 为什么选 STM32H743IIT6 作为 LVGL 的载体
把 LVGL 跑在 STM32H743IIT6 上,是很多做过 UI 项目的朋友都知道的“甜点”组合。这颗芯片属于 STM32H7 系列的高性能分支:Cortex-M7 内核,主频最高能跑到 480MHz,自带 1MB SRAM(分 AXI、SRAM1/2/3 等多块)、2MB Flash,还集成了 LTDC 液晶控制器和 DMA2D 图形加速器。用一句话概括:这是一颗“画 UI 画的动、刷屏刷的快、资源给得足”的单片机。
我之前在 F103 和 F407 上也移植过 LVGL,但那感觉完全是两码事。F407 主频 168MHz,用软件刷屏一帧 800x480 的 RGB565 图片要十几毫秒,UI 一复杂帧率就崩到十几帧;而 H743 有专门的 DMA2D 外设,可以硬件完成颜色格式转换、图像旋转、透明混合这些操作,加上 LTDC 直接从显存搬运数据到屏幕,CPU 几乎不参与刷屏过程。所以如果你打算做 7 寸屏、800x480 分辨率甚至更高,同时又不想上 Linux + Qt 那套重方案,STM32H743IIT6 + LVGL 属于比较务实的选型。
不过要注意一点:H743 的电源和时钟设计比 F1/F4 复杂得多,VDD 需要 3.3V 但内核有独立的 LDO 配置,CubeMX 里如果不小心把内核电压设错了,480MHz 是跑不起来的。这些细节后面我会单独讲。
1.2 LVGL 版本怎么选:8.3 LTS 还是 9.x
这是每一个新接手 LVGL 项目的人都会遇到的问题。目前主流是两个大版本:8.x 系列和 9.x 系列。
LVGL 8.3 是 8.x 的最后一个长期维护版本,API 稳定,资料最多,教程和现成驱动例子遍地都是。如果你用的是老屏、老触摸芯片,或者想快速出一版能跑的东西,选 8.3 基本不会错。
LVGL 9.x 则在架构上做了一些调整,比如配置头文件改名为lv_conf.h但其实内部结构做了大量重写,色彩管理、字体渲染、GPU 接入方式都有变化,对 CMSIS-RTOS2 的适配也更自然。如果你是新项目,想用更长时间不用大改,也可以直接上 9.x。我这次最终用的是 8.3.12,原因是触摸屏驱动和现有屏幕初始化代码更匹配,社区里踩坑方案也多,搜索问题方便。
版本选择这件事,核心思路就一条:团队熟悉哪个、你手头资料哪个多,就用哪个。版本本身不是瓶颈,瓶颈是你调试时能不能快速找到解决办法。
2. 基于 STM32CubeMX 的底层工程搭建
2.1 时钟树配置:让 H743 稳定跑到 480MHz
STM32H7 系列的时钟树非常复杂,和 F1/F4 完全不是一个概念。芯片内部有多个 PLL,D1、D2、D3 三个域分别是不同外设的时钟来源。刚上手的人容易在这里直接懵掉。
先说结论:如果你用的是外部 25MHz 晶振,想要 CPU 跑满 480MHz,参考配置如下:
- PLL1 配置:M=5,N=192,P=2,得到 25 / 5 × 192 / 2 = 480MHz。
- D1CPRE 保持 /1,HCLK(AHB1/2/3)设为 240MHz。
- APB3、APB1、APB2 如果跑 120MHz,需要各自设 2 分频。
- 内核电压必须选 VOS1(高性能档),否则 480MHz 会运行不稳定。
- Flash 延迟(Latency)要按 CubeMX 自动计算值设置,通常 480MHz 对应 2 个等待周期(如果开了 ART 缓存会好很多)。
还有一个特别容易被忽略的:CubeMX 里默认Power Regulator Voltage Scale可能是 VOS0 或 VOS2,必须切换到 VOS1 之后,480MHz 的频率选项才会出现在时钟树里,否则最高只能选到 400MHz 左右。
另外,LTDC 外设挂在 D3 域,它的像素时钟由 PLL2 或 PLL3 提供,不经过 APB 总线时钟。很多人把画面做成“花屏”或者“屏闪”,原因往往就出在这里——APB3 总线和像素时钟匹配出了问题。
2.2 SDRAM 与显存规划:FMC 接口配置细节
LVGL 做复杂界面时,内存占用很快会超过 H743 内部 SRAM 的合理使用范围。特别是放了两三张全屏背景图之后,内部内存吃得非常快。所以大多数人会做一件事:外挂一片 SDRAM,把它既当 LVGL 的绘制缓冲区,又当 LTDC 的显存。
我用的方案是 W9825G6KH,16MB SDRAM,挂在 FMC 的 Bank1。有人会问 16MB 大不大?实际上 800x480 分辨率、ARGB8888 格式的显存一帧要 800×480×4 = 1.5MB,如果做双缓冲要 3MB,再加 LVGL 绘制缓存和动态内存分配,16MB 是够用的,但也没有特别宽裕。
CubeMX 中配置 FMC SDRAM 时要注意几组关键参数:
- 列地址位数:W9825G6KH 是 8 位列地址,9 位行地址,4 个 Bank。
- CAS Latency:通常是 3。
- 刷新周期:标准 SDRAM 刷新周期 64ms,配置成 1536 个时钟周期左右即可。
- 时序参数:比如 tRCD、tRP、tWR 按 datasheet 填,我这边实测
LoadToActiveDelay=2、ExitSelfRefreshDelay=8、SelfRefreshTime=4、RowCycleDelay=6是稳定参数。
初始化顺序也很重要:先初始化 GPIO 和时钟,然后发送 SDRAM 模式寄存器指令,再执行几轮预充电和刷新,最后设置刷新率计数器。CubeMX 生成的HAL_SDRAM_Init只完成了基础时序初始化,SDRAM_Initialization_Sequence这段代码是必须要你手写的,漏了它整个 SDRAM 就是一个“死区”,一访问就 HardFault。
2.3 RGB 屏幕与 LTDC 接口配置:像素时钟和时序别乱填
我用的屏幕是常见的 7 寸 800x480 RGB 电容触摸屏,接口是 24 位 RGB 并行数据线,加上 VSYNC、HSYNC、DE、PCLK 四条同步控制线。LTDC 的作用就是把这块屏刷起来。
LTDC 配置的关键在于三块:屏幕时序参数、图层格式、显存地址。
屏幕时序参数是指“行同步脉冲宽度”“前后肩”这些值。这些参数在屏幕 datasheet 里一般都会明确给出,以我用的这块屏为例:
- HBP(水平后肩)88,HFP(水平前肩)40,HSYNC 宽度 48。
- VBP(垂直后肩)32,VFP(垂直前肩)13,VSYNC 宽度 3。
- 像素时钟 24MHz 左右。
把这些参数填到 CubeMX 的 LTDC 配置里。如果填错了,常见的表现是画面偏移、花屏、或者屏幕只亮一半。这类问题用逻辑分析仪看信号最直接,但如果没有,就先检查PCLK是否在面板允许范围内——太快了会出现画面闪烁,太慢了面板直接黑屏。
图层的设置上,默认 Layer0 用来做主显存,ARGB8888 格式。我在工程里开了一个 800×480 的显存缓冲,地址指向 SDRAM。这里有一个性能优化的点:LTDC 读取显存时,如果显存地址在 AXI SRAM 中效率最高,如果放 SDRAM 则带宽会打折。所以我把 LVGL 的绘制专用缓冲放在内部 AXI SRAM,而最终要送到 LTDC 的显存放在 SDRAM,刷屏时通过 DMA2D 从 SRAM 拷到 SDRAM,速度依然可观。
2.4 触摸屏通信接口配置:I2C 还是 SPI
绝大多数电容触摸屏用的是 I2C 接口,芯片常见的有 GT911、FT5x06、FT6336 等。我手上这块是 GT911,I2C 地址默认 0x5D,通过 INT 引脚产生中断通知主机有触摸事件。
GT911 这种触摸芯片有一个特殊性:它的 I2C 地址是可变的,由引脚电平决定。有些模组上电时可以通过复位引脚的时序来切换地址。如果你发现 I2C 扫描不到设备,先查一下I2C_ADDR引脚的电平,确定是 0x5D 还是 0x14。我为了保险,在驱动初始化时做了一次“探测 0x5D,失败则试 0x14”的逻辑,这样不管模组是哪种接法都能自动适应。
在 CubeMX 中把 I2C1 配成 100kHz 即可,触摸屏对速度要求不高。INT 引脚配置成外部中断,下降沿触发。用中断而不是轮询的好处是省 CPU,而且 LVGL 本身有事件驱动机制,你可以在中断里只做标记,然后在主循环里读取坐标数据,这样避免在中断上下文里操作 I2C,减少因优先级导致的通信异常。
3. LVGL 源码集成与移植层编写
3.1 把 LVGL 源码“装”进工程里
LVGL 源码体积不小,但不需要全部都加进工程。官方推荐的方式是只加入必须的核心源码目录,再配合lv_conf.h裁剪功能。我的工程结构如下:
lvgl/ ├── src/ │ ├── core/ # LVGL 核心对象与事件处理 │ ├── draw/ # 软件渲染引擎 │ ├── font/ # 内置字体 │ ├── misc/ # 内存、日志、定时器等 │ ├── hal/ # 显示刷新、输入设备接口 │ ├── widgets/ # 基础控件 │ ├── layouts/ # 布局引擎 │ └── themes/ # 主题 ├── demos/ # 官方 demo └── lv_conf.h # 配置文件你需要保证在 Keil/IAR/CMake 的编译环境中把src/下所有.c文件加进去,理论上新手最容易犯的错误是编译时漏了一两个目录里的文件,导致大量“undefined reference”的报错。
lv_conf.h这个文件在 LVGL 源码根目录下有个lv_conf_template.h模板,你必须复制一份重命名为lv_conf.h,并且把文件头的#if 0改成#if 1,否则配置项完全不生效,这是每个 LVGL 移植新手都会踩的坑,无论如何强调都不为过。
3.2 修改 lv_conf.h:决定整个框架运行方式的核心宏
lv_conf.h是整个 LVGL 移植的灵魂,里面定义了 LVGL 如何管理内存、如何处理颜色、是否启用 GPU、是否开启日志等。我这次改动中最关键的几个宏:
LV_COLOR_DEPTH:设为 16,也就是 RGB565 格式。很多人会问为什么不用 32 位?因为这次的屏幕色彩要求不高,用 RGB565 可以减少一半显存带宽和内存占用,画 UI 完全够用。LV_MEM_CUSTOM:设为 1,启用自定义内存接口。我把它挂到 FreeRTOS 的pvPortMalloc上,实现 LVGL 与系统的内存统一管理。LV_TICK_CUSTOM:设为 1,使用外部时基。这样可以避免 LVGL 内部用定时器产生冲突,直接复用 FreeRTOS 的系统 tick。LV_USE_LOG:设为 1,在调试期开着日志,发布前关掉。LVGL 的日志输出能帮你定位很多内存问题和渲染问题,但打印频率高时会拖慢性能。LV_USE_DMA2D:设为 1,启用 H743 的 DMA2D 硬件加速。
3.3 flush 回调:把像素数据交给 LTDC
LVGL 本身并不直接写屏幕,它只是把控件绘制到一块内存缓冲区里,然后把缓冲区交给用户实现的 flush 函数。这个函数是移植的“最后一公里”,直接决定了渲染效果。
flush 回调长这样:
static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { // 把 color_p 缓冲区里的数据,拷贝到屏幕对应的区域 LCD_Fill(area->x1, area->y1, area->x2, area->y2, (uint16_t *)color_p); // 重要:通知 LVGL 这一块区域刷新完成 lv_disp_flush_ready(disp_drv); }我实际用的LCD_Fill不是普通的循环逐点写。更高效的方式是配置 DMA2D 把color_p按矩形区域搬运到 SDRAM 显存地址。H743 的 DMA2D 能直接做 2D 矩形搬运,效率比 for 循环高几个数量级。代码大概是这样:
DMA2D_HandleTypeDef dma2d; dma2d.Init.Mode = DMA2D_M2M; // 内存到内存 dma2d.Init.ColorMode = DMA2D_RGB565; dma2d.Init.OutputOffset = LCD_PIX_WIDTH - (area->x2 - area->x1 + 1); HAL_DMA2D_Start(&dma2d, (uint32_t)color_p, (uint32_t)(SDRAM_FRAMEBUF + area->x1 * 2 + area->y1 * LCD_PIX_WIDTH * 2), area->x2 - area->x1 + 1, area->y2 - area->y1 + 1); HAL_DMA2D_PollForTransfer(&dma2d, 1000);这里有很多新手会犯的一个错误:flush 函数里忘了加lv_disp_flush_ready,或者没在 DMA2D 传输完成后再调用。结果就是画面要么一直白屏,要么撕裂严重。记住:lv_disp_flush_ready的调用时机必须是在数据真正写完显存之后,不是进入函数之后。
3.4 时基与心跳:tick 的三种实现方式
LVGL 需要有“心跳”来驱动动画、任务调度和超时检测。没有 tick,整个框架不会崩,但所有动画都卡死,触摸事件也会响应异常。
实现 tick 有三种方式,我按推荐度排序:
- FreeRTOS 的系统 tick 里直接
lv_tick_inc(1)。这要求你把LV_TICK_CUSTOM设为 0,然后保证每毫秒调用一次lv_tick_inc。这种方式最简单,我在项目里用的就是它。 - 使用硬件定时器(如 TIM2),在中断里调用
lv_tick_inc。如果你项目没用 RTOS,这种方案最干净。 - 把
LV_TICK_CUSTOM设为 1,让 LVGL 自己读取HAL_GetTick()。这种方案实现起来最容易,但要注意HAL_GetTick()在中断里的线程安全性。
如果用了 FreeRTOS,注意lv_tick_inc不要放在中断里嵌套太深的地方。我习惯的做法是在SysTick_Handler中判断当前是中断上下文,然后调用lv_tick_inc(1),虽然轻微增加了中断负载,但稳定可靠。
3.5 第一个界面:跑通 demo 的完整路径
移植完成之后,第一件事不是画自己的 UI,而是跑官方 demo,这样可以快速验证移植是否是完整的。
在lv_conf.h里启用:
LV_USE_DEMO_WIDGETS设为 1。LV_DEMO_WIDGETS_SLIDESHOW可以根据需要设置。
主函数中调用lv_demo_widgets()即可。首次运行我遇到过两种常见情况:一种是屏幕只显示纯色背景,控件一个都没出现,后来确认是绘制缓冲太小——LVGL 每次绘制一个控件时,可能需要比一个 buffer 更大的区域来绘制半透明效果,这种情况可以适当调大 buffer,或者开启 buffer split 模式。
另一种是画面能显示,但刷新“慢动作”,一帧要几百毫秒。这是典型的内存带宽问题——绘制缓冲区放在 SDRAM 里,而 SDRAM 的带宽已经被 LTDC 读显存占用了大半。我把绘制缓冲挪到内部 AXI SRAM 后,速度提升立竿见影。
4. 触摸屏接入:从裸机读取到 LVGL 输入设备
4.1 触摸芯片协议与坐标转换思路
GT911 这类芯片输出的原始数据是“触摸点坐标”,但它的坐标系和屏幕显示坐标系不一定完全对应。有些屏的触摸面板 X 轴方向和显示 X 轴方向相反,有些 Y 轴会有偏移。所以在驱动里做一次坐标映射是必须的。
GT911 读取坐标的流程:
- 主机通过 I2C 读取
0x814E寄存器,这个寄存器的高位表示是否有触摸事件,低 4 位表示触摸点数。 - 读取
0x8150开始的数据区,每 6 字节表示一个触摸点:x 低字节、x 高字节、y 低字节、y 高字节、尺寸、保留位。 - 清中断标志,等待下一次中断。
有一个坑:GT911 的坐标数据是大端模式,即先读高字节后读低字节?实际上 datasheet 上明确是按小端存储,但不同批次模组可能存在差异。我在驱动里做了个调试模式,开关打开时把原始坐标通过串口打印出来,点四下屏幕四角,对比原始值和显示值,就能确认字节序和坐标方向是否需要翻转。
4.2 编写 LVGL input device 驱动
LVGL 的输入设备更简单,不像 flush 那么复杂,核心就是注册一个“读数据”的回调:
static void touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) { if (touch_get_point(&x, &y)) { >lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = touchpad_read; lv_indev_drv_register(&indev_drv);这段代码放在一个touchpad_init()函数里,在 LVGL 初始化完成后调用。注意touch_get_point()内部不要做阻塞等待,如果触摸芯片还在 I2C 通信中,函数就返回0表示没有坐标。
4.3 坐标校验与屏幕旋转的处理细节
屏幕旋转是触摸驱动最折磨人的部分。LVGL 里有lv_disp_set_rotation,但它只影响显示内容方向,触摸坐标的映射还要你自己处理。
我之前在代码里加了几个方向宏:
#define ROTATE_0 0 #define ROTATE_90 1 #define ROTATE_180 2 #define ROTATE_270 3 #if (TOUCH_ROTATE == ROTATE_90) tmp_x = y; tmp_y = LCD_PIX_WIDTH - 1 - x; x = tmp_x; y = tmp_y; #endif旋转 90° 时,原来屏幕坐标x变成了新的y,原来的y被翻转之后变成新的x。这个映射关系最好是画出来,不要凭直觉硬想,画一个坐标系图和方向标就容易理解了。
这里我学到的经验是:不要把坐标变换写成硬编码,否则产品改版换屏时你会想哭。把变换函数单独抽象出来,配合调试打印,能节省大量时间。
4.4 多点触控与手势:要不要用单点就够了
LVGL 的指针设备本质上还是以“单点”为逻辑基础的,除非你实现完整的LV_INDEV_TYPE_POINTER的手势扩展。大多数工业界面、配置界面,单点触摸完全够用。
但如果你要做图片缩放、旋转等手势功能,LVGL 8.3 以后引入了替代性的 API,可以通过注册lv_indev的双指数据。如果只是做基础 UI,我不建议一上来就折腾多点。先把单点的准确率和稳定性做出来,再考虑扩展。
5. 性能调优:让 H743 的 480MHz 真正用在刀刃上
5.1 DMA2D 加速的开启与验证
LVGL 8.3 里,要启用 DMA2D 加速,除了配置宏LV_USE_DRAW_SW和LV_USE_DRAW_DMA2D之外,还需要实现一个接口:
void lv_draw_dma2d_blend(lv_color_t *dest, const lv_color_t *src, uint32_t length, lv_opa_t opa) { // 调用 HAL_DMA2D 完成颜色混合或拷贝 }在很多芯片上,DMA2D 能加速的是“填充”“拷贝”“混合”。LVGL 的软件渲染引擎在做很多圆角、阴影等复杂绘制时,仍然会走软件计算,所以 DMA2D 不是万能的。它主要提速的是大块区域的纯色填充和图片拷贝,UI 里大量使用的 Rectangle、背景填充、Image 显示场景可以显著增快。
验证方法很简单:在关闭 DMA2D 和开启 DMA2D 两种情况下,分别跑同一个含大面积图片填充的界面,用示波器抓LVGL 刷新完成引脚的电平持续时间,或者用串口打印每帧渲染耗时,差距一般都会有 30% 以上。
5.2 帧率实测对比:纯 CPU vs DMA2D
我在这个项目里实测过一组数据,面板 800x480,RGB565,画面是一个全屏背景图 + 若干圆角按钮,复杂程度中等:
| 配置方案 | 单帧渲染耗时 | 实际观感 |
|---|---|---|
| 纯软件渲染,绘制缓冲在内部 SRAM | 约 35ms | 流畅但偶有掉帧 |
| 软件渲染 + flush 用 DMA2D 搬运 | 约 22ms | 明显顺滑 |
| DMA2D 混合加速 + flush DMA 搬运 | 约 14ms | 手感干脆 |
这组数据说明,DMA2D 在 H743 上的收益是实打实的。注意这里的耗时不包含刷屏本身的时间——LTDC 刷新屏幕是持续后台行为,不会阻塞 CPU。
5.3 内存占用核算:这片 16MB SDRAM 能撑起多大的 UI
一个常见困惑是:LVGL 到底吃多少内存?我以这次的工程为例算笔账:
- 显存(LTDC 直接使用)RGB565,800x480:800×480×2 ≈ 768KB。
- LVGL 绘制缓冲,常用三分屏方式:800×100×2 × 3 ≈ 480KB,放在内部 AXI SRAM。
- LVGL 动态堆内存:分配 4MB,放在 SDRAM。
- DMA2D 临时操作缓存:2MB,放在 SDRAM。
这样算下来 SDRAM 一共用了约 7.2MB,还有不小的余量。如果 UI 里嵌入大量高分辨率图片,建议单独规划一个“图片资源区”,放到外部 Flash(比如 W25Q64 SPI Flash),配合 LVGL 的lv_img控件动态读取,而不是把图片全部塞进内存。
5.4 画面撕裂与缓存刷新策略
撕裂是指屏幕上半部分已经显示新的一帧,下半部分还在显示旧的一帧。产生原因很简单:LTDC 读取到显存的某个位置时,CPU/DMA2D 恰好改写了那个位置的数据。
解决方法有三种:
- 双缓冲 + 垂直同步。在 LTDC 配置里开启垂直同步中断,在 VSYNC 中断中交换显示层地址。这是一个标准做法,效果最好,但需要两个完整的 framebuffer,内存压力翻倍。
- 单缓冲 + 等待自动同步。在 LVGL 的 flush 回调里不立即启动拷贝,而是等到 VSYNC 中断到来后再执行。这种方式简单,但会浪费一点点帧间隔时间。
- 利用 LTDC 的 LineInterrupt。当 LTDC 扫描到屏幕底部之外的区域时再更新显存。
我在这项目里采用方案 1。800x480 RGB565 双缓冲多占 768KB,对 16MB SDRAM 来说可接受,换来的稳定性非常值。
6. 踩坑实录:移植过程最容易翻车的 8 个问题
下面的表格是我和几个朋友在 H743 + LVGL 项目中遇到的真实问题,按出现频率排序,基本覆盖了这类移植项目的典型坑:
| 问题现象 | 根因 | 解决办法 |
|---|---|---|
| 白屏无任何显示 | LTDC 时钟未开启、显存地址未配置 | 检查 LTDC 图层使能、Layer 显存地址是否在初始化前就有效 |
| 显示花屏、颜色错乱 | 颜色格式不匹配 | 确认 LV_COLOR_DEPTH、LTDC Layer 格式、面板数据位宽一致 |
| 显示偏移且有重影 | 时序参数不对 | 对照屏幕 datasheet 认真核对 HBP/HFP/VBP/VFP |
| 触摸完全没有反应 | I2C 地址错误或中断没触发 | 扫描 I2C 设备地址,检查 INT 中断是否有电平变化 |
| 触摸有反应但坐标反了 | 未做坐标映射 | 加打印定位原始坐标方向,做坐标变换函数 |
| 刷屏卡顿、动画掉帧 | 绘制缓冲放错了内存区域 | 把 LVGL 绘制缓冲挪到内部 AXI SRAM |
| 程序启动就 HardFault | SDRAM 未初始化或 heap 分配冲突 | 检查 FMC SDRAM 初始化顺序,确认 LVGL 堆与系统堆不重叠 |
| 点击按钮时有死区 | 触摸滤波器或去抖逻辑问题 | 在read_cb里加软件滤波,连续读几次坐标一致才上报 |
6.1 最关键的一条经验:先让裸机点灯,再谈移植
如果说这么多问题里最值得记住的,我个人体会是:移植 LVGL 前,一定要先把 LTDC 刷纯色、触摸芯片读坐标这两个底功能独立调通,再启用 LVGL。我之前在另一个项目里急着把 LVGL 接进来,结果画面花、触摸飘,排查了半天不知道是 LVGL 配置问题还是底层驱动问题。
后来我总结经验,每次移植都严格走三步:
- 先用 LTDC 在屏幕上刷一块纯色,确认显示通路正常。
- 然后写一个裸机触摸程序,把触摸坐标打印到串口,确认触摸通路正常。
- 最后才让 LVGL 接手,把显示和触摸都注册进去。
只要前两步都 OK,LVGL 移植的成功率基本就是 100%。如果你的时间有限,也一定要留出这一步的调试时间,省不掉的。
6.2 关于日志:调试期开着,发布前关掉
LVGL 的日志模块LV_USE_LOG在调试阶段非常有用,它能告诉你内存分配失败、渲染区域异常,甚至某些 API 使用错误。我当时在移植 GT911 驱动时,就是靠 LVGL 日志发现lv_indev_drv_register被调用了两次,导致事件被重复触发。
但日志模块在发布前必须关掉。因为 LVGL 的日志输出是通过串口的,每条日志的打印开销不小,而且在高频调用(比如每一帧都打印一次)时,会让实时性能大打折扣。发布版本把LV_USE_LOG设为 0,即可彻底关闭。
6.3 后续还可以这样扩展
这次移植完成后,如果你还有余力,可以考虑几个方向:
- 把 LVGL 跑在 FreeRTOS 上,用
lv_timer_handler作为独立任务,让 UI 线程与应用线程解耦。 - 引入 LVGL 的字体工具,用
lv_font_conv制作自己产品需要的汉字字库,目前中文显示是 LVGL 工程师问得最多的问题之一。 - 如果觉得界面制作效率太低,可以试试 SquareLine Studio 或 GUI Guider 这类所见即所得的 UI 设计工具,它们可以生成 LVGL 工程代码,后端直接对接手写驱动。
我自己在调完这套流程之后最大的感受是,STM32H743 这颗芯片的图形能力比想象中强得多,只要显存规划和驱动接口做对,LVGL 的流畅度和响应速度完全可以支撑一款正式产品的人机交互需求。整个移植流程里最花时间的部分往往不是写代码,而是反复核对那些即使填错一个数字也会导致全盘白屏的时序和地址参数。希望这篇文章能帮你少走这段弯路。