2、刷新机制精讲:深入理解LVGL的脏矩形刷新机制

各位做嵌入式UI的同行,咱们今天来聊聊LVGL最核心的机制之一——脏矩形刷新。说实话,我见过太多人把LVGL跑得卡顿,第一反应就是“CPU太弱”、“内存不够”。但很多时候,问题出在刷新策略上。

我自己刚接触LVGL时也踩过这个坑。一个简单的仪表盘界面,就几个表盘加数字,跑在STM32F429上居然掉帧。后来一查,发现每次刷新都是全屏重绘。嗯,这能不卡吗?

2.1 什么是脏矩形?

脏矩形,英文叫 Dirty Area,说白了就是“屏幕上需要重新绘制的区域”。LVGL不会傻乎乎地每次刷新整个屏幕,它只更新那些“变脏了”的部分。

你想想看,一个按钮按下去,只有按钮本身和周围可能受影响的区域需要重绘。屏幕其他99%的区域纹丝不动,干嘛要重绘它们?

核心思想:只重绘变化的部分,不动不变的部分。这是LVGL性能优化的第一原则。

2.2 脏矩形刷新流程

LVGL的刷新流程其实就三步:标记脏区 → 合并脏区 → 刷新脏区。我画了张图,你看一眼就明白了。

LVGL 脏矩形刷新流程 步骤1:标记脏区 lv_obj_invalidate() 步骤2:合并脏区 区域合并优化 步骤3:刷新脏区 flush_callback() 关键:脏区合并后,LVGL只刷新最终合并的矩形区域,而不是逐个刷新 脏矩形合并示例 脏区A 脏区B 合并后 合并脏区(包含A+B)

2.3 标记脏区的时机

什么时候会触发脏区标记?我总结了几种常见情况:

  • 对象属性变化:比如调用 lv_label_set_text()、lv_obj_set_pos()、lv_obj_set_size() 等
  • 对象状态变化:按钮按下、释放,复选框选中/取消
  • 对象创建/删除:新增控件或移除控件
  • 手动调用:你主动调用 lv_obj_invalidate(obj)

小技巧:如果你批量修改多个对象属性,可以先用 lv_obj_set_local_style_prop() 配合 LV_STATE_USER_1 临时禁用刷新,改完后再统一启用。这样能避免多次触发脏区标记。

2.4 脏区合并策略

LVGL的脏区合并策略其实挺聪明的。它维护一个脏区列表,每次有新脏区加入时,会尝试合并相邻或重叠的区域。

合并策略主要有三种:

策略 描述 适用场景
直接合并 将两个脏区合并成一个更大的矩形 脏区相邻或重叠时
保留独立 脏区相距较远,不合并 屏幕两端各自有变化时
全屏刷新 脏区数量超过阈值,直接刷全屏 大量控件同时变化时

这里有个坑要注意。我曾经在一个项目里,界面上有十几个独立的进度条同时更新。每个进度条变化都会产生一个脏区,结果脏区列表越积越多,合并策略直接退化成全屏刷新。性能瞬间崩了。

避坑指南:当脏区数量超过 LV_INV_BUF_SIZE(默认值在 lv_conf.h 中配置)时,LVGL会放弃合并,直接刷新整个屏幕。如果你的界面有大量独立变化的控件,记得调大这个值,或者重新设计布局。

2.5 如何查看脏区

LVGL提供了一个调试功能,可以直观地看到脏区范围。在 lv_conf.h 中开启:

#define LV_USE_PERF_MONITOR 1
#define LV_USE_MEM_MONITOR  1

开启后,屏幕上会显示一个性能监视器,其中就包含脏区信息。我个人习惯在开发阶段一直开着这个功能,能第一时间发现哪些区域在频繁刷新。

2.6 减少刷新区域的实战技巧

光说不练假把式。下面这几个技巧,是我在实际项目中验证过的:

  1. 使用容器分组:把相关的控件放在同一个容器里。容器本身就是一个脏区边界,容器内的变化不会影响到容器外的区域。
  2. 避免透明背景:透明背景意味着LVGL需要重绘背后的所有内容。能不用透明就不用透明。
  3. 合理使用 lv_obj_set_hidden():隐藏对象时,LVGL会标记隐藏区域为脏区。如果你只是临时隐藏,可以考虑用 lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN) 配合 lv_obj_invalidate(obj) 手动控制。
  4. 批量更新:用 lv_anim_start() 做动画时,动画回调里只更新最终值,不要在回调里频繁调用 lv_obj_invalidate()。

核心原则:脏区越小,刷新越快。每次刷新前,问问自己:这次真的需要重绘整个控件吗?还是只重绘变化的部分就够了?

2.7 实际案例分析

我记得有一次做智能家居面板,界面左侧是温度曲线,右侧是设备列表。温度曲线每秒更新一次,设备列表偶尔变化。

最初的设计是:温度曲线更新时,整个左侧面板都标记为脏区。结果每次刷新都要重绘整个曲线区域,包括背景网格、坐标轴、标签等。

优化方案很简单:把曲线数据单独放在一个子容器里,只让这个子容器标记脏区。背景网格和坐标轴用 lv_canvas 绘制一次后就不再变化。这样每次刷新只重绘曲线数据部分,脏区面积减少了70%。

嗯,这就是脏矩形刷新的精髓——精确控制刷新范围。

2.8 总结

脏矩形刷新是LVGL性能优化的基石。理解它,你就能从根源上减少不必要的重绘。记住三个关键点:

  • 脏区越小越好
  • 合并策略要理解
  • 调试工具要用起来