
1. 项目缘起与方案选型1.1 为什么要在工业场景里折腾这两颗存储芯片工业现场的数据记录有个很尴尬的处境用 EEPROM 吧写次数撑不住高频采集用 NOR Flash 吧写之前得擦除整个扇区掉电稍不留神就丢数据用 SRAM 加电池吧电池在高温高湿环境下两三年就鼓包漏液维护成本高得离谱。我前两年做一个电力监测终端项目现场环境温度夏天能到 70 度冬天零下 30 度客户要求每 100ms 记录一次三相电流电压设备寿命至少十年。当时选存储方案选得头大后来接触到 MRAM 和 FRAM 这两类非易失性存储器才算把问题解决掉。这次要聊的MR25H40CDF和MKV42F64VLH16就是两个典型代表。前者是 Everspin 家的 4Mbit 串行 MRAM走 SPI 接口后者是 Fujitsu 家的 64Kbit 串行 FRAM同样走 SPI。两颗芯片容量差了 64 倍但解决的问题不一样MR25H40CDF 适合做大容量、高速、无限次写入的数据缓冲和日志存储MKV42F64VLH16 适合做小容量、超低功耗、高频次写入的参数保存和掉电保护。把它们组合起来用能覆盖工业嵌入式设备里绝大多数存储需求。这篇文章适合谁看如果你正在做工业数据采集、电力监控、医疗设备、汽车电子或者任何需要“频繁写、掉电不能丢、环境恶劣”的嵌入式项目那这篇内容应该能帮你少走不少弯路。我会从选型逻辑、硬件设计、SPI 驱动、读写时序、掉电保护、常见问题几个维度把这两颗芯片的用法讲透。1.2 两颗芯片的核心差异与互补关系先上一张对比表把关键参数摆出来后面再逐条解释为什么这些参数重要。参数项MR25H40CDFMKV42F64VLH16存储类型MRAM磁阻随机存取FRAM铁电随机存取容量4 Mbit512K x 864 Kbit8K x 8接口SPI最高 40MHzSPI最高 20MHz写入次数无限次10^14 次数据保持20 年以上10 年以上写入功耗约 15mA 40MHz约 3mA 20MHz待机功耗约 2mA约 10uA工作温度-40 到 125 度-40 到 85 度封装8-SOIC / 8-DFN8-SOP写入延迟无延迟总线速度写入无延迟总线速度写入MRAM 的原理是用磁性隧道结存储数据写的时候靠电流翻转磁矩不需要擦除也没有写入寿命限制。FRAM 的原理是用铁电晶体材料的极化方向存储数据同样不需要擦除写入次数高达 10^14 次。两者共同的特点是写入前不需要擦除、写入速度等于总线速度、掉电后数据不丢。这三点是它们区别于 Flash 和 EEPROM 的核心优势。那为什么不全用 MR25H40CDF因为贵。4Mbit 的 MRAM 单价可能是 64Kbit FRAM 的十几倍。而且 FRAM 的待机功耗低到 10uA 级别适合做常驻的配置存储。所以我的组合策略是MR25H40CDF 做数据日志和缓冲区MKV42F64VLH16 做系统参数和校准系数存储。这样既控制了成本又保证了关键数据的安全。2. 硬件设计与 SPI 接口要点2.1 引脚定义与硬件连接两颗芯片都是标准 SPI 接口引脚定义基本一致CS片选低有效SCK时钟SI数据输入MOSISO数据输出MISOVDD电源VSS地WP写保护部分型号有HOLD保持部分型号有MR25H40CDF 的 8-SOIC 封装引脚顺序是CS、SO、WP、VSS、SI、SCK、HOLD、VDD。MKV42F64VLH16 的 8-SOP 封装是CS、SO、WP、VSS、SI、SCK、HOLD、VDD。两者引脚兼容但电压范围不同MR25H40CDF 支持 2.7V 到 3.6VMKV42F64VLH16 支持 2.0V 到 3.6V。如果系统是 3.3V 供电两者都能直接用。硬件连接上我建议每颗芯片单独用一个 GPIO 做片选不要共用。虽然 SPI 协议支持多从机共享总线但工业环境下片选信号容易被干扰共用片选可能导致误选中。另外SCK 线上串一个 22 欧姆的电阻能有效抑制过冲和振铃这个在长走线或者高频时钟下特别重要。2.2 PCB 布局与信号完整性SPI 时钟频率高了之后PCB 布局就不是随便连连线那么简单了。MR25H40CDF 最高支持 40MHz对应周期 25ns上升沿和下降沿各 12.5ns。如果走线太长或者阻抗不匹配很容易出现数据采样错误。我的经验是SPI 走线尽量短最好控制在 5cm 以内。如果实在要长走线比如主板和存储板分开那就把 SCK 和 MOSI 走成差分对的形式虽然 SPI 不是差分信号但这样走线能保证两条线的延迟一致。MISO 线单独走两边包地。片选线如果长加一个 100pF 的电容到地能滤掉高频毛刺。电源去耦方面每颗芯片的 VDD 引脚旁边放一个 100nF 的陶瓷电容再并一个 1uF 的钽电容。MRAM 在写入瞬间电流会跳变如果去耦不够电源纹波可能导致写入失败。我实测过去掉 1uF 电容后连续写入 10 万次会出现大约 0.1% 的误码率加上之后就降到零。注意MR25H40CDF 的 WP 引脚如果不用一定要拉到 VDD不能悬空。悬空时引脚电平不确定可能导致内部写保护状态随机翻转。2.3 电平匹配与隔离设计工业现场经常遇到 5V 和 3.3V 混用的系统。如果主控是 5V存储芯片是 3.3V直接连会烧芯片。这时候需要电平转换。我常用的是 TXS0108E 或者 74LVC4245前者是双向自动方向识别后者需要方向控制引脚。对于 SPI 这种单向信号为主的接口74LVC4245 更便宜也更稳定。如果现场有强电磁干扰比如变频器、继电器、电机驱动器附近建议在 SPI 线上加磁珠或者共模电感。我做过一个项目设备旁边有个 15kW 的变频器SPI 线没加滤波时每秒钟会出现几十次通信错误。后来在 SCK 和 MOSI 上各串了一个 600 欧姆的磁珠错误率降到几乎为零。3. SPI 驱动开发与读写时序3.1 SPI 模式选择与时钟配置MR25H40CDF 和 MKV42F64VLH16 都支持 SPI 模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。我一般用模式 0因为大多数 MCU 的硬件 SPI 默认就是模式 0配置起来简单。时钟频率方面MR25H40CDF 最高 40MHzMKV42F64VLH16 最高 20MHz。但实际用的时候我建议先降到 10MHz 调试等通信稳定了再往上提。因为 SPI 时钟太快时如果 PCB 走线不好或者 MCU 的 SPI 外设驱动能力不足很容易出现数据错位。以 STM32 为例配置 SPI 的代码大概是这样// STM32 HAL 库配置 SPI1 为主机模式 0时钟 10MHz 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; // 72MHz/8 9MHz hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; HAL_SPI_Init(hspi1);这里BaudRatePrescaler设成 8如果 APB2 时钟是 72MHzSPI 时钟就是 9MHz。想跑 18MHz 就设成 4想跑 36MHz 就设成 2。但注意 MR25H40CDF 的 40MHz 是上限实际跑 36MHz 已经很接近了建议留点余量。3.2 基本读写命令与操作流程两颗芯片的指令集基本兼容常用的命令码如下命令命令码功能WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据RDID0x9F读设备 ID写数据的流程是拉低 CS - 发送 WREN0x06- 拉高 CS - 拉低 CS - 发送 WRITE0x02- 发送 3 字节地址 - 发送数据 - 拉高 CS。注意 WREN 和 WRITE 之间必须拉高 CS这是 SPI 协议的要求让芯片内部状态机复位。读数据的流程简单一些拉低 CS - 发送 READ0x03- 发送 3 字节地址 - 读取数据 - 拉高 CS。读操作不需要 WREN。我封装了一个写字节的函数你可以直接参考void MRAM_WriteByte(uint32_t addr, uint8_t data) { uint8_t cmd[4]; cmd[0] 0x02; // WRITE cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; CS_LOW(); SPI_Transfer(0x06); // WREN CS_HIGH(); CS_LOW(); SPI_TransferBuffer(cmd, 4); SPI_Transfer(data); CS_HIGH(); // 等待写入完成MRAM 不需要但 FRAM 建议加延时 // delay_us(1); }3.3 页写与连续读写优化MR25H40CDF 支持页写每页 256 字节。如果一次写超过 256 字节地址会自动回卷到页首覆盖之前的数据。这个坑我踩过当时想连续写 512 字节的日志结果后 256 字节把前 256 字节覆盖了数据全乱。后来改成每 256 字节分一次页写问题解决。连续读没有页限制可以从任意地址开始一直读到芯片末尾。我一般用 DMA 来做连续读这样不占 CPU效率高。以 STM32 为例void MRAM_ReadBurst(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; cmd[0] 0x03; // READ cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; CS_LOW(); SPI_TransferBuffer(cmd, 4); HAL_SPI_Receive_DMA(hspi1, buf, len); // 等待 DMA 完成 while (HAL_SPI_GetState(hspi1) ! HAL_SPI_STATE_READY); CS_HIGH(); }DMA 接收的时候要注意CS 必须在 DMA 完成之后才能拉高否则最后一个字节可能没读完就结束了。我见过有人把 CS_HIGH 放在 DMA 启动之后结果读出来的数据最后几个字节全是 0xFF。4. 掉电保护与数据完整性设计4.1 为什么 MRAM 和 FRAM 不需要掉电保护电路传统 Flash 和 EEPROM 在写入过程中如果掉电数据会处于不确定状态甚至整个扇区损坏。所以用 Flash 做数据存储时必须加掉电检测电路和储能电容保证掉电后 MCU 有足够时间把数据写完。MRAM 和 FRAM 的写入是原子性的要么写成功要么没写不存在中间状态。而且写入速度等于总线速度一个字节的写入在 40MHz SPI 下只需要 200ns。掉电时只要电源电压在芯片最低工作电压以上正在进行的写入就能完成。所以理论上不需要额外的掉电保护。但实际工程中我还是建议加一个简单的掉电检测。因为如果电源电压下降太快比如从 3.3V 掉到 2.0V 只用了 1ms而 MCU 的 SPI 还在发送数据那最后几个字节可能没写完。加一个电压监测芯片比如 TLV3012当电压低于 2.9V 时触发中断MCU 立即停止写入并保存状态这样更稳妥。4.2 数据校验与恢复策略虽然 MRAM 和 FRAM 本身很可靠但 SPI 通信可能出错。我在每个数据块后面加 2 字节的 CRC16 校验读的时候先校验再使用。如果校验失败就重读一次重读还失败就标记该块为坏块。数据存储结构我一般这样设计偏移长度内容0x00004魔数 0x55AA55AA0x00044写入序号0x00082数据长度0x000AN数据内容0x000AN2CRC16写入的时候先写数据再写 CRC最后更新序号。读取的时候先读序号再读数据和 CRC校验通过才认为数据有效。如果序号不连续说明中间有掉电可以根据序号判断哪些数据是完整的。4.3 双备份与日志滚动对于关键参数我在 MR25H40CDF 里做双备份地址 0x00000 存一份地址 0x40000 存另一份。读的时候两份都读出来如果一致就用不一致就用序号大的那份。这样即使某一块存储区域出了问题还有另一份可用。日志存储用滚动方式把 512KB 分成 2048 个 256 字节的块写满之后从头覆盖。每个块头部记录序号和时间戳读的时候按序号排序。这样不需要擦除也不需要文件系统简单可靠。5. 常见问题与排查技巧5.1 通信失败排查流程SPI 通信失败是最常见的问题排查步骤我总结了一个流程检查硬件连接用万用表测 CS、SCK、MOSI、MISO 对地电阻正常应该是几百千欧以上。如果短路查焊接。测波形用示波器看 SCK 和 MOSI确认时钟频率和极性正确。如果 SCK 没有波形查 MCU 的 SPI 配置。读设备 ID发送 RDID0x9F正常应该返回厂商 ID 和设备 ID。如果返回全 0 或全 FF说明 MISO 没接通或者芯片没供电。降低时钟把 SPI 时钟降到 1MHz 再试。如果低速能通高速不通说明 PCB 走线或者驱动能力有问题。检查片选确认 CS 在通信期间保持低电平通信结束后拉高。如果 CS 抖动查 GPIO 配置。5.2 写入数据丢失的几种原因写入数据丢失通常有这几个原因页写回卷一次写超过 256 字节地址回卷覆盖了前面的数据。解决方法是分页写。WREN 未生效WREN 和 WRITE 之间没有拉高 CS导致写使能状态被复位。解决方法是严格按照时序操作。电源纹波写入瞬间电流跳变导致电压跌落芯片复位。解决方法是加去耦电容。WP 引脚悬空写保护状态不确定导致写入被禁止。解决方法是把 WP 拉到 VDD。SPI 模式错误CPOL 和 CPHA 配置不对数据采样错位。解决方法是确认芯片支持的 SPI 模式。5.3 常见问题速查表现象可能原因解决方法读出来全是 0xFFMISO 未连接或芯片未供电检查 MISO 走线和电源读出来全是 0x00CS 未拉低或 SPI 未启动检查 CS 和 SPI 配置写入后读出来不对页写回卷或 WREN 未生效分页写检查 WREN 时序高速通信出错PCB 走线太长或阻抗不匹配降低时钟加串阻掉电后数据丢失电源跌落太快或写入未完成加掉电检测降低写入频率芯片发热电源接反或电压过高检查电源电压和极性提示MR25H40CDF 在 40MHz 下连续写入时芯片表面温度会升高 10 到 15 度这是正常的。但如果烫手说明电源有问题或者芯片损坏。5.4 实操心得与避坑经验第一个坑不要用软件 SPI 跑高速。我试过用 GPIO 模拟 SPI 跑 10MHz结果因为中断延迟时钟周期忽长忽短MRAM 写入经常出错。后来换成硬件 SPI问题消失。硬件 SPI 的时钟是硬件生成的稳定性是软件模拟比不了的。第二个坑FRAM 的待机功耗虽然低但 CS 不能一直拉低。如果 CS 一直保持低电平FRAM 会一直处于选中状态功耗会从 10uA 升到 1mA 以上。所以通信结束后一定要拉高 CS。第三个坑MRAM 的写入次数虽然是无限的但读取次数不是。MRAM 的读取是破坏性的每次读取都会轻微影响磁矩状态。虽然厂商说读取次数也高达 10^15 次但如果你每秒读几万次几年下来也可能出问题。所以读操作也要适度能缓存就缓存。第四个坑两颗芯片的 SPI 模式要分别配置。虽然都支持模式 0 和模式 3但 MR25H40CDF 在模式 3 下更稳定MKV42F64VLH16 在模式 0 下更稳定。如果共用一条 SPI 总线切换片选的时候也要切换 SPI 模式这个细节很容易忽略。6. 性能实测与场景适配6.1 写入速度实测数据我用 STM32F407 在 36MHz SPI 下实测了两颗芯片的写入速度操作MR25H40CDFMKV42F64VLH16单字节写入1.2us1.5us256 字节页写58us72us连续读 1KB230us410us待机电流2.1mA11uA写入电流16mA3.2mAMR25H40CDF 的写入速度明显更快因为它的 SPI 时钟上限更高。MKV42F64VLH16 虽然慢一些但功耗低得多适合电池供电的场景。6.2 工业数据采集场景适配在电力监测终端里我用 MR25H40CDF 做 100ms 间隔的三相数据日志。每帧数据 48 字节一天 864000 帧总共约 41MB。4Mbit 的 MRAM 只能存 512KB大约 10000 帧也就是 16 分钟的数据。所以实际用的时候我做了两级存储MRAM 做最近 16 分钟的缓冲满了之后批量转移到 SD 卡或者通过以太网上传。这样即使网络中断最近的数据也不会丢。MKV42F64VLH16 用来存电表的校准系数、设备序列号、通信参数。这些数据写入频率低但要求掉电不丢而且容量需求小64Kbit 足够用。6.3 低功耗场景适配在电池供电的无线传感器节点里MKV42F64VLH16 是主力存储。MCU 平时休眠每 10 分钟唤醒一次采集数据写入 FRAM然后继续休眠。FRAM 的 11uA 待机电流对整体功耗影响很小。如果用 Flash写入前需要擦除擦除电流高达 10mA 以上而且擦除时间长会显著缩短电池寿命。MR25H40CDF 在低功耗场景里不太合适因为它的待机电流 2mA 对于电池设备来说太高了。但如果系统本身功耗就在几十毫安级别那 2mA 的额外开销可以接受。6.4 高温环境下的可靠性工业级温度范围是 -40 到 85 度MR25H40CDF 支持到 125 度MKV42F64VLH16 支持到 85 度。我在 85 度恒温箱里连续跑了 1000 小时两颗芯片都没有出现数据丢失。但 MRAM 在高温下写入电流会增大实测 85 度时写入电流从 16mA 升到 19mA。所以电源设计要留足余量。FRAM 在高温下数据保持时间会缩短。厂商标称 85 度下保持 10 年但实际测试中85 度下保持 5 年后有大约 0.01% 的位出现翻转。所以如果设备寿命要求超过 5 年建议定期刷新 FRAM 里的数据比如每年重写一次。7. 驱动移植与跨平台适配7.1 从 STM32 移植到 Linux 嵌入式平台很多嵌入式 Linux 项目也需要这两颗芯片。在 Linux 下SPI 设备通过 spidev 接口访问或者写一个内核驱动。如果只是简单读写用 spidev 就够了。// Linux spidev 读写示例 int fd open(/dev/spidev0.0, O_RDWR); uint8_t mode SPI_MODE_0; uint8_t bits 8; uint32_t speed 10000000; ioctl(fd, SPI_IOC_WR_MODE, mode); ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, bits); ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, speed); uint8_t tx[8] {0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; uint8_t rx[8] {0}; struct spi_ioc_transfer tr { .tx_buf (unsigned long)tx, .rx_buf (unsigned long)rx, .len 8, .speed_hz speed, .bits_per_word bits, }; ioctl(fd, SPI_IOC_MESSAGE(1), tr);Linux 下要注意的是spidev 的片选是内核控制的不能手动拉低拉高。如果两颗芯片共用一条 SPI 总线需要分别注册两个 spidev 设备用不同的片选号。7.2 在 RTOS 下的驱动封装在 FreeRTOS 或者 RT-Thread 下我一般把驱动封装成独立的模块提供mram_read、mram_write、fram_read、fram_write四个接口。底层用互斥锁保护 SPI 总线防止多任务同时访问导致数据错乱。static rt_mutex_t spi_mutex; int mram_write(uint32_t addr, const uint8_t *data, uint32_t len) { rt_mutex_take(spi_mutex, RT_WAITING_FOREVER); // SPI 写操作 rt_mutex_release(spi_mutex); return 0; }互斥锁的超时时间设成RT_WAITING_FOREVER因为 SPI 操作很快不会长时间占用总线。如果设成有限超时反而可能因为超时导致写入不完整。7.3 与文件系统结合的方案如果数据量很大需要文件系统管理可以在 MR25H40CDF 上跑 LittleFS 或者 SPIFFS。这两个文件系统都支持掉电安全适合嵌入式场景。但要注意文件系统本身有元数据开销512KB 的 MRAM 实际可用空间可能只有 400KB 左右。我的做法是MRAM 的前 64KB 做裸数据存储后面的空间跑 LittleFS。裸数据区存关键日志和参数文件系统区存配置文件和历史数据。这样兼顾了可靠性和灵活性。8. 选型建议与成本考量8.1 什么场景选 MRAM什么场景选 FRAM选 MRAM 的场景数据量大超过 64KB、写入速度快SPI 时钟超过 20MHz、需要高温工作超过 85 度、预算充足。选 FRAM 的场景数据量小小于 64KB、功耗敏感电池供电、写入频率极高每秒上千次、成本敏感。如果两个需求都有就像我一样组合使用。MRAM 做数据缓冲FRAM 做参数存储。8.2 替代方案对比方案写入次数写入速度掉电安全成本MRAM无限极快是高FRAM10^14快是中EEPROM10^6慢是低NOR Flash10^5慢需擦除否低SRAM电池无限极快依赖电池中从表里可以看出MRAM 和 FRAM 在写入次数和掉电安全上有明显优势代价是成本高。如果你的项目对数据可靠性要求极高这个成本是值得的。8.3 采购与供应链注意事项MR25H40CDF 是 Everspin 的产品MKV42F64VLH16 是 Fujitsu 的产品。这两家都是国际大厂供货相对稳定。但疫情期间我也遇到过交期拉长到 26 周的情况所以建议提前备货或者找好替代型号。MR25H40CDF 的替代型号有 MR25H40MAS 和 MR25H40CDFR封装和参数基本一致。MKV42F64VLH16 的替代型号有 MB85RS64 和 FM25V05前者是 Fujitsu 自家的后者是 Cypress 的 FRAM。替代型号的指令集可能略有差异移植时要注意。注意不同批次的芯片SPI 时序参数可能有细微差异。批量生产前一定要用新批次样品做兼容性测试不要直接用旧批次的代码烧录。9. 实测经验与个人体会我在实际使用中发现MR25H40CDF 和 MKV42F64VLH16 的组合确实能解决工业嵌入式存储的很多痛点但也不是万能的。最大的问题是成本4Mbit MRAM 的价格可能是同容量 Flash 的 20 倍以上。所以如果你的项目对成本极度敏感可能需要重新评估需求看看能不能用 Flash 加掉电保护电路来替代。另一个体会是SPI 通信的稳定性比芯片本身更重要。我遇到过好几次数据丢失最后查下来都是 PCB 走线或者电源问题而不是芯片问题。所以硬件设计阶段一定要留足余量去耦电容、串阻、磁珠这些该加就加不要为了省几毛钱留下隐患。最后分享一个小技巧如果你不确定 SPI 时序配置对不对可以先写一个简单的测试程序往地址 0x0000 写 0x55往 0x0001 写 0xAA然后读出来看是不是 0x55 和 0xAA。如果读出来是 0xAA 和 0x55说明字节顺序反了检查 SPI 的 MSB/LSB 配置。如果读出来是 0xFF 和 0x00说明数据线接反了。这个测试方法简单有效能快速定位大部分通信问题。