ARTICLE DETAIL

资讯详情

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

工业数据存储新思路:MRAM与PIC18F85K22的嵌入式实战

工业数据存储新思路:MRAM与PIC18F85K22的嵌入式实战 做工业控制这几年数据存储一直是个绕不开的话题。设备参数、运行日志、校准系数一旦掉电丢失轻则现场返工重则影响整个产线。最近在一个设备升级项目里我把 MR25H40CDF 这颗 SPI 接口的 MRAM 和 PIC18F85K22 单片机搭在了一起专门用来存工业现场的关键数据读写的体验和可靠性都让我比较满意。这篇就当一次完整复盘把选型、接线、驱动和调试中的坑从头到尾理清楚给同样做嵌入式存储的工程师一个可以直接参考的方案。1. 方案选型为什么把 MR25H40CDF 和 PIC18F85K22 放在一起先聊选型逻辑。很多嵌入式项目里提到非易失存储第一反应还是 EEPROM 或者 SPI Flash。但这两个东西在“频繁写小批量数据”的场景里各有短板。EEPROM 常用的 24Cxx 系列写次数在一百万次左右听起来不少但工业设备如果每秒钟写一次参数一两年就到了极限。SPI Flash 虽然容量大但擦写要按扇区来写一个字节也要先擦除再写块擦除时间几十毫秒对运行节奏敏感的控制系统不太友好。MR25H40CDF 走的是另一条路。它属于磁阻随机存储器也就是 MRAM本质上是把磁阻存储单元直接放进了 SRAM 和存储器的接口中间既有静态 RAM 的读写速度又具备非易失性。具体到这颗芯片容量是 4Mbit也就是 512KByte接口是标准 SPI支持连续读写不需要擦除也不会因为频繁写而磨掉寿命。对于工业应用来说这是非常少见的好脾气存储介质。PIC18F85K22 的选择则更务实。这颗单片机在 Microchip 的 8 位产品线里属于中高端自带硬件 MSSP 模块能当 SPI 主机用还有充足的 GPIO、模拟外设和看门狗一颗芯片就能完成采集、控制、通信和存储管理。更重要的是8 位机在工业现场的抗干扰能力和生态成熟度都很高很多老产线上的设备修修改改用的就是这类片子。把这个单片机和 MRAM 组合在一起整套方案不需要额外引入 MCU 换平台工具链、编程习惯都能直接延续。1.1 MRAM 为什么比 Flash 和 EEPROM 更适合频繁写直接上一张对比表看得更清楚项目MR25H40CDF (MRAM)串行 EEPROMSPI Flash写入前是否需要擦除不需要不需要必须擦除扇区写入寿命近乎无限100 万次级别10 万次级别单字节随机写支持支持通常不支持写后等待时间极短几毫秒到十几毫秒几十毫秒到几百毫秒数据保持约 20 年几十年几十年容量扩展中低容量为主低大我实际使用的时候最明显的感觉就是“不用考虑寿命”。参数保存、故障记录、采集缓冲这些功能以前用 EEPROM 时总得做磨损均衡把写入地址散开还要定期统计写次数换成 MRAM 后我只需要把数据按逻辑地址直接写完全不心疼。再加上写入完成的时间非常短在掉电瞬间抢时间写最后几个标志位比 EEPROM 从容得多。这里还要纠正一个常见误区MRAM 并不是零延迟它依然要遵守 SPI 时序也需要通过状态寄存器判断是否忙。只不过它的忙等待时间通常远小于传统 Flash绝大多数情况下不构成瓶颈。但只要是芯片就有时序要求后面调试部分我会重点说。1.2 PIC18F85K22 在这套方案里的定位PIC18F85K22 有多个串行通信模块MSSP1 用来给 MRAM 做 SPI 主机刚刚好。把 CS 片选线放在普通 GPIO 上SCK、SDO、SDI 交给硬件 SPI 模块CPU 只需要往缓冲区里填数据剩下的时钟和数据移位由外设自己完成效率很高。工业设备上还有一个实际问题旧项目散落着大量基于 PIC 的代码工程师对寄存器配置很熟。相比临时换 STM32 或者加一个 Linux 板子PIC18F85K22 的学习成本和迁移成本低得多。在嵌入式领域能用成熟且可控的器件解决就没必要为了炫技引入不必要的复杂度。这套方案非常稳不是那种追求极致性能的玩法而是求可靠、求好维护的工程做法。2. 硬件连接把两个芯片接到一起芯片选好之后下一步就是接线。MR25H40CDF 是 8 脚封装的 SPI 器件PIC18F85K22 这边用硬件 SPI 模块二者接起来并不复杂但有几个引脚细节不能马虎。2.1 MR25H40CDF 关键引脚说明MR25H40CDF 的典型引脚功能如下引脚名称功能接线注意事项1CS片选输入低有效接单片机 GPIO空闲拉高2SCKSPI 时钟输入接单片机 SCK13SI串行数据输入接单片机 SDO14SO串行数据输出接单片机 SDI15WP写保护低有效不用时直接上拉到 VDD6HOLD保持输入低有效不用时直接上拉到 VDD7VDD电源正极3.3V加去耦电容8VSS地接系统地WP 和 HOLD 这两个引脚容易被人忽略。WP 拉低后状态寄存器里的保护位会被锁住写指令不生效HOLD 拉低后芯片会暂停 SPI 通信时钟线乱跳也不会执行后续命令。正常工装上这两个脚千万别悬空必须用电阻上拉到 VDD不然上电瞬间的毛刺可能让芯片进入错误状态。在选择 SPI 工作模式时MR25H40CDF 支持 Mode 0 和 Mode 3也就是时钟极性低或者高都行。我习惯用 Mode 0因为大多数单片机和逻辑分析仪默认 Mode 0调试起来不容易乱。后面代码里的配置也是按 Mode 0 来写的。2.2 与 PIC18F85K22 的 SPI 连接我这里以 PIC18F85K22 的 MSSP1 模块为例。实际引脚对应关系是单片机 SCK1 接 MRAM 的 SCK单片机 SDO1 接 MRAM 的 SI单片机 SDI1 接 MRAM 的 SO。片选我用的是 RD0纯粹是照顾布线方便项目里你可以换成任意不影响其他外设的 IO。接线表如下单片机端引脚MR25H40CDF 端引脚MSSP1 SCK1具体见数据手册SCK2MSSP1 SDO1具体见数据手册SI3MSSP1 SDI1具体见数据手册SO4GPIO RD040/28 等具体封装引脚CS1VDD 3.3V-VDD7GND-VSS83.3V 上拉-WP53.3V 上拉-HOLD6注意PIC18F85K22 的工作电压范围虽然覆盖 5V但 MR25H40CDF 的工作电压上限就是 3.6V 左右所以整套系统必须统一跑在 3.3V。如果项目里还有其他 5V 器件也要先把 SPI 信号做电平转换再接过来不要直接直连。我见过不止一次因为 5V 信号怼到 MRAM 上导致读回来全是乱码的情况。2.3 电源、去耦和上拉的细节MRAM 虽然叫 RAM但它也怕电源噪声。芯片的 VDD 和 VSS 之间建议放一个 100nF 陶瓷电容然后再并一个 1uF 左右的储能电容两个电容都要尽量靠近芯片引脚放置。单片机的电源同样要加去耦最好让 MRAM 的电源和单片机数字电源之间有一点滤波隔离避免 SPI 翻转时钟时把噪声带到模拟采样上。CS 线在软件上要保证空闲为高但在硬件上最好也加上拉电阻。因为单片机刚上电、IO 还没配置成输出时CS 可能是高阻态如果 MRAM 此时收到毛刺信号被误选就可能读到垃圾指令。我一般给 CS 加一个 10kΩ 上拉到 VDD保证默认非选通。SCK、SI、SO 也可以各自串一个 22Ω 到 33Ω 的电阻主要是为了抑制边沿振铃尤其在排线较长、电机变频器附近时作用明显。供电时序也要想清楚。工业设备经常有整个系统掉电的过程如果不希望在掉电瞬间写坏数据就要确保 MRAM 的电源落后于数据线信号衰减。最简单的方法是在 VDD 处加大电容让电源晚一点掉下去同时用单片机的掉电中断把最后一笔数据尽快写进去。后面软件部分会再展开。3. 驱动开发让单片机顺畅读写 MRAM硬件接好之后开始写驱动。开发环境是 MPLAB X IDE XC8 编译器代码全部用标准 C 写逻辑很清晰想移植到其他单片机上也不难。3.1 SPI 控制器初始化PIC18F85K22 的 MSSP1 模块初始化主要做三件事把引脚方向配好、设置 SPI 主机模式、选择时钟极性和相位。下面是初始化函数#include xc.h #define MRAM_CS_TRIS TRISDbits.TRISD0 #define MRAM_CS_LAT LATDbits.LATD0 #define MRAM_CS_LOW() (MRAM_CS_LAT 0) #define MRAM_CS_HIGH() (MRAM_CS_LAT 1) #define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRDI 0x04 #define MRAM_CMD_RDSR 0x05 #define MRAM_CMD_WRSR 0x01 #define MRAM_CMD_READ 0x03 #define MRAM_CMD_WRITE 0x02 void SPI_Init(void) { // 先关闭MSSP1等配置完成再开启 SSP1CON1bits.SSPEN 0; // 主机模式SPI Mode 0空闲低、第一个边沿采样 SSP1STATbits.SMP 1; // 输入采样时机跟随主机 SSP1STATbits.CKE 0; // 与CKP0组合成Mode 0 SSP1CON1bits.CKP 0; // 时钟空闲为低 SSP1CON1bits.SSPM 0b0000; // 主模式时钟 FOSC/4 // 引脚方向 TRISCbits.TRISC3 0; // SCK1 输出 TRISCbits.TRISC4 0; // SDO1 输出 TRISCbits.TRISC5 1; // SDI1 输入 // 片选默认拉高MRAM不选中 MRAM_CS_TRIS 0; MRAM_CS_HIGH(); // 最后打开模块 SSP1CON1bits.SSPEN 1; }这里的 SSPM 选择0b0000表示时钟源为FOSC/4。如果你的单片机主频比较高而 SPI 线上走线又长可以通过修改这个分频值降低速度。工业现场不求跑满 40MHz稳定压倒一切我通常把 SPI 时钟控制在 1MHz 到 5MHz 之间调试时比 20MHz 舒服得多。还需要写一个最基础的字节收发函数。SPI 是全双工发送一个字节的同时会收到一个字节uint8_t SPI_Exchange(uint8_t data) { SSP1BUF data; while (!SSP1STATbits.BF) // 等待接收完成 { // 如果开了看门狗这里可以喂狗 } return SSP1BUF; }BF 是 MSSP 模块的接收缓冲区满标志读 SSP1BUF 后会自动清除。这个函数就是所有 MRAM 操作的底层原子操作后面所有的命令、地址、数据都靠它挪进挪出。3.2 MRAM 命令集与状态寄存器MR25H40CDF 的 SPI 命令风格和普通串行 Flash 很接近好处是资料好找思路直接。用到的主要命令如下命令名操作码功能WREN0x06设置写使能锁存WRDI0x04清除写使能锁存RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据状态寄存器里最关键的是 bit0 WIP。写操作发起后芯片内部需要一小段时间完成真正的写入期间 WIP 为 1此时不能发起新的写操作。虽然 MRAM 的写入速度很快但我仍然坚持每次写完都轮询一下状态这个习惯在 Flash 编程时代养成的放到 MRAM 上也不会错。读状态寄存器的实现uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_RDSR); status SPI_Exchange(0x00); MRAM_CS_HIGH(); return status; }注意读状态寄存器的时候命令字节发完后必须再填一个哑字节把 SO 上的数据“顶”出来。SPI 就是这么个特性想读几个字节之前就得发几个字节的时钟。写使能操作void MRAM_WriteEnable(void) { MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_WREN); MRAM_CS_HIGH(); }这里必须形成完整的 CS 低脉冲先拉低发完命令再拉高。命令收发过程中 CS 不能提前变动否则芯片不认这次操作。3.3 单字节和多字节读写实现单字节写函数void MRAM_WriteByte(uint32_t addr, uint8_t data) { // 写操作前必须先WREN MRAM_WriteEnable(); MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_WRITE); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); SPI_Exchange(data); MRAM_CS_HIGH(); // 等待内部写入完成 while (MRAM_ReadStatus() 0x01) { // 可加超时保护 } }单字节读函数uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_READ); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); data SPI_Exchange(0x00); MRAM_CS_HIGH(); return data; }MR25H40CDF 的好处就是写数据不需要像 Flash 那样按页约束理论上可以从任意地址连续写任意长度。所以多字节读写写起来很自然void MRAM_WriteBuffer(uint32_t addr, const uint8_t *buf, uint16_t len) { uint16_t i; MRAM_WriteEnable(); MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_WRITE); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); for (i 0; i len; i) { SPI_Exchange(buf[i]); } MRAM_CS_HIGH(); while (MRAM_ReadStatus() 0x01) { // 等待完成 } } void MRAM_ReadBuffer(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t i; MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_READ); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); for (i 0; i len; i) { buf[i] SPI_Exchange(0x00); } MRAM_CS_HIGH(); }地址传输时用三个字节高字节对 MR25H40CDF 来说其实只需要低 5 位因为芯片只有 19 根有效地址线。但指令格式就是按 24 位地址设计的直接发三个字节最省事。需要注意的是如果地址超过 0x7FFFF多出来的高位会被忽略或者导致回绕代码里要自己保证不出界。3.4 写入使能和状态轮询细节前面代码里写了写操作前必须先执行 WREN这是这颗芯片和普通 SRAM 最大的区别。MR25H40CDF 的写锁存机制是上电或者 WRDI 之后写锁存失效只有收到 WREN 命令才打开。这个设计是为了防止系统跑飞或者干扰信号误改数据。我见过不少新上手的人在这里翻车写了 WRITE 命令也发对了地址和数据但忘记在前面发 WREN结果片子纹丝不动读回来的全是旧数据。所以我的习惯是把 WREN、WRITE、轮询 WIP 封装成同一个小流程绝不在应用层把它拆开。状态轮询也不能死等。工业现场最忌讳一个函数卡死我是建议加超时计数uint16_t timeout 10000; while ((MRAM_ReadStatus() 0x01) timeout--) { // 延时或喂狗 } if (timeout 0) { // 上报错误重新初始化或标记坏块 }MRAM 正常工作时这个循环最多转几次就结束但一旦芯片异常或者 SPI 线接触不良超时逻辑能立刻把系统拉回正轨而不是让整台设备卡在等待里。4. 调试实录和工业应用注意事项写代码只是第一步真正花时间的是在调试中定位时序和现场故障。这套方案我做了好几轮验证也拆过不少次现场通信失败的案子把所有经验浓缩成下面几个部分。4.1 我踩过的四个坑第一个坑是 CS 时序不完整。刚开始调代码时我喜欢把发送操作写成一个大函数点亮 CS 后连发几个字节再拉高看着没问题。但后来发现偶尔写失败用逻辑分析仪一看才知道发 WREN 命令的时候 CS 提前几十微秒就拉高了芯片认为命令被中断。后来我强制要求自己把 CS 的拉低、收发、拉高看作一个不可分割的事务一次完整操作里中间不插任何别的功能。第二个坑是 SPI 模式配错了。MR25H40CDF 支持 Mode 0 和 Mode 3但 PIC 的 MSSP 模块里时钟极性和相位的叫法跟教科书上的 CPOL、CPHA 不完全一一对应。我用 PIC 时参考的是 CKE 和 CKP 的组合不同 Microchip 文档里表格位置又不一样第一次配反了读出来的数据全是隔位错乱。后来干脆用逻辑分析仪实测把 Mode 0 下采样沿的位置确认清楚再写进代码。第三个坑是 WP 和 HOLD 悬空。第一版调试板为了省事没有把这两个引脚接上拉结果设备在环境箱里做温度循环时偶发性写不进数据。查了半天发现温度变化后 HOLD 脚电平飘忽芯片经常被掐住。之后所有板子都把 WP、HOLD 用 10kΩ 电阻接到 VDD问题再没出现过。第四个坑是电平转换。有一版扩展板把单片机和 MRAM 放在不同电源域MRAM 是 3.3V单片机是 5VSCK 高电平到了 5V已经超过 MRAM 的绝对最大值。刚开始短时间测试没暴露跑了几分钟后读数据开始乱。换成电平转换芯片后系统立刻稳定。工业项目里不同电压域的 SPI 接口必须做转换不能赌芯片的耐受能力。4.2 用逻辑分析仪验证时序调试 SPI我强烈建议备一个逻辑分析仪哪怕只是几十块钱的 8 通道玩具级设备对付 4MHz 的 SPI 也够了。抓取一次“WREN WRITE RDSR”的完整过程能清楚看到 CS 的低电平区间、时钟的极性、SI 上的命令和地址字节、SO 上的返回值。重点看三处CS 在整条命令期间是否一直保持低电平是否在命令开始前至少先低了几微秒结束后有完整的高电平间隔。SCK 的空闲电平是否稳定在低命令字节的第一个 bit 是否在时钟的第一个上升沿被采样。WRITE 命令之后SO 上是否成功回读到一个已经更新的数据或者 RDSR 读出来的 WIP 是否为 0。如果发现 SO 上全是 0xFF大概率是 SDI/SDO 接反了如果读出的数据每个 bit 都错位大概率是采样沿配错如果 CS 波形毛刺多先查上拉和电源去耦。这些经验在现场排查时能节省大量时间。4.3 工业现场可靠性的几个补充做法从实验室到产线还有几个不能省略的工程细节。第一个是掉电保护。MRAM 虽然掉电不丢数据但掉电瞬间如果供电电压已经跌出规格芯片可能写出不确定状态。我会用单片机内部的掉电复位或者外部电压监测器在检测到电压跌到阈值时进入快速处理函数把最重要的参数写入 MRAM。同时 VDD 的储能电容尽量选大一点保证写操作能在电压跌破前完成。如果现场对掉电写盘的可靠性要求极高可以在单片机和 MRAM 之间增加一个电源管理芯片用超级电容保持一段时间的写入窗口。第二个是数据校验。MRAM 本身不会因为写多了而磨损但工业现场的 EMI 还是可能干扰 SPI 线上的信号。我的做法是每条记录后面加 16 位 CRC读取时校验不通过就报警并尝试从冗余区恢复。别把所有数据都放在同一个区域可以划出两个备份区交替写入降低同时损坏的概率。第三个是看门狗和超时恢复。PIC18F85K22 内置看门狗定时器必须启用。同时在 MRAM 底层驱动里要放超时机制不能因为芯片无响应就把整个系统带死。我甚至会在主循环里定期写一个心跳时间戳到 MRAM这样程序跑飞重启后能立刻判断上一次正常运行是什么时候对现场问题回溯很有帮助。第四个是环境适应。工业设备要考虑宽温、振动、接线板上的潮湿。MR25H40CDF 本身有工业级温度范围但封装焊接和 PCB 布局也得跟上。SPI 线不要走得太长最好控制在 10cm 以内如果必须用排线引出就加屏蔽或者双绞时钟线和数据线尽量分开。焊接完成后做一次 X-Ray 或者 AOI 检查虚焊因为 DFN/QFN 封装的焊点可靠性对存储设备影响很大。最后再分享一个我自己常用的批量验证技巧把所有新建的板子上电后对 MRAM 的整个地址空间写入随机数再读回来逐字节比对连续跑几十次。这个动作看起来粗暴但能同时检验 SPI 通信、供电稳定性、焊接质量和地址线无误。通过之后再把系统切到真实的参数存储逻辑。MRAM 不是永远不会出错封装、电源、外部干扰都可能让数据翻转只有在生产前把这些变量都压一遍后面在客户现场才睡得着觉。
返回列表