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性能优化知识体系。每次做新项目前,我都会对着它过一遍,看看哪些环节可能出问题。
这张图的核心逻辑就一句话:先定目标,再测指标,定位瓶颈,选择手段。每一轮优化后,回到指标重新评估,形成闭环。别指望一步到位,优化是个迭代过程。
1.5 本章小结
这一章咱们把性能优化的「总开关」拧开了。你记住三件事:
- 瓶颈分三类:CPU、内存、总线。别一卡就怀疑框架,先查硬件配置。
- 目标要务实:按场景定帧率和CPU预算,留余量,别追求数字好看。
- 指标要测准:FPS看谷值,CPU看峰值,内存看泄漏。工具和方法选对,数据才有意义。
一个小建议:从今天开始,每个TouchGFX项目都建一个「性能基线文档」。把初始的FPS、CPU、内存数据记下来。后面每做一次优化,对比基线看效果。我自己的项目里,这个文档救过我好几次——有一次优化后帧率没变,但CPU占用率翻倍了,要不是有基线数据,根本发现不了。