拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

MR25H40CDF MRAM与PIC18LF46K80:工业不掉电存储的SPI读写方案

MR25H40CDF MRAM与PIC18LF46K80:工业不掉电存储的SPI读写方案

在做工业嵌入式设备的这些年间,数据存储始终是最让我头疼的环节之一。现场设备要掉电保存参数、要记录运行日志、要频繁更新配置,MR25H40CDF 这颗 SPI 接口的 MRAM 芯片配上 PIC18LF46K80 这颗带 CAN 控制器的 8 位单片机,把“掉电丢数据”和“Flash 写穿”两个老大难问题一次解决了。这篇就把选型依据、硬件接线、C 语言读写实现、现场调试踩过的坑完整记录下来,给做工业控制、仪器仪表和车载电子的同行一个可直接抄作业的参考方案。

我最早接触到这对组合,是在一个需要连续记录设备运行状态的项目里。原来的方案用 SPI NOR Flash 存日志,结果现场反馈回来一堆坏块和丢数据的问题,查到最后发现是写入频率太高,Flash 的擦写寿命被提前耗尽了。换 EEPROM 容量不够,换带电池的 SRAM 又怕电池漏液和定期维护,最后目光落到了 MRAM 上。MR25H40CDF 是 Everspin 的 4Mbit 串行 MRAM,磁阻随机存储,掉电不丢,写前不用擦除,写入寿命比 Flash 高了几个数量级。PIC18LF46K80 则是 Microchip 的增强型 8 位 MCU,自带 CAN 2.0B 控制器、12 位 ADC、硬件 SPI 和多个串口,配上 XC8 编译器用 C 语言开发,整体方案既工业又顺手。

1. 为什么工业现场我选了“MRAM + 8位机”这对组合

1.1 工业数据存储的三个老大难问题

先聊聊工业设备里数据存储的特殊性。消费电子产品里,Flash 存固件、EEPROM 存配置,一年也写不了几次,所以寿命压力不大。但工业现场完全不是这个玩法。设备可能每隔几秒就记一条运行数据,一天下来就是上万次写入;操作工可能一天改十几次工艺参数,每次都要保存到非易失存储里。普通 NOR Flash 的写入寿命通常在 10 万次到 100 万次之间,看着不少,但按照这个频率算,几个月到一两年就写穿了。写穿之后要么数据错乱,要么整个扇区变成只读,现场设备只能停机返厂,损失远超一片芯片的价格。

第二个问题是写入速度。NOR Flash 写数据之前要先擦除,一个扇区擦除动辄几十到几百毫秒。如果设备突然断电,系统需要在极短时间内把关键现场数据保存下来,备电电容只能撑几十毫秒,Flash 根本来不及完成擦除再写入的操作。EEPROM 倒是可以按字节写,但单次写的等待时间也要 3 到 5 毫秒,而且大容量 EEPROM 价格偏高,容量还往往不够用。

第三个问题是环境温度。很多工业控制器装在户外或高温机柜里,环境温度经常超过 70 摄氏度,部分军工和车载场景要求 -40 到 125 摄氏度。民用级 Flash 芯片在高温下数据保持时间会大幅缩短,最后演变成“写进去当时是好的,过几天就丢数据”这种最难查的故障。MRAM 是磁阻存储,靠磁性状态保存数据,天然不受温度导致电荷泄漏的影响,高温下的数据保持特性比 Flash 和 EEPROM 靠谱得多。

1.2 为什么选 PIC18LF46K80 而不是随便一颗 ARM

先说结论:这个项目不是跑不了 ARM,而是没有必要。工业控制器讲究的是稳定、供货周期长、外设匹配。PIC18LF46K80 这颗片子有 64KB 程序闪存、3.7KB 数据 RAM、1KB EEPROM,集成了 CAN 2.0B 控制器、12 位 ADC、两个 EUSART、两个 MSSP 硬件 SPI/I2C 模块、多个定时器和 CCP/ECCP 模块。对于大多数工业数据采集和控制场景,这个外设规模完全够用,主频 64MHz,处理 PID 算法、Modbus 协议、CAN 报文收发绰绰有余。

