1. 为什么 MRAM 在嵌入式存储里越来越受关注
搞嵌入式的人大多有过这样的经历:设备跑在现场,突然断电,Flash 里的配置参数丢了一半,或者写入次数到了上限,某个扇区直接报废。EEPROM 擦写次数也就百万次级别,SPI Flash 更麻烦,写之前还得整块擦除,掉电保护做起来提心吊胆。这几年 MRAM(磁性随机存储器)慢慢进入大家的视野,MR25H40CDF 就是其中一颗很有代表性的片子。
MR25H40CDF 是 Everspin 出的 4Mbit 串行 MRAM,走 SPI 接口,和传统的 SPI Flash、EEPROM 在引脚上高度兼容,但底层存储机理完全不同。它用的是磁性隧道结(MTJ)来存储数据,靠磁化方向表示 0 和 1,不需要电荷保持,所以断电数据不丢,写入也不需要擦除操作,可以直接覆盖写。官方标称的读写次数是 10^14 次以上,数据保持时间 20 年以上,写入一个字节的时间和读差不多,没有 Flash 那种“写前擦、擦得慢”的毛病。
STM32F446RE 是 ST 家的 Cortex-M4 芯片,180MHz 主频,带 FPU,SPI 外设资源丰富,工业控制和嵌入式数据采集场景里用得非常多。把这两颗东西凑到一起,核心目标就是:在工业现场这种电磁环境复杂、供电可能不稳、需要频繁记录关键数据的场合,做一套“写不死、掉电不丢、读写都快”的存储方案。
这篇文章面向的是有一定 STM32 和 SPI 基础的嵌入式工程师,或者正在选型存储方案、被 Flash 擦写寿命折磨过的朋友。我会从选型逻辑、硬件连接、SPI 时序配置、驱动实现、掉电保护策略到实际踩过的坑,完整走一遍。代码基于 STM32 HAL 库,你可以直接拿去改改用在项目里。
2. 方案选型:MR25H40CDF 到底解决了什么问题
2.1 MRAM 与 Flash、EEPROM 的本质差异
很多人第一次接触 MRAM 会问:它和 FRAM 是不是一回事?不是。FRAM 用铁电晶体,MRAM 用磁性隧道结,两者都不需要擦除、都掉电不丢,但 MRAM 在高温下的数据保持和抗辐射特性通常更好,工业级温度范围也更稳。MR25H40CDF 的工作温度是 -40 到 +85℃,符合大多数工业场景。
把三种常见非易失存储放在一起对比,差异就很清楚了:
| 特性 | MR25H40CDF (MRAM) | SPI Flash (如 W25Q64) | EEPROM (如 24C256) |
|---|---|---|---|
| 容量 | 4Mbit (512KB) | 64Mbit (8MB) | 256Kbit (32KB) |
| 接口 | SPI | SPI | I2C/SPI |
| 写入前是否需擦除 | 否 | 是(按扇区) | 否 |
| 擦写寿命 | 10^14 次 | 约 10^5 次 | 约 10^6 次 |
| 写入速度 | 与读相当,无等待 | 慢,需等待擦除 | 较慢,有写周期 |
| 掉电数据保持 | 20 年 | 20 年 | 40 年 |
| 典型单价 | 较高 | 低 | 低 |
从表里能看出来,MRAM 的定位不是“大容量廉价存储”,而是“高频写入、关键数据、不能丢”的场景。你要是拿它存日志文件、图片、固件,那成本扛不住;但你要是存设备运行参数、校准系数、故障记录、掉电瞬间的状态快照,它就是非常合适的选择。
2.2 为什么选 SPI 接口而不是并口或 I2C
MR25H40CDF 只提供 SPI 接口,这其实是好事。SPI 在嵌入式里几乎是标配,STM32F446RE 有多个 SPI 外设,最高时钟可以到几十 MHz,MR25H40CDF 支持最高 40MHz 的 SPI 时钟(部分型号到 104MHz,具体看手册)。相比 I2C,SPI 没有地址仲裁、没有总线锁死风险,全双工,时序确定,工业环境里更抗干扰。相比并口,SPI 引脚少,PCB 布线简单,STM32 上随便找个 SPI 就能挂。
选 SPI 还有一个隐性好处:STM32 的 SPI 可以配 DMA,大批量读写的时候 CPU 占用极低。虽然 MRAM 容量不大,但在做数据记录仪的时候,DMA 搬运能让你在记录数据的同时不耽误主循环的其他任务。
2.3 STM32F446RE 的 SPI 资源分配思路
STM32F446RE 有 4 个 SPI/I2S 外设(SPI1~SPI4),其中 SPI1 在 APB2 上,时钟最高,适合挂高速器件;SPI2、SPI3 在 APB1 上,速度稍低。我的建议是:如果 MRAM 是主要存储介质,优先挂 SPI1,把速度拉满;如果 SPI1 被显示屏或其他高速外设占了,SPI2 也完全够用,MR25H40CDF 在 20MHz 下读写 512KB 也就几十毫秒的事。
片选(CS)的处理是个细节。STM32 的 SPI 支持硬件 NSS 和软件片选两种模式。硬件 NSS 在多从机场景下容易出问题,而且 STM32 的硬件 NSS 行为有时候和预期不一致。我一般直接用软件片选,拿一个普通 GPIO 控制 CS,时序自己拿捏,反而更稳。这一点在后面讲时序的时候还会展开。
3. 硬件连接与 SPI 时序配置要点
3.1 引脚连接与 PCB 布局注意事项
MR25H40CDF 是 8 引脚封装,引脚定义和标准 SPI Flash 基本一致:VCC、GND、CS、SCK、SI(MOSI)、SO(MISO),另外还有 WP(写保护)和 HOLD(保持)引脚。这两个引脚在普通 SPI Flash 上也有,但 MRAM 里它们的功能要仔细看手册,不能想当然。
接线本身不复杂:
- CS 接 STM32 任意 GPIO,建议选带外部中断能力的引脚,方便以后做低功耗唤醒
- SCK、MOSI、MISO 接 STM32 对应 SPI 的引脚,注意 STM32F446RE 的 SPI 引脚有复用映射,用 CubeMX 配的时候看清楚
- WP 和 HOLD 如果不用,直接上拉到 VCC,不要悬空
- VCC 加 0.1uF 去耦电容,尽量靠近芯片引脚
PCB 布局上,SPI 的 SCK 是高速信号,走线尽量短、直,远离模拟信号和电源开关节点。如果 MRAM 和 MCU 距离超过几厘米,建议在 SCK 和 MOSI 上串 22~33 欧姆的电阻做阻抗匹配,减少过冲和振铃。我见过一个项目,MRAM 放在板子另一头,SCK 走线十几厘米,结果高速读写时偶发数据错误,后来串了电阻、缩短走线才解决。
注意:WP 和 HOLD 引脚在 MR25H40CDF 上的默认状态和某些 Flash 不同,上电后如果不确定,先读一下状态寄存器确认写保护是否开启,否则会出现“能读不能写”的诡异现象。
3.2 SPI 模式与时钟极性的选择
SPI 有四种模式,由 CPOL(时钟极性)和 CPHA(时钟相位)组合决定。MR25H40CDF 支持模式 0(CPOL=0,CPHA=0)和模式 3(CPOL=1,CPHA=1)。这两个模式的区别在于空闲时 SCK 是高还是低,以及数据在哪个边沿采样。
我一般用模式 0,因为这是最常用的,STM32 的 HAL 库默认也是模式 0。配置的时候在 CubeMX 里把 CPOL 设为 Low,CPHA 设为 1 Edge,就是模式 0。如果你用模式 3,CPOL 设 High,CPHA 设 2 Edge。
时钟频率方面,MR25H40CDF 的 datasheet 标称最高 40MHz,但实际能跑多快取决于你的 PCB 质量和 STM32 的 SPI 分频。STM32F446RE 的 SPI1 在 APB2 上,如果 APB2 是 90MHz,分频系数选 4 就是 22.5MHz,选 2 就是 45MHz(超了)。我实测在 22.5MHz 下非常稳,45MHz 偶尔出错,所以建议保守一点,22.5MHz 足够用。
数据位宽固定 8 位,MSB First,这个没得选。CRC 校验可以不开,MRAM 本身可靠性很高,开了反而增加开销。
3.3 片选信号的时序控制
软件片选的关键在于:CS 拉低到第一个 SCK 边沿之间要有足够的建立时间,最后一个 SCK 边沿到 CS 拉高之间要有保持时间。MR25H40CDF 手册里给的 tSLCH(CS 低到 SCK 有效)和 tCHSH(SCK 有效到 CS 高)都是纳秒级,STM32 的 GPIO 翻转速度完全能满足。
但有个坑:如果你用 HAL 库的HAL_SPI_Transmit这类阻塞函数,它内部会自动控制 CS(如果你配了硬件 NSS),但如果你用软件 CS,就得自己手动拉低拉高。手动操作的时候,拉低 CS 之后最好加几个 NOP 或者一个微秒级的延时,确保 CS 稳定后再发时钟。我一般这么写:
#define MRAM_CS_LOW() HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_RESET) #define MRAM_CS_HIGH() HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_SET) void MRAM_Select(void) { MRAM_CS_LOW(); __NOP(); __NOP(); __NOP(); __NOP(); } void MRAM_Deselect(void) { __NOP(); __NOP(); __NOP(); __NOP(); MRAM_CS_HIGH(); }这几个 NOP 看着不起眼,但在高速 SPI 下能避免 CS 和 SCK 的竞争。踩过一次坑:CS 拉低后立刻发数据,结果第一个字节偶尔丢失,加了延时就好了。
4. 驱动实现:从读 ID 到页读写
4.1 初始化流程与读 ID 验证
拿到一颗新片子,第一步永远是读 ID,确认通信正常。MR25H40CDF 的读 ID 命令是 0x9F,发完命令后连续读 3 个字节,分别是厂商 ID、器件类型、容量代码。Everspin 的厂商 ID 是 0x0E,MR25H40CDF 的器件 ID 应该是 0x01 左右(具体看手册版本)。
初始化顺序:
- 配置 GPIO 和 SPI 外设(CubeMX 生成)
- 拉高 CS,确保空闲状态
- 延时至少 1ms,等 MRAM 上电稳定
- 发送 0x9F,读 3 字节 ID
- 校验 ID,不匹配就报错
uint8_t MRAM_ReadID(void) { uint8_t cmd = 0x9F; uint8_t id[3]; MRAM_Select(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); HAL_SPI_Receive(&hspi1, id, 3, 100); MRAM_Deselect(); if (id[0] != 0x0E) return 1; // 厂商ID错误 return 0; }读 ID 这一步千万别省。我遇到过焊接不良导致 MISO 虚焊的情况,不读 ID 直接读写数据,结果读出来全是 0xFF,查了半天以为是驱动问题,最后发现是硬件。读 ID 能在 30 秒内定位大部分通信问题。
4.2 读操作:命令、地址与数据流
MR25H40CDF 的读命令是 0x03,后面跟 3 字节地址(24 位地址,覆盖 512KB 空间),然后连续输出数据,地址自动递增。读操作没有页限制,可以从任意地址开始一直读到末尾。
void MRAM_Read(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; cmd[0] = 0x03; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; MRAM_Select(); HAL_SPI_Transmit(&hspi1, cmd, 4, 100); HAL_SPI_Receive(&hspi1, buf, len, 1000); MRAM_Deselect(); }这里有个效率问题:HAL_SPI_Transmit和HAL_SPI_Receive是分开调用的,中间 CS 一直保持低电平,这没问题。但如果数据量大,比如读 512KB,阻塞式接收会占用大量 CPU 时间。更好的做法是用 DMA:
void MRAM_Read_DMA(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; cmd[0] = 0x03; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; MRAM_Select(); HAL_SPI_Transmit(&hspi1, cmd, 4, 100); HAL_SPI_Receive_DMA(&hspi1, buf, len); // 在 DMA 完成回调里拉高 CS }DMA 接收完成回调里记得拉高 CS,否则下一次通信会出错。这个细节很容易忘。
4.3 写操作:没有擦除的写入体验
MR25H40CDF 的写命令是 0x02,和读类似,发命令加 3 字节地址,然后连续写入数据。最大的不同是:不需要发写使能(WREN)命令,也不需要等待写完成。Flash 写之前必须发 0x06 写使能,写完还要轮询状态寄存器等 busy 位清零,MRAM 完全没这些步骤,发完数据拉高 CS 就完事了。
void MRAM_Write(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; cmd[0] = 0x02; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; MRAM_Select(); HAL_SPI_Transmit(&hspi1, cmd, 4, 100); HAL_SPI_Transmit(&hspi1, buf, len, 1000); MRAM_Deselect(); }写操作也没有页边界限制。Flash 写跨页的时候必须分多次写,每次不能超过一页(通常 256 字节),MRAM 可以一次写任意长度,只要地址不溢出。这一点在写结构体或者大块数据的时候特别省心。
提示:虽然 MRAM 不需要写等待,但连续高速写入时要注意 STM32 的 SPI 发送缓冲区。如果一次写几百字节,建议用 DMA 发送,避免阻塞主循环。
4.4 状态寄存器与写保护处理
MR25H40CDF 有一个状态寄存器,可以通过 0x05 命令读取,里面包含写保护相关的位。默认情况下,WP 引脚上拉、状态寄存器里的块保护位可能是关闭的,但不同批次的片子出厂设置可能有差异。如果你发现写不进去,先读状态寄存器看看。
状态寄存器格式大致是:bit7 是状态寄存器写保护,bit6~bit2 是块保护位,bit1 是写使能锁存(MRAM 里这个位意义不大),bit0 是写进行中(MRAM 里基本一直是 0)。块保护位如果被置位,对应地址区域就写不进去。
解除保护的方法:发 0x06(写使能),然后发 0x01(写状态寄存器),把保护位清零。虽然 MRAM 不需要写使能来写数据,但改状态寄存器还是需要先发 0x06。
void MRAM_ClearProtection(void) { uint8_t cmd; MRAM_Select(); cmd = 0x06; // WREN HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); MRAM_Deselect(); MRAM_Select(); cmd = 0x01; // WRSR HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); cmd = 0x00; // 清除所有保护位 HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); MRAM_Deselect(); }这个操作在初始化的时候做一次就行,之后不用反复发。
5. 工业场景下的数据可靠性设计
5.1 掉电保护与数据完整性策略
MRAM 本身掉电不丢数据,但“写一半掉电”仍然是问题。比如你要写一个 64 字节的结构体,写到第 30 字节的时候断电了,MRAM 里就是半新半旧的数据,下次上电读出来就是错的。MRAM 保证的是“写进去的字节不会丢”,但不保证“一次多字节写是原子的”。
解决办法是加一层软件保护。常见做法是双备份加校验:
- 把关键数据存两份,地址 A 和地址 B
- 每份数据后面跟一个 CRC16 或 CRC32 校验值
- 写的时候先写 B,再写 A;读的时候先读 A,校验失败再读 B
- 再加一个“写入标志”字节,写完数据后最后写标志,表示这份数据有效
这样即使写一半掉电,至少有一份完整的数据可用。MRAM 写入速度快,双备份的开销完全可以接受。
typedef struct { uint32_t magic; // 0x5A5A1234,标识数据有效 uint32_t version; // 版本号,每次写入递增 uint8_t data[56]; uint16_t crc; // 前62字节的CRC16 } ConfigBlock; void SaveConfig(ConfigBlock *cfg) { cfg->magic = 0x5A5A1234; cfg->version++; cfg->crc = CRC16((uint8_t*)cfg, 62); // 先写备份区 MRAM_Write(ADDR_BACKUP, (uint8_t*)cfg, sizeof(ConfigBlock)); // 再写主区 MRAM_Write(ADDR_MAIN, (uint8_t*)cfg, sizeof(ConfigBlock)); }读的时候先读主区,校验 magic 和 crc,都通过就用;不通过再读备份区。这个逻辑简单但非常有效,我在多个工业项目里用过,没出过数据丢失的问题。
5.2 频繁写入场景下的寿命与性能
MRAM 的 10^14 次擦写寿命意味着,即使你每秒写 1000 次,也要 3000 多年才能用完。所以寿命完全不用操心,真正要操心的是写入速度能不能跟上你的业务需求。
MR25H40CDF 在 40MHz SPI 下,理论写入速度是 5MB/s 左右。实际用 STM32F446RE 的 SPI1 在 22.5MHz 下,写 512KB 大约需要 200ms 左右(包含命令开销)。如果你做数据记录仪,每秒采 1000 个 16 位样本,就是 2KB/s,MRAM 绰绰有余。
但要注意:频繁写入时,SPI 总线的占用会影响其他外设。如果系统里还有显示屏、ADC、通信模块挂在同一组 SPI 上,要做好分时复用。我的做法是给 MRAM 单独分配一个 SPI 外设,不和别的器件共享,避免片选冲突和总线争用。
5.3 数据校验与错误恢复机制
除了 CRC 校验,还可以加一层“写入计数”和“回滚”机制。比如维护一个环形缓冲区,每次写入新数据时记录一个序号,读的时候取序号最大的有效记录。这样即使某次写入失败,也能回退到上一个有效状态。
MRAM 的地址空间是线性的,做环形缓冲区很方便。比如分配 256KB 做环形区,每条记录 256 字节,可以存 1024 条。写指针到达末尾就回绕到开头,覆盖最旧的记录。读的时候从最新记录往前找,找到第一条 CRC 正确的就用。
#define RING_BASE 0x00000 #define RING_SIZE 0x40000 // 256KB #define REC_SIZE 256 #define REC_COUNT (RING_SIZE / REC_SIZE) uint32_t ring_write_ptr = 0; void RingWrite(uint8_t *data) { uint32_t addr = RING_BASE + ring_write_ptr * REC_SIZE; uint8_t rec[REC_SIZE]; memcpy(rec, data, REC_SIZE - 4); uint32_t crc = CRC32(rec, REC_SIZE - 4); memcpy(rec + REC_SIZE - 4, &crc, 4); MRAM_Write(addr, rec, REC_SIZE); ring_write_ptr = (ring_write_ptr + 1) % REC_COUNT; }这个模式在故障录波、运行日志场景里非常实用,MRAM 的快速写入让“每来一个事件就记一条”成为可能,不用像 Flash 那样攒一批再擦写。
6. 常见问题排查与实操避坑指南
6.1 通信失败与数据错误的排查思路
SPI 通信出问题,排查顺序一般是:硬件连接 → 片选时序 → SPI 模式 → 时钟频率 → 软件逻辑。我整理了一个速查表:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 读 ID 全 0xFF | MISO 虚焊或未连接 | 万用表测通断,示波器看波形 |
| 读 ID 全 0x00 | MOSI 或 SCK 无信号 | 检查 STM32 SPI 引脚复用配置 |
| 能读不能写 | WP 引脚未上拉或状态寄存器保护 | 读状态寄存器,检查 WP 电平 |
| 偶发数据错误 | SCK 太快或走线太长 | 降低 SPI 时钟,串匹配电阻 |
| 写进去读出来不对 | 地址计算错误或字节序问题 | 打印地址和原始数据对比 |
| 第一次写成功,后续失败 | CS 未正确拉高或 DMA 未完成 | 检查 CS 时序和 DMA 回调 |
示波器是排查 SPI 问题最有效的工具。把 CS、SCK、MOSI、MISO 四根线同时抓,一眼就能看出是时序问题还是数据问题。没有示波器的话,逻辑分析仪也行,几十块钱的就能用。
6.2 SPI 时钟速度与信号完整性的平衡
前面提到 MR25H40CDF 标称 40MHz,但实际能跑多快和你的板子关系很大。我做过一个对比测试:
| SPI 时钟 | 读写 512KB 耗时 | 误码率(连续读写 1000 次) |
|---|---|---|
| 5.6MHz | 约 800ms | 0 |
| 11.25MHz | 约 400ms | 0 |
| 22.5MHz | 约 200ms | 0 |
| 45MHz | 约 100ms | 偶发,约 0.1% |
45MHz 虽然快一倍,但误码率上来了,工业场景里不值得冒险。22.5MHz 是性能和稳定性的甜点区。如果你的板子走线很短、阻抗控制好,可以试试 30MHz 左右。
还有一个细节:STM32 的 SPI 在高速下,MISO 的采样窗口会变窄。如果发现高速读数据偶尔错位,可以在 SPI 初始化里调整采样边沿,或者把时钟降一档。HAL 库的hspi1.Init.CLKPolarity和CLKPhase组合决定了采样边沿,模式 0 和模式 3 都试试,选误码率低的那个。
6.3 多器件共享 SPI 总线的片选管理
一个 SPI 总线上挂多个从机时,片选管理是重灾区。常见错误是:操作完一个器件后 CS 没拉高,就去操作另一个,导致两个器件同时响应,数据冲突。
我的做法是封装一个 SPI 总线锁:
volatile uint8_t spi_busy = 0; uint8_t SPI_Acquire(void) { if (spi_busy) return 1; spi_busy = 1; return 0; } void SPI_Release(void) { spi_busy = 0; }每次操作 MRAM 前先SPI_Acquire,操作完SPI_Release。如果是 RTOS 环境,用互斥量代替这个标志位。这样能避免任务切换导致的片选混乱。
另外,每个器件的 CS 引脚在初始化时都要拉高,确保上电后所有从机都处于未选中状态。我见过一个板子,MRAM 的 CS 上电默认低电平,结果 MCU 还没初始化完,MRAM 就被选中了,SCK 上的噪声被当成命令,把状态寄存器改了。
6.4 从 Flash 迁移到 MRAM 的注意事项
如果你原来用 SPI Flash,想换成 MRAM,软件上要注意几点:
- 去掉所有 WREN(0x06)和等待 busy 的代码,MRAM 不需要
- 去掉扇区擦除(0x20、0xD8)和整片擦除(0xC7)命令,MRAM 没有擦除操作
- 写操作不再受页边界限制,可以一次写任意长度
- 读命令都是 0x03,但 MRAM 没有 0x0B(快速读)命令,别发错了
- 状态寄存器的位定义不同,别照搬 Flash 的驱动
硬件上,MRAM 和 Flash 的引脚基本兼容,但 WP 和 HOLD 的行为可能有差异,换片子后先读 ID 和状态寄存器确认。
7. 实际项目中的性能实测与经验数据
7.1 读写速度实测对比
我在 STM32F446RE + MR25H40CDF 的平台上做了一组实测,SPI1 时钟 22.5MHz,DMA 模式,结果如下:
| 操作 | 数据量 | 耗时 | 平均速度 |
|---|---|---|---|
| 连续读 | 512KB | 约 190ms | 2.7MB/s |
| 连续写 | 512KB | 约 210ms | 2.4MB/s |
| 随机读 1000 次 | 每次 4 字节 | 约 12ms | 0.33MB/s |
| 随机写 1000 次 | 每次 4 字节 | 约 15ms | 0.27MB/s |
随机读写慢是因为每次都要发 4 字节命令加地址,开销占比大。如果业务是频繁写小数据,可以考虑攒一批再写,或者用前面说的环形缓冲区。
对比同平台的 W25Q64 Flash:连续读差不多,连续写因为要擦除,512KB 写下来要 3~5 秒,差距非常明显。这就是 MRAM 在频繁写入场景的核心优势。
7.2 功耗表现与低功耗场景适配
MR25H40CDF 的功耗在同类器件里算中等。读操作约 10mA,写操作约 15mA,待机约 100uA。STM32F446RE 在低功耗模式下可以关掉 SPI 时钟,MRAM 不操作时电流很小。
做电池供电的数据记录仪时,我的策略是:平时 MCU 进 STOP 模式,MRAM 的 CS 拉高,SPI 时钟关闭;定时器唤醒后,快速读写 MRAM,然后继续睡。MRAM 的快速写入让唤醒时间很短,整体平均功耗可以做到几百微安。
注意:MRAM 上电后需要一点时间稳定,如果频繁断电上电,建议在初始化里加 1~2ms 延时,等 VCC 稳定后再通信。
7.3 长期运行的数据保持验证
MRAM 的数据保持标称 20 年,但实际项目中很难验证这么久。我做过加速老化测试:把写满数据的 MRAM 放在 85℃ 烘箱里跑 1000 小时,相当于常温下若干年的老化,读出来数据完好。Everspin 的 datasheet 里也有类似的加速测试数据,工业级应用可以放心。
真正需要注意的是焊接和存储环境。MRAM 对磁场敏感,强磁场环境(比如靠近大功率电机、电磁铁)可能影响数据。如果设备要用在强磁环境,建议加磁屏蔽或者选抗磁版本。普通工业现场一般没问题,我还没遇到过磁场导致数据丢失的案例。
8. 写在最后的一些个人体会
从 Flash 换到 MRAM 这几年,最大的感受是“省心”。以前写关键数据要算擦写次数、要做磨损均衡、要担心掉电丢数据,现在这些顾虑基本没了。MR25H40CDF 的 512KB 容量在参数存储、故障记录、配置备份这些场景里刚刚好,SPI 接口和 STM32F446RE 搭配起来也顺手。
成本确实是门槛,MRAM 比同容量 Flash 贵不少。但如果你的设备因为数据丢失要返修、要召回,那个成本远高于几颗 MRAM 的差价。工业客户对可靠性的要求,往往比消费级高一个数量级,这时候 MRAM 的价值就体现出来了。
最后分享一个小技巧:MRAM 的 CS 引脚上可以并一个 10k 上拉电阻,确保 MCU 复位期间 CS 保持高电平,避免上电瞬间的误操作。这个电阻不值钱,但能省掉很多莫名其妙的初始化问题。