尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MRAM在工业数据记录中的优势与PIC18 SPI驱动实现

MRAM在工业数据记录中的优势与PIC18 SPI驱动实现 1. 项目起因与方案选型为什么是MR25H40CDF PIC18F87J501.1 一个老生常谈的问题数据记录该用什么存做工业嵌入式的人应该都遇到过这个场景设备要记录运行状态、报警日志、计量数据掉电不能丢而且写入频率不低。以前大家第一反应肯定是EEPROM或者SPI Flash。但实际用下来EEPROM容量小、擦写寿命标称10万次写一页还要等几毫秒SPI Flash容量倒是大但块擦除的机制和写寿命管理在频繁记录数据的场景里非常麻烦一段日志可能几天就把一个扇区写穿了。我最近在一个工业监控项目里又撞上这个需求要存储设备实时状态每隔几百毫秒写一条加上固件参数、校准值总数据量不大但写入次数非常密集。一开始的方案是外挂一颗EEPROM算了一下寿命项目还没开发完我自己先焦虑了。后来把目光转向MRAM也就是磁阻随机存储器。MR25H40CDF这颗4Mbit SPI MRAM就是在这时候进入选型名单的。MRAM和Flash、EEPROM有个本质区别它存储数据靠的是磁性状态不是电荷。这意味着没有擦除操作、没有写寿命限制、写入速度接近SRAM。对工业数据记录来说这几乎是量身定做。我测试下来往同一个地址反复写完全不用想磨损均衡的事情写进去立刻就是新值不需要等页编程、不需要先擦后写这在逻辑上带来的省心程度用过Flash的人应该都懂。1.2 MR25H40CDF 到底是一颗什么样的芯片MR25H40CDF 是 Everspin 的串行SPI MRAM容量4Mbit也就是512KB8引脚小封装工作电压2.7V到3.6V典型的工业级温度范围。指令集和普通的串行EEPROM、SPI SRAM很接近有标准的WREN、WRDI、RDSR、WRSR、READ、WRITE这些命令硬件上几乎可以无缝替换同封装的SPI EEPROM或SRAM。这颗芯片让人舒服的一点是它不需要专门的高压编程也不存在写保护后要解锁的复杂流程靠标准的SPI时序就能完成读写。和同系列其他容量型号比如MR25H40、MR25H256在引脚和命令上基本兼容代换很方便。芯片内部没有页的概念一次写操作可以连续写任意长度的数据只要地址不超过4Mbit空间就行相比Flash要先擦除、再按页写MRAM的线性地址空间用起来就像操作普通SRAM。我还注意到一个细节MR25H40CDF 的工作时钟最高可以到40MHz对于PIC18F87J50这种8位单片机而言它的SPI外设一般也就跑个几MHz瓶颈根本不在存储芯片上。也就是说这颗MRAM的性能余量很大以后哪怕换更高性能的主控存储部分也不需要重新设计。1.3 为什么配PIC18F87J50这颗老将PIC18F87J50 是Microchip的8位单片机内置USB 2.0控制器最高工作时钟64MHz有完整MSSPMaster Synchronous Serial Port模块支持SPI/I2C跑工业控制、数据采集、协议转换这些活绰绰有余。它不是新片子但胜在资料多、稳定、开发环境成熟很多老产线设备里还在用这颗芯片。选它还有一个现实原因工业项目里不是所有地方都追新很多系统还在用8位单片机做底层控制需要一颗成熟可靠的主控来配合MRAM这种存储设备。PIC18F87J50的MSSP模块能配成SPI主模式有专用的SCK、SDO、SDI引脚配合普通IO模拟片选信号驱动MR25H40CDF非常直接代码量不大逻辑也清晰。我比较认可的组合方式是PIC18F87J50负责系统控制和通信MR25H40CDF负责所有需要掉电保存的数据两者用SPI直连中间不需要电平转换因为都在3.3V供电下工作。硬件上可以说零压力剩下的重点就全在软件驱动和存储策略上了。2. 硬件连接与SPI通讯设计2.1 MR25H40CDF的引脚功能与命令集先过一遍引脚。MR25H40CDF是8引脚封装关键引脚就是CS、SCK、SI、SO四个SPI信号加上VCC和GND另外还有WP和HOLD两个控制引脚。WP是写保护输入低电平有效拉低后禁止写操作HOLD是暂停通信输入低电平有效可以在传输中途暂停而不用重新同步时序。工业设计里我习惯把WP通过10k电阻上拉到VCC保持允许写入的状态HOLD同样上拉到VCC确保没有任何意外暂停。命令集方面这张表我非常建议贴在工作台上命令操作码功能说明WREN0x06设置写使能锁存器WRDI0x04清除写使能锁存器RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读存储区后面跟2字节地址WRITE0x02写存储区后面跟2字节地址和数据地址是17位所以从字节地址0x00000到0x7FFFF分三个字节传输其中第一个字节是高8位地址。READ和WRITE命令本身支持连续操作CS保持低电平期间地址会自动递增直到CS拉高为止。这个连续读写特性在记录一段连续日志时很有用一次性写入几十字节而不需要反复发命令。2.2 SPI模式为什么是Mode 0或Mode 3MR25H40CDF支持SPI Mode 0和Mode 3也就是CPOL0、CPHA0或者CPOL1、CPHA1。这个不是随便选一个都行MCU端和存储芯片必须在同一组极性和相位下工作。PIC18F87J50的MSSP模块初始化时需要明确设置时钟极性和采样边沿。我习惯用Mode 0CPOL0表示空闲时SCK为低电平CPHA0表示数据在SCK上升沿被采样、下降沿改变。这样理解起来最直观逻辑分析仪抓波形也容易看。Mode 3则是空闲高电平、下降沿采样两种模式芯片都认只要主从一致即可。这里有个小提醒很多SPI器件只支持一种模式或者所谓“兼容Mode 0/3”但实际对时序要求苛刻。MR25H40CDF在这方面很宽松SCK最高频率40MHz我在PIC上只用几MHz跑完全没有时序压力。2.3 硬件布局和去耦的实战细节工业环境里SPI总线最容易出问题的不是速率而是干扰和地弹。我画PCB时有几个固定习惯MR25H40CDF下方尽量保证完整地平面VCC对GND放一个0.1uF陶瓷电容位置尽可能贴近电源引脚如果空间允许再加一个1uF或者10uF的钽电容做低频去耦。CS、SCK、SI、SO四根线尽量不要布得又长又细特别是SCK这种时钟线如果走线过长会引入反射导致数据采样出错。我一般控制在20mm以内如果必须跨越板卡连接那就要考虑串接33R电阻做阻抗匹配或者在接收端加施密特触发器整形。WP和HOLD的上拉电阻我推荐10k不要用1k或者4.7k。原因很简单这两个引脚平时不动作10k上拉足够维持高电平同时静态电流也很小对工业低功耗设备友好。MCU侧CS用普通IO控制不要用MSSP硬件SS引脚因为后期如果要换主控或改时序软件IO控制的CS灵活性高得多。还有一点是给WP加个RC低通串联1k电阻并在WP引脚对地并一个100pF电容防止线缆上耦合的毛刺把写保护误触发。这个做法在工业设备上很有价值我遇到过几次现场无故写不进数据最后定位就是WP被噪声短暂拉低导致的。3. 基于PIC18F87J50的软件驱动实现3.1 MSSP外设初始化SPI主模式配置PIC18F87J50的MSSP模块可以工作在SPI主模式需要设置的寄存器主要是SSPCON1和SSPSTAT。用XC8编译器写代码比较直观我先把初始化函数贴出来。#include xc.h // PIC18F87J50 SPI主模式初始化 void SPI_Init(void) { // 设置SCK、SDO为输出SDI为输入 TRISBbits.TRISB1 0; // SCK TRISBbits.TRISB2 0; // SDO TRISBbits.TRISB3 1; // SDI // MSSP配置SPI主模式时钟FOSC/16Mode 0 SSPCON1 0x20; // 0010 0000: SPI主模式SCK空闲低电平 SSPCON1bits.SSPEN 1; // 使能MSSP SSPSTAT 0x40; // SMP1, CKE0对应Mode 0采样 }这里关键是把SMP和CKE配成与Mode 0匹配的组合。不同MCU的寄存器位含义略有差异核对数据手册最稳妥。SCK频率我选了FOSC/16假设系统时钟16MHz则SCK为1MHz对这个应用足够了。如果主频更高可以根据实际调整分频比。3.2 底层字节收发函数MSSP的收发是全双工想发一个字节的同时会收到一个字节。所以读写底层函数可以共用一个SPI_TransferByte写数据时发送目标字节读数据时发送0x00即可。unsigned char SPI_TransferByte(unsigned char data) { SSPBUF data; // 写入发送缓冲 while (!SSPSTATbits.BF); // 等待接收完成 return SSPBUF; // 返回收到的数据 }发送前还要确保CS已经拉低。我习惯单独封装片选操作#define MRAM_CS_LOW() LATCbits.LATC0 0 #define MRAM_CS_HIGH() LATCbits.LATC0 1片选引脚用普通IO初始化方向为输出默认拉高。这个操作看似简单却决定了整个SPI命令序列的边界CS的低脉冲必须完整包住一次命令不能中间释放否则芯片会把命令解释成两截。3.3 状态寄存器与写使能操作MRAM和EEPROM类似写入之前必须先发WREN把状态寄存器里的WEL位置1否则WRITE命令会被忽略。状态寄存器各位如下bit7: WPEN写保护引脚启用bit6: BP1bit5: BP0bit4: WEL写使能锁存bit3-bit0: 固定为1读状态寄存器unsigned char MRAM_ReadStatus(void) { unsigned char status; MRAM_CS_LOW(); SPI_TransferByte(0x05); // RDSR status SPI_TransferByte(0x00); // 读取状态字节 MRAM_CS_HIGH(); return status; }写使能void MRAM_WriteEnable(void) { MRAM_CS_LOW(); SPI_TransferByte(0x06); // WREN MRAM_CS_HIGH(); }需要注意的是MRAM没有像Flash那样需要等内部编程时间WREN之后立刻就可以执行WRITE不需要轮询状态寄存器等待“不忙”。这个特性在实时性要求高的场景特别有用写一条日志的时间上限是确定的不会因为芯片内部忙而产生不确定延迟。我习惯在每次WRITE之前都先发WREN哪怕上一次写操作已经让WEL处于置位状态多写一次WREN没有任何副作用反而能避免一些边界状态下的意外。3.4 标准读、写接口函数有了底层字节收发读写函数就很直接了。写多个字节void MRAM_WriteBuffer(unsigned long addr, unsigned char *buf, unsigned int len) { MRAM_WriteEnable(); // 1. 写使能 MRAM_CS_LOW(); // 2. 拉低CS SPI_TransferByte(0x02); // 3. WRITE命令 SPI_TransferByte((addr 8) 0xFF); // 高位地址 SPI_TransferByte(addr 0xFF); // 低位地址 while (len--) { SPI_TransferByte(*buf); } MRAM_CS_HIGH(); // 4. 拉高CS结束操作 }读多个字节void MRAM_ReadBuffer(unsigned long addr, unsigned char *buf, unsigned int len) { MRAM_CS_LOW(); SPI_TransferByte(0x03); // READ命令 SPI_TransferByte((addr 8) 0xFF); SPI_TransferByte(addr 0xFF); while (len--) { *buf SPI_TransferByte(0x00); } MRAM_CS_HIGH(); }这个接口已经可以直接用了。地址处理上传进去的addr是字节地址最大到0x7FFFF对应512KB空间。如果项目里要分区间管理比如前64KB存参数、后面存日志直接在应用层做地址规划就好存储芯片本身没有分区概念。3.5 用逻辑分析仪验证时序写完驱动第一件事不是接业务逻辑而是用逻辑分析仪抓一次完整的写操作时序。我推荐至少抓三样东西CS是否在整个命令期间保持低电平、SCK空闲电平是否符合Mode 0、命令字节0x06和0x02是否按预期出现在SI上。实际遇到的问题里最常见的是CKE位配置错误导致实际跑成Mode 1或Mode 2。芯片手册虽然写了支持Mode 0和Mode 3但如果MCU端配错成Mode 1SCK空闲电平和数据采样边沿对不上现象是能读出状态寄存器但读存储区数据全是0xFF写入完全无效。这种错位在逻辑分析仪上非常明显SCK第一个时钟边沿前的电平状态一眼就能看出来。4. 工业应用中的存储策略与掉电保护4.1 MRAM的“无限写”改变了什么设计逻辑传统Flash方案里每条日志都要考虑写磨损动不动就得做静态磨损均衡、动态磨损均衡、扇区管理、垃圾回收软件复杂度很高。MRAM没有擦除操作、写入寿命极长这些为Flash服务的软件层的存在意义就消失了。在PIC18F87J50这种资源有限的8位MCU上省掉Flash文件系统和管理层意味着能省下大量RAM和程序空间。我做的项目里日志存储直接就是“一个环形缓冲区的指针 裸地址写入”逻辑简单到极点。这在实际工程里好处非常明显代码量少出bug概率低现场排查问题容易。MRAM写数据不需要擦除也带来另外一个便利可以原地更新参数不需要“复制到临时区-擦除-写回”这种三步操作。设备校准值、运行计数值这种经常变的数据直接往同一地址写最新值即可读回来就是最近一次写入的内容不存在掉电丢失中间态。4.2 环形日志缓冲区的实现思路数据记录类的应用我最常用的是在MRAM里划分一块区域做环形日志。示例总容量512KB从地址0x10000开始分配256KB给日志区0x10000之前的空间存系统参数。环形日志需要一个写指针我把它存在日志区之外的固定地址也就是0x0F000这个位置。每次写日志时先读出指针值写日志数据再把指针加上本次长度回写。如果指针越过日志区末尾就回绕到起始地址。这个方案的优点是实现简单没有复杂的索引表缺点是如果MRAM写指针的时机不对可能出现日志区覆盖的指针错乱。我在实际项目中做了一点改进指针每次递增后立即写回并在其后附加两个字节的CRC16校验值。读指针时先验证CRC不对就从头扫描日志区找最后一条有效记录。MRAM写入没有延迟所以这个“读指针-写日志-回写指针-写校验”的序列可以非常紧密地连续执行整个过程的耗时主要花在SPI通信本身而这在1MHz的SCK下写几十字节大约几百微秒工业现场完全够用。4.3 掉电瞬间最后一条记录怎么保住掉电保存是工业设备的经典考题。MRAM本身是掉电非易失的数据写进去就丢不了真正的风险在于掉电瞬间主控有没有来得及把data buffer里的最后一条记录完整地写进MRAM。我常用的策略是给系统加一个“掉电预警”输入比如电源监控芯片如MCP101或TLC77的输出接到PIC的外部中断引脚。正常工作时这个引脚是高电平一旦检测到电源开始跌落立刻触发中断中断服务程序里只做一件事把当前待写数据通过SPI写入MRAM。这里要评估写入时间预算。MRAM单字节写入本身就是一次SPI命令1MHz下写32字节数据加开销大约400us以内。电源掉电后靠电容储能维持MCU工作几百微秒到几毫秒是可行的。我实测在一个5V电源上并联470uF电容3.3V稳压输出在掉电后还能维持约3ms足够把几十字节的日志写完。如果产品不允许加电容或成本敏感那也有一个备选方案每次数据变化时立刻写MRAM不依赖掉电中断。MRAM写入速度快、无寿命问题这种“实时写”策略非常适合它。相比之下如果用EEPROM频繁写几个毫秒的等待时间会严重拖累主程序这也是MRAM在工业存储里不可替代的地方。4.4 双备份与校验工业数据的基本尊严工业现场不能只靠“能写进去”就算完还要考虑数据完整性。我一般对关键参数区做双份存储主区和备份区各存一份每份自带CRC校验值。读取时先读主区校验失败再读备份区如果备份区也失败就用默认值并上报告警。MRAM本身误码率很低但SPI链路在强干扰环境下有可能出现单bit翻转双备份的成本很低效果却非常明显。写主区和写备份区之间一般间隔10ms以上避免在掉电瞬间两个区同时处于写一半的状态。对那些必须毫秒级恢复的系统还可以把系统运行参数镜像到MRAM的连续地址段启动时一次突发读取全部参数这样初始化时间极短不需要每个参数单独读一次。MRAM支持连续读这个特性在批量恢复现场数据时特别好用。5. 调试阶段踩过的坑与排查方案5.1 写入不生效的最常见原因漏发WREN我见过不止一次同事的代码READ一切正常WRITE就是没反应状态寄存器读出来WEL0。排查到最后都是因为漏了WREN这一步或者发了WREN之后CS拉高了太长时间导致内部状态被复位。按照MRAM手册WREN命令必须紧跟WRITE命令才能生效中间不要夹其他SPI操作也不要超过芯片规定的CS高电平时间。我封装写函数时把WREN和WRITE放在同一个函数里中间不插入其他操作从流程上杜绝了这个问题。5.2 读出来全是0xFF或数据错位这个现象我总结有三个常见原因一是SPI模式配错。PIC18F87J50的SSPSTAT寄存器里CKE位和SMP位组合不同会导致四种相位必须确认是Mode 0还是Mode 3。我推荐固定使用Mode 0初始化时CKE0、SMP1。二是CS时序不对。MSSP发送完最后一个字节后如果CS拉高太快实际上最后一个字节的接收可能还没完成。解决办法是在CS拉高前加一小段延时或者先读走SSPBUF确保事务完全结束。三是MSSP的写冲突。如果在BF位没有清空时就再次写入SSPBUF会产生写冲突数据发不出去。我的SPI_TransferByte函数里每个字节都等待BF置位后再返回原理上就避免了这个问题。5.3 地址越界和环形缓冲区覆盖MRAM地址空间是0x00000到0x7FFFF驱动层只做地址和数据的搬运不做边界检查。如果应用层计算出错误的地址比如指针回绕时算错偏移就会写到别的地方。我的习惯是在驱动函数里加一个调试用的断言if (addr 0x7FFFF || len (0x80000 - addr)) { // 记录错误标志不做实际写操作 return; }工业设备不能轻易停机但存储区的越界写会带来极其隐蔽的故障宁可这一次不写也不能把关键数据区搞坏。调试阶段这个断言能抓出大量低级错误。5.4 现场干扰导致的误写和通信异常工业现场的EMC环境比实验室恶劣很多电机启停、大电流切换都会在SPI线上耦合噪声。我遇到过的情况是CS线被干扰拉出很窄的毛刺MRAM误以为是一次片选操作内部状态错乱。解决的套路是三层硬件上CS线串联1k电阻并加100pF对地电容构成简单RC滤波线缆连接时CS、SCK、SI尽量靠近地线走减少环路面积软件上每帧数据加CRC校验发现CRC错误就连续重读三次取多数一致的结果。另外WP引脚的抗干扰处理同样重要前面说了加RC滤波这里再强调一次。MRAM在WP低电平时禁用写命令如果噪声毛刺把WP拉低会出现“有时能写有时不能写”的诡异现象比直接通信失败更难排查。5.5 常见问题排查速查表现象可能原因处理方式写不进去读回旧数据漏发WREN或WEL未置位检查写操作序列是否在WRITE前发WREN读取全是0xFFSPI模式不匹配确认PIC配置为Mode 0或Mode 3数据错位第一个字节对后续不对CS拉高过早接收未完成读走SSPBUF并延时后再拉高CS偶发写入失败WP引脚被噪声拉低WP加RC滤波软件重试掉电后最后一条数据丢失掉电检测到写入间隔过长增加掉电中断处理或使用实时写策略环形日志指针错乱指针写入过程掉电指针附带CRC校验启动时校验后恢复我在这块调试上最有价值的一条经验是MRAM本身的可靠性非常高绝大多数故障都出在MCU侧的SPI时序和系统级掉电处理上。不要一上来就怀疑芯片先把逻辑分析仪挂在CS和SCK上通常十分钟就能定位问题。6. 写在最后的一点个人体会这个项目做下来我对MRAM在工业存储里的定位有了更实的感受。它不是来替代大容量Flash的而是解决那些“容量要求不高、写入频繁、掉电必须可靠”的特定场景。MR25H40CDF这种小容量SPI MRAM搭配PIC18F87J50这类成熟8位MCU正好覆盖了大量工业控制器、仪表、数据采集设备的需求。如果项目还在选型阶段我的建议是别只盯着数据手册上的容量和价格要把整个软件维护成本算进去。用Flash做频繁日志记录光擦写均衡和掉电管理就够写两千行代码还容易出隐蔽bug。换MRAM之后存储部分的代码量压缩到几百行逻辑清晰现场问题少了一大半。最后再分享一个实际操作里的小技巧MRAM在量产测试时写一遍全地址0xA5、再写一遍0x5A、最后全部读回校验可以非常快速地把芯片、焊接、SPI链路一起测一遍。这个测试几分钟就能跑完早期发现硬件问题的效率奇高值得写进产测固件里。
返回列表