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

资讯详情

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

工业级MRAM替代SRAM+电池方案:PIC18F45K40与MR25H40CDF的SPI驱动实践

工业级MRAM替代SRAM+电池方案:PIC18F45K40与MR25H40CDF的SPI驱动实践 1. 为什么工业现场还在用并行SRAM而MR25H40CDF值得你重新考虑如果你拆过工业控制柜里的老式数据采集板大概率会看到一颗带32根引脚的并行SRAM旁边还配着电池座和一颗掉电检测芯片。这套方案在过去二十年里非常经典SRAM速度快、接口简单、读写没有寿命限制唯一的问题就是掉电就丢数据所以必须靠纽扣电池兜底。但电池在高温高湿的工业环境里是个定时炸弹——漏液、容量衰减、接触不良维护成本高得离谱。MR25H40CDF这类MRAM磁性随机存储器的出现本质上是把SRAM的速度和Flash的非易失性捏到了一起。它的存储单元用的是磁性隧道结MTJ靠电子自旋方向存储数据而不是电荷。这意味着两件事第一掉电后数据还在第二写入不需要先擦除也没有Flash那种10万次的擦写寿命焦虑理论上可以承受10的14次方次以上的写入。我手上这个项目用的是PIC18F45K40做主控通过SPI总线挂载MR25H40CDF目标是在工业数据记录场景里替代SRAM电池的老方案。选PIC18F45K40的原因很实际它自带独立的SPI模块支持模式0到模式3主频64MHz下SPI时钟可以跑到16MHz而且这颗芯片在工业温度范围-40到125摄氏度内的供货一直比较稳。MR25H40CDF容量是4Mbit也就是512KB按每秒钟记录一次、每次记录32字节算可以连续存超过4小时的高频数据或者按分钟级记录存几个月对大多数现场设备来说够用了。这篇文章我会把整个链路拆开讲从MR25H40CDF的SPI时序特征、PIC18F45K40的MSSP模块配置、读写指令集的实现到实际调试中遇到的片选毛刺、时序余量、数据完整性校验这些坑。如果你正在做工业仪表、电力监测、车载记录仪这类需要高频写入掉电不丢的场景这套组合值得认真评估。2. MR25H40CDF的SPI协议细节别把它当成普通Flash来用2.1 指令集与Flash的差异在哪里很多人第一次用MRAM会下意识地按照W25Q64那套逻辑去写代码结果发现读出来的数据全是0xFF或者乱码。问题出在指令集上。MR25H40CDF的指令集比标准SPI Flash精简得多它没有页编程Page Program、扇区擦除Sector Erase这些概念核心指令只有几条指令名称指令码功能说明WREN0x06写使能写入前必须发送WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据注意这里没有擦除指令。MRAM的写入是覆盖式的你往某个地址写新数据旧数据直接被替换不需要先擦成0xFF。这一点和Flash完全不同也是它写入速度快字节写入没有页边界限制的根本原因。但有一个细节容易忽略WREN指令必须在每次WRITE之前发送而且WREN之后如果片选拉高写使能锁存器会被复位。也就是说你不能发一次WREN然后连续写多个不相关的地址段每次WRITE操作前都要重新发WREN。我在早期版本里图省事初始化时发了一次WREN就反复写结果只有第一笔写进去了后面的全部被忽略排查了半天才发现是这个锁存器机制。2.2 时序参数里最容易被忽视的三个数MR25H40CDF的数据手册里给了不少时序参数但实际调试时真正卡人的是这三个tCLQX片选高到输出低阻的时间典型值5ns但如果你用的GPIO翻转速度不够或者片选线上有RC滤波这个时间会被拉长导致第一个时钟沿采到无效数据。tSLCH片选低到第一个时钟沿的建立时间最小20ns。PIC18F45K40的SPI模块在片选由软件控制时从拉低片选到发出第一个时钟沿之间的延迟取决于代码执行速度。如果你用C语言写CS0; SPI1_ExchangeByte(0x03);中间的函数调用开销可能只有几百纳秒看起来够但如果开了中断或者编译器优化等级低就可能踩线。tCHSH最后一个时钟沿到片选高的保持时间最小20ns。这个最容易出问题因为很多人在SPI传输完成后立刻拉高片选如果SPI时钟相位配置不对最后一个bit可能还没移完就被截断了。我的做法是在片选拉低和第一个时钟之间插入一个NOP()序列大概延迟100ns左右确保建立时间充裕。片选拉高之前也加同样的延迟。虽然理论上SPI模块的硬件时序可以保证但工业现场的温度变化会让GPIO的翻转特性漂移留余量比省那几个纳秒重要得多。2.3 状态寄存器的实际用途MR25H40CDF的状态寄存器里有一个WELWrite Enable Latch位和一个WIPWrite In Progress位。WEL位反映当前写使能锁存器的状态WIP位在写入周期内为1。MRAM的写入周期极短典型值只有几十纳秒所以WIP位几乎瞬间就归零了。但我在调试时发现一个现象如果SPI时钟太快超过10MHz而电源电压偏低比如3.0VWIP位偶尔会保持1超过1微秒这时候如果发起下一次写入数据会丢失。解决办法是在每次WRITE之后轮询RDSR确认WIP为0再继续。虽然这会增加一点开销但在工业场景里数据完整性比那几微秒的延迟重要得多。另外状态寄存器的BPBlock Protect位可以用来锁定部分地址区域防止误写。如果你的应用里有标定参数区和运行数据区可以把标定参数所在的块设成写保护只允许特定流程解锁后写入。3. PIC18F45K40的MSSP模块配置从寄存器到实际波形3.1 SPI模式选择与时钟极性相位PIC18F45K40的MSSPMaster Synchronous Serial Port模块支持SPI和I2C两种模式我们这里用的是SPI主模式。关键寄存器是SPI1CON1和SPI1CON2。SPI1CON1里的CKPClock Polarity和CKEClock Edge两位决定了SPI模式模式0CKP0CKE1空闲低第一个上升沿采样模式1CKP0CKE0空闲低第一个下降沿采样模式2CKP1CKE1空闲高第一个下降沿采样模式3CKP1CKE0空闲高第二个上升沿采样MR25H40CDF支持模式0和模式3。我选的是模式0因为PIC18F45K40在模式0下的时序余量最大而且大多数逻辑分析仪的默认解码设置也是模式0调试起来方便。配置代码大概长这样SPI1CON1bits.SSPM 0b0001; // SPI主模式时钟 Fosc/4 SPI1CON1bits.CKP 0; // 空闲低 SPI1CON1bits.CKE 1; // 第一个上升沿采样 SPI1CON1bits.SSPEN 1; // 使能SPI这里SSPM设成0b0001意味着SPI时钟是系统时钟的四分之一。如果系统时钟是64MHzSPI时钟就是16MHz。但MR25H40CDF在3.3V下的最高SPI时钟是40MHz16MHz完全在安全范围内。如果你把系统时钟降到16MHz工业场景里为了降低功耗和EMI很多人会这么做SPI时钟就只有4MHz这时候读写512KB需要的时间会明显增加需要权衡。3.2 片选控制硬件片选还是软件片选PIC18F45K40的SPI模块有一个SSSlave Select引脚但在主模式下这个引脚的行为比较特殊如果SSPM配置成某些值SS引脚会被强制作为输入一旦检测到低电平就会把SPI模块切到从模式导致通信中断。我在早期版本里把SS引脚直接接地结果SPI完全没输出查了半天数据手册才发现这个问题。正确的做法有两种一是把SSPM配置成0b0000或0b0001这两个模式下SS引脚不参与主模式控制可以当普通GPIO用二是把SS引脚配置成输出并拉高但这样会浪费一个引脚。我选的是第一种用RC0作为软件片选代码里手动控制。软件片选的好处是灵活你可以在一条总线上挂多个SPI设备用不同的GPIO做片选。但要注意片选拉低和拉高之间不能有中断打断否则SPI时序会被拉长MRAM可能误判指令。我的做法是在片选操作前后关中断传输完成后再开中断。如果传输的数据量很大比如连续读几KB可以分段传输每段之间开中断处理其他任务。3.3 时钟频率与PCB布线的配合SPI时钟频率不是越高越好。在工业现场SPI总线往往要经过连接器、排线甚至跨板传输。如果时钟频率太高信号反射和串扰会导致数据错误。我实测过在10cm的排线上16MHz的SPI时钟下用普通杜邦线连接时误码率明显上升换成带地线隔离的排线后误码率降到可接受范围。PCB布线时SPI的四根线SCK、MOSI、MISO、CS要尽量等长尤其是SCK和MISO之间的长度差要控制在5mm以内。如果MRAM和主控不在同一块板上建议在SCK和CS上串22欧姆的电阻做阻抗匹配MISO上拉10k欧姆到VCC。这些措施看起来简单但能省掉很多后期调试的麻烦。4. 读写操作的完整实现从单字节到块传输4.1 单字节写入的完整流程写一个字节到MR25H40CDF的指定地址流程如下拉低片选发送WREN指令0x06拉高片选拉低片选发送WRITE指令0x02发送24位地址MR25H40CDF是4Mbit需要22位地址但按24位发送高2位忽略发送数据字节拉高片选注意第3步和第4步之间必须拉高片选这是WREN锁存器生效的必要条件。如果你把WREN和WRITE放在同一个片选周期里WREN不会生效写入会被忽略。代码实现void MRAM_WriteByte(uint32_t addr, uint8_t data) { CS 0; SPI1_ExchangeByte(0x06); // WREN CS 1; __delay_us(1); // 给锁存器一点时间 CS 0; SPI1_ExchangeByte(0x02); // WRITE SPI1_ExchangeByte((addr 16) 0xFF); SPI1_ExchangeByte((addr 8) 0xFF); SPI1_ExchangeByte(addr 0xFF); SPI1_ExchangeByte(data); CS 1; __delay_us(1); }这里的__delay_us(1)是保险起见加的。理论上MRAM的写入周期只有几十纳秒但GPIO的翻转和信号传播需要时间加1微秒的延迟可以确保状态机稳定。4.2 连续读取的地址自动递增MR25H40CDF的READ指令支持地址自动递增你发送起始地址后可以连续读取任意数量的字节芯片内部地址指针会自动加一到达最高地址后回卷到0。这个特性非常适合做批量读取。void MRAM_ReadBlock(uint32_t addr, uint8_t *buf, uint16_t len) { CS 0; SPI1_ExchangeByte(0x03); // READ SPI1_ExchangeByte((addr 16) 0xFF); SPI1_ExchangeByte((addr 8) 0xFF); SPI1_ExchangeByte(addr 0xFF); for (uint16_t i 0; i len; i) { buf[i] SPI1_ExchangeByte(0x00); } CS 1; }注意读取时发送的0x00是哑字节因为SPI是全双工主设备必须提供时钟才能接收数据。发送什么内容无所谓MRAM会忽略MOSI上的数据。4.3 写入速度的实测数据我用逻辑分析仪抓过实际波形在16MHz SPI时钟下操作类型耗时微秒说明单字节写入约3.2包含WREN和WRITE两个片选周期32字节连续写入约22需要32次WRENWRITE因为每次WRITE只能写一个地址512字节连续读取约260包含指令和地址开销4KB连续读取约2050地址开销被摊薄接近纯数据传输速率这里有个关键点MRAM的WRITE指令不支持地址自动递增写入。也就是说你不能像读操作那样发一次WRITE指令然后连续写多个字节。每次写一个字节都要重新发WREN和WRITE。这是MR25H40CDF的一个限制也是它在高频写入场景下需要权衡的地方。如果你需要高速写入大量数据有两个思路一是用页写入模式如果芯片支持但MR25H40CDF没有二是用DMASPI的方式让DMA自动搬运数据CPU只负责发指令。PIC18F45K40没有DMA所以只能靠优化代码来提升速度。我的做法是把WRENWRITE的指令序列做成一个数组用SPI连续发送减少函数调用开销。5. 数据完整性保障校验、冗余与掉电保护5.1 CRC校验的轻量级实现工业现场电磁干扰大SPI总线上的数据可能被翻转。我在每个数据块后面附加2字节的CRC-16校验值读取时先校验再使用。CRC-16的查表法在PIC18F45K40上大概占用256字节的ROM计算1KB数据的CRC大约需要200微秒完全可以接受。uint16_t CRC16_Calculate(uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; for (uint16_t i 0; i len; i) { crc ^ (uint16_t)data[i] 8; for (uint8_t j 0; j 8; j) { if (crc 0x8000) crc (crc 1) ^ 0x1021; else crc 1; } } return crc; }如果校验失败我会把该数据块标记为无效并尝试从备份区读取。备份区就是MRAM里另一块地址空间存储同样的数据。MRAM的容量有512KB存两份数据完全够用。5.2 双区冗余与写入指针我的数据存储方案是这样的把MRAM分成两个256KB的区域A区和B区。每次写入时先写A区再写B区两个区都写成功后更新一个写入指针记录当前有效数据的位置。如果A区校验失败就从B区读如果B区也失败就认为该条数据丢失。写入指针本身也存在MRAM里用一个单独的扇区存储每次更新时采用追加写入的方式而不是覆盖。比如指针区有4KB每次写入新指针时往后移4字节写满4KB后擦除重来。这样即使在某次写入指针时掉电之前的指针记录还在可以通过扫描指针区找到最后一个有效指针。5.3 掉电检测与写入完成标志PIC18F45K40有一个HLVDHigh/Low Voltage Detect模块可以监测VDD电压。我把HLVD阈值设在3.0V当电压降到3.0V时触发中断在中断里立即停止新的写入操作并设置一个掉电标志到MRAM的特定地址。下次上电时先读这个标志如果发现上次是异常掉电就检查最后一条数据的CRC决定是否保留。这个机制的关键是中断响应时间。PIC18F45K40在64MHz下的中断延迟大约是几十个时钟周期加上HLVD模块本身的响应时间从电压降到3.0V到中断服务程序开始执行大概有几十微秒的窗口。在这段时间里如果正在写MRAM写入操作可能被中断导致数据不完整。所以我在HLVD中断里只做最紧急的事拉高片选、停止SPI、写掉电标志。其他清理工作留到上电后做。6. 实际调试中踩过的坑与排查思路6.1 片选毛刺导致的偶发写入失败现象系统运行几个小时后偶尔出现一条数据写入失败CRC校验不通过。用逻辑分析仪抓波形发现片选信号在WRITE指令发送过程中有一个约10ns的毛刺。排查过程一开始怀疑是MRAM芯片问题换了一片还是一样。后来把逻辑分析仪的采样率提到500MHz才看清毛刺出现在片选拉低后的第3个时钟周期附近。查PCB发现片选走线经过了一个过孔旁边就是SPI时钟线两者平行走了约8mm。时钟线的快速翻转通过容性耦合到了片选线上。解决办法在片选线上靠近MRAM端加一个100pF的电容到地滤掉高频毛刺。同时在PCB上把片选线远离时钟线如果无法远离就在两者之间加一条地线做隔离。改板后连续运行72小时没有再出现写入失败。6.2 SPI时钟相位配置错误导致读出的数据错位现象读出的数据整体右移了一位比如写入0xAA10101010读出来是0x5501010101。排查过程这是典型的SPI模式不匹配。MR25H40CDF支持模式0和模式3我配置的是模式0CKP0CKE1但逻辑分析仪解码设置的是模式3。改了解码设置后数据正确。但为了确认硬件配置没问题我特意用示波器看了SCK和MOSI的相位关系在片选拉低后SCK保持低电平第一个上升沿时MOSI上的数据是稳定的确认是模式0。这个坑的教训是逻辑分析仪的解码设置必须和实际SPI模式一致否则你会怀疑代码、怀疑硬件最后发现是工具配置错了。6.3 电源纹波导致的写入数据随机翻转现象在电机启动的瞬间MRAM里的数据出现随机位翻转。排查过程用示波器看MRAM的VCC引脚发现电机启动时电源上有约200mV的纹波持续时间约50微秒。MRAM在电源波动时内部电荷泵可能工作异常导致写入的数据不稳定。解决办法在MRAM的VCC引脚旁边加一个10微法和一个100纳法的电容10微法用钽电容100纳法用陶瓷电容两者并联。同时把MRAM的电源和电机驱动电源分开用磁珠隔离。改完后在电机频繁启停的条件下测试数据没有再翻转。6.4 写入速度不达预期的优化过程现象按数据手册计算16MHz SPI下写入512KB应该只需要几秒钟但实际测下来花了将近30秒。排查过程用逻辑分析仪统计波形发现每次WRITE操作之间有大段的空闲时间。查代码发现我在每次写入后都调用了__delay_us(10)本意是等MRAM内部状态稳定但实际上MRAM的写入周期只有几十纳秒10微秒的延迟完全是浪费。把延迟降到1微秒后写入时间缩短到约8秒。进一步优化把WREN和WRITE之间的片选拉高时间从1微秒降到200纳秒写入时间进一步降到约5秒。这个坑的教训是不要凭感觉加延迟。数据手册给了时序参数按照参数留20%到50%的余量就够了加太多延迟只会拖慢系统。7. 这套方案适合什么场景不适合什么场景MR25H40CDF加PIC18F45K40的组合我在几个项目里用过之后总结出它的适用边界。适合的场景工业数据记录仪每秒写入几十到几百字节要求掉电不丢数据电力监测终端需要频繁更新电参数但每次更新的数据量不大车载黑匣子记录车辆状态写入频率高但总容量需求在几百KB以内。这些场景的共同特点是写入频繁、单次数据量小、对掉电可靠性要求高。不太适合的场景需要存储大量历史数据比如几MB以上的日志因为MRAM的容量有限单位成本比Flash高不少需要高速连续写入比如音频采样每秒几十KB因为MR25H40CDF的WRITE指令不支持地址自动递增每次写一个字节都要发指令效率上不去。这种场景更适合用带页写入的FRAM或者带SRAM缓存的Flash方案。另外如果你的系统对成本极其敏感MRAM的价格确实比普通SPI Flash高出一截。但如果你把电池SRAM方案的电池更换维护成本、电池漏液导致的板子报废成本算进去MRAM的综合成本未必更高。我在一个电力监测项目里做过对比原来用SRAM电池三年内因为电池问题返修了约5%的板子换成MRAM后三年返修率降到0.5%以下。这个账算下来MRAM的溢价是值得的。最后分享一个我在实际布线中总结的小经验MRAM的VCC引脚旁边一定要放一个100纳法的陶瓷电容而且这个电容要尽量靠近引脚走线长度不要超过3mm。我试过把电容放在离引脚10mm的位置结果在高温测试时出现了偶发的写入失败。把电容挪到引脚旁边后问题消失。这个细节在数据手册里不会特别强调但在工业级温度范围内电源的瞬态响应对MRAM的写入可靠性影响很大。
返回列表