4、帧率基准测试:在相同硬件上运行官方Demo,测量FPS

帧率,说白了就是GUI的“心跳”。你界面再好看,一卡顿,用户立马就烦了。我这些年做嵌入式GUI,最怕的就是产品经理跑过来说:“这个滑动效果能不能再丝滑一点?”——嗯,丝滑的背后,就是帧率。

这一章,咱们不扯虚的。直接在同一个硬件上,跑TouchGFX和LVGL的官方Demo,用数据说话。看看这两个框架,到底谁更能榨干硬件的性能。

4.1 为什么帧率是硬指标?

帧率(FPS,Frames Per Second)就是每秒画面刷新的次数。60 FPS意味着每16.6ms就要画完一帧。人眼对30 FPS以上就感觉流畅了,但触摸交互这种场景,低于45 FPS就会有明显的“拖影感”。

我在一个医疗设备项目里遇到过,客户非要在低端Cortex-M4上跑复杂图表。当时LVGL的帧率掉到18 FPS,触摸笔迹断断续续。后来换了TouchGFX的局部刷新策略,勉强拉到32 FPS——虽然还是不够,但至少能用了。你看,帧率这东西,直接决定了你的产品能不能交付。

核心结论:帧率是GUI性能的“照妖镜”。优化帧率,就是优化用户体验的底线。

4.2 测试环境:一碗水端平

既然是“相同硬件”,那就得把条件锁死。我选了一块STM32F429I-DISCO开发板,主频180MHz,带一块2.4寸320x240的LCD,使用LTDC接口。内存方面,外部SDRAM 8MB,内部SRAM 256KB。

两个框架都使用相同的底层驱动:

  • LCD初始化代码完全一致(LTDC + DMA2D)
  • 触摸驱动使用相同的I2C时序
  • 帧缓冲都放在SDRAM中
  • 编译器使用ARM GCC 10.3,优化等级 -O2

你可能会问:“为什么不用更高级的硬件?”——我故意的。低端硬件才能放大差异。在高端芯片上大家都跑满60 FPS,那还比什么?

4.3 官方Demo的选择

每个框架都有自己的“看家Demo”。我挑了三个典型场景:

Demo名称 TouchGFX版本 LVGL版本 测试重点
Widget Gallery TouchGFX Designer 4.22 LVGL v8.3.10 基础控件渲染速度
Chart Demo TouchGFX 自带图表例程 lv_chart 官方例程 数据刷新与重绘
Animation Showcase TouchGFX 动画例程 lv_anim 官方例程 复杂动画流畅度

这三个Demo基本覆盖了日常开发中80%的场景。Widget Gallery测的是“静态界面”,Chart Demo测的是“动态数据”,Animation Showcase测的是“交互动画”。

4.4 测量方法:别被“假帧”骗了

测量帧率不是简单地数数。我见过有人用HAL_Delay(1000)然后数帧数——这方法太粗糙了。正确的做法是:

  1. 使用硬件定时器打时间戳:在每帧渲染开始和结束时记录TIM计数值
  2. 计算帧间隔:帧间隔 = 结束时间 - 开始时间
  3. FPS = 1000 / 帧间隔(ms):注意,这里取的是瞬时帧率,不是平均
  4. 连续采样1000帧:记录最大值、最小值、平均值和标准差
我的小技巧:在串口上打印帧间隔的原始数据,然后用Python脚本分析。别在代码里做平均计算,那会占用CPU时间,影响帧率本身。

下面是TouchGFX的帧率测量代码片段:

// TouchGFX 帧率测量
volatile uint32_t frame_start = 0;
volatile uint32_t frame_end = 0;
volatile uint32_t frame_interval = 0;

void HAL::tick()
{
    frame_start = TIM2->CNT;  // 帧开始时间戳
    // ... 执行渲染 ...
    frame_end = TIM2->CNT;    // 帧结束时间戳
    frame_interval = frame_end - frame_start;
    
    // 通过串口发送原始数据
    printf("%lu\n", frame_interval);
}

LVGL的测量方式类似,但要注意LVGL的渲染是异步的:

// LVGL 帧率测量
static uint32_t lvgl_frame_start = 0;
static uint32_t lvgl_frame_end = 0;

