ARTICLE DETAIL

资讯详情

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

工业掉电场景下MRAM数据存储方案:PIC18F46K20与MR25H40CDF SPI驱动实战

工业掉电场景下MRAM数据存储方案:PIC18F46K20与MR25H40CDF SPI驱动实战 1. 项目缘起与方案选型思考工业现场的数据记录仪、PLC 扩展模块、电力监测终端这类设备有一个绕不开的痛点频繁掉电场景下的关键数据保存。我手上这个项目就是给一台工业称重仪表做数据存储升级要求每秒记录一次称重数据断电后数据不能丢而且设备可能随时被拉闸没有任何安全关机的窗口期。原来的方案用的是 EEPROM类似 24C 系列问题很明显写入速度慢字节写入周期动辄 5ms一秒一次勉强够用但如果要提速到 10Hz 记录EEPROM 根本扛不住。后来考虑过 FRAM速度快、寿命长但容量小、价格高而且供货周期不稳定。再后来评估 SPI FlashW25Q 系列容量大、便宜但写入前要擦除扇区擦除时间长掉电时正在擦除的扇区数据全丢而且 Flash 有写寿命限制频繁写同一地址很快就坏。最终选定的方案是MR25H40CDF 这颗 MRAM磁性随机存储器主控用PIC18F46K20通过SPI 接口通信。MRAM 的核心优势在于写入不需要擦除、字节级写入、写入速度接近 SRAM、几乎无限次擦写寿命官方标称 10^14 次以上、掉电后数据保持 20 年以上。这几个特性几乎完美匹配工业数据记录的需求。PIC18F46K20 是 Microchip 的一款经典 8 位单片机64KB Flash、3936 字节 RAM、64KB 外部存储空间寻址能力自带 MSSP 模块支持 SPI 主模式主频最高 64MHz用 PLL 倍频后在工业控制领域用量极大资料齐全开发工具成熟。用它来驱动 MR25H40CDF属于老将配新兵的组合稳定性和开发效率都能兼顾。这篇文章我会把整个方案从选型逻辑、硬件连接、SPI 时序配置、读写驱动实现、掉电保护策略到实际调试踩坑完整地拆一遍。适合正在做工业嵌入式数据存储、或者想了解 MRAM 实际用法的朋友参考。即使你用的是 STM32 或者其他平台SPI 操作 MRAM 的核心逻辑是通用的移植起来不难。2. MR25H40CDF 与 PIC18F46K20 核心特性拆解2.1 MR25H40CDF 到底强在哪MR25H40CDF 是 Everspin 公司的一款 4Mbit512KBSPI MRAM。注意单位是 4Mbit换算成字节是 512KB不是 4MB这个容量在数据记录场景里够用但如果你要存大量历史曲线得做好分页管理和滚动覆盖策略。它的关键参数我整理成表格方便对照参数项数值说明容量4Mbit / 512KB按字节寻址接口SPIMode 0 和 Mode 3 都支持最高 40MHz 时钟写入方式字节级、页级均可无需擦除写入寿命10^14 次以上几乎不用考虑磨损数据保持20 年掉电不丢工作电压2.7V ~ 3.6V典型 3.3V写入周期无内部写等待写命令发出即完成封装8-SOIC / 8-DFN引脚兼容部分 SPI Flash这里要重点说无需擦除和无内部写等待这两点。SPI Flash 写入前必须先发擦除命令擦除一个 4KB 扇区典型要 50~100ms这期间芯片忙你得轮询状态寄存器等它完成。如果这时候掉电整个扇区数据可能处于不确定状态。MRAM 完全没有这个问题写命令发完数据就进去了不需要等待也不需要擦除。对于掉电频繁的工业场景这是决定性的优势。2.2 PIC18F46K20 的 MSSP 模块PIC18F46K20 内部集成了 MSSPMaster Synchronous Serial Port模块可以配置成 SPI 或 I2C。做 SPI 主设备时它支持四种 SPI 模式由 CKP 和 CKE 位组合决定时钟极性、相位都可配。最高 SPI 时钟是 Fosc/4如果系统跑 64MHzSPI 时钟最高能到 16MHz驱动 MR25H40CDF 绰绰有余。MSSP 相关寄存器主要有这几个SSPCON1控制位包括 CKP时钟极性、SSPM模式选择SPI 主模式设为 0b0000 或 0b0001SSPSTAT状态位CKE 位决定时钟边沿SSPBUF数据缓冲写入即发送读取即接收SSPADD在 SPI 主模式下用于设置时钟分频PIR1 的 SSPIF 位中断标志发送/接收完成时置位配置成 SPI 主模式的核心代码大概是这样// PIC18F46K20 SPI 主模式初始化 // 假设系统时钟 64MHzSPI 时钟 Fosc/16 4MHz SSPSTAT 0x40; // CKE1数据在时钟上升沿采样对应 SPI Mode 0 的变体需结合 CKP SSPCON1 0x20; // SSPEN1, CKP0, SSPM0000 - SPI Master, clock Fosc/4 // 如果要降速用 SSPADD 设置分频 // 注意PIC18 的 SPI 主模式时钟由 SSPADD 决定公式为 Fosc/(4*(SSPADD1)) SSPADD 3; // 64MHz / (4*(31)) 4MHz这里有个容易搞混的点PIC18 的 SPI 主模式时钟计算和 STM32 不一样。STM32 是直接分频PIC18 是通过 SSPADD 寄存器算的公式是Fosc / (4 × (SSPADD 1))。我一开始按 STM32 的思路去配结果时钟差了 4 倍读出来的数据全是乱的后来查手册才发现这个公式。2.3 为什么不用硬件片选PIC18F46K20 的 MSSP 模块在 SPI 主模式下片选信号CS需要软件控制它没有专门的硬件 CS 引脚。这一点和 STM32 的 SPI 外设不同STM32 可以配置 NSS 硬件管理也可以软件管理。PIC18 这边你只能用一个普通 GPIO 来拉 CS。软件片选的好处是灵活总线上可以挂多个 SPI 从设备每个设备用不同 GPIO 控制 CS。坏处是每次读写都要手动拉低、拉高代码里不能忘。我踩过的坑是有一次在中断里读 MRAM忘了在函数开头拉低 CS结果数据读出来全是 0xFF查了半天才发现是 CS 没动。提示软件片选时CS 拉低到第一个时钟沿之间要留至少 5ns 的建立时间MR25H40CDF 手册里叫 tSLCH。PIC18 跑 4MHz SPI 时指令周期是 62.5ns天然满足但如果 SPI 跑到 16MHz就要注意在 CS 拉低后加一个 NOP 或者短延时。3. 硬件连接与 PCB 布局要点3.1 引脚连接对照MR25H40CDF 是 8 引脚封装引脚定义和标准 SPI Flash 基本一致这给硬件替换带来了便利。具体连接如下MR25H40CDF 引脚名称连接到 PIC18F46K20说明1CSRC2GPIO软件片选低有效2SORC4SDI主入从出3WP接 VDD 或 GPIO写保护低有效4VSSGND地5SIRC5SDO主出从入6SCKRC3SCK时钟7HOLD接 VDD 或 GPIO暂停通信低有效8VDD3.3V电源WP 和 HOLD 这两个引脚如果不用硬件保护功能直接上拉到 VDD 就行。我建议 WP 用一个 GPIO 控制这样可以在固件里做写保护防止程序跑飞时误写关键区域。HOLD 一般不用直接拉高。3.2 PCB 布局的几条经验SPI 总线跑 4MHz 时PCB 布局要求不算苛刻但如果想跑到 16MHz 以上就得注意了。我总结了几条实际踩坑经验第一SCK 走线尽量短且不要和 SI/SO 长距离并行。并行走线会产生串扰SCK 的跳变会耦合到数据线上导致误码。如果必须并行中间加地线隔离。第二CS 走线不要和 SCK 靠太近。CS 是控制信号如果被 SCK 干扰可能出现误触发。我在一块板子上把 CS 和 SCK 走了并排 5cm结果偶尔读出错数据后来把 CS 绕开问题消失。第三电源去耦电容必须加。MR25H40CDF 的 VDD 引脚旁边放一个 0.1uF 陶瓷电容越近越好。MRAM 写入瞬间电流会有波动去耦不好会导致写入失败。我实测过去掉这个电容后连续写入 1000 次里会出现 1~2 次错误。第四地平面要完整。双层板的话底层尽量铺完整地平面不要被其他信号割裂。SPI 的返回电流需要地平面提供低阻抗路径。注意MR25H40CDF 的 WP 和 HOLD 引脚内部有弱上拉但不要依赖它。工业环境干扰大建议外部加 10K 上拉电阻到 VDD确保引脚状态确定。4. SPI 读写驱动实现与关键命令解析4.1 MR25H40CDF 的命令集MR25H40CDF 的命令集比 SPI Flash 简单很多因为它不需要擦除。核心命令就几个命令十六进制码功能WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据注意MRAM 的写操作必须先发 WREN 命令否则写不进去。这一点和 SPI Flash 一样。但不同的是MRAM 的 WREN 之后不需要等待直接发 WRITE 命令就行。SPI Flash 的 WREN 之后要等状态寄存器的 WEL 位置位MRAM 没这个要求。状态寄存器的 bit0 是 WEL写使能锁存bit1 是 BP0bit2 是 BP1用于块保护。读状态寄存器可以确认写使能是否生效调试时很有用。4.2 底层字节收发函数PIC18 的 SPI 收发是同一个寄存器 SSPBUF写进去就发送读出来就是接收。底层函数这样写// SPI 收发一个字节 unsigned char SPI_ExchangeByte(unsigned char data) { SSPBUF data; // 写入数据启动发送 while (!PIR1bits.SSPIF); // 等待发送/接收完成 PIR1bits.SSPIF 0; // 清除标志 return SSPBUF; // 返回接收到的数据 }这个函数是阻塞式的简单可靠。如果系统对实时性要求高可以用中断方式但 SPI 事务通常很短阻塞几微秒影响不大。4.3 读数据函数实现读 MRAM 的流程是拉低 CS - 发 READ 命令0x03- 发 3 字节地址24 位地址因为 512KB 需要 19 位地址用 3 字节对齐- 连续读数据 - 拉高 CS。// 从指定地址读取指定长度数据 void MRAM_Read(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; CS_LOW(); // 拉低片选 SPI_ExchangeByte(0x03); // READ 命令 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(); // 拉高片选 }这里地址是 24 位的虽然 MR25H40CDF 只有 512KB19 位地址但命令格式要求发 3 字节地址高位补 0 即可。如果你换成更大容量的 MRAM比如 16Mbit 的 MR25H16地址还是 3 字节代码不用改。4.4 写数据函数实现写数据的流程多一步 WREN拉低 CS - 发 WREN0x06- 拉高 CS - 拉低 CS - 发 WRITE0x02- 发地址 - 发数据 - 拉高 CS。// 向指定地址写入指定长度数据 void MRAM_Write(unsigned long addr, const unsigned char *buf, unsigned int len) { unsigned int i; // 第一步写使能 CS_LOW(); SPI_ExchangeByte(0x06); // WREN CS_HIGH(); // 第二步写数据 CS_LOW(); SPI_ExchangeByte(0x02); // WRITE 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(); }注意 WREN 和 WRITE 之间必须拉高 CS 再拉低这是 SPI 事务的分界。有些朋友图省事WREN 之后不拉高 CS 直接发 WRITE这样是不行的MRAM 会把 WREN 和 WRITE 当成一个事务命令解析出错。4.5 页写入与跨页处理MR25H40CDF 支持页写入一页是 256 字节。如果你写入的数据超过 256 字节且跨越了页边界地址会自动回绕到页首覆盖前面的数据。这是个坑我第一次用的时候没注意写了 300 字节结果后 44 字节覆盖了前 44 字节数据全乱了。解决办法是在写入函数里做跨页判断void MRAM_WriteSafe(unsigned long addr, const unsigned char *buf, unsigned int len) { unsigned int pageRemain; while (len 0) { pageRemain 256 - (addr % 256); // 当前页剩余空间 if (pageRemain len) pageRemain len; MRAM_Write(addr, buf, pageRemain); addr pageRemain; buf pageRemain; len - pageRemain; } }这个函数会自动把长数据拆成不跨页的块逐块写入。虽然多几次 CS 拉低拉高但数据安全。5. 掉电保护与数据完整性策略5.1 掉电检测电路工业现场掉电是随机的你不可能预测。所以需要一个掉电检测电路在电源电压降到阈值以下时给单片机一个中断让它在电容储能耗尽前把关键数据写进 MRAM。典型做法是用一个电压比较器比如 TL431 或者专用监控芯片设定阈值 3.0V。当 VDD 降到 3.0V 时比较器输出翻转触发 PIC18 的外部中断INT0 或 INT1。中断服务程序里立刻把 RAM 中的关键数据写入 MRAM。PIC18F46K20 的中断响应时间很快从触发到进入 ISR 大概几个微秒。MRAM 写入 256 字节按 4MHz SPI 算每字节 8 个时钟周期加上命令和地址开销大概 600 微秒。电源端加一个 1000uF 电容3.3V 降到 2.7V 的时间足够完成写入。5.2 双备份与校验即使有掉电检测也不能保证 100% 可靠。我的做法是双备份 CRC 校验。关键数据存两份地址 A 和地址 B每份后面跟一个 CRC16 校验值。读取时先读 A校验通过就用 A不通过再读 B校验通过就用 B两个都不通过用默认值并报警。typedef struct { unsigned char data[64]; unsigned short crc; } Record_t; #define ADDR_A 0x00000 #define ADDR_B 0x00100 void SaveRecord(Record_t *rec) { rec-crc CRC16(rec-data, 64); MRAM_WriteSafe(ADDR_A, (unsigned char*)rec, sizeof(Record_t)); MRAM_WriteSafe(ADDR_B, (unsigned char*)rec, sizeof(Record_t)); } int LoadRecord(Record_t *rec) { MRAM_Read(ADDR_A, (unsigned char*)rec, sizeof(Record_t)); if (CRC16(rec-data, 64) rec-crc) return 0; // A 有效 MRAM_Read(ADDR_B, (unsigned char*)rec, sizeof(Record_t)); if (CRC16(rec-data, 64) rec-crc) return 0; // B 有效 return -1; // 都无效 }这个策略的代价是存储空间翻倍但 512KB 容量下存 64 字节记录可以存 4000 条翻倍后还有 2000 条够用。5.3 写入顺序与原子性双备份写入时顺序很重要。如果先写 A 再写 B写 A 的过程中掉电A 可能损坏但 B 还是旧的完整数据读取时 B 能通过校验。如果先写 B 再写 A同理。关键是两份数据不能同时处于写入状态。我的做法是先写 A延时 1ms再写 B。这个延时确保 A 的写入完全结束。MRAM 写入本身很快1ms 足够。如果掉电发生在写 A 期间A 损坏B 完好如果发生在写 A 之后、写 B 之前A 是新数据B 是旧数据读取时 A 校验通过用 A如果发生在写 B 期间A 是新数据B 损坏读取时 A 通过用 A。所有情况都能保证至少有一份完整数据。6. 实际调试中的常见问题与排查6.1 读出来全是 0xFF 或 0x00这是最常见的问题原因通常有三个第一CS 没拉低。检查代码里 CS_LOW() 是否执行。我遇到过宏定义写错CS_LOW() 实际是拉高结果读出来全是 0xFF。第二SPI 模式不对。MR25H40CDF 支持 Mode 0 和 Mode 3。Mode 0 是 CPOL0, CPHA0Mode 3 是 CPOL1, CPHA1。PIC18 的 CKP 和 CKE 组合要对应。如果模式错了数据会移位读出来全是乱码。我建议先用 Mode 0CKP0, CKE1PIC18 的 CKE 定义和标准 CPHA 相反注意看手册。第三时钟太快。MR25H40CDF 最高 40MHz但如果你 PCB 布局不好或者杜邦线连接4MHz 以上就可能出错。调试时先把 SPI 降到 1MHz确认能读写后再提速。6.2 写入后读出来不对写入数据后立刻读发现数据不对可能的原因WREN 没发或者没生效。读状态寄存器确认 WEL 位是否为 1。如果 WREN 后 WEL 还是 0说明 WREN 命令没被正确接收检查 CS 时序。跨页写入。前面说过超过 256 字节且跨页会回绕。用 MRAM_WriteSafe 函数避免。写入过程中被中断打断。如果 SPI 事务被高优先级中断打断CS 可能被意外拉高导致写入不完整。解决办法是在 SPI 事务期间关中断或者用互斥锁保护。6.3 掉电后数据丢失如果掉电检测电路正常但数据还是丢了检查掉电中断优先级。确保掉电中断是最高优先级不会被其他中断阻塞。写入时间是否足够。用示波器看 VDD 下降曲线确认从掉电中断触发到电压降到 MRAM 最低工作电压2.7V之间的时间是否大于写入所需时间。如果不够加大电容或者减少每次写入的数据量。MRAM 的 WP 引脚状态。如果 WP 被拉低写入会被禁止。检查 WP 引脚是否意外被拉低。6.4 常见问题速查表现象可能原因排查方法读出全 0xFFCS 未拉低、SPI 模式错、时钟太快查 CS 宏、降速、换模式读出全 0x00MISO 未连接、从设备未供电查硬件连接、测 VDD写入无效WREN 未发、WP 拉低、跨页读状态寄存器、查 WP、用安全写函数数据偶尔出错电源干扰、CS 串扰、去耦不足加去耦电容、改 PCB 走线掉电丢数据中断优先级低、电容不够调中断、加大电容、减写入量7. 性能实测与优化建议7.1 实测数据我在 4MHz SPI 时钟下做了实测结果如下操作数据量耗时备注读256 字节约 520us含命令和地址开销写256 字节约 540us含 WREN 和 WRITE读64 字节约 140us适合高频记录写64 字节约 150us每秒 100 次无压力如果把 SPI 提到 16MHz耗时会降到 1/4 左右。但 16MHz 对 PCB 布局要求高杜邦线连接肯定不行必须 PCB 走线。7.2 优化建议第一批量写入。如果每秒记录一次不要每次只写 64 字节可以攒 10 次写一次 640 字节。虽然单次写入时间长了但总开销CS 拉低拉高、WREN减少了。实测批量写入 640 字节比 10 次写 64 字节节省约 30% 时间。第二用 DMA。PIC18F46K20 没有 DMA但如果你换成 PIC24 或者 dsPIC 系列可以用 DMA 驱动 SPICPU 占用率大幅降低。不过对于这个项目4MHz SPI 下 CPU 占用率本来就不高DMA 不是必须的。第三地址对齐。MRAM 支持任意地址读写但如果你按页对齐256 字节边界写入可以避免跨页拆分代码更简单速度也略快。第四减少 WREN 次数。如果连续写多个不连续地址可以在第一次 WREN 后连续发多个 WRITE 命令只要 CS 不拉高WEL 位保持有效。但要注意每次 WRITE 之间要拉高 CS 再拉低否则命令解析会出错。这个技巧在写多个分散数据块时有用。8. 方案扩展与移植思路这套方案的核心是 SPI 读写 MRAM换平台主要改 SPI 底层驱动。如果你用 STM32把 SPI_ExchangeByte 换成 HAL_SPI_TransmitReceive 就行上层读写逻辑不用动。如果你用 ESP32SPI 主机驱动更复杂一些但原理一样。MR25H40CDF 的容量是 512KB如果不够用可以换 MR25H162MB或者 MR25H40512KB 的工业级版本。命令集完全兼容代码不用改只需要注意地址范围。如果项目对成本敏感可以考虑用 FRAM 替代 MRAM比如 FM25V05512Kbit价格便宜一些但容量小、速度慢一点。或者用带电池备份的 SRAM成本更低但电池需要维护工业现场不太喜欢。最后分享一个我在实际项目中的小技巧在 MRAM 的末尾区域存一份配置参数和校准数据这些数据不常变但很重要。用 WP 引脚保护这个区域防止程序跑飞时误写。配置区用单独的 CRC 校验启动时读取校验失败就用默认值并记录故障标志。这样即使 MRAM 部分区域损坏设备还能以默认参数运行不会完全瘫痪。
返回列表