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 那么合适。

小技巧: 选型时别只看 tACC。有些 SRAM 的 tACC 很快,但 tCYC 很长,因为内部预充电时间拉胯。我踩过这个坑,一个号称 10ns 的 SRAM,实际读写周期只能跑到 20ns。

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,性能直接翻了一倍。你想想看,这差距有多大。

注意: 外部 SRAM 的时序必须严格匹配。我曾经遇到过,因为 PCB 走线过长导致信号反射,SRAM 的片选信号出现毛刺,数据偶尔被写飞。最后加了个 33Ω 的串联电阻才搞定。

3.3 Cache 一致性问题

Cache 一致性,说白了就是:CPU 缓存里的数据和主存里的数据,到底谁说了算?

这个问题在单核单片机里其实不太严重。但一旦你用了 DMA、双核、或者外部总线主设备,麻烦就来了。

我画个图帮你理解:

Cache 一致性问题示意图 Cache 一致性问题:谁的数据才是对的? CPU 核心 Cache (数据=0x42) 主存 (数据=0x00) DMA 控制器 CPU 写入 DMA 直接写入主存 ⚠ Cache 与主存数据不一致! 典型场景: 1. CPU 修改了 Cache 中的数据 (0x42),但还没写回主存 (主存还是 0x00)。 2. DMA 从主存读取数据,拿到的是旧值 0x00,而不是 CPU 刚写的新值 0x42。 3. 结果:DMA 搬运了错误的数据,系统行为异常。

你看,问题就出在这里。CPU 写了自己的 Cache,但主存还是旧数据。这时候如果 DMA 去读主存,拿到的就是脏数据。

3.4 怎么处理 Cache 一致性问题?

我总结了三种常用方法,按推荐程度排序:

  1. Cache 写透策略 (Write-Through)
    • CPU 写 Cache 的同时,也写主存
    • 优点:主存永远是最新的,DMA 随便读
    • 缺点:每次写操作都要等慢速的主存,性能下降
  2. Cache 写回 + 软件刷写 (Write-Back + Flush)
    • CPU 只写 Cache,由软件在关键时刻主动刷回主存
    • 比如:启动 DMA 之前,先调用 SCB_CleanDCache()
    • 优点:性能好;缺点:程序员要操心,容易漏
  3. 非缓存访问 (Non-Cacheable)
    • 把 DMA 用到的内存区域配置为不可缓存
    • CPU 直接读写主存,绕过 Cache
    • 优点:绝对一致;缺点:性能最差
我的建议: 在实时性要求高的场景,用 Write-Back + 手动 Flush。在数据吞吐量大的场景,用 Non-Cacheable 区域给 DMA,Cache 区域给 CPU 计算。别混用,否则调试起来你会怀疑人生。

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 读的时候会从主存重新加载
曾经踩过的坑: 有一次我忘了做 Invalidate,DMA 写完了数据,CPU 读到的还是 Cache 里的旧值。结果数据校验一直失败,查了整整一个下午。后来加了一行 SCB_InvalidateDCache,问题秒解。所以,Clean 和 Invalidate 一定要成对出现,别偷懒。

3.6 总结一下

SRAM 和 Cache 是单片机性能的关键。片上 SRAM 快但贵,外部 SRAM 便宜但慢。Cache 能弥合速度差距,但带来了一致性问题。

我个人习惯的选型思路:

  • 中断向量表、实时性要求高的变量 → 片上 SRAM
  • 大块数据缓冲、DMA 缓冲区 → 外部 SRAM 或 SDRAM
  • Cache 策略:能 Write-Through 就用,不行就手动 Flush

记住一句话:Cache 一致性不是 Bug,是特性——但用不好就是 Bug。