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 第二步:划分显存区域

显存分配不是一整块全给帧缓冲。我建议按优先级划分:

  1. 帧缓冲区:最高优先级,必须连续、对齐(通常32字节对齐,方便DMA)
  2. 图层缓冲区:如果有硬件图层叠加,每个图层需要独立缓冲区
  3. DMA传输缓冲区:用于从片外搬运数据到片内,或从Flash搬运到显存
  4. 临时绘制缓冲区:用于字体渲染、图像解码等中间操作

我的习惯:把帧缓冲放在显存区域的起始位置,地址对齐到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。原理很简单:

  1. DMA正在传输缓冲区A到显存
  2. CPU同时绘制下一帧到缓冲区B
  3. 传输完成后,DMA自动切换到缓冲区B
  4. 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传输流程,你可以参考:

显存分配与DMA传输流程图 片外SDRAM 备用帧缓冲 背景图层 图像资源 DMA读取 DMA引擎 突发传输/Burst=8 DMA写入 片内SRAM 前台帧缓冲(Ping) 后台帧缓冲(Pong) 临时绘制缓冲区 CPU绘制 绘制到空闲缓冲区 CPU直接写入 显示控制器 从片内SRAM读取并显示 LTDC/DSI读取 流程说明: 1. 片外SDRAM存储备用帧缓冲和图像资源,容量大但访问慢。 2. DMA引擎以突发传输模式将数据从片外搬运到片内SRAM。 3. 片内SRAM存放当前正在显示和绘制的帧缓冲(Ping-Pong模式)。 4. CPU直接写入片内SRAM的空闲缓冲区,与DMA传输并行工作。 5. 显示控制器直接从片内SRAM读取数据刷新屏幕,无需等待片外访问。

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并行传输这两招,大部分场景都能找到平衡点。