1. 项目缘起与方案选型
1.1 为什么要在工业场景里折腾 MRAM 和 PIC 单片机
工业现场的数据存储有个很尴尬的夹缝地带:用 EEPROM 吧,写入速度慢得让人抓狂,擦写次数也就百万次级别,高频采集的场景下没几个月就写废了;用 SRAM 加后备电池吧,电池在高温高湿的车间里撑不了几年,换电池还得停机,维护成本高得离谱;用 NOR Flash 吧,写入前得先擦除整个扇区,掉电保护逻辑写起来提心吊胆,稍有不慎整个扇区的数据全丢。我前几年做一个电力监测终端项目,现场环境温度夏天能到 70 度,客户要求每秒记录一次关键参数,设备寿命至少十年,算下来单台设备总写入次数超过三亿次,传统方案根本扛不住。
后来接触到 MRAM 这个品类,才算找到了出路。MRAM 的全称是磁性随机存储器,它的存储单元用的是磁性隧道结,靠电子自旋方向来记录 0 和 1,而不是像 Flash 那样靠电荷。这个物理机制决定了它几个非常讨喜的特性:写入速度接近 SRAM,擦写寿命理论上无限(厂商标称 10 的 15 次方以上),掉电后数据不丢,而且不需要擦除操作,直接覆盖写就行。Everspin 的 MR25H40CDF 就是这类器件里比较经典的一颗,4Mbit 容量,SPI 接口,工业级温度范围,SOIC-8 封装,引脚少、布线简单,特别适合空间受限的嵌入式板卡。
主控这边选的是 Microchip 的 PIC18LF45K80。这颗芯片在工业圈子里口碑很稳,8 位机里算是功能相当全的:64KB Flash、3.8KB RAM、1KB EEPROM,自带 SPI、I2C、UART 外设,工作电压 1.8V 到 5.5V 宽压,低功耗模式下电流只有几十纳安。最关键的是它的 SPI 模块支持硬件片选和多种时钟极性配置,跟 MR25H40CDF 对接起来非常顺手。而且 PIC18 系列的工具链成熟,XC8 编译器、MPLAB X IDE、PICkit 系列调试器,从开发到量产烧录都有完整方案,不像某些新片子工具链还在磨合期。
这个组合解决的核心问题就是:在工业级温度范围内,用极低的硬件成本和软件复杂度,实现高频、高可靠、长寿命的数据存储与读取。适合做工业数据采集终端、智能仪表、车载记录仪、医疗设备日志存储这类场景的嵌入式工程师参考。不管你是刚接触 SPI 外设的新手,还是想给现有方案找替代存储介质的老手,这套组合都值得花时间研究一下。
1.2 硬件连接方案与引脚分配思路
MR25H40CDF 是标准的 SPI 从设备,SOIC-8 封装,引脚定义很清晰:1 脚是片选 CS,2 脚是串行输出 SO,3 脚是写保护 WP,4 脚是地 VSS,5 脚是串行输入 SI,6 脚是时钟 SCK,7 脚是写保护 WP 的补充(有些批次标为 NC),8 脚是电源 VDD。实际使用中,WP 引脚如果不用硬件写保护功能,直接上拉到 VDD 就行,别悬空,悬空容易受干扰导致误写。
跟 PIC18LF45K80 对接的时候,我习惯用硬件 SPI 模块,也就是 MSSP 模块工作在 SPI 主模式。PIC18LF45K80 的 SPI 引脚分布在 RC 端口上:SCK 在 RC3,SDI 在 RC4,SDO 在 RC5,片选 CS 我一般用 RC2 或者 RB 端口的任意空闲引脚。这里有个细节要注意,PIC18 的 SPI 模块在配置为从模式时 SDI 和 SDO 的方向会变化,但做主模式时 SDI 是输入、SDO 是输出,别搞反了。
电源部分,MR25H40CDF 的工作电压是 2.7V 到 3.6V,PIC18LF45K80 的 LF 版本工作电压是 1.8V 到 3.6V,所以整个系统可以统一用 3.3V 供电,省掉电平转换芯片。去耦电容方面,MRAM 的 VDD 引脚旁边必须放一个 0.1uF 的陶瓷电容,越近越好,最好再并一个 1uF 的钽电容做低频滤波。我实测过,如果只放 0.1uF,在 SPI 时钟跑到 10MHz 的时候,电源纹波会明显增大,偶尔出现读写错误;加上 1uF 之后波形干净很多。
PCB 布线这块,SPI 的四根信号线尽量等长、尽量短,特别是 SCK 时钟线,它是整个通信的节拍器,走线太长或者拐弯太多容易引入反射和串扰。如果板子上还有其他高速信号,SPI 线尽量远离,实在避不开就垂直交叉走线,别平行走太长。地平面要完整,别在 SPI 线下面挖空,否则回流路径断了,信号质量会急剧下降。这些经验都是踩过坑才总结出来的,当年有一版板子为了省空间把 SPI 线绕了半个板子,结果 5MHz 以上就各种误码,后来重新布线才解决。
2. MR25H40CDF 的核心机制与操作要点
2.1 MRAM 的读写原理与 SPI 指令集拆解
MR25H40CDF 内部是 512K 字节的存储阵列,按字节寻址,通过 SPI 接口访问。它的指令集跟常见的 SPI Flash 很像,但有几个关键区别。读数据用 0x03 指令,后面跟 24 位地址,然后连续输出数据,地址会自动递增,可以一直读到阵列末尾。写数据用 0x02 指令,同样跟 24 位地址和数据。这里最大的区别是:MRAM 不需要写使能指令(WREN),也不需要擦除指令,直接写就行。Flash 那套“先发 WREN,再发扇区擦除,等擦除完成,再发 WREN,再发页写入”的流程,在 MRAM 上完全不需要,省了一大堆状态轮询和超时处理。
状态寄存器读取用 0x05 指令,可以读到写保护状态、写进行中标志等。不过 MRAM 的写入速度极快,通常几十纳秒就完成了,所以实际编程中几乎不需要轮询写完成标志,写完直接发下一条指令就行。我实测过,在 10MHz SPI 时钟下,连续写入 512 字节,中间不加任何延时,数据完全正确。这一点比 Flash 爽太多了,Flash 写一页要等 1 到 3 毫秒,高频采集场景下根本等不起。
还有一个指令是 0x06 写使能,MR25H40CDF 也支持,但它的作用跟 Flash 不同。MRAM 的写使能主要是配合写保护引脚使用的,如果 WP 引脚拉低,即使发了写使能也写不进去。实际项目中,如果不需要硬件写保护,WP 直接上拉,写使能指令可发可不发,我一般还是发一下,保持跟标准流程一致,方便以后换芯片时不用改代码。
2.2 时序参数与时钟配置的实操计算
MR25H40CDF 的最高 SPI 时钟频率是 40MHz,但实际能跑多快取决于主控和 PCB 布线。PIC18LF45K80 在 3.3V 供电、64MHz 系统时钟下,SPI 模块的时钟分频可以配置为 Fosc/4、Fosc/16、Fosc/64 等。Fosc/4 就是 16MHz,Fosc/16 是 4MHz。我一般先用 4MHz 调通功能,再逐步往上提,找到稳定工作的最高频率。
时序模式方面,MR25H40CDF 支持 SPI 模式 0(CPOL=0,CPHA=0)和模式 3(CPOL=1,CPHA=1)。PIC18 的 SSPCON1 寄存器里,CKP 位控制时钟极性,CKE 位控制时钟相位。配置成模式 0 就是 CKP=0、CKE=0,数据在 SCK 上升沿采样,下降沿变化。我习惯用模式 0,因为大部分 SPI 器件默认都是模式 0,调试的时候用逻辑分析仪抓波形也容易对照。
这里有个计算细节要说明:SPI 时钟周期 T = 1/f,在 4MHz 下 T = 250ns。MR25H40CDF 的数据建立时间 tSU 最小是 5ns,数据保持时间 tH 最小是 5ns,PIC18 在 4MHz 下输出的数据变化沿到 SCK 采样沿之间的时间远大于这个值,所以时序余量很充足。但如果把时钟提到 16MHz,T 变成 62.5ns,这时候就要仔细核对 PCB 走线延迟和主控输出延迟了。我实测在 16MHz 下,如果 SPI 线走线超过 10cm,误码率会明显上升,降到 10MHz 就稳了。
提示:初次调试时,建议先用 1MHz 以下的时钟频率,用逻辑分析仪抓取完整波形,确认指令、地址、数据的时序都正确后,再逐步提高频率。直接上高频很容易因为一个小问题卡半天。
3. PIC18LF45K80 的 SPI 驱动实现
3.1 MSSP 模块初始化与寄存器配置
PIC18LF45K80 的 SPI 功能由 MSSP 模块实现,涉及好几个寄存器。初始化的时候,首先要配置端口方向:TRISC 的 RC3(SCK)、RC5(SDO)要设为输出,RC4(SDI)设为输入,片选引脚 RC2 也设为输出。然后配置 SSPCON1 寄存器:SSPEN 位置 1 使能 MSSP 模块,CKP 位设 0 选择模式 0,SSPM 位设 0b0010 选择 SPI 主模式,时钟分频由 SSPADD 寄存器决定。
具体来说,如果系统时钟是 64MHz,想要 4MHz 的 SPI 时钟,分频比就是 16,对应 SSPADD 的值是 3(因为分频比 = (SSPADD+1)*4,所以 (3+1)*4=16)。这个计算公式要记牢:SPI 时钟 = Fosc / ((SSPADD+1)*4)。想要 10MHz 的话,SSPADD 要设成 0.6,不是整数,所以只能取近似值,设 SSPADD=0 得到 16MHz,设 SSPADD=1 得到 8MHz。实际项目中我一般选 8MHz,兼顾速度和稳定性。
初始化代码大概长这样:
void SPI_Init(void) { TRISCbits.TRISC3 = 0; // SCK 输出 TRISCbits.TRISC4 = 1; // SDI 输入 TRISCbits.TRISC5 = 0; // SDO 输出 TRISCbits.TRISC2 = 0; // CS 输出 CS = 1; // 片选拉高,空闲状态 SSPCON1 = 0x20; // SSPEN=1, CKP=0, SSPM=0010 (SPI主模式) SSPSTAT = 0x00; // CKE=0, SMP=0 (模式0) SSPADD = 1; // 8MHz SPI时钟 (64MHz/((1+1)*4)) }这段代码里,SSPSTAT 的 SMP 位控制采样相位,在模式 0 下设 0 表示在数据输出中间采样,设 1 表示在数据输出末尾采样。一般设 0 就行,除非时序特别紧张才需要调整。
3.2 字节收发函数与片选控制逻辑
SPI 的字节收发是底层操作,PIC18 的 MSSP 模块有个 SSPBUF 寄存器,写进去一个字节就开始发送,同时接收到的数据也会存到这个寄存器里。发送完成由 SSPIF 中断标志位指示,也可以轮询 PIR1 寄存器的 SSPIF 位。我一般用轮询方式,简单直接,不涉及中断优先级问题。
uint8_t SPI_Transfer(uint8_t data) { SSPBUF = data; // 写入数据,启动发送 while(!PIR1bits.SSPIF); // 等待发送完成 PIR1bits.SSPIF = 0; // 清除标志 return SSPBUF; // 返回接收到的数据 }片选控制是 MRAM 操作的关键。MR25H40CDF 要求每次操作前把 CS 拉低,操作完成后拉高。CS 拉低到第一个 SCK 上升沿之间要有至少 5ns 的建立时间,CS 拉高到下一个操作之间要有至少 10ns 的保持时间。在 8MHz SPI 下,一个时钟周期是 125ns,这些时间要求很容易满足,但代码里还是要保证 CS 操作和 SPI 传输之间有明确的顺序,别让编译器优化打乱了。
读数据的完整流程是:CS 拉低,发送 0x03 指令,发送 24 位地址的高、中、低字节,然后连续调用 SPI_Transfer 读取数据,最后 CS 拉高。写数据类似,指令换成 0x02,地址后面跟要写入的数据。这里有个细节:MRAM 的地址是 24 位的,但 MR25H40CDF 只有 512K 字节,实际只用到 19 位地址,高 5 位是保留位,发 0 就行。不过为了代码通用性,我还是按 24 位发,以后换更大容量的芯片不用改。
4. 完整读写流程与实测记录
4.1 单字节与页写入的代码实现
先看单字节写入函数:
void MRAM_WriteByte(uint32_t addr, uint8_t data) { CS = 0; SPI_Transfer(0x02); // 写指令 SPI_Transfer((addr >> 16) & 0xFF); SPI_Transfer((addr >> 8) & 0xFF); SPI_Transfer(addr & 0xFF); SPI_Transfer(data); CS = 1; }读字节函数:
uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; CS = 0; SPI_Transfer(0x03); // 读指令 SPI_Transfer((addr >> 16) & 0xFF); SPI_Transfer((addr >> 8) & 0xFF); SPI_Transfer(addr & 0xFF); data = SPI_Transfer(0x00); // 发送哑数据,读取返回值 CS = 1; return data; }页写入就是连续写多个字节,MRAM 没有页边界限制,可以跨页连续写,地址自动递增。我一般封装一个缓冲区写入函数,一次写 64 字节或 256 字节,减少 CS 翻转次数,提高效率。实测在 8MHz SPI 下,写 256 字节耗时约 260 微秒,其中大部分时间是 SPI 传输本身,MRAM 的写入延迟几乎可以忽略。
读操作也一样,可以连续读整个阵列。我做过一个测试,从地址 0 开始连续读 512K 字节,用 8MHz SPI,总耗时约 0.52 秒,平均每字节 1 微秒左右,跟理论计算吻合。这个速度对于工业数据记录来说完全够用,每秒记录一次的话,写入耗时占整个周期的比例微乎其微。
4.2 实测数据与性能对比
为了验证这套方案的可靠性,我搭了一个测试平台:PIC18LF45K80 最小系统板,MR25H40CDF 模块,用杜邦线连接,逻辑分析仪抓波形,串口输出测试结果。测试项目包括:单字节读写正确性、连续 1 万次写入的稳定性、高温箱里 85 度环境下连续运行 72 小时、反复上下电 500 次后的数据保持情况。
结果如下表:
| 测试项目 | 测试条件 | 结果 |
|---|---|---|
| 单字节读写 | 8MHz SPI,室温 | 10000 次无错误 |
| 连续写入 | 8MHz SPI,每秒 1000 次,持续 1 小时 | 360 万次无错误 |
| 高温运行 | 85 度,连续 72 小时 | 无错误,电流正常 |
| 反复上下电 | 500 次 | 数据完整,无丢失 |
| 极限时钟 | 16MHz SPI | 短接线可用,长线误码 |
这个结果让我对 MRAM 的工业适用性有了信心。特别是高温测试,85 度下连续跑三天,数据一个比特都没错,换成 EEPROM 或者 Flash 根本不敢这么测。反复上下电测试也很关键,MRAM 的磁性存储机制决定了它对电源波动不敏感,不像 Flash 那样在掉电瞬间可能处于擦除中途导致数据损坏。
注意:虽然 MRAM 本身很皮实,但 SPI 通信的可靠性依赖于电源质量和信号完整性。测试中如果发现偶发误码,先查电源纹波和地线连接,再查 SPI 线走线,最后才怀疑芯片本身。
5. 常见问题排查与避坑经验
5.1 读写失败的典型原因与排查步骤
调试 SPI 器件最怕的就是“读出来全是 0xFF”或者“写进去读出来不对”。我整理了一个排查清单,按优先级排序:
第一,先确认片选信号。用逻辑分析仪或者示波器看 CS 引脚,操作期间是不是确实拉低了,操作结束后是不是拉高了。我遇到过好几次是因为 CS 引脚配置成了输入,或者被其他外设复用了,导致 CS 一直高电平,MRAM 根本不响应。
第二,检查 SPI 模式。MR25H40CDF 支持模式 0 和模式 3,如果主控配成了模式 1 或模式 2,数据采样沿不对,读出来就是乱的。用逻辑分析仪抓 SCK 和 MOSI,对照数据手册的时序图,看数据在哪个沿变化、哪个沿采样。
第三,确认地址对齐。MRAM 是按字节寻址的,地址发错一个字节,读出来的数据就完全不对。我习惯在发送地址后加一个断点,用调试器看发送的地址值是否正确。
第四,检查电源和地。MRAM 的 VDD 如果低于 2.7V,可能工作不正常。用万用表量一下芯片引脚上的电压,别只看电源模块输出,要量到芯片引脚,排除走线压降。
第五,怀疑芯片本身。如果以上都排除了,换一片 MRAM 试试。虽然 MRAM 抗静电能力不错,但焊接温度过高或者静电击穿还是有可能损坏芯片的。
5.2 提升可靠性的几个实操技巧
第一个技巧是加软件 CRC 校验。虽然 MRAM 本身很可靠,但 SPI 通信在工业现场可能受干扰,加一个 CRC16 校验,写入时计算并存储,读取时校验,发现错误就重读或者报警。这个开销很小,但能大幅提升数据可信度。
第二个技巧是双备份存储。关键数据存两份,地址隔开,读取时两份都读出来对比,不一致就根据时间戳或者版本号选择较新的。MRAM 容量大,4Mbit 存两份关键数据绰绰有余。
第三个技巧是上电自检。设备上电后,先往测试地址写一个已知模式,读回来对比,确认 MRAM 工作正常后再开始正式的数据记录。这个自检流程能提前发现硬件故障,避免带着问题运行。
第四个技巧是注意写保护引脚的处理。如果 WP 引脚悬空,可能受干扰导致误写。我一般直接上拉到 VDD,如果确实需要硬件写保护,就用主控的一个 GPIO 控制,平时拉高,需要保护时拉低。
第五个技巧是 SPI 时钟线的串联电阻。在 SCK 线上串一个 22 欧姆到 33 欧姆的电阻,可以抑制过冲和振铃,改善信号质量。这个电阻值不是固定的,跟走线长度和负载电容有关,可以用示波器看波形,调到过冲最小为止。
6. 方案扩展与个人体会
这套 MRAM 加 PIC18 的方案,除了基本的读写,还能玩出不少花样。比如把 MRAM 当成一个环形缓冲区,用两个指针管理读写位置,实现无限循环记录,永远不覆盖最新数据。或者把 MRAM 分成多个扇区,每个扇区存不同类别的数据,用文件系统的方式管理,方便上位机读取解析。再或者结合 PIC18 的 EEPROM 存配置参数,MRAM 存采集数据,分工明确,各司其职。
我在实际项目中还试过用 MRAM 存固件升级包。PIC18 的 Flash 自编程功能配合 MRAM 的快速写入,可以实现双区固件升级,升级失败还能回滚。这个用法比较进阶,但对工业设备来说很实用,现场升级不用拆机,通过通信接口就能完成。
最后分享一个小技巧:MR25H40CDF 的 SOIC-8 封装在手工焊接时,引脚间距是 1.27mm,不算太难焊,但要注意别把锡桥连到相邻引脚。我一般用刀头烙铁,先固定对角两个引脚,再逐个焊剩下的,最后用吸锡带清理多余的锡。焊完后用放大镜检查一遍,确认没有虚焊和短路再上电。这个芯片不贵,但焊坏了换起来麻烦,一次焊好最省事。