帧率

1. 介绍

1.1 帧率定义

帧率指单位时间内屏幕显示的帧数,思澈方案采用2 秒内平均帧数作为统计标准,直接反映 UI 交互的流畅度(通常 60Hz 为理想流畅标准,对应每帧渲染耗时≤16.7ms)。

1.2 核心影响因素

帧率表现由两大环节共同决定,任一环节耗时过长均会导致卡顿:

  1. 页面渲染耗时:LVGL 图形库处理控件布局、图像绘制、数据计算的时间;

  2. LCD 送数时间:CPU/GPU 将渲染完成的帧数据传输至 LCD 显示屏的时间。

2. 刷新周期协同机制

LVGL 刷新周期与 LCD TE 周期的协同是保证帧率稳定的基础,二者需合理配置以避免画面撕裂或资源浪费。

2.1 LVGL 刷新周期

配置方式:在config中设置,例如目标帧率 60Hz 时,配置周期为 16ms(1000ms/60≈16.7ms);

作用:决定图形库多久触发一次渲染任务,是 UI 更新的 “调度时钟”。

2.2 LCD TE 周期

  • TE 信号定义:LCD 完成一帧显示后发出的同步脉冲,标志着下一帧可开始传输数据,是避免画面撕裂的核心信号;

  • 协同原则:

    • 优先配置:LVGL 刷新周期与 TE 周期保持一致(如均为 16ms),确保渲染与显示节奏匹配;

    • 兼容配置:若需统一驱动适配,可设置 LVGL 刷新周期小于 TE 周期(如 10ms),避免因渲染延迟错过 TE 同步点。

3. 帧率关键影响因素解析

3.1 控件与渲染效率

  • 控件开销:依赖软件处理的控件(arc、label、chart)会显著增加渲染耗时,尤其 chart 包含大量小尺寸子控件时,递归遍历成本极高;

  • 控件数量:LVGL 采用递归方式处理控件树,控件总数过多(如单页>50 个复杂控件)会导致递归耗时占比超 30%。

3.2 图像资源负载

  • 图片体积过大:高分辨率、带透明度的图片会占用更多 GPU 算力;

  • 旋转图片低效

3.3 线程与数据操作

  • Flash 读写阻塞:UI 线程直接操作 Flash 会导致渲染任务被中断(单次读写耗时可达毫秒级);

4. 帧率优化实战方案

4.1 硬件加速与渲染优化

  • 启用硬件加速:升级至Solution最新版本(≥ V2.3),arc、label 控件支持硬件加速后,渲染效率提升约 50%;

  • 快照替换高耗控件:将复杂控件(如多子控件组合、动态图表)初始化后转为快照(snapshot),后续渲染直接使用图片,减少递归与计算开销。

4.2 页面与资源拆分

  • 长页面动态加载: 使用lvsf_multlist控件实现分页加载,仅渲染当前可视区域内容,避免一次性处理海量控件。参考示例:app_mainmenu_list.capp_tileview.c

  • 页面初始化延迟:on_resume回调中优先初始化当前可视区域内容,其余部分延后 2-3 个渲染周期加载(如一级页面仅先初始化中心页,上下左右页延迟加载)。

4.3 图像与缓存配置

  • 旋转图片优化:

    • 将旋转图片预加载至 PSRAM 缓存(NAND 方案默认已缓存);

  • 缓存资源分配:

    • 字体缓存配置 400-600KB,提升文本渲染命中率;

    • 加大图片的缓存,特别是NAND方案,减少换入换出

4.4 NAND 方案专项优化

  1. 推荐优先启用builtin_res配置,直接访问图片资源,避免文件系统遍历开销;

  2. 如果采用文件系统的方案,将图片按应用分类存放至对应目录,减少找图时间

  3. GIF 背景缓存:内存充足时使用lv_agif_dec_create_comm接口缓存完整 GIF 资源,避免帧切换时重复读取。

4.5 一级页面专项优化

一级页面的框架介绍参见平铺应用框架。代码参考app_tileview.c。 -预加载子页面: 配合延迟激活配置,预加载所有子页面,左右滑动时无需重新初始化,参考app_tileview.c注释。

5. LCD送数

送数时长,以QSPI为例:

  • QSPI 是4个数据线, 在RGB565模式下 一个像素点(16bit)数据需要16/4个时钟周期

  • 送一帧全屏数据耗时(us) = 图形库宽 * 图形库高 * 每个像素需要的时钟周期 / QSPI速率

  • 如16bit的410x494的屏,在48M的速率下:t = 410 x 494 * 4 / 48 000 000 = 16.87(ms),在这种条件下,帧率就不能跑60hz了,会导致lcd自刷新时,数据还未送完出现撕裂

6. 调试方法

6.1 抓点工具sysview

使用方法参见文档Systemview工具抓点

6.2 调试指令

以下的finsh指令不带传参的都是发一次开启,再发一次关闭。

  1. 查看每个刷新周期耗时:task_time

    • 发送一次仅打印每个周期task handle的耗时

    • 发送两次则在一的基础上打印indev 以及refr 的耗时

    • 发送四次则关闭打印

  2. 查看文本的refr耗时:ft_time 返回ft_time: 1表示设置成功

进入文本页面时会打印该文本花费的时间以及文本长度(小于1ms的不打印):lv_label_set_text: ft time, 4(ms) size 10

  1. 查看字体的log:ft_log

  2. 查看每个页面的msg handle(start/resume/pause/stop)耗时:app_sche_print_perf_tick 1

  3. 查看一级页面msg handle(start/resume/pause/stop)耗时:tileview_fwk

  4. 查看一级页面拍照耗时:trans_anim

  5. 调试打印时间的接口

  • 毫秒级的可以直接使用rt_tick_get

  • 微秒级使用cpu_get_hw_us

统计某个行为的耗时,直接使用上面的接口计算该行为前后的时间差即可