3、SRAM存储器详解:SRAM的静态特性,STM32的SRAM分区
SRAM,全称是静态随机存取存储器。说白了,它就是我们写代码时用来放变量、堆栈的那块“草稿纸”。掉电就丢,但速度快得飞起。
我刚开始用STM32的时候,总觉得SRAM不就是一块内存嘛,有啥好研究的?直到有一次项目里跑FreeRTOS,任务栈分配不合理,系统莫名其妙死机。查了两天才发现是TCM和普通SRAM的访问延迟不一样,导致临界区保护出了问题。从那以后,我每次画板子都要先翻翻参考手册的SRAM分区图。
3.1 SRAM的静态特性
SRAM的“静态”二字,指的是它不需要像DRAM那样周期性刷新。只要不断电,数据就能一直保持。每个存储单元由6个晶体管构成一个触发器电路。
为什么用6个管子?因为要保证数据稳定、读写快。代价就是面积大、成本高。MCU里用SRAM,图的就是它的确定性——你永远不用担心刷新周期会干扰实时任务。
| 特性 | SRAM | DRAM(对比) |
|---|---|---|
| 存储单元结构 | 6晶体管触发器 | 1晶体管+1电容 |
| 是否需要刷新 | 否 | 是(每64ms) |
| 访问速度 | 快(ns级) | 较慢(需刷新周期) |
| 功耗 | 静态功耗较高 | 刷新功耗较高 |
| 集成度 | 低(占面积大) | 高(容量大) |
关键指标:STM32的SRAM访问速度通常与CPU时钟同步,零等待周期(0WS)访问。但要注意,跨总线桥访问时会有1~2个等待周期。
3.2 STM32的SRAM分区详解
STM32的SRAM不是一整块。以F4/H7系列为例,它被分成了多个物理区域。每个区域挂在不同总线上,特性也不同。我习惯把这几个分区记成“三室一厅”:TCM是主卧,SRAM1是客厅,SRAM2是书房,备份SRAM是保险柜。
3.2.1 TCM(紧耦合内存)
TCM直接挂在CPU内核上,不走总线矩阵。这意味着CPU访问TCM时,不会被DMA或其他总线主机抢断。延迟固定为0等待周期。
- ITCM:指令TCM,专门放中断向量表、实时性要求高的代码。
- DTCM:数据TCM,放临界变量、任务栈、RTOS内核数据结构。
我的习惯:把FreeRTOS的堆栈和任务控制块(TCB)全部放在DTCM里。这样上下文切换时,CPU直接访问TCM,不会被DMA干扰。实测任务切换抖动从1.2μs降到了0.4μs。
3.2.2 SRAM1和SRAM2
这两块挂在AHB总线矩阵上。CPU、DMA、以太网、USB等主机都能访问。SRAM1通常容量最大,SRAM2小一些,但两者在地址空间上是连续的。
| 分区 | 总线 | 典型容量(F407) | 访问延迟 | 适用场景 |
|---|---|---|---|---|
| TCM | CPU私有总线 | 64KB(ITCM+DTCM) | 0WS | 中断向量、RTOS内核、临界数据 |
| SRAM1 | AHB总线矩阵 | 112KB | 0WS(无冲突时) | 全局变量、堆、DMA缓冲区 |
| SRAM2 | AHB总线矩阵 | 16KB | 0WS(无冲突时) | DMA双缓冲、USB FIFO |
| 备份SRAM | APB1 | 4KB | 1~2WS | 休眠保持数据、RTC校准参数 |
我曾经踩过的坑:把DMA的源地址和目的地址都放在SRAM1里,同时CPU也在频繁读写SRAM1。结果总线仲裁导致DMA传输偶尔丢数据。后来把DMA缓冲区挪到SRAM2,问题解决。记住:SRAM1和SRAM2可以并行访问,这是硬件设计时就规划好的。
3.2.3 备份SRAM
备份SRAM挂在APB1总线上,由VBAT引脚供电。主电源掉电后,只要纽扣电池还在,数据就能保留。容量不大,通常4KB,但足够存一些关键参数。
- 典型用途:系统配置参数、校准系数、运行日志、RTC闹钟设置。
- 访问方式:通过PWR和RTC寄存器使能,然后像普通SRAM一样读写。
避坑指南:备份SRAM在系统复位后内容不会丢失,但上电复位(POR)会清空。区分清楚:按复位键数据还在,断电再上电数据就没了。除非你用了VBAT供电。
3.3 SRAM分区结构图
下面这张图展示了STM32的SRAM分区和总线连接关系。我画的时候特意把TCM放在最上面,因为它离CPU最近。
3.4 实际项目中的分区策略
我一般按这个优先级分配SRAM:
- DTCM:RTOS任务栈、临界区变量、中断服务函数用到的全局变量。
- SRAM1:普通全局变量、malloc堆、大数组。
- SRAM2:DMA双缓冲、USB端点缓冲区、以太网描述符。
- 备份SRAM:设备序列号、校准系数、掉电前保存的运行状态。
小技巧:在链接脚本(.ld文件)里,可以用 __attribute__((section(".dtcm"))) 把变量强制放到DTCM。比如:
// 放到DTCM
__attribute__((section(".dtcm"))) volatile uint32_t critical_flag;
// 放到SRAM2
__attribute__((section(".sram2"))) uint8_t dma_buffer[1024];
3.5 故障排查要点
SRAM出问题,最常见的现象就是“随机死机”或“变量莫名其妙被改”。我排查时按这个顺序来:
- 检查链接脚本:确认每个分区的起始地址和大小是否正确。我见过有人把SRAM1的起始地址写错,导致变量覆盖了系统堆栈。
- 检查总线冲突:如果DMA和CPU同时访问同一块SRAM,用逻辑分析仪抓AHB总线信号。或者干脆把DMA缓冲区放到SRAM2,物理隔离。
- 检查备份SRAM使能:访问备份SRAM前,必须先使能PWR和RTC时钟,然后设置备份域控制寄存器。否则读回来全是0xFF。
- 检查堆栈溢出:在DTCM和SRAM1的边界处填充0xDEADBEEF,运行一段时间后检查是否被改写。
我曾经遇到的一个诡异问题:产品低温运行时偶尔死机。查了三天,发现是SRAM1在-40°C时访问延迟变大了,而我的定时器中断服务函数里有个循环等待标志位,刚好卡在临界点上。后来把那个标志位挪到DTCM,问题消失。嗯,温度对SRAM的影响,有时候比你想的更大。