2. MCU内存架构深度解析:Flash、SRAM、CCM RAM、TCM的区别与用途

做嵌入式开发这么多年,我见过太多工程师把MCU内存当成一个黑盒子。代码能跑就行,管它放哪里?直到项目出现诡异的死机、响应延迟、或者堆栈溢出,才回头研究内存布局。

说实话,MCU的内存架构比PC要复杂得多。PC有操作系统帮你管理虚拟内存,你基本不用操心物理地址。但MCU不一样——你写的每一行代码、定义的每一个变量,最终都要落在具体的物理存储上。选错了地方,性能可能差一个数量级。

这一章,我们就来拆解MCU的几种核心存储介质:Flash、SRAM、CCM RAM、TCM。搞清楚它们各自的特点和用途,是后续内存优化和任务调度的基础。

2.1 四大存储介质速览

先给个总览表格,方便你快速对比:

存储类型 速度 容量 掉电保持 典型用途
Flash 慢(~几十ns读,~ms写) 大(256KB~2MB+) 是 代码存储、常量、固件
SRAM 快(~几ns) 中等(64KB~512KB) 否 堆、栈、全局变量、RTOS数据
CCM RAM 极快(与CPU同频) 小(16KB~64KB) 否 中断向量表、实时性要求高的数据
TCM 极快(零等待) 小(8KB~64KB) 否 关键任务栈、时间敏感变量

你可能会问:既然SRAM已经很快了,为什么还要搞CCM RAM和TCM?

嗯,这里有个关键点——总线竞争。SRAM通常挂在系统总线上,CPU和DMA、外设都要抢这条总线。而CCM RAM和TCM是直接挂在CPU内核上的,没有总线仲裁延迟。说白了,就是给CPU开了条专用通道。

2.2 Flash:代码的安身之所

Flash是MCU里容量最大的存储,也是代码的默认存放位置。上电后CPU直接从Flash取指令执行。

但Flash有个硬伤——读取速度慢。以STM32F4为例,Flash读取需要等待周期(Wait State),主频越高,等待周期越多。168MHz运行时,Flash需要5个等待周期。这意味着CPU取一条指令,要等5个时钟周期才能拿到数据。

关键认知:Flash的等待周期是MCU性能瓶颈的常见元凶。很多工程师发现代码跑得慢,第一反应是优化算法,其实先看看Flash预取缓冲和ART加速器有没有打开,往往效果更明显。

我在一个电机控制项目里遇到过这种情况:同样的控制算法,在RAM里跑比在Flash里跑快了将近30%。后来我把中断服务函数和实时性要求高的循环体搬到了SRAM里,问题才解决。

Flash的另一个特点是写操作极慢(ms级),而且有擦写寿命限制(通常1万~10万次)。所以运行时频繁修改的数据,千万别放Flash。

2.3 SRAM:运行时的主力军

SRAM是MCU的运行时内存。全局变量、堆、栈、RTOS的任务控制块、信号量、消息队列……这些东西默认都在SRAM里。

SRAM的优点是速度快、无等待周期(在MCU主频范围内)。但它的容量通常只有Flash的1/4到1/2。以STM32H7为例,Flash有2MB,SRAM只有1MB左右(还分成了好几个区域)。

我个人习惯把SRAM分成几个功能区:

  • .bss段:未初始化的全局变量
  • .data段:已初始化的全局变量(初始值从Flash拷贝过来)
  • 堆:动态分配(malloc/new)
  • 栈:局部变量、函数调用
  • RTOS内核对象:任务栈、TCB、队列等

避坑指南:我曾经在一个项目中把任务栈设得太大,导致SRAM的堆空间被挤占,malloc返回NULL,系统跑着跑着就挂了。后来我改用静态分配+任务栈大小计算工具,才彻底解决这个问题。

2.4 CCM RAM:藏在核心里的高速缓存

CCM RAM的全称是Core Coupled Memory。它直接挂在CPU内核的数据总线上,不经过系统总线。这意味着CPU访问CCM RAM时,不会和DMA、外设、USB等模块争抢总线。

CCM RAM的特点:

  • 零等待访问:与CPU同频,没有等待周期
  • 容量小:通常16KB~64KB
  • 不能放代码:CCM RAM只能存数据,不能取指令(至少STM32F4/H7系列是这样)
  • DMA不能访问:DMA控制器走的是系统总线,够不到CCM RAM

这就很有意思了。CCM RAM最适合放什么?

  • 中断服务程序的局部变量:中断响应要快,CCM RAM零等待
  • 实时性要求高的数据:比如PID控制器的系数、传感器校准值
  • RTOS的临界区数据:关中断后要尽快操作的数据

注意:因为DMA不能访问CCM RAM,所以如果你用DMA搬运数据,目标地址不能放在CCM RAM里。我见过有人把ADC的DMA缓冲区放在CCM RAM,结果数据一直不对,查了两天才发现是DMA根本写不进去。

