拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

PIC18F4610 与 MRAM 工业存储方案:SPI 驱动与掉电保护实战

PIC18F4610 与 MRAM 工业存储方案:SPI 驱动与掉电保护实战

1. 项目缘起与方案选型

1.1 为什么要在工业场景里折腾 MRAM 和 PIC 单片机

工业现场的数据存储有个很尴尬的夹缝地带:用 EEPROM 吧,写入速度慢得让人抓狂,擦写次数也就百万次级别,高频记录日志的场合撑不了多久;用 SRAM 加后备电池吧,电池在高温高湿环境下三五年就鼓包漏液,维护成本高得离谱;用 Flash 吧,写之前得先擦一整块,掉电瞬间还可能把整块数据搞成半吊子状态。我前几年做一个电力监测终端项目,现场环境温度夏天能到 70 度,客户要求每 200 毫秒记录一次关键参数,掉电不能丢数据,这个需求直接把 EEPROM 和 SRAM 方案都毙掉了。

后来接触到MRAM(磁性随机存储器),这东西的特性正好卡在这个缝隙里。以MR25H40CDF为例,它是 4Mbit(512K×8)的 SPI 接口磁存储器,写入不需要擦除、字节级随机写入、写入速度可以跑到 SPI 总线的极限、理论上擦写次数接近无限、数据保持时间超过 20 年,而且工作温度范围覆盖工业级甚至车规级。这些特性组合起来,对于工业数据记录、参数存储、掉电保护这类场景几乎是量身定做的。

主控这边选PIC18F4610,原因也很实在。它是 Microchip 经典的 8 位增强型单片机,64 引脚,自带 MSSP 模块可以硬件驱动 SPI,程序存储器 64KB,RAM 接近 4KB,片上还有 EEPROM。在工业仪表、传感器节点这类对成本敏感、对可靠性要求高、又不需要跑操作系统的场合,PIC18 系列是很多老工程师的默认选择。它的开发工具链成熟,编译器稳定,资料多,出了问题好查。

把这两个东西凑到一起,核心要解决的问题就是:在资源受限的 8 位平台上,用 SPI 总线可靠地完成 MRAM 的读写,并且保证掉电、干扰、时序异常等工业现场常见状况下数据不丢不错。这篇文章就把我实际做这个方案时踩过的坑、验证过的参数、写过的驱动代码,完整地摊开讲一遍。

1.2 方案整体架构与数据流设计

整个系统的数据流其实不复杂,但每一层都有讲究。PIC18F4610 作为 SPI 主机,MR25H40CDF 作为从机,中间四根线:SCK、SDI、SDO、CS。PIC18 的 MSSP 模块配置成 SPI 主模式,时钟极性、相位要跟 MRAM 的时序要求对齐。MRAM 这边支持最高 40MHz 的 SPI 时钟,但 PIC18F4610 在 40MHz 晶振下 SPI 时钟最高只能到 10MHz(Fosc/4),所以实际跑 10MHz 是安全的,我实测下来在 10MHz 下读写都稳。

数据组织上,我把 512KB 的空间做了分区:前 4KB 存设备配置参数和校准系数,中间 500KB 做循环日志区,最后 8KB 做掉电保护缓冲区。循环日志区用写指针加序号的方式管理,每次写入递增序号,写满一圈后覆盖最旧的数据。掉电保护缓冲区用来在检测到电源异常时,把 RAM 里还没落盘的关键数据紧急写进去。

提示:MRAM 虽然写入不需要擦除,但 SPI 通信本身是有时序要求的,CS 拉低到第一个时钟沿之间需要满足 tSLCH 时间,这个参数在数据手册里写的是最小 5ns,实际用 10MHz 时钟时基本不用额外延时,但如果你的 SPI 时钟跑到 20MHz 以上,就要认真算一下了。

2. 硬件连接与底层驱动细节

2.1 引脚连接与硬件设计要点

PIC18F4610 的 MSSP 模块引脚是固定的:RC3 是 SCK,RC4 是 SDI(主机输入),RC5 是 SDO(主机输出)。CS 我用了 RC2 做软件片选,因为硬件片选在 PIC18 上配置起来反而麻烦,而且软件片选灵活,可以在两次操作之间精确控制 CS 的拉高拉低。MR25H40CDF 这边,VDD 接 3.3V,VSS 接地,SCK、SI、SO 分别对接 PIC 的 SCK、SDO、SDI,CS 接 RC2。注意 MRAM 的 SI 是数据输入,对应主机的 SDO,别接反了,我见过有人把 SI 和 SO 接反,读出来全是 0xFF,查了半天。

