4. 内存带宽优化:别让数据搬运拖垮你的动画
做嵌入式UI,尤其是跑TouchGFX动画的时候,我经常跟团队说一句话:CPU算力不是瓶颈,内存带宽才是。你想想看,一个60fps的动画,每秒钟要在屏幕上刷新60次,每次都要搬运几百KB甚至几MB的数据。如果带宽不够,再漂亮的动画也会变成幻灯片。
这一章,我们就来聊聊怎么把内存带宽的每一分钱都花在刀刃上。
4.1 显存 vs 系统内存:谁在拖后腿?
先搞清楚两个概念:显存(VRAM)和系统内存(SDRAM)。在TouchGFX里,帧缓冲区(Frame Buffer)通常放在SDRAM里,而显存是MCU内部或外置的图形加速器专用内存。
带宽瓶颈通常发生在两个地方:
- CPU → SDRAM:写入像素数据
- LCD控制器 → SDRAM:读取像素数据刷新屏幕
我遇到过最典型的情况:一个800×480的屏幕,用ARGB8888格式,每帧数据量是800×480×4 = 1.536MB。如果刷新率是60Hz,那每秒就要从SDRAM读取92.16MB的数据。这还没算上CPU写入和DMA传输的开销。
关键结论:带宽是共享资源。LCD控制器、CPU、DMA都在抢SDRAM的带宽。谁抢得多,谁就卡。
4.2 像素格式转换:RGB565 vs ARGB8888
这是最直接、最有效的优化手段。说白了,就是减少每个像素占用的位数。
| 格式 | 每像素位数 | 800×480 每帧大小 | 60fps 带宽需求 | 适用场景 |
|---|---|---|---|---|
| ARGB8888 | 32 bit | 1.536 MB | 92.16 MB/s | 需要透明通道、高色彩精度 |
| RGB565 | 16 bit | 0.768 MB | 46.08 MB/s | 大多数UI场景,无透明需求 |
| L8(索引色) | 8 bit | 0.384 MB | 23.04 MB/s | 纯色块、图标、文字 |
看到没?从ARGB8888降到RGB565,带宽直接砍半。我个人的习惯是:能用RGB565绝不用ARGB8888。除非你真的需要每个像素的透明度,否则别浪费那16位。
小技巧:TouchGFX的Image和Text控件都支持设置像素格式。在资源转换时,可以指定输出格式。比如在.touchgfx文件里,把format改成RGB565,就能自动压缩。
4.2.1 避坑指南:格式转换的代价
我曾经犯过一个错误:为了省带宽,把所有图片都转成RGB565,但UI里用了大量半透明遮罩。结果呢?每次渲染都要在运行时把RGB565转成ARGB8888才能做Alpha混合,反而更慢了。
所以,格式转换不是免费的。如果你需要透明效果,要么用ARGB8888,要么用硬件Alpha混合(如果MCU支持)。
4.3 Frame Buffer Partial Update:只刷新变化的部分
这是TouchGFX里我最喜欢的功能之一。它的核心思想很简单:屏幕没变的地方,就别刷新。
传统的做法是:每帧都重新绘制整个帧缓冲区,然后整个刷到LCD上。但很多时候,UI只有一小块区域在变化,比如一个进度条在动,或者一个数字在跳。其他99%的区域都是静态的。
Partial Update就是只更新变化的那一小块矩形区域。
4.3.1 怎么用?
在TouchGFX里,启用Partial Update需要两步:
- 硬件支持:LCD控制器必须支持局部刷新(比如通过设置显示窗口坐标)。
- 软件配置:在
TouchGFXConfiguration.cpp里,设置frameBufferUpdateRect回调。
代码示例:
// 在HAL层注册回调
void HAL::setFrameBufferUpdateRect(const Rect& rect) {
// 告诉LCD控制器只刷新rect区域
LCD_SetWindow(rect.x, rect.y, rect.width, rect.height);
LCD_StartTransfer();
}
// 在TouchGFX初始化时启用
hal->setFrameBufferUpdateCallback(setFrameBufferUpdateRect);
嗯,这里要注意:Partial Update不是万能的。如果你的UI每帧都在全屏变化(比如全屏视频),那它就没用。但大多数嵌入式UI,比如仪表盘、菜单、设置界面,都是局部变化为主。
4.3.2 性能提升有多大?
我拿一个实际项目举例:一个汽车仪表盘,转速表指针每帧都在动,但背景是静态的。指针区域大概只有100×100像素。
- 全屏刷新:800×480 = 384,000像素/帧
- Partial Update:100×100 = 10,000像素/帧
- 带宽节省:97.4%
你看,带宽需求直接降到原来的2.6%。这就是Partial Update的威力。
注意:Partial Update会增加CPU的调度开销。因为每次刷新都要计算脏矩形(Dirty Rect)。如果变化区域太多、太碎,反而可能得不偿失。我建议:脏矩形数量控制在5个以内,超过就退化为全屏刷新。
4.4 综合策略:把带宽省到极致
在实际项目中,我通常会把三种手段组合使用:
- 像素格式:默认用RGB565,只有透明控件用ARGB8888
- Partial Update:只刷新变化区域,静态背景不碰
- DMA2D加速:用硬件DMA搬运数据,不占CPU
这三板斧下来,带宽需求通常能降低70%-90%。
4.5 知识体系图:内存带宽优化全景
下面这张图,是我自己总结的优化路径。你可以把它当成一个检查清单:
4.6 实战建议:从测量开始
最后,我想分享一个个人习惯:优化之前,先测量。用逻辑分析仪或者MCU的DMA计数器,看看实际带宽用了多少。我曾经见过一个项目,开发者凭感觉优化了半天,结果发现瓶颈根本不在带宽,而在CPU中断响应。
所以,先测再改,用数据说话。这才是工程师该有的样子。