上周帮朋友排查一块板子,现象很奇怪:程序烧进去能跑,但只要一断电,之前配好的参数全变回出厂值。查了半天,发现是有人把配置数据放在了RAM里。这让我想起这些年带硬件团队、做方案评审时经常被问的一个问题:RAM、ROM、SRAM、DRAM、SDRAM、NOR Flash、NAND Flash,这一堆名字到底怎么区分?说实话,很多写了几年代码的人,能把SRAM和SDRAM的分工讲清楚的也不多见,更不用说NOR和NAND在嵌入式选型里的那点微妙博弈了。
这篇文章不打算写成教科书式的名词解释,而是想结合我实际做过的一些项目和踩过的坑,把这几类存储器的底层逻辑、适用场景、选型平衡讲透。适合嵌入式开发者、硬件工程师、电子相关专业的学生,也适合所有被面试官问过"RAM和ROM有什么区别"的人——看完你会比大部分面试官的理解还深一层。
1. 从MCU的存储布局看懂RAM和ROM的分工使命
1.1 ROM早就不是"只读"了,但它的角色定位没变
很多入门资料喜欢把ROM解释成"只读存储器",把RAM解释成"随机访问存储器",然后考试就考到这里。但真实世界早就不这么简单了。现代MCU里几乎没有真正的"Mask ROM"(掩膜ROM),取而代之的是Flash。那为什么大家还是习惯叫"ROM"?因为它的角色没有变:存储那些掉电后不能丢的东西,也就是固件代码、启动参数、字库、配置文件。
ROM家族其实走了一段很长的路:早期是掩膜ROM,出厂时数据就固定了,你没法改;后来出现PROM(可编程只读存储器),用户可以写一次;接着是EPROM(可擦除可编程只读存储器),用紫外线照窗口才能擦除重写,所以老电路板上经常能看到贴着银白色标签的芯片,揭掉标签露出一个玻璃窗,那玩意儿在实验室里特别不方便;再往后是EEPROM(电可擦除可编程只读存储器),可以按字节擦写,但速度慢、容量小。而现在我们讨论的主角——Flash,其实就是EEPROM理念的"大容量高速进化版",它继承了ROM"掉电不丢"的家族使命,但擦写能力和速度已经远超老前辈了。
所以你在很多MCU的datasheet里看到"Flash 512KB"和"SRAM 128KB"这样的配置,本质上就是:Flash承担传统ROM的工作,SRAM承担传统RAM的工作。程序编译出来烧进Flash,上电后MCU从Flash里取指令执行;变量、堆栈这些运行时数据才放到SRAM里。那为什么程序不直接在Flash里执行?这个问题放到下一小节,正好引出两者的核心差异。
1.2 启动文件里的秘密:为什么要"搬代码"和"清数据"
如果你点开过任何一个ARM Cortex-M工程的启动文件(startup文件),除了堆栈设置和中断向量表,你一定会看到三个段:text、data、bss。text是代码段,留在Flash里没问题,CPU可以直接从Flash取指令(现代MCU内部Flash有预取缓存,速度损耗没有想象中大)。但data段就很有意思了——它存的是有初值的全局变量,比如int count = 100,这个初始值"100"必须放在Flash里,因为掉电不能丢。可变量真正运行时要能被频繁修改,Flash擦写次数有限、速度也慢,不能当变量区用,所以启动代码要做一件事:把data段从Flash的镜像位置拷贝一份到SRAM里,之后再读写的都是SRAM里的副本。bss段更简单,那些没有初值的全局变量,启动代码把它们所在的SRAM区域统一清零就行。
这一"拷"一"清",其实就是RAM和ROM最真实的分工场景:ROM负责"存档",RAM负责"上场操作"。如果你在做低功耗设计或者内存紧张的方案,这些段的大小直接影响了SRAM选型——有些工程师不看map文件,凭感觉选MCU,结果程序跑到一半栈溢出,往往是没算清楚data段占了多少SRAM。我习惯在工程最后看一眼编译输出的map文件,SRAM用量、Flash用量分别列清楚,再决定要不要换芯片。
还有个细节值得注意:常量字符串和const变量一般放在Flash里,不会拷到SRAM。有些嵌入式新手在Flash较大的MCU上肆无忌惮地写超大日志缓冲,结果一看SRAM爆了,原因就是他们误以为所有变量都在Flash里,没有意识到"可修改的变量才在SRAM"这个基本规则。
2. SRAM和DRAM:一个靠锁存一个靠电容,价格差出数量级
2.1 SRAM的核心是"双稳态触发器",你不需要给它刷新
SRAM全称Static RAM,静态随机访问存储器。它为什么叫"静态"?因为它只要不掉电,数据就能一直保持,不需要任何外部干预。实现原理并不神秘,SRAM的每个存储单元通常由6个晶体管组成一对交叉耦合的反相器,形成"双稳态锁存器"——就像两颗钉子互相别住,系统稳定在"0"或"1"两个状态之一。只要供电不中断,这个状态就一直锁着。
我做PCB设计的时候特别喜欢SRAM这一点:不需要纠结刷新时序、不需要担心刷新电流,上电就能用,读写访问快速、时序简单。MCU内部的SRAM、CPU的一级二级缓存、路由器里的缓冲表,基本都是SRAM的天下。但它的代价也很直接:6个晶体管才能存1个bit,而且交叉耦合结构决定了它不好做高密度工艺,所以同样容量下SRAM的芯片面积大、成本高。这就是为什么你给MCU外扩一片128KB的并行SRAM,价格可能比买一颗带1MB内部Flash的MCU还贵,而128KB的DRAM几乎可以便宜到忽略不计。
2.2 DRAM是"漏水的水桶",全靠刷新续命
DRAM全称Dynamic RAM,动态随机访问存储器。"动态"的意思就是"不会一直保持",因为它的每个存储单元只有1个晶体管加1个电容,靠电容上有没有电荷来表示1和0。问题是电容会漏电,电荷会跑,所以必须周期性给所有存储单元"充电"——这就是刷新(Refresh)操作。另外DRAM的读操作本身是破坏性的:读出电荷后电容状态会被破坏,读完必须立刻写回(这一般由内部逻辑自动完成,但会占用时间)。
这个"1T1C"的单元结构让DRAM的集成密度远高于SRAM,便宜、容量大,所以电脑内存条、显卡显存、手机运行内存,清一色都是DRAM家族的。但代价是速度慢、需要刷新、时序复杂。如果把SRAM比作一个开得很稳的水龙头,打开就是水,关上就是关上了;那DRAM就是一只底上漏水的缸,你得不停地往里续水才能保证里面始终有货。控制器每隔一小段时间就要发Refresh命令,所有正在进行的读写都得排队等它先完成任务。刷新不只是消耗时间,还会消耗电流,所以在电池供电的穿戴设备里,用大容量外部DRAM要保持数据不丢,功耗是一个不容忽视的问题。
我见过一个很有意思的选型对比:同样是1MB的存储,用SRAM,待机电流可能只有几微安,但物料成本很高;用DRAM,物料便宜但待机时必须持续刷新,几十毫安的刷新电流根本省不掉。低功耗产品如果只是要个"断电数据保存",最优解往往是写进Flash而不是挂一块DRAM。
2.3 嵌入式里到底选哪种RAM,别只看价格
说句实在话,很多嵌入式工程师一辈子都用不到DRAM,因为MCU内部SRAM够跑RTOS和业务逻辑了。到需要用DRAM的时候,通常是这几类场景:跑Linux/Android这类复杂系统、做大分辨率显示缓存、做摄像头图像帧缓冲、做音频算法的大块数据缓存。这时候SRAM的高成本和高功耗撑不住,只能上DRAM,而DRAM家族里最常用的就是SDRAM和DDR系列。
不过近两年有一种新趋势:在中等性能MCU上通过QSPI/OSPI接口接一颗PSRAM(伪SRAM,其实内部是DRAM加自刷新电路),它对外表现像SRAM,时序简单、不需要MCU做专门控制器,但容量能做到几十Mb,价格又远低于真SRAM。我在这类产品上踩过的坑是:PSRAM内部自刷新会突然插入"总线暂停",如果你正在做音频I2S流式播放,偶尔会碰到一个毛刺。所以最后我还是给主控加了硬件FMC/SDRAM控制器,代价是PCB走线复杂一些,但数据吞吐的稳定性完全不是一个量级。
3. SDRAM的"同步"到底同步了什么,以及它的时序为啥这么啰嗦
3.1 从异步DRAM到同步DRAM:一套固定节拍的"流程化作业"
很多初学者看到SDRAM的第一反应是"这是不是一个很高级的RAM?"其实SDRAM就是Synchronous DRAM,同步动态随机访问存储器。它和那些老掉牙的FPM/EDO异步DRAM相比,核心区别就一个词:同步。异步DRAM时代,CPU给出地址和读写信号后,DRAM内部慢慢干活,CPU只能干等,干多长时间取决于芯片自身,性能和频率没法稳定预测。SDRAM则给整个访问过程引入了一个参考时钟,所有命令、地址、数据的进出都与时钟上升沿对齐,就像一场所有员工都按照统一节拍器工作的流水线作业,吞吐量一下子拉开了。
但"流程化作业"肯定意味着控制变复杂。SDRAM把存储阵列分成若干个Bank,访问前要先打开对应的Bank和行(Active命令),这一行里面的数据会送到一个叫"行缓冲器"的地方,然后你才能通过列地址去取这一行的某几个数据(Read命令),用完了要关闭这一行(Precharge命令),还要定期给电容充电(Refresh命令)。命令之间的间隔也不是随意定的,比如tRCD(行寻址到列寻址的延时)、CL(CAS Latency,列地址发送到首个数据输出的时钟数)、tRFC(刷新周期),每一个都写死在SDRAM的时序参数里,你的内存控制器必须严格照做。
我在初期调试SDRAM时最有用的一个比喻:把SDRAM想象成一张巨大的网格书架,Bank是在第几排,Active命令是把某一排的书抽出来放到阅览桌上,Read命令是从桌上取某一本,用完不还回去就开不了下一排。理解了这个流程,datasheet上那些密密麻麻的时间参数就不再是乱码了,它们全是"抽书-取书-还书-刷新"这些动作之间的固定等待时间。
3.2 一个实际项目里的SDRAM时序初始化:我踩过的坑
我之前在某块带LCD的高性能主控方案上外扩了一片SDRAM,用于显示缓冲(framebuffer)和GUI控件存储。画面上总是偶发一条横带撕裂,类似"屏幕上部正常、下部错位"的问题。一开始怀疑是LCD扫描时序配置有问题,查了两天没头绪。后来用示波器抓了SDRAM时钟和片选信号,发现刷新命令和读写命令经常挤在同一帧里,才怀疑到SDRAM初始化参数上。
我重新打开主控的内存控制器驱动,对照SDRAM芯片手册逐项核对行列地址位宽、Bank数量、刷新周期,发现刷新计数器的配置值写错了:我按64ms刷新所有行的常见值算出一个数,但实际芯片的刷新周期要求是32ms/4096行,意味着每行的刷新间隔应该更短。修改刷新计数器和tRFC参数后,画面撕裂消失了。
这次经历让我学到一个教训:SDRAM的寄存器参数不能从网上随便复制,必须亲自对着所选芯片的手册和主控参考手册算一遍,特别是刷新周期这个值,不同容量的颗粒不一样,同一颗芯片在不同温度下还会放宽或收紧。另外调试顺序最好是:先跑官方或通用内存测试程序,确认全地址读写稳定,再接LCD。我后来花了一个晚上自己写了个伪随机地址的回文测试,把坏位抓出来,比直接接外设调试要高效得多。
4. NOR Flash和NAND Flash:掉电不丢世界里的一对欢喜冤家
4.1 NOR Flash:能在原地址上执行代码的"慢写怪"
NOR Flash名字里的NOR来自"或非门",它的存储单元阵列结构使得它的每一位都可被独立随机访问。这意味着你可以像读ROM一样,按任意地址直接取数据,而不用先读一整页。正是这个特性,让NOR Flash支持XIP(Execute in Place,原地执行):代码可以直接在NOR Flash上跑,不需要在启动时拷到RAM里再执行。这对于MCU来说太重要了,上电复位向量就能直接从Flash取指令,系统秒起。
但是NOR Flash有它的两大"软肋"。第一,写入数据前必须先擦除,而且擦除是按扇区(sector)进行的,一个扇区通常是4KB、32KB或64KB,哪怕你只想改一个字节,也得先把整个扇区擦除,再重新写入。第二,擦除和写入速度都比读取慢几个数量级,比如读取可以按几十兆波特率跑,但擦除一个64KB扇区往往要几百毫秒甚至一秒钟。所以NOR Flash适合放固件、代码、启动加载器、关键配置,不适合当文件系统做频繁数据写入。寿命上,NOR的擦写次数一般在10万次级别,这个数字看着还行,但如果某个存储区域每秒被写一次,一天就86400次写,寿命只有一天多,所以"磨损均衡"在Flash方案里不是可选项,是必选项。
我做过一个小批量产品,初版用NOR Flash存日志,每5秒写一条记录,结果样机跑了一周就出现配置丢失。后来日志改存到外部大容量NAND Flash,NOR只放代码和出厂参数,问题才彻底解决。这就是典型的"没有搞清楚NOR的写寿命边界"的教训。
4.2 NAND Flash:按页读写的"大仓库",坏块和纠错是它的命门
NAND Flash的存储阵列结构和NOR完全不同,它不能随机访问任意字节,读取和写入都按页(page)进行,一个页常见是2KB、4KB或者更大;擦除则按块(block)进行,一个块由多个页构成,常见是128KB、256KB甚至1MB。这个组织结构决定了NAND适合顺序读写、大文件存储,不适合像代码那样频繁随机访问。
NAND真正麻烦的地方在于"底层工艺不保证可靠"。因为NAND堆叠密度高、单元间距小,天然会存在坏块(bad block),出厂时就有,使用过程中还会产生新的坏块;同时读数据可能出现比特翻转(bit flip),需要ECC(纠错编码)来检测和修复。所以裸的NAND Flash必须搭配坏块管理、ECC、磨损均衡、垃圾回收这一整套FTL(Flash Translation Layer)逻辑才能稳定使用。这些逻辑如果放在主控CPU里做,会占用大量CPU时间;放在专用SSD主控或eMMC控制器里做,就是市面上所有U盘、存储卡、SSD的底层原理。
一个很有帮助的类比:把NAND Flash想成一座码头仓库,你只能一箱一箱地搬货(按页读写),不能打开某个箱子从里面拿一个零件(按字节随机读);而且这个仓库本身管理不善,有些角落是塌的(坏块),有些货物来说不定上面被水打湿了(比特翻转),你必须请一套仓库管理系统(FTL)来记账、修补、定期搬运防止货箱腐烂(磨损均衡)。相比之下,NOR Flash就是一个小货架,东西摆得整整齐齐,随手就能拿,但放不了多少货。
4.3 这对冤家的选型细节:接口、寿命、控制器成本
在实际产品选型里,NOR和NAND从来不是"谁取代谁"的关系,而是各司其职。NOR容量往往做到1Mb到256Mb,接口除了并行的还有QSPI串行封装,引脚少、读速度快,很适合放在MCU旁边当外部启动代码存储;NAND容量从1Gb起步,往上能到几Tb,接口有并行8位/16位和串行(如SPI NAND),密度和单位成本优势极其明显,适合存数据、日志、媒体文件、操作系统文件系统。如果产品对启动时间要求极高,且固件又大,很多方案会采用"Boot ROM/NOR启动 + NAND存数据"的组合,这个思路非常常见。
另外要注意的是,同样是NAND,颗粒类型不同,寿命也差得很远。SLC是1个单元存1bit,寿命最长,通常几万到十万次擦写;MLC是1个单元存2bit,寿命几千到一万次;TLC是3bit,寿命几百到一千次;QLC是4bit,寿命就更短了。所以别看到"这颗NAND便宜一半"就换,得算明白你要写多少数据、产品生命周期内要多久刷一遍磨损均衡。我之前在某数据采集器项目里选了TLC颗粒,因为产品打算用五年、每天写几十MB日志,结果固件团队测试阶段就把磨损均衡算法放弃了,最后只能换回“带FTL的eMMC”方案,成本和开发周期都远超预期。
还有一个容易漏掉的点:NOR和NAND的"擦写次数"指的都是整块擦除后的重新编程次数,而且高温下数据保留时间会显著缩短。工业级和消费级Flash的规格就是这么分出来的:工业级往往保证更宽的温度范围和更长的数据保持时间。你在选型时看datasheet里的"Data Retention"那一栏,不是看常温下的,而是看最高工作温度下的保持时间。同为NOR Flash,消费级在85摄氏度下可能只保证10年,工业级能到20年甚至更长,价格差也由此而来。
5. 实践选型跑一圈:这套存储器搭配方案我用了好几年
5.1 一个典型智能控制器的完整内存地图
我拿最近做的一个带触摸屏的智能设备举例。主控选的是带内部Flash和SRAM的MCU,但为了跑HMI界面和大缓冲,外部还挂了SDRAM,固件存储在内部Flash里,出厂参数存储在另一片EEPROM里,日志和大屏图片资源放到外部的SD NAND里。整个存储布局是这样的:
| 数据类别 | 存放介质 | 为什么选它 | 注意事项 |
|---|---|---|---|
| 固件代码 | 内部NOR Flash(MCU集成) | 支持XIP,上电直接执行,速度快、封装小 | 写前必须按扇区擦除,OTA升级时要设计双备份 |
| 全局变量/堆栈 | 内部SRAM | 高速、时序简单、无需刷新 | 容量有限,需要看map文件控制使用率 |
| 显示缓冲/大算法缓冲 | 外部SDRAM | 容量大、单位价格低、吞吐量高 | 初始化时序复杂,需要认真配置刷新 |
| 出厂参数/校准数据 | 独立EEPROM(或Flash模拟) | 按字节改写、寿命高、掉电保持 | 注意写次数,不要高频写入 |
| 日志/图片/媒体 | SD NAND(带FTL的NAND) | 容量大、可靠性由NAND控制器承载,文件系统友好 | 注意文件系统掉电保护,防止日志目录损坏 |
| 字库/关键配置 | NOR Flash(外部QSPI) | 随机读取快、可靠性高 | 可考虑映射成只读区域,防止误写 |
这份搭配用了好几个项目都没出过大问题。它体现了一个思路:按数据类型把"速度、密度、寿命、可修改性"四个维度逐一匹配,不要试图用一颗Flash包打天下。NOR负责代码和关键只读数据,SDRAM负责大块运行数据,带控制器的NAND负责大量外部数据存储,EEEPROM负责需要频繁小量改写的参数,这四者分工明确,各自都待在各自性价比最高的位置。
如果产品功耗要求特别苛刻,外部SDRAM会用得谨慎,因为它的刷新电流在待机时也省不掉,显示缓冲如果一定要用,可以考虑LPDDR或让LCD进入自刷新模式,或者降级采用带自刷新的PSRAM。我那个低功耗版本,最后确实把SDRAM换成PSRAM,尽管吞吐量略降,但待机功耗从毫安级降到了微安级,整个产品形态都变了。
5.2 我保留的几条存储选型铁律
这几条是我从各种教训里沉淀出来的,算不上系统方法论,但实用。
第一,能放在内部就不放外部。内部Flash和SRAM的走线、时序、可靠性都由MCU厂家负责,你只要在配置工具里勾选就行;外部存储一接上,就要面对PCB布局、信号完整性、初始化时序、坏块管理等一堆问题。外部存储是为了解决内部容量不足,不是为了看起来更专业。
第二,所有NAND类或者大容量Flash应用,都认真算刷新和磨损。哪怕你不懂FTL内部实现,也要留好“分区规划”的余地,比如日志分区、配置分区、升级分区分开管理,避免某个分区被频繁擦写拖垮整盘寿命。
第三,调试存储相关硬件时,不要跳过"读写测试"环节。任何外扩的SDRAM、NOR、NAND,上电后先跑一遍标准的地址线、数据线测试(地址线相邻短路、数据线粘连都能测出来),再接文件系统、再接应用。这一步省下来的时间,永远比花费的时间多。有一次我在某产品上跳过这一步直接调文件系统,结果写文件随机报错,排查了很久才定位到是数据线有一根虚焊,如果提前跑32位数据线测试,可能半小时就结束了。
第四,多看map文件和编译报告。很多存储相关的问题不是芯片选错了,而是工程配置导致某个段超出了物理存储范围,链接器和编译器其实已经警告过了,却被大家忽略了。养成每次编译后看Flash/RAM占用率的习惯,可以避开大量的隐性Bug。
6. 我是怎么把这一串名字理成一条记忆主线的
看到这里,前面那些含义你现在应该能梳理出一个主干了:先把存储分成"掉电丢不丢"两条线,RAM线路只关心速度,ROM线路只关心数据保持;再在RAM里区分"要不要刷新"(SRAM不用、DRAM要用),DRAM里分"同时钟不"(SDRAM同步了,DDR也是它的后辈);在ROM线路里,Flash接过现代大旗,再按"能不能原地执行、按字节读"还是"按页按块读、密度更极致"来区分NOR和NAND。这一条主线靠场景串起来,比死记硬背名词好记得多。
最后再分享一个我做板子调试时的习惯:上电先不急着跑业务代码,而是先判断板上每一颗存储芯片的"身份归属"——它是掉电保持的ROM/Flash,还是运行时才需要的RAM?它需要刷新、需要ECC、需要坏块管理吗?把这些问题写在原理图上对应的网络标号旁边,作为BOM评审时的注释。这个方法帮我在后续维护和转交接时省了很多口水,因为每一个后来接手的人都能一眼看懂:为什么这颗芯片在这,为什么这颗芯片必须在那。
存储这东西不深奥,无非是芯片内部的"保持"和"速度"在做权衡,产品上的"成本"和"功耗"也在做权衡。把权衡做好了,你手里的方案真正落地的那一天,自然会理解这些名词背后那些实实在在的本事。