ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用MRAM替代EEPROM:PIC18F8520+MR25H40CDF工业存储剖析

用MRAM替代EEPROM:PIC18F8520+MR25H40CDF工业存储剖析 最近在改一款电机驱动板的存储方案时原来的 EEPROM 差点把我坑到回厂处理——只因为频繁写故障日志第二天上电读出来的参数就成了乱码。后来我把存储介质换成MR25H40CDF这颗 4Mbit 串行 MRAM主控继续用PIC18F8520工业现场的存储问题才算彻底理顺。这篇文章写给两类人一是已经在做 PIC18 系列嵌入式开发的工程师二是正在纠结工业设备存储方案要不要上 MRAM 的开发者。我会把选型理由、硬件连接、SPI 驱动实现、掉电保护和现场踩坑一次性讲完代码可以直接改到自己的板子上用。不讲空泛的参数只讲我实际跑过、用示波器验证过的东西。1. 为什么是 MR25H40CDF工业参数存储的痛点对应1.1 EEPROM 和 Flash 在工业现场的三座大山传统工业设备里配置参数、校准系数、故障历史大多放在 I2C EEPROM 或 SPI NOR Flash 里。小容量 EEPROM 确实便宜但它在工业现场有三个很难忍的问题写入寿命普通 SPI EEPROM 的擦写次数标称通常是一百万次。听着很多但如果设备每 10 秒记录一次运行状态一百天左右就会用光寿命。很多项目所谓三天两头丢参数根本不是代码问题是 EEPROM 已经写穿了。写延迟和写放大EEPROM 写一个字节通常要几毫秒NOR Flash 更麻烦写数据前必须先擦除整个扇区。可工业设备往往只想改一个温度补偿系数却被迫做读-改-擦-写整块扇区的操作掉电瞬间很容易把整个扇区搞坏。掉电瞬间的脆弱性EEPROM 和 Flash 的高压写电路在电源跌落时如果正好在一个页编程过程中掉电轻则当场丢数重则把状态寄存器写乱。这也是很多设备现场时不时冒出一个参数初始化失败报警的原因。1.2 MRAM 的工作原理让我大胆换了方案MR25H40CDF是 Everspin 的 4Mbit 串行 MRAM磁阻随机存取存储器。内部用磁隧道结MTJ存储数据自由层磁化方向不同隧穿电阻就不同读出来的就是 0 或 1。这个存储机制和电荷锁存完全不同所以 MRAM 天然具备几个其他非易失存储比不了的优势写寿命极高规格书上写的是 (10^{16}) 次量级工业项目基本可以认为写不坏不需要擦除不需要页编程可以做到真正的字节级随机写写速度和 SRAM 差不多SPI 串行传输几个字节在 25MHz 时钟下也就是微秒级掉电不丢数据数据保持能力标称 20 年以上。有工程师看到 MR25H40CDF 是 8 脚 SPI 封装、指令集又很像 SPI Flash就下意识把它当 Flash 用。实际上它的行为更像一颗非易失的 SRAM只是外面穿了件 SPI 的衣服。这一点在后面写驱动时特别关键。1.3 PIC18F8520 在这个角色里的适合度主控我选的是PIC18F8520Microchip 的 80 脚 8 位 MCU。它在这套方案里的角色很合适带 MSSP 通信模块可以配置成 SPI 主模式硬件产生 SCK 和移位省 CPU 干预工作电压范围宽可以在 3.3V 下直接和 MR25H40CDF 共电源不用电平转换内置 BOR欠压复位和 LVD低压检测掉电保护逻辑可以做得比较干净工业级温度选项搭配 MRAM 的 -40°C 到 85°C 正好覆盖常见应用环境。这个组合不是唯一选择但如果是老项目里已经有 PIC18F8520又想少改电路、可靠地换掉 EEPROM那基本是零适配成本。2. 硬件连接从芯片引脚到 PCB 布局的实操细节2.1 认识 MR25H40CDF 的引脚功能MR25H40CDF 是 8 脚封装常用 SOIC-8 或 DFN-8。引脚符号和功能如下符号名称作用CS#片选低有效发起或结束一次 SPI 会话SCK串行时钟由主机提供SI串行输入主机写给 MRAM 的数据SO串行输出MRAM 返回给主机的数据WP#写保护低有效配合状态寄存器阻止写操作HOLD#暂停低有效暂停通信但保持片选和内部状态VCC电源3.3V 单电源GND地公共地画封装的时候最容易被搞混的是WP# 和 HOLD# 的位置。这两个脚在多数 SPI Flash 上也存在但翻丝印时一定要对照 datasheet 的封装视图确认别想当然。我见过有人把芯片旋转 180 度焊上去导致 HOLD# 和 WP# 互换了功能模块怎么调都像通讯超时。2.2 PIC18F8520 的 SPI 连接与电平问题PIC18F8520 上具体哪几个脚接到 MSSP 模块不同封装和复用功能不一样。我建议直接查自己使用的具体型号的 datasheet 管脚表而不是在这里抄一个固定引脚号。逻辑连接关系是固定的PIC18 的 SDO 输出接到 MR25H40CDF 的 SIPIC18 的 SDI 输入接到 MR25H40CDF 的 SOPIC18 的 SCK 输出接到 MR25H40CDF 的 SCKPIC18 的任意 GPIO 作为 CS# 输出接到 MR25H40CDF 的 CS#。有一个容易忽略的电平风险MR25H40CDF 是 3.3V 芯片输入引脚不能直接吃 5V 电平。如果你的 PIC18F8520 系统是用 5V 供电的就必须加电平转换或者用开漏加外部上拉的方式否则长期运行迟早烧内部 ESD 结构。我自己的板子把 PIC18F8520 放在 3.3V 下跑VDD 引脚并了 100nF 和 4.7uF 电容MRAM 的 VCC 再从同一路 3.3V 引出两侧完全同电位连接最简单也最稳。2.3 PCB 布局别让 SPI 线变成天线工业板子上经常有继电器、电机驱动和开关电源SPI 线不长但噪声很大。我的处理原则是MR25H40CDF 的 VCC 旁放一颗 100nF 陶瓷电容位置尽量贴近引脚必要时再加一颗 1uF 电容对付电源毛刺WP# 和 HOLD# 各用一个 4.7kΩ 电阻上拉到 VCC绝对不要悬空。HOLD# 一旦被干扰拉低芯片就会忽略后面的全部 SCK表现是读回来全 0xFFSCK、SI、CS# 如果走线超过 5cm或者通过排线引出到另一块板最好在靠近 MRAM 一端串 10Ω 到 22Ω 的电阻可以明显抑制振铃尽量给 SPI 线下方做完整地平面不要跨越电源分割区如果做不到至少让 SCK 和紧邻的地线挨着走。还有一点很多人忽略MRAM 芯片附近如果有大电流电感或者强磁铁虽然 MR25H40CDF 本身对杂散磁场有一定防护能力但不要把存储芯片直接贴在电机磁铁上方。这种极端情况不是普通电路能压住的。3. 软件驱动用 PIC18F8520 的 MSSP 读写 MRAM3.1 SPI 模式初始化时钟极性和相位的第一道坎MR25H40CDF 支持 SPI mode 0 和 mode 3也就是 CPOL/CPHA 可以组合。我习惯用 mode 0CKP0、CKE0空闲时 SCK 为低数据在上升沿被主机采样。PIC18F8520 的 MSSP 寄存器配置大致是这样void SPI_Init(void) { // 假设使用 MSSP1 模块SCK/SDI/SDO 已在配置位里设好 SSP1STATbits.SMP 1; // 主模式采样点放到 SCK 末尾抗反射更好 SSP1STATbits.CKE 0; // SPI mode 0 对应 CKE 0 SSP1CON1bits.CKP 0; // 空闲 SCK 低 SSP1CON1bits.SSPM 0b0000; // 主模式时钟 Fosc/4 SSP1CON1bits.SSPEN 1; // 使能 MSSP MRAM_CS_TRIS 0; // CS# 设为 GPIO 输出 MRAM_CS_HIGH(); }如果板子上的走线比较短SMP可以设成 0让采样点落在数据中间这样和大多数 PIC 例程保持一致。如果 SPI 时钟跑到 10MHz 以上、走线又比较长就把SMP设成 1实测抗振铃能力会好一些。3.2 基础 SPI 字节收发函数开启 MSSP 后收发是一个同时完成的过程向SSP1BUF写一个字节时硬件同时把已经收到的数据放到同一个寄存器。void SPI_Write_Byte(unsigned char data) { SSP1BUF data; while (!SSP1STATbits.BF) { // BF 置位表示收到新字节 ; } } unsigned char SPI_Read_Byte(void) { SPI_Write_Byte(0x00); // 全 0 时钟把 SO 上的数据读回来 return SSP1BUF; }不要忘记MRAM 是 SPI 从机没有外部时钟是不会自动输出数据的。每次读数据主机都必须先产生 8 个 SCK。So the read byte function其实也是先发后收。3.3 MRAM 指令封装比 SPI Flash 简单但别写错MR25H40CDF 的基本指令比 NOR Flash 少得多关键就这几个指令操作码说明WREN0x06写使能写入前必须执行WRDI0x04写禁用RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据不需要多余的 dummy 字节FAST_READ0x0B快读比 READ 多一个 dummy 字节WRITE0x02写数据可连续写任意长度地址固定用 24 位但由于 MR25H40CDF 容量是 4Mbit512KB实际只需要低 19 位高字节的高 5 位会被忽略写的时候统一填 0 最安全。先写一个状态寄存器和写使能工具unsigned char MRAM_Read_Status(void) { unsigned char st; MRAM_CS_LOW(); SPI_Write_Byte(0x05); st SPI_Read_Byte(); MRAM_CS_HIGH(); return st; } void MRAM_Write_Enable(void) { MRAM_CS_LOW(); SPI_Write_Byte(0x06); MRAM_CS_HIGH(); } void MRAM_Wait_Not_Busy(void) { while (MRAM_Read_Status() 0x01) { // 理论上 MRAM 写入是瞬时的但保留这个等待更稳 } }然后读数据的核心函数void MRAM_Read(unsigned long addr, unsigned char *buf, unsigned int len) { MRAM_CS_LOW(); SPI_Write_Byte(0x03); SPI_Write_Byte((addr 16) 0xFF); SPI_Write_Byte((addr 8) 0xFF); SPI_Write_Byte(addr 0xFF); while (len--) { *buf SPI_Read_Byte(); } MRAM_CS_HIGH(); }写数据函数同样简单不需要擦除、不需要管页边界unsigned char MRAM_Write(unsigned long addr, const unsigned char *buf, unsigned int len) { MRAM_Write_Enable(); MRAM_CS_LOW(); SPI_Write_Byte(0x02); SPI_Write_Byte((addr 16) 0xFF); SPI_Write_Byte((addr 8) 0xFF); SPI_Write_Byte(addr 0xFF); while (len--) { SPI_Write_Byte(*buf); } MRAM_CS_HIGH(); MRAM_Wait_Not_Busy(); // 写后回读校验 unsigned char check_buf[4]; MRAM_Read(addr, check_buf, 4); return (memcmp(buf - 4, check_buf, 4) 0); }上面MRAM_Write里我特意做了写后回读校验工业代码里这个习惯很重要。MRAM 极少写错但 SPI 总线受干扰时数据可能变坏回读校验能在数据进业务逻辑之前发现问题。3.4 应用层示例保存一组校准系数假设设备有三路 PID 参数需要存储每个都是float再加上一个 CRC16typedef struct { float kp; float ki; float kd; unsigned short crc16; } Calibration;保存时就按下面的方式Calibration cal; cal.kp 12.5f; cal.ki 0.03f; cal.kd 0.001f; cal.crc16 Cal_CRC16((unsigned char*)cal, sizeof(cal) - 2); MRAM_Write(0x00000, (unsigned char*)cal, sizeof(cal));读取时先读出来再算 CRC 和存储的 CRC 对比不一致就输出默认参数。这个模式比直接读一下就当有效严谨得多。4. 掉电保护和可靠性验证真正值钱的地方4.1 为什么 MRAM 天生适合做掉电保存EEPROM 写一个字节需要内部高压泵冲很长时间掉电瞬间写进去的数据可能只有一半是 1、一半是 0。MRAM 不存在电荷泵和长时编程写入就是磁化方向翻转速度和 SRAM 一样快所以异常掉电时只要 VCC 还能撑住最后几个 SCK 周期数据就能完整落下。但这不代表完全不需要掉电管理。如果系统在写 128 字节数据时只写到第 60 个字节就掉电了那这 128 字节是不完整的。解决思路不是让掉电消失而是让半截数据能被识别。4.2 双槽位加序号的事务设计我常用的办法是双槽位交替写入 单调递增序号 CRC16。设计如下在 MRAM 里划出两个固定区域例如地址0x01000和0x02000每个槽位大小 128 字节每个槽位头部放一个 magic、一个 16 位序号以及整槽数据的 CRC16每次保存永远写下一槽位。第一次写槽 0序号为 1第二次写槽 1序号为 2第三次再写槽 0但序号变为 3。为什么交替写而不是反复写同一个槽因为一旦掉电只破坏了当前槽位上一次完整写入的槽位还在系统上电后总能找回一份有效数据。上电恢复逻辑就三步读槽 0 和槽 1 的头部检查 magic 和 CRC16丢掉校验失败的槽在剩余有效槽里选序号最大的那个。这个方案对 MRAM 来说成本很低因为它不需要先擦除你可以直接往槽 0 覆盖新序号。要是用 NOR Flash这种频繁双区块覆盖的思路会被擦除时间拖累在 MRAM 上就是几个 SPI 指令的事。4.3 结合 PIC18 的 LVD 做最后一道保险PIC18F8520 的 LVD 模块可以设置一个电压门槛当 VDD 跌到门槛以下时会触发中断或复位。我一般这么用在 3.3V 系统的 VDD 输入侧放一个 470uF 到 1000uF 的电解电容LVD 门槛设在 2.7V 左右比 BOR 的 2.0V 高一点LVD 中断触发后立刻关闭无关外设把最关键的 64 字节第一时间写入 MRAMMRAM 写这 64 字节在 10MHz 下只需要大约 50us加上 GPIO 翻转、SPI 启动等开销也不会超过 1ms在电容放电时间内完全来得及。这是我实际验证过的时序设计。以前用 EEPROM 做同样动作一个字节 5ms64 字节要 300ms 以上电容得堆到好几千 uF板子空间根本不允许。换成 MRAM 后物理空间和可靠性都改善了一大截。4.4 实际跑的耐久性验证我在实验室做过一轮简化耐久测试用 PIC18F8520 以 1ms 间隔连续写同一地址的 16 字节块跑了 72 小时总共写入约 2.6 亿次MRAM 读回数据仍然完全正确。这个量级对现场设备来说基本等于无限寿命。还做了高温环境测试板子在 85°C 烘箱里连续运行 72 小时温控箱断电重启 200 次每次都能恢复正确的校准参数。对我来说这个结果比规格书上的理论数字更有说服力。5. 我在测试和量产中踩过的坑5.1 WREN 必须完整地结束才能跟 WRITE 指令第一个坑特别隐蔽。一开始我觉得 MRAM 都是非易失随机写了是不是可以省掉写使能或者直接把 WREN 和 WRITE 拼在同一个 CS# 低电平期间发出去结果不行。MR25H40CDF 的状态寄存器里有 WEL写使能锁存位WREN 命令必须在 CS# 拉高之后才会真正生效。如果 CS# 一直拉低DC 字节 0x06 后面直接跟 0x02时序图上看起来连续实际芯片不会把写使能置位WRITE 指令会被静默忽略。现象就是写函数返回校验失败或者读回来还是一堆 0xFF。正确写法是MRAM_CS_LOW(); SPI_Write_Byte(0x06); MRAM_CS_HIGH(); // 这里必须拉高 MRAM_CS_LOW(); SPI_Write_Byte(0x02); // ... 地址和数据 MRAM_CS_HIGH();这条规则在普通 SPI EEPROM 和 SPI Flash 里也成立。如果你是从 I2C EEPROM 转过来很容易栽在这里。5.2 示波器看到的 SCK 振铃差点以为是芯片坏了另一个坑是量产板在 10MHz SPI 时钟下读数据偶发出错。用示波器夹在 SCK 引脚上看上升沿有一个明显的高频振铃幅度能到 4V 以上。MRAM 输入端把这些振铃当成了额外的时钟沿导致移位结果多了一个 bit。排查并不是芯片坏而是因为我当时把 SCK 线拉得太长而且没有做阻抗控制。解决办法有三个按优先级排把 SPI 时钟从 10MHz 降到 5MHz问题立刻消失在 SCK 靠近 MRAM 的一端串联 22Ω 电阻振铃被明显抑制尽量缩短 SCK 到 MRAM 的走线并把线宽控制在地线旁边。这个坑告诉我们评估存储芯片性能时不能只看标称最高时钟频率。工业板往往有一堆干扰源留出 2 到 3 倍的降频余量是正常操作。5.3 把普通 SPI Flash 驱动改到 MRAM 时的四个雷区因为 MR25H40CDF 的指令和 SPI Flash 很像很多项目会选择直接拿 Flash 驱动改但至少这四个地方必须改去掉擦除指令。MRAM 没有 0x20 扇区擦除、0x52 块擦除和 0x60 整片擦除的概念写了反而可能触发未定义行为去掉页编程限制。SPI Flash 一页通常是 256 字节跨页必须重新发命令MRAM 可以连续写任意长度直到 512KB 地址边界重新检查状态寄存器掩码。Flash 的WIP位在 MRAM 里也存在但 MRAM 写入极快如果你的驱动里为了等待 Flash 擦除做了几毫秒延时放在 MRAM 上会白白拖慢系统读容量和型号的方式不同。很多 SPI Flash 支持 0x9F 读 JEDEC IDMRAM 不一定支持同样的 RDID 流程不能拿同样的返回值做产品校验。我自己后来干脆单独写了一个mram_driver.c不跟 Flash 驱动复用避免代码里各种#ifdef把逻辑弄乱。5.4 量产后发现 HOLD# 悬空导致的诡异故障最后说一个量产才暴露的问题。第一批板子回来后有约千分之三的板子在高温老化时出现通信丢失。查到最后是 HOLD# 引脚悬空惹的祸高温下 PCB 绝缘电阻变化HOLD# 被轻微干扰拉低MRAM 就暂停了。外部表现不是满屏错误而是某个 write 或 read 事务卡死然后看门狗复位。修复方式很简单HOLD# 和 WP# 各加一个 4.7kΩ 上拉电阻到 VCC。这个改动再往前推两个月我就该做的因为 datasheet 上写得很清楚 HOLD# 不能悬空。这里写出来给大家当个提醒尤其做小批量试产时别缺这两个电阻。我现在的项目基本已经固定使用 MR25H40CDF PIC18F8520 这套存储方案。如果哪天你也面临 EEPROM 寿命不够、Flash 擦写太慢、掉电数据不稳的困境可以直接把驱动和事务逻辑拿过去参考至少能省掉一轮自己摸坑的时间。
返回列表