3. SRAM 与 Cache:静态特性、片上 vs 外部、Cache 一致性
SRAM 这东西,说白了就是单片机里的“高速便签本”。你写进去什么,只要不掉电,它就老老实实待着。我刚开始做嵌入式那会儿,总觉得 SRAM 不就是个存储器嘛,有啥好研究的?直到有一次,一个外部 SRAM 的时序配错了,整块板子在高温下随机死机,查了整整三天……嗯,从那以后我再也不敢小看它了。
3.1 SRAM 的静态特性
SRAM 的全称是 Static Random-Access Memory。静态,指的是它不需要像 DRAM 那样周期性刷新。每个 bit 由 6 个晶体管构成一个锁存器,只要供电,数据就稳稳锁住。
几个关键参数,我建议你刻在脑子里:
| 参数 | 典型值 | 说明 |
|---|---|---|
| 访问时间 (tACC) | 8~70 ns | 从地址有效到数据稳定的时间 |
| 周期时间 (tCYC) | 10~80 ns | 两次连续访问的最小间隔 |
| 功耗 | 几 mW ~ 几十 mW | 静态功耗极低,动态功耗随频率上升 |
| 保持电压 | 1.2V ~ 3.6V | 低于此值数据可能丢失 |
我个人习惯把 SRAM 看作“电平触发的 D 触发器阵列”。它的读操作是非破坏性的——读完之后数据还在,不像 DRAM 读一次就要重写一次。这也是为什么 SRAM 做 Cache 那么合适。
3.2 片上 SRAM 与外部 SRAM 的区别
很多初学者会问:都是 SRAM,片上和片外到底差在哪?
我直接给你列个对比表,一目了然:
| 对比项 | 片上 SRAM | 外部 SRAM |
|---|---|---|
| 访问速度 | 1~2 个 CPU 时钟周期 | 3~20 个时钟周期(取决于总线) |
| 容量 | 几 KB ~ 几 MB | 几 MB ~ 几十 MB |
| 总线宽度 | 与 CPU 数据总线同宽(32/64位) | 通常 8/16 位,需多次访问 |
| 功耗 | 低(无 I/O 驱动损耗) | 较高(需驱动 PCB 走线) |
| 可靠性 | 高(无外部干扰) | 受 PCB 噪声、串扰影响 |
| 成本 | 高(占用芯片面积) | 低(按 bit 算) |
为什么片上 SRAM 这么快?因为它和 CPU 核心在同一个 die 上,走的是内部金属连线,没有 PCB 走线延迟,没有 I/O 缓冲延迟。外部 SRAM 呢?地址和数据要先通过 GPIO 或 FMC 控制器,经过 PCB 走线,再到芯片引脚,一来一回,几个纳秒就没了。
我曾经在一个工业控制项目里,用外部 SRAM 做数据缓冲。结果发现,CPU 每次读写都要等 5 个等待周期。后来我把热数据搬到片上 SRAM,性能直接翻了一倍。你想想看,这差距有多大。
3.3 Cache 一致性问题
Cache 一致性,说白了就是:CPU 缓存里的数据和主存里的数据,到底谁说了算?
这个问题在单核单片机里其实不太严重。但一旦你用了 DMA、双核、或者外部总线主设备,麻烦就来了。
我画个图帮你理解:
你看,问题就出在这里。CPU 写了自己的 Cache,但主存还是旧数据。这时候如果 DMA 去读主存,拿到的就是脏数据。
3.4 怎么处理 Cache 一致性问题?
我总结了三种常用方法,按推荐程度排序:
- Cache 写透策略 (Write-Through)
- CPU 写 Cache 的同时,也写主存
- 优点:主存永远是最新的,DMA 随便读
- 缺点:每次写操作都要等慢速的主存,性能下降
- Cache 写回 + 软件刷写 (Write-Back + Flush)
- CPU 只写 Cache,由软件在关键时刻主动刷回主存
- 比如:启动 DMA 之前,先调用
SCB_CleanDCache() - 优点:性能好;缺点:程序员要操心,容易漏
- 非缓存访问 (Non-Cacheable)
- 把 DMA 用到的内存区域配置为不可缓存
- CPU 直接读写主存,绕过 Cache
- 优点:绝对一致;缺点:性能最差
3.5 实际代码示例:Cortex-M7 的 Cache 操作
以 STM32H7 为例,我写个简单的 DMA 传输前刷 Cache 的代码:
// 定义 DMA 缓冲区,必须 32 字节对齐(Cache Line 大小)
__attribute__((aligned(32))) uint8_t dma_buffer[1024];
// 启动 DMA 传输前,先刷 Cache
void dma_transfer_start(void)
{
// 将 CPU 写入的数据从 Cache 刷回主存
SCB_CleanDCache_by_Addr(dma_buffer, sizeof(dma_buffer));
// 启动 DMA(此时主存数据是最新的)
HAL_DMA_Start(&hdma, (uint32_t)dma_buffer, (uint32_t)periph_addr, 1024);
}
// DMA 传输完成后,使 Cache 失效(让 CPU 重新从主存读取)
void dma_transfer_complete(void)
{
// 使 Cache 中对应地址的行失效
SCB_InvalidateDCache_by_Addr(dma_buffer, sizeof(dma_buffer));
// 现在读 dma_buffer 就是 DMA 写进来的新数据
process_data(dma_buffer);
}
这段代码我用了好几年,基本没出过问题。注意两个关键点:
- Clean:把 Cache 里的脏数据写回主存,给 DMA 看
- Invalidate:把 Cache 里的旧数据标记为无效,下次 CPU 读的时候会从主存重新加载
3.6 总结一下
SRAM 和 Cache 是单片机性能的关键。片上 SRAM 快但贵,外部 SRAM 便宜但慢。Cache 能弥合速度差距,但带来了一致性问题。
我个人习惯的选型思路:
- 中断向量表、实时性要求高的变量 → 片上 SRAM
- 大块数据缓冲、DMA 缓冲区 → 外部 SRAM 或 SDRAM
- Cache 策略:能 Write-Through 就用,不行就手动 Flush
记住一句话:Cache 一致性不是 Bug,是特性——但用不好就是 Bug。