(六)LVGL 8.3.11 移植:性能优化、抗锯齿与内存管理
目标:把"能跑"打磨成"跑得好"------帧率更高、边缘更顺、内存可控、长时间运行稳定。
1. 先量化,再优化:性能监控
调试期务必打开(量产关闭):
c
#define LV_USE_PERF_MONITOR 1 /* 右下角:FPS + CPU 占用 */
#define LV_USE_MEM_MONITOR 1 /* 左下角:LVGL 堆用量 + 碎片率 */
#define LV_USE_LOG 1
#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN
#define LV_LOG_PRINTF 1
经验参考值(F429 @180MHz,800×480 RGB565,模式三 + DMA2D):
| 场景 | 典型 FPS | CPU 占用 |
|---|---|---|
| 静态界面 | 30+(受刷新周期限制) | <10% |
lv_demo_widgets 滑动 |
25~40 | 40%~70% |
| 全屏动画 | 20~33 | 60%~90% |
| 未开 DMA2D 的同场景 | 减半以上 | 接近 100% |
优化顺序铁律:先看 Perf Monitor 定位瓶颈(渲染 CPU 高 → DMA2D/布局;FPS 低但 CPU 低 → flush 等待/刷新周期),不要凭感觉改。
2. DMA2D 硬件加速细节
2.1 它替 LVGL 做什么
lv_gpu_stm32_dma2d.c 接管了三类渲染原语:
| 原语 | 场景 | 收益 |
|---|---|---|
| 矩形填充 fill | 背景、纯色块、进度条 | 大 |
| Alpha 混合 blend | 圆角、阴影、半透明叠加 | 最大 |
| 颜色转换 copy | ARGB8888 图片 → RGB565 | 大 |
未命中 GPU 路径的操作(如抗锯齿文字边缘的逐像素混合)仍走软件渲染,属正常。
2.2 确认加速生效
lv_conf.h中LV_USE_GPU_STM32_DMA2D 1且LV_GPU_STM32_DMA2D_CMSIS_INCLUDE指向正确的 HAL 头;- 编译进
lvgl/src/gpu/lv_gpu_stm32_dma2d.c(8.3 的 src 全目录编译时自动包含); - 对比开关前后的 Perf Monitor CPU 占用,应有明显下降;
- 失效常见原因:DMA2D 时钟未使能、头文件宏名写错、把 9.x 的宏名抄到了 8.3。
2.3 F429 vs F7/H7 的 cache 差异
F429(Cortex-M4)没有 D-Cache,DMA2D 写完内存 CPU 立即可见,无需维护。若代码未来要移植到 F7/H7:
c
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
SCB_CleanDCache_by_Addr((uint32_t *)color_p, w * h * sizeof(lv_color_t));
#endif
flush 前清 D-Cache,避免 DMA2D 读到未回写的脏数据。
3. 抗锯齿与显示质量
3.1 LVGL 的抗锯齿体系
| 配置 | 作用 | 代价 |
|---|---|---|
LV_ANTIALIAS 1(默认开) |
圆角、圆弧、斜线边缘平滑 | 渲染时间 +10%~20% |
字体 bpp(转换器 --bpp) |
1/2/4/8 bit 每像素,bpp=4 是质量与体积的甜点 | Flash/SDRAM 占用随 bpp 翻倍 |
LV_USE_FONT_SUBPX |
子像素渲染,小字号更锐利 | 仅对特定排列屏有效,图片字库不适用 |
LV_USE_FONT_COMPRESSED |
字库压缩存储 | 解压耗时,Flash 紧张才开 |
LV_DPI 正确设置 |
影响默认间距/圆角尺寸的视觉比例 | 无 |
实用建议:
- 正文用 bpp=4 字体;超大标题字可 bpp=8;
- 圆角半径 ≤ 8px 时肉眼几乎无锯齿,大圆角才明显吃性能;
- 阴影(
LV_SHADOW_*)是 8.3 中最贵的样式之一,满屏阴影控件会显著掉帧------大阴影改用小半径或多层圆角矩形模拟。
3.2 图片质量
- 转换器输出
CF_TRUE_COLOR(带 alpha)质量最高;纯不透明图用CF_TRUE_COLOR_565省一半存储; - 避免运行时缩放图片(
lv_img_set_zoom是软件逐像素运算),分辨率在转换阶段就定好。
4. 内存管理:TLSF 堆的摆布艺术
4.1 LVGL 内存从哪来
LVGL 用内置 TLSF(Two-Level Segregated Fit)算法管理一块连续堆,对象、样式、定时器、事件、图片缓存全部分配于此。8.3 提供三种摆布方式:
c
/* 方式一:默认------内部静态数组,吃内部 SRAM */
#define LV_MEM_CUSTOM 0
#define LV_MEM_SIZE (64U * 1024U)
#define LV_MEM_ADR 0
/* 方式二:堆整体搬到 SDRAM(F429 + 32MB SDRAM 平台的推荐做法) */
#define LV_MEM_CUSTOM 0
#define LV_MEM_SIZE (1U * 1024U * 1024U) /* 想吃多大给多大 */
#define LV_MEM_ADR 0xD0200000U /* 避开两块帧缓冲后的空闲区 */
/* 方式三:完全接管------用编译器 malloc 或自研分配器 */
#define LV_MEM_CUSTOM 1
/* 需实现 lv_mem_alloc/lv_mem_free 映射到自己的分配器 */
方式二的关键是地址规划:

