第2章:内存架构分析——STM32内部SRAM与外部SDRAM的分配策略
做TouchGFX双缓冲,说白了就是跟内存打交道。你想想看,一个1024×600的16位色帧缓冲区,光一帧就要占1.2MB。STM32内部SRAM通常只有几百KB,根本塞不下。所以外部SDRAM几乎是标配。
但问题来了——SDRAM和内部SRAM速度差多少?帧缓冲区放哪里最合适?对齐要求到底多严格?这些坑,我早年做第一个TouchGFX项目时全踩过。今天咱们一条一条捋清楚。
2.1 STM32的内存家族
先看一张总览图,把STM32内部SRAM和外部SDRAM的层级关系搞清楚。
2.2 内部SRAM:快但小
STM32内部SRAM分好几块。以H743为例,有DTCM(128KB)、ITCM(64KB)、SRAM1(128KB)、SRAM2(128KB)、SRAM3(32KB),总共480KB。看着不少,但扣掉系统堆栈、RTOS任务栈、中间件缓冲区,能剩给UI的也就一两百KB。
内部SRAM的优势:CPU和DMA2D可以直接全速访问,零等待。我在一个项目里把部分频繁更新的小图标缓存放在DTCM,刷新速度比SDRAM快了将近一倍。
内部SRAM的劣势:容量太小。一个1024×600的16位色帧缓冲区就要1.2MB,内部SRAM根本放不下。所以双缓冲方案里,至少有一个缓冲必须放在外部SDRAM。
2.3 外部SDRAM:大但慢
外部SDRAM通过FMC接口挂载,容量从8MB到64MB不等。对于TouchGFX来说,SDRAM是帧缓冲区的主战场。
但SDRAM有个硬伤——访问延迟高。内部SRAM一个时钟周期就能读完的数据,SDRAM可能要等好几个周期。再加上FMC总线的时钟频率通常只有CPU主频的几分之一,差距就更明显了。
| 对比项 | 内部SRAM | 外部SDRAM |
|---|---|---|
| 典型容量 | 192KB ~ 1MB | 8MB ~ 64MB |
| 访问速度 | CPU时钟频率(如400MHz) | FMC时钟频率(如100MHz) |
| 延迟 | 1~2个时钟周期 | 5~15个时钟周期 |
| 适合存放 | 堆栈、热数据、小缓存 | 帧缓冲区、大纹理、资源池 |
| 对齐要求 | 4字节对齐(ARM推荐) | 32字节对齐(LTDC/DMA2D要求) |
2.4 帧缓冲区的对齐要求
这是最容易踩坑的地方。TouchGFX的帧缓冲区,或者说LTDC和DMA2D使用的缓冲区,有严格的对齐要求。
为什么需要对齐?LTDC从内存读取像素数据时,是以32字节(256位)为单位的突发传输。如果缓冲区首地址不是32字节对齐的,硬件会做额外的对齐操作,轻则性能下降,重则直接花屏。
我见过一个案例:工程师把帧缓冲区定义在SDRAM的某个偏移地址,没注意对齐,结果屏幕上半部分正常,下半部分全是乱码。查了两天才发现是地址没对齐。
addr % 32 == 0。对于双缓冲方案,两个缓冲区的首地址都要各自32字节对齐。
在代码里怎么保证?看下面这个例子:
// 正确做法:使用 __attribute__((aligned(32))) 强制对齐
static uint16_t framebuffer1[1024 * 600] __attribute__((aligned(32)));
static uint16_t framebuffer2[1024 * 600] __attribute__((aligned(32)));
// 或者用内存池分配,手动对齐
#define ALIGN_32(x) (((x) + 31) & ~31)
uint32_t buffer_addr = (uint32_t)malloc(size1 + size2 + 32);
uint32_t fb1_addr = ALIGN_32(buffer_addr);
uint32_t fb2_addr = ALIGN_32(fb1_addr + size1);
2.5 分配策略:怎么分最合理?
根据我多年的项目经验,总结出三种主流分配策略:
- 双缓冲全放SDRAM:最简单,容量管够。适合分辨率不高(如480×272)或SDRAM带宽充裕的场景。
- 一内一外:一个缓冲在内部SRAM,一个在外部SDRAM。DMA2D渲染时从SDRAM读数据,写到内部SRAM;LTDC从内部SRAM读取显示。性能最优,但内部SRAM要够大。
- 三缓冲混合:两个渲染缓冲在SDRAM,一个显示缓冲在内部SRAM。适合高帧率动画场景,但实现复杂。
| 策略 | 内部SRAM占用 | SDRAM占用 | 性能 | 适用场景 |
|---|---|---|---|---|
| 双缓冲全SDRAM | 0 | 2×帧大小 | 中等 | 低分辨率、简单UI |
| 一内一外 | 1×帧大小 | 1×帧大小 | 高 | 中高分辨率、流畅动画 |
| 三缓冲混合 | 1×帧大小 | 2×帧大小 | 最高 | 高分辨率、60fps动画 |
2.6 实际项目中的避坑指南
最后分享几个我踩过的坑:
- SDRAM初始化顺序:FMC和SDRAM的初始化必须在TouchGFX启动之前完成。我见过有人把SDRAM初始化放在HAL_Init之后,结果LTDC一启动就访问未初始化的SDRAM,直接HardFault。
- Cache一致性:如果开启了CPU Cache(D-Cache),SDRAM区域必须配置为不可缓存或使用MPU设置成强序内存。否则DMA2D写完了数据,CPU读到的还是Cache里的旧数据,画面会撕裂。
- SDRAM刷新周期:SDRAM需要定时刷新,刷新期间不能访问。如果刷新周期配置不当,会导致帧缓冲区访问出现周期性延迟,表现为画面偶尔卡顿。