1. 课程导入

各位好,我是老张。做嵌入式GUI有些年头了,踩过的坑比吃过的盐还多。今天咱们开始聊TouchGFX性能优化,第一讲先搭个框架。

很多人一上来就调代码,结果调了半天发现方向错了。为什么?因为没搞清楚瓶颈在哪。我刚开始做TouchGFX项目时也犯过这毛病——界面卡了,二话不说去优化渲染算法,折腾两周,帧率纹丝不动。后来一查,是DMA配置没开。你说冤不冤?

所以这一章,咱们先把「性能优化」这件事拆清楚:瓶颈怎么找、目标怎么定、指标怎么看。地基打牢了,后面每一招才能打在七寸上。

1.1 TouchGFX性能瓶颈分析

TouchGFX的渲染管线,说白了就是一条流水线:应用层生成绘制命令 → 框架处理 → 驱动层搬运像素 → 屏幕显示。任何一个环节堵了,整体帧率就掉。

我个人习惯把瓶颈分成三类:

  • CPU瓶颈:MCU算力不够,绘制命令处理不过来。常见于复杂矢量图形、大量文字渲染、频繁的alpha混合。
  • 内存瓶颈:帧缓冲或缓存区不够用,导致频繁刷写或降级。比如双缓冲切到单缓冲,或者局部刷新策略失效。
  • 总线/外设瓶颈:数据从MCU到屏幕的通道太窄。SPI/QSPI/LTDC接口速度、DMA配置、显存带宽,都可能成为短板。

核心观点:80%的性能问题,根源不在TouchGFX框架本身,而在硬件配置和驱动层。别一上来就怀疑框架。

我在一个项目里遇到过:同样的UI,在STM32F429上跑30fps,换到F746反而掉到20fps。查了两天,发现是LTDC时钟配置错了,像素时钟频率没跟上。嗯,这种坑,你光看代码是看不出来的。

1.2 优化目标设定

目标怎么定?不是越高越好。你想想看,一个工业HMI界面,60fps和30fps用户根本看不出区别,但60fps可能要多花30%的CPU资源。值吗?

我建议按应用场景分档:

场景 目标帧率 CPU占用率上限 说明
消费电子(手表/手环) 50-60fps 40% 动画流畅,响应快
工业HMI 25-30fps 30% 稳定优先,留余量给业务
车载仪表 30-60fps 50% 安全关键,不能掉帧
低功耗IoT屏 15-20fps 20% 省电优先,局部刷新

定目标时记住一条铁律:先满足业务需求,再追求极致性能。我曾经为了把帧率从45fps提到60fps,把CPU占用率干到了85%,结果主控任务频繁超时,系统直接死机。得不偿失。

我的习惯:项目启动时就定好性能预算——帧率、CPU、内存各留20%余量。后面迭代时,只要不超预算,就不动优化。省下来的时间,多睡会儿觉不好吗?

1.3 性能评估指标

指标就三个:FPS、CPU占用率、内存占用。但怎么测、怎么解读,有讲究。

1.3.1 FPS(帧率)

TouchGFX自带了FPS计数器,在TouchGFXConfiguration.cpp里可以打开。但我建议别只看平均值——峰值和谷值更重要。一个界面平时跑55fps,但切换页面时掉到15fps,用户感知到的就是「卡」。

怎么测?我一般用两种方法:

  • 软件打点:在Screen::tick()里记录时间戳,算帧间隔。简单,但会引入少量开销。
  • 硬件示波器:拉一个GPIO,在VSYNC中断里翻转电平。用示波器看周期,最准,不干扰系统。
// 软件打点示例
static uint32_t last_tick = 0;
static uint32_t frame_count = 0;

void Screen::tick() {
    frame_count++;
    uint32_t now = HAL::getInstance()->getCPUTicks();
    if (now - last_tick > 1000) { // 每秒统计一次
        float fps = frame_count * 1000.0f / (now - last_tick);
        // 输出或记录
        frame_count = 0;
        last_tick = now;
    }
}

1.3.2 CPU占用率

这个指标容易被忽略。很多人只看帧率,觉得帧率够了就行。但CPU占用率高了,其他任务(通信、传感器、控制逻辑)就没时间跑了。

测量方法:

  • RTOS自带统计:FreeRTOS的uxTaskGetSystemState()可以拿到每个任务的运行时间占比。
  • 空闲任务钩子:在空闲任务里计数,反推CPU占用率。轻量,够用。

注意:TouchGFX的渲染任务通常跑在最高优先级。如果CPU占用率超过60%,建议检查是否有不必要的重绘——比如静态背景每帧都刷,或者动画循环里做了大量计算。

1.3.3 内存占用

嵌入式环境内存金贵。TouchGFX的内存消耗主要在三块:

  • 帧缓冲:单缓冲、双缓冲还是局部缓冲,差别很大。480x272@16bit,单缓冲就要260KB。
  • 缓存区:字体缓存、图像缓存、动画缓存。用多少,看你的UI复杂度。
  • 堆栈:动态分配的对象、容器、字符串。这部分容易泄漏。

我建议在main()里加一段内存统计代码,跑完所有UI场景后打印峰值占用。这样能快速发现内存泄漏或过度分配。

// 内存统计示例(基于FreeRTOS heap)
extern uint8_t ucHeap[];
extern const uint32_t xTotalHeapSize;

void print_memory_usage() {
    uint32_t free = xPortGetFreeHeapSize();
    uint32_t min_free = xPortGetMinimumEverFreeHeapSize();
    // 输出到串口或日志
}

1.4 知识体系总览

下面这张图,是我自己梳理的TouchGFX性能优化知识体系。每次做新项目前,我都会对着它过一遍,看看哪些环节可能出问题。

TouchGFX性能优化知识体系 优化目标设定 FPS(帧率) CPU占用率 内存占用 CPU瓶颈 内存瓶颈 总线/外设瓶颈 渲染算法优化 缓存策略 DMA加速 硬件加速 ...更多 先定目标 → 再测指标 → 定位瓶颈 → 选择手段 每一轮优化后,回到指标重新评估,形成闭环

这张图的核心逻辑就一句话:先定目标,再测指标,定位瓶颈,选择手段。每一轮优化后,回到指标重新评估,形成闭环。别指望一步到位,优化是个迭代过程。

1.5 本章小结

这一章咱们把性能优化的「总开关」拧开了。你记住三件事:

  1. 瓶颈分三类:CPU、内存、总线。别一卡就怀疑框架,先查硬件配置。
  2. 目标要务实:按场景定帧率和CPU预算,留余量,别追求数字好看。
  3. 指标要测准:FPS看谷值,CPU看峰值,内存看泄漏。工具和方法选对,数据才有意义。

一个小建议:从今天开始,每个TouchGFX项目都建一个「性能基线文档」。把初始的FPS、CPU、内存数据记下来。后面每做一次优化,对比基线看效果。我自己的项目里,这个文档救过我好几次——有一次优化后帧率没变,但CPU占用率翻倍了,要不是有基线数据,根本发现不了。