ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

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

PIC18F4610 与 MRAM 工业存储方案:SPI 驱动与掉电保护实战 1. 项目缘起与方案选型1.1 为什么要在工业场景里折腾 MRAM 和 PIC 单片机工业现场的数据存储有个很尴尬的夹缝地带用 EEPROM 吧写入速度慢得让人抓狂擦写次数也就百万次级别高频记录日志的场合撑不了多久用 SRAM 加后备电池吧电池在高温高湿环境下三五年就鼓包漏液维护成本高得离谱用 Flash 吧写之前得先擦一整块掉电瞬间还可能把整块数据搞成半吊子状态。我前几年做一个电力监测终端项目现场环境温度夏天能到 70 度客户要求每 200 毫秒记录一次关键参数掉电不能丢数据这个需求直接把 EEPROM 和 SRAM 方案都毙掉了。后来接触到MRAM磁性随机存储器这东西的特性正好卡在这个缝隙里。以MR25H40CDF为例它是 4Mbit512K×8的 SPI 接口磁存储器写入不需要擦除、字节级随机写入、写入速度可以跑到 SPI 总线的极限、理论上擦写次数接近无限、数据保持时间超过 20 年而且工作温度范围覆盖工业级甚至车规级。这些特性组合起来对于工业数据记录、参数存储、掉电保护这类场景几乎是量身定做的。主控这边选PIC18F4610原因也很实在。它是 Microchip 经典的 8 位增强型单片机64 引脚自带 MSSP 模块可以硬件驱动 SPI程序存储器 64KBRAM 接近 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 时钟最高只能到 10MHzFosc/4所以实际跑 10MHz 是安全的我实测下来在 10MHz 下读写都稳。数据组织上我把 512KB 的空间做了分区前 4KB 存设备配置参数和校准系数中间 500KB 做循环日志区最后 8KB 做掉电保护缓冲区。循环日志区用写指针加序号的方式管理每次写入递增序号写满一圈后覆盖最旧的数据。掉电保护缓冲区用来在检测到电源异常时把 RAM 里还没落盘的关键数据紧急写进去。提示MRAM 虽然写入不需要擦除但 SPI 通信本身是有时序要求的CS 拉低到第一个时钟沿之间需要满足 tSLCH 时间这个参数在数据手册里写的是最小 5ns实际用 10MHz 时钟时基本不用额外延时但如果你的 SPI 时钟跑到 20MHz 以上就要认真算一下了。2. 硬件连接与底层驱动细节2.1 引脚连接与硬件设计要点PIC18F4610 的 MSSP 模块引脚是固定的RC3 是 SCKRC4 是 SDI主机输入RC5 是 SDO主机输出。CS 我用了 RC2 做软件片选因为硬件片选在 PIC18 上配置起来反而麻烦而且软件片选灵活可以在两次操作之间精确控制 CS 的拉高拉低。MR25H40CDF 这边VDD 接 3.3VVSS 接地SCK、SI、SO 分别对接 PIC 的 SCK、SDO、SDICS 接 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 要求空闲时为低电平所以 CKP0SSPSTAT 的 CKE 位决定数据在时钟的哪个边沿采样MRAM 支持模式 0 和模式 3我用模式 0即 CKE0数据在时钟上升沿采样下降沿变化。时钟频率计算PIC18F4610 用 40MHz 晶振时Fosc/4 10MHzSPI 时钟周期 100ns。MRAM 的时序要求里SCK 高电平时间和低电平时间最小都是 10ns10MHz 下高电平 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/4CKP0 SSPSTAT 0x00; // CKE0模式 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 的命令集很简洁常用的就几条WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据。注意 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 是 WIPWrite In ProgressMRAM 写入速度极快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.0VPIC 最低工作电压通常有几十毫秒的余量时间完全够。但这里有个坑电压监测中断的响应时间。如果 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。如果你写入的地址超过 0x7FFFFMRAM 会回卷到 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 可以到 16MHzMRAM 支持 40MHz瓶颈在 PIC 这边。二是用 DMA但 PIC18 没有 DMA只能靠中断优化。三是减少 CS 操作次数比如连续写入时不要每次都发 WRENMRAM 的写使能状态在一次 CS 周期内保持有效可以在一次 CS 拉低期间完成 WREN 和多个 WRITE 命令但这样掉电风险会增加需要权衡。我个人在实际操作中的体会是工业场景下稳定性永远优先于速度。10MHz 的 SPI 时钟、2.5 微秒的单字节写入、加上掉电保护和双备份策略这套组合已经能覆盖绝大多数工业数据存储需求。与其花时间把速度从 10MHz 提到 16MHz不如把精力放在异常处理和恢复逻辑上后者才是现场真正会出问题的地方。
返回列表