☰
MRAM掉电保存实战:PIC18LF45K22驱动MR25H40CDF
2026/10/5 20:58:43 网站建设 项目流程

1. 项目缘起与方案选型:为什么是 MRAM 加 PIC18

1.1 一个真实场景引出的需求

前阵子帮一个做工业数据采集的朋友处理一块老设备的主控板,需求说起来很简单:设备在野外无人值守,市电断了靠后备电池撑一会儿,主控需要在掉电瞬间把几十个字节的关键参数(累计流量、校准系数、运行时长)存下来,下次上电再读回来。原来的方案用的是带纽扣电池的 SRAM,电池两年一换,维护成本高,而且低温环境下电池容量掉得厉害,冬天经常丢数据。

这种场景其实在工业仪表、电力监测、医疗设备里非常普遍。核心诉求就三条:写入要快、掉电不能丢、擦写寿命要够长。EEPROM 写入慢(毫秒级),Flash 有擦除块限制且写入前要整块擦,铁电存储器 FRAM 容量小价格高。这时候 MRAM 就进入了视野——MR25H40CDF 这颗 4Mbit 的磁性随机存储器,SPI 接口,写入没有延迟,理论上无限次擦写,正好卡在这个需求点上。

主控这边选的是 PIC18LF45K22,8 位机里比较经典的一颗,自带硬件 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即时写入无延迟近乎无限低
MRAM(MR25H40CDF)即时写入无延迟近乎无限低

1.3 MR25H40CDF 的关键特性拆解

这颗芯片的几个参数值得单独拎出来说。容量 4Mbit 也就是 512KB,对于存参数、日志、配置来说绰绰有余。接口是标准 SPI,支持模式 0 和模式 3,最高时钟 40MHz,实际在 PIC18 上跑 10MHz 完全没问题。供电 2.7V 到 3.6V,注意它不能直接接 5V,如果主控是 5V 系统需要做电平转换或者用分压。

它内部按 512 字节一页组织,支持页写和字节写。读操作就是标准的 SPI 读,发命令字加地址,然后连续读数据。写操作需要先发写使能命令(WREN),再发写命令加地址加数据,最后拉高片选。整个流程没有等待,写完片选一拉高数据就落盘了。

注意:MR25H40CDF 的写保护引脚 WP 和保持引脚 HOLD 在实际布线时不要悬空,建议 WP 上拉到 VCC,HOLD 也上拉,否则容易受干扰误触发写保护或暂停通信。

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 直接连没问题。如果主控供 5V,MRAM 的输入引脚虽然标称耐压到 VCC+0.3V,但长期超压会缩短寿命,稳妥做法是用 74LVC1T45 这类电平转换芯片,或者用电阻分压(只适合低速场景)。我实测在 5V 主控下用 10k 加 20k 分压,跑 1MHz 时钟没问题,但再高就不稳了。

2.2 SPI 模式选择与时序参数

MR25H40CDF 支持 SPI 模式 0(CPOL=0,CPHA=0)和模式 3(CPOL=1,CPHA=1)。PIC18 的 SSPCON1 寄存器里 CKP 位控制时钟极性,CKE 位控制采样边沿。用模式 0 的话,CKP=0,CKE=0,数据在时钟上升沿采样,空闲时时钟为低。

时序上要注意几个时间参数。片选拉低到第一个时钟沿的建立时间 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; // SSPEN=1, CKP=0, SPI 主模式 Fosc/16 SSPSTAT = 0x00; // CKE=0, SMP=0 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/dt,C = 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 或者 ESP32,SPI 硬件操作会变,但 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 ID(MR25H40CDF 支持 0x9F 命令读 ID),读到的 ID 正确再执行一次全片读写测试,写 0x55 读回比对,写 0xAA 再读回比对,两步都通过就亮绿灯。

这个工装单板测试时间在 200ms 以内,产线上用起来很顺手。ID 读不到的基本是虚焊或者芯片方向贴反,读写比对失败的多半是去耦电容没焊好或者 SPI 走线有短路。

实操心得:工装测试的时候不要只测一颗芯片,最好把板子上所有 SPI 设备都遍历一遍。我遇到过 MRAM 正常但 Flash 虚焊的情况,如果只测 MRAM 就漏检了。遍历测试虽然多花几十毫秒,但能挡住更多不良品。

7. 写在最后的一点个人体会

这套 MRAM 加 PIC18 的方案我前后用了三个项目,从工业流量计到电力监测终端,最长的已经连续运行两年多,没有出现过数据丢失。MRAM 的价格比 EEPROM 贵不少,但省掉了电池和复杂的掉电保护电路,整体成本反而更低,而且免维护这一点在野外场景里太重要了。

如果你也在做类似的数据存储需求,我的建议是先把掉电时序用示波器测清楚,确认电容能撑到数据写完,再考虑双备份和校验。很多人一上来就堆冗余,结果基础时序没调通,冗余逻辑反而增加了出错概率。先把最简单的单区读写跑稳,再逐步加保护机制,这样每一步都可控。

另外 MRAM 虽然标称无限次擦写,但也不建议在循环里无节制地写。我一般会在 RAM 里做一层缓存,攒够一定量或者检测到掉电才真正写入 MRAM,这样既减少总线占用,也降低意外干扰导致误写的风险。这个策略在低功耗场景里尤其有用,因为每次写 MRAM 都会拉高功耗,攒批写入能让系统大部分时间待在休眠模式。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询