电源去耦这块,MRAM 的 VDD 引脚旁边一定要放一个 0.1uF 的陶瓷电容,越近越好。工业现场电源纹波大,MRAM 在写入瞬间电流会有波动,去耦不好会导致写入数据出错。我在一个项目里因为去耦电容离得太远,批量生产后有 3% 的板子出现偶发写入错误,后来把电容挪到引脚 2mm 以内就再没出现过。

PCB 走线上,SPI 的四根线尽量等长,尤其是 SCK 和 SDO,如果走线长度超过 10cm,建议在 SCK 上串一个 22 欧姆的电阻做阻抗匹配,减少过冲和振铃。CS 线如果走线长,也要注意,因为 CS 的边沿如果太缓,MRAM 可能识别不到有效的片选信号。

2.2 SPI 初始化配置与参数计算

PIC18F4610 的 MSSP 配置寄存器是 SSPCON1 和 SSPSTAT。SPI 主模式的关键配置位:SSPCON1 的 SSPM3:SSPM0 设为 0000 表示主模式,时钟由 Fosc/4 决定;CKP 位决定空闲时钟电平,MRAM 要求空闲时为低电平,所以 CKP=0;SSPSTAT 的 CKE 位决定数据在时钟的哪个边沿采样,MRAM 支持模式 0 和模式 3,我用模式 0,即 CKE=0,数据在时钟上升沿采样,下降沿变化。

时钟频率计算:PIC18F4610 用 40MHz 晶振时,Fosc/4 = 10MHz,SPI 时钟周期 100ns。MRAM 的时序要求里,SCK 高电平时间和低电平时间最小都是 10ns,10MHz 下高电平 50ns、低电平 50ns,余量充足。如果你用 20MHz 晶振,Fosc/4 = 5MHz,更慢但更稳。我建议工业场景不要追求极限速度,10MHz 已经足够,稳定性优先。

初始化代码大概长这样:

