ARTICLE DETAIL

资讯详情

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

MR25H40CDF串行MRAM与PIC18F45K22的SPI存储实战

MR25H40CDF串行MRAM与PIC18F45K22的SPI存储实战 做嵌入式系统的人早晚都会遇到一个特别磨人的需求——数据要保存不能丢更不能写着写着把存储介质写报废。早年我帮一家做伺服驱动器的客户改过参数存储逻辑原来用的是普通EEPROM设备每天断电重启几十次每次都往里写一次参数结果上量之后陆续有设备读出来一堆乱码查到最后就是EEPROM写寿命到了。从那以后我开始认真研究怎么把“掉电保存频繁读写”这件事做得又稳又简单最后固定下来的组合是Everspin的MR25H40CDF这颗串行MRAM搭配Microchip的PIC18F45K22来进行存储和读取数据。这篇文章就把这个组合的选型思路、硬件接线、SPI读写代码、工业可靠性措施和踩坑经验完整讲一遍适合做工业控制、设备日志、参数存储的工程师也适合正在学嵌入式、想找一个能练手又能学到真东西的项目的同学参考。1. 为什么选MRAM一个产线事故让我彻底换了思路1.1 老方案的三座大山寿命、擦除、掉电窗口先说那次事故。客户的伺服驱动器里有一个“断电记忆当前位置”的需求设备每次断电前要把当前位置、运行模式、几个校准系数写进EEPROM。一开始用的是某品牌的I²C EEPROM型号不大容量也不大但客户出货之后出现了千分之几的返修率现象是上电后位置错乱或者参数变成乱码。我带着逻辑分析仪和热风枪折腾了好久最后定位到原因设备一天断电几十次每次上电和断电过程里还有多次写入EEPROM的标称寿命也就十万到一百万次几个月就磨到临界值。更麻烦的是EEPROM在读写过程中如果遇到电源跌落可能会写进去一半字节下一半还是旧数据读出来就是半新半旧的结果。换NOR Flash行不行也不行。SPI NOR Flash寿命通常在一万到十万次比EEPROM略好但有个让人头疼的机制写入之前必须先擦除。擦除最小单位是扇区一般是4KB意味着我为了刷一个字节得先把整个扇区搬到RAM里改完再擦再写。而且擦除操作耗时几十甚至几百毫秒掉电窗口更长反而更容易出问题。后来我把目光转向FRAM和MRAM对比之后选定了MRAM理由后面细说。1.2 MR25H40CDF是什么凭什么耐写MR25H40CDF是Everspin推出的一款串行MRAM芯片容量4Mbit换算成字节正好是512KB。它用SPI接口通信8个引脚3.3V供电封装很小在PCB上占不了多少地方。MRAM的存储单元核心是磁隧道结MTJ简单理解就是一层很薄的绝缘层夹在两层磁性材料之间磁性材料的磁化方向决定隧穿电阻是高还是低从而表示“0”或“1”。这个结构和Flash、EEPROM完全不同。Flash靠浮栅里存电荷表示数据电荷会漏电而且每次写入都要先把电子隧穿进氧化层反复隧穿会损伤介质所以寿命有限。MRAM是靠磁化方向存储断电之后磁化状态照样保留属于真正的非易失存储而写入过程就是改变磁化方向不存在介质磨损所以它的写寿命可以到10的15次方级别在实际工程里基本可以认为“随便写”。我后来用开发板做过连续写入测试按10毫秒写一次算一天就是864万次连续跑了一年多也没出问题这在以前用Flash是根本不敢想象的事。1.3 与EEPROM、NOR Flash、FRAM的同台对比选型那会儿我把几种常见非易失存储拉了一张表放在这里帮大家省事维度EEPROMNOR FlashFRAMMRAMMR25H40CDF非易失是是是是写寿命约10万~100万次约1万~10万次约10的12次方次约10的15次方次级别写前是否需要擦除不需要必须按扇区擦除不需要不需要最小写入粒度字节页/扇区字节字节写速度慢毫秒级慢毫秒级快总线速度快总线速度典型容量小Kbit级大Mbit~GbitKbit~Mbit级Mbit级成本低低中中偏高从表里能看出来MRAM最吸引人的地方不只是寿命长而是它的写行为和SRAM一样“即写即生效”却还保留非易失特性。没有擦除操作没有编程等待时间没有页对齐约束这让软件逻辑变得非常简单。我后来在项目里甚至把MRAM当成一片不会丢数据的SRAM来用脑海里那些Flash驱动残留的“还要先擦一下”的思维惯性花了好一阵才扭转过来。2. 硬件接线PIC18F45K22与MR25H40CDF的连接细节2.1 PIC18F45K22的SPI资源与引脚规划PIC18F45K22是Microchip的8位MCU40引脚左右3.3V供电自带两个MSSP模块可以配置成SPI主机或从机。选它来配合MRAM一个重要原因是两边电压都是3.3V可以直接连接不用加电平转换电路。另一个原因是PIC18F45K22的外设引脚选择PPS功能非常灵活SCK、SDO、SDI这些信号可以通过寄存器映射到多个引脚上布线的时候很方便不会像固定映射的芯片那样被引脚位置卡死。实际项目里我建议把SPI的片选信号CS#用普通GPIO控制不要用MSSP模块的硬件SS#引脚。原因很简单硬件SS#的时序是模块自己拉的有时候你希望在一次多字节突发传输结束后精确地把CS#拉高用GPIO控制更加可控也更容易做掉电保护逻辑。PIC18F45K22的SPI时钟源可以从系统时钟分频得到比如系统时钟16MHz分频4就是4MHz的SCK这个速度对于10cm以内的板内走线完全没问题。2.2 最小连接图与引脚处理MR25H40CDF的引脚定义和多数SPI NOR Flash类似包含电源、地、SPI四线以及写保护、HOLD引脚。连接关系可以归纳成这么几张MR25H40CDF引脚接法说明VCC3.3V就近放0.1µF去耦电容供电必须稳定GND系统地注意和MCU共地CS#GPIO如RB0命令边界靠它拉高SCKMCU的SCK输出建议串33Ω电阻SIMCU的SDO输出数据进MRAMSOMCU的SDI输入数据出MRAMWP#直接接VCC禁止写保护HOLD#通过10kΩ上拉到VCC不能悬空这里有两个引脚最容易翻车WP#和HOLD#。WP#如果被拉低MRAM会进入写保护状态WREN命令发了也白发写入操作会被忽略。HOLD#如果悬空可能被外界干扰拉低芯片会暂停SPI传输MCU这边还以为数据已经送进去了结果读回来完全是乱的。所以我把这两个引脚的处理当成硬件检查清单里的固定项目每次画板都要核对一遍。另外CS#和SCK之间没必要追求绝对等长工业板上这点走线差异根本不影响保持布线短而直就好。2.3 供电、去耦和PCB走线经验工业环境里最容易忽视的是电源质量。MR25H40CDF虽然本身不挑电源但如果MCU和MRAM的供电在掉电瞬间出现抖动SPI通信就可能错位。我的做法是在MRAM的VCC引脚旁边放两个电容一个0.1µF陶瓷电容滤高频一个1µF电容兜底两个电容都尽量靠近芯片引脚。MCU侧的VDD同理C0G或X7R都行关键是位置。还有一个经验是给SCK和SDO线上各串一个33Ω的小电阻。这个电阻不是为了限流而是为了抑制信号边沿的过冲和振铃。工业现场的长线缆、继电器触点、变频器都可能把噪声耦合进来串了电阻之后波形会干净很多EMC测试也好过一些。如果MRAM和MCU分处两块板子通过排线连接那我建议SPI速率直接降到1MHz以下而且两块板之间必须有完整的电源和地回路否则信号电平跟着地漂移读出来的数据就是薛定谔的状态。3. 软件实现从SPI时序到可落地的读写代码3.1 命令集、SPI模式与状态寄存器MR25H40CDF的命令集比SPI NOR Flash简单得多核心就几个WREN写使能、READ、WRITE、RDSR读状态寄存器。常用的命令字节如下命令字节作用后面的数据WREN0x06使能写操作无WRDI0x04禁止写操作无RDSR0x05读状态寄存器返回1字节READ0x03读取数据3字节地址数据输出WRITE0x02写入数据3字节地址数据输入最重要的一个认知差异是MRAM没有擦除命令也不需要查BUSY位。SPI Flash写入之后要轮询状态寄存器等待编程完成MRAM的数据在发送完最后一个字节时已经真正写进存储单元了。我一开始还不习惯写代码时总想找个类似Flash的“编程完成标志”结果发现数据手册里根本没有后来干脆把这句话写在代码注释里“MRAM没有忙状态写完就是写完。”SPI模式方面MR25H40CDF支持Mode 0和Mode 3也就是CPOL/CPHA在0/0或1/1两种组合下都能工作。我习惯用Mode 0也就是空闲时SCK为低、在上升沿采样数据。需要注意如果你的逻辑分析仪或者MCU配置成了Mode 1或Mode 2看起来波形差不多但数据会整体错位最常见的结果就是命令字节被当成地址、数据整体少一位后面排查了好几个小时才发现是极性问题。3.2 核心读写代码XC8下面这套代码是在MPLAB X XC8环境下写的底层SPI收发函数用的是MCC生成的SPI1_ExchangeByte()业务逻辑部分自己实现。关键的寄存器配置在MCC里完成把MSSP1设为SPI主机、Mode 0分频到4MHz左右。CS#用普通GPIORB0手动控制。#include xc.h #define MRAM_CS LATBbits.LATB0 #define MRAM_CS_TRIS TRISBbits.TRISB0 #define MRAM_WREN 0x06 #define MRAM_WRITE 0x02 #define MRAM_READ 0x03 #define MRAM_ADDR_MASK 0x0007FFFFUL // 4Mbit 512KB19位地址有效 void MRAM_Init(void) { MRAM_CS_TRIS 0; // CS设为输出 MRAM_CS 1; // 空闲时片选拉高 // 注意MRAM的WP#在硬件上要接VCC // HOLD#要通过电阻上拉这两个不做软件没法救。 } // MRAM写入前必须先发WREN不然写操作会被忽略 void MRAM_WriteEnable(void) { MRAM_CS 0; SPI1_ExchangeByte(MRAM_WREN); MRAM_CS 1; } // 发送3字节地址高字节最高位必须清零地址只有19位 void MRAM_SendAddress(uint32_t addr) { addr MRAM_ADDR_MASK; SPI1_ExchangeByte((uint8_t)((addr 16) 0xFF)); SPI1_ExchangeByte((uint8_t)((addr 8) 0xFF)); SPI1_ExchangeByte((uint8_t)(addr 0xFF)); } // 任意地址连续写入 void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_WriteEnable(); MRAM_CS 0; SPI1_ExchangeByte(MRAM_WRITE); MRAM_SendAddress(addr); while (len--) { SPI1_ExchangeByte(*buf); } MRAM_CS 1; // CS拉高命令结束 } // 任意地址连续读取 void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_CS 0; SPI1_ExchangeByte(MRAM_READ); MRAM_SendAddress(addr); while (len--) { *buf SPI1_ExchangeByte(0x00); } MRAM_CS 1; }这段代码的逻辑看起来很简单但有几个细节必须强调。第一WREN命令本身也要靠CS#拉高来结束很多第一次接触MRAM的人写完WREN后CS一直拉低然后接着写WRITE命令结果芯片会认为后面的字节还是WREN的一部分整个帧就乱了。第二发送地址时不能只发2字节哪怕你只需要512字节以内的空间也要把3字节地址帧发完整否则后面数据会错位。第三CS#拉高的时机很重要最后一个数据字节发送完后下一个SCK边沿到来之前必须把CS#拉高我在实际代码里就是紧跟着while循环结束立刻拉因为MCC的SPI1_ExchangeByte是阻塞式的数据移位结束后SCK已经停在高电平拉CS是安全的。3.3 批量读写和地址规划技巧MRAM最大的好处之一是没有页边界限制。SPI Flash通常一页256字节跨页写要拆成多次操作MRAM则可以一次突发写几千字节只要CS#保持低电平就行。我在做日志记录时经常一次写128字节省了Flash那种“先判断是否跨页”的烦人逻辑。但是“没有页边界”不等于“随便写”。地址规划上我建议把512KB分成几块用途明确的区域比如头部参数区存放版本号、魔数、关键配置、循环日志区、临时缓冲区。头部参数区可以用结构体方式写入每次写入前先计算CRC读出来时校验确保数据完整。循环日志区则维护一个写指针指针本身也存到MRAM的固定位置这样无论什么时候掉电上电后都能顺着指针找到最新日志。这个思路和Flash日志设计一样但MRAM可以省去擦除和磨损均衡的复杂处理实现起来快很多。4. 工业可靠性把“存得进、读得出、掉电不乱”做扎实4.1 掉电瞬间的误写防护嵌入式存储最常见的翻车场景不是正常读写而是“叫着电突然没了”。设备可能在MRAM写数据写了一半时掉电CS#还拉着低电平SCK可能因为电源跌落产生毛刺MRAM会把这些毛刺当成正常数据写进去。软件上再怎么容错遇到这种情况也会出现整块数据被污染。我的方案分三层。第一层是硬件上的大电容和BOR欠压复位PIC18F45K22自带BOR模块设置得当的话掉电瞬间MCU会先进入复位状态而不是继续跑乱代码复位期间所有GPIO保持安全的输出状态CS#自然保持高电平MRAM不会再被误写。第二层是在软件里加一个掉电检测任务用ADC监控VDD电压一旦发现电压逼近某个阈值立即封锁SPI模块并把CS#拉高。这个手段不用很高优先级几毫秒的响应足够。第三层是写入策略层面的后面详细说。4.2 高频日志与磨损均衡MRAM写寿命虽然极高但工程上不能因此就完全不管磨损。我见过有人把循环日志的写指针固定写在同一个地址假设MRAM寿命够长就直接忽视这种习惯我不太赞成。虽然MRAM的寿命比Flash高好几个数量级但“足够高”和“无限高”之间还是有一线之差尤其产品生命周期是十年、二十年的时候宁可多写几行代码做均衡也不要在寿命问题上留隐患。我常用的做法非常简单把日志区做成环形写指针存在一个专门的指针区内但指针区设两个备份地址每次写完指针后交替使用两个备份地址记录读取时比较两个备份的CRC和序号取最新且完整的那个。这个方案在Flash上还要考虑擦除块对齐在MRAM上只是简单的结构体写入逻辑非常简单。对大多数项目来说到这一步已经非常稳了。4.3 数据块封装魔数、版本、CRC与备份光靠存储介质可靠还不够数据内容本身要做完整性校验。我写存储驱动时会把所有关键数据封装成固定格式的数据块块头包含魔数、版本号、数据长度、时间戳或序号块尾放CRC。读取时先检查魔数再校验CRC最后看序号是否连续。如果头部损坏就尝试读备份块。备份块可以和主块放在相邻地址也可以用另一个固定区域。这里有一个容易踩的坑CRC算法本身要选对实现。很多人图省事直接用查表法CRC-16但查表法的多项式、初值、输出异或值在不同实现里五花八门。我建议在项目一开始就把CRC参数定义清楚写好单元测试否则设备之间互换数据时会出现“这次读出来校验通过下次换了固件版本就报错”的诡异问题。工业项目里宁可代码多几行也要把数据格式的版本字段留出来方便以后扩展。我的数据块头部固定是魔数(2字节) 版本(1字节) 长度(2字节) 序号(2字节) 数据 CRC16(2字节)总共不到10字节的开销换来的却是掉电乱码时一眼定位问题的能力。5. 常见问题排查与避坑实录5.1 写入不生效读出全是0xFF这是新手接触MRAM时第一个常见问题。我的排查顺序是固定的先查WP#引脚是不是被拉低。很多开发板上WP#默认通过跳线接地用来模拟写保护如果你照抄别人NOR Flash电路没注意MRAM会持续处于写保护状态。第二步查代码里有没有发WREN而且每次写操作前都要发不像有些FRAM芯片写了使能之后一直有效。第三步查CS#拉高的时机CS#一定要在SPI字节帧之间拉高来区分命令边界不能一直拉低。第四步查地址帧确实有同事把地址拆成两个字节发以为4Mbit只需要低16位结果芯片把第一个数据字节当成了地址高位数据全写到别的地方去了。第五步才是怀疑芯片本身换一颗或换一片板子验证。5.2 数据时对时错先查这三样如果写入后立刻能读对过一会儿再读就错或者连续写多次之后读到半新半旧问题大概率不在MRAM芯片上而在通信链路上。第一样是SPI极性用逻辑分析仪抓一下波形对照数据手册确认是Mode 0还是Mode 3如果代码里和万用表看到的边沿差一位那就是配置和芯片要求不一致。第二样是SCK频率太高我在工业板上实测过4MHz在10cm内走线没问题但超过20cm或经过排线后信号完整性问题就会冒出来此时把分频降到1MHz多数异常直接消失。第三样是地回路不稳定MRAM读操作和主控之间如果存在地电位差结果就是SO线上的数据电平被判错这种故障有很强的随机性很难复现解决方法是重新检查PCB的电源地和模拟地规划。5.3 用逻辑分析仪给时序“拍个X光”写MRAM驱动时逻辑分析仪比仿真器好用得多。我建议抓到四条线CS#、SCK、SI、SO。波形里先确认CS#拉低后SCK才开始翻转然后确认第一个字节是0x06WRENCS#拉高后再拉低第二个字节是0x02WRITE后面跟着3字节地址和你要写的数据。读取时注意SO线上的数据是在SCK的哪个边沿变化的和MCU的采样边沿对不对得上。排查时最有效地做法是一次只改一个变量要么改分频要么改极性不要同时调好几个参数否则波形对了也不知道是哪个改动救回来的。最后分享一个我个人很受用的习惯上电之后别急着开始业务逻辑先把MRAM头部区域的魔数和CRC读出来验一遍如果CRC不对就走恢复流程而不是直接读取并信任里面的数据。这套“先验再读”的思路和存储介质本身靠不靠谱没有直接关系它防的是整个系统里比介质脆弱得多的那些环节——电源干扰、走线噪声、配置错误。把这一关守住了MR25H40CDF配合PIC18F45K22这套方案在工业现场大概率能一路安稳地跑下去。
返回列表