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最近。

STM32 SRAM分区与总线结构 CPU Cortex-M4 TCM(紧耦合内存) ITCM: 指令缓存 / 中断向量 DTCM: 临界数据 / RTOS栈 0等待周期,CPU私有总线 私有总线 AHB总线矩阵 CPU / DMA / 以太网 / USB 系统总线 SRAM1(主内存) 全局变量 / 堆 / DMA缓冲区 典型容量: 112KB SRAM2(辅助内存) DMA双缓冲 / USB FIFO 典型容量: 16KB APB1总线桥 低速外设总线 备份SRAM VBAT供电 / 休眠保持 典型容量: 4KB 说明 TCM与SRAM1/2可并行访问;备份SRAM由VBAT独立供电,主电源掉电不丢失

3.4 实际项目中的分区策略

我一般按这个优先级分配SRAM:

  1. DTCM:RTOS任务栈、临界区变量、中断服务函数用到的全局变量。
  2. SRAM1:普通全局变量、malloc堆、大数组。
  3. SRAM2:DMA双缓冲、USB端点缓冲区、以太网描述符。
  4. 备份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的影响,有时候比你想的更大。