void SPI_Init(void) { TRISC3 = 0; // SCK 输出 TRISC5 = 0; // SDO 输出 TRISC4 = 1; // SDI 输入 TRISC2 = 0; // CS 输出 CS = 1; // 初始拉高 SSPCON1 = 0x00; // 主模式,时钟 Fosc/4,CKP=0 SSPSTAT = 0x00; // CKE=0,模式 0 SSPEN = 1; // 使能 MSSP }

读写一个字节的函数:

unsigned char SPI_Exchange(unsigned char data) { SSPBUF = data; while(!SSPIF); // 等待传输完成 SSPIF = 0; return SSPBUF; }

注意:SSPIF 标志位在写入 SSPBUF 后不会立即置位,要等 8 个时钟周期传输完成才置位。如果你在中断里调用 SPI 函数,要确保没有其他中断打断这个等待循环,否则可能死等。我一般会在等待循环里加一个超时计数器,防止硬件故障时卡死。

3. MRAM 读写操作完整实现

3.1 命令集与操作时序解析

MR25H40CDF 的命令集很简洁,常用的就几条:WREN(0x06)写使能,WRDI(0x04)写禁止,RDSR(0x05)读状态寄存器,WRSR(0x01)写状态寄存器,READ(0x03)读数据,WRITE(0x02)写数据。注意 MRAM 没有擦除命令,这是它跟 Flash 最大的区别,写入就是直接覆盖。

写操作的完整时序:拉低 CS,发送 WREN 命令,拉高 CS;再拉低 CS,发送 WRITE 命令,然后发送 3 字节地址(24 位地址,因为 512KB 需要 19 位地址,但 MRAM 用 3 字节地址格式),接着发送要写入的数据字节,可以连续发送多个字节,地址自动递增;最后拉高 CS 结束。这里有个关键点:WREN 和 WRITE 之间必须拉高 CS,让 MRAM 内部锁存写使能状态,如果 WREN 之后不拉高 CS 直接发 WRITE,写使能不会生效,数据写不进去。

读操作简单一些:拉低 CS,发送 READ 命令,发送 3 字节地址,然后连续读取数据字节,地址自动递增,读完拉高 CS。

状态寄存器的 bit0 是 WIP(Write In Progress),MRAM 写入速度极快,WIP 位通常几个微秒就清零了,但为了保险,写完关键数据后我还是会轮询一下 WIP,确认写入完成再继续。

3.2 字节写入与页写入的取舍

MRAM 支持连续写入,没有页边界限制,你可以从任意地址开始连续写任意长度。这跟 Flash 完全不同,Flash 跨页写会回卷到页首,MRAM 不会。所以写日志的时候,我可以放心地一次写几十个字节,不用管地址对齐。

但连续写入有个隐患:如果写入过程中电源掉了,已经写入的部分是有效的,没写完的部分是旧数据,这会导致数据不一致。我的做法是,对于关键数据,采用“先写数据区,再写标志位”的两阶段提交。比如写一条日志,先在日志区写入数据,然后在标志区写入一个递增的序号,读取时检查序号连续性,如果发现序号跳变,就知道中间有掉电,可以丢弃不完整的记录。

字节写入函数:

void MRAM_WriteByte(unsigned long addr, unsigned char data) { CS = 0; SPI_Exchange(0x06); // WREN CS = 1; CS = 0; SPI_Exchange(0x02); // WRITE SPI_Exchange((addr >> 16) & 0xFF); SPI_Exchange((addr >> 8) & 0xFF); SPI_Exchange(addr & 0xFF); SPI_Exchange(data); CS = 1; // 可选:轮询 WIP // while(MRAM_ReadStatus() & 0x01); }

连续写入函数就是把 SPI_Exchange(data) 放进循环里,地址自动递增由 MRAM 内部处理,我只需要保证发送的字节数不超过剩余空间。

3.3 读取操作的实现与校验

读取操作看似简单,但有个细节容易忽略:MRAM 在 CS 拉高后,SO 引脚会进入高阻态,如果此时你的 SPI 总线上的其他从机也在驱动 SO,就会冲突。所以 CS 拉高后,最好把 PIC 的 SDI 引脚配置成输入并确保没有内部上拉干扰。我一般会在 CS 拉高后加一个 NOP 延时,等总线稳定后再进行下一次操作。

读取函数:

unsigned char MRAM_ReadByte(unsigned long addr) { unsigned char data; CS = 0; SPI_Exchange(0x03); // READ SPI_Exchange((addr >> 16) & 0xFF); SPI_Exchange((addr >> 8) & 0xFF); SPI_Exchange(addr & 0xFF); data = SPI_Exchange(0x00); // 发送哑字节,读取数据 CS = 1; return data; }

读回来之后,我习惯做一个简单的校验:对于配置参数区,我会在写入时同时写入原数据和反码,读取时校验两者是否互补,如果不互补就说明数据损坏,自动加载默认参数。这个技巧在工业现场特别管用,因为强电磁干扰偶尔会翻转某一位,反码校验能立刻发现。

4. 工业场景下的可靠性设计与问题排查

4.1 掉电保护与数据完整性策略

工业现场掉电是常态,不是异常。我的做法是在电源输入端加一个电压监测芯片,比如 MCP130,当电压低于 4.5V 时产生中断,PIC 在中断里立刻把 RAM 中的关键数据写入 MRAM 的掉电保护缓冲区。MRAM 的写入速度极快,一个字节的写入加上 SPI 开销大概 2 微秒,写 256 字节也就 0.5 毫秒,而电源从 4.5V 掉到 3.0V(PIC 最低工作电压)通常有几十毫秒的余量,时间完全够。

但这里有个坑:电压监测中断的响应时间。如果 PIC 正在执行一个长指令或者中断被关闭,响应会延迟。我实测下来,PIC18F4610 在 40MHz 下,中断响应最坏情况大概 10 个指令周期,也就是 1 微秒左右,可以接受。但如果你在中断里还做了其他耗时操作,就要重新评估了。

另一个策略是“双备份写入”:关键数据在 MRAM 里存两份,地址 A 和地址 B,每次写入时先写 A 再写 B,读取时如果 A 和 B 不一致,以 B 为准(因为 B 是后写的)。这个策略牺牲了一半空间,但换来了极高的可靠性,我在一个不能停机的化工监测项目里用了这个方案,运行三年没有出现过数据丢失。

4.2 常见问题速查与排查技巧

下面这张表是我在实际项目中遇到过的典型问题,以及对应的排查思路和解决方法:

现象可能原因排查方法解决方法
读出来全是 0xFFSI/SO 接反,或 CS 未拉低用示波器看 CS 和 SCK 波形检查接线,确认 CS 在操作期间为低
写入后读出来是旧数据WREN 后未拉高 CS用逻辑分析仪抓 SPI 时序确保 WREN 和 WRITE 之间 CS 拉高
偶发数据位翻转电源去耦不足,或 SCK 过冲示波器看电源纹波和 SCK 波形加去耦电容,SCK 串匹配电阻
高速时钟下读写错误SPI 时钟超过 MRAM 时序余量降低时钟频率测试降到 10MHz 或更低,或调整 CKP/CKE
多从机时数据冲突其他从机 SO 未释放逐个断开从机测试确保同一时刻只有一个从机 CS 有效
长时间运行后写入失败MRAM 进入未知状态读状态寄存器发送 WRDI 后重新初始化

提示:逻辑分析仪是调 SPI 的必备工具,没有之一。我用的是一款 8 通道 24MHz 的入门款,抓 SPI 时序完全够用。抓波形时重点看 CS 的边沿、SCK 的极性和相位、数据在哪个边沿变化,这三个对上了,通信基本就通了。

4.3 实操心得与避坑经验

第一个心得:不要相信“理论上没问题”。MRAM 的数据手册说写入不需要延时,但我在一个项目里发现,如果连续写入之间不插入任何延时,在 10MHz 时钟下偶尔会出现写入失败。后来我在每次写入后加了一个 1 微秒的短延时,问题就消失了。这个延时不是 MRAM 要求的,而是 PIC 的 SPI 模块在连续操作之间需要一点恢复时间。数据手册不会告诉你这个,只有实际跑起来才知道。

第二个心得:地址边界要自己管。MRAM 的地址是 24 位的,但实际容量只有 512KB,也就是 0x00000 到 0x7FFFF。如果你写入的地址超过 0x7FFFF,MRAM 会回卷到 0x00000,不会报错。我在写循环日志时,写指针递增到 0x80000 后自动回卷到 0,这个逻辑要自己写,不能依赖硬件。

第三个心得:初始化时先读一次设备 ID。MR25H40CDF 没有标准的电子签名命令,但你可以通过读状态寄存器确认通信正常。我一般在上电初始化时,先读状态寄存器,如果读回来是 0x00 或 0xFF,说明通信有问题,直接进入错误处理,避免后续操作全部失败。

第四个心得:写保护要慎用。MRAM 的状态寄存器里有块保护位,可以保护部分地址不被写入。我一开始为了安全,把配置参数区设了写保护,结果后来需要在线修改参数时忘了先解除保护,调了半天才发现是保护位在作怪。后来我的做法是:只在最终产品出厂时设置写保护,开发阶段和需要在线修改的场合不设。

5. 性能实测与场景扩展

5.1 读写速度实测数据

我在 10MHz SPI 时钟下做了实测,结果如下:

操作耗时说明
单字节写入约 2.5 微秒含 WREN 和 WRITE 两次 CS 操作
单字节读取约 1.8 微秒含命令和地址发送
256 字节连续写入约 220 微秒平均每字节 0.86 微秒
256 字节连续读取约 210 微秒平均每字节 0.82 微秒
状态寄存器读取约 1.2 微秒仅命令和读数据

这个速度对于工业数据记录来说绰绰有余。按每 200 毫秒记录一次、每次 64 字节计算,写入耗时约 55 微秒,占空比不到 0.03%,对主循环几乎没有影响。

5.2 扩展应用场景

这套方案不只适用于数据记录。我在另一个项目里用它做参数存储:设备的校准系数、PID 参数、通信地址都存在 MRAM 里,修改后立即生效,掉电不丢。因为 MRAM 写入不需要擦除,修改一个参数只需要几微秒,比 EEPROM 快了几个数量级,用户体验完全不一样。

还有一个场景是高速缓存:PIC 采集的传感器数据先存在 MRAM 里,等上位机来读取时再批量上传。MRAM 的写入速度足够跟上 1kHz 的采样率,而 EEPROM 根本做不到。

甚至可以用 MRAM 做代码补丁存储:PIC18F4610 的程序存储器是 Flash,擦写次数有限,如果需要在现场升级部分代码,可以把补丁存在 MRAM 里,启动时加载到 RAM 执行。这个用法比较高级,但对需要频繁更新的场合很有价值。

5.3 后续优化方向

如果你想把性能再压榨一下,有几个方向可以试。一是提高 SPI 时钟,PIC18F4610 在 64MHz 晶振下(需要外部时钟输入)SPI 可以到 16MHz,MRAM 支持 40MHz,瓶颈在 PIC 这边。二是用 DMA,但 PIC18 没有 DMA,只能靠中断优化。三是减少 CS 操作次数,比如连续写入时不要每次都发 WREN,MRAM 的写使能状态在一次 CS 周期内保持有效,可以在一次 CS 拉低期间完成 WREN 和多个 WRITE 命令,但这样掉电风险会增加,需要权衡。

我个人在实际操作中的体会是,工业场景下稳定性永远优先于速度。10MHz 的 SPI 时钟、2.5 微秒的单字节写入、加上掉电保护和双备份策略,这套组合已经能覆盖绝大多数工业数据存储需求。与其花时间把速度从 10MHz 提到 16MHz,不如把精力放在异常处理和恢复逻辑上,后者才是现场真正会出问题的地方。

返回列表