ARTICLE DETAIL

资讯详情

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

MRAM与PIC18F46K20工业数据采集方案:SPI驱动与掉电保护实战

MRAM与PIC18F46K20工业数据采集方案:SPI驱动与掉电保护实战 1. 项目缘起与方案选型为什么是 MRAM 加 PIC18F46K201.1 一个真实的需求场景前阵子接了个工业数据采集终端的活儿客户的要求很朴素设备装在配电柜里每天要记录几百条运行参数断电不能丢现场电磁环境还特别脏。一开始我下意识想上 SPI Flash比如 W25Q 系列便宜、容量大、资料多几乎是嵌入式存储的默认答案。但仔细一算账问题就来了。这个终端每 200ms 就要写一次数据一天下来是 43 万次写操作。SPI Flash 的擦写寿命通常在 10 万次左右就算做了磨损均衡一块 8MB 的 Flash 也撑不了太久而且 Flash 写入前必须先擦除整个扇区写入延迟动辄几毫秒到几十毫秒在掉电瞬间极容易丢数据。客户还要求“写入即持久”不能有缓存窗口。这时候 Flash 的短板就暴露得很彻底了。于是我把目光转向了 MRAM。MR25H40CDF 是 Everspin 家的 4Mbit SPI 磁阻随机存储器它的核心卖点正好戳中这个场景写入不需要擦除、字节级随机写、写入延迟纳秒级、擦写寿命理论上无限官方标称 10^15 次以上、断电即保存。说白了它用起来像 SRAM但断电不丢数据这就很香了。主控这边选 PIC18F46K20理由也很实在。这颗 8 位 MCU 是 Microchip 的经典款64KB Flash、3936 字节 RAM、64KB 寻址空间自带 MSSP 模块可以硬件跑 SPI工作温度覆盖工业级 -40 到 85 摄氏度抗干扰能力在 8 位机里属于第一梯队。关键是它便宜、好买、开发工具链成熟用 XC8 编译器加 MPLAB X 就能干活不需要上 Linux 那套重装备。对于这种数据量不大、实时性要求高、成本敏感的工业小终端PIC18F46K20 配 MR25H40CDF 是一对很务实的组合。1.2 为什么不用 EEPROM 或者 FRAM有朋友会问那为啥不用 EEPROM 或者 FRAM我确实都评估过。EEPROM 的问题是写入速度太慢字节写典型 5ms页写也要 3ms 左右而且寿命同样只有百万次量级高频写入场景下依然会磨损。FRAM 倒是快、寿命也高但容量普遍偏小、价格偏高而且很多型号的 SPI 时序在高频下对 PCB 布线要求更苛刻。MRAM 在速度、寿命、容量、成本之间找到了一个比较舒服的平衡点尤其是 4Mbit 这个容量对于存参数、日志、配置表来说刚刚好。还有一个容易被忽略的点MR25H40CDF 的 SPI 接口支持最高 40MHz 时钟而 PIC18F46K20 的 MSSP 模块在 64MHz 系统时钟下最高能跑到 16MHz SPI 速率。虽然跑不满 MRAM 的极限但 16MHz 已经足够让读写操作在微秒级完成对实时性帮助巨大。这个速率匹配关系在选型时就要算清楚不然买回来发现 MCU 拖后腿就尴尬了。1.3 整体架构长什么样整个方案的结构其实很清晰PIC18F46K20 作为主控通过 MSSP 模块的 SPI 接口连接 MR25H40CDF另外用几个普通 GPIO 做片选和写保护控制。MRAM 负责存储掉电不能丢的关键数据MCU 内部的 3936 字节 RAM 作为运行时缓存Flash 用来放程序代码和常量表。数据流是这样的传感器采集到数据后先写进 MCU 的 RAM 缓冲区攒够一批或者到达定时点再通过 SPI 批量写入 MRAM。读取的时候反过来从 MRAM 读回 RAM 再处理。这个架构的好处是分工明确MRAM 只干它最擅长的事——高频、可靠、掉电安全的存储不承担程序存储的职责。PIC18F46K20 的 64KB Flash 放代码绰绰有余不需要外部程序存储器省了一颗芯片和一堆布线。对于工业现场这种“稳定压倒一切”的场景越简单的架构越不容易出问题。2. 硬件连接与 SPI 时序细节别在布线上栽跟头2.1 引脚连接与片选策略MR25H40CDF 是标准的 8 脚 SOIC 封装引脚定义很清爽VDD、VSS、SCK、SI、SO、CS、WP、HOLD。和 PIC18F46K20 连接时SCK 接 RC3SI 接 RC5SO 接 RC4这三个是 MSSP 模块的固定 SPI 引脚不能随便换。CS 片选我接在 RA5 上用普通 GPIO 控制。WP 写保护和 HOLD 保持引脚我接在 RA4 和 RA3 上也当普通 IO 用。这里有个细节值得说MR25H40CDF 的 CS 必须由主机控制而且每次 SPI 事务开始前拉低、结束后拉高。有些朋友图省事想用硬件片选自动管理但 PIC18F46K20 的 MSSP 模块在 SPI 主模式下并不自动控制 CS还是得手动操作 GPIO。手动片选虽然多几行代码但控制更灵活尤其是需要在一次 CS 有效期内连续发送命令加地址加数据的时候手动模式反而更顺手。WP 引脚我建议也接上 GPIO 控制不要直接接地或者接 VDD。为什么因为 MRAM 的写保护和 Flash 不一样它是通过状态寄存器控制的WP 引脚只是硬件层面的额外保护。在调试阶段你可能需要临时解除保护来写数据如果 WP 直接焊死接地那就只能靠软件改状态寄存器少了一层保险。接 GPIO 的话软件和硬件双重控制调试和量产都方便。2.2 SPI 模式选择Mode 0 还是 Mode 3MR25H40CDF 支持 SPI Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1。这两个模式的区别在于时钟空闲电平和数据采样边沿。Mode 0 下 SCK 空闲为低数据在上升沿采样Mode 3 下 SCK 空闲为高数据在下降沿采样。我实测下来PIC18F46K20 的 MSSP 模块在 Mode 0 下最稳。配置寄存器 SSPCON1 的 SSPM 位设为 0b0010 就是 SPI 主模式CKP 位控制时钟极性设 0 就是空闲低电平。配合 SSPSTAT 的 CKE 位设 0表示数据在时钟上升沿从主动输出、下降沿输入采样正好对应 Mode 0。这个配置在 16MHz 时钟下跑了几万次读写没出过错。注意如果你用 Mode 3CKP 要设 1CKE 也要相应调整。但 Mode 3 在高频下对 PCB 走线的等长要求更高因为下降沿采样对时钟抖动更敏感。工业现场电磁干扰大能选 Mode 0 就别折腾 Mode 3。2.3 PCB 布线的几个坑SPI 总线在低速下随便拉线都能跑但到了 16MHz 就不是闹着玩的了。我踩过的坑主要有三个。第一个是走线太长。第一版板子为了布局好看把 MRAM 放在了离 MCU 大概 8cm 的位置SCK 走线绕了个弯。结果 16MHz 下读写偶尔出错示波器一看SCK 上升沿有明显的振铃和过冲。后来把 MRAM 挪到离 MCU 3cm 以内走线尽量短直问题就消失了。经验是 SPI 时钟线最好控制在 5cm 以内超过这个距离就要考虑加串联匹配电阻。第二个是地线处理。MRAM 的 VSS 和 MCU 的 VSS 必须用粗短线连到同一个地平面不能各自走细线再汇合。我见过一个案例MRAM 的地线走了 0.2mm 的细线结果写入电流一大地电位就跳数据偶尔写错。换成 0.5mm 以上的铺铜连接后彻底解决。第三个是去耦电容。MR25H40CDF 的 VDD 引脚旁边必须放一个 0.1uF 的陶瓷电容越近越好最好在 2mm 以内。这个电容不是摆设MRAM 写入瞬间的电流尖峰全靠它来扛。我试过省掉这个电容结果就是随机写入失败查了半天才找到原因。2.4 上电时序与初始化MR25H40CDF 的上电要求不复杂VDD 上升到 2.7V 以上后等待 1ms 左右就可以发命令了。但 PIC18F46K20 的上电复位时间可能更长所以实际代码里我在初始化 SPI 之前先加了个 10ms 的延时确保 MRAM 完全就绪。初始化流程是这样的先配置 MCU 的时钟和 IO 方向然后初始化 MSSP 模块为 SPI 主模式设置时钟速率最后拉高 CS 和 WP 引脚让 MRAM 处于待命状态。这里有个顺序问题CS 必须在上电后保持高电平否则 MRAM 可能误进入某种状态。我在代码里把 CS 引脚初始化为输出高电平放在所有初始化之前这样最保险。3. 核心驱动实现从状态寄存器到读写函数3.1 MR25H40CDF 的命令集MR25H40CDF 的命令集很精简常用的就那么几条。读数据是 0x03写数据是 0x02读状态寄存器是 0x05写状态寄存器是 0x01写使能是 0x06写禁止是 0x04。另外还有读标识、写保护相关的一些命令但日常读写用不到。每条命令的格式都是先拉低 CS发送命令字节然后跟地址读写的地址是 24 位因为 4Mbit 等于 512KB需要 19 位地址但 MRAM 用 24 位地址格式最后是数据。读操作时地址发完后 MRAM 会在 SCK 的驱动下逐字节输出数据写操作时地址发完后主机逐字节发送数据。这里要特别注意地址格式。MR25H40CDF 的地址是 24 位的高 5 位是无关位实际有效的是低 19 位。发送的时候要按高字节、中字节、低字节的顺序发。我一开始没注意把地址当 16 位发结果只能访问前 64KB后面的地址全乱套了。后来查数据手册才发现是 24 位地址改过来就正常了。3.2 状态寄存器与写保护MR25H40CDF 的状态寄存器只有 8 位但每一位都有用。Bit 7 是状态寄存器写保护位Bit 6 到 Bit 2 是块保护位Bit 1 是写使能锁存位Bit 0 是就绪位。块保护位可以设置哪些地址区域被保护防止误写。我的做法是上电后先读一次状态寄存器确认就绪位为 1然后根据应用需求设置块保护。比如配置参数区需要频繁写就不保护固件版本区只读就保护起来。设置完块保护后再发写使能命令才能进行写操作。每次写操作前都要发一次写使能写完后写使能自动清除这是 MRAM 的安全机制防止意外写入。提示写使能命令和实际的写操作之间不能插入其他命令否则写使能会失效。我在代码里把这两个操作封装在一个函数里中间不干别的确保万无一失。3.3 底层字节收发函数PIC18F46K20 的 MSSP 模块收发字节很简单往 SSPBUF 寄存器写一个字节然后等 SSPIF 标志位置起再从 SSPBUF 读回来就是收到的字节。但这里有个坑如果只发不收也要读一次 SSPBUF 来清除标志位否则下一次发送会出错。我封装的 SPI 收发函数是这样的先写 SSPBUF然后 while 循环等 SSPIF然后读 SSPBUF 返回。这个函数同时完成了发送和接收对于 MRAM 这种全双工 SPI 设备正好合适。读数据的时候发送的是哑元字节比如 0xFF接收到的才是 MRAM 返回的数据。代码大概长这样unsigned char spi_transfer(unsigned char data) { SSPBUF data; while (!SSPIF); SSPIF 0; return SSPBUF; }这个函数是整个驱动的基础所有 MRAM 操作都建立在它之上。实测在 16MHz SPI 时钟下一次字节传输大概 0.5us效率很高。3.4 页写与连续读的实现MR25H40CDF 支持连续读写没有页边界限制。也就是说你发一个读命令加起始地址然后可以一直读下去地址会自动递增直到 CS 拉高。写操作也一样发一个写命令加起始地址然后连续写多个字节地址自动递增。这个特性非常好用因为不需要像 Flash 那样考虑页对齐和跨页问题。我实现了一个mram_write_buf函数参数是起始地址、数据指针和长度内部就是拉低 CS、发写使能、发写命令、发 24 位地址、循环发数据、拉高 CS。读函数类似只是把发数据换成收数据。这里有个优化技巧如果连续写多个字节可以在一次 CS 有效期内完成不需要每个字节都拉低拉高 CS。这样能显著提高吞吐率。我实测过批量写 256 字节一次 CS 完成比每字节一次 CS 快了将近 3 倍。4. 数据可靠性与掉电保护工业场景的必修课4.1 掉电检测与紧急写入工业现场最怕的就是突然断电。虽然 MRAM 本身写入即持久但如果 MCU 在发送写命令的过程中断电数据可能只写了一半。所以掉电检测电路是必须的。我的做法是在电源输入端加一个电压比较器当 VDD 降到 4.5V 以下时比较器输出一个中断信号给 PIC18F46K20 的 INT 引脚。MCU 收到中断后立即停止正常任务把 RAM 里还没写入 MRAM 的关键数据紧急写进去。由于 MRAM 写入速度极快几百字节的数据在几毫秒内就能写完而电源端的电容能提供足够的维持时间。这个方案的关键是电容容量要算够。假设系统正常电流 20mA掉电后需要维持 10ms电压从 5V 降到 4.0V那么电容 C I × t / ΔV 20mA × 10ms / 1V 200uF。实际选型时我留了一倍余量用了 470uF确保万无一失。4.2 数据校验与恢复光有掉电保护还不够数据写入后还得能验证对错。我在每个数据块后面加了一个 CRC16 校验值读取的时候先算 CRC 再比对不一致就说明数据损坏触发恢复流程。恢复流程是这样的MRAM 里维护两个数据区A 区和 B 区交替写入。每次写入时先写 A 区CRC 校验通过后再写 B 区作为备份。读取时如果 A 区 CRC 错误就自动切到 B 区。如果两个区都错那就只能上报故障了。这个双区备份方案牺牲了一半容量但对于关键参数来说可靠性比容量重要得多。4.3 写入均衡的考虑虽然 MRAM 寿命极高理论上不需要磨损均衡但在极端高频写入的场景下我还是建议做一个简单的地址轮转。比如日志存储不要每次都写同一个地址而是按块轮转写满一圈再从头覆盖。这样能把写入压力分散到整个存储空间进一步延长寿命。实现起来很简单维护一个写指针每次写完递增到达末尾归零。读取的时候按时间戳或者序号来定位。这个逻辑不复杂但能有效避免局部过热导致的早期失效。5. 常见问题与排查实录5.1 读写失败问题速查表现象可能原因排查方法解决方案读回全 0xFFCS 未拉低或时序错误示波器看 CS 和 SCK检查 CS 控制代码确保拉低后才发时钟读回全 0x00SO 线未接或 MRAM 未供电万用表测 VDD 和 SO 通断检查供电和焊接写入后读回不变写使能未发或 WP 保护读状态寄存器确认发写使能命令检查 WP 引脚电平偶尔写入错误SPI 时钟太快或布线差降低时钟测试降速到 8MHz优化布线地址错乱地址位数错误检查地址发送代码确认发送 24 位地址上电后无响应上电延时不够增加延时测试上电后延时 10ms 再初始化5.2 几个我踩过的坑第一个坑是 CS 时序。我一开始用硬件 SPI 的自动 CS 功能结果发现 PIC18F46K20 的 MSSP 在连续传输时 CS 不会自动拉高导致多个命令连在一起被 MRAM 当成一个长命令。后来改成手动 GPIO 控制 CS每个事务前后明确拉低拉高问题解决。第二个坑是写使能丢失。有一次我在发完写使能后中间插了一个读状态寄存器的操作结果写操作失败。查手册才知道写使能命令后必须紧接着写操作中间不能有其他命令。这个细节手册里写得很清楚但容易忽略。第三个坑是电源纹波。工业现场的 24V 转 5V 电源纹波比较大导致 MRAM 偶尔写入错误。后来在 MRAM 的 VDD 引脚旁边加了一个 10uF 的钽电容并联 0.1uF 陶瓷电容纹波从 200mV 降到 50mV 以内问题消失。5.3 调试工具与技巧调试 SPI 设备示波器是必备的。我一般会同时抓 CS、SCK、SI、SO 四根线看时序是否匹配。逻辑分析仪更好用能直接解码 SPI 协议看到发送的命令和地址对不对。如果没有这些设备也可以用 MCU 的串口打印调试信息把读回的状态寄存器值打出来间接判断问题。还有一个技巧先写一个最简单的测试程序只做一件事——读 MRAM 的标识寄存器。MR25H40CDF 有一个读标识命令0x9F会返回厂商 ID 和设备 ID。如果这个能读对说明硬件连接和基本时序没问题再往下查软件逻辑。如果读不对那就是硬件或者底层 SPI 配置的问题。这个分步排查法能省很多时间。6. 性能实测与优化建议6.1 读写速度实测数据我在 16MHz SPI 时钟下做了批量读写测试结果如下操作数据量耗时等效速率单字节写1 字节约 2us500KB/s批量写256 字节约 140us1.8MB/s单字节读1 字节约 1.5us666KB/s批量读256 字节约 135us1.9MB/s批量操作的效率明显高于单字节操作因为省去了 CS 拉低拉高和命令地址的开销。所以在实际应用中尽量攒一批数据再写不要每来一个字节就写一次。6.2 降低功耗的技巧MRAM 的写入电流比读取大典型写入电流 15mA读取只有 5mA。在电池供电的场景下要尽量减少写入次数。我的做法是加大 RAM 缓冲区攒够 512 字节再写一次这样写入次数降到原来的 1/512功耗大幅下降。另外MRAM 在 CS 高电平时进入待机模式电流只有几微安。所以在不操作的时候确保 CS 保持高电平能省不少电。6.3 代码优化建议驱动代码里SPI 收发函数是调用最频繁的优化它能提升整体性能。我试过用汇编重写这个函数把等待 SSPIF 的循环展开速度能提升 20% 左右。不过对于大多数应用C 语言版本已经够用了。另一个优化点是地址计算。如果每次读写都要算 24 位地址开销不小。我建议把常用地址预计算好存在变量里用的时候直接取避免重复计算。7. 这个方案还能怎么扩展这套 MRAM 加 PIC18F46K20 的组合除了存参数和日志还能干不少事。比如做数据黑匣子把设备的运行状态实时记录到 MRAM 里出故障时读出来分析。MRAM 的无限寿命意味着你可以每秒写几十次连续写几年都不用担心磨损。还可以做配置存储把设备的校准参数、网络配置、用户设置都存在 MRAM 里掉电不丢上电即用。相比 EEPROMMRAM 的写入速度快了几个数量级修改配置时用户几乎感觉不到延迟。如果数据量再大一些可以多片 MRAM 级联用不同的 CS 引脚选择。PIC18F46K20 的 IO 足够多挂四五片 MRAM 没问题容量能扩展到 2MB 以上对于大多数工业终端来说绰绰有余。我个人在实际操作中的体会是MRAM 这种器件最大的价值不在于它有多快或者多大而在于它把“非易失”和“随机读写”这两个原本矛盾的属性统一在了一起。用惯了 Flash 的人第一次用 MRAM会有一种“原来存储可以这么简单”的感慨。当然它的价格确实比 Flash 高但在那些写入频繁、掉电不能丢数据、维护成本高的场景下多花的这点钱很快就能从可靠性和维护成本上省回来。选型的时候别只看器件单价要把整个生命周期的成本算进去这样才不会因小失大。
返回列表