LF 后缀代表低电压版本,工作电压 1.8V 到 3.6V,这样可以直接使用 3.3V 供电,和 MR25H40CDF 的电源轨完全一致,不需要额外的电平转换芯片。这一点在实际硬件设计里能省掉很多麻烦。如果选 5V 供电的 PIC18F46K80,MRAM 是 3.3V 器件,SPI 信号电平匹配就得额外做处理,要么加转换芯片,要么所有 IO 配置成开漏加上拉,徒增复杂度。

另外要夸一下 Microchip 的供货策略。8 位 PIC 系列生命周期极长,很多型号官方承诺 15 年以上供应周期。这对工业产品是刚需,设备要卖十年,芯片不能五年后停产。ARM 内核的 MCU 虽然性能强,但选型时要花大量精力评估供货稳定性、停产风险、开发工具链。PIC 配合 MPLAB X IDE 和免费的 XC8 编译器,一个人从零开始写工程,半天就能点亮。对老工程师来说是老朋友,对新手来说学习曲线也很平缓。

还有一个容易被忽视的理由:PIC18LF46K80 自带 CAN 控制器。工业现场设备互通,CAN 总线是绝对主力,尤其是车辆、工程机械、分布式控制系统。这颗芯片把 CAN 控制器集成在内部,外围只需要加一个 CAN 收发器芯片就能组网,成本低,抗干扰能力也比普通串口好。数据采集、存储、上报、组网一次搞定,整个方案的物料清单非常简洁。

2. 关键硬件设计与电路连接细节

2.1 MR25H40CDF 芯片特性与引脚说明

MR25H40CDF 是一颗 4Mbit 的串行 MRAM,也就是 512KB 存储空间,供电电压 3.3V,SPI 接口,最高串行时钟可达 40MHz 级别(具体以数据手册为准),支持 SPI Mode 0 和 Mode 3。它的指令集设计成和传统 SPI NOR Flash 高度兼容,所以大多数现成的 SPI Flash 驱动代码稍作修改就能跑起来,这对快速移植非常友好。

芯片一共 8 个引脚,常见 DFN8 封装或者 SOP8 封装,引脚定义很清楚。CS# 是片选,低电平有效;SCK 是串行时钟;SI 是串行输入(MOSI);SO 是串行输出(MISO);WP# 是写保护,低电平有效;HOLD# 是暂停输入,低电平有效;VDD 和 VSS 是电源和地。这两个控制引脚在普通读写场景下一定要处理妥当,很多新手栽跟头就栽在这里。

引脚名称功能说明连接建议
CS#片选,低电平选中接 MCU GPIO,加 10k 上拉到 VDD
SCKSPI 时钟接 MCU SCK 引脚
SI串行数据输入接 MCU SDO(MOSI)
SO串行数据输出接 MCU SDI(MISO)
WP#写保护直接接 VDD,禁用写保护
HOLD#暂停通信直接接 VDD,确保不挂起
VDD电源 3.3V加 0.1uF 和 10uF 去耦电容
VSS地接系统地

HOLD# 引脚如果悬空,在受到干扰时可能会意外拉低,导致芯片进入暂停状态,主机发数据毫无反应,表现出来就是 SPI 通信超时。WP# 悬空同样有风险,一旦被拉低,状态寄存器里的 WPEN 位会被激活,写操作直接无效。我的习惯是这两个引脚都通过 10k 电阻上拉到 VDD,上电默认锁定在高电平,从根上排除隐患。

2.2 PIC18LF46K80 侧的电路要素

PIC18LF46K80 的电源就近放置 0.1uF 和 10uF 的去耦电容,这一点在工业环境里格外重要。现场电机启停、继电器吸合都会在电源线上制造巨大的毛刺,去耦不良的板子会在高速 SPI 通信时随机出 bit 错误。所有数字 IO 的串入电阻可以按需加 33 到 100 欧姆,有效抑制边沿振铃。

