MRAM 这东西我第一次用是在一个工业数据采集板上。当时项目要求断电后关键配置不能丢还要能高频写入EEPROM 的擦写寿命和写入速度都不太够看FRAM 又贵得离谱。后来选型选到 MR25H40CDF4Mbit 的磁阻随机存储器SPI 接口跟 STM32F405RG 搭起来用整个方案跑下来相当舒服。这篇就把我从选型、硬件设计、驱动编写到实测踩坑的完整过程梳理一遍给正在做工业嵌入式存储方案的朋友一个参考。1. 为什么在这个场景下我选了 MRAM 而不是 EEPROM 或 Flash1.1 工业场景对存储的真实需求是什么先说说项目背景。这是一块用于工业现场的数据采集与控制板主控是 STM32F405RGCortex-M4 内核168MHz 主频1MB Flash192KB SRAM。板子需要周期性记录设备运行参数、校准系数、故障日志还要保存一些用户配置。这些数据有几个特点写入频繁、单次数据量小、断电不能丢、上电要能快速读取。一开始我考虑的是最常见的方案——SPI Flash 加 EEPROM 组合。大块日志放 Flash配置参数放 EEPROM。但实际评估下来问题不少。SPI Flash 的擦写寿命通常在 10 万次左右而且写入前必须先擦除整个扇区对于频繁更新几个字节的配置参数来说这个操作既慢又伤寿命。EEPROM 虽然可以字节级写入寿命大概 100 万次但写入速度慢典型页写入周期要 5ms 左右在需要快速响应的控制循环里这个延迟是不能接受的。后来我把目光转向了 MRAM。MRAM 的全称是磁性随机存储器它的存储原理跟传统电荷存储完全不同——利用磁性隧道结的磁化方向来存储数据。这个原理带来的好处非常直接写入不需要擦除、没有擦写寿命限制理论上无限次、写入速度接近 SRAM、断电后数据保持。MR25H40CDF 就是 Everspin 家的 4Mbit SPI MRAM正好卡在我的需求点上。1.2 MR25H40CDF 的关键参数拆解选型不能只看宣传得把数据手册翻透。MR25H40CDF 的几个核心参数我列一下顺便说说它们在实际使用中意味着什么。参数数值实际意义容量4Mbit (512KB)存配置和日志绰绰有余接口SPI最高 40MHz读取带宽够用写入无延迟供电2.7V ~ 3.6V跟 STM32 的 3.3V 系统直接兼容写入寿命无限次不用再操心磨损均衡数据保持20 年以上工业设备生命周期内无忧写入周期无延迟写入即完成不需要等待工作温度-40°C ~ 85°C工业级温度范围这里要特别说一下写入无延迟这件事。传统 EEPROM 写入后需要等待内部编程完成期间芯片不响应指令。MR25H40CDF 没有这个过程你发完写指令和数据的最后一个时钟沿数据就已经写进去了。这个特性在中断服务程序里写日志的时候特别香不用担心阻塞太久。1.3 和 FRAM、nvSRAM、BBSRAM 的横向对比既然聊到选型索性把几个非易失存储方案都摆出来对比一下方便你根据自己的场景做判断。FRAM铁电存储器写入速度也很快寿命也高但容量普遍偏小价格按位算比 MRAM 还贵而且部分型号对供电时序有特殊要求。nvSRAM 本质上是 SRAM 加后备电池虽然速度快但电池是个隐患工业现场高温环境下电池寿命会大打折扣。BBSRAM 类似封装里集成电池体积和成本都上去了。MRAM 的优势在于它把非易失和高速随机访问这两个特性结合得比较好而且没有电池这种消耗品。缺点是容量密度不如 Flash价格也比 Flash 高。但对于我这种只需要几百 KB 存储、又要求高频写入的场景MRAM 是最优解。2. STM32F405RG 与 MR25H40CDF 的硬件连接设计2.1 SPI 接口的引脚分配与模式选择STM32F405RG 有多个 SPI 外设我选的是 SPI2因为 SPI1 留给了板上的另一颗 ADC。MR25H40CDF 支持 SPI 模式 0 和模式 3我选的是模式 0CPOL0CPHA0也就是空闲时时钟低电平数据在时钟上升沿采样。这个模式跟 STM32 的 SPI 配置对应起来最直观不容易出错。引脚连接如下SCK→ PB13SPI2_SCKMISO→ PB14SPI2_MISOMOSI→ PB15SPI2_MOSICS→ PB12GPIO 输出软件片选VCC→ 3.3VGND→ GND这里我特意用了软件片选而不是硬件 NSS。原因后面会详细说简单讲就是软件片选在时序控制上更灵活尤其是 MR25H40CDF 对片选信号的建立和保持时间有要求用 GPIO 手动控制更稳妥。2.2 电源去耦与 PCB 布局的注意事项MRAM 虽然不像模拟器件那么娇气但电源质量还是要注意。我在 MR25H40CDF 的 VCC 引脚旁边放了一个 100nF 的陶瓷电容紧贴引脚另外在电源入口处加了一个 10uF 的钽电容。这样做的目的是滤除高频噪声和提供瞬态电流。PCB 布局上SPI 的四根信号线尽量等长、靠近走线减少环路面积。SCK 是频率最高的信号我把它走在中间两边分别是 MOSI 和 MISO地线包在两侧。CS 线单独走避免跟 SCK 平行太长距离防止串扰导致误触发。注意MR25H40CDF 的 CS 引脚内部没有上拉如果悬空可能导致芯片误进入通信状态。我在 CS 线上加了一个 10k 的上拉电阻到 3.3V确保上电默认状态是未选中。2.3 软件片选与硬件片选的取舍分析前面提到我用了软件片选这里展开说说为什么。STM32 的硬件 NSS 有两种模式一种是由 SPI 外设自动控制另一种是输出使能信号。自动控制模式下NSS 在数据传输开始和结束时自动拉低拉高但它的时序是跟 SPI 时钟同步的可能在最后一个时钟沿之后立刻拉高而 MR25H40CDF 要求 CS 在最后一个时钟沿之后至少保持 20ns 才能拉高。虽然 20ns 很短但在某些主频配置下硬件 NSS 的时序可能刚好卡在边界上。软件片选就没这个问题。我在发送完最后一个字节后手动延时几个微秒再拉高 CS确保满足保持时间。代价是多占一个 GPIO但对于 STM32F405RG 这种引脚资源丰富的芯片来说完全不是问题。3. 从零编写 MR25H40CDF 的 SPI 驱动3.1 初始化序列与寄存器配置MR25H40CDF 的初始化其实很简单它没有复杂的配置寄存器上电后直接就可以读写。但有几个状态寄存器需要关注状态寄存器 1 里的 WEL写使能锁存位和 BP块保护位。写操作之前必须先发 WREN0x06指令把 WEL 置 1否则写指令会被忽略。这个设计跟 EEPROM 类似是为了防止误写入。BP 位用来设置写保护区域我一般全部清零允许全片写入。初始化流程如下void MR25H40_Init(void) { // GPIO 和 SPI 外设初始化由 CubeMX 生成的代码完成 // 这里只做 MRAM 特有的初始化 MR25H40_CS_HIGH(); // 确保片选拉高 HAL_Delay(1); // 等待芯片上电稳定 // 读取状态寄存器确认通信正常 uint8_t status MR25H40_ReadStatus(); // 正常应该返回 0x00 或已知值 }SPI 配置参数时钟极性低、时钟相位第一个边沿、数据宽度 8 位、波特率预分频到 10MHz 左右MR25H40CDF 最高支持 40MHz但初期调试建议降速。3.2 读操作、写操作与状态轮询的实现读操作最简单发 READ 指令0x03然后发 3 字节地址接着连续读出数据。地址是 24 位的因为 4Mbit 容量需要 19 位地址但 MR25H40CDF 用 3 字节地址格式高位补零。void MR25H40_Read(uint32_t addr, uint8_t *buf, uint16_t len) { MR25H40_CS_LOW(); uint8_t cmd 0x03; HAL_SPI_Transmit(hspi2, cmd, 1, 100); uint8_t addr_bytes[3]; addr_bytes[0] (addr 16) 0xFF; addr_bytes[1] (addr 8) 0xFF; addr_bytes[2] addr 0xFF; HAL_SPI_Transmit(hspi2, addr_bytes, 3, 100); HAL_SPI_Receive(hspi2, buf, len, 1000); MR25H40_CS_HIGH(); }写操作稍微复杂一点要先发 WREN再发 WRITE 指令0x02然后地址和数据。void MR25H40_Write(uint32_t addr, uint8_t *buf, uint16_t len) { // 第一步写使能 MR25H40_CS_LOW(); uint8_t wren 0x06; HAL_SPI_Transmit(hspi2, wren, 1, 100); MR25H40_CS_HIGH(); // 第二步写入 MR25H40_CS_LOW(); uint8_t cmd 0x02; HAL_SPI_Transmit(hspi2, cmd, 1, 100); uint8_t addr_bytes[3]; addr_bytes[0] (addr 16) 0xFF; addr_bytes[1] (addr 8) 0xFF; addr_bytes[2] addr 0xFF; HAL_SPI_Transmit(hspi2, addr_bytes, 3, 100); HAL_SPI_Transmit(hspi2, buf, len, 1000); MR25H40_CS_HIGH(); }注意 WREN 和 WRITE 之间必须拉高一次 CS这是 MR25H40CDF 的指令格式要求。我一开始把 WREN 和 WRITE 放在同一个 CS 低电平周期里结果写不进去排查了半天才发现这个问题。3.3 用 HAL 库还是寄存器操作我的实际选择STM32F405RG 的开发我用的 STM32CubeMX 加 HAL 库。HAL 库的 SPI 函数封装得比较完善HAL_SPI_Transmit和HAL_SPI_Receive用起来很方便。但在高频写入场景下HAL 库的函数调用开销和状态检查会带来一些额外延迟。我实测过用 HAL 库写 256 字节数据从拉低 CS 到拉高 CS 大概需要 180 微秒左右其中 SPI 传输本身占了大头。如果换成直接操作寄存器能省下大概 20 到 30 微秒。对于我的应用来说这个差异可以接受所以最终保留了 HAL 库方案开发效率更高代码可读性也好。如果你对写入速度有极致要求可以考虑用 DMA 加寄存器操作的方式把 CPU 解放出来。不过 MRAM 的写入本身没有等待周期瓶颈主要在 SPI 时钟频率上所以提升 SPI 时钟比优化 CPU 开销更有效。4. 实测中遇到的三个典型问题与排查过程4.1 上电后首次读取数据异常板子刚打样回来的时候我发现一个现象每次上电后第一次读取 MRAM读出来的数据总是错的但第二次读就正常了。这个问题困扰了我一个下午。排查过程是这样的。首先用逻辑分析仪抓 SPI 波形发现第一次读取时 MISO 线上的数据确实不对但 SCK、MOSI、CS 的时序都正常。然后我怀疑是 MRAM 上电稳定时间不够在初始化里加了一个 10ms 的延时问题依旧。接着我检查了电源用示波器看 3.3V 轨上电斜率正常没有明显跌落。最后我把注意力放到 SPI 外设的初始化顺序上。原来我在 main 函数里先初始化了 MRAM 的 CS 引脚然后才初始化 SPI2。在 SPI2 还没配置好的时候CS 引脚的状态是不确定的可能被拉低了一下导致 MRAM 误以为收到了时钟信号内部状态机错乱。把 SPI2 的初始化提到 CS 引脚初始化之前问题就消失了。经验SPI 从设备的 CS 引脚一定要在主控 SPI 外设初始化完成之后再配置为输出高电平否则上电瞬间的毛刺可能让从设备进入错误状态。4.2 高速 SPI 时钟下的数据错误调试通过之后我想把 SPI 时钟从 10MHz 提到 20MHz提高读写效率。结果一改分频系数读出来的数据就开始随机出错错误率大概在千分之一左右。这种随机错误通常是信号完整性问题。我用示波器看了 SCK 和 MISO 的波形发现 20MHz 下 MISO 的上升沿有点缓而且有过冲。MR25H40CDF 的 MISO 是推挽输出驱动能力还可以但我的走线比较长大概 8 厘米分布电容导致边沿变缓。解决办法有两个一是降低 SPI 时钟二是改善信号质量。我先在 MISO 线上串了一个 33 欧姆的电阻过冲明显减小但错误率还有。后来把 SPI 时钟降到 15MHz错误完全消失。最终我定在 15MHz兼顾速度和稳定性。SPI 时钟误码率结论10MHz0稳定15MHz0稳定推荐20MHz约 0.1%信号完整性问题30MHz约 1%不可用4.3 写入过程中被中断打断的后果我的系统里有一个 1ms 的定时器中断优先级比较高。有一次我在主循环里写 MRAM 的时候中断来了中断服务程序里也调用了 MRAM 写入函数。结果两个写入操作交叉执行数据全乱了。MRAM 本身没有原子性保护SPI 总线也是共享资源。如果在一个写入序列执行到一半的时候被另一个写入序列打断CS 被意外拉高又拉低MRAM 的内部地址指针就乱了。解决办法是加互斥锁。我在 MRAM 驱动里加了一个简单的标志位写入前检查标志位如果正在写入就等待。中断里的写入操作改成把数据放入缓冲区在主循环里统一写入。这样虽然增加了一点延迟但保证了数据一致性。static volatile uint8_t mram_busy 0; void MR25H40_WriteSafe(uint32_t addr, uint8_t *buf, uint16_t len) { while (mram_busy); // 等待总线空闲 mram_busy 1; MR25H40_Write(addr, buf, len); mram_busy 0; }注意在中断里调用这个函数要小心死锁如果主循环正在写入中断会一直自旋等待。更好的做法是中断里只做标记把实际写入放到主循环。5. 数据存储结构设计与磨损均衡的取舍5.1 为什么 MRAM 不需要磨损均衡传统 Flash 和 EEPROM 需要磨损均衡是因为它们的擦写寿命有限。比如 10 万次寿命如果每天写 100 次不到三年就写坏了。磨损均衡通过把写入分散到不同物理地址来延长整体寿命。MRAM 没有这个问题。MR25H40CDF 的写入寿命是无限的你可以在同一个地址上反复写写一亿次也不会坏。所以我的存储结构设计得非常简单直接配置参数放在固定地址日志按顺序写入写满后从头覆盖。这带来的好处是代码简单、读取速度快、不需要维护复杂的地址映射表。对于工业设备来说可靠性比什么都重要越简单的方案越不容易出问题。5.2 我的地址空间划分方案512KB 的容量我做了如下划分地址范围用途大小0x00000 - 0x00FFF系统配置区4KB0x01000 - 0x01FFF校准参数区4KB0x02000 - 0x03FFF设备信息区8KB0x04000 - 0x7FFFF日志存储区496KB系统配置区存设备 ID、通信参数、工作模式等。校准参数区存传感器校准系数。设备信息区存生产日期、硬件版本、序列号。日志区循环写入每条日志固定 64 字节包含时间戳、事件类型和数据。每个区域的开头我都放了一个魔数和一个 CRC 校验值。上电时先读魔数如果魔数不对说明这块区域还没初始化过就写入默认值。CRC 用来检测数据是否被意外篡改或损坏。5.3 掉电保护与数据完整性校验虽然 MRAM 写入本身是原子的但如果在写入多个字节的过程中掉电可能只写进去一部分。比如我要更新一个 32 字节的配置结构体写到第 16 字节的时候断电了那这个结构体就处于半新半旧的状态。我的做法是双备份加 CRC。每个配置结构体存两份分别放在主区和备份区。更新的时候先写备份区再写主区。上电读取时比较两边的 CRC如果主区 CRC 不对就用备份区恢复如果备份区也不对就用默认值。日志区每条日志都带 CRC读取时校验校验失败就跳过这条。因为日志是循环写入的偶尔丢一两条不影响整体分析。6. 性能实测MR25H40CDF 在 STM32F405RG 上的真实表现6.1 读写速度的实测数据我用定时器测量了不同操作的实际耗时SPI 时钟设为 15MHz结果如下操作数据量耗时单字节读1 字节约 3.2 微秒256 字节连续读256 字节约 142 微秒单字节写1 字节约 4.5 微秒256 字节连续写256 字节约 148 微秒状态寄存器读1 字节约 3.0 微秒写入比读取稍慢因为每次写入前要多发一个 WREN 指令。但整体速度已经远超 EEPROM 了。我之前用 EEPROM 写 256 字节需要大概 5 到 10 毫秒MRAM 快了将近 50 倍。6.2 与 EEPROM 方案的对比测试为了有个直观的对比我在同一块板子上焊了一颗 24C256 EEPROM做了同样的读写测试。结果差距非常明显。对比项MR25H40CDF24C256 EEPROM接口SPI 15MHzI2C 400kHz256 字节写入耗时148 微秒约 6.8 毫秒写入寿命无限100 万次写入前是否需要擦除否否但需页对齐写入后是否需要等待否是约 5ms单位容量成本较高低从数据上看MRAM 在速度上的优势是碾压性的。成本确实是 MRAM 的短板但对于工业设备来说存储芯片的成本占整机比例很小性能和可靠性才是关键。6.3 长时间高频写入的稳定性观察我在实验室里做了一周的连续写入测试每秒写入 100 次每次 64 字节累计写了大概 6000 万次。期间没有出现任何写入失败或数据错误。芯片表面温度比环境温度高了不到 2 摄氏度功耗很低。这个测试结果让我对 MRAM 的可靠性有了信心。如果是 EEPROM6000 万次写入早就超过寿命了即使做了磨损均衡也需要很大的冗余空间。7. 这套方案适合什么场景不适合什么场景7.1 推荐使用的典型工业场景根据我的实际经验MR25H40CDF 加 STM32F405RG 这套组合特别适合以下几类场景。第一类是工业数据采集器。需要频繁记录传感器数据断电不能丢上电要快速恢复。MRAM 的写入无延迟特性让它可以放在中断里直接写不用担心阻塞。第二类是设备参数存储。比如变频器的参数、伺服驱动器的配置用户可能频繁修改MRAM 的无限寿命让用户随便改不用操心写坏。第三类是故障录波和黑匣子功能。设备出故障的瞬间需要把关键状态快速保存下来MRAM 的写入速度足够快能在电源跌落之前把数据写进去。7.2 容量需求大时的替代思路MR25H40CDF 只有 512KB如果你需要存大量历史数据比如几个 GB 的日志那 MRAM 就不合适了。这种场景还是得用 SPI Flash 或者 SD 卡。我的建议是混合方案用 MRAM 存关键配置和最近一段时间的日志用 Flash 存历史归档数据。系统运行时先把日志写到 MRAM当 MRAM 里的日志积累到一定量再批量转移到 Flash。这样既保证了关键数据的写入速度又降低了整体成本。7.3 成本敏感型项目的权衡建议如果你的项目对成本非常敏感比如消费类电子产品那 MRAM 可能不是最佳选择。一颗 MR25H40CDF 的价格大概是同容量 SPI Flash 的十几倍。这种情况下可以考虑用 FRAM 或者带磨损均衡的 Flash 方案。但如果是工业设备、医疗设备、汽车电子这类对可靠性要求高的领域MRAM 多出来的成本是值得的。一次现场故障的维修成本可能就超过整个设备上所有 MRAM 芯片的价格了。8. 几个容易被忽略的细节和我的实操心得8.1 指令格式中的 CS 时序要求前面提过 WREN 和 WRITE 之间要拉高 CS这里再强调一下 MR25H40CDF 的指令格式。它的每条指令都是以 CS 拉低开始以 CS 拉高结束。如果你在一条指令中间拉高了 CS这条指令就被终止了芯片会回到空闲状态。我见过有人把 WREN 和 WRITE 连在一起发中间不拉高 CS结果写不进去。数据手册上写得很清楚但如果不仔细看很容易忽略。另外CS 拉高之后到下一次拉低之间要留至少 20ns 的间隔。在 15MHz 的 SPI 时钟下一个时钟周期是 66ns所以正常操作都不会违反这个要求。但如果你用 DMA 连续发送多条指令就要注意在指令之间插入足够的延时。8.2 状态寄存器的读取时机MR25H40CDF 的状态寄存器里有一个 WIPWrite In Progress位但跟 EEPROM 不同MRAM 的 WIP 位几乎永远是 0因为写入不需要时间。所以不需要像 EEPROM 那样轮询 WIP 位等待写入完成。但状态寄存器里的 WEL 位需要关注。每次写入前都要发 WREN写入完成后 WEL 会自动清零。如果你连续写多个地址每次都要重新发 WREN。我一开始想省事发一次 WREN 然后连续写多个地址结果只有第一个地址写进去了。8.3 批量写入时的页边界问题MR25H40CDF 没有页的概念你可以从任意地址开始连续写入任意长度的数据地址会自动递增写到芯片末尾会自动回卷到开头。这个特性比 EEPROM 方便很多EEPROM 通常有 32 字节或 64 字节的页限制跨页写入会回卷到页首导致数据错位。但要注意虽然 MRAM 没有页限制但如果你写入的数据长度超过了芯片容量它会回卷覆盖之前的数据。所以写入前一定要检查地址和长度确保不会越界。8.4 我的调试工具链配置调试 SPI 设备逻辑分析仪是必不可少的。我用的是 Saleae Logic 8配合它的 SPI 分析器可以直观地看到每一条指令的发送和接收数据。设置的时候注意把 CPOL 和 CPHA 配对我用的模式 0 对应 CPOL0、CPHA0。另外STM32CubeMX 里配置 SPI 的时候记得把 NSS 设为软件模式否则硬件 NSS 会自动控制片选跟我的软件片选冲突。这个坑我也踩过现象是 CS 信号一直处于不确定状态通信完全失败。代码调试方面我习惯在 SPI 读写函数里加一些断言检查 HAL 函数的返回值。如果返回 HAL_ERROR 或 HAL_TIMEOUT就记录错误码方便定位问题。在量产固件里可以关掉这些断言减少开销。8.5 关于数据保持的一个小测试MRAM 的数据保持能力标称 20 年但我没法等 20 年去验证。我做了一个加速老化测试把芯片放在 85 摄氏度的恒温箱里每隔 24 小时读取一次数据持续了一个月。数据没有出现任何位翻转。虽然这个测试不能完全等价于 20 年的常温保持但至少说明在工业温度范围内MRAM 的数据保持是可靠的。对于大多数工业设备 10 到 15 年的生命周期来说完全够用。这套 MR25H40CDF 加 STM32F405RG 的方案我从第一版打样到现在已经迭代了三版硬件固件也更新了十几个版本。中间踩过的坑基本都在这篇文章里了。如果你正在做类似的项目希望这些经验能帮你少走点弯路。存储方案的选择没有绝对的好坏关键看你的场景需要什么。对我来说MRAM 的无限寿命和写入无延迟这两个特性是 EEPROM 和 Flash 给不了的多花的那点成本换来的是整个系统可靠性的提升。
阅读完成 · 觉得有帮助?