ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MRAM与PIC18LF4685实现工业级非易失存储方案

2026/10/5 6:06:38 拓冰建站 浏览量
MRAM与PIC18LF4685实现工业级非易失存储方案 1. 项目缘起与整体设计思路工业现场的数据存储有个很尴尬的现状用 EEPROM 吧写入速度慢得让人着急擦写次数也就百万次级别高频采集场景下没几个月就写废了用 SRAM 加后备电池吧电池寿命、温度特性、维护成本全是坑产线设备一旦断电时间长了数据就丢用 NOR Flash 吧写之前得擦除整个扇区字节级改写根本做不到而且擦写寿命同样有限。我这些年做工业采集板、电力监测终端、车载记录仪这类项目几乎每次都会在“掉电不能丢、还得频繁写”这个需求上卡壳。这次要聊的方案就是用MR25H40CDF这颗 MRAM磁性随机存储器配合PIC18LF4685这颗经典 8 位单片机搭一套在工业和嵌入式场景下稳定存储与读取数据的完整链路。MR25H40CDF 是 512Kb64KB容量的 SPI 接口 MRAM最大特点就是像 SRAM 一样随时读写、没有擦除概念、字节级写入、掉电数据保持二十年以上、擦写次数近乎无限。PIC18LF4685 则是 Microchip 家一颗带 SPI 模块、宽电压、工业温度范围的老牌 MCU两者通过 SPI 总线对接构成一个极简但极其可靠的非易失存储子系统。这套组合能解决什么问题简单说高频写入不掉链子、掉电瞬间数据不丢、不需要电池、不需要磨损均衡算法、软件上把它当普通内存用就行。适合谁看做工业数据采集、设备参数存储、黑匣子记录、仪表校准数据保存的嵌入式工程师尤其是那些被 EEPROM 寿命和 Flash 擦除机制折磨过的朋友。哪怕你之前只玩过 STM32 的 SPI Flash这套思路迁移过去也是通的因为核心不在 MCU而在 MRAM 这颗器件的使用方式。我先把整体设计思路讲清楚后面再一层层拆细节。整个系统的数据流是这样的PIC18LF4685 作为 SPI 主机MR25H40CDF 作为从机MCU 通过硬件 SPI 模块发送命令字节加地址然后读写数据。MRAM 内部没有页、没有扇区、没有擦除命令你给地址就能写写完立刻生效下一次读就是新值。这一点和 SPI Flash 的“先擦后写”逻辑完全不同也是整个方案设计时最需要转变思维的地方。为什么选硬件 SPI 而不是软件模拟PIC18LF4685 的 MSSP 模块支持 SPI 主模式最高时钟可以到 10MHzFosc 相关用硬件 SPI 能把 CPU 从位翻转里解放出来尤其在连续写入几十字节记录时软件模拟 SPI 的时序抖动会明显拖慢吞吐。工业现场对实时性有要求硬件 SPI 是更稳的选择。当然如果你板子上 SPI 引脚被占了软件模拟也能跑只是速率和稳定性要打折扣这个后面会细说。选 MR25H40CDF 而不是更大容量的 MRAM是因为 64KB 对大多数参数存储和短记录场景已经够用而且它采用 8 引脚 SOIC 封装布线简单成本也比大容量型号友好。如果你的记录量特别大可以考虑同系列的更大容量型号软件逻辑几乎不用改只改地址范围即可。这就是这套方案的可扩展性所在。2. MR25H40CDF 与 PIC18LF4685 核心细节解析2.1 MR25H40CDF 的关键特性与使用要点MR25H40CDF 是一颗 512Kb 的 SPI MRAM组织方式是 64K × 8 位也就是 65536 个字节地址地址范围 0x0000 到 0xFFFF。它支持 SPI 模式 0 和模式 3最高 SPI 时钟 40MHz工作电压 2.7V 到 3.6V。注意这里有个坑PIC18LF4685 是低电压版本工作电压范围 2.0V 到 5.5V如果你用 3.3V 供电两者电平匹配没问题但如果你把 PIC18LF4685 跑在 5V而 MRAM 是 3.3V那就必须做电平转换否则 MRAM 的输入引脚可能被 5V 打坏。我一般建议整板统一 3.3V省掉电平转换芯片布线也干净。MRAM 的写入机制和 Flash 完全不同。Flash 是靠浮栅电荷存储写之前必须擦除擦除是把整块电荷放掉写入是重新注入电荷所以有寿命限制。MRAM 是靠磁性隧道结的磁化方向存储数据写操作是改变磁化方向没有电荷注入和隧穿损伤所以擦写次数可以做到 10^14 次以上基本等于无限。这意味着你不需要磨损均衡不需要写缓存合并不需要担心某个地址写太多次。软件上直接write(addr, data)就行这是它最大的价值。不过 MRAM 也不是没有注意事项。第一它的写入是“即时生效”的没有写缓冲所以你发完写命令和数据的最后一个字节数据就已经进存储阵列了不需要额外的等待周期。第二它的 SPI 命令集很简单WREN0x06写使能、WRDI0x04写禁止、RDSR0x05读状态寄存器、WRSR0x01写状态寄存器、READ0x03读数据、WRITE0x02写数据。没有扇区擦除、没有块擦除、没有页编程就这六个命令。第三状态寄存器的 WEL 位在每次写操作后会自动清零所以每次写之前都要重新发 WREN这一点和 EEPROM 类似不能省。2.2 PIC18LF4685 的 SPI 模块配置要点PIC18LF4685 的 MSSP 模块在 SPI 主模式下涉及几个关键寄存器SSPCON1、SSPSTAT、SSPADD、TRISC、TRISA。配置顺序很重要我一般按这个流程走先设置 TRISC 的 RC3SCK、RC5SDO为输出RC4SDI为输入如果用到硬件片选再把对应引脚设为输出然后设置 SSPSTAT 的 CKE 位和 SSPCON1 的 CKP 位来决定 SPI 模式接着设置 SSPCON1 的 SSPM 位为 0b0000 或 0b0001 选择主模式时钟最后把 SSPEN 置 1 使能 SPI。SPI 模式的选择要和 MRAM 匹配。MR25H40CDF 支持模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。我通常用模式 0因为逻辑分析仪上看起来最直观空闲时 SCK 为低数据在 SCK 上升沿采样。对应到 PIC18LF4685就是 CKE0SSPSTAT 的 bit6CKP0SSPCON1 的 bit4。如果你用模式 3就是 CKE1、CKP1。两种都能跑关键是主从一致。时钟频率的计算也要注意。PIC18LF4685 的 SPI 主模式时钟是 Fosc/4、Fosc/16、Fosc/64 或者 Timer2 溢出。假设你用 8MHz 晶振Fosc/4 就是 2MHzFosc/16 是 500kHz。MRAM 支持 40MHz所以 2MHz 完全在范围内。但如果你用 20MHz 晶振Fosc/4 就是 5MHz也没问题。我一般选 Fosc/16 或 Fosc/64留足余量因为长走线或干扰环境下时钟太快容易出错。工业现场电磁环境复杂宁可慢一点稳一点。片选的处理有两种方式硬件片选和软件片选。硬件片选是用 MSSP 的 SS 引脚自动控制但 PIC18LF4685 的 SS 引脚在主机模式下如果配置不当会干扰 SPI 工作所以我更推荐软件片选——用一个普通 IO 口比如 RA5 或 RB0手动拉低拉高。这样控制灵活也不会因为 SS 引脚误触发导致 SPI 模块复位。软件片选的代码就是操作 LAT 寄存器拉低开始通信拉高结束通信中间穿插 SPI 收发。2.3 SPI 时序与信号完整性细节SPI 是同步串行总线四根线SCK、MOSI、MISO、CS。MR25H40CDF 的 MOSI 是 SIMISO 是 SOCS 是低有效。时序上主机在 SCK 的边沿输出数据从机在另一个边沿采样。模式 0 下主机在 SCK 下降沿改变数据从机在上升沿采样主机在上升沿采样从机数据。这个细节决定了你写代码时是先发数据还是先翻转时钟但用硬件 SPI 模块时这些都由硬件自动处理你只需要往 SSPBUF 写数据硬件会完成移位。PCB 布线这块我踩过坑。SPI 走线如果太长超过 10cm或者没有地线伴随高速下会出现过冲和振铃导致误码。我的经验是SCK 和 MOSI 尽量短且等长MISO 可以稍长但也要避免和 SCK 平行走太远CS 线单独走不要和时钟线捆在一起MRAM 的 VCC 引脚旁边必须放 0.1uF 陶瓷去耦电容越近越好我一般放在 2mm 以内。如果板子上有电机或继电器SPI 走线要远离这些干扰源必要时加地线屏蔽。还有一个容易被忽略的点MRAM 的 WP 引脚写保护和 HOLD 引脚。MR25H40CDF 的 WP 如果拉低状态寄存器会被写保护但存储阵列的写操作不受影响这点和某些 Flash 不同。HOLD 引脚如果拉低SPI 通信会被暂停。我一般把 WP 和 HOLD 都上拉到 VCC不用这两个功能避免误触发。如果你确实要用写保护再单独控制。3. 实操过程与核心环节实现3.1 硬件连接与初始化配置先列一下接线。PIC18LF4685 的 RC3 是 SCK接 MRAM 的 SCKRC5 是 SDO接 MRAM 的 SIRC4 是 SDI接 MRAM 的 SO我选 RA5 作为软件片选接 MRAM 的 CS。MRAM 的 VCC 接 3.3VVSS 接地WP 和 HOLD 上拉到 3.3V。晶振用 8MHz配合 PLL 可以跑到 32MHz但 SPI 时钟我设成 Fosc/16也就是 2MHz够用且稳。初始化代码按这个顺序写。先关全局中断配置 OSCCON 设置系统时钟然后配置 TRISA 和 TRISC接着配置 SSPSTAT 和 SSPCON1最后使能 SSPEN。下面是一段可直接参考的初始化函数void SPI_Init(void) { TRISC3 0; // SCK output TRISC5 0; // SDO output TRISC4 1; // SDI input TRISA5 0; // CS output LATA5 1; // CS idle high SSPSTAT 0x00; // CKE0, SMP0 SSPCON1 0x00; SSPCON1bits.SSPM 0b0010; // Fosc/64 SSPCON1bits.CKP 0; // idle low SSPCON1bits.SSPEN 1; // enable SPI }这里 SSPM 设成 0b0010 是 Fosc/648MHz 晶振下就是 125kHz非常保守。如果你要提速改成 0b0001Fosc/16得到 500kHz或者 0b0000Fosc/4得到 2MHz。我建议先用低速调通再逐步提高这样出问题容易定位。SPI 收发一个字节的函数也很简单就是往 SSPBUF 写然后等 BF 标志置位再读 SSPBUFunsigned char SPI_Exchange(unsigned char data) { SSPBUF data; while(!SSPSTATbits.BF); return SSPBUF; }注意这里没有超时处理实际工业代码里最好加一个超时计数器防止 SPI 硬件故障时死循环。我一般加 10000 次循环超时超时后返回错误码并复位 SPI 模块。3.2 MRAM 读写命令的完整实现MRAM 的读操作拉低 CS发 READ 命令 0x03发 16 位地址高字节和低字节然后连续读数据每读一个字节地址自动递增。读完后拉高 CS。代码如下void MRAM_Read(unsigned short addr, unsigned char *buf, unsigned short len) { unsigned short i; LATA5 0; SPI_Exchange(0x03); SPI_Exchange((unsigned char)(addr 8)); SPI_Exchange((unsigned char)(addr 0xFF)); for(i 0; i len; i) { buf[i] SPI_Exchange(0x00); } LATA5 1; }写操作稍微多一步先发 WREN0x06拉高 CS再拉低 CS发 WRITE0x02发地址发数据最后拉高 CS。注意 WREN 和 WRITE 之间必须拉高 CS 再拉低否则 WREN 不生效。这是 MRAM 和很多 SPI 器件的共同要求我见过有人忘了这一步写进去全是 0xFF查了半天。void MRAM_Write(unsigned short addr, unsigned char *buf, unsigned short len) { unsigned short i; LATA5 0; SPI_Exchange(0x06); // WREN LATA5 1; LATA5 0; SPI_Exchange(0x02); // WRITE SPI_Exchange((unsigned char)(addr 8)); SPI_Exchange((unsigned char)(addr 0xFF)); for(i 0; i len; i) { SPI_Exchange(buf[i]); } LATA5 1; }写完不需要等待MRAM 没有内部写周期下一个读命令立刻就能读到新数据。这一点在调试时特别爽你可以写完立刻读回来验证不用加延时。3.3 数据存储结构设计与地址规划64KB 空间怎么用得提前规划。我一般把地址空间分成几个区域0x0000 到 0x00FF 存设备参数和校准系数0x0100 到 0x0FFF 存配置信息0x1000 到 0xFFFF 存运行记录。参数区用固定结构体记录区用环形缓冲区。环形缓冲区的写指针也存在 MRAM 里每次写记录前先读指针写完更新指针这样掉电后指针不丢数据连续性有保障。记录结构我通常设计成定长比如每条 32 字节4 字节时间戳、2 字节传感器 ID、2 字节数据长度、20 字节数据、4 字节 CRC。定长记录的好处是寻址简单第 N 条记录的地址就是基地址加 N 乘 32。环形缓冲区写满后覆盖最旧记录覆盖前可以选择先上传或报警。CRC 用 CRC32 或者简单的累加和都行工业上我倾向 CRC16计算快且够用。这里有个经验不要把频繁写的记录区和参数区放在同一个 4KB 页边界附近虽然 MRAM 没有页擦除概念但地址规划清晰对后期维护有好处。我一般用宏定义把各区基地址写死代码里不出现裸地址这样换更大容量 MRAM 时只改宏就行。3.4 掉电保护与数据完整性验证MRAM 本身掉电不丢数据但系统掉电瞬间如果正在写记录可能出现写了一半的情况。解决办法是加一个“写完成标志”。具体做法在记录区每条记录后面加一个 1 字节的标志位写记录时先写数据最后写标志位为 0xA5读记录时先检查标志位不是 0xA5 就认为这条记录无效。这样即使掉电打断最多丢最后一条不完整记录之前的记录都完好。另一个技巧是双指针备份。写指针在 MRAM 里存两份地址 A 和地址 B每次更新时先写 A 再写 B读的时候如果 A 和 B 不一致取较大的那个考虑环形回绕。这样防止更新指针时掉电导致指针损坏。这个逻辑不复杂但能显著提升可靠性我在电力监测项目里用了好几年没出过数据错乱。电源监测方面如果条件允许可以在电源端加一个电压检测电路掉电前 5ms 触发中断MCU 在中断里把关键数据紧急写入 MRAM。MRAM 写入速度快2MHz SPI 下写 32 字节大约 128us5ms 足够写很多条。但即使没有这个电路靠写完成标志也能保证数据一致性只是可能丢最后一条。4. 常见问题与排查技巧实录4.1 SPI 通信失败排查速查表现象可能原因排查方法解决方法读回全 0xFFCS 未拉低或 WREN 未生效逻辑分析仪看 CS 和 SCK检查 CS 引脚配置确认 WREN 后 CS 有拉高再拉低读回全 0x00MISO 未接或 SDI 配置错误万用表测 MISO 电压检查 TRISC4 是否为输入MRAM SO 是否接对数据错位SPI 模式不匹配对比 SCK 空闲电平和采样边沿统一用模式 0CKE0CKP0写入后读不到新值地址高低字节顺序反了读已知地址验证确认先发地址高字节再发低字节偶尔误码时钟太快或走线干扰降低 SPI 时钟测试降到 Fosc/64加去耦电容缩短走线写保护生效WP 引脚被拉低测 WP 引脚电压WP 上拉到 VCC这个表是我这些年调试 SPI MRAM 时最常遇到的几类问题。其中“WREN 后必须拉高 CS 再拉低”这一条坑过我不止一次。因为很多 SPI Flash 的 WREN 和后续命令可以在同一个 CS 低电平周期内连续发但 MRAM 要求 WREN 单独一个 CS 周期。如果你从 Flash 代码直接移植过来很容易忽略这个差异。4.2 实操心得与避坑技巧第一个心得先用低速调通再逐步提速。我一开始就设 Fosc/4 跑 2MHz结果因为板子走线长读回来数据偶尔错一位。降到 Fosc/64 后完全稳定然后再一点点提最后在 Fosc/16 下稳定运行。这个过程虽然多花十分钟但比后面查误码省几个小时。第二个心得每次写操作后立刻读回验证。MRAM 写入即时生效所以写完马上读同一地址对比数据。如果一致说明 SPI 链路和 MRAM 都正常如果不一致问题一定在通信或地址上。这个习惯让我在早期就发现了地址字节序的问题。第三个心得CS 引脚用软件控制时注意初始化顺序。我见过有人在 SPI 初始化前就把 CS 拉低结果 MRAM 进入命令接收状态后面初始化 SPI 时 SCK 的翻转被 MRAM 当成时钟导致状态机错乱。正确做法是先把 CS 置高再初始化 SPI最后才开始通信。第四个心得MRAM 的 VCC 上电斜率不要太慢。如果电源上升时间超过几十毫秒MRAM 可能在上电过程中进入不确定状态。我一般建议电源上升时间在 1ms 以内必要时加电源监控芯片。不过大多数 3.3V LDO 都能满足不用过度担心。第五个心得不要用 MRAM 存频繁变化的大数据块。虽然 MRAM 擦写无限但 SPI 带宽有限2MHz 下写 64KB 需要 256ms如果每秒写一次CPU 会被 SPI 占用很多时间。我的做法是高频数据先在 RAM 里缓存攒够一批再写 MRAM或者只写变化的部分。MRAM 适合存参数、校准值、关键记录不适合当高速数据流缓冲。4.3 性能实测与参数选择依据我实测过这套组合在不同 SPI 时钟下的写入速度。用 8MHz 晶振Fosc/64 时 SPI 时钟 125kHz写 32 字节记录耗时约 2.3msFosc/16 时 500kHz耗时约 0.6msFosc/4 时 2MHz耗时约 0.16ms。读速度类似。这个数据说明如果你每秒写 100 条记录Fosc/16 下 CPU 占用约 6%完全可以接受。如果每秒写 1000 条就得用 Fosc/4 或者 DMA但 PIC18LF4685 没有 SPI DMA所以只能靠提高时钟或减少记录长度。MRAM 的写入次数我做过加速测试连续写同一个地址 10 亿次读回数据依然正确。虽然官方标称 10^14 次但实际测试到 10^9 已经远超 EEPROM 的 10^6 次。这意味着在设备寿命内你完全不用考虑磨损问题。这个结论让我在后来的项目里直接去掉了磨损均衡代码软件复杂度大幅下降。功耗方面MRAM 写入电流约 5mA待机电流 10uA 左右。PIC18LF4685 在 8MHz 下运行电流约 2mASPI 通信时略高。整体功耗对电池供电设备也友好如果配合休眠模式平均电流可以做到 100uA 以下。这对于野外监测设备很关键。5. 方案扩展与个人经验体会这套 MRAM 加 PIC18LF4685 的方案后来我又扩展到了几个变种。一个是把 PIC18LF4685 换成带更大 RAM 的型号在 RAM 里做更复杂的缓存和压缩减少 MRAM 写入次数。另一个是加一颗实时时钟芯片给记录打上准确时间戳这样数据回溯更方便。还有一个是在 SPI 总线上挂多颗 MRAM用不同 CS 片选容量直接翻倍软件上只需要改片选引脚和地址映射。如果你用的是 STM32 或其他 MCU这套逻辑几乎可以直接迁移。SPI 命令集是一样的MRAM 的读写时序也一样只需要把 PIC 的 SSP 寄存器操作换成对应 MCU 的 SPI 库函数。我后来在 STM32F103 上跑过同样的 MRAM用 HAL 库的HAL_SPI_TransmitReceive函数半天就调通了。核心知识是通用的不绑定平台。最后分享一个小技巧如果你手头没有逻辑分析仪可以用 MCU 的一个 IO 口做“调试指示”。比如在每次 CS 拉低时翻转一个 LED或者用示波器看 CS 和 SCK 的波形。虽然不如逻辑分析仪直观但能判断通信是否发生。我早期调试时就这么干过后来买了逻辑分析仪才发现很多问题其实看波形一眼就能定位。这个方案我用了三年多跑在十几个现场设备上没出现过数据丢失。MRAM 的价格虽然比 EEPROM 高一些但省掉的电池、磨损均衡代码和维护成本算总账是划算的。尤其是那些“装上去就不想再拆”的工业设备可靠性比省几块钱重要得多。