去年做一套工业数据采集终端的时候,被“掉电数据保存”这件事折腾得够呛。现场要求设备状态、校准参数、故障日志都不能丢,而且允许的技术人员一天改几十次参数。最开始用的 SPI EEPROM,写一个字节要等内部擦写,改一次参数列表卡顿明显;换过 SPI NOR Flash,写之前还得先擦整个扇区,日志循环写根本扛不住。后来换到 MR25H40CDF 这颗 SPI MRAM,配合手头现有的 STM32F302VC 主控,整个存储链路终于清净了。这篇就把这个组合从选型、电路、驱动到现场排障的完整过程整理出来,给同样在做工业和嵌入式数据存储的朋友做个参考。
先说结论:如果你也需要非易失存储、但不想处理擦写寿命、不想等页编程时间、又希望容量比常见 FRAM 大一点,MRAM 这颗芯片会是很好的选择。MR25H40CDF 是 Everspin 的 4Mbit SPI MRAM,容量 512KB,接口简单,读写速度接近 SRAM 的水平,而且写次数基本不限制。STM32F302VC 是带 Cortex-M4F 内核的工业级 MCU,72MHz 主频,SPI 接口足够把这颗 MRAM 跑满。整套方案体积小、成本可控、代码量也不大,非常适合工业控制器、仪表、能源管理设备这类场景。
1. 为什么要在这种场景里选MRAM而不是EEPROM或Flash
1.1 工业数据存储的三个硬指标
工业设备里的数据存储,和消费电子产品不太一样。消费级产品丢一两条日志,顶多影响体验;工业设备丢了校准参数,可能整台设备直接不在精度范围内,甚至停机。我归纳下来有三个硬指标:
第一是掉电不能丢。设备随时可能在写数据的中途断电,存储介质必须是非易失的,而且写入过程不能依赖一个漫长的等待窗口。
第二是寿命要够长。产线设备一天可能记录几千条事件,一个月就是十几万次写入。传统 EEPROM 标称擦写寿命常见值是 10 万次到 100 万次,听上去不少,但实际跑起来,如果写策略设计得不好,几个月就可能摸到寿命上限。
第三是写入速度要跟得上现场节奏。工业触摸屏改一次参数,上位机下发一组数据,存储芯片必须在几十毫秒内完成应答,不能让人感觉界面卡住。
1.2 EEPROM、NOR Flash、FRAM各自卡在哪里
EEPROM 的问题是慢和寿命。经典的 I2C EEPROM 写一个字节内部要经历一个擦写周期,典型时间是 5 到 10 毫秒,写 128KB 数据理论上要几十秒,更别提频繁写入对寿命的消耗。SPI 接口的 EEPROM 好些,但本质还是一个字节级别擦写的结构,写大块数据依然受内部状态机限制。
NOR Flash 的问题是擦除粒度太大。W25Q128 这类芯片写之前必须先擦扇区,一个扇区通常是 4KB,擦除时间动辄几百毫秒。你只想改其中 16 个字节的参数,也得先读出整个 4KB、擦除、再写入。日志类应用更别扭,因为写日志是高频小量追加,Flash 的分区管理和磨损均衡做不好,很快就出问题。
FRAM 其实是一个很好的选择,无限次写入、掉电不丢、速度也快。但 FRAM 的问题在于容量和价格,常见型号容量段偏小,做到 4Mbit 级别的 FRAM 选择很少,单价也更贵。
1.3 MRAM 的核心优势正好补上这几个缺口
MRAM(磁阻随机存取存储器)的原理是用磁性隧道结来存储数据,数据以磁化方向的形式存在,而不是电荷形式。这意味着它天生具备三个特性:掉电后磁性状态不会消失,所以是非易失的;写入过程是物理上改变磁化方向,不需要先擦除,所以没有写前擦除这个动作;磁化翻转本身没有机械磨损和化学衰减,写次数可以做到极高水平。
MR25H40CDF 这颗芯片把这三个特性落到了具体产品里。容量 512KB,对工业设备来说,既能存参数表,也能存一段时间的运行日志。SPI 接口让 MCU 侧接线非常直接,不需要额外控制器。写一个字节和写连续数据块都不需要等待内部擦写完成,只要 SPI 时钟在跑,数据就进去了。
2. MR25H40CDF 关键特性与选型依据
2.1 容量、接口与封装细节
MR25H40CDF 的容量是 4Mbit,组织方式是 512K x 8bit。外部通过标准 SPI 接口访问,支持 Mode 0(CPOL=0,CPHA=0)和 Mode 3(CPOL=1,CPHA=1)两种模式,这点和大部分 SPI NOR Flash 兼容,主控侧配置起来几乎零成本。
封装是 8 引脚 DFN,尺寸很小,适合空间受限的工业板卡。供电电压典型值 3.3V,工业级版本的工作温度范围通常能覆盖 -40℃ 到 105℃左右,具体要以选定的批次为准。对户外机柜、设备内部靠近发热源这类场景来说,这个温度范围比很多商用级存储芯片稳妥。
需要注意的一个规格点是最高 SPI 时钟。MR25H40CDF 在 3.3V 供电下支持到几十 MHz 级别,具体数值以官方数据手册为准。实际使用时我建议留出至少 2 到 4 倍裕量,不是因为芯片跑不上去,而是工业现场走线长、干扰多,把时钟降下来对信号完整性非常有利。
2.2 命令集速查:比 Flash 简单得多
MR25H40CDF 的命令集和 SPI NOR Flash 有点像,但去掉了擦除命令。最常用的可以列一个表:
| 命令名 | 命令字 | 作用 |
|---|---|---|
| WREN | 0x06 | 写使能,写操作前必须发送 |
| WRDI | 0x04 | 写禁止,关掉写使能状态 |
| RDSR | 0x05 | 读状态寄存器 |
| WRSR | 0x01 | 写状态寄存器 |
| READ | 0x03 | 从指定地址读数据,可连续读 |
| WRITE | 0x02 | 向指定地址写数据,可连续写 |
WREN 这个命令和 Flash 一样,每次写数据之前都要先发一次写使能。这是最容易漏掉的一步,漏掉之后写操作会被芯片忽略,读出来的还是旧数据和全 0xFF 之类的内容。
地址字段是 24 位,但芯片有效地址范围只有 0x000000 到 0x07FFFF,也就是 512KB。超出范围的地址会被截断或者回卷,这一点后面会专门说,因为现场出过问题。
2.3 状态寄存器与写保护机制
状态寄存器是 8 位,上电默认是 0x00,代表全片可写。寄存器里主要包含写保护使能位(WPEN)和块保护位(BP2:BP0),和外部 WP 引脚配合使用。
生产环境中,如果设备参数写好后不希望现场人员误改,可以把块保护位置 1,这样指定区域就不能写了。但我个人建议在开发调试阶段不要开保护,因为每次改固件还要先去解除保护,麻烦。量产出货前再根据实际要求决定要不要把整个存储区锁住。
写状态寄存器本身也需要先发 WREN 命令,流程是:WREN,然后发送 WRSR 命令和新的状态寄存器值。如果 WP 引脚被拉低,写状态寄存器的操作会被硬件禁止,这个机制和 SPI Flash 的写保护引脚类似。
2.4 一个容易踩的坑:连续读写时地址回卷
这是 MR25H40CDF 使用中最容易忽略的行为。普通 SPI NOR Flash 在跨页读或写时,有些芯片会在页边界停止,有些会回卷。MR25H40CDF 的连续读写如果越过 0x07FFFF,地址会自动回到 0x000000,中间不会有任何报错。
这意味着如果你的软件没有在驱动层做边界检查,写日志写到最后一个字节后继续追加,数据会直接写到文件头的位置,把最早的参数覆盖掉。我在第一版驱动里没考虑这个问题,结果运行日志模块把校准参数区域覆盖了,整台设备的数据全乱套。后来驱动里加了严格的地址范围检查,任何越界写入都直接返回错误码,不再向下传输数据。
3. STM32F302VC 侧硬件接线与电路设计
3.1 STM32F302VC 的资源回顾
STM32F302VC 是 ST 的工业级 MCU,Cortex-M4F 内核,主频 72MHz,带浮点运算单元。存储资源上,它有 256KB 内部 Flash 和几十 KB SRAM,跑一个中等复杂度的控制固件没有问题,剩下的内部资源刚好可以用来接外部存储。
外设方面,F302VC 有多个串口、CAN、SPI、I2C、ADC 和 DAC 资源。对工业设备来说,主控负责采样、通信、控制逻辑,外部 MRAM 负责参数和日志,任务边界非常清楚。选这颗 MCU 的另一个原因就是它和 MRAM 都是工业温度等级,整块板子的温区不需要降档。
3.2 SPI1 引脚映射与最小接线
我在这套设计里用的是 SPI1,因为 SPI1 在 F3 系列里挂在高速外设总线上,时钟可以跑得比 SPI2 更高,给以后提速留了空间。标准引脚映射是:
| STM32F302VC 引脚 | 功能 | 接到 MR25H40CDF |
|---|---|---|
| PA5 | SPI1_SCK | SCK |
| PA6 | SPI1_MISO | MISO(DO) |
| PA7 | SPI1_MOSI | MOSI(DI) |
| PA4 | 普通GPIO | CS(片选) |
CS 我没有用硬件 NSS,而是用了一个普通 GPIO 来软件控制。原因是软件控制 CS 的时序更灵活,可以严格保证事务开始和结束都在正确的电平上,不受 SPI 外设内部状态机影响。MRAM 的 HOLD 引脚接上拉到 VDD,WP 引脚在不需要写保护时也接 VDD,两个引脚都不能悬空。
3.3 电源与去耦设计
MRAM 供电用 3.3V,建议在电源引脚旁边放一个 100nF 陶瓷电容和一个 4.7uF 电解电容,位置尽量靠近芯片。工业板卡如果有大的继电器、电机等负载,电源线上容易有毛刺,可以在板级入口再加一个 TVS 管或者共模电感,把浪涌挡在存储芯片之前。
另外要强调的是接地。MRAM 和 MCU 之间的 SPI 走线属于高速数字信号,信号回流路径必须经过完整的地平面。如果板上地平面被槽孔切开,或者 SPI 走线绕了一个大圈,MISO 信号在高时钟下可能采到错误的电平。我第一次画板就是图好看,把 SPI 线拉得很长绕过了一个连接器,结果 18MHz 时钟下数据偶发错误,降频到 4.5MHz 才稳定,后来重新布线才彻底解决。
3.4 布局布线经验三条
第一条,SPI 四根线尽量并行等长,长度控制在 5cm 以内。第二条,CS 线可以稍微长一点,但不能和 SCK 靠太近,避免 CS 切换瞬间串扰到时钟。第三条,如果 MISO 线上有明显的噪声毛刺,可以在 MCU 引脚附近串联一个 33 到 100 欧姆的电阻,或者在 MISO 和地之间加一个 10pF 到 20pF 的小电容,牺牲一点上升沿换稳定。
4. SPI 驱动完整实现与读写流程
4.1 SPI 外设初始化配置
我用的是 HAL 库,初始化 SPI1 为模式 0、主模式、软件 NSS、8 位数据。关键的几个参数如下:
hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; HAL_SPI_Init(&hspi1);BaudRatePrescaler 选 8,也就是把 SPI1 时钟 8 分频。如果 SPI1 的总线时钟是 72MHz,那 SPI 时钟就是 9MHz。这个频率读写 512KB 数据大约几十毫秒,对参数存储场景绰绰有余,而且信号裕量很足。
还有一个必须做的事是确认 SPI 引脚在 GPIO 初始化时配置为复用功能。很多人初始化完 SPI 外设,忘了把 PA5、PA6、PA7 的 GPIO 模式设成 GPIO_MODE_AF_PP,结果 SCK 根本没有时钟输出,查半天查不出来。
4.2 基础命令封装:写使能与状态寄存器操作
先写一个最简单的写使能函数,它是一切写操作的前提:
#define MRAM_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define MRAM_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) void MRAM_WriteEnable(void) { uint8_t cmd = 0x06; MRAM_CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 10); MRAM_CS_HIGH(); }注意 CS 的操作方式是把每次事务隔离起来。发送写使能命令时 CS 拉低、发送完立即拉高,形成完整的一个命令周期。不要在一次 CS 拉低周期里既发 WREN 又发后续写命令,虽然有些芯片能容忍,但严格的时序习惯可以避免很多潜在问题。
读状态寄存器的函数类似,只是需要用一个假的发送字节把 MISO 上的数据带出来:
uint8_t MRAM_ReadStatus(void) { uint8_t cmd = 0x05; uint8_t dummy = 0x00; uint8_t status = 0x00; MRAM_CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 10); HAL_SPI_TransmitReceive(&hspi1, &dummy, &status, 1, 10); MRAM_CS_HIGH(); return status; }初始化阶段,我习惯先读一次状态寄存器,确认能读到数值而不是全 0xFF。如果读出来全 0xFF,基本上就是硬件没接对,或者芯片供电有问题。
4.3 数据读写函数:从单字节到连续块
单字节写和连续块写本质上是同一个动作。写操作流程是:CS 拉低,发送 WRITE 命令(0x02),再发送 24 位地址(高字节在前),然后发送数据字节。写单个字节之后 CS 拉高;写多个字节时,CS 可以在整个数据块发送完后再拉高。
void MRAM_WriteData(uint32_t addr, uint8_t *buf, uint32_t len) { MRAM_WriteEnable(); uint8_t header[4]; header[0] = 0x02; header[1] = (addr >> 16) & 0xFF; header[2] = (addr >> 8) & 0xFF; header[3] = addr & 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(&hspi1, header, 4, 20); HAL_SPI_Transmit(&hspi1, buf, len, 100); MRAM_CS_HIGH(); }这里有一个关键点:MRAM 的写操作没有“页缓冲”的概念,不需要像 NOR Flash 那样按页对齐。理论上只要 CS 保持低电平,可以连续写任意长度的数据,芯片会按地址自动递增。实际写大量数据时,我会强制每次调用不超过剩余有效地址长度,比如先算出当前地址到 0x07FFFF 还剩多少字节,超过部分分两次写完。这主要是防地址回卷,不是芯片有什么限制。
读数据流程更简单,不需要写使能:
void MRAM_ReadData(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t header[4]; header[0] = 0x03; header[1] = (addr >> 16) & 0xFF; header[2] = (addr >> 8) & 0xFF; header[3] = addr & 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(&hspi1, header, 4, 20); HAL_SPI_Receive(&hspi1, buf, len, 100); MRAM_CS_HIGH(); }HAL 的 HAL_SPI_Receive 在接收时会自动发全 0 字节来产生时钟,所以不需要额外维护 dummy 缓冲。这个函数在速度上不如 DMA 版,但数据量不大时已经足够用。
4.4 掉电保存策略与事务状态机
MRAM 本身掉电不丢,但“保存数据”这件事不能只依赖芯片,还得保证写入过程的完整性。我的做法是在应用层设计一个简单的保存协议:
先写数据区,再写一个 8 字节的完整标志区。标志区包含固定魔数、数据长度、CRC 校验值。上电读取时,先读标志区,魔数正确且 CRC 校验通过,才认为数据区有效;否则回退到备份区。
这套协议解决了一个实际问题:如果写入过程中系统掉电,MRAM 内部可能已经写入了部分数据,但标志区的魔数和 CRC 永远不会被写完整。下次上电读到一个不完整的标志,就知道这次保存失败了,不会拿残缺数据当有效参数用。
另一个值得推荐的策略是双备份,参数区放两份,每次写入交替选择当前活动备份。这样即使某次写入把当前备份区写到一半断电,另一个完整的备份还在,系统可以自动回滚。代价是芯片空间多占一倍,对 512KB 来说完全可接受。
5. 现场实测、遇到的问题与排查技巧
5.1 一个实际案例:数据采集终端的存储分区
我做过的那套采集终端,MRAM 的 512KB 空间这样分配的:前 64KB 放参数区,分 A/B 两份,每份 32KB;中间 128KB 放运行日志,采用环形覆盖;末尾留作预留和固件配置信息。日志每 10 秒写一条,每条约 32 字节,128KB 能存 4000 多条,覆盖一个多月的数据,足够回溯现场了。
这个分区方式的好处是日志区和参数区完全隔离,即使日志环形写到了边界,也不会影响参数区。上一版用 NOR Flash 时,日志区的磨损均衡和参数备份是耦合在一起的,代码复杂度高,还出过擦除时序干扰导致参数丢失的问题。换 MRAM 之后,这个问题从根上消失了。
5.2 现场问题一:写进去的数据读出来全是 0xFF
第一次拿到新板子,烧好代码后发现写进去再读出来全是 0xFF。排查过程并不复杂:先量了 MRAM 供电电压,3.3V 正常;再用逻辑分析仪看 SPI 波形,发现 MOSI 上确实有命令和数据,但 MISO 一直是高电平。
最后定位到原因:PA6 的 GPIO 模式没有配置成复用功能,导致 MISO 引脚一直是高阻态,主控读回来的全是 1。这是一个很低级的错误,但也很容易犯。后来我把 SPI 引脚的 GPIO 配置检查放进了初始化代码的断言里,硬件调试时先通过一段自检代码,把四个引脚的复用功能逐一确认,才在第 一个环节堵住类似问题。
5.3 现场问题二:日志写到一半,参数区被覆盖
这个问题的根源就是前面说的地址回卷。日志模块用的是环形追加,指针走到 0x07FFFF 之后继续写,MR25H40CDF 直接把地址回卷到 0x000000,开始往参数区覆盖。参数区的魔数被冲掉后,设备重启时参数校验失败,整机处于异常状态。
修复方案很直接:驱动层加了范围检查,所有读写函数在进入 SPI 事务之前先判断地址和长度。如果 addr + len 超过 0x080000,函数直接返回错误码,不再发任何 SPI 命令。应用层收到这个错误码会把日志指针重置到 0x10000,开始新一轮环形覆盖。从那以后,再没有出现过跨界覆写。
5.4 现场问题三:高低温循环后偶发读写失败
设备在温箱里做 -40℃ 到 85℃ 循环测试时,偶尔出现一次读数据超时,概率大约千分之几。起初怀疑 MRAM 芯片本身有问题,后来查下来是电源问题。低温下电源模块输出下跌到 3.0V 左右,MRAM 工作在临界电压附近,SPI 高电平阈值也不稳定,导致通信失败。
解决方法是两件事:一是把 SPI 时钟从 18MHz 降到 9MHz,给信号电平留出更多裕量;二是在 MRAM 供电附近加了一个 100nF 的低 ESR 陶瓷电容,改善瞬态响应。调整后温箱跑了一周,没有再出现故障。
这个案例让我养成一个习惯:新设计中使用任何存储芯片,第一步就是在极限温度下做读写压力测试,用全 0x55、全 0xAA、递增地址模式各跑一轮,确认芯片在最差条件下依然稳定。
5.5 常见问题速查表
我把这个组合调试过程中的常见问题整理成一个表,方便现场快速定位:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 读回全 0xFF | SPI 引脚复用未配置 | 检查 GPIO 复用设置,用示波器看 MISO 波形 |
| 写入后读回全 0x00 | WREN 未发送或 CS 时序不对 | 检查写使能流程,确认 CS 低电平时长足够 |
| 数据偶发错位 | 地址越界发生回卷 | 检查读写函数边界判断 |
| 高低温下偶发失败 | 电源裕量不足 | 加强电源滤波,降低 SPI 时钟 |
| 待机电流偏高 | MRAM 未进入睡眠模式 | 发送 SLEEP 命令,确认 CS 时序 |
| 读状态寄存器超时 | MISO 线上有干扰 | 串阻或小电容滤波,检查走线长度 |
5.6 低功耗场景的睡眠命令
如果整机是电池供电或者有严格的待机功耗要求,MR25H40CDF 的睡眠模式就很有用。发送 SLEEP 命令(0xB9)后,芯片进入低功耗状态,静态电流明显下降。唤醒需要发送 WAKE 命令(0xAB),唤醒后要等一小段时间才能继续正常读写。
我实际测试下来,睡眠模式对电池供电的手持设备很友好。主控进入 STOP 模式前先发 SLEEP 命令,把 MRAM 也休眠,整机待机电流比不睡眠时低得多。需要注意的是,睡眠状态下的芯片不会响应常规的 READ 命令,所以软件里要处理好唤醒时序,不要刚发完 WAKE 就立刻读数据,留出足够的时间窗口。
6. 这套方案的扩展思路与量产经验
6.1 把 MRAM 当普通 RAM 用
MRAM 的读写速度和随机访问能力,让它不只是参数存储器。我在后面的项目里扩展了一种用法:运行中的关键状态变量,镜像一份到 MRAM 的固定区域。正常运行时,变量更新后同步写入 MRAM;设备意外掉电后重新上电,直接从 MRAM 恢复这些变量,相当于掉电记忆。
这种用法在传统 EEPROM 上根本不现实,因为频繁写寿命受不了;在 NOR Flash 上更不可能,因为要擦除。用在 MRAM 上就很自然,写次数不是瓶颈,速度也够快。比如设备正在执行的运动流程,掉电后恢复时可以直接从断点继续,不必每次都从头开始。
6.2 量产烧录与校验注意点
批量生产时,存储芯片里要烧录初始化数据。我的产线流程是:先用一个单独的量产工具通过 SPI 把默认参数和魔数写入 MRAM,再上电跑一遍自检,读取并 CRC 校验。如果自检不过,直接判定为不良品,避免流到现场。
这个环节还要注意产线的静电防护。DFN 封装芯片引脚小,插拔和贴片过程中容易积累静电。如果产线干燥,建议在测试工装接口加 ESD 保护器件,操作人员佩戴防静电手环。我曾经在冬天遇到过一批芯片读写不稳定,排查到最后是测试工装上静电积累导致的,加了防护后问题立刻消失。
6.3 芯片供应与替代方案
MR25H40CDF 不是唯一选择。如果你需要更大容量,可以看同系列的更高密度型号;如果需要 1.8V 供电的版本,也有对应产品线。选型时主要核对三点:容量、电压、温度等级,其他引脚兼容性基本一致。
不过我不建议轻易在量产项目里临时替换存储芯片。不同厂家的 MRAM/FRAM 虽然都叫 SPI 非易失存储,但命令集、状态寄存器定义、唤醒时间都有差异,驱动移植和温度测试都要重新做。一个项目里验证过的方案,除非有明确的成本或供货压力,否则保持稳定更划算。
回头看我在这套方案上踩过的坑,最值钱的教训其实不是某一条命令怎么发,而是“不把存储当普通外设对待”。MRAM 让好多存储管理问题消失了,但地址边界、掉电一致性、低温电压裕量这些工程问题,一个都不会少。把这些细节处理好,MR25H40CDF 加 STM32F302VC 的组合,在工业设备里会非常省心。