4. 显存优化:片内SRAM vs 片外SDRAM/PSRAM,显存分配策略与DMA传输优化
显存怎么放,放哪里,这问题我几乎在每个项目里都要纠结一遍。说白了,嵌入式GUI的流畅度,一半看CPU,另一半就看显存怎么折腾。
你想想看,一个800×480的RGB565帧缓冲,算下来就是800×480×2 = 768KB。要是换成1024×600,直接飙到1.2MB。很多MCU片内SRAM总共才512KB或1MB,你一个显存就吃掉大半,剩下的给谁用?
所以,显存放片内还是片外,怎么分配,怎么搬运,这就是本章要聊的核心。
4.1 片内SRAM vs 片外SDRAM/PSRAM:谁更适合做显存?
先看一张对比表,我直接说结论:没有绝对的好坏,只有合不合适的场景。
| 特性 | 片内SRAM | 片外SDRAM | 片外PSRAM |
|---|---|---|---|
| 访问速度 | 极快(1~2个CPU周期) | 较快(通常几十ns,但需刷新) | 中等(类似SRAM时序,但略慢) |
| 容量 | 小(通常≤2MB) | 大(16MB~512MB常见) | 中等(4MB~64MB常见) |
| 功耗 | 低(无刷新) | 较高(需定期刷新) | 低(类似SRAM) |
| 引脚占用 | 无额外引脚 | 多(地址+数据线,通常≥40pin) | 少(SPI/QSPI接口,4~8pin) |
| 实时性 | 确定性强,无等待 | 有延迟,刷新期间可能阻塞 | 较好,但受SPI时钟限制 |
| 典型场景 | 小屏、低分辨率、实时性要求极高 | 大屏、复杂UI、多图层 | 中等分辨率、引脚受限、需中等容量 |
我个人习惯是:能放片内就放片内。但现实往往不允许。比如我做过一个HMI项目,客户要求7寸屏,1024×600分辨率,还要双缓冲做动画。片内SRAM总共1MB,一个帧缓冲就1.2MB,根本塞不下。最后只能把显存放到片外SDRAM。
核心原则:
- 分辨率≤480×272(QVGA),且只需单缓冲 → 优先片内SRAM
- 分辨率≥800×480,或需要双缓冲/三缓冲 → 必须片外SDRAM/PSRAM
- 引脚受限但需要中等容量 → PSRAM(QSPI接口)是折中方案
4.2 显存分配策略:怎么分才不浪费?
显存分配不是简单地把帧缓冲扔到某个地址就完事。我见过太多人踩坑:显存和堆栈冲突,或者DMA传输时缓冲区重叠,画面撕裂得一塌糊涂。
这里我总结了一套分配策略,分三步走:
4.2.1 第一步:确定显存总需求
先算清楚你需要多少显存。公式很简单:
显存大小 = 宽度 × 高度 × 像素字节数 × 缓冲数量
举个例子:
- 800×480,RGB565(2字节),单缓冲 → 800×480×2 = 768KB
- 800×480,RGB565,双缓冲 → 768KB × 2 = 1.5MB
- 800×480,ARGB8888(4字节),双缓冲 → 800×480×4×2 = 3.07MB
嗯,这里要注意:双缓冲不是必须的。如果你的GUI框架支持局部刷新,或者你有硬件图层叠加,单缓冲+脏矩形也能做到不撕裂。我在一个低端MCU项目里就用单缓冲+局部刷新,效果一样好,省了一半显存。
4.2.2 第二步:划分显存区域
显存分配不是一整块全给帧缓冲。我建议按优先级划分:
- 帧缓冲区:最高优先级,必须连续、对齐(通常32字节对齐,方便DMA)
- 图层缓冲区:如果有硬件图层叠加,每个图层需要独立缓冲区
- DMA传输缓冲区:用于从片外搬运数据到片内,或从Flash搬运到显存
- 临时绘制缓冲区:用于字体渲染、图像解码等中间操作
我的习惯:把帧缓冲放在显存区域的起始位置,地址对齐到256字节边界。这样DMA传输时效率最高,而且调试时一眼就能看出显存起始地址。
4.2.3 第三步:片内片外混合分配
这是我最常用的策略——片内SRAM做“热区”,片外SDRAM做“冷区”。
具体做法:
- 片内SRAM:存放当前正在绘制的帧缓冲(或当前图层的局部缓冲区)
- 片外SDRAM:存放备用帧缓冲、背景图层、大尺寸图像资源
为什么这样分?因为绘制操作频繁访问的是当前帧缓冲,放片内速度最快。而备用缓冲只在切换画面时才用到,放片外慢一点没关系。
我曾经在一个项目里,把双缓冲中的前台缓冲放片内,后台缓冲放片外。切换时通过DMA把后台缓冲搬运到前台,速度完全能接受,而且片内SRAM只用了不到200KB。
4.3 DMA传输优化:让搬运不再拖后腿
显存放在片外,访问速度肯定不如片内。这时候DMA就是救星。但DMA不是随便配个通道就能跑满的,我踩过的坑不少。
4.3.1 DMA传输的核心参数
| 参数 | 推荐配置 | 说明 |
|---|---|---|
| 数据宽度 | 32位(字) | 比8位或16位效率高,一次传4字节 |
| 突发传输 | 使能(Burst=4或8) | 减少总线仲裁开销,提高吞吐量 |
| 源地址递增 | 使能 | 从片外SDRAM读取时地址递增 |
| 目标地址递增 | 使能 | 写入片内SRAM时地址递增 |
| 优先级 | 高(或最高) | 显存传输优先级应高于其他外设 |
4.3.2 双缓冲DMA传输模式
我最常用的模式是双缓冲DMA,也叫Ping-Pong Buffer。原理很简单:
- DMA正在传输缓冲区A到显存
- CPU同时绘制下一帧到缓冲区B
- 传输完成后,DMA自动切换到缓冲区B
- CPU开始绘制下一帧到缓冲区A
这样CPU和DMA并行工作,几乎没有等待时间。
关键点:缓冲区A和B必须在片内SRAM中,且地址连续、大小相同。DMA传输完成后触发中断,在中断服务函数中切换缓冲区指针。
4.3.3 我曾经踩过的坑
避坑指南:
- 缓存一致性问题:如果MCU有Cache,DMA传输后Cache中的数据可能和内存不一致。我遇到过画面出现随机噪点,查了两天才发现是Cache没刷新。解决方案:DMA传输完成后,手动调用SCB_CleanInvalidateDCache()。
- DMA传输未完成就访问目标地址:DMA是异步的,传输没结束就去读目标缓冲区,读到的是半残数据。一定要等DMA传输完成标志位置位后再操作。
- 对齐问题:DMA传输的源地址和目标地址必须对齐到数据宽度。比如32位传输,地址必须4字节对齐。不对齐会导致DMA传输异常或效率骤降。
4.4 显存分配与DMA传输流程图
下面这张图是我在实际项目中总结的显存分配与DMA传输流程,你可以参考:
4.5 实战建议:不同场景的显存配置
最后,我根据实际项目经验,给出几个典型场景的显存配置建议:
| 场景 | 分辨率 | 显存方案 | DMA配置 | 备注 |
|---|---|---|---|---|
| 低端HMI | 480×272 | 全部片内SRAM | 无需DMA | 单缓冲+局部刷新 |
| 中端HMI | 800×480 | 片内SRAM(前台)+ 片外PSRAM(后台) | QSPI DMA,Burst=4 | 双缓冲,DMA搬运后台到前台 |
| 高端HMI | 1024×600 | 片内SRAM(热区)+ 片外SDRAM(冷区) | FMC DMA,Burst=8,32位 | 三缓冲,CPU和DMA完全并行 |
| 智能家居面板 | 480×480 | 全部片内SRAM | 可选DMA(用于图像解码) | 单缓冲+脏矩形,帧率要求不高 |
我的经验:如果你用的是Cortex-M7或M33内核的MCU,记得开启ICache和DCache,并且DMA传输完成后一定要做Cache Clean操作。我见过有人忘了这一步,画面显示出来全是马赛克,还以为是硬件坏了。
显存优化说白了就是权衡——速度、容量、引脚、功耗,你总得牺牲一个。但掌握了片内片外混合分配和DMA并行传输这两招,大部分场景都能找到平衡点。