资讯动态

PIC18LF4525 与 MR25H40CDF 的 SPI 接口驱动与工业数据存储实践

发布时间:2026/10/5 1:18:03 来源:尧图企业网站定制
1. 项目缘起与方案选型1.1 为什么要在工业场景里折腾 MRAM 这颗怪胎做工业嵌入式的人都有一个共同的痛掉电丢数据。你辛辛苦苦采集的传感器数据、累计的运行时长、标定参数一旦断电就全没了。传统的解决方案无非是外挂一颗 EEPROM 或者 SPI Flash但这两样东西都有各自的毛病——EEPROM 写入速度慢得让人抓狂擦写寿命也就百万次级别SPI Flash 虽然容量大、便宜但写入前必须整块擦除而且擦写次数只有十万次左右频繁写入的场景下很快就报废了。MR25H40CDF 这颗芯片就不一样了。它是一颗 4Mbit 的 MRAM磁性随机存储器采用 SPI 接口最关键的特性是写入不需要擦除、写入速度接近 SRAM、擦写寿命几乎无限10^14 次以上、掉电数据不丢失。你可以把它理解成带非易失特性的 SRAM读写行为跟普通内存几乎一样但断电后数据还在。我最初接触这颗芯片是因为一个工业数据采集项目现场环境恶劣电网波动频繁设备经常意外断电。之前用的铁电存储器 FM25V05 容量太小512Kbit换成 SPI Flash 又担心擦写寿命和写入延迟。MR25H40CDF 的 4Mbit 容量、40MHz SPI 时钟、以及无延迟写入的特性正好卡在这个需求点上。1.2 PIC18LF4525 为什么成了我的主控首选PIC18LF4525 是 Microchip 的一款经典 8 位单片机44 引脚 TQFP 封装自带硬件 MSSP 模块支持 SPI 和 I2C工作电压范围 2.0V 到 5.5V低功耗版本LF 后缀特别适合电池供电或者对功耗敏感的工业节点。它的 SPI 模块支持主从模式最高时钟频率可以到 Fosc/4配合 10MHz 晶振就是 2.5MHz 的 SPI 速率虽然不算快但对于 MRAM 这种写入无延迟的存储器来说瓶颈反而在单片机这边的数据处理能力上。选它还有一个很实际的原因PIC18 系列的开发工具链成熟、资料多、抗干扰能力强。工业现场电磁环境复杂PIC18 的 I/O 驱动能力和抗噪性能在 8 位机里属于第一梯队。而且 LF 版本在 3.3V 下运行稳定跟 MR25H40CDF 的供电电压完美匹配不需要额外的电平转换电路。1.3 整体架构设计思路整个方案的核心思路很直接PIC18LF4525 通过硬件 SPI 接口连接 MR25H40CDF实现数据的非易失存储和读取。但这里面有几个关键决策点需要说清楚。第一用硬件 SPI 还是软件模拟 SPI。硬件 SPI 的优势是速度快、CPU 占用低但引脚固定PIC18LF4525 的 MSSP 模块固定在 RC3/SCK、RC4/SDI、RC5/SDO。软件 SPI 灵活但速度慢、代码量大。我最终选了硬件 SPI因为 MRAM 的写入速度优势必须靠高速 SPI 才能发挥出来软件模拟那点速率还不如直接用 EEPROM。第二片选信号怎么处理。MR25H40CDF 的 CS 引脚必须由主机控制PIC18LF4525 的硬件 SPI 模块本身不产生片选信号需要用普通 GPIO 来手动拉低/拉高。这里有个细节MRAM 的 CS 拉低后需要等待至少 5ns 才能开始传输时钟拉高后需要等待至少 5ns 才能开始下一次操作。虽然 PIC18 的指令周期远大于这个时间但如果你用的是高速主控这个时序必须注意。第三电源和去耦。MRAM 对电源噪声比较敏感尤其是在写入瞬间电流会有波动。我在 VDD 引脚旁边放了 0.1uF 和 1uF 的陶瓷电容尽量靠近芯片引脚。工业现场如果有大功率设备频繁启停建议再加一个 10uF 的钽电容做储能。2. 硬件连接与底层驱动细节2.1 引脚连接与硬件设计要点MR25H40CDF 采用 8 引脚 SOIC 封装引脚定义很标准VDD、VSS、CS、SCK、SI、SO、WP写保护、HOLD保持。实际使用中WP 和 HOLD 如果不用可以接 VDD但我的建议是至少把 WP 引出来接到 GPIO这样可以在固件升级或者关键数据保护时临时锁死写入。与 PIC18LF4525 的连接方式如下MR25H40CDF 引脚PIC18LF4525 引脚说明VDDVDD (3.3V)电源正极VSSVSS电源地CSRC2 (GPIO)片选低有效SCKRC3 (SCK)SPI 时钟SIRC5 (SDO)主机输出从机输入SORC4 (SDI)主机输入从机输出WPRC1 (GPIO)写保护低有效HOLDVDD不使用时接高这里有个容易踩坑的地方PIC18LF4525 的 SDO 和 SDI 引脚方向容易搞混。SDO 是单片机输出、MRAM 输入所以接 MR25H40CDF 的 SISDI 是单片机输入、MRAM 输出所以接 SO。我见过不止一个新手把这两根线接反结果读出来全是 0xFF 或者 0x00。PCB 布局上SPI 的四根信号线尽量等长、靠近走线SCK 作为时钟线要远离模拟信号。如果工业现场干扰严重可以在 SCK 和 CS 上串联 22Ω 到 100Ω 的电阻做阻抗匹配和限流。MRAM 芯片尽量靠近单片机放置走线长度控制在 5cm 以内。2.2 SPI 模式与时钟配置MR25H40CDF 支持 SPI 模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。我一般用模式 0因为这是最通用的配置大多数 SPI 从设备都支持。在 PIC18LF4525 上配置 MSSP 模块为 SPI 主模式关键寄存器设置如下// SPI 主模式配置时钟 Fosc/4 SSPCON1 0x20; // SSPEN1, CKP0 (空闲低), SSPM0000 (Fosc/4) SSPSTAT 0x40; // CKE0 (数据在时钟上升沿采样), SMP0 (中间采样)如果系统时钟是 10MHzSPI 时钟就是 2.5MHz。MR25H40CDF 最高支持 40MHz所以 2.5MHz 完全在安全范围内。但如果你用 40MHz 晶振SPI 时钟就是 10MHz这时候要注意 PCB 走线和信号完整性必要时降低到 Fosc/16。注意PIC18LF4525 的 MSSP 模块在 SPI 主模式下SSPSTAT 的 BF 位表示缓冲区满传输完成判断要轮询这个位或者用中断。我习惯用轮询代码简单可靠。2.3 MRAM 指令集与操作时序MR25H40CDF 的指令集很精简常用的就几条指令名称指令码说明WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据写操作之前必须先发 WREN 指令否则数据写不进去。这是很多新手容易忽略的一步——直接发 WRITE 指令结果数据没写进去读出来还是旧值。读操作不需要 WREN直接发 READ 指令加地址就行。时序上MRAM 跟普通 SPI Flash 最大的区别是没有忙等待。SPI Flash 写入后需要轮询状态寄存器等待写完成MRAM 写入是立即生效的发完数据拉高 CS 就完成了。这意味着你可以连续写入而不需要任何延迟这在高速数据记录场景下非常爽。地址是 19 位4Mbit 512KB分三个字节发送A18-A16 在第一个字节的低 3 位A15-A8 在第二个字节A7-A0 在第三个字节。读和写都是这个格式。3. 固件实现与核心代码解析3.1 SPI 底层读写函数先写两个最基础的字节收发函数后面所有操作都建立在这上面unsigned char SPI_ExchangeByte(unsigned char data) { SSPBUF data; // 写入发送缓冲区 while (!SSPSTATbits.BF); // 等待传输完成 return SSPBUF; // 读取接收缓冲区 }这个函数同时完成发送和接收SPI 的全双工特性就在这里体现。注意SSPSTATbits.BF在读取 SSPBUF 后会自动清零所以不需要手动清除标志。片选控制用宏定义方便移植#define MRAM_CS_LOW() (LATCbits.LATC2 0) #define MRAM_CS_HIGH() (LATCbits.LATC2 1) #define MRAM_WP_LOW() (LATCbits.LATC1 0) #define MRAM_WP_HIGH() (LATCbits.LATC1 1)3.2 写使能与状态检查每次写操作前必须发 WREN我把它封装成一个函数void MRAM_WriteEnable(void) { MRAM_CS_LOW(); SPI_ExchangeByte(0x06); // WREN MRAM_CS_HIGH(); }读状态寄存器可以检查 WEL 位写使能锁存是否置位确认 WREN 生效unsigned char MRAM_ReadStatus(void) { unsigned char status; MRAM_CS_LOW(); SPI_ExchangeByte(0x05); // RDSR status SPI_ExchangeByte(0xFF); MRAM_CS_HIGH(); return status; }状态寄存器的 bit1 是 WELbit0 是 WIP写进行中。MRAM 的 WIP 几乎永远是 0因为写入太快了。但 WEL 位在每次 WREN 后置 1写完数据后自动清零这个行为跟 SPI Flash 一致。3.3 单字节写入与读取写入一个字节到指定地址void MRAM_WriteByte(unsigned long addr, unsigned char data) { MRAM_WriteEnable(); MRAM_CS_LOW(); SPI_ExchangeByte(0x02); // WRITE SPI_ExchangeByte((addr 16) 0x07); // A18-A16 SPI_ExchangeByte((addr 8) 0xFF); // A15-A8 SPI_ExchangeByte(addr 0xFF); // A7-A0 SPI_ExchangeByte(data); MRAM_CS_HIGH(); }注意地址的高 3 位用 0x07掩码因为只有 19 位地址。如果你传的地址超过 0x7FFFF高位会被截断实际写入的是取模后的地址。这个坑我在调试时踩过——数组越界写到了错误地址读出来数据对不上查了半天才发现是地址没做边界检查。读取一个字节unsigned char MRAM_ReadByte(unsigned long addr) { unsigned char data; MRAM_CS_LOW(); SPI_ExchangeByte(0x03); // READ SPI_ExchangeByte((addr 16) 0x07); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); data SPI_ExchangeByte(0xFF); // 发送哑元数据接收返回值 MRAM_CS_HIGH(); return data; }3.4 页写入与连续读取优化单字节写入效率太低每次都要发指令地址实际数据传输只占一小部分。MR25H40CDF 支持连续写入只要 CS 保持低电平地址会自动递增。我封装了一个多字节写入函数void MRAM_WriteBuffer(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; MRAM_WriteEnable(); MRAM_CS_LOW(); SPI_ExchangeByte(0x02); SPI_ExchangeByte((addr 16) 0x07); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); for (i 0; i len; i) { SPI_ExchangeByte(buf[i]); } MRAM_CS_HIGH(); }这里没有页边界限制——MRAM 不像 Flash 那样有 256 字节页的限制可以跨页连续写地址自动递增到 0x7FFFF 后回绕到 0x00000。这个特性在记录连续数据流时非常方便。连续读取同理void MRAM_ReadBuffer(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; MRAM_CS_LOW(); SPI_ExchangeByte(0x03); SPI_ExchangeByte((addr 16) 0x07); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); for (i 0; i len; i) { buf[i] SPI_ExchangeByte(0xFF); } MRAM_CS_HIGH(); }实测下来用 2.5MHz SPI 时钟连续读取 512 字节大约需要 1.7ms写入同样长度约 1.8ms多一个 WREN 指令。这个速度对于工业数据记录来说绰绰有余。3.5 数据完整性校验设计工业场景下数据完整性至关重要。我在每个数据块后面加了 CRC16 校验写入时计算并存储读取时重新计算比对。如果 CRC 不匹配说明数据损坏可以触发重写或者报警。unsigned int CRC16_Calculate(unsigned char *data, unsigned int len) { unsigned int crc 0xFFFF; unsigned int i, j; for (i 0; i len; i) { crc ^ data[i]; for (j 0; j 8; j) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; }存储结构设计成数据区 CRC 区。比如每 64 字节数据配 2 字节 CRC总共 66 字节为一个记录单元。读取时先读 66 字节然后校验前 64 字节的 CRC 是否等于后 2 字节。实操心得CRC 多项式我用的 0xA001Modbus CRC16工业领域通用查表法和计算法都成熟。如果你对速度有要求可以预生成 256 项的 CRC 表放在 ROM 里计算速度能快 5 倍以上。4. 工业场景下的可靠性设计与问题排查4.1 掉电保护与写入原子性MRAM 虽然写入速度快但在极端情况下比如电源跌落过程中刚好在写数据仍然可能出现部分写入。为了保证关键数据的原子性我采用了双备份标志位的策略。具体做法是把关键参数存两份分别放在地址 0x00000 和 0x40000。每个备份区前面加一个 4 字节的标志头写入时先写备份区 B再写标志最后写备份区 A。读取时优先读 A如果 A 的标志无效或 CRC 错误则读 B。这样即使写入过程中掉电至少有一个备份是完整的。标志头设计成0x55AA55AA 表示有效其他值表示无效。写入流程擦除 B 区标志写 0x00000000写 B 区数据写 B 区标志为 0x55AA55AA擦除 A 区标志写 A 区数据写 A 区标志为 0x55AA55AA这个顺序保证任何时候掉电至少有一个区的标志是有效的。虽然 MRAM 不需要擦除但擦除标志就是写 0效果一样。4.2 SPI 通信异常排查调试 SPI 通信时最常遇到的问题就是读出来全是 0xFF 或者 0x00。我整理了一个排查清单现象可能原因排查方法读出全 0xFFMISO 线未连接或从机未供电检查 SO 引脚连接和 MRAM 供电读出全 0x00时钟极性/相位配置错误尝试 SPI 模式 0 和模式 3数据偶尔错误时序余量不足或干扰降低 SPI 时钟加去耦电容写入后读回旧值WREN 未发送或 CS 时序错误检查 WREN 后 WEL 位是否置 1地址错位地址字节顺序错误确认先发高字节再发低字节我遇到最诡异的一次问题是SPI 通信正常但写入 100 次里总有 1-2 次数据错误。后来用示波器抓波形发现CS 拉高时 SCK 上有一个毛刺导致 MRAM 误判了一个时钟沿。解决方法是在 CS 拉高之前先把 SCK 拉低配置为 GPIO 输出低然后再拉高 CS。这个细节在数据手册里没有明确写但实测有效。4.3 电源管理与低功耗设计工业现场很多设备是电池供电或者用能量收集方案功耗很关键。MR25H40CDF 的待机电流只有 10uA 左右写入电流约 15mA读取电流约 5mA。PIC18LF4525 在 3.3V、10MHz 下运行电流约 5mA休眠模式可以降到 1uA 以下。我的低功耗策略是平时单片机休眠定时器唤醒后采集数据写入 MRAM然后继续休眠。MRAM 不需要额外的掉电保护电路因为数据本身就是非易失的。整个系统平均功耗可以控制在 100uA 以内用两节 AA 电池能跑好几年。注意MRAM 的 CS 引脚在单片机休眠时应该保持高电平否则 MRAM 可能进入低功耗模式但 SPI 接口仍然耗电。我在 CS 上加了 100kΩ 上拉电阻确保单片机 I/O 高阻时 CS 被拉高。4.4 常见问题速查表问题原因分析解决方案写入数据不生效未发 WREN 或 WP 引脚为低发 WREN检查 WP 引脚电平读取速度慢SPI 时钟配置过低提高 SSPCON1 分频比用 Fosc/4数据随机翻转电源噪声或地弹加去耦电容优化 PCB 地平面高温下数据丢失超出工作温度范围MR25H40CDF 工业级 -40~85°C确认环境温度多设备 SPI 冲突片选信号未正确隔离每个从设备独立 CS空闲时拉高写入后立即读取错误CS 拉高时序太快CS 拉高后加 10ns 延时再操作4.5 实测性能数据与经验总结我用逻辑分析仪抓了实际波形在 2.5MHz SPI 时钟下单字节写入含 WREN约 6.4us单字节读取约 5.2us512 字节连续写入约 1.8ms512 字节连续读取约 1.7ms写入 10 万次后读取校验无错误断电 1000 次后数据保持无丢失这些数据跟数据手册标称值基本一致。MRAM 的写入寿命是 10^14 次按每天写 100 万次算能用 270 年实际项目中根本不用担心寿命问题。踩过的坑主要集中在这几个方面一是 CS 时序一定要在 SCK 稳定后再操作 CS二是地址边界19 位地址最大 0x7FFFF超出会回绕三是 WP 引脚如果悬空可能被干扰拉低导致写入失败一定要接固定电平。这个方案后来在三个工业现场跑了两年多经历了夏天高温、冬天低温、电网波动、电磁干扰数据没丢过。MRAM 这颗芯片虽然比 EEPROM 贵不少但在关键数据存储场景下省下来的维护成本和数据丢失风险完全值回票价。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