SMARTWATCH 学习手册有 C 基础 · 嵌入式从零开始
支线一 / 07 · LVGL 8.3.11

把刷新做稳:性能、DMA 改造与排错

先测量绘图和传输,再理解局部刷新、缓冲归还与异步状态,最后完成整套界面的功能验收。

前置:完成上一篇「让界面持续正确:数据、定时器与生命周期」。各篇独立示例按说明替换,不把多份页面创建循环叠在一起。

界面功能正确以后,再找刷新慢在哪一段

“感觉卡”可能来自绘图、SPI、应用长任务或处理函数调用不及时。先把一帧分成可观察的阶段:业务更新对象,LVGL 绘制,驱动发送,再通知完成。没有区分这些阶段就改 DMA,可能既没解决瓶颈,又增加并发问题。

本仓库当前使用十行单缓冲和轮询 SPI。先给它建立基线:固定页面内容、SCK、缓冲大小,分别观察小数值变化与整屏切换。不要一边改动画、一边改频率,再把结果归因于一个设置。

绘图与传输决定一批像素占用的时间
双缓冲只是创造重叠工作的条件;是否真正重叠要看端口与 DMA 完成流程。

先算一条无法绕过的线速限制

RGB565 每像素两字节。240×240 全屏是 115200 字节,在 20 MHz 一位一时钟的理想传输下约为 46.08 ms,这还没有画图、命令与软件开销。DMA 减少 CPU 搬数据的工作,不会凭空减少线上的位数。

估算一个矩形的纯传输时间

数值范围只为模型计算,不表示面板支持该频率。真实最高频率查模组电气与时序条件。

为什么只改一个数字,也可能画了很多像素?

LVGL 会记录需要重绘的区域。文字尺寸改变、透明叠层、阴影、渐变、滚动和大动画,都可能扩大实际重绘范围。局部更新不仅是最后少发几个字节,也要让对象结构与变化方式支持它。

先减少不必要工作:页面初始化一次,更新已有对象;值与状态没变时不重复写同样文字;固定数字区域,避免每次字符宽度变化影响整个布局;对不可见页面按业务需要降低更新频率。不能为了性能保留失效数据或跳过过期检查。

观察点记录什么能说明什么
handler 周围的时间差调用前后毫秒计数,或调试计时UI 处理占用多久;毫秒计数可能看不清很短动作
刷新回调收到的 area区域坐标、像素数和调用次数是否小变化触发了大面积绘制
CS 与 SCK 波形实际传输长度与间隙线速是否达到配置、软件是否插入长等待
内存与对象数量稳定运行与反复切页后的变化是否重复创建、忘记清理或过度缓冲

测量代码也会影响时间。不要在每个像素处理里输出长串口日志;先记录计数和时间,降低频率汇总。逻辑分析仪与示波器用于不同层次的观察,截图上的理想模型不能替代真实波形。

再考虑 DMA:先把“谁还能用缓冲区”写清楚

轮询版 flush 在传完之后返回,所以随后的 ready 表示传输不再占用缓冲。DMA 启动后主流程先返回,DMA 后面还会读取内存;这段时间缓冲区归传输使用,LVGL 不能提前覆盖它。

可靠的异步移植至少需要保存当前驱动指针、正在发送的区域与数据引用、忙状态,以及完成和错误结果。下面先按状态理解完整过程,不给一段只换函数后缀的伪完整驱动。

状态当前允许做什么退出条件
空闲接收新的一批刷新数据完整保存本次信息并启动
命令阶段发送区域命令,设置片选与 D/C确认命令已完成,才能切像素数据
像素发送中保持缓冲与控制线所需状态,不重复占用 SPI真正的 SPI 完成通知或错误
收尾结束片选,按策略处理结果,归还缓冲完成通知只发一次,再接受下一批
故障停止新提交,清理硬件、数据与事务状态明确重试或放弃策略,不能永久扣住库缓冲

STM32U5 普通非循环 SPI DMA 完成路径还可能依赖 SPI 的 EOT 中断收尾,SPI 章已经解释。DMA 通道搬完与最后一位出引脚并非同一时刻,不能在前一个时刻就释放片选并 ready。

当前 GC9A01 驱动的命令和坐标使用局部变量,CS、D/C 在函数中快速切换,也有没有上报错误的 void 接口。真正异步化要一起调整这些约定。把 GC9A01_DrawBitmap 内部的调用改成 _DMA,然后马上返回 ready,会导致未发完的数据被覆盖。

双缓冲什么时候才有帮助?

一块正在发送,另一块可以供 CPU 绘图,才有可能形成流水。必须有正确的忙状态和缓冲归还规则;如果底层仍一直阻塞等待,两块数组可能只增加 RAM。即使形成重叠,总速度仍受较慢阶段限制,并非简单变成两倍。

把显示移植的常见故障按层排查

现象先分开验证典型原因
一直黑屏GC9A01 纯色是否正常,再检查刷新调用底层初始化、字节传输,或端口未注册
纯色正常,LVGL 颜色乱直接颜色字节与库缓冲字节逐项比较LV_COLOR_16_SWAP 与八位 SPI 输出未统一,或重复交换
第一帧后不动flush 是否 ready、handler 是否继续缓冲未归还,或主流程被长工作阻塞
DMA 时偶发花屏像素存储是否被改写、CS/D/C 是否提前变生命周期或传输状态竞争
切页越久内存越少创建、事件、定时器、动画和资源销毁重复创建与遗漏清理
动画突然加速或很慢只核对一套时基,观察 handler 间隔tick 重复累计,或以循环次数代替时间
圆屏角落内容不见矩形地址与可见面板范围并不一定是驱动丢像素

项目 GC9A01_WritePixels 的长度参数虽然写成 uint32_t,底层 HAL 的 Size 仍是 uint16_t。大的 RGB565 图像必须分块;当前十行批次为 4800 字节,不能据此推断一次整屏 115200 字节调用也正确。

先完成顺序清晰的轮询版本,再保存同样的纯色、窗口、数据有效性和销毁测试,作为以后 DMA 改造的对照。当前教程没有改写固件或运行生成代码;优化结论需要实际设备上的相同条件测量。

支线毕业练习

  1. 完成数值卡片与设置页,物理按键能切换或确认,焦点和状态明确。
  2. 模拟有效数据、无效、过期,显示与业务含义一致,不依赖真实传感器也能复现。
  3. 反复进入退出页面,说明对象、定时器与动画如何停止或复用。
  4. 列出从对象更新到 GC9A01 像素写入的完整路径,算出一次矩形传输长度。
  5. 记录一个固定场景的刷新面积、传输耗时和 RAM,再只改变一个因素比较。

完成后再进入 App、AI 或固件升级支线时,界面能够承担数据入口和状态反馈,底层驱动、图形库与业务职责也已经分开。

检查理解

两块绘图缓冲一定比一块快吗?

参考与项目源码

按仓库实际的 LVGL 8.3.11 与本地头文件组织接口。教学图与网页模型不是设备实测;源码引用均使用 GitHub 文件页。完整索引见 资料来源。