1. 内存分区基础:RAM与ROM的区别、Flash与SRAM的特性、内存映射的概念

各位同学,咱们今天聊聊嵌入式系统里最基础、也最容易被忽视的东西——内存分区。说实话,我见过不少工程师,代码写得飞起,结果一上板子就翻车,十有八九是内存没搞明白。嗯,咱们从最根本的说起。

1.1 RAM vs ROM:一个要电,一个不要电

先问个问题:你手机里存照片的地方,和运行游戏的地方,是同一个吗?当然不是。这就引出了RAM和ROM的区别。

RAM(Random Access Memory),随机存取存储器。特点是:断电丢数据。你想想看,电脑一关机,正在编辑的文档没保存,全没了——这就是RAM干的“好事”。它用来跑程序、存变量、做运算。速度快,但“记性差”。

ROM(Read-Only Memory),只读存储器。特点是:断电不丢数据。你的手机开机画面、操作系统内核、Bootloader,都放在ROM里。它“记性好”,但速度慢,而且一般不能随便改(当然现在有Flash这种可擦写的ROM)。

核心区别一句话:RAM是工作台,ROM是仓库。工作台断电就清空,仓库断电东西还在。

我个人习惯,在设计系统时先画一张内存地图,标清楚哪块是RAM、哪块是ROM。这样后面写链接脚本(Linker Script)时心里有数,不会把变量扔到Flash里去。

1.2 Flash vs SRAM:嵌入式里的“黄金搭档”

嵌入式系统里,最常用的ROM是Flash,最常用的RAM是SRAM。咱们一个一个说。

Flash存储器

Flash是一种非易失性存储器,说白了就是断电后数据还在。它分两种:NOR Flash和NAND Flash。咱们嵌入式MCU里用的基本都是NOR Flash,因为它支持随机访问,可以直接执行代码(XIP,Execute In Place)。

Flash的特性:

  • 读快写慢:读速度还行,但写(编程)和擦除操作很慢,而且有寿命限制(一般10万次擦写)
  • 按块擦除:你不能像改RAM那样直接改一个字节,得先把整个块擦干净,再写进去
  • 掉电不丢:这是它最大的价值

避坑指南:我曾经在一个项目里,把频繁更新的配置参数直接放在Flash里。结果产品用了半年就开始出问题——Flash被写坏了。后来我改成在RAM里维护一份镜像,只在掉电前写回Flash。嗯,这个教训值好几万块钱。

SRAM存储器

SRAM(Static RAM),静态随机存取存储器。它不需要像DRAM那样周期性刷新,速度快,但集成度低、成本高、功耗相对大。MCU内部一般配几KB到几MB的SRAM。

SRAM的特性:

  • 速度快:访问时间可以做到几纳秒,CPU直接挂总线上跑
  • 字节寻址:想改哪个字节就改哪个字节,不用擦除
  • 掉电丢失:一断电,数据全飞了

我建议你在选型时,先算算你的全局变量、堆栈、DMA缓冲区总共需要多少SRAM。留出20%的余量,不然调试时加个日志缓冲区都加不进去,很尴尬。

特性 Flash (NOR) SRAM
易失性 非易失(掉电保留) 易失(掉电丢失)
读速度 较快 极快
写速度 慢(需擦除) 极快(直接写)
擦写寿命 约10万次 无限(理论)
典型用途 代码存储、常量数据 变量、堆栈、运行时数据

1.3 内存映射:给每个字节一个“门牌号”

好了,现在你知道有Flash和SRAM了。但CPU怎么找到它们?靠的就是内存映射

内存映射,说白了就是给系统里的每个存储器(Flash、SRAM、外设寄存器等)分配一个唯一的地址范围。CPU通过地址总线发出地址,内存控制器根据这个地址范围,决定访问哪个设备。

举个例子,STM32F103系列的内存映射是这样的:

0x0800 0000 - 0x0801 FFFF  →  Flash (128KB)
0x2000 0000 - 0x2000 4FFF  →  SRAM  (20KB)
0x4000 0000 - 0x4002 3FFF  →  外设寄存器
0xE000 0000 - 0xE00F FFFF  →  内核外设 (NVIC, SysTick等)

你看,地址空间是统一编址的。CPU并不知道它访问的是Flash还是SRAM,它只知道往某个地址写数据。至于这个地址对应什么物理设备,那是内存控制器的事。

注意:不同MCU的内存映射完全不同。你换了一个型号,Flash基地址可能就变了。我见过有人把STM32的代码直接烧到GD32上,结果因为Flash地址偏移,程序直接跑飞。嗯,这种低级错误,犯一次就够了。

为什么要理解内存映射?因为写链接脚本(Linker Script)时,你得告诉编译器:代码段(.text)放Flash,数据段(.data)放SRAM,未初始化变量(.bss)也放SRAM。如果映射搞错了,程序要么跑不起来,要么跑起来也是错的。

我个人习惯,在项目初期就画一张内存映射图,标清楚每个段的起始地址和大小。然后贴在工位上。调试时遇到HardFault,先查是不是访问了非法地址——十有八九是内存映射没搞对。

小结

这一节咱们聊了三个基础概念:

  • RAM vs ROM:一个要电,一个不要电
  • Flash vs SRAM:一个存代码,一个跑代码
  • 内存映射:给每个存储器发个门牌号

下一节,咱们会深入MPU(内存保护单元),看看怎么给这些内存区域上锁,防止程序“越界”。你想想看,如果代码段被意外写坏了,系统是不是直接崩了?嗯,MPU就是干这个的。