SDRAM 32MB @0xD0000000 建议分区:
0xD0000000 ~ 0xD00B7700 帧缓冲 FB1 (750KB)
0xD00B7700 ~ 0xD016EE00 帧缓冲 FB2 (750KB)
0xD0200000 ~ 0xD0300000 LVGL 堆(1MB,LV_MEM_ADR)
0xD0300000 起 图片解码缓存 / 用户数据 / 预留
关键细节解析
① 三种方式怎么选?
- 方式一(内部静态数组):不外挂 SDRAM 时的唯一选择,堆大小受 192KB SRAM 的硬约束;
- 方式二(
LV_MEM_ADR指定 SDRAM 地址):本平台推荐。不改 LVGL 源码、保留 TLSF 的确定性分配行为,只换地基。TLSF 的优势是分配/释放均为 O(1) 且时间确定,这对 UI 线程很重要------你不会希望某个界面跳转因为 malloc 的长路径而偶发卡顿; - 方式三(完全自定义):只在你需要把 LVGL 内存纳入统一的内存池管理(如带统计/越界检测的自研分配器)时才用,代价是放弃 TLSF 的确定性。
② LV_MEM_ADR 取 0xD0200000 的两个讲究
一是避让 :两块帧缓冲占了 0xD0000000 起约 1.43MB,堆起点必须在其后;取 0xD0200000(2MB 处)留了约 500KB 裕量,帧缓冲越界(比如日后改成 1024×600 屏)也不会立刻踩到堆。二是对齐:取 2MB 的整数倍边界,既方便肉眼核算地址,也满足任何 SIMD/DMA 对大块内存的对齐假设。写错地址的典型症状是"显示正常但 UI 数据莫名被改"或"界面正常但屏幕下半截花屏"------两个区域互相覆盖的结果,排障时先核对这张分区表。
③ 为什么碎片率(frag)比已用量(used)更能反映健康度?
TLSF 堆上频繁"创建-销毁"大小不一的对象,会把空闲内存切成越来越多不连续的小块:used 不高但 frag 很高时,一次稍大的分配(比如解码一张大图的缓冲)照样失败------这就是"内存明明够却分配失败"的经典现象。frag 持续爬升说明应用在反复创建销毁控件(见 4.3 的纪律),正确的修法是复用与隐藏,而不是无脑加大堆。
④ 优化为什么要"先开监控再动手"?
LV_USE_PERF_MONITOR 把"觉得卡"变成可定位的数据:FPS 低 + CPU 100% 说明瓶颈在渲染(开 DMA2D、减阴影圆角);FPS 低 + CPU 很低说明瓶颈在 flush 等待(检查是不是误用了轮询等待、TE 等待逻辑死等);FPS 高但操作卡顿则多半是 LVGL 任务被高优先级业务任务抢占。方向错了的优化(比如给 CPU 瓶颈的界面去折腾文件系统)一分钱收益都没有。
4.2 堆大小怎么定
| 应用规模 | 建议 LV_MEM_SIZE |
|---|---|
| 简单仪表盘(十几个控件) | 48~64KB |
| 多页面 HMI | 128~256KB |
lv_demo_widgets 全量 |
≥ 96KB 才能跑顺 |
| 大量图片解码 + FreeType | 512KB 起,直接放 SDRAM |
Mem Monitor 的 used 稳定在 60% 以下、碎片率 frag 低于 30% 是健康状态;碎片率持续爬升说明有泄漏或频繁创建销毁控件。
4.3 应用层内存纪律
- 切页用
lv_obj_del+ 新建,或单页面lv_scr_load_anim换屏后及时删除旧屏; - 大量重复控件优先复用(改属性而非删了重建);
lv_timer_create的定时器记得lv_timer_del;- 大图片不用时
lv_img_cache_invalidate_src释放解码缓存。
5. 刷新路径优化
- 缩小脏区域 :改动局部属性(文字、值)而不是重建控件;
lv_obj_invalidate尽量精确; - 隐藏优于删除 :暂时不用的元素
lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN),比重建便宜; - 动画节制 :
LV_DISP_DEF_REFR_PERIOD30ms 足够流畅,盲目调到 10ms 只会空转 CPU; - 不透明覆盖 :给底层容器设置不透明底色(
bg_opa = LV_OPA_COVER),减少多层混合; - 编译优化 :MDK
-O2/ GCC-O2+ 链接期优化;LVGL 的lv_conf.h里LV_USE_STDLIB_*保持默认即可。
6. 综合调优清单(按优先级)
| 优先级 | 动作 | 预期收益 |
|---|---|---|
| ★★★ | 全屏双缓冲 + LTDC 地址翻转(第 02 篇模式三) | 消除撕裂,拷贝归零 |
| ★★★ | LV_USE_GPU_STM32_DMA2D 1 |
CPU 占用降 30%~60% |
| ★★★ | LVGL 堆放 SDRAM(LV_MEM_ADR),内部 SRAM 留给 RTOS/栈 |
消除内存天花板 |
| ★★☆ | 编译 -O2;LV_DISP_DEF_REFR_PERIOD 30ms |
渲染提速 |
| ★★☆ | 图片预载 + LV_IMG_CACHE_DEF_SIZE 8~16 |
切页顺滑 |
| ★★☆ | 字体 bpp=4,范围裁剪到实际用到的字符 | Flash 省 50%+ |
| ★☆☆ | 阴影/大圆角克制使用,隐藏替代重建 | 个别卡顿场景改善 |
7. 长时间运行稳定性验证
lv_demo_stress()连续跑 24 小时;- Mem Monitor 碎片率曲线平稳,无单调爬升;
- 定时打印:
lv_mem_monitor_t mon; lv_mem_monitor(&mon);输出free_size与frag_pct; - FreeRTOS 下同时打印各任务栈水位;
- 最终验收:界面静置 24h 后触摸响应时间无劣化。
至此,一个可量产水准的 LVGL 8.3.11 HMI 平台就搭建完成了。