
1. 项目缘起与方案选型1.1 为什么要在工业场景里同时用上 MRAM 和 FRAM这个项目的起点其实很朴素一块工业数据采集板需要在断电、强电磁干扰、宽温-40℃到85℃的环境下长期、高频地记录运行参数和故障快照。早期方案用的是 EEPROM 加锂电池备份 SRAM结果现场跑了一年多陆续出现电池失效、写入寿命耗尽、掉电丢数据的问题。后来把存储方案换成了MR25H40CDFMRAM和MKV44F64VLH16FRAM整个系统的可靠性上了一个台阶。先说说这两颗芯片的定位。MR25H40CDF是一颗 4Mbit 的 SPI 接口 MRAMMRAM 的核心特点是“非易失 近乎无限次写入 纳秒级写入速度”它不像 Flash 那样需要先擦后写也不像 EEPROM 那样写入慢、寿命有限。MKV44F64VLH16则是一颗 64Kbit 的 SPI 接口 FRAMFRAM 的写入机制是基于铁电材料的极化翻转写入能耗极低、速度快而且读写寿命可以达到万亿次级别。把这两颗芯片放在同一块板子上分工是很明确的MRAM 负责存放大块的数据日志、参数表、故障录波数据因为它容量相对大、写入快FRAM 负责存放高频更新的计数器、状态标志、掉电保护的关键变量因为它写入次数几乎不受限适合“每毫秒都在写”的场景。这种组合在工业嵌入式里其实很常见只是很多人一上来就只想到 Flash结果被擦写寿命和写入延迟坑得很惨。1.2 SPI 接口选型的考量与硬件连接思路两颗芯片都是 SPI 接口这一点很关键。SPI 在嵌入式里几乎是“标配”速度快、协议简单、几乎所有 MCU 都有硬件 SPI 外设。MR25H40CDF 支持最高 40MHz 的 SPI 时钟MKV44F64VLH16 支持最高 20MHz实际用的时候要根据主控的 SPI 能力和 PCB 走线质量来折中。硬件连接上我采用的是独立片选方案而不是共用片选加菊花链。原因很简单两颗芯片的读写时序、命令集不完全一样共用片选会增加软件复杂度而且一旦某颗芯片总线冲突排查起来非常痛苦。独立片选就是每颗芯片各占一个 GPIO 作为 CSSCK、MOSI、MISO 三根线共用。这样软件上只需要切换 CS 就能区分操作对象逻辑清晰调试也方便。注意SPI 的 MISO 线在多个从设备共享时未选中的从设备必须处于高阻态。MR25H40CDF 和 MKV44F64VLH16 都支持这一点但如果你用的是一些老式 Flash要特别确认它的 MISO 在 CS 无效时是否真的释放总线否则会出现“读回来的数据莫名其妙”的问题。PCB 布局上SPI 的 SCK 和 MOSI 尽量短、尽量等长MISO 可以稍微放宽。如果板子上有电机驱动或者继电器SPI 走线要远离这些干扰源必要时加地线隔离。我吃过一次亏SPI 走线从继电器下方穿过结果每次继电器动作读回来的数据就错一位后来重新布线才解决。1.3 与常见存储方案的对比为了说清楚为什么选这两颗芯片我整理了一个对比表把 EEPROM、NOR Flash、MRAM、FRAM 放在一起看特性EEPROMNOR FlashMRAM (MR25H40CDF)FRAM (MKV44F64VLH16)写入寿命100万次10万次10^14 次10^14 次写入速度慢ms级慢需擦除快ns级快ns级擦除需求无需擦除必须擦除无需擦除无需擦除掉电保持好好好好容量小中中小接口I2C/SPISPISPISPI成本低低高中高从表里能看出来MRAM 和 FRAM 的核心优势就是“写入快、寿命长、不用擦除”。在工业场景里设备可能每秒要记录几十次数据一跑就是好几年EEPROM 和 Flash 根本扛不住。MRAM 和 FRAM 虽然贵一点但省去了电池、省去了复杂的磨损均衡算法整体系统成本反而更低。2. 核心细节解析与实操要点2.1 MR25H40CDF 的命令集与读写时序MR25H40CDF 的命令集和标准 SPI Flash 很像但有几个关键区别。它支持读命令0x03、写命令0x02、写使能0x06、写禁止0x04、读状态寄存器0x05、写状态寄存器0x01。注意它没有擦除命令因为 MRAM 不需要擦除直接写就行。读操作时序拉低 CS发送 0x03然后发送 3 字节地址24 位接着就可以连续读出数据。地址会自动递增所以可以一次性读完整个芯片。写操作时序拉低 CS发送 0x06写使能拉高 CS再拉低 CS发送 0x02发送 3 字节地址然后发送要写的数据。写完之后可以读状态寄存器确认写入完成。这里有个细节MR25H40CDF 的写操作是按字节写入的不需要页对齐。这一点比 Flash 友好太多。Flash 写入必须按页通常 256 字节对齐跨页写会回卷导致数据错位。MRAM 没有这个问题你可以在任意地址写任意长度的数据。实操心得虽然 MR25H40CDF 不需要擦除但写之前还是要发写使能命令0x06。我一开始以为 MRAM 可以像 RAM 一样直接写结果发现写不进去后来查手册才知道每次写操作前必须发写使能。这个坑很隐蔽因为读操作不需要写使能所以很容易忽略。2.2 MKV44F64VLH16 的读写特性与注意事项MKV44F64VLH16 是一颗 64Kbit 的 FRAM容量不大但胜在写入寿命几乎无限。它的命令集和 MR25H40CDF 类似也是读0x03、写0x02、写使能0x06、写禁止0x04、读状态0x05。地址是 16 位的因为容量只有 8K 字节。FRAM 的写入速度非常快SPI 时钟可以跑到 20MHz实际写入一个字节的时间主要取决于 SPI 时钟周期。在 20MHz 下一个字节 8 位加上命令和地址开销写一个字节大概 1 微秒左右。这意味着你可以用 FRAM 来存高频更新的数据比如每毫秒更新一次的状态变量完全没问题。但 FRAM 有一个需要注意的地方它的读操作是破坏性的。FRAM 读取时铁电材料的极化状态会被读取操作改变所以每次读之后芯片内部会自动把数据写回去。这个过程是透明的用户不需要干预但它意味着读操作的功耗比普通 SRAM 高一点。在低功耗场景下如果频繁读 FRAM要考虑功耗预算。注意MKV44F64VLH16 的 VDD 范围是 2.7V 到 3.6V典型值 3.3V。如果你用 1.8V 的 MCU需要加电平转换。我见过有人直接把 1.8V 的 SPI 接到 3.3V 的 FRAM 上结果通信不稳定时好时坏后来加了电平转换芯片才解决。2.3 SPI 模式与时钟极性的选择SPI 有四种模式由 CPOL时钟极性和 CPHA时钟相位决定。MR25H40CDF 和 MKV44F64VLH16 都支持模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。我一般用模式 0因为这是最常用的大多数 MCU 的 SPI 外设默认也是模式 0。模式 0 的时序是SCK 空闲时为低电平数据在 SCK 的上升沿采样下降沿变化。如果你用模式 3SCK 空闲时为高电平数据在下降沿采样上升沿变化。两种模式都能用但一旦选定主从双方必须一致。我遇到过一个问题MCU 的 SPI 配置为模式 0但 FRAM 的初始化代码里不小心写成了模式 3结果读出来的数据全是 0xFF。后来用逻辑分析仪抓波形才发现 SCK 的极性不对。实操心得调试 SPI 通信时逻辑分析仪是必备工具。不要靠猜直接抓 SCK、MOSI、MISO、CS 四根线的波形对照芯片手册的时序图一眼就能看出问题。我用的是一款便宜的 8 通道逻辑分析仪采样率 24MHz足够看 SPI 波形了。2.4 电源管理与掉电保护设计工业场景里掉电是常态。存储芯片的掉电保护设计直接决定了数据会不会丢。MRAM 和 FRAM 本身是非易失的掉电后数据不会丢但问题是掉电的瞬间正在进行的写操作可能只写了一半。比如你正在写一个 4 字节的浮点数写到第 2 个字节时电源掉了那这个浮点数就损坏了。解决这个问题的思路是在电源上加大电容保证掉电后 MCU 和存储芯片还能工作足够长的时间完成当前写操作。具体电容值怎么算假设系统电流是 50mA掉电后需要维持 10ms 完成写操作电容电压从 3.3V 降到 2.7V芯片最低工作电压根据公式 C I * t / ΔVC 0.05A * 0.01s / 0.6V ≈ 833μF。实际选型时留一倍余量用 1000μF 以上的电容。另外软件上要做“写完成标志”。每次写数据之前先在 FRAM 里写一个“正在写”的标志写完之后再清除这个标志。系统上电时先检查这个标志如果发现“正在写”说明上次掉电时写操作没完成需要做数据恢复或者丢弃这条数据。3. 实操过程与核心环节实现3.1 硬件连接与初始化配置先说一下我的硬件平台主控是一颗 STM32H7 系列 MCUSPI1 用于连接 MR25H40CDFSPI2 用于连接 MKV44F64VLH16。两颗芯片的 CS 分别接在 PA4 和 PB12 上。SCK、MOSI、MISO 分别接在 SPI1 和 SPI2 的对应引脚上。初始化配置的步骤如下使能 SPI 和 GPIO 时钟。配置 GPIOSCK、MOSI、MISO 为复用推挽输出MISO 为浮空输入CS 为普通推挽输出。配置 SPI 参数主机模式、模式 0、8 位数据、MSB 先出、时钟分频根据芯片能力设置。使能 SPI。对于 MR25H40CDFSPI 时钟可以设到 40MHz但 STM32H7 的 SPI1 在 200MHz 时钟下分频系数最小是 2所以实际时钟是 100MHz太高了。我设的是分频系数 8得到 25MHz稳定可靠。对于 MKV44F64VLH16分频系数设 16得到 12.5MHz也足够快。// SPI1 初始化用于 MR25H40CDF void SPI1_Init(void) { // GPIO 和 SPI 时钟使能 RCC-AHB4ENR | RCC_AHB4ENR_GPIOAEN; RCC-APB2ENR | RCC_APB2ENR_SPI1EN; // PA5SCK, PA6MISO, PA7MOSI, PA4CS GPIOA-MODER ~(3 10 | 3 12 | 3 14 | 3 8); GPIOA-MODER | (2 10 | 2 12 | 2 14 | 1 8); GPIOA-AFR[0] | (5 20 | 5 24 | 5 28); // SPI1 配置 SPI1-CFG1 SPI_CFG1_MBR_DIV8 | SPI_CFG1_DSIZE_7; SPI1-CFG2 SPI_CFG2_MASTER | SPI_CFG2_SSM | SPI_CFG2_SSOE; SPI1-CR1 SPI_CR1_SPE; }3.2 MR25H40CDF 的读写函数实现读写函数的核心是“拉低 CS - 发命令 - 发地址 - 读写数据 - 拉高 CS”。下面是我实际用的代码// 写使能 void MRAM_WriteEnable(void) { MRAM_CS_LOW(); SPI_Transfer(0x06); MRAM_CS_HIGH(); } // 写数据 void MRAM_Write(uint32_t addr, uint8_t *data, uint32_t len) { MRAM_WriteEnable(); MRAM_CS_LOW(); SPI_Transfer(0x02); SPI_Transfer((addr 16) 0xFF); SPI_Transfer((addr 8) 0xFF); SPI_Transfer(addr 0xFF); for (uint32_t i 0; i len; i) { SPI_Transfer(data[i]); } MRAM_CS_HIGH(); } // 读数据 void MRAM_Read(uint32_t addr, uint8_t *buf, uint32_t len) { MRAM_CS_LOW(); SPI_Transfer(0x03); SPI_Transfer((addr 16) 0xFF); SPI_Transfer((addr 8) 0xFF); SPI_Transfer(addr 0xFF); for (uint32_t i 0; i len; i) { buf[i] SPI_Transfer(0x00); } MRAM_CS_HIGH(); }这里SPI_Transfer是一个阻塞式的收发函数发送一个字节并返回接收到的字节。实际项目中如果数据量大可以用 DMA 来传输减少 CPU 占用。实操心得MRAM 的写操作不需要等待写完之后立刻可以读。但如果你用 DMA 写要确保 DMA 传输完成后再拉高 CS否则数据可能没写完就结束了。我一开始用 DMA 写 MRAMCS 拉高太早结果最后几个字节没写进去后来在 DMA 传输完成中断里拉高 CS 才解决。3.3 MKV44F64VLH16 的高频写入实现FRAM 的写入和 MRAM 类似但地址是 16 位的。下面是一个高频写入的例子每毫秒更新一次计数器写入 FRAM。// FRAM 写数据 void FRAM_Write(uint16_t addr, uint8_t *data, uint16_t len) { FRAM_WriteEnable(); FRAM_CS_LOW(); SPI_Transfer(0x02); SPI_Transfer((addr 8) 0xFF); SPI_Transfer(addr 0xFF); for (uint16_t i 0; i len; i) { SPI_Transfer(data[i]); } FRAM_CS_HIGH(); } // 定时器中断里调用每毫秒更新计数器 void TIM_IRQHandler(void) { static uint32_t counter 0; counter; FRAM_Write(0x0000, (uint8_t *)counter, 4); }这个写法看起来很简单但实际跑起来要注意SPI 传输是阻塞的在中断里做 SPI 传输会占用中断时间。如果 SPI 时钟是 12.5MHz写 4 个字节加上命令和地址大概需要 7 个字节的时间约 4.5 微秒。这个时间在 1ms 的中断周期里完全可以接受。但如果你的中断周期是 10 微秒那就不能这么干了得用 DMA 或者把数据缓存起来在主循环里批量写。3.4 数据分区与磨损均衡的简化处理虽然 MRAM 和 FRAM 的写入寿命极长但并不意味着可以完全不做分区管理。我的做法是把 MRAM 分成几个区域参数区、日志区、故障录波区。参数区存放系统配置日志区循环写入运行日志故障录波区存放触发故障前后的数据快照。日志区采用环形缓冲区的方式写满之后从头覆盖。这样不需要复杂的磨损均衡算法因为 MRAM 的写入寿命是 10^14 次即使每秒写 1000 次也能写 10^11 秒约 3000 年。所以对于大多数工业场景直接循环写就行不用考虑寿命问题。FRAM 则用来存放“掉电保护变量”比如系统运行状态、故障标志、累计运行时间等。这些变量更新频率高但数据量小正好适合 FRAM。注意虽然 MRAM 和 FRAM 寿命长但如果你在高温环境下使用写入寿命会下降。比如在 85℃ 下MRAM 的写入寿命可能降到 10^12 次。所以如果你的设备工作在高温环境还是要稍微注意一下写入频率不要无脑狂写。4. 常见问题与排查技巧实录4.1 SPI 通信失败排查速查表现象可能原因排查方法解决方法读回全 0xFFMISO 未连接或 CS 未拉低检查 MISO 焊接用逻辑分析仪看 CS 波形重新焊接确认 CS 控制正确读回全 0x00MOSI 未连接或 SCK 无输出检查 MOSI 焊接看 SCK 是否有波形重新焊接检查 SPI 使能数据错位SPI 模式不匹配对照手册检查 CPOL/CPHA统一主从模式写入后读回不对写使能未发或写保护检查写使能命令读状态寄存器每次写前发 0x06偶尔出错电源噪声或走线干扰用示波器看电源纹波加滤波电容重新布线高速时出错SPI 时钟太快降低时钟分频逐步降低时钟直到稳定这个表是我在实际调试中总结出来的基本上覆盖了 90% 的 SPI 通信问题。遇到问题的时候先查表再动手能省很多时间。4.2 掉电数据丢失的排查与解决有一次现场反馈说设备断电后最后几条日志丢了。我一开始以为是 MRAM 写失败后来用逻辑分析仪抓波形发现掉电瞬间MCU 的电源电压下降得比 MRAM 快导致 MCU 已经停止工作但 MRAM 还在写。结果就是写了一半的数据。解决办法是在 MCU 的电源上加了一个 1000μF 的电解电容同时在软件里加了“写完成标志”。每次写日志之前先在 FRAM 里写一个标志写完之后清除。上电时检查这个标志如果发现标志还在说明上次写没完成就把这条日志标记为“可能损坏”不参与后续分析。这个方案实测下来很稳后来再也没有出现过掉电丢数据的问题。4.3 高温环境下的可靠性验证工业设备要在 -40℃ 到 85℃ 下工作存储芯片的可靠性必须验证。我做过一个高温老化测试把板子放在 85℃ 的恒温箱里连续写入 72 小时每秒钟写 1000 次。测试结束后读回所有数据校验无误。MR25H40CDF 和 MKV44F64VLH16 都通过了测试没有出现写入失败或数据损坏。但有一个细节要注意高温下 SPI 的时序余量会变小。我在 85℃ 下把 SPI 时钟从 25MHz 降到 20MHz通信更稳定。如果你在高温环境下发现偶发的通信错误不妨试试降低 SPI 时钟。实操心得高温测试的时候不要只测存储芯片整个板子都要测。我遇到过一个问题存储芯片没问题但 MCU 的 SPI 外设在高温下时序偏移导致通信失败。后来换了 MCU 的 SPI 时钟配置才解决。4.4 多设备共享 SPI 总线的片选管理如果板子上有多个 SPI 从设备片选管理就很重要。我的做法是每个从设备独立片选并且在软件里加一个“SPI 总线锁”防止多个任务同时访问 SPI 总线导致数据错乱。// SPI 总线锁 volatile uint8_t spi_busy 0; void SPI_Acquire(void) { while (spi_busy); spi_busy 1; } void SPI_Release(void) { spi_busy 0; }在 RTOS 环境下可以用互斥量来代替这个简单的标志。但如果你用的是裸机系统这个简单的锁就够用了。注意片选切换的时候要确保上一个操作的 CS 已经拉高并且总线空闲。我见过有人在 CS 还没拉高的时候就切换片选结果两个从设备同时被选中MISO 上出现总线冲突读回来的数据完全不对。5. 性能优化与扩展思路5.1 用 DMA 提升 SPI 吞吐量如果你的系统需要高速读写 MRAM比如连续采集大量数据并实时存储用 DMA 可以大幅提升吞吐量。STM32 的 SPI 支持 DMA 收发配置好 DMA 通道后SPI 传输不需要 CPU 干预CPU 可以去做其他事情。我实测过用阻塞式 SPI 写 1KB 数据到 MRAM耗时约 400 微秒用 DMA 写耗时约 320 微秒而且 CPU 占用从 100% 降到接近 0%。对于数据量大的场景DMA 是必选项。5.2 数据校验与恢复机制工业场景对数据可靠性要求极高所以我在每个数据块后面加了 CRC16 校验。写入时计算 CRC 并一起写入读取时重新计算 CRC 并比对。如果 CRC 不匹配说明数据损坏可以尝试从备份区恢复。MRAM 的容量是 512KB我用了两个 256KB 的区域做镜像主区和备份区。每次写数据时先写主区再写备份区。读取时如果主区 CRC 错误就从备份区读。这样即使某个区域出现坏块数据也不会丢。5.3 后续扩展方向这个方案目前跑在工业数据采集板上后续可以考虑几个扩展方向一是增加 RTC 和 MRAM 配合实现带时间戳的数据日志二是把 FRAM 用作 MCU 的扩展 RAM存放频繁更新的变量减少对内部 RAM 的占用三是增加无线模块把存储的数据定期上传到云端实现远程监控。这些扩展都不难核心的存储架构已经搭好了剩下的就是根据具体需求加功能。我在实际项目中最大的体会是存储方案选对了后面的事情就顺了选错了后面全是坑。MRAM 和 FRAM 虽然贵一点但省心、可靠长期来看反而更划算。