理解了 Flash 和 SRAM,你就理解了单片机运行时的“数据流向”,我们继续用“工厂”来做比喻
1. 形象比喻:仓库 vs. 办公桌
- Flash(闪存):像是一个巨大的“仓库”或“说明书珍藏馆”。
- 里面存放的是固定的、不常变动的东西。比如你写的代码逻辑、出厂预设的固定数据。
- 特点: 停电了,里面的东西不会丢。但是,往里放东西(烧录代码)比较慢。
- SRAM(静态随机存取存储器):像是一张“办公桌”或“黑板”。
- 它是 CPU 正在干活的地方。存放的是正在变动的、临时的数据。比如程序运行时的变量
i、传感器读到的实时温度。 - 特点: 办公效率极高(读写飞快),但一停电(或复位),黑板上的字就全擦掉了。
- 它是 CPU 正在干活的地方。存放的是正在变动的、临时的数据。比如程序运行时的变量
2. 它们分别存什么
在 STM32 的开发中,你的程序会被拆成不同的部分分别存放:
Flash 存的是“只读”内容:
- 指令代码:你写的
if…else、while(1)编译后变成的机器码。 - 常量 (const):比如你在代码里定义的
const int a = 100;。因为它是常量,不需要在运行时修改,所以直接存在 Flash 里省空间。
SRAM 存的是“读写”内容:
- 全局变量:程序运行过程中需要一直记录的数据。
- 局部变量:函数内部临时定义的变量。
- 堆栈 (Stack/Heap):函数嵌套调用时的现场保存、或者你动态申请的内存。
3. 什么是“存储器映射”
这是初学者最容易听懵的词,其实它非常简单。
STM32 的内核(Cortex-M)是一个 32 位的 CPU,它的“视力”范围是 $2^{32}$ 个地址(即 $0x0000 0000$ 到 $0xFFFF FFFF$)。
存储器映射,就是给这些硬件设备分配“门牌号”。
- Flash 的门牌号:通常从
0x0800 0000开始。当 CPU 想读代码时,它就去这个地址段找。 - SRAM 的门牌号:通常从
0x2000 0000开始。当 CPU 想改变量时,它就去这个地址段找。 - 外设(寄存器)的门牌号:比如 GPIO、定时器,它们的控制开关也被映射到了特定的地址(通常从
0x4000 0000开始)。
结论: 对 CPU 来说,无论是读 Flash、写 SRAM,还是控制 LED 灯,本质上都只是在不同的地址(门牌号)上读写数据。这就是“万物皆地址”。
4. 核心区别对比表
| 特性 | Flash (闪存) | SRAM (内存) |
|---|---|---|
| 存放内容 | 程序代码 (Code)、常量 (RO-data) | 变量 (RW-data)、堆栈 |
| 断电数据 | 保存 | 丢失 |
| 读写速度 | 较慢(所以有时需要等待周期) | 极快 |
| 擦写寿命 | 有次数限制(约 10 万次) | 无限制 |
| 起始地址 | 0x0800 0000 |
0x2000 0000 |
总结
graph TD
CPU[Cortex-M 内核] -- 访问地址 --> Bus[4GB 总线地址空间]
subgraph "0x0000 0000 - 0xFFFF FFFF (总视野)"
Bus --> Flash_Space["【Flash 区】
起始: 0x0800 0000"] Bus --> SRAM_Space["【SRAM 区】
起始: 0x2000 0000"] Bus --> Peripheral_Space["【外设寄存器区】
起始: 0x4000 0000"] end subgraph "Flash (仓库: 掉电不丢失)" Flash_Space --> Code[指令代码 .text] Flash_Space --> RO_Data[只读常量 const] end subgraph "SRAM (办公桌: 掉电丢失)" SRAM_Space --> RW_Data[全局变量/静态变量] SRAM_Space --> Stack_Heap[堆栈: 函数局部变量] end subgraph "Peripherals (功能按钮)" Peripheral_Space --> GPIO[GPIO 控制器] Peripheral_Space --> UART[串口/定时器等] end style CPU fill:#f96,stroke:#333,stroke-width:2px style Flash_Space fill:#bbf,stroke:#333 style SRAM_Space fill:#dfd,stroke:#333 style Peripheral_Space fill:#fff4dd,stroke:#333
起始: 0x0800 0000"] Bus --> SRAM_Space["【SRAM 区】
起始: 0x2000 0000"] Bus --> Peripheral_Space["【外设寄存器区】
起始: 0x4000 0000"] end subgraph "Flash (仓库: 掉电不丢失)" Flash_Space --> Code[指令代码 .text] Flash_Space --> RO_Data[只读常量 const] end subgraph "SRAM (办公桌: 掉电丢失)" SRAM_Space --> RW_Data[全局变量/静态变量] SRAM_Space --> Stack_Heap[堆栈: 函数局部变量] end subgraph "Peripherals (功能按钮)" Peripheral_Space --> GPIO[GPIO 控制器] Peripheral_Space --> UART[串口/定时器等] end style CPU fill:#f96,stroke:#333,stroke-width:2px style Flash_Space fill:#bbf,stroke:#333 style SRAM_Space fill:#dfd,stroke:#333 style Peripheral_Space fill:#fff4dd,stroke:#333
在图的最上方,CPU 不直接管硬件的名字。它只管发指令:“去 0x0800 0000 取个东西”或者“往 0x2000 0000 写个数字”。
- 如果地址落在 Flash 范围,它就读到了代码。
- 如果地址落在 SRAM 范围,它就访问了变量。
- 如果地址落在 外设(Peripherals) 范围,它就控制了硬件(比如点亮 LED)。
- 你会发现,控制 GPIO 引脚的寄存器也有地址。这就是为什么在底层驱动里,我们可以通过指针操作来点灯:
*(uint32_t *)0x4001080C = 0x00000001; // 假设这是控制灯的地址这就是 STM32 编程的底层逻辑。
- 你会发现,控制 GPIO 引脚的寄存器也有地址。这就是为什么在底层驱动里,我们可以通过指针操作来点灯: