1. 存储器概述:单片机存储体系结构
做嵌入式这些年,我调试过不少“跑飞”的板子。十有八九,问题都出在存储器上——要么是Flash写穿了,要么是SRAM溢出,要么是EEPROM数据丢了。说白了,不理解存储器,你就没法真正掌控单片机。
这一章,咱们先把存储器的底牌翻出来看看。
1.1 哈佛结构与冯·诺依曼结构
这两种结构,是单片机存储体系的“基因”。
冯·诺依曼结构:程序指令和数据共用一套总线、一个存储空间。CPU取指令和读写数据不能同时进行,得排队。PC、x86这类处理器大多走这条路。
哈佛结构:程序存储器和数据存储器物理上分开,各有各的总线。CPU可以一边取指令,一边读写数据——并行操作。绝大多数单片机(比如AVR、PIC、8051、ARM Cortex-M)都采用哈佛结构或改进型哈佛结构。
| 对比项 | 冯·诺依曼 | 哈佛 |
|---|---|---|
| 存储空间 | 统一编址 | 程序/数据独立编址 |
| 总线 | 单套总线 | 两套独立总线 |
| 取指与数据访问 | 不能同时 | 可同时进行 |
| 典型代表 | x86、ARM Cortex-A | 8051、AVR、ARM Cortex-M |
| 执行效率 | 较低(总线瓶颈) | 较高(指令流水线友好) |
我的一点经验: 用STM32做项目时,我习惯把关键中断服务函数放在SRAM里执行。因为Flash读取有等待周期,而SRAM零等待——这就是哈佛结构带来的优化空间。
1.2 存储器层次:Flash、SRAM、EEPROM
单片机内部一般有三层存储器,各有各的脾气。
Flash(程序存储器)
- 非易失性,掉电不丢
- 读快写慢(擦除按扇区,写入按页)
- 寿命约1万~10万次擦写
- 存放固件、常量、启动代码
SRAM(数据存储器)
- 易失性,掉电即丢
- 读写速度极快,零等待
- 容量小(几KB到几百KB)
- 存放堆栈、全局变量、动态数据
EEPROM(非易失数据存储器)
- 非易失性,可字节级擦写
- 速度比Flash慢,但比Flash灵活
- 寿命约10万~100万次
- 存放配置参数、校准值、掉电保存数据
我曾经踩过的坑: 有个产品量产5000台后,陆续出现参数丢失。查了三天,发现是EEPROM擦写次数超了——代码里每次上电都写一次配置,一天开关机20次,一年就7000多次。后来改成“值变化才写”,问题解决。记住:EEPROM不是无限寿命的。
1.3 存储器映射图
存储器映射,说白了就是给每个存储单元分配一个地址。CPU通过地址总线找到对应的存储单元,然后读写数据。
以STM32F103为例,它的存储器映射是这样的:
| 地址范围 | 存储器类型 | 用途 |
|---|---|---|
| 0x0000 0000 - 0x0000 FFFF | 别名区/系统存储器 | 启动配置、Bootloader |
| 0x0800 0000 - 0x0801 FFFF | Flash(128KB) | 程序代码、常量 |
| 0x2000 0000 - 0x2000 4FFF | SRAM(20KB) | 变量、堆栈 |
| 0x4000 0000 - 0x4002 3FFF | 外设寄存器 | GPIO、USART、TIM等 |
| 0xE000 0000 - 0xE00F FFFF | Cortex-M3内部外设 | NVIC、SysTick、MPU |
你想想看,为什么Flash地址从0x0800 0000开始?因为STM32把Flash映射到了这个区域,CPU复位后从0x0800 0000取第一条指令。SRAM从0x2000 0000开始,外设寄存器从0x4000 0000开始——每个区域都有明确的边界。
调试时最实用的技能: 在IDE的Memory窗口里直接输入地址,就能看到该地址存储的原始数据。我曾经靠这个定位过一个堆栈溢出——在0x2000 4F00附近看到本该是0x00的地方出现了0xA5,立马知道堆栈踩到了变量区。
下面这张图,把单片机存储体系的三个核心维度串在了一起:
我的习惯: 拿到一款新单片机,第一件事就是打开参考手册的“Memory Map”章节,把Flash、SRAM、外设寄存器的起始地址和大小记下来。调试时遇到“HardFault”,十有八九是访问了非法地址——看一眼映射图就能定位。