1. 项目缘起与方案选型:为什么是 MRAM 加 PIC18F97J94
工业现场的数据记录有个很尴尬的处境:用 EEPROM 吧,写入速度慢、擦写寿命也就百万次级别,遇到高频采集场景没几个月就写废了;用 SRAM 加后备电池吧,电池在高温高湿环境下两三年就鼓包漏液,维护成本高得离谱;用 Flash 吧,写之前得擦整个扇区,掉电瞬间还容易丢数据。我前几年做过一个电力监测终端,客户要求每 100ms 记录一次三相电流电压,一天就是 86 万次写入,用 EEPROM 方案算下来寿命撑不过半年,后来换成铁电存储器才勉强扛住,但铁电容量小、价格也不便宜。
这次要聊的MR25H40CDF就是来解决这类痛点的。它是 Everspin 出的 4Mbit 串行 MRAM(磁性随机存储器),SPI 接口,关键特性很硬核:写入没有延迟,不像 Flash 需要等待擦除和编程周期;擦写寿命理论上无限,官方标称 10^14 次以上;数据保持能力在工业温度范围内超过 20 年,而且不需要电池。这三点加起来,基本就是工业数据记录的理想存储介质。
主控这边选PIC18F97J94,是 Microchip 的 8 位 PIC18 系列,带 96KB Flash、4KB RAM,集成了 4 个 SPI 模块,工作温度范围 -40°C 到 +85°C(工业级),外设资源对于这种数据记录场景绰绰有余。有人可能会问,都什么年代了还用 8 位机?我的看法是,工业记录仪这种场景对算力要求不高,但对稳定性和长期供货的要求极高,PIC18 系列在工业领域铺货时间长、工具链成熟、抗干扰能力强,选它是有工程理性的,不是守旧。
这套组合的核心价值在于:用 SPI 总线把 MRAM 挂到 PIC18 上,实现高速、高可靠、长寿命的数据存储和读取。适合做工业数据记录仪、智能电表、车载黑匣子、医疗设备日志、POS 机交易记录这类需要频繁写入且不能丢数据的嵌入式项目。下面我把整个设计思路、硬件连接、SPI 驱动、读写时序、常见坑点全部拆开讲一遍。
2. 核心器件解析与硬件设计要点
2.1 MR25H40CDF 的关键参数与内部结构
先把这颗芯片的规格理清楚,不然后面写驱动容易踩坑。MR25H40CDF 的容量是 4Mbit,也就是 512KB,组织方式是 512K × 8 位。SPI 接口支持模式 0 和模式 3,最高时钟频率 40MHz,不过实际用的时候要看主控能不能跑到这个速度。供电范围 2.7V 到 3.6V,典型工作电流在 40MHz 下大约 15mA,待机电流只有几微安。
内部结构上,MRAM 的存储单元是磁性隧道结(MTJ),通过磁化方向来存储 0 和 1,这跟 Flash 靠电荷存储的物理机制完全不同。带来的直接好处是:写入时不需要先擦除,直接覆盖写就行;写入能量低,速度快,没有写等待周期;读写次数没有实际限制。我实测过连续写入 100 万次,读取数据完全正常,没有任何位翻转。
引脚方面,MR25H40CDF 是 8 脚 SOIC 封装,引脚定义如下:
| 引脚编号 | 名称 | 功能说明 |
|---|---|---|
| 1 | CS | 片选,低电平有效 |
| 2 | SO | 数据输出(MISO) |
| 3 | WP | 写保护,低电平有效 |
| 4 | VSS | 地 |
| 5 | SI | 数据输入(MOSI) |
| 6 | SCK | 时钟 |
| 7 | HOLD | 保持,低电平有效 |
| 8 | VDD | 电源 |
这里有个细节要注意:WP 和 HOLD 引脚如果不使用,必须上拉到 VDD,不能悬空。我见过有人把 HOLD 悬空导致通信偶尔失败,查了半天以为是时序问题,结果是引脚状态不确定。
2.2 PIC18F97J94 的 SPI 模块配置
PIC18F97J94 有 4 个 SPI 模块,我用的是 SPI1,对应引脚是 RC3(SCK)、RC4(SDI)、RC5(SDO),片选用 RB0 普通 IO 来控制。这里选择软件片选而不是硬件片选,原因是硬件片选在某些情况下会在字节边界自动释放,而 MRAM 的读写命令需要 CS 在整个命令序列期间保持低电平,软件控制更灵活。
SPI 模式选择上,MR25H40CDF 支持模式 0(CPOL=0,CPHA=0)和模式 3(CPOL=1,CPHA=1)。我选模式 0,因为这是最常用的配置,示波器抓波形也方便对照。时钟频率方面,PIC18F97J94 在 64MHz 系统时钟下,SPI 分频可以做到 Fosc/4,也就是 16MHz。虽然 MRAM 支持 40MHz,但 16MHz 已经足够满足大部分工业记录场景的需求,而且信号完整性更容易保证。
配置寄存器关键位设置如下:
// SPI1 配置为模式 0,时钟 = Fosc/4 SSP1CON1 = 0x20; // SSPEN=1, CKP=0, SSPM=0000 (Fosc/4) SSP1STAT = 0x40; // CKE=0 (模式0) // 片选引脚配置 TRISBbits.TRISB0 = 0; // RB0 输出 LATBbits.LATB0 = 1; // 初始拉高2.3 硬件连接与 PCB 布局注意事项
接线本身不复杂,SPI 四根线加电源地,但 PCB 布局有几个坑得提前说。第一,SCK 走线尽量短,如果超过 10cm 建议加串联电阻(22Ω 到 33Ω)做阻抗匹配,否则高速下波形振铃会导致误码。第二,MRAM 的电源引脚旁边必须放 0.1μF 陶瓷电容,越近越好,我一般放在 2mm 以内。第三,如果 MRAM 和主控不在同一块板上,排线要用地线隔离 SCK 和 MOSI,减少串扰。
还有一个容易被忽略的点:MRAM 的 WP 引脚如果直接接地,芯片就永远处于写保护状态,只能读不能写。我建议用主控的一个 IO 来控制 WP,这样可以在固件升级或关键数据保护时动态使能写保护,灵活性更高。
3. SPI 驱动层实现与读写时序拆解
3.1 底层字节收发函数
SPI 驱动的基础是字节收发函数,PIC18F97J94 的 SPI 模块用起来很直接,写 SSP1BUF 然后等 BF 标志位就行。但这里有个细节:读的时候要先写一个 dummy 字节(通常是 0xFF)来产生时钟,然后读 SSP1BUF。我见过有人在读的时候不写 dummy,结果时钟没出来,读到的全是 0。
uint8_t SPI1_ExchangeByte(uint8_t data) { SSP1BUF = data; while (!PIR1bits.SSP1IF); // 等待传输完成 PIR1bits.SSP1IF = 0; return SSP1BUF; }这个函数看起来简单,但实际用的时候要注意:如果 SPI 时钟太快而主频太低,等待循环可能来不及,建议在初始化时确认 SPI 时钟不超过 Fosc/4。另外,SSP1IF 标志位在读取 SSP1BUF 后会自动清除,但为了保险我习惯手动清一次。
3.2 MRAM 命令集与读写操作
MR25H40CDF 的命令集很精简,常用的就几条:
| 命令名称 | 命令码 | 功能说明 |
|---|---|---|
| WREN | 0x06 | 写使能 |
| WRDI | 0x04 | 写禁止 |
| RDSR | 0x05 | 读状态寄存器 |
| WRSR | 0x01 | 写状态寄存器 |
| READ | 0x03 | 读数据 |
| WRITE | 0x02 | 写数据 |
写操作流程是:拉低 CS → 发送 WREN(0x06)→ 拉高 CS → 拉低 CS → 发送 WRITE(0x02)→ 发送 3 字节地址 → 发送数据 → 拉高 CS。注意 WREN 和 WRITE 之间必须拉高 CS 再拉低,这是 MRAM 的协议要求,跟有些 Flash 不一样。我一开始没注意这个细节,WREN 和 WRITE 连在一起发,结果写不进去,查了一下午才发现问题。
读操作简单一些:拉低 CS → 发送 READ(0x03)→ 发送 3 字节地址 → 读取数据 → 拉高 CS。读操作不需要 WREN,也不需要中间拉高 CS。
地址是 24 位的,因为 512KB 需要 19 位地址,用 3 字节传输刚好覆盖。地址的高位字节在前,这是 SPI 存储器的通用惯例。
3.3 页写与连续写策略
MR25H40CDF 支持连续写,没有页边界限制,这点比 Flash 友好太多。Flash 通常有 256 字节页限制,跨页写会回卷覆盖,MRAM 完全没这个问题,可以从任意地址开始连续写任意长度。但实际用的时候我还是建议按页对齐写,原因是:第一,方便计算和校验;第二,如果写入过程中掉电,页对齐的数据更容易做恢复。
我一般把页大小定义为 256 字节,写的时候先算好起始页和结束页,分页写入。每页写完读回来校验一次,确认无误再写下一页。这样虽然慢一点,但可靠性高很多。实测下来,16MHz SPI 时钟下,写 256 字节大约需要 130μs,加上校验时间也就 200μs 左右,对于 100ms 的记录周期来说完全够用。
4. 数据存储架构与掉电保护设计
4.1 环形缓冲区设计
工业记录场景通常是循环覆盖,最老的数据被新数据覆盖。我在 MRAM 里划了三个区域:数据区、索引区、配置区。数据区占大部分空间,用环形缓冲区管理;索引区记录当前写指针和读指针;配置区存设备参数和校准数据。
环形缓冲区的实现关键是写指针的原子更新。每次写完一条记录,更新写指针,然后再写数据。如果掉电发生在写数据过程中,写指针没更新,下次上电会覆盖这条不完整的记录,不会影响已有数据。这个顺序不能反,先更新指针再写数据的话,掉电会导致指针指向一条无效记录。
4.2 掉电检测与数据完整性
PIC18F97J94 有内置的掉电检测模块(HLVD),可以设置触发电压。我一般设到 2.9V 触发,因为 MRAM 最低工作电压是 2.7V,留 0.2V 余量。掉电中断触发后,立即停止写入操作,把当前写指针和状态标志写入索引区,然后进入低功耗模式等待电源完全掉电。
这里有个经验:掉电中断里不要做太多事情,中断服务程序越短越好。我见过有人在掉电中断里做数据校验和保存,结果中断还没执行完电就掉没了,数据反而写坏了。正确做法是中断里只置一个标志位,主循环检测到标志后快速保存关键状态,其他事情等上电再说。
4.3 数据校验与纠错
MRAM 本身可靠性很高,但工业环境电磁干扰大,SPI 传输过程中还是可能出错。我在每条记录后面加 2 字节 CRC16 校验,读取时先校验再使用。如果 CRC 不匹配,标记该记录为无效,跳过继续读下一条。这样即使偶尔有一条记录损坏,也不会影响整体数据的可用性。
CRC16 我用的是 CCITT 多项式(0x1021),查表法实现,速度很快。每条记录 32 字节数据加 2 字节 CRC,总共 34 字节,按 256 字节页对齐的话,一页能存 7 条记录,剩 18 字节填充。填充字节写 0xFF,方便识别。
5. 实操调试与常见问题排查
5.1 调试工具与波形分析
调试 SPI 通信,示波器是必备的。我一般用四通道示波器同时抓 CS、SCK、MOSI、MISO 四根线,触发设在 CS 下降沿。重点看几个地方:CS 拉低到第一个 SCK 边沿的时间是否足够(MRAM 要求至少 5ns,一般都能满足);SCK 空闲电平是否正确(模式 0 是低电平);MISO 数据在 SCK 上升沿是否稳定。
如果没有示波器,逻辑分析仪也行,但要注意采样率至少是 SCK 频率的 4 倍以上,否则抓不到细节。我用的是一款 100MHz 采样率的逻辑分析仪,抓 16MHz SPI 勉强够用,能看到数据变化但边沿细节不太清楚。条件允许的话还是上示波器。
5.2 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方法 |
|---|---|---|---|
| 读不到数据,全 0 或全 FF | CS 未正确拉低 | 示波器看 CS 波形 | 检查片选 IO 配置 |
| 写入后读回数据错误 | WREN 和 WRITE 之间未拉高 CS | 逻辑分析仪看命令序列 | 按协议要求插入 CS 高电平 |
| 偶尔通信失败 | HOLD 或 WP 引脚悬空 | 万用表测引脚电压 | 上拉到 VDD |
| 高速下误码率高 | SCK 走线太长或未匹配 | 示波器看 SCK 波形 | 缩短走线或加串联电阻 |
| 掉电后数据丢失 | 掉电检测阈值设置不当 | 测掉电时 VDD 下降曲线 | 调整 HLVD 阈值,留足余量 |
| 写入速度慢 | SPI 时钟分频太大 | 测 SCK 实际频率 | 调整分频比,提高时钟 |
5.3 实操心得与避坑技巧
第一个坑:MRAM 的写使能是自动复位的。每次写操作前都要发 WREN,写完自动清除。我一开始以为发一次 WREN 可以连续写多次,结果第二次写就失败了。后来看数据手册才发现,WREN 在每次 WRITE 命令执行后自动复位,必须重新发送。
第二个坑:地址对齐问题。虽然 MRAM 支持任意地址读写,但如果你用 DMA 或者某些优化编译器,可能会要求地址对齐。我建议数据区起始地址按 4 字节对齐,这样用 32 位变量直接映射访问时不会出问题。
第三个坑:SPI 模式配置错误。MR25H40CDF 支持模式 0 和模式 3,但有些主控的 SPI 模式 0 和模式 3 在实际波形上差异很小,容易混淆。我建议先用模式 0 调通,如果不行再试模式 3。判断方法很简单:模式 0 下 SCK 空闲为低,模式 3 下 SCK 空闲为高。
第四个坑:电源去耦不足。MRAM 在写入瞬间电流会有波动,如果去耦电容不够,VDD 会瞬间跌落导致写入失败。我一般在 MRAM 电源引脚旁边放 0.1μF 和 10μF 两个电容,0.1μF 滤高频,10μF 储能。
6. 性能实测与场景扩展
6.1 读写速度实测数据
我在 16MHz SPI 时钟下做了实测,结果如下:
| 操作类型 | 数据量 | 耗时 | 备注 |
|---|---|---|---|
| 单字节写 | 1 字节 | 约 5μs | 含 WREN 和地址 |
| 页写 | 256 字节 | 约 130μs | 连续写无页边界 |
| 单字节读 | 1 字节 | 约 3μs | 含地址 |
| 连续读 | 256 字节 | 约 128μs | 无额外开销 |
| 写加校验 | 256 字节 | 约 200μs | 含读回校验 |
这个速度对于 100ms 记录周期来说绰绰有余,即使 10ms 周期也能轻松应对。如果换成 40MHz SPI 时钟,速度还能再提升一倍多,但需要主控支持。
6.2 工业场景扩展应用
这套方案不只适用于数据记录仪,还可以扩展到很多场景。比如智能电表里用来存电量数据和事件记录,车载黑匣子里存行驶数据和故障码,医疗设备里存患者参数和操作日志。核心优势都是一样的:高频写入不丢数据,掉电不丢数据,长期使用不换电池。
如果数据量更大,可以多片 MRAM 级联,用不同的 CS 引脚选择。PIC18F97J94 有 4 个 SPI 模块,理论上可以挂 4 组 MRAM,每组再通过片选扩展,容量可以做到几 MB。对于大部分工业记录场景,512KB 到 2MB 的容量已经足够存几个月的数据了。
6.3 后续优化方向
如果让我继续优化这套方案,我会从三个方向入手。第一,把 SPI 时钟提到 40MHz,需要换用支持高速 SPI 的主控,或者用 FPGA 做 SPI 控制器。第二,加文件系统,把 MRAM 模拟成 FATFS 磁盘,方便用 PC 读取数据。第三,加无线传输模块,把记录的数据定期上传到云端,实现远程监控。
不过这些都是锦上添花,核心的 MRAM 加 PIC18 方案已经足够稳定可靠。我在实际项目中用这套方案跑了两年多,没有出现过数据丢失或写入失败的情况,工业现场的电磁干扰、温度变化、电源波动都扛住了。对于需要高可靠数据存储的嵌入式项目,这个组合值得认真考虑。