SPI 接线方面,MR25H40CDF 的数据输入 SI 接 PIC 的 SDO 引脚,数据输出 SO 接 PIC 的 SDI 引脚,时钟 SCK 对 SCK,片选可以接到任意普通 GPIO。这里我建议不要把 CS# 直接接地,虽然这颗芯片只挂在一个 SPI 总线上时接地也能工作,但后期如果多挂一颗 Flash 或者传感器,CS# 就必须由主机控制。用 GPIO 控制 CS# 还有一个额外好处:即使 SPI 模块配置出了问题,软件也能通过手动拉低 CS# 来模拟时序,调试阶段极其有用。

上电时序这个问题容易忽略。MR25H40CDF 内部有上电复位逻辑,VDD 上升到稳定电平后,芯片需要一个短暂的时间来完成内部初始化,典型时间在几百微秒到几毫秒量级。如果 MCU 复位完成后立刻发起 SPI 操作,可能碰到芯片还没准备好的窗口,第一条指令就丢失了。稳妥做法是在系统初始化代码里,对 MRAM 执行片选拉低再拉高的空操作,并延时 1 毫秒以上再开始正式通信。后来的经验告诉我,这 1 毫秒的投资非常值。

3. 读写程序实现:从 SPI 协议到可跑的 C 代码

3.1 SPI 模式配置与初始化

MR25H40CDF 支持 SPI Mode 0 和 Mode 3,两者在时钟极性和相位定义上不同。PIC 的 MSSP 模块通过设置时钟极性位和时钟边沿位来匹配。我的建议是优先使用 Mode 0,即空闲时时钟为低、数据在上升沿采样。理由主要有两个:一是大部分 MCU 的硬件 SPI 默认模式就是 Mode 0,配置最简单;二是 Mode 0 和 Mode 3 在实际通信中都能正常工作,选择 Mode 0 意味着将来换用其他 MCU 平台时,代码移植改动最小。

下面这段是基于 MPLAB X + XC8 的 SPI 初始化代码,以 PIC18LF46K80 的 MSSP 模块为例。需要注意的是,不同型号的 MSSP 实例编号可能不同,使用前请查对你手上芯片数据手册中 SPI 模块的具体寄存器名。

