1. 课程导入
做嵌入式UI开发,动画调优是个绕不开的坎。我刚开始接触LVGL时,总觉得动画卡顿是硬件不行,后来才发现——很多时候是我们没搞懂LVGL的刷新机制。这一章,咱们先把地基打牢。
1.1 帧率概念:别被「60fps」骗了
帧率,说白了就是屏幕每秒刷新多少次。LVGL里默认是60fps,也就是每16.6ms刷新一帧。但这里有个坑——60fps不代表你的动画一定能跑到60fps。
我在一个STM32F429的项目上遇到过:UI跑起来只有20fps,但硬件明明能跑60fps。查了半天,发现是渲染任务把CPU占满了。你想想看,帧率是目标,不是结果。
关键概念:
- 目标帧率:你设置的刷新间隔(如60fps)
- 实际帧率:硬件真正能跑到的帧数
- 掉帧:实际帧率低于目标帧率,动画就会卡顿
帧率不是越高越好。我建议根据硬件选:
| 硬件平台 | 推荐帧率 | 说明 |
|---|---|---|
| 低端MCU(如STM32F1) | 30fps | CPU资源有限,跑60fps反而容易掉帧 |
| 中端MCU(如STM32F4/H7) | 50-60fps | 带硬件加速,可以跑满 |
| 高端MPU(如i.MX RT) | 60fps+ | 双核或GPU加持,可以更高 |
我的习惯:开发初期先设30fps,等UI稳定了再往上调。这样能快速定位性能瓶颈。
1.2 LVGL刷新机制:它到底怎么画屏的?
LVGL的刷新机制,我画了张图帮你理解:
LVGL的刷新机制,核心就一句话:只刷新变化的部分。这叫「脏区域刷新」。你动了一个按钮,LVGL不会重绘整个屏幕,只重绘按钮那一块。
嗯,这里要注意:lv_task_handler() 是LVGL的主循环函数。你必须在主循环里定期调用它,LVGL才能处理动画和刷新。我见过有人把它放在中断里调,结果系统直接崩了——千万别这么干。
我曾经踩过的坑:在一个项目里,我把 lv_task_handler() 放在了一个高优先级任务里,结果低优先级的触摸任务一直抢不到CPU,UI直接死机。后来我把主循环放在中等优先级,才解决问题。
1.3 lv_timer vs lv_anim:到底用哪个?
很多新手分不清 lv_timer 和 lv_anim。我刚开始也迷糊过。简单说:
- lv_timer:定时器,每隔固定时间执行一次回调。适合周期性任务,比如轮询传感器、更新状态栏。
- lv_anim:动画引擎,自动处理插值和回调。适合做平滑动画,比如移动、缩放、渐变。
我画了个对比表,一目了然:
| 特性 | lv_timer | lv_anim |
|---|---|---|
| 用途 | 周期性任务 | 平滑动画 |
| 自动插值 | ❌ 需要手动实现 | ✅ 自动计算中间值 |
| 回调频率 | 固定间隔 | 每帧调用 |
| 性能开销 | 低 | 中(需计算插值) |
| 适用场景 | 状态轮询、数据更新 | 移动、缩放、透明度变化 |
举个例子。你想让一个按钮每隔500ms闪烁一次,用 lv_timer:
// lv_timer 示例:按钮闪烁
lv_timer_t *timer = lv_timer_create(btn_blink_cb, 500, btn);
static void btn_blink_cb(lv_timer_t *t) {
lv_obj_t *btn = (lv_obj_t *)t->user_data;
static bool visible = true;
visible = !visible;
lv_obj_add_flag(btn, visible ? LV_OBJ_FLAG_HIDDEN : 0);
}
但如果你想让按钮平滑移动到另一个位置,用 lv_anim:
// lv_anim 示例:按钮平滑移动
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_var(&a, btn);
lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_x);
lv_anim_set_values(&a, 0, 200); // 从x=0移动到x=200
lv_anim_set_time(&a, 1000); // 持续1秒
lv_anim_set_path_cb(&a, lv_anim_path_ease_out); // 缓出效果
lv_anim_start(&a);
我的建议:能用 lv_anim 就别用 lv_timer 手撸动画。LVGL的动画引擎已经帮你做了插值和帧率控制,自己写定时器做动画,很容易出现帧率不稳、卡顿的问题。
1.4 性能调优的第一把钥匙
理解了帧率、刷新机制和定时器/动画的区别,你就拿到了性能调优的第一把钥匙。我个人习惯,拿到一个新项目,先做三件事:
- 测帧率:用
lv_disp_get_inactive_time()或外接逻辑分析仪,看实际帧率 - 看脏区域:打开LVGL的调试模式,看脏区域是不是太大
- 检查主循环:确保
lv_task_handler()没有被阻塞
这三步走完,80%的性能问题都能定位到。剩下的,就是具体优化了——那是后面章节的事。
本章核心要点:
- 帧率是目标,不是结果。实际帧率受硬件和渲染负载影响
- LVGL采用脏区域刷新,只重绘变化部分
lv_timer适合周期性任务,lv_anim适合平滑动画- 性能调优第一步:测帧率、看脏区域、查主循环