帧率与刷新率:LVGL的帧率概念、屏幕刷新率与动画流畅度的关系
做嵌入式UI,尤其是用LVGL做动画的时候,有两个词你肯定绕不开:帧率和刷新率。
很多人把它们混为一谈,觉得“屏幕刷新率60Hz,那动画就是60帧”。其实不是这么回事。我刚开始做项目时也踩过这个坑,以为硬件参数到位了,动画就一定流畅。结果呢?动画一跑起来,肉眼可见的卡顿。后来一查,LVGL的帧率和屏幕的刷新率根本就是两码事。
今天我们就来把这层窗户纸捅破。
1. 屏幕刷新率:硬件的“底牌”
屏幕刷新率,单位是Hz,指的是屏幕每秒钟更新画面的次数。常见的LCD屏是60Hz,也就是每16.67ms刷新一次。高刷屏有90Hz、120Hz,甚至更高。
这个参数是硬件决定的,你改不了。它就像一条高速公路的限速——最高就跑这么快。
关键点:屏幕刷新率是LVGL动画流畅度的上限。你的动画帧率再高,如果屏幕只有60Hz,那用户实际看到的画面最多也就是60fps。
2. LVGL的帧率:软件能跑多快
LVGL的帧率,单位是fps(Frames Per Second),指的是LVGL的绘图引擎每秒钟能完成多少次完整的渲染和刷新。
这个值取决于很多因素:
- MCU的主频和架构——算力够不够
- 内存带宽——显存读写快不快
- 渲染复杂度——动画里有多少对象、用了多少特效
- LVGL的配置——比如是否开启了部分刷新、是否用了DMA2D
说白了,LVGL的帧率是软件实际跑出来的成绩。它可能比屏幕刷新率低,也可能比屏幕刷新率高。
注意:如果LVGL的帧率低于屏幕刷新率,动画就会掉帧。如果LVGL的帧率高于屏幕刷新率,多余的帧会被丢弃,造成撕裂(tearing)。
3. 帧率与刷新率的匹配关系
理想的匹配是:LVGL帧率 = 屏幕刷新率,或者LVGL帧率是屏幕刷新率的整数倍(比如60Hz屏幕配30fps或60fps)。
我习惯用一个表格来对比这两种情况:
| 场景 | 屏幕刷新率 | LVGL帧率 | 用户感受 |
|---|---|---|---|
| 理想 | 60Hz | 60fps | 丝滑流畅 |
| 帧率不足 | 60Hz | 30fps | 明显卡顿,掉帧 |
| 帧率过高 | 60Hz | 90fps | 画面撕裂,浪费算力 |
| 整数倍匹配 | 60Hz | 30fps | 尚可接受(需开启垂直同步) |
为什么会撕裂?因为LVGL在屏幕还没完成当前帧刷新时,就把下一帧的数据写进了显存。屏幕读到一半,画面就“劈叉”了。
4. LVGL的帧率控制机制
LVGL本身有一套帧率控制逻辑。核心是 lv_timer_handler() 这个函数。
你想想看,LVGL的主循环里,每次调用 lv_timer_handler(),它会处理所有待执行的定时器任务,包括动画的更新。然后通过 lv_disp_flush_ready() 通知屏幕驱动刷新。
LVGL默认的帧率上限是 LV_DISP_DEF_REFR_PERIOD,单位是毫秒。默认值是30ms,对应大约33fps。
// lv_conf.h 中的配置
#define LV_DISP_DEF_REFR_PERIOD 30 // 默认30ms,约33fps
这个值可以调。如果你把周期改成16ms,理论上帧率就能跑到60fps。但前提是你的MCU和屏幕能扛得住。
我的经验:不要盲目追求高帧率。我曾经在一个STM32F429的项目上,把刷新周期从30ms改到16ms,结果CPU占用率直接飙到95%,其他任务全被饿死了。后来我老老实实调回30ms,配合部分刷新和DMA2D,动画反而更稳。
5. 帧率与动画流畅度的关系
动画流畅度,说白了就是人眼的感受。这里有个常识:
- 24fps:电影级,勉强能接受,但快速移动会模糊
- 30fps:嵌入式UI的及格线,大部分场景够用
- 60fps:丝滑,人眼基本感觉不到卡顿
- 120fps:极致,但嵌入式设备很少需要
但要注意,帧率只是其中一个维度。还有一个关键因素是帧间隔的稳定性。
什么意思?就是每一帧之间的时间间隔要均匀。如果一帧用了10ms,下一帧用了30ms,再下一帧又用了10ms,即使平均帧率是50fps,人眼也会感觉到“一顿一顿”的。这叫微卡顿(micro-stutter)。
我在排查掉帧问题时,经常用逻辑分析仪抓 lv_timer_handler() 的执行时间。如果发现执行时间忽长忽短,那问题大概率出在某个耗时的渲染操作上。
6. 核心知识体系
下面这张图,是我自己总结的帧率与刷新率的关系图。你看一眼就能明白整个逻辑链条。
7. 实际排查中的思路
当你发现动画掉帧时,第一步不是去优化代码,而是先搞清楚:瓶颈在硬件还是软件?
- 先确认屏幕刷新率——看屏幕数据手册,或者用示波器量一下VSYNC信号。如果屏幕只有30Hz,那再怎么优化LVGL也跑不到60fps。
- 再测量LVGL实际帧率——在
lv_timer_handler()前后加GPIO翻转,用逻辑分析仪抓周期。或者用LVGL自带的lv_disp_get_fps()接口。 - 对比两者——如果LVGL帧率远低于屏幕刷新率,那问题在软件。如果LVGL帧率高于屏幕刷新率,那要考虑开启垂直同步(VSYNC)或者限制帧率。
一个小技巧:我习惯在调试阶段,把LVGL的帧率信息打印到串口或者显示在屏幕角落。这样一跑动画,就能实时看到帧率变化。如果帧率突然掉到20fps以下,那肯定是有地方卡住了。
8. 避坑指南
最后,分享几个我踩过的坑:
- 别把屏幕刷新率当帧率用——屏幕60Hz不代表LVGL能跑到60fps。中间差着MCU的算力和渲染开销。
- 别盲目调高LVGL的刷新周期——调高意味着更短的渲染时间,CPU压力更大。如果MCU扛不住,反而会掉帧。
- 注意帧间隔的抖动——平均帧率50fps不代表每一帧都稳定。用逻辑分析仪抓一下,看看帧间隔是不是均匀的。
- 开启垂直同步(VSYNC)——如果屏幕支持,尽量开启。它能保证LVGL的帧和屏幕的刷新同步,避免撕裂。