ARTICLE DETAIL

资讯详情

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

MRAM替代Flash:PIC18LF45K40工业可靠存储方案

MRAM替代Flash:PIC18LF45K40工业可靠存储方案 1. 这个项目为什么把存储芯片定成 MRAM1.1 工业现场的真实痛点做嵌入式项目特别是工业类的设备存储这块最让人操心的不是能存多大而是“存完能不能靠得住”。我之前做的一套工业采集终端主控是 Microchip 的 PIC18LF45K40需要在现场保存标定参数、运行日志和最近掉电时刻原来的方案一直用 SPI Flash结果客户经常反馈一个问题设备重启以后偶尔会读出一片空白或者某一段数据变得面目全非。排查到最后才发现不是代码逻辑错而是 Flash 的擦写寿命和掉电时序把数据坑了。后来我把非易失存储换成了 Everspin 的 MR25H40CDF也就是一颗 4Mbit 的 SPI 接口 MRAM。配合 PIC18LF45K40 重新做了驱动和存储管理这个“存储和读取数据”的项目才算真正稳下来。这篇文章不写理论堆砌直接从选型理由、硬件连接、SPI 驱动实现、现场踩坑四个层面复盘整个过程。如果你也在用单片机做工业数据记录、参数保存、掉电保护这类需求这套方案可以直接参考。1.2 MR25H40CDF 相比 Flash 和 EEPROM 差在哪先说结论MRAM 本质上是一种非易失性的 RAM读写行为和 SRAM 类似但断电之后数据不丢。它的底层是磁阻效应不是电荷存储所以不需要像 Flash 那样靠隧穿注入电荷来写数据也不存在“先擦除、再写入”这种操作。在工业嵌入式场景里这个差异带来的好处非常明显对比项MR25H40CDF MRAMSPI NOR FlashSPI EEPROM写入前是否需要擦除不需要需要按块或扇区擦除不需要按字节/任意地址直接写支持不支持页内写较大才行支持写次数寿命典型约 1e11 ~ 1e12 次约 1e5 ~ 1e6 次约 1e6 次写入后等待时间几乎无无“写周期忙”需要等待页编程或忙轮询有字节写周期时间掉电数据保持按规格 20 年以上一般 10~20 年一般 40 年以上从这里能看出来MRAM 更适合“频繁写入、写入量不大、但可靠性要求高”的场景。比如这个项目里要保存的标定参数一个参数块可能只有几十字节但每次设备校准都要更新一天几十次一年就是近万次写操作。用 Flash 虽然也能做但要考虑磨损均衡、掉电写坏块、恢复时间这些麻烦事用 MRAM 就简单得多代码里甚至可以把 MRAM 当作掉电不丢的 SRAM 来用写完立即读回也不存在时延。MR25H40CDF 这颗具体芯片是 MR25H40 系列中的型号容量 4Mbit也就是 512K 字节SPI 接口工作电压常见为 3.3V。你完全可以把它当作一颗“容量更大、不用擦除、寿命更长”的 EEPROM 来规划。1.3 为什么是 PIC18LF45K40主控选 PIC18LF45K40 有几个原因。工业项目里不一定非要上 Cortex-M很多设备主控只要跑跑逻辑、读读传感器、跟扩展板通信就够了。这颗 MCU 是 8 位单片机最高可以跑到 64MHz 内部时钟内置两个 MSSP 模块可以同时供 SPI 和 I2C 使用还有内置的掉电复位和低功耗特性整体资源对这个项目来说非常合适。更重要的是Microchip 的 MPLAB X 开发环境配合 MCC 图形化配置工具生成外设初始化代码非常快。对嵌入式开发来说这种开发方式可以把注意力放在存储驱动和业务逻辑上而不是纠结寄存器初始化顺序。如果你比较熟悉寄存器编程也没问题后面我会把关键寄存器的思路一起写出来。2. 硬件连接与信号处理别把 MRAM 当普通 Flash 用2.1 MR25H40CDF 引脚功能与接线MR25H40CDF 是标准的 8 引脚 SPI NOR 兼容封装引脚和很多 SPI Flash 完全一致这是个好消息说明硬件上可以直接兼容旧板位。但要注意引脚兼容不代表驱动逻辑兼容MRAM 的写使能、状态寄存器语义还是和 Flash 有细微差别。一颗典型的 MR25H40CDF 引脚功能如下引脚名功能单片机侧连接注意事项CS#片选低有效接任意 GPIO或 SPI 从机选择引脚默认必须保持高电平SCKSPI 时钟接 MSSP2_SCK时钟极性可配SI串行输入主机发送给从机接 MSSP2_SDO对应 MOSISO串行输出从机输出给主机接 MSSP2_SDI对应 MISOWP#写保护低有效接 3.3V平时上拉不要在传输中悬空HOLD#保持输入低有效接 3.3V 或通过电阻上拉悬空是很大的隐患VDD电源正极3.3V注意电源纹波VSS地GND尽可能单点接地很多工程师画板子时容易犯一个毛病觉得 WP# 和 HOLD# 不用就直接空着。这在普通 SPI Flash 上可能侥幸能用但在工业环境里问题很大。特别是 HOLD#一旦有噪声把它拉低芯片就会暂停当前传输单片机还在发时钟结果数据错位且极难排查。我在这个项目里把 WP# 和 HOLD# 都直接接 3.3V然后在 PCB 上靠近引脚处加了一个 4.7kΩ 电阻到 VDD确保上电瞬间不会出现低电平窗口。2.2 供电、去耦与 PCB 走线要求MR25H40CDF 工作在 3.3V 逻辑环境PIC18LF45K40 也支持 3.3V 供电所以这俩芯片可以在同一电源轨上工作。有些老的 8 位单片机习惯 5V 供电如果拿 5V 单片机的 IO 直接连 MRAM 的 3.3V 引脚超压风险是实打实的轻则读写不稳定重则长期可靠性下降。所以尽量保证同一套 3.3V 电源轨如果必须用 5V 主控就得加电平转换芯片不要用电阻分压凑合。去耦电容我是这么布置的每个芯片的 VDD 引脚就近放一个 0.1μF 陶瓷电容然后在 PCB 电源入口再放一个 10μF 钽电容保证掉电瞬间能撑住短暂的能量缺口。SPI 时钟线如果走线比较长比如从主板到子板之间通过排线连接建议在 SCK、SI 和 CS# 上各串一个 22Ω 或 33Ω 的电阻能有效抑制振铃和尖峰。SCK 和 CS# 等信号线尽量不要和继电器控制线、电机驱动线平行走线这个项目最早版就是因为盖子线挨着 220V 驱动线出现随机读错重新布线后才彻底消除。2.3 上下电时序先供电再拉片选工业设备经常会遇到突然断电MRAM 虽然不依赖电荷存储写入可靠性更高但如果你在 VDD 已经掉出规格范围时还去拉低 CS# 发命令结果仍然可能不理想。就像你写文件的时候正好赶上电脑蓝屏操作系统再强也可能产生半截文件MRAM 只是把“磁盘损坏”的概率降到很低不意味着你可以完全不在乎时序。我给这个项目加了一个非常简单可靠的策略使用 PIC18LF45K40 的 BORBrown-Out Reset检测把掉电阈值设为 2.8V 左右一旦检测到主电源跌过阈值系统立刻进入保存流程禁止接收新的上位机写入命令然后赶在电压进一步跌落前把关键运行参数写到 MRAM。同时在 MRAM 的 CS# 控制上保证默认上电时 GPIO 输出高电平避免 MCU 引脚未初始化期间 MOSI 上随机电平组合被 MRAM 当作有效命令。3. SPI 协议拆解命令、状态寄存器与读写时序3.1 MR25H40 的命令集速览MR25H40CDF 的命令集兼容经典的 SPI 串行存储规范记住下面几个命令就够用了命令操作码功能说明WREN0x06写使能必须在写命令之前发送WRDI0x04写禁止不常用但可以用来保护数据RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据从指定地址开始连续读WRITE0x02写数据从指定地址开始写入MR25H40CDF 的 READ 和 WRITE 地址都是三个字节也就是 24 位地址。芯片容量只有 512K 字节实际有效地址位是 A16~A0高 7 位地址字节必须写 0否则芯片可能会忽略或导致地址溢出回绕。我刚开始就吃过这个亏直接传了 MCU 的 32 位地址结果每次读写都错位逻辑分析仪一抓波形才发现是高字节地址出现了非零值。3.2 SPI 模式MODE0 和 MODE3 千万别混用这颗 MRAM 支持 SPI Mode 0 和 Mode 3也就是 CPOL0、CPHA0或者 CPOL1、CPHA1。如果只看数据手册你会觉得两种都能用但在实际工程里请务必在硬件设计阶段就定死用哪一种并在驱动代码里写清楚。我用的是 Mode 0。在 Mode 0 下SCK 空闲时为低电平数据在上升沿被采样下降沿输出变化。PIC18LF45K40 的 MSSP 外设可以配置时钟极性和边沿你只需要确保 SPI 配置界面选的是“Standard SPI Mode”并且时钟空闲电平为低。如果把 Mode 0 和 Mode 3 混用最常见的表现是读写都“看起来正常”但读回来的数据偶尔错一两个字节尤其是高速时钟下尤其明显。因为芯片采样数据的时间点正好贴着信号翻转沿板子稍有噪声就触发亚稳态。我建议调试阶段用逻辑分析仪采样一次 SCK、SI、SO、CS# 四路信号确认数据翻转和采样沿的关系这一步能帮你节约很多时间。3.3 写使能、写入和读出的完整命令序列MRAM 有个很重要的特性写入之前必须先发 WREN 命令把状态寄存器里的 WEL 位置 1。这是 SPI 存储芯片的常规设计防止系统跑飞时乱发数据把存储内容冲掉。每一条 WRITE 命令都必须配一个 WREN。而且 WREN 要作为一个独立的 CS# 片选周期完成不能和后续 WRITE 命令连在一起发。典型写入流程// 第一步写使能CS拉低 - 发送0x06 - CS拉高 MRAM_CS_LOW(); SPI2_ByteExchange(MRAM_CMD_WREN); // 0x06 MRAM_CS_HIGH(); // 第二步发送写命令和地址 MRAM_CS_LOW(); SPI2_ByteExchange(MRAM_CMD_WRITE); // 0x02 SPI2_ByteExchange((uint8_t)(addr 16) 0x07); SPI2_ByteExchange((uint8_t)(addr 8)); SPI2_ByteExchange((uint8_t)addr); // 第三步连续写入数据 for (uint16_t i 0; i len; i) { SPI2_ByteExchange(data[i]); } MRAM_CS_HIGH();这段代码看起来简单真正要注意的是第二步里字节数据的顺序。MR25H40 的地址传输顺序是高字节在前、中间字节、低字节在后。如果你的单片机是 8 位机直接拆分 32 位地址变量没问题如果用的是 32 位单片机第一字节务必(uint8_t)(addr 16)不要直接发最低字节。读取流程类似MRAM_CS_LOW(); SPI2_ByteExchange(MRAM_CMD_READ); // 0x03 SPI2_ByteExchange((uint8_t)(addr 16) 0x07); SPI2_ByteExchange((uint8_t)(addr 8)); SPI2_ByteExchange((uint8_t)addr); for (uint16_t i 0; i len; i) { data[i] SPI2_ByteExchange(0x00); } MRAM_CS_HIGH();和 Flash 最大的不同是MRAM 的 READ 可以跨边界连续读不会因为过了 256 字节“页”边界而停顿。因为 MRAM 本身不是页式存储地址可以线性连续递进。这也让数据组织变得很自由你可以把它当作一颗字节寻址的非易失 RAM 来用不需要考虑擦除块大小。4. PIC18LF45K40 侧的实现实录MCC 配置与核心代码4.1 在 MPLAB X 中使用 MCC 配置 SPI 外设如果你也使用 PIC18LF45K40建议直接用 MCC 生成 SPI 驱动省时省力。打开 MPLAB X创建工程后进入 MCC 界面在 Device Resources 里找到 MSSP2把它配置成 SPI Master 模式。时钟选择定时器或 Fosc 分频都可以我实际分配的是 8MHz SCK对于这颗 MRAM 来说完全够用。MCC 里需要注意两个选项时钟极性选择“Idle Low”数据输出边沿选择“Transmit on Falling Edge, Sample on Rising Edge”也就是 Mode 0 的标准配置。CS# 引脚不用交给 MSSP 的自动片选我习惯用普通 GPIO 自己控制这样时序更明确方便将来扩展其他 SPI 设备。配置完成后MCC 会自动生成SPI2_Open、SPI2_ByteExchange、SPI2_Close这些函数。4.2 把读写封装成可复用的驱动层直接调 SPI 底层函数不够友好我通常会在上面再包一层 MRAM 驱动把命令封装好。实际项目里我的头文件里定义了#define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRDI 0x04 #define MRAM_CMD_RDSR 0x05 #define MRAM_CMD_WRSR 0x01 #define MRAM_CMD_READ 0x03 #define MRAM_CMD_WRITE 0x02然后封装三个函数写使能、写数据、读数据。写数据函数内部会自动先发 WREN避免调用者忘记写使能。这个封装看起来多了一个步骤但对工程化开发是必要的。我带的几个新工程师就经常在测试代码里直接调 SPI 发送忘了写使能得到全 0xFF 后一脸疑惑。4.3 参数存储的数据结构与校验工业设备存储数据最忌讳的就是裸存裸读。除了把字节写进去还要设计帧结构这样即使出现异常程序也能识别并丢弃坏数据。我在 MRAM 里为每一个参数块设计了一套简单的帧布局偏移内容长度0魔数 MAGIC固定 0xA51 字节1数据版本号1 字节2~5参数块长度4 字节6~N5参数数据体N 字节N6~N7CRC16 校验值2 字节读取时先读前 6 个字节检查魔数是否为 0xA5版本号是否匹配长度是否超过预设上限。接着读整个数据体计算一遍 CRC16和存储的校验值比较。只要魔数或 CRC 不过就判定这一块数据异常需要走备份恢复逻辑。这样可以有效避免把全 0xFF 或半截数据当合法参数使用。CRC16 实现对单片机来说开销很小我在 8MHz 主频下计算 1KB 数据大概不到 2ms完全可接受。用查表法的话代码量稍微大一点但速度更快。如果你觉得 CRC16 还不够可以升级成 CRC32不过考虑到这个场景是参数块CRC16 已经足够了。4.4 实测性能与通信过程记录PIC18LF45K40 在 64MHz 内部时钟下工作我配置 SPI2 的 SCK 为 8MHz。实际测试写入 512 字节数据从开始发送写命令到最后一个字节结束时间小于 1ms而且不需要额外等待。读取同样大小的数据时间也在 1ms 量级。对比原来板卡上的 SPI Flash擦除一个 4KB 扇区需要几十毫秒写入一页还要再等几毫秒这个差距在频繁记录数据的场景里非常明显。实测下来我把 PLC 系统的运行日志存储频率从“每分钟写一条”提高到“每秒写一条”MRAM 毫无压力。而且长时间写入测试跑到几十万次再读回数据都完全正确。这在原来的 Flash 方案上是不敢想象的因为普通 SPI Flash 写到十万次左右坏块率和数据错误率就会开始上升。5. 实际调试中最容易踩的坑与排查方法5.1 典型问题与排查对照表这个项目前后调了小两个月中间遇到过不少问题。我把最有代表性的几个整理成表格供大家对照参考现象可能原因排查方法写进去的数据读回来全是 0xFF没发 WREN或 WP# 引脚拉低读 RDSR 看 WEL 是否为 1MISO 一直无数据输出SI/SO 接反或 CS# 没拉低用逻辑分析仪抓 CS# 和 SO数据整体错位 1~2 个字节地址字节顺序不对检查发送地址的移位顺序偶发读错一两个字节SPI Mode 0/3 配置和波形不一致检查 SCK 空闲电平与采样沿设备重启后数据丢失供电跌落前 VDD 已低于规格加 BOR 保护和掉电检测电容和另一路 SPI 设备冲突CS# 控制不及时总线被占用保证任一片选拉高后再切设备写入后立刻读回偶尔失败SCK 信号振铃或串扰在 SCK/SI 路径串 22Ω 电阻5.2 调试心得先从状态寄存器入手我先分享一下排查“写不进去”这个问题的思路。遇到写入后读出全 0xFF不要急着改代码先把状态寄存器读出来。通过 RDSR 命令读取状态寄存器的 bit0就是 WEL 位。发送 WREN 之后如果 WEL 变成 1说明芯片正常进入写使能状态如果它一直是 0说明 CS# 片选时序、SI 波形或 WP# 引脚有问题。我甚至遇到过因为 GPIO 配置还没生效CS# 引脚上拉被内部分压到中间电平导致芯片始终不能识别命令最后换了一个真正带内部上拉的引脚才解决。调试 SPI 这类接口逻辑分析仪是必须的。我最常用的是采样率足够高的逻辑分析仪同时观察 4 路信号。特别提醒一下MRAM 的 SO 引脚在 CS# 为高时是“高阻或弱上拉”状态如果逻辑分析仪看到 MISO 线上悬空有噪声不要紧张正常现象。只有 CS# 拉低并发出有效命令SO 才会输出数据。5.3 掉电场景下的数据保护策略工业设备最容易出数据问题的时间点不是正常工作时而是断电那一瞬间。比如设备正在往 MRAM 写数据AC 电源突然断开DC/DC 输出电压跌落MCU 供电电压逐渐下降这时如果还在执行写操作数据可能写到一半。PIC18LF45K40 的 BOR 可以帮上忙但硬件设计上最好保留足够的储能电容。我一般会在主电源输入端放一个较大的电解电容容量根据整板功耗算尽量保证 BOR 触发到程序保存关键状态之间还有几毫秒的余量。在软件里我使用优先级最高的外部中断来检测掉电信号一旦进入掉电处理函数立刻关闭全局中断里的正常数据流把最核心的状态字和故障码写进 MRAM然后进入低功耗死循环。这里有电学机理MRAM 写入本身非常快不多的时间窗口足够写几十字节。6. 针对工业场景的存储与读取设计补充6.1 参数存储的双副本滚动机制即使 MRAM 可靠性已经很高我依然建议对关键参数做双副本存储。原因很简单任何一颗芯片在极端条件下都有可能出错双副本是在软件层面做最后一道防线。我在 MRAM 里划分两个区域称为 A 区和 B 区两个区域里保存同一份参数。每个区域的开头都放一个 active 标记通常用一个布尔值加 CRC 来标识。写入流程是读取当前 active 标记确定上一次完整写入的是 A 区还是 B 区。选择另一个区域写入新参数。写入完成后读回校验 CRC。校验通过后更新 active 标记指向新写入的区域。这样做的好处是如果写第二个副本的过程中掉电第一个副本还是完整的。下次上电时读取 active 标记程序会发现标记没有更新于是自动回退使用完整副本。整个过程对用户透明不会出现参数完全丢失的情况。有的嵌入式工程师可能会问MRAM 寿命这么长为什么还要做双副本因为工业设备还要应对“电压超范围”“EMC 干扰”等外部因素双副本不是防芯片老化而是防系统异常。这个习惯我建议保留下来成本很低收益却很直接。6.2 日志记录的高效组织环形缓冲区运行日志是工业设备里比较典型的功能。以前用 Flash 记录日志写满以后要擦除整块扇区处理起来很麻烦。Flash 的循环日志往往要维护“当前写位置”“擦除位置”“干净块数量”一旦掉电这个索引表非常容易损坏。而 MRAM 因为不需要擦除实现一个简单的环形缓冲区非常自然。我在 MRAM 的日志区开了一个固定大小的环形缓冲每条日志记录由一个固定头部和 CRC 组成记录字段长度说明日志序号2 字节递增用来识别顺序时间戳4 字节设备内部 UTC 时间日志数据N 字节业务数据CRC162 字节整条记录校验写入时直接在当前写指针处顺序写写完更新写指针当指针到达缓冲末尾就绕回开头覆盖。读取时从最后的完整记录开始反向扫描遇到 CRC 错误就跳过或者标记异常。这个方案在 Flash 上也能实现但 MRAM 省掉了擦除等待日志写入频率可以大幅度提高。6.3 给后来者的几条实用建议如果你准备在新项目里照抄这套方案我再补几条从项目里攒下来的经验第一固件里给存储模块加一个“版本号”和“容量宏”不要只写死地址。MRAM 虽然 512K 字节很大但 8 位单片机的地址计算还是要小心建议把所有地址用宏或常量表管理。第二上位机写入参数和现场操作人员手动修改参数时尽量都走同一套驱动接口不要前面已经做了 CRC 封装后面又绕过封装直接从底层写一个字节这种“后门”是事故高发区。第三批量生产时每台设备出厂后要在产线上做一次写入回读测试把序列号、生产日期、校准系数等一次性写入 MRAM然后启动时读取并与上位机比对。这套自检流程投入不大但能拦截大部分焊连、虚焊问题。我见过一些项目在实验室跑得好好的一上产线就暴露问题大部分都是引脚焊接短路或 SPI 信号干扰导致。所以硬件测试阶段不要只看功能还要用示波器看一遍 SPI 波形的边沿质量和串扰情况。这个项目做完以后我最深的体会是嵌入式系统里换一颗存储芯片不仅仅是硬件替换更是对整个数据管理逻辑的重构。MRAM 的“无擦除、高寿命、快写入”特性让很多曾经需要复杂疲劳算法才能实现的事情变得非常简单。如果你手头的项目也在为 Flash 寿命或掉电保存发愁认真考虑一次 MRAM 方案可能会打开一条很顺畅的新路。
返回列表