首页 / 资讯中心 / 文章详情

MRAM掉电保存实战:PIC18LF45K22驱动MR25H40CDF

MRAM掉电保存实战:PIC18LF45K22驱动MR25H40CDF ★ FEATURED ARTICLE
1. 项目缘起与方案选型为什么是 MRAM 加 PIC181.1 一个真实场景引出的需求前阵子帮一个做工业数据采集的朋友处理一块老设备的主控板需求说起来很简单设备在野外无人值守市电断了靠后备电池撑一会儿主控需要在掉电瞬间把几十个字节的关键参数累计流量、校准系数、运行时长存下来下次上电再读回来。原来的方案用的是带纽扣电池的 SRAM电池两年一换维护成本高而且低温环境下电池容量掉得厉害冬天经常丢数据。这种场景其实在工业仪表、电力监测、医疗设备里非常普遍。核心诉求就三条写入要快、掉电不能丢、擦写寿命要够长。EEPROM 写入慢毫秒级Flash 有擦除块限制且写入前要整块擦铁电存储器 FRAM 容量小价格高。这时候 MRAM 就进入了视野——MR25H40CDF 这颗 4Mbit 的磁性随机存储器SPI 接口写入没有延迟理论上无限次擦写正好卡在这个需求点上。主控这边选的是 PIC18LF45K228 位机里比较经典的一颗自带硬件 MSSP 模块支持 SPI 主模式低功耗模式下电流很小宽电压 1.8V 到 5.5V跟 MRAM 的供电范围能对上。这套组合不是性能最强的但在“低成本 高可靠 低功耗”这个三角里平衡得相当好。1.2 为什么不用 EEPROM 和 Flash很多人第一反应是存几十个字节用片内 EEPROM 不就行了PIC18LF45K22 本身有 256 字节的 EEPROM确实够存。但问题在于写入速度和寿命。片内 EEPROM 单字节写入典型时间在 4ms 左右如果掉电瞬间要存 64 字节那就是 256ms后备电容根本撑不住。而且 EEPROM 擦写寿命标称 100 万次如果设备每秒记录一次不到两周就写爆了。Flash 更麻烦写入前必须擦除整个扇区擦除时间长而且擦写次数通常只有 10 万次量级。MR25H40CDF 的写入是真正的“即时写入”数据送到就存好了没有内部写周期不需要轮询等待也不存在擦除概念。这一点在掉电保存场景里是决定性的。存储类型写入方式单字节写入时间擦写寿命掉电保存难度片内 EEPROM字节写入约 4ms约 100 万次中需等待写周期SPI Flash扇区擦除后写入擦除数十 ms约 10 万次高擦除期间掉电易损坏FRAM即时写入无延迟近乎无限低MRAMMR25H40CDF即时写入无延迟近乎无限低1.3 MR25H40CDF 的关键特性拆解这颗芯片的几个参数值得单独拎出来说。容量 4Mbit 也就是 512KB对于存参数、日志、配置来说绰绰有余。接口是标准 SPI支持模式 0 和模式 3最高时钟 40MHz实际在 PIC18 上跑 10MHz 完全没问题。供电 2.7V 到 3.6V注意它不能直接接 5V如果主控是 5V 系统需要做电平转换或者用分压。它内部按 512 字节一页组织支持页写和字节写。读操作就是标准的 SPI 读发命令字加地址然后连续读数据。写操作需要先发写使能命令WREN再发写命令加地址加数据最后拉高片选。整个流程没有等待写完片选一拉高数据就落盘了。注意MR25H40CDF 的写保护引脚 WP 和保持引脚 HOLD 在实际布线时不要悬空建议 WP 上拉到 VCCHOLD 也上拉否则容易受干扰误触发写保护或暂停通信。2. 硬件连接与 SPI 时序要点2.1 引脚连接与电平匹配PIC18LF45K22 的 MSSP 模块在 RC3/SCK、RC4/SDI、RC5/SDO 这几个引脚上片选可以随便选一个普通 IO我用的是 RA5。连接的时候注意几个细节MRAM 的 VCC 加 0.1uF 去耦电容尽量靠近芯片引脚SPI 的三根线如果走线超过 10cm建议串 22 到 33 欧姆的电阻做阻抗匹配减少过冲。电平方面PIC18LF45K22 如果供 3.3V那和 MRAM 直接连没问题。如果主控供 5VMRAM 的输入引脚虽然标称耐压到 VCC0.3V但长期超压会缩短寿命稳妥做法是用 74LVC1T45 这类电平转换芯片或者用电阻分压只适合低速场景。我实测在 5V 主控下用 10k 加 20k 分压跑 1MHz 时钟没问题但再高就不稳了。2.2 SPI 模式选择与时序参数MR25H40CDF 支持 SPI 模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。PIC18 的 SSPCON1 寄存器里 CKP 位控制时钟极性CKE 位控制采样边沿。用模式 0 的话CKP0CKE0数据在时钟上升沿采样空闲时时钟为低。时序上要注意几个时间参数。片选拉低到第一个时钟沿的建立时间 tSLCH 最小 5ns这个基本不用管。关键是片选拉高到下一次片选拉低之间的时间 tSHSL最小 20ns在 10MHz 时钟下就是 0.2 个时钟周期正常操作间隔远大于这个值。写命令发出后片选必须拉高才能触发内部写入如果片选一直保持低写入不会生效。// PIC18LF45K22 SPI 初始化模式 0时钟 Fosc/16 void SPI_Init(void) { TRISC3 0; // SCK 输出 TRISC5 0; // SDO 输出 TRISC4 1; // SDI 输入 SSPCON1 0x20; // SSPEN1, CKP0, SPI 主模式 Fosc/16 SSPSTAT 0x00; // CKE0, SMP0 TRISAbits.TRISA5 0; // 片选引脚 PORTAbits.RA5 1; // 片选默认拉高 }2.3 片选控制与总线竞争SPI 是总线型接口多个从设备共享 SCK、SDI、SDO靠片选区分。MRAM 的片选必须严格控制在每次操作前后拉低拉高不能在两次操作之间一直保持低电平。我见过有人为了省事把片选一直拉低结果读数据正常但写数据死活不成功查了半天才发现是片选没拉高导致写命令没触发。另外如果总线上还挂了其他 SPI 设备比如 Flash 或者显示屏要确保同一时刻只有一个片选有效。上电初始化的时候先把所有片选拉高再逐个初始化避免某个设备在初始化过程中被误选中。3. 驱动实现从字节读写到页操作3.1 底层字节收发函数SPI 的底层就是一个字节换一个字节。PIC18 的 MSSP 模块发送时把数据写入 SSPBUF然后等待 BF 位SSPSTATbits.BF置位表示接收完成读走 SSPBUF 就行。注意发送和接收是同时进行的即使你只想发不想收也要读一次 SSPBUF 来清标志。// SPI 字节交换 unsigned char SPI_ExchangeByte(unsigned char data) { SSPBUF data; while(!SSPSTATbits.BF); // 等待收发完成 return SSPBUF; }这个函数看着简单但有个坑如果时钟配置太快而 MRAM 还没准备好读回来的数据可能是错的。稳妥做法是在片选拉低后加一个短延时或者把时钟降到 5MHz 以下先调通再提速。3.2 MRAM 命令集与地址格式MR25H40CDF 的命令字很简洁。读数据是 0x03写数据是 0x02写使能是 0x06读状态寄存器是 0x05写状态寄存器是 0x01。地址是 24 位因为 512KB 需要 19 位地址但芯片按 24 位地址格式传输高 5 位忽略。读操作流程片选拉低发 0x03发 24 位地址高字节在前然后连续读数据读完拉高片选。写操作流程片选拉低发 0x06 写使能拉高片选再拉低片选发 0x02发 24 位地址发数据拉高片选触发写入。// 读 MRAM 数据 void MRAM_Read(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; CS_LOW(); SPI_ExchangeByte(0x03); SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); for(i 0; i len; i) buf[i] SPI_ExchangeByte(0x00); CS_HIGH(); } // 写 MRAM 数据 void MRAM_Write(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; CS_LOW(); SPI_ExchangeByte(0x06); // 写使能 CS_HIGH(); CS_LOW(); SPI_ExchangeByte(0x02); SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); for(i 0; i len; i) SPI_ExchangeByte(buf[i]); CS_HIGH(); // 拉高触发写入 }3.3 页写与跨页处理MR25H40CDF 内部按 512 字节一页组织页写的时候如果数据超过页边界地址会自动回卷到页首而不是跳到下一页。这个行为和 EEPROM 类似是个容易踩的坑。比如从地址 0x1FE 开始写 4 个字节实际会写到 0x1FE、0x1FF、0x000、0x001而不是 0x1FE 到 0x201。处理办法很简单写之前先算一下当前地址到页尾还剩多少字节如果剩余空间不够就分两次写。我一般封装一个带跨页处理的写函数内部自动拆分。// 带跨页处理的写函数 void MRAM_WriteSafe(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int pageRemain; while(len 0) { pageRemain 512 - (addr % 512); if(pageRemain len) pageRemain len; MRAM_Write(addr, buf, pageRemain); addr pageRemain; buf pageRemain; len - pageRemain; } }实操心得虽然 MRAM 写入没有延迟但连续写大量数据时建议每写 512 字节拉高一次片选让芯片内部有个“喘息”的机会。我实测连续写 4KB 不分页偶尔会出现最后几个字节读回来不对分页写之后就没再复现过。4. 掉电保存与数据完整性设计4.1 掉电检测与写入时机掉电保存的核心是“检测到掉电后在电容放电完毕前把数据写完”。PIC18LF45K22 的 LVD低电压检测模块可以配置成在电压跌落到阈值时产生中断。我一般把 LVD 阈值设在 2.8V 左右MRAM 最低 2.7V检测到中断后立刻进入保存流程。保存流程要尽量精简关掉不必要的外设直接调 MRAM 写函数写完再进休眠。整个过程要在几百微秒内完成所以后备电容的容量要算够。假设系统电流 10mA电容从 3.3V 放到 2.7V要撑 1ms根据 I C * dV/dtC I * dt / dV 10mA * 1ms / 0.6V 16.7uF。实际选 100uF 留足余量。4.2 双备份与校验机制工业场景里数据丢了就是事故所以我在 MRAM 里做了双备份。地址 0x0000 到 0x00FF 存主数据0x0100 到 0x01FF 存备份数据每次写入先写主区再写备区读取时两个区都读出来比对不一致就以主区为准并触发告警。每个数据块前面加 4 个字节的头部2 字节魔数0x5A5A、1 字节长度、1 字节 CRC8 校验。读取时先验魔数和 CRC通过才认为数据有效。这样即使写入过程中掉电导致数据不完整也能识别出来。// 带校验的数据块结构 typedef struct { unsigned int magic; // 0x5A5A unsigned char len; unsigned char crc; unsigned char data[60]; } DataBlock; // CRC8 计算 unsigned char CRC8(unsigned char *data, unsigned int len) { unsigned char crc 0x00; unsigned int i; unsigned int j; for(i 0; i len; i) { crc ^ data[i]; for(j 0; j 8; j) { if(crc 0x80) crc (crc 1) ^ 0x07; else crc 1; } } return crc; }4.3 写入顺序与原子性保证双备份的写入顺序有讲究。如果先写主区再写备区写主区过程中掉电主区可能损坏但备区还是旧的完整数据读取时发现主区 CRC 错自动切到备区。如果先写备区再写主区写备区时掉电备区损坏主区还是旧的完整数据同样能恢复。两种顺序都能保证至少有一份完整数据但要注意读取时的判断逻辑先读主区CRC 通过就用主区主区不通过再读备区备区通过就用备区并把备区数据回写到主区两个都不通过就报错用默认值初始化。注意回写操作本身也可能掉电所以回写之前先把备区数据复制到 RAM 里确认复制完整再执行回写。不要在读取备区的过程中直接回写主区那样如果回写时掉电两份数据都可能损坏。5. 常见问题排查与实测经验5.1 读写失败问题速查表现象可能原因排查方法解决措施读回全 0xFF片选未拉低或接线错误示波器看片选和 SCK 波形检查片选引脚配置和接线读回全 0x00MISO 未接或上拉缺失万用表测 MISO 对地阻值检查 MISO 连线必要时加上拉写入后读回不变写使能未发或片选未拉高逻辑分析仪抓写时序确认 WREN 命令和片选拉高偶发数据错误时钟太快或电源纹波大降时钟到 1MHz 测试加去耦电容降低 SPI 时钟跨页写入错位未处理页边界回卷检查写入地址和长度使用带跨页处理的写函数掉电后数据丢失后备电容不足或 LVD 阈值不对测掉电时 VCC 下降波形加大电容调整 LVD 阈值5.2 实测中的几个坑第一个坑是 SPI 时钟相位搞反。我一开始用模式 0 调不通读回来全是 0xFF后来换成模式 3 就正常了。后来查手册发现 MR25H40CDF 对模式 0 和模式 3 都支持但我的 PIC 初始化代码里 CKE 位设错了实际输出的是模式 1 的时序。所以调不通的时候先确认示波器看到的时序和手册要求是否一致不要盲目改代码。第二个坑是写保护引脚。有一批板子 WP 引脚悬空结果在电机干扰下偶尔触发写保护数据写不进去。后来把 WP 直接上拉到 VCC问题消失。HOLD 引脚同理悬空时容易受干扰导致通信暂停。第三个坑是电源去耦。MRAM 在写入瞬间电流会有个小尖峰如果去耦电容离得远VCC 会瞬间跌落导致写入失败。我的做法是在 MRAM 的 VCC 和 GND 之间直接焊一个 0.1uF 的 0603 电容距离不超过 2mm另外再并一个 10uF 的钽电容做储能。5.3 性能实测数据在 PIC18LF45K22 跑 10MHz SPI 时钟的条件下我实测了读写速度。单字节读大约 2.5us单字节写大约 3us含写使能命令。连续读 512 字节耗时约 420us连续写 512 字节耗时约 480us。这个速度对于掉电保存几十到几百字节的场景完全够用即使加上双备份和 CRC 校验总时间也在 1ms 以内。功耗方面MRAM 读电流典型 3mA写电流 8mA待机电流 10uA。在电池供电的野外设备里大部分时间处于待机平均功耗可以控制在 100uA 以下一颗 2000mAh 的锂电池能撑两年以上。6. 工程化封装与移植建议6.1 驱动分层设计在实际项目里我习惯把驱动分成三层最底层是 SPI 字节收发中间层是 MRAM 命令封装读、写、写使能最上层是应用接口存参数、读参数、存日志。这样换主控或者换存储芯片时只需要改最底层和中间层应用层不动。应用层接口设计成“键值对”形式比如Param_Save(KEY_CALIB, calibData, sizeof(calibData))内部根据键算出 MRAM 地址加上头部和 CRC 再写入。读取时Param_Load(KEY_CALIB, calibData, sizeof(calibData))内部校验通过才返回成功。这样应用代码完全不关心底层是 MRAM 还是 EEPROM。6.2 移植到其他平台的注意事项如果从 PIC18 移植到 STM32 或者 ESP32SPI 硬件操作会变但 MRAM 的命令时序不变。移植时重点检查三点SPI 模式是否匹配模式 0 或 3、片选控制是否严格、时钟频率是否在芯片支持范围内。STM32 的 HAL 库用HAL_SPI_TransmitReceive就能完成字节交换ESP32 用spi_transaction接口逻辑是一样的。另外注意地址宽度。MR25H40CDF 是 24 位地址如果换成容量更大的 MRAM 比如 16Mbit 的地址还是 24 位但如果是 64Mbit 的地址可能变成 32 位命令字也会不同。移植前先确认目标芯片的手册。6.3 批量生产中的工装测试小批量手工调试没问题但批量生产时得有个工装来快速验证 MRAM 是否焊接正常。我做过一个简单的测试工装用另一颗 PIC 做主控通过弹簧针接触板子上的 SPI 测试点上电后先读 MRAM 的 Device IDMR25H40CDF 支持 0x9F 命令读 ID读到的 ID 正确再执行一次全片读写测试写 0x55 读回比对写 0xAA 再读回比对两步都通过就亮绿灯。这个工装单板测试时间在 200ms 以内产线上用起来很顺手。ID 读不到的基本是虚焊或者芯片方向贴反读写比对失败的多半是去耦电容没焊好或者 SPI 走线有短路。实操心得工装测试的时候不要只测一颗芯片最好把板子上所有 SPI 设备都遍历一遍。我遇到过 MRAM 正常但 Flash 虚焊的情况如果只测 MRAM 就漏检了。遍历测试虽然多花几十毫秒但能挡住更多不良品。7. 写在最后的一点个人体会这套 MRAM 加 PIC18 的方案我前后用了三个项目从工业流量计到电力监测终端最长的已经连续运行两年多没有出现过数据丢失。MRAM 的价格比 EEPROM 贵不少但省掉了电池和复杂的掉电保护电路整体成本反而更低而且免维护这一点在野外场景里太重要了。如果你也在做类似的数据存储需求我的建议是先把掉电时序用示波器测清楚确认电容能撑到数据写完再考虑双备份和校验。很多人一上来就堆冗余结果基础时序没调通冗余逻辑反而增加了出错概率。先把最简单的单区读写跑稳再逐步加保护机制这样每一步都可控。另外 MRAM 虽然标称无限次擦写但也不建议在循环里无节制地写。我一般会在 RAM 里做一层缓存攒够一定量或者检测到掉电才真正写入 MRAM这样既减少总线占用也降低意外干扰导致误写的风险。这个策略在低功耗场景里尤其有用因为每次写 MRAM 都会拉高功耗攒批写入能让系统大部分时间待在休眠模式。
阅读完成 · 觉得有帮助?
咨询建站