最近在 STM32H757XIH6 上实跑了一块 6.86 寸 MIPI 竖屏,LVGL V9.4 的动画和触摸都完整过了一遍。先说总判断:这套方案真正的分水岭不在 LVGL 代码本身,而在 MIPI DSI 屏幕能不能被稳定点亮、触摸控制器和竖屏旋转映射有没有接对,以及高分辨率下帧率、带宽、内存这三笔账算没算清。下面按我实际调试的顺序拆开讲,能帮你少走不少弯路。
1. STM32H757XIH6 + MIPI 竖屏到底适合什么场景
1.1 为什么说这不是普通 SPI 屏方案
STM32H757XIH6 不是普通裸机点灯用的 MCU。它带 M7 + M4 双核,带 LTDC,带 MIPI DSI Host。型号里的 XIH6 主要对应封装、Flash 和温度等级,选型时只要确认芯片外设包含 DSI 就可以。这类芯片做 GUI 方案,最大的价值在于可以直接驱动 MIPI 接口的中大尺寸屏,而不是只能接 SPI 屏或者并口 RGB 屏。
MIPI 竖屏在 6.86 寸这种条型面板上很常见,适合智能家居控制面板、桌面摆件、工控 HMI、充电桩、电梯楼层显示这类产品。相比 SPI 屏,MIPI 屏带宽高很多,可以跑更大的分辨率、更复杂的动画;相比 RGB 并口屏,MIPI 排线少,抗干扰和连接可靠性也更好。
但注意,MIPI 不是“接上就能亮”。它是整套协议链路,需要芯片端有 DSI Host,屏端有对应的驱动 IC,中间还要配置 lane、时钟、初始化序列。很多人第一次拿到 40P MIPI 排线就以为跟 SPI 屏一样能直接被点亮,结果卡了好几天。
1.2 适合谁,不适合谁
如果你手上已经有一块 6.86 寸或类似规格的 MIPI 模组,想用 STM32H7 系列把 LVGL 跑起来,这篇文章可以给你一个完整参考。如果你正在从 LVGL V8 往 V9 迁移,碰到动画、显示回调、触摸输入接口变化,也有参考价值。
反过来,如果你做的是成本极度敏感、出货量很大的小玩具,STM32H757 这颗料成本不低,MIPI 屏也不会比普通 SPI 屏便宜。如果产品要求极低功耗、电池供电、长期待机,H7 的功耗也不会太好看。还有一类情况要特别提醒:不要拿手机拆机屏来做,手机屏没有完整初始化代码,排线定义也特殊,硬接非常痛苦。
2. 先把硬件链路理清楚:DSI、LTDC、触摸谁管什么
2.1 MIPI DSI 不是一根线,是协议链路
在 STM32H7 上,MIPI DSI 不是独立生成图像的外设。真正负责往 framebuffer 里画图的是 LTDC,DSI Host 负责把 LTDC 输出的像素按 DSI 协议转成差分信号发给屏幕。LVGL 负责把内容画进 framebuffer,LTDC 从 framebuffer 读像素送给 DSI,DSI 再发给屏。任何一环时序不对,画面就出不来。
理解这一点很重要。很多人遇到白屏,第一反应是查 LVGL,但实际问题可能出在 DSI 的 HS 信号没有起来,或者 LTDC 时序和屏规格书对不上。
MIPI DSI 协议本身涉及不少概念:数据 lane、时钟 lane、LP 低功耗模式、HS 高速模式、命令模式和视频模式。搜索 mipi 协议、mipi dsi 相关文章时,你会发现大部分资料都在讲 D-PHY 的电气特性和时序,这些对写底层驱动有参考意义,但不一定直接帮你点亮屏幕。你更需要的是屏厂提供的 datasheet 和初始化代码。
2.2 6.86 竖屏的分辨率、带宽和内存估算
6.86 寸竖屏常见分辨率在 480x1280、720x1280 这一档,具体以模组规格书为准。先说内存账:720x1280 单帧用 16bit 色就是 1.84MB,双缓冲接近 3.7MB;如果用 24bit 色,单帧 2.76MB,双缓冲 5.5MB。H7 内部 RAM 虽然不小,但要放完整双帧缓冲很紧张,所以外挂 SDRAM 基本是硬需求。
再说带宽账。MIPI DSI 每 lane 的数据率可以由像素时钟乘以每像素 bit 数再除以 lane 数估算。分辨率越高、刷新率越高、颜色格式越大,DSI 时钟就越高,D-PHY 信号质量和 PCB 走线问题就越容易暴露。实测时如果屏幕闪烁、花屏、跑一段时间亮点漂移,不要只怀疑 LVGL,先看 DSI 时钟是否接近上限。
建议:
- 第一次调通用 RGB565 或 RGB666,降低带宽压力。
- 刷新率不用一上来就锁定 60Hz,先跑 45Hz 或 50Hz。
- 双缓冲建议放在 SDRAM,用 DMA2D 做搬运和格式转换。
- 不要拿手机上 4 lane 的高分屏直接接 H757,STM32 的 DSI lane 数量有限,很多屏根本不是为 MCU 场景设计的。
2.3 原生 DSI 和外挂转换芯片不是一回事
网上经常看到“ssd2828 转 MIPI 格式步骤图解”这类内容。SSD2828 是一颗 RGB/SPI 转 MIPI 的桥接芯片,如果你的 MCU 没有 DSI Host,只能用 RGB 接口屏,确实可以靠它转出去。但那是另一条链路,转换芯片的 PLL、时钟、初始化命令都要单独配,和 STM32H757 原生 DSI 的配置逻辑不一样。
本文按 H757 原生 DSI 方案来讲,但排查思路可以通用:先确认电源和背光,再确认信号链路,最后才查上层软件。转换方案的参数不要直接套到原生 DSI 上,反之也一样。
3. 工程环境与 LVGL V9.4 移植准备
3.1 从哪里开始配置:CubeMX 或手写驱动
建议直接用 STM32CubeMX 生成基础工程,工具版本不要太老,太老版本对 H7 系列的支持和时钟树处理不够好。生成工程后,LVGL V9.4 以源码方式加入工程,不要用旧版本例程里的 lv_conf.h 硬套,V9 和 V8 的配置项结构已经有不少变化。
CubeMX 里我建议按下述顺序配置:
- SDRAM 控制器,先研究外扩内存能不能用。
- MIPI DSI:配置 lane 数量、像素格式、DSI 时钟。
- LTDC:配置有效显示区域、前后肩、同步信号。
- DMA2D:做图像搬运和颜色格式转换。
- 触摸芯片用的 I2C。
- 背光、触摸复位、触摸中断对应的 GPIO。
顺序不是随便排的。先有内存,LVGL 才有 buffer;先有 LTDC 时序,才知道 DSI 像素时钟往哪走。如果配置顺序反过来,后面查问题会非常混乱。
时钟树是重点。DSI PLL 的输入输出、LTDC 像素时钟、AHB 时钟之间要匹配。很多白屏和闪烁问题不是屏坏了,而是时钟树里 DSI PLL 根本没按要求配置。第一次调试时不要追求最高刷新率,先按一个偏保守的像素时钟跑,确认画面稳定后再提升。
3.2 lv_conf.h 和显示 buffer 怎么设
LVGL V9.4 的 lv_conf.h 要打开关键配置:
LV_COLOR_DEPTH:常见设 16,和屏的 RGB565 对应。LV_MEM_SIZE:LVGL 自己管理的内存池,动画多、控件多就调大一点。LV_DEF_REFR_PERIOD:默认刷新周期,越小越吃 CPU,但不是越小越好。LV_USE_LOG:调试时打开,出问题先看日志。
显示 buffer 是另一个经常出问题的地方。我一般先把功能跑通,使用一两个屏幕面积 1/10 左右的部分缓冲区,然后观察动画和触摸效果。如果画面撕裂或者刷新有明显闪烁,再考虑双缓冲。
在 LVGL V9 里,创建 buffer 和显示设备的方式和 V8 不完全一样。V9 里很多接口从lv_disp_xxx改成了lv_display_xxx,编译报错时先看头文件里的新函数名,不要凭记忆改。
4. 点亮屏幕:从屏厂初始化到 LVGL 上屏
4.1 初始化序列按屏厂给,不要照抄网络模板
MIPI 屏的初始化序列是第一个大坑。市面上 40P MIPI 模组常用的驱动 IC 有 ST7701S、ST7262、JD9365 等,每家命令差异很大,同一颗 IC 不同分辨率初始化序列也不一样。所以不要从网上随便抄一段初始化数组就往屏里写,必须以屏厂提供的代码为准。
初始化命令一般通过 DSI 的 LP 模式发送,用 HAL 层的长短包写函数。代码结构类似下面这样:
static void lcd_panel_init(void) { /* 1. 退出睡眠 */ HAL_DSI_ShortWrite(&hdsi, 0, DSI_SHORT_PKT0_WRITE_P0, LCD_DSI_EXIT_SLEEP_MODE, 0); HAL_Delay(120); /* 2. 按屏厂提供的初始化数组逐条写入 */ /* 这里的 init[] 必须换成你自己屏的序列 */ const uint8_t init[] = { 0x00, 0x00, 0x01, 0x11, 0x00, 0x00, 0x02, 0x35, 0x00 }; HAL_DSI_LongWrite(&hdsi, 0, DSI_LONG_PKT_WRITE_P0, (uint8_t *)init, sizeof(init)); /* 3. 开启显示 */ HAL_DSI_ShortWrite(&hdsi, 0, DSI_SHORT_PKT0_WRITE_P0, LCD_DSI_SET_DISPLAY_ON, 0); }上面只是演示结构,实际命令必须以屏厂给的为准。写完后先不要接 LVGL,直接往 framebuffer 里填纯色,看看屏幕有没有反应。
4.2 先看画面,再接 LVGL
点亮屏幕这个过程,我强烈建议拆成两步。
第一步:关闭 LVGL 对底层的影响,往 framebuffer 写一整块颜色,比如全红、全绿、全蓝。如果屏幕能稳定显示纯色,说明电源、背光、DSI、LTDC、framebuffer 地址这一层没问题。
第二步:再接入 LVGL,让 LVGL 往 framebuffer 写内容,验证 flush 回调能不能正常工作。
很多人把 LVGL 和底层驱动一次性全部写好,出问题后根本分不清是屏幕没点亮,还是 LVGL 没刷新。先做纯色验证,能省掉大量无效排查。
如果写纯色没有反应,不要急着看 LVGL。检查顺序是:背光是否打开,DSI 有没有正常从 LP 切到 HS,LTDC 时钟和屏规格书是否一致,framebuffer 地址是不是落在了 SDRAM 的有效范围。
4.3 flush 回调和 DMA2D
LVGL 负责把脏区域画到 buffer,flush 回调负责把这块区域同步到屏。V9 的回调签名比 V8 更明确:
static void lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) { /* 把 LVGL 画好的区域拷贝到屏的 framebuffer */ lcd_copy_area(area->x1, area->y1, area->x2, area->y2, px_map); /* V9 用 lv_display_flush_ready,V8 是 lv_disp_flush_ready */ lv_display_flush_ready(disp); }如果编译报错,大概率是接口名不匹配,看当前 LVGL 版本头文件里的实际函数名。lcd_copy_area 内部建议用 DMA2D 做搬运,减少 CPU 拷贝时间。但注意,DMA2D 不是万能的,它只是让搬运更快,不能解决重绘面积过大和逻辑卡顿的问题。
5. 触摸接入:I2C 时序、中断和坐标映射
5.1 触摸芯片的初始化与读点
6.86 寸电容触摸屏一般走 I2C,常见触摸控制器有 GT911、FT5x06、CST 系列。第一步先做 I2C 扫描,确认芯片地址,再读厂商 ID 确认芯片型号。不要直接按网上的地址硬写,地址可能因为 I2C 引脚上拉电阻配置不同而改变。
触摸芯片通常有复位引脚和中断引脚。复位后要延迟一段时间,等芯片内部初始化完成。中断引脚用来告诉 MCU 有触点,但真正的坐标数据还是通过 I2C 寄存器读取。
LVGL 里的读取回调大致这么写:
static void touch_read(lv_indev_t *indev, lv_indev_data_t *data) { TouchPoint pt; if (touch_i2c_get_point(&pt) == 0) { >lv_anim_t a; lv_anim_init(&a); lv_anim_set_var(&a, obj); lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_x); lv_anim_set_values(&a, 0, 200); lv_anim_set_duration(&a, 400); lv_anim_set_path_cb(&a, lv_anim_path_ease_out); lv_anim_start(&a);注意,lv_anim_exec_xcb_t在新版本里可能要求强转,编译报错时先看当前版本头文件里的回调类型定义。V9 中很多显示相关接口改成lv_display_xxx,动画相关接口则还是lv_anim_xxx。如果拿 V8 的例程直接编译,经常是一堆函数名不匹配。
透明度动画在界面上也很常见,可以在动画回调里驱动lv_obj_set_style_opa,也可以直接用 style transition 实现过渡效果。不过透明度会触发整个控件区域重绘,低配置下不要堆太多全屏透明叠加层。
6.2 动画卡顿先从重绘面积和 buffer 找原因
动画卡顿是 LVGL 项目里最常见的抱怨。但问题往往不在 LVGL,而在重绘策略和硬件资源。
我实测时的排查顺序:
- 先确认是不是一个单条动画都卡。单条动画卡,先看 buffer 大小和刷新周期。
- 多条动画同时跑时才卡,看动画涉及的区域是否重叠、是否触发大面积重绘。
- 背景透明、大张 PNG 图频繁变化,会显著增加重绘面积。
- 串口日志输出量大,也会影响刷新节奏,调试时开日志,测性能时关掉。
如果你的屏是 720x1280,建议至少使用双缓冲配合 DMA2D。单缓冲在部分刷新模式下,动画速度快时容易看到撕裂感。双缓冲能缓解撕裂,但内存占用接近 4MB,必须确保 SDRAM 可用。
另一个经验是:不要把多个控件同时做位移和透明度动画。在低主频或者高分辨率下,同时出现路径、透明、缩放三种动画,CPU 会被拖得很明显。动画数量多时,优先简化动效,或者把动画帧率降下来,而不是盲目调高主频。
7. 实测结果与踩坑记录
7.1 按什么标准判断“跑通了”
“能显示一张静态图”不算完整跑通。我的判断标准是这样的:
- 上电后屏幕能稳定进入 LVGL 主界面,不闪不花。
- 主界面上的按钮触摸单击、双击、长按都能正确响应。
- 页面切换动画、列表滚动动画、数值动态刷新动画能连续跑,不掉帧到肉眼可见卡顿。
- 背光亮度调节正常,睡眠唤醒后画面能恢复。
- 连续运行几个小时没有出现画面冻结、触摸漂移、内存耗尽。
这个清单看起来简单,但每一项背后都可能藏着底层问题。比如动画跑久了内存耗尽,很可能是 LV_MEM_SIZE 设置太小或者某个界面没有正确释放对象;触摸漂移很可能是坐标没做校准或者触摸芯片寄存器配置不对。
7.2 我这次遇到的主要问题
第一次点亮时是白屏。检查顺序走了一遍,最后发现 DSI PLL 配置没有按屏的像素时钟算好,HS 信号起不来。把时钟树重新按屏规格书配置后,画面才正常。
第二个问题是触摸方向。屏是竖屏,但触摸芯片原始坐标和显示方向差了 90 度,点左上角实际识别到右下角。后来在触摸读取层做了坐标交换,没有去改 LVGL rotation,问题解决。
第三个问题是动画卡顿。一开始只有一个屏幕面积 1/10 的缓冲区,页面切换加了透明渐入,加上串口调试日志没关,动画肉眼可见掉帧。改成双缓冲、关掉日志、减少透明叠加后,流畅度明显改善。
这三个问题都不是 LVGL 本身的 bug,而是底层配置和资源策略问题。越是看起来像上层问题,越要先确认底层链路是否稳定。
8. 从白屏到触摸漂移的排查顺序
8.1 白屏或者无显示
白屏时先确认背光有没有亮。背光亮但屏幕没有画面,问题在显示链路;背光也不亮,先查背光供电和 PWM 控制引脚。
继续往下查:
- DSI 是否完成初始化,有没有从 LP 模式切到 HS 模式。
- LTDC 的时序参数和屏规格书是否一致,包括水平同步、垂直同步、前肩、后肩。
- framebuffer 地址是否正确,指针有没有落到有效的 SDRAM 区域。
- DSI PLL 输出频率是否在屏支持范围内。
- LVGL 是否真正调用了 flush 回调,回调里有没有把数据送到 framebuffer。
这个顺序不要乱。很多时候复位引脚没拉好、SDRAM 没初始化成功,就会表现成白屏,其实和 LVGL 无关。
8.2 花屏、颜色不对、位置偏移
花屏和颜色不对,最常查的是像素格式。屏是 RGB666,你却喂了 RGB565,颜色一定不对。LTDC 和 DSI 的像素格式、framebuffer 的 bpp、LVGL 的 LV_COLOR_DEPTH,三者要一致。
位置偏移或画面错位,优先查 LTDC 时序和 framebuffer 每行字节数。framebuffer