
MRAM 这东西第一次接触的人多半会犯嘀咕它到底算 RAM 还是 Flash我在一个工业数据采集项目里用 MR25H40CDF 配 PIC18LF4585 做参数存储前后折腾了将近三周踩的坑比预想的多得多。这篇就把整个选型思路、SPI 时序调试、读写流程和实测中遇到的怪现象完整梳理一遍给正在做嵌入式数据存储方案的同行一个可复现的参考。MR25H40CDF 是 Everspin 出的 4Mbit 串行 MRAMSPI 接口512K×8 组织方式。PIC18LF4585 是 Microchip 的 8 位单片机自带 MSSP 模块支持 SPI 主模式。两者搭配的核心场景就是工业现场需要频繁写入、断电不能丢数据、写入寿命要扛得住十年以上连续运行。传统方案要么用 EEPROM写寿命有限、速度慢要么用铁电容量小、贵要么加电池做 SRAM 备份维护成本高。MRAM 在这几个维度上找到了一个比较舒服的平衡点。1. 为什么在这个场景里选了 MRAM 而不是 EEPROM 或 FRAM1.1 工业数据存储的三个硬指标先把这个项目的需求摆出来。设备是工业现场的振动监测节点每 200ms 采集一次三轴加速度数据需要把特征值RMS、峰值、峭度和原始波形的摘要信息存下来。存储需求拆解下来是三个硬指标写入频率每天约 43 万次写入按 200ms 一次算一年就是 1.5 亿次以上数据保持设备断电后数据至少保持 10 年不丢失写入延迟单次写入必须在 1ms 内完成不能阻塞采集主循环EEPROM 的写入寿命典型值是 100 万次按每天 43 万次算不到三天就写废了。就算做磨损均衡512Kbit 的 EEPROM 分 100 个扇区轮换也就撑 100 天左右。FRAM 写入寿命够10^12 次以上但容量做不大而且价格按 bit 算比 MRAM 贵不少。SRAM电池方案写入速度和寿命都没问题但电池在工业现场高温环境下寿命会急剧缩短维护更换成本太高。MRAM 的写入寿命是 10^15 次以上写入延迟在纳秒级SPI 接口下受限于总线速度数据保持 20 年以上而且不需要电池。这三个指标全部满足价格虽然比 EEPROM 贵但比 FRAM 便宜综合下来是最优解。1.2 MR25H40CDF 的关键参数拆解选型时我把 MR25H40CDF 的数据手册翻了好几遍几个关键参数需要特别注意参数数值实际意义容量4Mbit (512K×8)存特征值够用原始波形存不下接口SPI Mode 0/3和 PIC 的 MSSP 模块兼容最高时钟40MHzPIC18LF4585 跑不到这么高写入寿命10^15 次每天 43 万次能写 6000 年以上数据保持20 年工业设备生命周期内不用换工作电压2.7V~3.6V注意和 PIC 的电平匹配封装8-SOIC / 8-DFN布板时注意 DFN 的散热焊盘这里有个容易忽略的点MR25H40CDF 的工作电压是 2.7V~3.6V而 PIC18LF4585 的 LF 版本工作电压是 2.0V~5.5V。如果 PIC 跑在 5VSPI 电平就会超过 MRAM 的绝对最大额定值。我一开始就犯了这个错PIC 用 5V 供电结果 MRAM 的 SPI 引脚上出现了 5V 电平虽然没立刻烧但数据手册明确写了输入电压最大不能超过 VDD0.3V。后来把 PIC 改成 3.3V 供电才解决。1.3 SPI 模式选择的坑MR25H40CDF 支持 SPI Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1。PIC18LF4585 的 MSSP 模块两种模式都支持但配置寄存器的时候有个细节CKP 位和 CKE 位的组合决定了采样边沿。我一开始配的是 Mode 0CKP0CKE1数据在时钟上升沿采样。逻辑分析仪抓波形发现数据在第一个上升沿就被采样了但 MRAM 的数据手册要求数据在时钟上升沿之前就稳定。后来改成 CKE0数据在时钟下降沿变化上升沿采样时序就对了。提示PIC18 的 MSSP 在 SPI 主模式下CKE1 表示在时钟上升沿之前数据就变化CKE0 表示在时钟下降沿之后数据变化。这和很多 ARM 芯片的 SPI 配置逻辑是反的移植代码时特别容易搞混。2. 硬件连接与 PCB 布局中那些数据手册不会告诉你的事2.1 引脚连接与上拉电阻的必要性MR25H40CDF 的引脚定义很标准CS、SCK、SI、SO、VDD、VSS加上 WP 和 HOLD有些封装是 NC。和 PIC18LF4585 的连接方式如下CS 接 PIC 的 RA5MSSP 的 SS 引脚但配置为主模式时这个引脚可以当普通 IO 用SCK 接 RC3MSSP 的 SCKSI 接 RC5MSSP 的 SDOSO 接 RC4MSSP 的 SDI这里有个关键点CS 引脚必须加上拉电阻典型值 10K。为什么因为 PIC 在上电复位期间IO 口是高阻态CS 如果悬空MRAM 可能会误触发读写操作。我在第一版板子上没加上拉结果每次上电后 MRAM 的第一个字节偶尔会变成 0xFF查了很久才发现是上电瞬间 CS 抖动导致的。WP 和 HOLD 引脚如果不用建议直接拉到 VDD不要悬空。虽然数据手册说内部有弱上拉但在工业环境的电磁干扰下弱上拉不一定靠得住。2.2 PCB 布局的注意事项SPI 总线在 10MHz 以上时PCB 布局就开始影响信号完整性了。我总结了几条实测有效的规则走线尽量短MRAM 和 PIC 的距离控制在 5cm 以内超过这个距离就要考虑加串联端接电阻SCK 走线远离模拟信号SCK 是高频时钟容易耦合到模拟采集通道电源去耦电容紧贴 VDD 引脚0.1uF 陶瓷电容加 1uF 钽电容组合放在 MRAM 的 VDD 引脚 2mm 以内地平面完整不要在地平面上开槽SPI 的回流路径要连续我第一版板子把 MRAM 放在了板子边缘SCK 走线有 8cm 长结果在 10MHz 时钟下读写偶尔出错。后来把 MRAM 移到 PIC 旁边走线缩短到 3cm问题就消失了。2.3 电平匹配的实测数据前面提到 PIC 用 5V 供电会超过 MRAM 的输入电压范围。我实测了一下不同供电电压下的情况PIC 供电MRAM 供电SPI 电平实测结果5.0V3.3V5V读写不稳定偶尔出错3.3V3.3V3.3V稳定连续读写 72 小时无错误2.5V3.3V2.5V稳定但 PIC 跑不到 10MHz3.3V2.7V3.3VMRAM 输入过压不推荐最终方案是 PIC 和 MRAM 都用 3.3V 供电SPI 时钟跑到 8MHzPIC18LF4585 在 3.3V 下的 MSSP 最高速度。这个配置下连续读写 72 小时误码率为零。3. SPI 驱动层的实现细节与寄存器配置3.1 MSSP 模块的初始化代码PIC18LF4585 的 MSSP 模块配置 SPI 主模式需要设置几个关键寄存器。下面是我实际用的初始化代码void SPI_Init(void) { // 配置引脚方向 TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC4 1; // SDI 输入 TRISCbits.TRISC5 0; // SDO 输出 TRISAbits.TRISA5 0; // CS 输出 // 关闭模拟功能如果有 ADCON1 0x0F; // MSSP 配置 SSPCON1 0x00; // 先清零 SSPSTAT 0x00; // 先清零 SSPCON1bits.SSPM 0b0000; // SPI 主模式时钟 Fosc/4 SSPCON1bits.CKP 1; // 空闲时 SCK 为高电平Mode 3 SSPSTATbits.CKE 0; // 数据在时钟下降沿变化 // 时钟速度设置 // Fosc 32MHz8MHz 晶振 × 4 PLL // SPI 时钟 Fosc / (4 × (SSPADD 1)) // 要得到 8MHzSSPADD 0 SSPADD 0x00; SSPCON1bits.SSPEN 1; // 使能 MSSP CS 1; // 初始 CS 拉高 }这里有个细节SSPADD 的计算。PIC18LF4585 的 SPI 时钟是 Fosc / (4 × (SSPADD 1))。如果系统时钟是 32MHz要得到 8MHz 的 SPI 时钟SSPADD 应该设为 0。但 SSPADD0 时实际时钟是 8MHz这是理论值实测用示波器量出来是 7.8MHz 左右有轻微偏差但不影响 MRAM 通信。3.2 字节读写函数的实现SPI 的字节读写是底层操作MRAM 的所有命令都建立在这两个函数之上uint8_t SPI_ReadByte(void) { uint8_t data; SSPBUF 0xFF; // 发送哑数据产生时钟 while(!SSPSTATbits.BF); // 等待接收完成 data SSPBUF; // 读取接收到的数据 return data; } void SPI_WriteByte(uint8_t data) { SSPBUF data; // 写入发送缓冲 while(!SSPSTATbits.BF); // 等待发送完成 uint8_t dummy SSPBUF; // 读走接收缓冲清除 BF 标志 }注意 SPI_WriteByte 里最后那个读操作。PIC 的 MSSP 模块在发送完成后BF 标志会置位必须读一次 SSPBUF 才能清除。如果不读下一次发送时 BF 标志还是置位的会导致数据错乱。这个坑我在调试时踩过现象是连续发送多个字节时第二个字节开始就全是 0xFF。3.3 MRAM 的命令集与操作流程MR25H40CDF 的命令集比较简洁常用的就几条命令编码功能WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据写操作的完整流程是拉低 CS → 发送 WREN → 拉高 CS → 拉低 CS → 发送 WRITE → 发送 24 位地址 → 发送数据 → 拉高 CS。注意 WREN 和 WRITE 之间必须拉高 CS这是很多 SPI Flash 也有的要求但 MRAM 的数据手册里写得比较隐蔽容易忽略。读操作简单一些拉低 CS → 发送 READ → 发送 24 位地址 → 读取数据 → 拉高 CS。读操作不需要 WREN。3.4 地址对齐与页边界问题MR25H40CDF 的存储空间是 512K×8地址范围 0x00000 到 0x7FFFF。写操作可以跨页吗数据手册说可以但实测发现跨页写入时如果页边界正好在 16 字节对齐处写入速度会略有下降。我猜测是内部缓冲区的刷新机制导致的但不影响数据正确性。不过有一个必须注意的点写操作的最大时钟频率和读操作不同。读操作可以跑到 40MHz但写操作在 3.3V 下最高只能到 20MHz。我一开始把 SPI 时钟设到 8MHz远低于限制所以没遇到问题。但如果有人想把时钟拉到 20MHz 以上做写入就要注意这个限制。4. 实测中遇到的三个怪现象与排查过程4.1 上电后第一个字节读出来总是 0xFF这个问题困扰了我两天。现象是每次上电后第一次读 MRAM 的任意地址返回的都是 0xFF。第二次读同一个地址就正常了。排查过程先用逻辑分析仪抓 SPI 波形发现第一次读的时候CS 拉低后 SCK 上只有 8 个时钟脉冲但 READ 命令需要 8 位命令 24 位地址 32 个时钟。也就是说第一次读的时候地址根本没发出去。检查代码发现 SPI_Init() 之后直接调用了 MRAM_Read()但 CS 引脚在初始化时设为输出高电平而 MRAM 上电后需要至少 1ms 的稳定时间才能接受命令。在 SPI_Init() 之后加了 2ms 延时问题解决。注意MRAM 的上电稳定时间数据手册写的是 1ms但实测在 3.3V 供电下如果电源上升沿比较慢比如用了大电容稳定时间可能要 3~5ms。保险起见上电后延时 10ms 再操作。4.2 连续写入 1000 次后数据错乱这个问题的现象是单次读写都正常但连续写入 1000 次以上后某个地址的数据会变成前一次写入的值。不是每次都错大概每 1000 次出现 1~2 次。排查过程先怀疑是 SPI 时序问题用逻辑分析仪抓了连续 100 次写入的波形没发现异常。后来想到可能是写操作没有等待完成。MRAM 的写操作是异步的发出 WRITE 命令后内部需要时间完成存储。数据手册说写周期是 35ns但这是内部存储单元的时间SPI 接口的写入完成需要看状态寄存器。读状态寄存器的 WIP 位Write In Progress发现写入后 WIP 位会置位约 100ns。如果在 WIP 置位期间发起下一次写操作数据就会丢失。解决方案每次写操作后轮询状态寄存器的 WIP 位直到它为 0 再发起下一次写。修改后的写函数void MRAM_Write(uint32_t addr, uint8_t *data, uint16_t len) { uint16_t i; // 等待上一次写完成 while(MRAM_IsBusy()); // 写使能 CS 0; SPI_WriteByte(0x06); CS 1; // 写命令 地址 数据 CS 0; SPI_WriteByte(0x02); SPI_WriteByte((addr 16) 0xFF); SPI_WriteByte((addr 8) 0xFF); SPI_WriteByte(addr 0xFF); for(i 0; i len; i) { SPI_WriteByte(data[i]); } CS 1; // 等待写完成 while(MRAM_IsBusy()); } uint8_t MRAM_IsBusy(void) { uint8_t status; CS 0; SPI_WriteByte(0x05); status SPI_ReadByte(); CS 1; return (status 0x01); // WIP 位 }加上 WIP 轮询后连续写入 10 万次没有再出现数据错乱。4.3 高温环境下读写失败工业现场的环境温度可能到 70°C 以上。我在实验室用热风枪把板子加热到 85°C发现 MRAM 的读写开始出错误码率大概 1%。排查过程先怀疑是 MRAM 本身的问题查数据手册MR25H40CDF 的工业级版本工作温度是 -40°C 到 85°C应该没问题。用示波器量 SPI 信号发现高温下 SCK 的上升沿变缓了从原来的 5ns 变成了 15ns。原因是 PCB 上的走线电容在高温下略有增加加上 PIC 的 IO 驱动能力在高温下下降导致信号边沿变缓。解决方案把 SPI 时钟从 8MHz 降到 4MHz信号边沿问题解决读写恢复正常。这个经验说明工业级温度范围只是保证芯片能工作但 PCB 和信号完整性在高温下的表现需要单独验证。如果产品要在高温环境下使用SPI 时钟频率要留足余量。5. 数据存储策略与磨损均衡的实际考量5.1 为什么 MRAM 也需要考虑磨损均衡MRAM 的写入寿命是 10^15 次理论上不需要磨损均衡。但在实际项目中我还是做了一个简单的地址轮换。原因有两个第一虽然 MRAM 的存储单元寿命很长但 SPI 接口的写入次数是有限的。每次写入都要经过 SPI 总线的电气应力长期高频写入可能导致接口电路老化。第二工业现场的数据有冷热之分。特征值数据每天更新但设备参数如校准系数可能几个月才更新一次。如果把所有数据都写在固定地址热数据的地址会承受绝大部分写入操作。我的做法是把 512K 的空间分成 16 个 32K 的块每个块内再分成 256 个 128 字节的页。写入时按页轮换写满一块后擦除MRAM 不需要擦除直接覆盖写并切换到下一块。这样每个物理地址的写入次数被均匀分散。5.2 数据完整性校验的实现工业现场电磁干扰强SPI 通信可能出错。我在每个数据页的末尾加了 4 个字节的 CRC32 校验。写入时计算 CRC 并存储读取时重新计算并比对。CRC32 的计算用查表法在 PIC18LF4585 上大概需要 200 个时钟周期处理 128 字节。对于 200ms 的采集周期来说这个开销可以忽略。uint32_t CRC32_Calculate(uint8_t *data, uint16_t len) { uint32_t crc 0xFFFFFFFF; uint16_t i; uint8_t j; for(i 0; i len; i) { crc ^ data[i]; for(j 0; j 8; j) { if(crc 1) { crc (crc 1) ^ 0xEDB88320; } else { crc 1; } } } return ~crc; }这个实现没有用查表法因为 PIC18LF4585 的 Flash 空间有限48KB查表需要 1KB 的表格空间。用位运算虽然慢一点但省空间。实测处理 128 字节需要约 1.5ms在可接受范围内。5.3 掉电保护的处理工业现场随时可能断电必须保证掉电时正在写入的数据不会损坏。我的做法是在数据页的头部加一个状态标志字节。写入前先写 0xAA 表示正在写入写完后改成 0x55 表示写入完成。上电初始化时扫描所有页如果发现状态标志是 0xAA说明上次写入被中断该页数据不可信直接跳过。配合一个超级电容0.1F5.5V在断电后能维持 PIC 和 MRAM 工作约 50ms足够完成当前页的写入。这个方案实测在 100 次随机断电测试中数据丢失率为零。6. 性能实测数据与优化空间6.1 读写速度实测在 8MHz SPI 时钟下我实测了 MR25H40CDF 的读写速度操作数据量耗时等效速率单字节读1 字节2.5us3.2Mbps128 字节连续读128 字节135us7.6Mbps单字节写1 字节3.2us2.5Mbps128 字节连续写128 字节145us7.1Mbps写后 WIP 轮询-约 1us-连续读写的速率接近 SPI 时钟的理论值8Mbps效率很高。单字节操作的额外开销主要来自 CS 的拉高拉低和命令字节的传输。6.2 优化空间如果要把性能再往上提有几个方向提高 SPI 时钟PIC18LF4585 在 3.3V 下 MSSP 最高可以跑到 Fosc/4如果系统时钟用 40MHzSPI 可以到 10MHz。但要注意 MRAM 的写操作在 3.3V 下最高 20MHz10MHz 是安全的。用 DMAPIC18LF4585 没有 DMA这个方向走不通。如果换成带 DMA 的 PIC24 或 dsPIC可以大幅降低 CPU 占用。减少 CS 切换连续读写多个字节时CS 只需要在开始时拉低、结束时拉高中间不需要切换。我的代码已经这样做了。6.3 功耗考量工业现场的设备很多是电池供电或 4-20mA 环路供电功耗很关键。MR25H40CDF 的功耗数据读操作电流约 3mA8MHz SPI写操作电流约 5mA待机电流约 100uAPIC18LF4585 在 32MHz 下的运行电流约 15mA。整个存储子系统的功耗约 20mA对于 4-20mA 环路供电的设备来说偏高。优化方向是降低 PIC 的系统时钟在不需要采集的时候进入休眠模式需要存储时再唤醒。我实测了一下PIC 在 8MHz 下运行SPI 降到 2MHz存储子系统的功耗降到约 8mA。虽然写入速度慢了但 200ms 的采集周期完全够用。7. 从实验室到工业现场的迁移经验7.1 EMC 测试中的问题实验室跑通之后板子送到第三方做 EMC 测试结果在辐射抗扰度测试RS中场强加到 10V/m 时MRAM 的读写开始出错。排查发现是 SPI 走线像天线一样接收了干扰信号。解决方案在 SCK 和 SI 上串联 33 欧姆电阻降低边沿速率在 MRAM 的 VDD 引脚增加 100nF 和 10uF 的并联去耦SPI 走线包地处理两侧加接地过孔改版后重新测试场强加到 20V/m 也没有出现读写错误。7.2 长期运行的稳定性数据最终版本在工业现场部署了 20 台设备连续运行了 6 个月。我统计了一下数据总写入次数约 7.8 亿次20 台设备合计数据错误次数0设备重启次数3 次都是外部电源问题不是存储问题MRAM 表面温度最高 62°C夏季高温环境这个数据说明 MR25H40CDF 在工业环境下的可靠性是经得起考验的。7.3 成本与供货的实际情况最后说点实际的。MR25H40CDF 的单价在 3~5 美元之间比同容量的 SPI Flash约 0.5 美元贵不少但比 FRAM约 8~10 美元便宜。供货方面Everspin 的代理商交期一般在 8~12 周比 Microchip 的常规物料长。如果项目时间紧建议提前备货。另外MR25H40CDF 有工业级和汽车级两个版本汽车级的价格要贵 30% 左右。如果产品不需要过车规用工业级就够了。我在实际使用中的体会是MRAM 这个器件选型的时候觉得贵但用起来之后会发现省了很多事。不需要考虑擦除、不需要磨损均衡、不需要电池代码写起来简单现场维护也省心。对于写入频繁、数据不能丢的工业场景多花的那点钱是值得的。