(六)LVGL 8.3.11 移植:性能优化、抗锯齿与内存管理

(六)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 确认加速生效

  1. lv_conf.hLV_USE_GPU_STM32_DMA2D 1LV_GPU_STM32_DMA2D_CMSIS_INCLUDE 指向正确的 HAL 头;
  2. 编译进 lvgl/src/gpu/lv_gpu_stm32_dma2d.c(8.3 的 src 全目录编译时自动包含);
  3. 对比开关前后的 Perf Monitor CPU 占用,应有明显下降;
  4. 失效常见原因: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_ADR0xD0200000 的两个讲究

一是避让 :两块帧缓冲占了 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. 刷新路径优化

  1. 缩小脏区域 :改动局部属性(文字、值)而不是重建控件;lv_obj_invalidate 尽量精确;
  2. 隐藏优于删除 :暂时不用的元素 lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN),比重建便宜;
  3. 动画节制LV_DISP_DEF_REFR_PERIOD 30ms 足够流畅,盲目调到 10ms 只会空转 CPU;
  4. 不透明覆盖 :给底层容器设置不透明底色(bg_opa = LV_OPA_COVER),减少多层混合;
  5. 编译优化 :MDK -O2 / GCC -O2 + 链接期优化;LVGL 的 lv_conf.hLV_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_sizefrag_pct
  • FreeRTOS 下同时打印各任务栈水位;
  • 最终验收:界面静置 24h 后触摸响应时间无劣化。

至此,一个可量产水准的 LVGL 8.3.11 HMI 平台就搭建完成了。


相关推荐
smallerxuan13 小时前
(八)LVGL 8.3.11 移植与应用实战:图片解码 —— 格式、缓存与性能
lvgl移植·lvgl8.3.11移植·stm32f429移植lvgl
alive9031 年前
STM32移植LVGL8.3 (保姆级图文教程)
stm32·单片机·嵌入式硬件·stm32f407·lvgl8.3·lvgl移植