ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用MR25H40CDF MRAM解决工业数据记录高频写入与掉电难题

用MR25H40CDF MRAM解决工业数据记录高频写入与掉电难题 前一阵子接了一个工业数据记录的项目设备要求每 100ms 往非易失存储里追加一条运行状态现场环境还常常直接断电重启。我把常见的 EEPROM、NOR Flash 都试了一遍最后换成了 Everspin 的 MR25H40CDF 这颗 SPI 接口 MRAM搭配手头非常成熟的 PIC18F4455 单片机在嵌入式系统里把“频繁写入、掉电保持、数据可回读”这条链路完整跑通了。这篇文章就是把整个存储和读取方案从头拆到尾为什么选 MRAM、硬件怎么接、MSSP 模块怎么配、代码怎么写以及几个只有实测才能踩到的坑。如果你也在做工业数据记录、电力监测、轨道交通或者其他需要高频写日志的嵌入式项目这篇应该能帮你少走不少弯路。这里先说明一下后面讲的所有内容都围绕“裸机 标准 SPI 主机”这种最常见的工作模式展开不涉及复杂操作系统。PIC18F4455 虽然带了 USB 控制器但我最终走的还是最简单的 SPI 访问路径原因后面会专门讲到。1. 为什么最终选 MR25H40CDF工业写入场景里被忽视的寿命问题很多嵌入式项目选存储器时第一个想到的都是内部 EEPROM 或者外挂 NOR Flash觉得“反正能存数据就行”。但在工业现场真正决定方案能不能用的通常不是容量而是写入寿命和掉电行为。这两个指标一旦不满足其他优势全是白搭。1.1 高频写入时EEPROM 和 Flash 的寿命根本撑不住先算一笔账。假设设备每 100ms 写一次日志一天就是 86.4 万次写入。普通 MCU 内部 EEPROM 的擦写寿命大概在 10 万次左右按这个频率不到三个小时就会把寿命耗尽。NOR Flash 虽然稍好一些但很多型号的擦写寿命也就是 10 万到 100 万次这个量级用在这种场景下同样撑不过几天。这里说的寿命不是“写满为止”的概念而是“达到额定擦写次数后存储单元可能出现位翻转、写入不可靠”的工程概念。你不可能每块板子都去验证“到底写多少次才坏”所以必须留很充足的裕量。MRAM 这类磁阻存储器的寿命要友好得多官方标称循环次数在 10 的 16 次方以上基本可以视为“无限次写入”。这是我做选型时最看重的一点。1.2 掉电时 Flash 的“半写状态”很难处理第二个问题是掉电。Flash 和 EEPROM 的写入过程分两步先擦除、再编程中间有毫秒级的内部操作时间。如果刚好在擦除或者编程过程中掉电可能留下一个既不是旧值也不是新值的中间状态严重的甚至会把整个页搞坏。所以做 Flash 日志系统时通常要设计复杂的掉电保护逻辑记录序号、写双缓冲区、启动时做一致性检查。MRAM 就不一样。它写入是字节级的原子操作不需要擦除也不需要等待内部编程完成写完就是新的掉电不会影响已经完成的部分。从软件角度看它更像是“非易失的 SRAM”而不是需要照顾擦写时序的 Flash。这个特性在做工业日志时非常省心后面第 4 节我会专门讲怎么利用这个特性设计掉电安全的数据结构。1.3 PIC18F4455 在这里的角色那为什么用 PIC18F4455 而不是更强的 ARM 或 RISC-V 芯片其实这个项目里存储部分并不需要太强的算力PIC18F4455 有 24KB 程序 Flash、768 字节 RAM、256 字节内部 EEPROM本身就算一个小型“带非易失存储的单片机”但内部 EEPROM 容量小、寿命低不适合做日志所以需要外扩一块适合频繁写入的存储器。PIC18F4455 的 MSSP 模块支持标准 SPI 主机模式MR25H40CDF 正好就是标准 SPI 接口两个器件接起来非常顺手。另外这颗芯片的资料多、例程多、供货稳定工业项目里用起来比较放心。唯一需要留意的是它的 USB 控制器与 MSSP 有引脚复用问题这个坑我放在第 5 节细说。为了把选型思路说得更清楚我对比了几种常见的非易失存储方案存储类型典型容量写寿命写入特点掉电行为适合场景内部 EEPROMKB 级10 万次级字节写需等待写一半可能损坏参数保存NOR FlashMB 级10 万~100 万次级先擦再写页操作掉电窗口风险大固件、大块数据FRAM几百 KB 级高频写总线速度写入非易失计量、小日志MRAMMB 级近乎无限次总线速度写入无需擦除非易失字节原子写工业日志、黑匣子表格里 FRAM 也是个不错的方案但容量做到 4Mbit 级别的产品很少价格也更贵所以这次最终选了 MR25H40CDF。2. 硬件连接MR25H40CDF 引脚、电平匹配和几个容易翻车的点选型定了之后最直接的工作就是画电路、接引脚。MR25H40CDF 是一颗 8 脚的 SPI 接口 MRAM封装很小引脚定义和普通 SPI Flash 很像但有两个细节要特别留意一个是 HOLD 引脚必须正确处理另一个是电源电平要和 MCU 匹配。2.1 引脚定义与最小接线MR25H40CDF 的 8 个引脚定义是这样的引脚号名称功能说明1/CS片选低有效2HOLD保持拉低后暂停串行通信3/WP写保护低有效4VSS地5SI串行数据输入接 MCU 的 SDO6SCK串行时钟7VDD电源3.3V8SO串行数据输出接 MCU 的 SDI对应到 PIC18F4455我用的 MSSP 引脚是 RC3、RC4、RC5片选没有用 MSSP 自带的 /SS而是直接拿 RA5 当普通 GPIO 手动控制。这样更灵活也方便后续调整。功能MR25H40CDFPIC18F4455SCK6RC3SDOSI5RC5SDISO8RC4/CS1RA5/WP3上拉到 VDDHOLD2上拉到 VDD值得注意的是MR25H40CDF 的片选低电平期间整个指令包括地址和数据的字节流都必须完整发送完才能释放不能像某些 Flash 那样中途释放再继续。读操作时CS 可以在任意一个数据字节之后拉高拉高之后 SO 就会进入高阻态。2.2 电源与电平转换最省事的做法是统一 3.3VMR25H40CDF 是 3.3V 器件工作电压范围大概是 2.7V 到 3.6V不能直接接 5V 的 SPI 信号。PIC18F4455 虽然供电范围比较宽但要注意如果系统统一用 3.3V 供电PIC18F4455 的 48MHz PLL 工作模式对电源电压有更高要求这时候把 MCU 降频到 16MHz 或 20MHz 运行是比较稳妥的。我这次项目为了和上一代产品的 5V 外设兼容MCU 用 5V 供电、MRAM 单独用 3.3V中间加了电平转换。这里提醒一句如果只是简单做产品原型最省事的方式是让 PIC18F4455 也用 3.3V 供电跑 16MHz 或 20MHz 主频这样 SCK、SI、SO、CS 全部是 3.3V 电平直接连接 MRAM 就行少一层电平转换就少一批信号完整性问题。如果是 5V MCU 接 3.3V MRAM千万不要指望用串联电阻降压来跑高速 SPI12MHz 时钟下波形会非常难看老老实实用双向电平转换芯片或者带方向控制的电平转换器件。我这边实际用的电平转换方案是单向数据线单独处理SCK、SI、CS 这些 MCU 输出转 3.3VSO 是 MRAM 输出直接进 MCU 的 3.3V 兼容输入引脚。做之前先查 PIC18F4455 的输入高电平门限确认能识别 3.3V 信号。不同批次、不同温度下要留足裕量。2.3 WP 和 HOLD 引脚的常态处理MR25H40CDF 的 /WP 和 HOLD 两个引脚如果悬空会在实际使用中带来非常隐蔽的偶发故障。HOLD 引脚拉低之后器件会暂停时钟边沿的采样表现为 SPI 读写到一半突然卡住数据永远是错的。我见过有人在 HOLD 上不接任何东西结果现场一有干扰就复现问题排查了很久才发现是这里。正确的做法是/WP 和 HOLD 都通过 1k 到 10k 电阻上拉到 VDD。HOLD 保持高电平才能正常通信/WP 在软件没有配置块保护的情况下也保持高电平这样整颗芯片就是全地址范围可读可写。另外在 VDD 引脚旁边放一颗 100nF 的陶瓷电容靠近芯片引脚放置电源纹波对 MRAM 的影响虽然不像对射频器件那么敏感但工业现场还是按规范做更稳。3. PIC18F4455 的 MSSP 配置和完整读写函数硬件接好了接下来就是把软件打通。PIC18F4455 的 MSSP 模块配置成 SPI 主机模式关键是 SSPCON1 和 SSPSTAT 两个寄存器。这里给出一套我验证过的配置和读写函数可以直接抄到项目里。3.1 MSSP 初始化模式 0、Fosc/4我用的配置是 SPI 模式 0也就是时钟空闲为低电平、数据在第一个边沿采样。MR25H40CDF 同时支持 SPI 模式 0 和模式 3两者的区别在于空闲电平和采样边沿只要主机从机一致就行。我选模式 0主要是 PIC18F44xx 系列例程里模式 0 的配置最不容易出错。初始化代码如下void SPI1_Init(void) { // 引脚方向SCK/R输出SDI为输入SDO为输出 TRISC3 0; // SCK TRISC4 1; // SDI TRISC5 0; // SDO TRISA5 0; // CS作为GPIO输出 // SPI 主机模式Fosc/4空闲低电平 SSPCON1 0x20; // SMP1CKE0对应模式0 SSPSTAT 0x40; SSPCON1bits.SSPEN 1; }这里要注意一个细节为什么选 Fosc/4 而不是更慢的分频因为 MSSP 的 SPI 时钟最高就是 Fosc/4在 PIC18F4455 跑 16MHz 或 20MHz 的情况下SPI 时钟就是 4MHz 或 5MHz对 MR25H40CDF 来说完全在规格范围内。实测稳定时序余量也够。3.2 核心收发函数处理 BF 和 WCOLSPI 全双工模式下往 SSPBUF 写一个字节的同时就会收到一个字节。判断收发的标志是 BFBuffer Full但是有一个注意事项在发送第一个字节之前BF 位可能是残留状态所以标准做法是写数据后等待 BF 置位再读取。uint8_t SPI1_Transfer(uint8_t dat) { SSPBUF dat; // 等待接收完成 while (!SSPSTATbits.BF); // 检查溢出冲突 if (SSPCON1bits.WCOL) { SSPCON1bits.WCOL 0; } return SSPBUF; }这个函数是后面所有 MRAM 操作的基础。读数据时发送一个空字节获取一个返回字节写数据时发送真实数据同时丢弃返回字节。实际测试下来这个函数配合 Fosc/4 的 SPI 时钟连续传输 100 万字节没有出现数据错位。3.3 读流程READ 指令和 24 位地址MR25H40CDF 的读指令是 0x03后面跟 3 字节地址。这颗芯片容量是 4Mbit也就是 512KB实际地址只需要 19 位但指令格式固定用 24 位地址所以地址的高字节只用到低 5 位多出来的高位可以填 0。这是 SPI 接口的定义和 Flash 的 24 位地址格式一致。void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t i; RA5 0; // CS 拉低 SPI1_Transfer(0x03); // READ 指令 SPI1_Transfer((addr 16) 0xFF); SPI1_Transfer((addr 8) 0xFF); SPI1_Transfer(addr 0xFF); for (i 0; i len; i) { buf[i] SPI1_Transfer(0x00); } RA5 1; // CS 拉高 }这里特别想强调一下地址必须是完整的 24 位不能像操作小容量 EEPROM 那样只发两个地址字节。我第一版代码就是图省事只发了 16 位地址结果读出来的全是 0xFF排查了很久才发现是地址字节数少了。3.4 写流程WREN、WRITE 和页边界问题MRAM 的写操作比 Flash 简单但有一个强制前提写之前必须先发 WREN0x06指令把状态寄存器里的 WEL 位置 1。否则写指令会被忽略数据不会真正写进去。WREN 之后再发 WRITE0x02指令后面跟 3 字节地址和要写的数据。void MRAM_WriteEnable(void) { RA5 0; SPI1_Transfer(0x06); // WREN RA5 1; } void MRAM_WriteByte(uint32_t addr, uint8_t dat) { MRAM_WriteEnable(); RA5 0; SPI1_Transfer(0x02); // WRITE SPI1_Transfer((addr 16) 0xFF); SPI1_Transfer((addr 8) 0xFF); SPI1_Transfer(addr 0xFF); SPI1_Transfer(dat); RA5 1; }MR25H40CDF 支持页写一次最多写 256 字节。注意“页”是固定 256 字节对齐的如果写入跨过页边界地址会自动回卷到页开头这会覆盖已经写过的数据。所以连续写大数据时一定要自己做拆分确保每次 WRITE 指令的数据范围不越过 256 字节边界。void MRAM_WritePage(uint32_t addr, const uint8_t *buf, uint16_t len) { uint16_t i; MRAM_WriteEnable(); RA5 0; SPI1_Transfer(0x02); // WRITE SPI1_Transfer((addr 16) 0xFF); SPI1_Transfer((addr 8) 0xFF); SPI1_Transfer(addr 0xFF); for (i 0; i len; i) { SPI1_Transfer(buf[i]); } RA5 1; }使用的时候先计算当前地址到页边界的剩余距离再判断一次能写多少字节。如果剩余不够就先写满这一页下一页再继续。4. 掉电场景下的数据可靠性不只是“装个MRAM”就完事把读写函数调通之后项目其实才完成了一半。工业设备最典型的场景就是不知道什么时候会掉电掉电瞬间系统能不能把最后一条数据完整保存下来才是检验方案是否合格的关键。MRAM 本身不怕掉电但整个系统在掉电瞬间的行为仍然需要认真设计。4.1 有趣的反差MRAM 不怕掉电系统怕MR25H40CDF 写入是即时生效的这点对掉电场景非常友好但这不代表随便怎么写都行。如果电源跌落时 MCU 已经处于不稳定状态GPIO 输出电平不确定SCK、SI、CS 上可能出现毛刺这就会让 MRAM 收到伪指令写入一些垃圾数据。所以掉电安全的核心不只是“芯片能保持数据”还包括“芯片不会在掉电瞬间被写入错误数据”。我这边用了几层保护。第一层是 PIC18F4455 内部的上电复位和欠压复位配置合适的 BOR 电压门限让 MCU 在电源低到一定程度之前先进入复位状态。第二层是在复位中断服务程序里把 CS 引脚立刻拉高释放 MRAM 的片选这样总线上再有什么信号毛刺MRAM 也不会响应。第三层是硬件上给 MRAM 的电源加足够的储能电容保证 MRAM 的 VDD 比 MCU 的 VDD 晚跌落一小段时间这样即使 MCU 在掉电时发出最后一笔合法写入MRAM 也能有足够的能量完成接收。4.2 记录格式设计Magic Len Seq Payload CRC16硬件上的保护只是外围数据结构的健壮性才是最后一层保险。一条工业日志记录不能只是简单地往一个地址写一串字节因为掉电可能发生在任何两个字节之间。虽然 MRAM 的单个字节写入是原子的但一条记录如果是 10 个字节掉电时可能只写完其中 5 个字节。重启后怎么知道这条记录是完整的答案是在记录里带上完整性校验信息。我使用的记录格式如下字段长度说明Magic1 字节固定值 0x5A用于快速定位记录头Len1 字节Payload 长度Seq4 字节记录序号递增Payload0~250 字节实际业务数据CRC162 字节对前面的字段做 CRC16 校验读日志时从记录区起始地址开始扫描先看 Magic再看 CRC16校验通过才算有效记录。掉电导致最后一条记录只写了一半时下一轮启动扫描会看到 Magic 对得上但 CRC 校验失败直接把它当作无效数据跳过即可。这种设计在 MRAM 上非常实用因为它不涉及擦除操作跳过半条坏记录的成本几乎为零不需要像 Flash 那样做复杂的恢复算法。4.3 参数区的 A/B 镜像与启动判定除了日志工业设备还会有一些参数比如设备地址、校准系数、运行模式。这些参数需要频繁更新而且更新时一旦掉电不能出现“参数丢失”的情况。我的做法是双镜像在 MRAM 里划出两个参数区每次写参数时写其中一个区同时更新这个区头部的版本号。启动时同时读两个区取版本号更高且校验通过的那一份作为有效参数。这样做的逻辑其实很简单同一个参数不可能同时写两个区任何一个区写了一半时 CRC 校验会失败系统就自动回退到另一个区的旧版本。MRAM 的无限寿命让这种双写方案没有额外负担但如果是 Flash 或 EEPROM双区镜像依然可行只是要考虑磨损均衡的问题。5. 实测结果和三个花了半天才定位的坑方案搭建完成之后我在实验室做了大量读写测试和掉电测试。测试结果整体很稳定但也遇到了几个非常典型的坑这里单独拿出来讲。5.1 实测吞吐量PIC18F4455 在 20MHz 主频、SPI 时钟 5MHz 的情况下连续读速度大约是 0.4MByte/s 到 0.5MByte/s单字节写因为每次都要发 WREN 和地址吞吐会低一些但页写模式下基本可以接近连续读的速度。如果 MCU 跑 48MHz、SPI 时钟 12MHz理论吞吐还能再高一些但受限于 MSSP 的 Fosc/4 上限软件实现的开销也比较明显。操作SPI 时钟实测耗时备注读 512 字节5MHz约 1.2ms纯数据读取页写 256 字节5MHz约 1.0ms含地址发送不含 WREN连续写 100 万次 16 字节记录5MHz数据全部回读正确无写入失败这个速度对“每 100ms 写一条日志”的工业场景来说绰绰有余实际上能支撑到毫秒级写入频率。5.2 坑一PIC18F4455 的 USB 引脚和 SPI 打架这是选型阶段最容易被忽略的问题。PIC18F4455 的 MSSP 模块使用 RC3、RC4、RC5 三个引脚其中 RC4 和 RC5 同时也是 USB 模块的 D- 和 D。这意味着你没法在把这颗 MRAM 挂在 MSSP 上的同时又用 USB 接口导出数据。我一开始的计划是“MRAM 存数据USB 导数据”等画原理图的时候才发现两个模块引脚冲突。实际项目里我最后改成了 UART 导出固件升级和数据导出都走串口。如果你确实需要 USB要么换一颗带有独立 USB 和独立 SPI 控制器引脚的单片机要么用软件模拟 SPI 把 MSSP 释放给 USB但软件 SPI 的速率和稳定性都不好控制。这个取舍必须在选型阶段就定下来。5.3 坑二忘了 WREN写了个寂寞MRAM 的写使能机制和 SPI EEPROM 很像但又容易被忽略。我第一次调写函数的时候写完数据再读回来发现读到的全是旧值一开始怀疑是地址算错了后来查了不少资料才发现是漏掉了 WREN 指令。MR25H40CDF 的 WEL 位在上电后默认是 0写操作之前必须用 WREN 指令把 WEL 位置 1。每写完一组数据WEL 位会在 WRITE 指令完成后自动清零所以每一次写操作都需要重新发 WREN。排查这个问题的标准方法是读状态寄存器0x05 指令返回的第 0 位就是 WEL。调试时可以先写一条 WREN再 RDSR看到 WEL 变成 1基本就能确认写使能链路是通的。5.4 坑三HOLD 引脚悬浮数据读到一半卡死这个坑我在第 2 节硬件部分提了一嘴但值得再展开一次。有一次现场反馈设备偶发数据异常实验室里怎么复现都不稳定。后来用示波器盯着 HOLD 引脚看发现它在某些上电时序下会有一小段低电平毛刺正好落在 SPI 数据交互中间导致 MRAM 暂停采样主机这边等 BF 位等到超时。解决办法很简单HOLD 引脚加 10k 上拉电阻到 VDD并且在初始化阶段把 HOLD 配置为强上拉状态。同样/WP 引脚也建议上拉虽然它的作用只是写保护但如果悬空上电瞬间的干扰电平也可能让芯片进入写保护状态表现为写操作静默失败。6. 可复用的扩展方向日志轮询、文件系统与 Linux 场景这套“PIC18F4455 MR25H40CDF”组合跑通之后后面再遇到类似项目基本可以把方案直接复制过去。我也总结了一些可以继续扩展的方向方便在不同项目里复用。6.1 无限寿命下的环形日志既然 MRAM 写入寿命几乎无限做日志系统时就不需要像 Flash 那样担心磨损均衡。最简单的做法是固定一块区域记录指针从头写到尾写满后回卷到开头覆盖旧数据。每次启动时通过记录里的 Seq 序号找到最新的那条日志就能知道当前写指针位置。这个方案代码量非常小非常适合单片机环境。6.2 在 MRAM 上放一个掉电安全的文件系统如果你想在 MRAM 上做更复杂的文件管理比如存多个配置文件、固件升级包可以考虑移植 littlefs 这类针对嵌入式设备设计的文件系统。littlefs 本身设计目标是掉电安全和磨损均衡但它在 Flash 上的性能受限于擦除操作而 MRAM 没有擦除操作也能做到原地更新所以跑在 MRAM 上会非常顺。我在 Linux 嵌入式板卡上也试过直接把 MR25H40CDF 挂到 SPI 总线上用 spidev 接口做应用层读写效果同样稳定只是要注意文件系统的“块擦除”概念和 MRAM 的字节写特性需要一个适配层。6.3 容量升级路径Everspin 的 SPI MRAM 家族指令集是兼容的MR25H40 是 4MbitMR25H64 是 64Mbit地址格式都是 24 位。如果后续产品需要更大容量软件上基本不用改只需要注意地址空间和页大小是否发生变化。这一点在选型时是个很有价值的加分项因为硬件平台不变、软件改动小产品迭代成本会低很多。6.4 和低功耗采集节点结合如果项目是电池供电的数据采集节点MR25H40CDF 的静态功耗也很低平时待机时可以把芯片置于掉电模式。需要记录时再唤醒写一条数据后会立刻进入低功耗状态。PIC18F4455 本身也有多种低功耗模式配合外部 RTC 定时唤醒完全可以做一个低功耗、非易失、长寿命的数据记录节点。不过这种情况下写入频率要保持适中毕竟 SPI 通信本身还是需要一些能量的。最后说一件实测中最让我安心的事。项目做完后我在实验室做了几十次“写日志写到一半直接拔电源”的测试重新上电后回读数据除了最后一条记录可能因 CRC 校验失败被跳过之前写入的数据全部完好无损。以前用 Flash 方案的时候我最怕的就是这种拔电测试因为每拔一次电我都得担心是否丢了一页数据。MR25H40CDF 这种“字节原子写、无擦除等待、掉电不丢数据”的特性配上 PIC18F4455 这种成熟稳定的单片机确实非常适合工业记录类应用。如果你也在做类似的东西先想清楚三个问题写入频率多高、掉电瞬间希望表现如何、数据完整性怎么校验。这三个问题想透了存储方案基本就不会选错。
返回列表