
1. 为什么 MRAM 在工业嵌入式场景里越来越受关注搞过工业数据采集或者电力终端的朋友应该都有体会选存储芯片这件事很多时候比选主控还让人头疼。EEPROM 写入慢、擦写寿命有限NOR Flash 虽然容量大但写入前必须擦除整个扇区而且擦写次数撑死十万次放在高频记录工况数据的场景里用不了多久就废了。铁电存储器 FRAM 倒是不错但容量小、价格高而且很多型号已经停产或者交期感人。MR25H40CDF 这颗芯片是我近两年在几个工业项目里反复用到的方案。它是一颗 4Mbit 的 MRAM磁性随机存储器SPI 接口DFN 封装工作温度覆盖 -40°C 到 105°C。MRAM 的核心优势在于写入不需要擦除、写入速度接近 SRAM、擦写寿命理论上无限至少 10^14 次以上、掉电数据不丢失。这几个特性叠在一起基本上把工业场景里对存储的苛刻要求全覆盖了。STM32F427ZI 则是 ST 家比较经典的一款高性能 MCUCortex-M4 内核180MHz 主频2MB Flash256KB SRAM外设资源丰富带多个 SPI 接口支持 DMA。拿它来驱动 MR25H40CDF硬件上几乎不需要额外器件软件上 STM32CubeMX 加 HAL 库就能快速跑通。这套组合我实测下来在 -40°C 到 85°C 的宽温范围内读写稳定连续写入 72 小时没有出现任何丢帧或者数据错误。这篇文章主要面向做工业嵌入式开发的工程师尤其是那些正在选型存储方案、或者已经在用 Flash/EEPROM 但遇到瓶颈的朋友。我会从硬件连接、SPI 配置、读写时序、DMA 优化、掉电保护、常见问题排查这几个维度把整套方案拆开讲清楚。不管你是刚接触 MRAM 的新手还是已经用过类似方案的老手应该都能从里面找到可以直接抄作业的东西。2. 方案整体设计与选型思路拆解2.1 为什么选 MRAM 而不是 Flash 或 EEPROM先把这个事情说透。工业场景里存储数据通常分两类一类是参数配置写入频率低但要求掉电不丢另一类是运行日志或者采样数据写入频率高可能每秒几次到几百次不等。传统方案里参数配置用 EEPROM运行日志用 NOR Flash 或者加铁电。但这套组合有几个硬伤。EEPROM 的写入周期通常在 5ms 左右按字节写入擦写寿命大概 100 万次。如果你每秒写一次一天就是 86400 次十天就接近百万次了芯片直接报废。NOR Flash 更麻烦写入前必须擦除整个扇区通常 4KB擦除时间几十到几百毫秒擦写寿命只有 10 万次左右。而且擦除过程中如果掉电整个扇区的数据都可能丢失。MR25H40CDF 的写入机制完全不同。它基于磁性隧道结MTJ写入时通过改变磁化方向来存储数据不需要擦除操作写入一个字节的时间和读一个字节差不多都在几十纳秒级别。擦写寿命方面官方标称 10^14 次以上按每秒写 1000 次算也能用上几千年。这个特性对于高频记录场景来说基本就是一劳永逸。容量方面4Mbit 等于 512KB对于大多数工业参数存储和中等频率的日志记录来说够用了。如果你需要更大容量可以多颗级联SPI 接口支持片选扩展硬件上多拉几根 CS 线就行。2.2 STM32F427ZI 的 SPI 资源分配STM32F427ZI 有多个 SPI 接口其中 SPI1 挂在 APB2 总线上最高时钟 90MHzSPI2 和 SPI3 挂在 APB1 总线上最高 45MHz。MR25H40CDF 支持的最高 SPI 时钟是 40MHz所以用 SPI1 的话需要分频用 SPI2/SPI3 的话可以跑到接近满速。我一般建议把 MRAM 挂在 SPI2 或者 SPI3 上原因有两个一是 SPI1 通常留给其他高速外设比如显示屏或者高速 ADC二是 SPI2/SPI3 的 45MHz 上限已经超过 MRAM 的 40MHz 要求配置成分频系数 2 就能得到 22.5MHz分频系数 4 得到 11.25MHz都足够用。如果你对读写速度有极致要求可以用 SPI1 配置分频系数 4得到 22.5MHz或者分频系数 2 得到 45MHz但注意超过 40MHz 后 MRAM 可能不稳定需要实测确认。DMA 方面STM32F427ZI 的 SPI2 和 SPI3 都支持 DMA 请求可以配置 DMA 通道来实现大批量数据的自动搬运。这个在连续记录场景里非常关键后面会详细讲。2.3 硬件连接与 PCB 布局要点MR25H40CDF 是 8 引脚 DFN 封装引脚定义如下CS、SO、SI、SCK、VCC、GND、WP、HOLD。其中 WP 是写保护HOLD 是暂停通信这两个引脚如果不用的话WP 接 VCCHOLD 接 VCC。和 STM32F427ZI 的连接方式CS 接任意 GPIO建议用 SPI 的硬件 NSS 引脚也可以用普通 GPIO 软件控制SCK 接 SPI 时钟引脚SI 接 MOSISO 接 MISO。VCC 接 3.3VGND 共地。PCB 布局有几个坑我踩过。第一SCK 走线尽量短如果超过 5cm建议在源端串一个 22Ω 到 33Ω 的电阻做阻抗匹配否则高速通信时波形振铃严重容易误码。第二CS 走线不要和 SCK 平行走太长避免耦合干扰。第三VCC 引脚旁边必须放一个 0.1μF 的陶瓷电容越近越好最好再并一个 1μF 的钽电容。第四DFN 封装的散热焊盘要接地但注意不要和信号线短路。注意MR25H40CDF 的 WP 引脚是低电平有效如果悬空或者接低芯片会进入写保护状态只能读不能写。很多新手调试时发现写不进去数据最后查出来是 WP 引脚没拉高。3. SPI 通信协议与 MRAM 操作指令详解3.1 MR25H40CDF 的指令集MR25H40CDF 支持标准的 SPI 模式 0 和模式 3指令集和普通 SPI Flash 类似但有几个关键区别。常用指令包括指令名称指令码功能说明WREN0x06写使能写入前必须发送WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据RDID0x9F读设备 ID和 Flash 最大的区别是MRAM 没有扇区擦除指令也不需要擦除。写入时直接发送 WRITE 指令后面跟 24 位地址然后连续写入数据就行。地址自动递增可以跨页写入没有页边界限制。这一点比 Flash 方便太多Flash 写入时如果跨页必须分多次写MRAM 完全不用管。状态寄存器的 bit0 是 WEL写使能锁存bit1 是 BP0bit2 是 BP1bit3 是 WPEN。每次写入前必须先发 WREN写入完成后 WEL 自动清零。如果你连续写多个字节只需要在第一次写之前发一次 WREN后面不用重复发。3.2 STM32CubeMX 配置 SPI 参数用 STM32CubeMX 配置 SPI2 的步骤在 Pinout 视图里找到 SPI2选择 Full-Duplex Master 模式。配置引脚PB13 为 SCKPB14 为 MISOPB15 为 MOSI。CS 可以用 PB12 作为 GPIO 输出也可以用硬件 NSS。在 Configuration 里设置参数Frame Format: MotorolaData Size: 8 BitsFirst Bit: MSB FirstClock Polarity: LowClock Phase: 1 EdgeNSS Signal Type: SoftwarePrescaler: 4得到 45MHz/4 11.25MHzBaud Rate: 11.25 MBits/sCRC Calculation: Disabled这里 Clock Polarity 和 Clock Phase 的组合决定了 SPI 模式。CPOL0CPHA0 是模式 0CPOL1CPHA1 是模式 3。MR25H40CDF 两种都支持我一般用模式 0因为大部分 SPI 设备默认都是模式 0统一起来方便。提示Prescaler 的值需要根据实际布线长度和信号质量调整。如果 PCB 走线较长或者有干扰建议先用较大的分频系数比如 8 或 16调通后再逐步提高速度。3.3 读写时序与代码实现先看读数据的时序。拉低 CS发送 0x03然后发送 24 位地址高字节在前然后连续读取数据。每读一个字节地址自动加一。读完拉高 CS。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; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi2, cmd, 4, 100); HAL_SPI_Receive(hspi2, buf, len, 1000); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); }写数据的时序稍微复杂一点。先发 WREN0x06拉高 CS再拉低 CS发 WRITE0x02发 24 位地址然后连续写入数据。注意 WREN 和 WRITE 之间必须拉高 CS 再拉低否则 WREN 不生效。void MRAM_Write(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; uint8_t wren 0x06; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi2, wren, 1, 100); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); cmd[0] 0x02; cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi2, cmd, 4, 100); HAL_SPI_Transmit(hspi2, buf, len, 1000); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); }写完以后建议读一下状态寄存器确认 WEL 已经清零表示写入完成。虽然 MRAM 写入速度很快但保险起见还是加一个轮询。uint8_t MRAM_ReadStatus(void) { uint8_t cmd 0x05; uint8_t status; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi2, cmd, 1, 100); HAL_SPI_Receive(hspi2, status, 1, 100); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); return status; }4. DMA 加速与大批量数据读写优化4.1 为什么需要 DMA如果你只是存几个参数用阻塞式 SPI 传输完全没问题。但工业场景里经常需要连续记录采样数据比如每秒几千个点的 ADC 采样每个点 2 字节一秒就是几 KB 到几十 KB。用阻塞式传输的话CPU 大部分时间都在等 SPI 移位完成根本干不了别的事。STM32F427ZI 的 SPI2 支持 DMA 请求可以配置 DMA1 的 Channel 4SPI2_RX和 Channel 5SPI2_TX。配置好以后SPI 收发数据时 DMA 自动搬运CPU 只需要在传输完成后处理数据就行。4.2 DMA 配置步骤在 CubeMX 里配置 DMA在 SPI2 的 Configuration 里找到 DMA Settings点击 Add。添加 SPI2_TX选择 DMA1 Channel 5Priority 设为 HighMode 设为 Normal。添加 SPI2_RX选择 DMA1 Channel 4Priority 设为 HighMode 设为 Normal。在 NVIC Settings 里使能 DMA1 Channel 4 和 Channel 5 的中断。生成代码后HAL 库会自动初始化 DMA。读写函数改成 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; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi2, cmd, 4, 100); HAL_SPI_Receive_DMA(hspi2, buf, len); }注意 DMA 接收完成后需要手动拉高 CS可以在 DMA 接收完成回调函数里处理void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI2) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); } }4.3 双缓冲与循环记录策略对于连续记录场景我一般用双缓冲加循环队列的方式。开两个缓冲区DMA 往缓冲区 A 写的时候CPU 处理缓冲区 B 的数据写满后交换。这样能实现无缝连续记录不会丢数据。具体实现定义一个二维数组 buf[2][BUF_SIZE]一个标志位 active_buf。DMA 接收完成中断里切换 active_buf并启动下一次 DMA 接收。主循环里检测到 active_buf 变化后处理另一个缓冲区的数据写入 MRAM。MRAM 的写入速度很快11.25MHz 的 SPI 时钟下写入 1KB 数据大概需要 1ms 左右。如果你用 22.5MHz时间减半。这个速度对于大多数工业采样场景来说完全够用。实操心得DMA 传输时 CS 信号的控制非常关键。如果 CS 拉高的时机不对最后一个字节可能还没移完就被截断。我一般会在 DMA 传输完成中断里加一个微小的延时比如几个 NOP再拉高 CS确保数据完整。5. 掉电保护与数据完整性设计5.1 MRAM 的掉电特性MRAM 的写入是磁化方向翻转不需要电荷保持所以掉电瞬间数据就已经固化了。这一点比 Flash 和 EEPROM 强太多。Flash 写入过程中掉电整个扇区可能变成乱码EEPROM 写入过程中掉电当前字节可能写了一半。MRAM 完全没有这个问题写入操作是原子的要么完成要么没完成不会出现中间状态。但这不代表掉电保护可以完全不做。如果你在写入过程中掉电虽然 MRAM 本身的数据不会坏但你的数据结构可能处于不一致状态。比如你正在更新一个结构体写了前一半掉电了后一半还是旧数据。所以软件层面还是需要一些保护机制。5.2 双备份加校验的设计我常用的方案是双备份加 CRC 校验。把关键数据存两份地址 A 和地址 B每份数据后面跟一个 CRC32 校验值。写入时先写 A再写 B读完以后对比两份数据和 CRC如果 A 坏了用 BB 坏了用 A两个都坏就报错。具体实现typedef struct { uint32_t magic; uint8_t data[256]; uint32_t crc; } DataBlock; void SaveData(DataBlock *block) { block-magic 0xAA55AA55; block-crc CRC32_Calculate(block-data, 256); MRAM_Write(ADDR_A, (uint8_t*)block, sizeof(DataBlock)); MRAM_Write(ADDR_B, (uint8_t*)block, sizeof(DataBlock)); } bool LoadData(DataBlock *block) { DataBlock tempA, tempB; MRAM_Read(ADDR_A, (uint8_t*)tempA, sizeof(DataBlock)); MRAM_Read(ADDR_B, (uint8_t*)tempB, sizeof(DataBlock)); bool validA (tempA.magic 0xAA55AA55) (tempA.crc CRC32_Calculate(tempA.data, 256)); bool validB (tempB.magic 0xAA55AA55) (tempB.crc CRC32_Calculate(tempB.data, 256)); if (validA) { memcpy(block, tempA, sizeof(DataBlock)); return true; } if (validB) { memcpy(block, tempB, sizeof(DataBlock)); return true; } return false; }这个方案的好处是简单可靠坏处是占用双倍空间。4Mbit 的 MRAM 存 256 字节的数据块可以存 1000 组左右对于参数存储来说绰绰有余。5.3 写入过程中的异常处理虽然 MRAM 写入很快但如果 SPI 通信过程中出现干扰可能导致写入的数据错误。我一般会在写入后立即读回校验如果校验失败就重写一次。重写次数超过 3 次就报错记录到错误日志里。bool WriteWithVerify(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t verify_buf[256]; for (int retry 0; retry 3; retry) { MRAM_Write(addr, buf, len); MRAM_Read(addr, verify_buf, len); if (memcmp(buf, verify_buf, len) 0) { return true; } } return false; }注意读回校验会增加写入时间如果对写入速度要求极高可以只在关键数据写入时做校验普通日志数据可以跳过。6. 常见问题排查与实操避坑指南6.1 读写失败问题速查表现象可能原因排查方法解决方案读不到设备 IDSPI 接线错误用示波器看 SCK、MOSI、MISO 波形检查接线确认 CS 拉低写入后读回全是 0xFFWP 引脚未拉高万用表测 WP 引脚电压WP 接 VCC写入后读回数据错乱SPI 模式不匹配尝试模式 0 和模式 3修改 CPOL/CPHA 配置高速通信时误码信号完整性差示波器看 SCK 波形是否有振铃降低 SPI 时钟加串阻DMA 传输丢数据CS 拉高时机不对逻辑分析仪抓 CS 和 SCK 时序在 DMA 完成中断里延时后拉高 CS掉电后数据丢失写入未完成就掉电检查电源监控电路加掉电检测提前保存6.2 我踩过的几个坑第一个坑是 WP 引脚。刚开始调试的时候焊接的是 DFN 封装WP 引脚在芯片底部肉眼看不到。我以为悬空就是高电平结果芯片一直处于写保护状态写什么都写不进去。后来用万用表一测WP 引脚电压只有 0.3V明显是悬空被拉低了。重新焊接把 WP 接到 VCC 后一切正常。第二个坑是 SPI 时钟速度。我一开始用 SPI1 配置了 45MHz想着 MRAM 标称 40MHz应该差不多。结果通信时好时坏读出来的数据偶尔错几个字节。用示波器一看SCK 波形上升沿有明显的振铃过冲到了 4V 多。后来降到 22.5MHz波形干净了通信也稳定了。所以标称最高频率是理想条件下的值实际能跑多少要看 PCB 布线质量。第三个坑是 DMA 传输的 CS 控制。用阻塞式传输时HAL_SPI_Transmit 函数返回后数据肯定已经发完了拉高 CS 没问题。但 DMA 传输是异步的函数返回时数据还在搬。我一开始在 HAL_SPI_Transmit_DMA 后面直接拉高 CS结果最后一个字节总是丢。后来改成在 DMA 完成回调里拉高 CS问题解决。第四个坑是地址对齐。MRAM 支持任意地址读写没有对齐要求。但我一开始写代码时习惯性地按 4 字节对齐结果读出来的数据总是偏移。后来发现是地址计算错误把字节地址当成字地址用了。这个坑比较低级但新手容易犯。6.3 调试工具与技巧调试 SPI 通信逻辑分析仪是必备的。我用的是 Saleae Logic 8配合它的 SPI 协议解析器可以直接看到发送的指令、地址和数据非常直观。如果没有逻辑分析仪至少要用示波器看 SCK 和 MOSI 的波形确认时钟频率和数据的建立保持时间。另外STM32CubeMX 生成的代码里HAL_SPI_Transmit 和 HAL_SPI_Receive 的超时参数不要设太大否则通信失败时会卡很久。我一般设 100ms 到 1000ms根据数据长度调整。还有一个技巧在初始化 MRAM 之前先读一下设备 ID0x9F确认通信正常。MR25H40CDF 的设备 ID 是 0x18 开头具体值可以查数据手册。如果读不到正确的 ID后面的读写操作都不用试了肯定是硬件或者配置有问题。7. 实际项目中的性能表现与扩展思路7.1 实测性能数据我在一个工业数据采集项目里用了这套方案主控 STM32F427ZIMRAM 挂 SPI2时钟 22.5MHzDMA 传输。实测数据操作数据量耗时平均速度单字节写入1 字节约 2μs500KB/s连续写入1KB约 0.8ms1.25MB/s连续读取1KB约 0.5ms2MB/s连续写入64KB约 52ms1.23MB/s连续读取64KB约 32ms2MB/s写入速度比读取慢主要是因为每次写入前要发 WREN 指令而且写入后需要等待状态寄存器更新。但 1.25MB/s 的写入速度对于工业场景来说已经非常充裕了。如果你把 SPI 时钟提到 40MHz写入速度可以到 2MB/s 左右。7.2 多颗 MRAM 级联扩展容量4Mbit 如果不够用可以挂多颗 MR25H40CDF。SPI 总线是共享的SCK、MOSI、MISO 并联每颗芯片的 CS 单独接一个 GPIO。读写时拉低对应芯片的 CS其他芯片的 CS 保持高电平。这种方式的优点是硬件简单软件上只需要在读写函数里指定 CS 引脚就行。缺点是占用 GPIO 资源而且多颗芯片共享 SPI 总线不能同时读写。如果你需要更大容量也可以考虑用 SPI 转并行的方案但那就复杂了。7.3 结合文件系统管理数据如果数据量比较大建议在 MRAM 上跑一个轻量级文件系统比如 LittleFS 或者 SPIFFS。这两个都是为嵌入式设计的支持掉电保护、磨损均衡虽然 MRAM 不需要、目录结构。LittleFS 的移植比较简单STM32 上有很多现成的例程可以参考。跑文件系统的好处是数据管理方便不用自己算地址、管校验。坏处是会增加一些开销写入速度会慢一点。如果你的数据量不大或者对速度要求极高直接裸写地址更合适。实操心得MRAM 虽然擦写寿命无限但 SPI 通信本身可能出错。我在文件系统层加了一个写入校验每次写完读回对比不匹配就重写。这个机制在实际项目里救过好几次数据。8. 写在最后的一些个人体会这套 MR25H40CDF 加 STM32F427ZI 的方案我从 2022 年开始在多个项目里使用覆盖了电力监测、工业网关、医疗设备几个领域。最深的体会是MRAM 的“写入不用擦除”这个特性在软件架构上带来的简化是巨大的。以前用 Flash 的时候要设计复杂的扇区管理、磨损均衡、掉电保护逻辑现在这些统统不需要代码量少了三分之一调试时间也大幅缩短。成本方面MR25H40CDF 单颗价格比同容量的 FRAM 便宜不少比 NOR Flash 贵一些但考虑到它省掉了复杂的软件逻辑和潜在的维护成本整体性价比是划算的。尤其是那些需要频繁记录、又要求高可靠性的场景MRAM 几乎是唯一的选择。如果你正在选型存储方案建议先拿一颗 MR25H40CDF 的评估板或者自己画个小板子试试。SPI 接口的器件调试起来很快半天就能跑通。跑通之后再评估是否满足你的项目需求比看数据手册靠谱得多。