MRAM替代EEPROM:PIC18F96J65工业嵌入式存储方案实战 1. 为什么 MRAM 在工业嵌入式场景里越来越受关注如果你做过工业现场的数据采集设备大概率经历过这样的场景设备运行在户外配电柜里夏天柜内温度能到 70 度以上冬天在北方野外又能跌到零下 30 度。系统需要每隔几秒记录一次关键运行参数断电之后这些数据不能丢下次上电还得能读出来。传统的做法是用 EEPROM 或者带后备电池的 SRAM但 EEPROM 写入速度慢、擦写寿命有限电池方案又要考虑电池老化和更换维护的问题。MR25H40CDF 这颗芯片就是冲着这类需求来的。它是一颗 4Mbit 的 MRAM磁阻随机存储器走 SPI 接口和常见的 SPI Flash、EEPROM 在硬件连接上几乎一样但内部机理完全不同。MRAM 的存储单元用的是磁性隧道结MTJ通过磁化方向来记录 0 和 1而不是靠电荷。这个物理特性带来几个直接好处写入不需要先擦除字节级随机写入写入速度可以到纳秒级擦写寿命理论上接近无限次而且断电后数据能保持二十年以上。我第一次在项目里用 MRAM 替代 EEPROM 是因为一个铁路信号监测的板子。原来的方案用 SPI EEPROM 存故障日志现场反馈说偶尔会出现日志丢失的情况。排查下来发现是掉电瞬间正好在写 EEPROM而 EEPROM 的写周期比较长掉电导致写入不完整。换成 MR25H40CDF 之后因为它是字节级写入且写入极快掉电窗口大大缩小这个问题就基本消失了。PIC18F96J65 是 Microchip 的一款 8 位单片机带 96KB Flash、3936 字节 RAM集成了 SPI 模块工作电压范围宽工业级温度范围也能覆盖。它和 MR25H40CDF 搭配算是一个很典型的工业嵌入式数据存储方案单片机负责采集和处理MRAM 负责可靠地保存关键数据。下面我会从硬件连接、SPI 驱动、读写时序、掉电保护、实际踩坑几个角度把这个组合的用法讲透。2. MR25H40CDF 的引脚与 PIC18F96J65 的 SPI 硬件对接2.1 MR25H40CDF 引脚定义和封装选择MR25H40CDF 常见的是 8 引脚 SOIC 封装引脚定义和标准 SPI Flash 非常接近但有几个细节需要注意。它的引脚包括片选 CS、时钟 SCK、数据输入 SI、数据输出 SO、写保护 WP、保持 HOLD、电源 VDD 和地 VSS。这里要特别说 WP 和 HOLD 两个引脚。WP 是写保护低电平有效也就是说如果你把它拉低芯片内部的状态寄存器写保护位就会被锁定无法修改。HOLD 是保持引脚低电平有效拉低之后可以暂停当前的 SPI 通信而不丢失时序状态适合多主机或者需要中途让出总线的场景。在实际工业设计中我的建议是WP 引脚不要直接接地而是接到单片机的一个 GPIO 上这样你可以在需要写保护的时候主动拉低正常读写的时候拉高。HOLD 引脚如果不用直接上拉到 VDD 即可不要悬空悬空容易受干扰导致通信异常。2.2 PIC18F96J65 的 SPI 模块配置要点PIC18F96J65 内部有一个 MSSP 模块可以配置成 SPI 主模式。配置的时候有几个寄存器需要设置SSPCON1、SSPSTAT、SSPADD主模式下用于波特率设置。SPI 模式有四种由 CKP 和 CKE 两个位决定。MR25H40CDF 支持 SPI 模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。我一般用模式 0因为大多数 SPI 从设备都支持调试起来也方便。在 PIC18F96J65 上设置 SSPCON1 的 CKP0SSPSTAT 的 CKE0就是模式 0。波特率方面PIC18F96J65 的 SPI 时钟来源于系统时钟的分频。假设系统时钟是 64MHz通过 PLL 倍频SPI 时钟可以设置到 16MHz 甚至更高。但 MR25H40CDF 的最高 SPI 时钟是 40MHz所以从芯片规格上不是瓶颈。实际布线的时候如果走线比较长或者环境干扰大建议先降到 10MHz 左右跑通再逐步往上提。// PIC18F96J65 SPI 主模式初始化示例 void SPI_Init(void) { TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC5 0; // SDO 输出 TRISCbits.TRISC4 1; // SDI 输入 TRISDbits.TRISD0 0; // CS 输出根据实际连接调整 SSPCON1 0x20; // SSPEN1, CKP0, SPI 主模式 Fosc/4 SSPSTAT 0x00; // CKE0, SMP0 SSPADD 0x03; // 根据实际时钟调整波特率 CS 1; // 片选默认拉高 }2.3 硬件布线中容易忽略的细节SPI 总线在工业环境里跑布线有几个坑我踩过。第一是 CS 信号的处理很多人用软件片选就是普通 GPIO 控制。软件片选的问题是在 CS 拉低到第一个时钟沿之间如果中断打断了操作时序就会乱。所以如果 SPI 速率比较高建议用硬件片选或者至少在操作 SPI 的时候关中断。第二是 SCK 和数据的走线长度。SPI 是同步总线SCK 和 MOSI/MISO 如果走线长度差异太大在高频下会出现建立保持时间不够的问题。我的经验是SCK 和 MOSI 尽量等长走线MISO 可以稍微宽松一点但也不要差太多。如果板子上有多个 SPI 从设备每个设备的 CS 单独走线SCK 和 MOSI/MISO 可以共用。第三是电源去耦。MR25H40CDF 在写入瞬间电流会有波动建议在 VDD 引脚旁边放一个 0.1uF 的陶瓷电容再并一个 1uF 的电容。这两个电容要尽量靠近芯片引脚走线越短越好。3. MR25H40CDF 的读写命令与时序拆解3.1 状态寄存器与写使能机制MR25H40CDF 和 SPI Flash 一样有状态寄存器和写使能锁存机制。每次写操作之前必须先发送 WREN写使能命令把内部的写使能锁存器置位。写操作完成后写使能锁存器会自动复位。这个机制是为了防止误写入。状态寄存器里有一个 WEL 位表示写使能锁存器的状态还有一个 WIP 位表示写操作是否正在进行。MRAM 的写入速度极快WIP 位通常很快就清零但为了保险写完一个字节或者一页之后还是建议读一下状态寄存器确认 WIP 为 0。// 发送写使能命令 void MRAM_WriteEnable(void) { CS 0; SPI_WriteByte(0x06); // WREN 命令 CS 1; } // 读取状态寄存器 uint8_t MRAM_ReadStatus(void) { uint8_t status; CS 0; SPI_WriteByte(0x05); // RDSR 命令 status SPI_ReadByte(); CS 1; return status; }3.2 读命令与地址组织MR25H40CDF 的容量是 4Mbit也就是 512KB地址范围从 0x00000 到 0x7FFFF需要 19 位地址。读命令是 0x03后面跟 3 个字节的地址然后就可以连续读出数据。地址会自动递增所以可以一次读出一整块数据。这里有个细节MRAM 的读操作没有页的概念你可以从任意地址开始读任意长度不像 Flash 那样有页边界限制。这在实际使用中很方便比如你要读一个结构体直接从结构体起始地址读 sizeof 个字节就行。// 从指定地址读取数据 void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { CS 0; SPI_WriteByte(0x03); // READ 命令 SPI_WriteByte((addr 16) 0xFF); SPI_WriteByte((addr 8) 0xFF); SPI_WriteByte(addr 0xFF); for (uint16_t i 0; i len; i) { buf[i] SPI_ReadByte(); } CS 1; }3.3 写命令与字节级写入的优势写命令是 0x02后面同样跟 3 字节地址然后是要写入的数据。MRAM 的写入是字节级的不需要擦除也不需要按页对齐。这是它和 Flash 最大的区别。Flash 写入之前必须先擦除整个扇区而 MRAM 可以直接覆盖写。这个特性在存储日志或者频繁更新的参数时特别有用。比如你要更新一个 4 字节的计数器直接往那个地址写 4 个字节就行不需要读-改-写整个扇区。这大大简化了软件逻辑也减少了写入延迟。// 向指定地址写入数据 void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_WriteEnable(); CS 0; SPI_WriteByte(0x02); // WRITE 命令 SPI_WriteByte((addr 16) 0xFF); SPI_WriteByte((addr 8) 0xFF); SPI_WriteByte(addr 0xFF); for (uint16_t i 0; i len; i) { SPI_WriteByte(buf[i]); } CS 1; // 等待写入完成 while (MRAM_ReadStatus() 0x01); }3.4 SPI 时序中的建立保持时间MR25H40CDF 的 SPI 时序要求数据在 SCK 上升沿采样在下降沿变化。这意味着在模式 0 下MOSI 数据要在 SCK 上升沿之前稳定MISO 数据在 SCK 下降沿之后变化。PIC18F96J65 的 SPI 模块在模式 0 下SCK 空闲为低数据在 SCK 上升沿采样正好匹配。实际调试的时候如果发现读出来的数据不对第一件事就是用示波器或者逻辑分析仪看 SCK 和 MOSI/MISO 的波形。重点看 CS 拉低到第一个 SCK 沿之间的时间以及最后一个 SCK 沿到 CS 拉高之间的时间。MR25H40CDF 对这两个时间有要求CS 建立时间最小 5ns保持时间最小 5ns。在低速下这不是问题但高速下如果 CS 控制不及时就会出错。4. 工业场景下的数据可靠性设计4.1 掉电检测与写入时机工业设备最怕的就是掉电时数据写了一半。MRAM 虽然写入快但如果电源电压跌到芯片工作电压以下写入还是会失败。所以掉电检测电路是必须的。我的做法是用一个电压监测芯片比如 MCP131当电源电压跌到阈值以下时产生一个中断给单片机单片机在中断里立即停止其他操作把关键数据写入 MRAM。这里有个时间预算的问题。从掉电检测到单片机响应中断再到完成 SPI 写入整个过程要在电源电压跌到 MRAM 最低工作电压之前完成。MR25H40CDF 的最低工作电压是 2.7V假设电源从 3.3V 跌到 2.7V 的时间是 10ms那么你的写入操作必须在 10ms 内完成。MRAM 写入一个字节的时间是纳秒级SPI 传输 4 个字节在 10MHz 下是 3.2us所以时间非常充裕。但如果你要写几 KB 的数据就要算一下了。4.2 数据校验与冗余存储MRAM 本身可靠性很高但在工业环境里电磁干扰可能导致 SPI 通信出错。所以写入的数据最好加上校验。我一般用 CRC16 或者简单的校验和。存储的时候数据区后面跟两个字节的 CRC读取的时候校验一下如果不匹配就重读或者报错。另外关键数据可以做双备份。比如在 MRAM 的 0x00000 和 0x40000 各存一份读取的时候两份都读出来对比如果一致就用不一致就用 CRC 正确的那份。这个做法在铁路和电力项目里很常见因为一旦数据出错后果可能很严重。4.3 写保护策略的灵活运用前面提到 WP 引脚不要直接接地就是为了灵活控制写保护。在正常运行时WP 拉高允许写入。当系统进入某个关键状态比如正在执行不可中断的操作时可以把 WP 拉低防止误写入。另外MR25H40CDF 内部的状态寄存器也有块保护位可以保护部分地址区域不被写入。你可以把引导程序或者出厂参数放在受保护的区域应用层代码无法修改。5. 从 EEPROM 迁移到 MRAM 的实操经验5.1 软件层面的改动量如果你原来的项目用的是 SPI EEPROM比如 25LC256迁移到 MR25H40CDF 的软件改动其实不大。命令集基本兼容READ、WRITE、WREN、RDSR 这些命令的编码是一样的。主要区别在于地址字节数25LC256 是 2 字节地址MR25H40CDF 是 3 字节地址。所以你需要把地址发送的部分从 2 字节改成 3 字节。另外EEPROM 写入之后需要等待 5ms 左右的写周期而 MRAM 几乎不需要等待。所以原来代码里的延时可以去掉或者改成查询 WIP 位。这个改动会让写入速度大幅提升。5.2 硬件层面的兼容性硬件上MR25H40CDF 和 25LC256 都是 8 引脚 SOIC引脚定义也基本一致。但要注意 WP 和 HOLD 引脚的位置可能不同画 PCB 的时候要仔细核对数据手册。另外MRAM 的写入电流比 EEPROM 稍大电源设计要留够余量。5.3 成本与收益的权衡MRAM 的价格比 EEPROM 贵不少这是事实。但如果你的应用场景对数据可靠性要求极高或者写入频率很高EEPROM 的擦写寿命可能撑不住。比如一个每秒写一次日志的设备EEPROM 的 100 万次擦写寿命只能撑 11 天左右如果每次都写同一地址。而 MRAM 没有这个限制可以用好多年。所以选型的时候要算总账不能只看芯片单价。6. 调试过程中遇到的典型问题与排查思路6.1 读出来的数据全是 0xFF这个问题我遇到过好几次原因通常有几个。第一是 CS 没有正确拉低或者 CS 拉低之后没有等待足够的时间就发时钟。第二是 SPI 模式不对比如从设备要求模式 0你配成了模式 3。第三是 MISO 线没有接好或者被其他设备拉住了。排查的时候先用示波器看 CS、SCK、MOSI、MISO 四根线。CS 拉低之后SCK 应该有 8 个脉冲对应一个字节。MOSI 上应该能看到命令码和地址。MISO 上如果一直是高电平说明从设备没有响应检查电源和片选。6.2 写入后读出来数据不对这种情况通常是写使能没有成功或者写入过程中被中断打断了。检查 WREN 命令之后读一下状态寄存器的 WEL 位确认是 1。另外写入过程中如果 CS 被意外拉高写入会中止。所以写操作期间要关中断或者确保没有其他任务操作 SPI 总线。还有一种可能是地址算错了。MR25H40CDF 是 512KB地址范围 0x00000 到 0x7FFFF。如果你写的地址超出了这个范围芯片会回绕到 0 地址。所以写之前确认一下地址有没有越界。6.3 高速 SPI 下的通信不稳定当 SPI 时钟超过 20MHz 时如果 PCB 布线不理想可能会出现偶发错误。这时候可以尝试降低 SPI 时钟看问题是否消失。如果降速后正常说明是信号完整性问题。解决办法包括缩短走线、增加地线隔离、在 SCK 上串一个 22 欧姆的电阻阻尼电阻来减少反射。另外PIC18F96J65 的 SPI 模块在高频下如果中断优先级配置不当SPI 传输可能被高优先级中断打断导致时序错乱。我的做法是把 SPI 传输放在高优先级中断里或者传输期间关掉其他中断。6.4 掉电后数据丢失如果掉电后数据丢失首先要确认掉电检测电路是否正常工作。用示波器看掉电检测中断信号和电源电压的波形确认中断产生时电源电压还在 MRAM 工作范围内。其次检查写入操作是否真的完成了。可以在写入后立即读回来对比确认数据正确。最后如果数据量比较大算一下写入时间是否超过了电源维持时间。7. 这个方案还能怎么扩展MR25H40CDF 加 PIC18F96J65 的组合除了存数据还能做点别的。比如你可以把 MRAM 当成一个高速数据缓冲区采集到的数据先写到 MRAM然后再慢慢处理或者通过串口发出去。因为 MRAM 写入快不会丢数据。另外MRAM 的非易失特性可以用来存引导程序。PIC18F96J65 本身有 Flash但如果你的程序需要现场升级可以把新固件先存到 MRAM 里校验通过后再写入 Flash。这样即使升级过程中掉电也不会变砖因为 MRAM 里的固件还在。还有一个思路是用 MRAM 存文件系统。虽然 512KB 不大但对于存配置参数、日志文件来说足够了。你可以自己写一个简单的文件系统或者用现成的嵌入式文件系统把 MRAM 当成块设备来用。我在实际项目里还遇到过一种需求设备需要记录运行时间精确到秒而且断电不能丢。用 MRAM 就很合适每秒更新一次计数器写入量完全在承受范围内。如果用 EEPROM每秒写一次几年下来就超过擦写寿命了。最后说一个选型上的建议。如果你的项目对成本敏感数据写入频率不高EEPROM 或者 Flash 可能更合适。但如果你的项目对可靠性要求高写入频繁或者工作环境恶劣MRAM 多花的钱是值得的。我在几个工业项目里用了 MR25H40CDF最长的已经跑了三年多没有出现过数据丢失的情况。这个稳定性是 EEPROM 很难保证的。