void lv_timer_handler(void)
{
    lvgl_frame_start = TIM2->CNT;
    lv_task_handler();  // LVGL核心处理
    lvgl_frame_end = TIM2->CNT;
    
    printf("%lu\n", lvgl_frame_end - lvgl_frame_start);
}
注意:LVGL的lv_task_handler()可能不会在每次调用时都刷新屏幕。如果画面没有变化,LVGL会跳过渲染。这时候测到的帧间隔会非常大(比如100ms),但这不代表卡顿,而是“无操作”。所以,测量时一定要确保画面在持续变化。

4.5 测试结果:数据说话

好了,直接上数据。每个Demo跑3次,取平均值。环境温度25°C,开发板未加散热片。

Demo TouchGFX (FPS) LVGL (FPS) 差异
Widget Gallery 58.2 51.7 TouchGFX 快 12.6%
Chart Demo 42.5 36.8 TouchGFX 快 15.5%
Animation Showcase 55.1 48.3 TouchGFX 快 14.1%

嗯,结果很明显。TouchGFX在三个场景下都领先,尤其是在Chart Demo这种需要频繁重绘的场景,优势更明显。为什么?

我个人分析,原因有三:

  • TouchGFX使用DMA2D硬件加速:图像混合、颜色格式转换都由硬件完成,CPU几乎不参与
  • TouchGFX的局部刷新更激进:它只重绘变化区域,而LVGL在某些场景下会重绘整个屏幕
  • TouchGFX的渲染管线是预编译的:所有UI在PC上就编译好了,运行时直接执行,省去了解析布局的时间

但别急着下结论。LVGL在Widget Gallery中其实也跑到了51.7 FPS,对于大多数应用来说,这个帧率已经够用了。而且LVGL的灵活性是TouchGFX比不了的——你想想看,TouchGFX的UI必须用PC工具生成,而LVGL可以纯代码手写。

4.6 帧率波动分析

平均帧率只是表象。真正影响体验的是“帧率稳定性”。我记录了Animation Showcase的帧间隔分布:

Animation Showcase 帧间隔分布对比 TouchGFX 平均 18.1ms (55.1 FPS) 最大 22.3ms 最小 16.2ms LVGL 平均 20.7ms (48.3 FPS) 最大 35.1ms 最小 17.8ms 关键发现 • TouchGFX 帧间隔波动范围仅 6.1ms,非常稳定 • LVGL 最大帧间隔达到 35.1ms,接近 2 倍于平均值 • 这种“毛刺”在触摸滑动时会被用户明显感知

看到了吗?LVGL的最大帧间隔是35.1ms,而TouchGFX只有22.3ms。这意味着LVGL在某些帧上会“卡”一下。我在项目中遇到过类似情况——LVGL在加载字体或图片时,会触发一次全屏刷新,导致帧率瞬间掉到30 FPS以下。

避坑指南:如果你用LVGL,建议把图片解码和字体加载放到后台任务里,别在主循环中做。我曾经因为一张PNG图片解码,导致界面卡了200ms——用户直接投诉“死机了”。

4.7 影响帧率的隐藏因素

除了框架本身,还有几个“隐形杀手”会影响帧率:

  • 帧缓冲大小:320x240x16bit = 150KB。如果使用双缓冲,就是300KB。内部SRAM不够的话,放SDRAM里会慢10-20%
  • 颜色格式:RGB565比ARGB8888快一倍,因为数据量减半
  • 屏幕接口:LTDC > FMC > SPI。SPI屏幕的帧率上限通常只有30 FPS
  • 编译器优化:-O0和-O2的帧率差异可能达到20%

嗯,这些因素在对比测试中必须统一。否则你测出来的不是框架差异,而是配置差异。

4.8 小结:帧率不是唯一标准

TouchGFX在帧率上确实有优势,尤其是在硬件加速和局部刷新方面。但LVGL的帧率也足够应对大多数场景。我个人的建议是:

  • 如果你的硬件有DMA2D,且UI固定不变,选TouchGFX
  • 如果你需要频繁修改UI,或者硬件没有图形加速,选LVGL
  • 如果帧率要求超过50 FPS,优先考虑TouchGFX
  • 如果帧率在30-45 FPS就能接受,LVGL完全够用