void spi_init(void) { // 设置 SPI 引脚:SCK、SDO 为输出,SDI 为输入 TRISCbits.TRISC3 = 0; // SCK 输出 TRISCbits.TRISC5 = 0; // SDO 输出 TRISCbits.TRISC4 = 1; // SDI 输入 // CS 引脚配置为普通 GPIO 输出,初始为高电平(未选中) CS_MRAM_TRIS = 0; CS_MRAM = 1; // MSSP 配置为 SPI Master 模式,Mode 0,时钟 = FOSC / 16 SSPCON1 = 0x00; SSPCON1bits.SSPM = 0b0010; // SPI Master mode, clock = FOSC/64 SSPCON1bits.SSPEN = 1; // 使能 MSSP SSPSTATbits.SMP = 1; // 输入采样在数据输出末尾 }

关于分频系数,FOSC/64 是把 64MHz 系统频率分到 1MHz 的 SPI 时钟。这个速度对 MRAM 来说非常轻松,而且低速能显著提高抗干扰能力。调试阶段从低速开始,跑通了以后再逐步提高分频,这是一个靠谱的开发节奏。

3.2 写使能:每次写入前不能少的操作

MR25H40CDF 的指令集里,写操作前置条件是先发送写使能指令 0x06。这个设计是沿袭了标准 SPI NOR Flash 的习惯,目的是防止误写。在实际程序里,必须保证 0x06 指令发出后,紧接着发起后续的写状态寄存器或写数据指令,中间不要夹杂其他 SPI 操作。

写使能后可以通过读状态寄存器 0x05 来确认 WEL 位是否置 1。状态寄存器的 bit 1 就是 WEL(Write Enable Latch),读出来的值为 0x02 时代表写使能已经生效。这个确认步骤在调试阶段很有用,可以把问题快速定位到是外部原因还是软件原因。

void mram_write_enable(void) { CS_MRAM = 0; spi_write_byte(0x06); // WRITE ENABLE 指令 CS_MRAM = 1; } unsigned char mram_read_status(void) { unsigned char status; CS_MRAM = 0; spi_write_byte(0x05); // READ STATUS 指令 status = spi_read_byte(); CS_MRAM = 1; return status; }

写完使能、读状态、确认 WEL,再到正式写数据,这段代码看起来多做了几步,但实际价值非常大。我见过太多现场故障报告说“数据写不进去”,最后发现就是代码里漏了写使能,或者写使能和写数据之间又插了别的 SPI 占用操作,导致 WEL 被清零。

3.3 数据读取与写入的完整函数

读数据用 0x03 指令,操作最简单。MCU 先拉低 CS#,发送读指令和高 16 位地址,然后连续产生时钟,芯片会把指定地址开始的数据依次从 SO 引脚送出来。MRAM 支持连续读,不需要像 Flash 那样按页翻,读多长都行,只要主机持续给时钟。

void mram_read_data(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; CS_MRAM = 0; spi_write_byte(0x03); // READ DATA spi_write_byte((addr >> 16) & 0xFF); // 地址高字节 spi_write_byte((addr >> 8) & 0xFF); // 地址中字节 spi_write_byte(addr & 0xFF); // 地址低字节 for (i = 0; i < len; i++) { buf[i] = spi_read_byte(); // 连续读回数据 } CS_MRAM = 1; }

写数据使用 0x02 指令,逻辑上比 Flash 写简单得多,关键区别在于不需要发擦除命令,不需要等待内部擦写完成。每条指令如下:拉低 CS#,发送 0x02 指令、3 字节地址,然后连续发送要写的数据,最后拉高 CS# 事务结束。MRAM 在内部会自动完成写入,典型值在几十纳秒级别,从主机视角看,写完立刻就可以读,甚至不需要查询忙状态。

void mram_write_data(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; mram_write_enable(); // 写操作前必须发写使能 CS_MRAM = 0; spi_write_byte(0x02); // WRITE DATA spi_write_byte((addr >> 16) & 0xFF); spi_write_byte((addr >> 8) & 0xFF); spi_write_byte(addr & 0xFF); for (i = 0; i < len; i++) { spi_write_byte(buf[i]); // 连续写入数据 } CS_MRAM = 1; }

如果跨 64KB 边界,理论上需要把地址拆成两次操作。不过实际工程中我会在应用层约定好存储结构:把参数区和日志区分别规划在固定地址段,每次写入不超过 256 字节,这样既简化了驱动代码,也方便将来做数据管理。这个习惯从 Flash 时代保留下来,放 MRAM 上依然合理。

这里再补充一个使用习惯上的细节:MRAM 的写操作不需要擦除,因为它的存储单元不是靠电荷而是靠磁阻状态表示数据,写入时直接覆盖旧值。所以完全不需要像 Flash 驱动那样做“读-改-写”的操作,更不需要维护块映射表。这也是它能替代“SRAM+电池”方案的核心优势。

4. 实测性能表现与功耗评估

4.1 写入速度和寿命对比

以前用 NOR Flash 时,最难受的就是擦除操作。往 Flash 里写一页 256 字节,要先擦除整个扇区,等扇区擦除完成,再逐页编程,整个过程几百毫秒。而 MR25H40CDF 的写入是完全即时性的,数据字节被时钟打入后,内部立刻完成存储状态翻转,无需等待。实测在 1MHz SPI 时钟下传输 256 字节数据,全程耗时大约 2 毫秒出头,其中绝大多数时间花在 SPI 字节传输上,芯片内部写入时间几乎可以忽略。

把 SPI 时钟提高到 5MHz 以上,256 字节写入时间可以压到 0.5 毫秒以下。这个速度在掉电保存场景非常关键。控制器的备电电容如果只够维持 3 毫秒,Flash 方案可能连扇区擦除都完不成,而 MRAM 方案已经可以把几百字节的现场数据完整写进去了。

寿命方面是 MRAM 的绝对主场。常规 EEPROM 写入寿命标称 100 万次,SPI NOR Flash 普遍在 10 万次到 100 万次。MR25H40CDF 的标称写入耐久性在 10^14 次以上,这是什么概念?就算每微秒写一次,也要写三年多才够到寿命上限。实际工业设备每秒写 100 次,连续跑十年,也才消耗了寿命的极小一部分。这意味着在设计数据存储策略时,基本不需要写均衡算法,不需要磨损均衡逻辑,彻底省心。

4.2 功耗与数据保持

工业设备经常要求低功耗,尤其是电池供电的采集终端。MR25H40CDF 在待机状态下的功耗极低,典型待机电流在微安级别。相比带电池的 SRAM 方案,MRAM 方案省掉了电池供电电路和掉电检测电路,整个系统的静态功耗反而更低,还消除了电池漏液、更换维护、高温鼓包等一串隐患。

数据保持方面,MRAM 的优势是天然免疫于温度和辐射导致的电荷衰减。Flash 靠浮栅电荷存储数据,电荷会随时间缓慢泄漏,温度越高泄漏越快,高温环境下的数据保持时间可能缩短到几年甚至几个月。MRAM 依靠磁性隧道结的磁化方向存储数据,不存在电荷泄漏问题,数据保持时间按年和系统寿命来算。磁存储还有一个特性是抗辐射,gamma 射线、重离子对磁化状态的影响远小于对电荷存储的影响,所以在航天、军工和核工业场景,MRAM 是少数能用的非易失存储方案。

不过要提醒一句:MRAM 虽然很多指标优秀,但它和所有电子器件一样会被强磁场影响。系统设计时不要把 MRAM 芯片靠近大功率电机、电磁铁或强磁体,布置 PCB 时保持至少 1 厘米以上的距离,否则强磁场可能翻转存储单元的磁化方向,导致数据被改写。这一点在电机控制板设计时尤其要注意。

5. 常见问题与现场排查实录

5.1 读出来全是 0xFF 的排查顺序

这是我在工程中最常遇到的第一个问题,几乎每个第一次用 MRAM 的开发者都会碰到。出现读回数据全是 0xFF,按照这个顺序查,一般几分钟内能定位:

先查供电。MRAM 的 VDD 是不是 3.3V,用万用表实测芯片引脚上的电压,别只看电源指示灯亮不亮。很多开发板上电瞬间电压下降,或者在长走线末端电压跌落,芯片等于没工作。再查 CS# 引脚,确认片选信号确实被拉低,有些代码里 GPIO 初始化顺序不对,上电瞬间 CS# 悬空,之后虽然代码拉低了,但第一次事务可能已经被破坏。接下来查 HOLD# 和 WP#,这两个引脚如果悬空,在带电插拔、静电放电时可能出现低电平,芯片被挂起或写保护,表现为要么不响应,要么只能读不能写。

如果硬件检查都正常,那就是 SPI 模式不匹配。MRAM 支持 Mode 0 和 Mode 3,而 MCU 侧默认可能是 Mode 0 也可能是 Mode 1,比如某些 STM32 的 SPI 默认 CPOL 和 CPHA 配置就和 Mode 0 不一样。检查你的 SPI 模块配置,确保时钟极性和采样沿与芯片一致。用示波器看 SCK 空闲电平最直观:Mode 0 空闲为低,Mode 3 空闲为高。

5.2 数据写不进或者写完读不对

写不进的问题九成出在写使能环节。程序里发送 0x06 写使能后,必须在下一条写指令之前保持 WEL 位有效。如果中间插入了读状态寄存器以外的操作,或者 CS# 被拉高又拉低,WEL 可能被清掉。正确的做法是写使能和写数据尽量连续,公共驱动函数内部完成这两个操作,对外只暴露“写某地址某数据”的 API,不让调用者有机会在中间插入其他操作。

还有一种现象是写入后立刻读,数据是错的,但再读一次又对了。这种大概率是 SPI 时序边沿问题。MCU 采样和数据翻转的相位与芯片实际输出不匹配,导致第一位或最后一位丢失。把 SPI 时钟频率降低一半再试,如果问题消失,就是高速模式下的时序裕量不足。解决办法是调整 SMP 位和时钟分频,找到最稳定的配置。

5.3 上电瞬间的数据被篡改

这个故障很隐蔽,我一度排查了很久。现象是设备断电再上电后,MRAM 里原本保存的参数在某个固定地址上出现随机变化。查到最后发现是 MCU 复位期间 GPIO 处于高阻态,如果 CS# 引脚连接的线路有较长的寄生电容和上拉电阻,复位瞬间 CS# 可能短暂处于低电平,MRAM 检测到片选有效,同时 SCK 引脚又受到电源爬坡时的干扰跳动了几拍,结果就发生了一次意外的写操作。

解决方案很直接:给 CS# 加一个 10k 到 100k 的下拉电阻,确保 MCU 复位期间 CS# 维持在高电平,强制芯片不被选中。同时把 WP# 和 HOLD# 接 VDD,即使出现异常片选,芯片也处于写保护和正常通信状态,被误写的概率大幅下降。对于一些对可靠性要求极高的设备,还可以在上电初始化时先向关键参数区写入一串固定的校验字节,程序启动时校验失败就判定为存储区数据异常,自动恢复默认参数并重新初始化。

5.4 工业现场的电磁干扰和布线策略

MRAM 本身抗干扰能力不弱,但 SPI 总线的数据线在长距离传输时仍然会被现场电磁噪声干扰。我遇到的典型场景是把存储板通过排线连接到主控板,排线长度超过 20 厘米,结果在伺服电机启停时出现偶发的读写数据错位。排查时用示波器观察 SCK 和 SO 线上的波形,可以看到明显的振铃噪声。

针对这类问题,我的处理方案是:SPI 时钟频率从 2MHz 降到 500kHz,信号完整性大幅改善;SCK、SI、SO 三根线并联 33pF 到 50pF 对地电容,滤除高频噪声;所有 SPI 信号线在 PCB 上用地线包络,减少环路面积;如果排线较长,改用带屏蔽层的 FFC 排线,屏蔽层单端接地。这些措施全部实施后,问题彻底消失。顺便说一句,把 SPI 通信的 CS# 控制和数据校验都搬进中断里,而不是靠主循环轮询,能够显著减少因为任务调度延迟导致的时序抖动。

6. 这套方案还能怎么用:一点个人体会

项目做完之后,我把这套“SPI 接口 MRAM + 低电压 8 位 MCU”的组合方案沉淀成了模块,在后面好几个产品里直接复用。一个典型的适用场景是设备运行日志系统:PIC18LF46K80 通过 CAN 总线收集各节点数据,按时间戳写入 MRAM,掉电不丢,上电后通过 Modbus RTU 或 USB 转串口导出,上位机解析生成 CSV 文件。整个过程不需要文件系统,不需要掉电检测,不需要备用电池,硬件成本压得很低,可靠性却很高。

具体到代码路径上,我再分享一个实际经验的排序:先把 SPI 裸读写跑通,用固定数据模式反复读写校验;再把状态寄存器检查加上,确保每次写入前 WEL 总是 1;然后在应用层加 CRC16 校验字段,让每条记录独立可验证;最后再做掉电测试,特意让 MCU 在写入过程中断电,再上电去检查数据区完整性。这一套流程下来,基本上现场出现的问题都能被提前拦截。

我记得有次去客户现场处理故障,设备在高温高湿的车间里连续运行了半年,其他板卡上的 Flash 存储芯片已经出现扇区错误,而这套 MRAM 方案的设备一直稳定运行,数据一条没丢。那一刻我就觉得,选型时候较真的那些参数,最终都在长期可靠性上还回来了。如果读者朋友也在做工业数据采集和存储相关的工作,手里有 PIC 平台或者类似 SPI 主控,强烈建议试试 MR25H40CDF 这颗芯片,驱动代码半小时就能移植完,却能把你从 Flash 寿命和掉电保存的泥潭里彻底解放出来。

返回列表