1. 动画基础概念:帧率、刷新率、屏幕撕裂与垂直同步
做嵌入式GUI开发,说白了就是在跟「时间」打交道。你写的每一帧画面,最终都要在屏幕上呈现给用户。如果时间没卡好,画面就会卡顿、撕裂,用户一眼就能看出来。这一章,我们先把这些基础概念掰扯清楚。
1.1 帧率(FPS)—— 你的程序能跑多快
FPS,全称 Frames Per Second,每秒能渲染多少帧。我习惯把它叫做「程序的心跳」。你写了一个动画,每秒钟能画出 30 张图,那 FPS 就是 30;能画出 60 张,就是 60。
但这里有个坑:FPS 高不代表画面就流畅。为什么?因为屏幕还有个刷新率在等着你。
1.2 刷新率(Hz)—— 屏幕的固定节奏
刷新率是屏幕硬件决定的。常见的 60Hz 意味着屏幕每秒刷新 60 次,每次刷新间隔约 16.67ms。这个时间是固定的,你改不了。
你想想看,如果你的程序 FPS 是 60,屏幕刷新率也是 60,那理论上每一帧都能完美对齐。但现实没这么理想——FPS 经常波动,而刷新率纹丝不动。这就引出了下一个问题。
| 参数 | 帧率 (FPS) | 刷新率 (Hz) |
|---|---|---|
| 决定者 | 软件(你的代码 + GPU/CPU) | 硬件(屏幕面板 + 驱动 IC) |
| 是否固定 | 波动,随场景复杂度变化 | 固定,除非你主动切换 |
| 典型值 | 30、60、120 | 60、90、120、144 |
| 影响 | 决定动画的「流畅感」 | 决定画面的「闪烁感」 |
1.3 屏幕撕裂(Tearing)—— 不同步的后果
屏幕撕裂长什么样?就是画面中间出现一条错位的「断层」,上半张图和下半张图对不上。我在做智能手表项目时第一次遇到这问题,当时还以为是屏坏了。
原因很简单:屏幕正在刷新第 N 行像素时,你的程序把显存里的内容换成了下一帧。结果屏幕上半截是旧帧,下半截是新帧,中间就裂开了。
1.4 垂直同步(V-Sync)—— 让两队对齐
V-Sync 就是解决这个打架问题的。它的做法很简单:让程序等屏幕把当前帧显示完,再开始画下一帧。具体来说,屏幕每扫完一帧,会发一个「垂直消隐」信号(VBlank)。程序收到这个信号,才允许更新显存。
这样做的好处是:永远不会撕裂。坏处是:如果程序画一帧的时间超过了 16.67ms(60Hz 下),那下一帧就得等两个 VBlank,FPS 直接掉到 30。这就是为什么有些游戏开了 V-Sync 反而卡。
// 伪代码:V-Sync 的典型实现
while (1) {
wait_for_vblank(); // 等待垂直消隐信号
swap_buffers(); // 交换前后缓冲区
render_next_frame(); // 开始渲染下一帧
}
1.5 四者的关系——一张图说清楚
下面这张图展示了帧率、刷新率、屏幕撕裂和 V-Sync 之间的逻辑关系。你可以把它当作本章的知识地图。
1.6 嵌入式环境下的特殊考量
在 PC 上,V-Sync 通常由显卡驱动和操作系统管理。但在嵌入式系统里,这些全得你自己写。我做过几个项目,总结出三条经验:
- 双缓冲是标配: 一个缓冲区给屏幕读,一个给程序写。VBlank 到来时交换。没有双缓冲,V-Sync 基本没法用。
- 小心 LTDC 控制器: 很多 MCU 内置的 LTDC(LCD-TFT 显示控制器)支持硬件 VBlank 中断。记得使能它,别自己用定时器模拟。
- 帧率不要硬追刷新率: 如果屏幕是 60Hz,你的 FPS 稳定在 30 也比在 40~60 之间剧烈波动体验好。人眼对「卡顿」比「低帧率」更敏感。
1.7 本章小结
帧率是你的程序能跑多快,刷新率是屏幕能刷多快。两者步调不一致,就会撕裂。V-Sync 通过等 VBlank 信号来对齐它们,代价是可能掉帧。在嵌入式环境里,你得自己实现这套机制,而且要考虑功耗和 CPU 占用。