2.5 TCM:紧耦合存储的极致性能

TCM(Tightly Coupled Memory)是ARM Cortex-M7/M33等高性能内核才有的特性。它和CCM RAM类似,也是直接挂在CPU内核上,但TCM分为ITCM(指令TCM)和DTCM(数据TCM)。

ITCM可以取指令,DTCM可以读写数据。这意味着你可以把关键代码放到ITCM里,关键数据放到DTCM里,CPU访问它们都是零等待。

TCM的典型应用场景:

  • ITCM:放中断向量表、启动代码、实时性要求高的函数
  • DTCM:放任务栈(特别是RTOS中优先级最高的任务)、时间关键变量

我记得在一个音频处理项目里,FFT计算占用了大量CPU时间。后来我把FFT的查找表和核心循环体搬到了TCM,计算时间缩短了40%。效果立竿见影。

2.6 内存映射与MPU保护单元基础

MCU上电后,Flash、SRAM、CCM RAM、TCM这些存储介质会被映射到统一的地址空间里。以STM32H743为例:

地址范围 大小 映射目标
0x0000 0000 - 0x000F FFFF 1MB ITCM / Flash别名(可配置)
0x0800 0000 - 0x081F FFFF 2MB Flash主存储
0x2000 0000 - 0x2001 FFFF 128KB DTCM
0x2400 0000 - 0x2407 FFFF 512KB SRAM1
0x3000 0000 - 0x3000 3FFF 16KB SRAM4(备份域)
0x3800 0000 - 0x3800 3FFF 16KB CCM SRAM(F4系列)

有了内存映射,CPU才能通过地址访问不同的存储介质。但这也带来一个问题:如果程序跑飞了,或者野指针乱写,可能会破坏关键数据。

这时候就需要MPU(Memory Protection Unit)出场了。

MPU是ARM Cortex-M系列内核提供的一个硬件保护机制。你可以把内存空间划分成多个区域,每个区域设置不同的访问权限:

  • 读/写/执行:允许或禁止
  • 特权/用户模式:区分内核态和用户态
  • 缓存策略:是否允许Cache缓存

举个例子,你可以把Flash区域设为只读+可执行,把SRAM区域设为可读写+不可执行。这样即使黑客注入了一段代码,也没办法在SRAM里执行。

实用配置建议:在RTOS环境下,我通常把任务栈所在的SRAM区域设为可读写+不可执行,把内核代码所在的Flash区域设为只读+可执行。这样能有效防止栈溢出导致的代码执行攻击。

MPU的配置并不复杂,但很多人容易忽略。我建议你在项目初期就把MPU配好,而不是等到出问题再补。因为一旦系统跑起来,再想加MPU保护,可能会影响现有功能。

2.7 知识结构图:MCU内存全景

下面这张SVG图展示了MCU内存架构的全景,包括四种存储介质、它们与CPU/总线的连接关系,以及MPU的保护范围:

MCU内存架构全景图 MCU内存架构全景图 CPU 内核 系统总线(AHB/APB) Flash 代码存储 / 常量 SRAM 堆 / 栈 / 全局变量 外设(DMA/USB等) 总线竞争源 CCM RAM 零等待 / 无总线竞争 TCM(ITCM/DTCM) 零等待 / 可执行 MPU 保护范围 区域1:Flash → 只读 + 可执行 特权模式可写 区域2:SRAM → 可读写 + 不可执行 用户模式可访问 区域3:外设 → 可读写 + 不可执行 仅特权模式可访问 CCM RAM和TCM通过专用通道直连CPU,不受系统总线竞争影响,是实时性数据的首选存放位置

2.8 实战建议:如何选择存储介质

说了这么多理论,最后给几条实战建议:

  1. 代码放Flash,关键函数搬SRAM或ITCM:利用链接脚本的section属性,把中断服务函数、实时性高的循环体放到指定区域。
  2. 全局变量放SRAM,时间敏感数据放CCM RAM或DTCM:比如PID系数、传感器滤波值、RTOS的tick计数。
  3. 任务栈优先用DTCM:如果MCU支持TCM,把最高优先级任务的栈放在DTCM里,能显著减少上下文切换时间。
  4. DMA缓冲区不能放CCM RAM:记住,DMA走系统总线,够不到CCM RAM。
  5. MPU一定要配:哪怕只是最简单的保护——Flash只读、SRAM不可执行。这能帮你挡住很多低级错误和潜在攻击。

我的习惯:每次新建MCU项目,第一件事就是打开链接脚本(.ld或.scf文件),确认内存布局。然后配置MPU。这两步做完,后面写代码心里才有底。

内存架构是MCU性能调优的基石。搞清楚了Flash、SRAM、CCM RAM、TCM的区别和用途,你才能知道代码和数据该放哪里。下一层,我们就要基于这些知识,开始优化ThreadX的任务调度配置了。