搞嵌入式这么多年,凡是涉及“参数保存”“掉电存储”“运行日志”的项目,我第一反应都是外挂一颗 Flash 或者 EEPROM。但最近做一套工业变送器的数据记录模块,我把方案彻底换成了 MRAM:Everspin 的 MR25H40CDF,4Mbit 串行接口,搭配 Microchip 的 PIC18F46K42 做主机。这套组合解决了一个特别熬人的问题——设备每分钟都要写一次运行状态,产品设计寿命十年以上,普通 EEPROM 的擦写寿命根本扛不住,而 MRAM 几乎“写不死”,又能像普通 SPI 存储芯片一样简单操作。接下来我会把硬件接线、MCC 配置、SPI 驱动代码、可靠写策略以及调试中踩过的坑全部整理出来,希望对做工业仪表、车载设备或者需要频繁掉电保存的嵌入式项目有点参考价值。
1. 为什么工业数据存储场景里,我放弃了 Flash 改用 MRAM
1.1 NOR Flash 的“先擦后写”和寿命上限
很多工程师习惯性把 SPI NOR Flash 当成大容量存储的首选,确实,大容量、低成本、生态成熟,但一旦遇到“频繁小数据更新”,Flash 的弊端就暴露了。NOR Flash 在写入之前必须先擦除,而且擦除以扇区为单位,哪怕你只想改 1 个字节,也得先把整个扇区搬到 SRAM、擦掉、再整体写回去。这个机制带来的问题不只是速度慢,更致命的是擦除次数限制:一般 SPI NOR Flash 的擦写寿命在 10 万次左右,工业设备每天写几十次,几年就到寿命边界了。就算你做磨损均衡,日志型应用也会把寿命问题变得非常复杂。
EEPROM 看起来好一些,支持字节级覆写,不用先擦除,但容量普遍偏小,寿命大多只有 100 万次。以本文这个项目为例:每分钟写一次运行数据,一天 1440 次,一年约 52 万次,两年就把普通 EEPROM 的寿命耗尽了。选型时我算了一笔账,如果继续用 EEPROM,要么缩小记录频率,要么增加多份轮换存储,都会增加固件复杂性,还未必可靠。
1.2 MR25H40CDF 的核心卖点
MR25H40CDF 是 Everspin 的串行 MRAM,4Mbit 容量,也就是 512KB,工作在 2.7V 到 3.6V,SPI 接口支持 Mode 0 和 Mode 3。它最大的特点是存储单元基于磁阻效应,而不是电荷。既然不是电荷,就不存在“电荷泄漏”和“擦除老化”的问题,掉电后数据靠磁性状态保存,写入时直接覆盖旧值,既不需要扇区擦除,也没有写前读-改-写那一套。
从指标上看,MRAM 的写寿命通常在 10 的 14 次方以上,实际工程中可以当“无限寿命”来用,数据保持时间在工业温度范围内也能达到 20 年以上。更关键的体验是写入速度,同样通过 SPI 发送命令和数据,Flash 写完往往还要等内部擦写时间,MRAM 基本是命令发完数据就持久化了,对我这种要频繁记录现场数据的场景来说,固件逻辑会简单很多。
| 存储类型 | 写入方式 | 典型寿命 | 是否需要擦除 | 典型容量 |
|---|---|---|---|---|
| 串行 EEPROM | 字节覆盖 | 约 100 万次 | 否 | 几 KB 到几 MB |
| SPI NOR Flash | 扇区擦除后写 | 约 10 万次 | 是 | 几 MB 到几十 MB |
| MR25H40CDF MRAM | 字节覆盖 | 大于 10 的 14 次方 | 否 | 512KB |
1.3 带电池 SRAM 也不是最优解
可能有人会提出来,用 SRAM 加电池不是也能做到掉电保存吗?确实可以,典型做法是外挂一颗低功耗 SRAM,再用电池和电源检测电路维持数据。但工业现场对免维护要求很高,电池会漏电、会耗尽、高低温下性能还会恶化。机构提出“十年内不更换任何部件”时,带电池方案第一轮就被我淘汰了。MRAM 本质上保留了 SRAM 那种直接覆写的快捷体验,又完全没有掉电丢失的担忧,属于那种“缺点明显但槽点极少”的存储介质,用在工业参数的保存上非常合适。
2. 方案定型和硬件连接
2.1 为什么选用 PIC18F46K42
这颗单片机是 Microchip 的 8 位 PIC 家族里外设比较完整的一员:主频最高 64MHz,内置 64KB 程序 Flash、4KB 数据 SRAM,还有 ADCC、CLC、DMA、硬件 CRC 等模块。对于工业变送器这种项目,它的 ADCC 可以带计算功能直接处理传感器信号,CLC 可以做外围逻辑,我这次主用它的 SPI 主机功能,工作电压直接选 3.3V,和 MRAM 匹配,省去电平转换电路。
更重要的是开发效率。MPLAB X 里集成了 MCC,也就是 MPLAB Code Configurator,图形化勾选 SPI 模块、引脚分配、时钟分频,代码自动生成。PIC18F46K42 的数字引脚支持 PPS 重映射,SCK、SDO、SDI 可以分配到任意支持的引脚上,布线自由度很高。这在以前的老 PIC 上是不敢想的,老平台很多外设引脚固定,画板子时常常被逼得绕大圈。
2.2 引脚分配和最小电路
我在这个项目里用的是 SOP-8 封装的 MR25H40CDF,引脚功能很明确:
- 引脚1 CS#:片选输入,低电平有效
- 引脚2 SO:串行数据输出,接单片机的 SDI
- 引脚3 WP#:写保护输入,必须接高电平或由 GPIO 控制
- 引脚4 VSS:地
- 引脚5 SI:串行数据输入,接单片机的 SDO
- 引脚6 SCK:时钟输入
- 引脚7 HOLD#:暂停通信输入,必须接高电平
- 引脚8 VDD:3.3V 电源
我的连接规划如下:
| PIC18F46K42 引脚 | 方向 | MR25H40CDF 引脚 | 说明 |
|---|---|---|---|
| RB0 (GPIO) | 输出 | CS# | 片选控制 |
| RB2 (SCK) | 输出 | SCK | SPI 时钟 |
| RB3 (SDO) | 输出 | SI | 主机发送数据 |
| RB4 (SDI) | 输入 | SO | 主机接收数据 |
| VDD 3.3V | - | VDD | 电源 |
| GND | - | VSS | 地 |
WP# 和 HOLD# 不能悬空。我在实际板上把这两个引脚分别通过 10K 电阻上拉到 VDD。如果悬空,芯片内部虽然可能有弱上拉,但工业环境里的干扰信号很容易在引脚上造成毛刺,导致数据误写或者通信暂停。另外,每个电源引脚旁边都放了 0.1uF 陶瓷电容,并且在 MRAM 附近加了一个 10uF 的钽电容,用于吸收 SPI 写入时的瞬态电流。
还有一个非常容易忽略的细节:CS# 也要加 10K 上拉到 VDD。原因是单片机在上电复位和编程器连接期间,IO 引脚可能处于高阻态,如果 CS# 悬空,引脚电平就完全取决于环境噪声。一旦噪声把 CS# 拉低,MRAM 会误以为主机要通信,然后在 SCK 上的毛刺驱动下执行随机操作。虽然 MR25H40CDF 有写使能锁存保护,但外部总线毛刺依然可能导致误读或异常功耗,做工业产品必须把这种隐患在原理图上就解决掉。
2.3 电源和电平匹配
MR25H40CDF 是纯 3.3V 器件,不能直接接 5V 输入。PIC18F46K42 支持 2.3V 到 5.5V 供电,所以最简单可靠的做法是整个系统统一跑 3.3V。如果板子上不得不有 5V 单片机,那就必须加电平转换芯片,比如 74LVC2T45 或类似器件,千万不能靠电阻分压偷懒,SPI 时钟频率稍高一点,分压网络的上升沿衰减就会让你怀疑人生。
我这边同时用到了 3.3V 和 5V 的传感电路,所以是分区域供电,MCU 和 MRAM 稳定在同一路 3.3V 域内,电源入口再统一做过压和反接保护。
3. 用 MCC 快速初始化 SPI
3.1 建立工程和添加 SPI 外设
在 MPLAB X 中新建项目,选择 PIC18F46K42 型号,打开 MCC 插件,左侧 Device Resources 里找到 SPI 模块,添加后给它命名为 SPI1。配置页面需要设几个关键参数:
- Mode:Host(主机模式)
- SPI Mode:0,即 CPOL=0、CPHA=0
- 时钟分频:先选低一点的速率,比如 1MHz
- 数据位:8 位
- 帧类型:标准
MR25H40CDF 在数据手册里明确写了支持 SPI Mode 0 和 Mode 3,两种模式都能用,但必须保证主机和从机一致。我习惯选 Mode 0,因为大部分 MCU 默认上电状态就是时钟空闲低电平,少一个容易踩错的参数。
初次调试时钟频率建议从 1MHz 起步。数据手册上 MRAM 允许跑几十 MHz,但那是在理想 PCB 布线和短走线条件下。工业板卡上信号要过连接器、排线,再加上 EMI 防护器件,时序余量会被压缩,所以我一般先在低速下把功能跑通,再逐步提频,最后定格在可靠值上。
3.2 通过 PPS 重映射分配引脚
PIC18F46K42 的 PPS 配置在 MCC 里非常直观。在 Pin Manager 界面里找到输出引脚 SCK 和 SDO,把它们分别映射到 RB2 和 RB3,再把输入引脚 SDI 映射到 RB4。MCC 会在生成的初始化代码里自动写入 PPS 锁定寄存器和输入选择寄存器,不需要手写,但你要核对生成的代码是否符合预期。
这里有个经验:PPS 寄存器属于“受保护”寄存器,修改前需要解锁序列。MCC 生成的初始化代码会自动执行 PIN_MANAGER_Initialize(),如果你手动改动过引脚映射,一定要确认初始化被调用且没有被优化器跳过去。我在一个旧项目里就吃过亏,手动加了引脚配置代码但忘了执行解锁,结果引脚映射始终是默认值,SPI 怎么调都不通,最后查了半天才发现是 PPS 没锁住。
3.3 核对初始化代码
MCC 生成的核心代码大致长这样:
void SPI1_Initialize(void) { SPI1CON0 = 0x10; // 主机模式,SPI 使能 SPI1CON1 = 0x00; // 模式 0,8 位数据 SPI1BAUD = 0x01; // 分频设置 }实际寄存器位定义可能因 MCC 版本略有差异,但概念一致。生成代码后我会做三件事:第一,确认 CS 引脚没有被 SPI 模块占用,而是普通 GPIO;第二,确认时钟分频计算出来的实际频率符合预期;第三,把生成的 SPI1_Open、SPI1_Exchange8bit、SPI1_Close 接口抄到自己的驱动文件里,而不是直接改动生成文件。MCC 重新生成时会覆盖用户代码,所以我的习惯是单独建一个 mram_drv.c 文件,把对 MRAM 的操作全部封装起来,只调用 MCC 生成的底层 SPI 函数。
4. 封装 MR25H40CDF 读写驱动
4.1 指令速查
MR25H40CDF 的指令集和普通 SPI NOR Flash 相近,最常用的是这几个:
| 指令名 | 操作码 | 功能 |
|---|---|---|
| WREN | 0x06 | 写使能,置位状态寄存器 WEL |
| WRDI | 0x04 | 写禁止,清除 WEL |
| RDSR | 0x05 | 读状态寄存器 |
| WRSR | 0x01 | 写状态寄存器 |
| READ | 0x03 | 读数据,普通读命令 |
| WRITE | 0x02 | 写数据,普通写命令 |
READ 和 WRITE 都是三字节地址寻址,因为 512KB 需要 19 位地址,典型发送格式是“命令字节 + 地址高位 + 地址中位 + 地址低位”。需要注意的是,MRAM 不像 Flash 那样有页缓冲编程的概念,WRITE 命令可以在片选保持低电平期间连续写任意多个字节,直到地址达到 0x7FFFF 后回卷到 0x00000。
写入之前必须执行 WREN,否则 WRITE 和 WRSR 都会被忽略。写使能锁存器在成功执行一次写入之后会被自动清零,所以这并不是“配置一次就永久有效”,而是每条写入指令前都要重新发 WREN。这一点对经验不足的工程师来说特别容易漏:第一次给 Flash 写数据时习惯性先擦除,然后写命令直接跟着地址发,结果发现数据没写进去,状态寄存器里 WEL 还是 0,基本就是忘了 WREN。
4.2 三个基础函数
下面是我实际使用的驱动骨架,底层收发用的是 MCC 生成的 SPI1_Exchange8bit。先看片选和写使能:
#define MRAM_CS_LAT LATBbits.LATB0 #define MRAM_CS_TRIS TRISBbits.TRISB0 static void mram_cs_low(void) { MRAM_CS_LAT = 0; } static void mram_cs_high(void) { MRAM_CS_LAT = 1; } void mram_wren(void) { mram_cs_low(); SPI1_Exchange8bit(0x06); // WREN mram_cs_high(); }读状态寄存器的函数如下。RDSR 命令也叫“只读状态”,发送命令字节后,主机继续产生时钟,从机就会把状态寄存器的内容一位一位送出来。因为状态寄存器只要读一个字节,我就在读完之后立刻拉高 CS,结束本次命令。
uint8_t mram_rdsr(void) { uint8_t status; mram_cs_low(); SPI1_Exchange8bit(0x05); // RDSR status = SPI1_Exchange8bit(0x00); mram_cs_high(); return status; }单字节写入的实现:
void mram_write_byte(uint32_t addr, uint8_t data) { mram_wren(); // 1. 写使能 while ((mram_rdsr() & 0x01) == 0) {} // 2. 等待 WEL 置位 mram_cs_low(); // 3. 发送 WRITE SPI1_Exchange8bit(0x02); SPI1_Exchange8bit((addr >> 16) & 0xFF); SPI1_Exchange8bit((addr >> 8) & 0xFF); SPI1_Exchange8bit(addr & 0xFF); SPI1_Exchange8bit(data); mram_cs_high(); }在等待 WEL 置位这个循环里,理论上只会执行一次,因为 MRAM 不像 Flash 需要内部擦写时间,写使能基本立刻生效。但如果 SPI 总线上有干扰,或者刚才发过 WRDI,多轮询一次并不会造成显著开销,反而能提前发现异常。
单字节读取的实现就更短了:
uint8_t mram_read_byte(uint32_t addr) { uint8_t data; mram_cs_low(); SPI1_Exchange8bit(0x03); // READ SPI1_Exchange8bit((addr >> 16) & 0xFF); SPI1_Exchange8bit((addr >> 8) & 0xFF); SPI1_Exchange8bit(addr & 0xFF); data = SPI1_Exchange8bit(0x00); // 读一个字节 mram_cs_high(); return data; }4.3 连续读写和中断保护
单字节操作适合参数修改,如果是批量日志写入,最好还是用连续写。做法很简单:WREN 之后拉低 CS,发送写命令和起始地址,然后连续发送数据字节,全部发完再拉高 CS。这样能减少 CS 翻转次数,也减少总线上的无效开销。MRAM 不需要页对齐,只要不超过 0x7FFFF 的地址上界,中间可以一直写下去。
void mram_write_burst(uint32_t addr, const uint8_t *buf, uint16_t len) { uint16_t i; mram_wren(); mram_cs_low(); SPI1_Exchange8bit(0x02); SPI1_Exchange8bit((addr >> 16) & 0xFF); SPI1_Exchange8bit((addr >> 8) & 0xFF); SPI1_Exchange8bit(addr & 0xFF); for (i = 0; i < len; i++) { SPI1_Exchange8bit(buf[i]); } mram_cs_high(); }这里要特别提醒中断问题:整段连续写过程中,CS 必须保持低电平,数据传输格式不能被打断。如果在发送过程中进入中断服务程序,而中断里又恰好操作了 SPI1 或者 GPIO,CS 时序就可能被拉长或者产生额外时钟,后果是写入的数据错位。我的处理策略是:如果 MRAM 写入函数执行期间本来就会开中断,就在写函数外围临时关闭中断,等 CS 拉高后再恢复。PIC18F46K42 可以简单用:
INTCONbits.GIE = 0; mram_write_burst(addr, buf, len); INTCONbits.GIE = 1;这是老派但非常可靠的做法。你要是有更讲究的调度器,当然可以用互斥信号量或临界区保护,原理一样。
4.4 快速验证驱动正确性
写完驱动不要急着往正式应用里接,先在 main 函数里跑一个简单的自检:向几个特定地址写入一个已知测试向量,然后读回来比对。我习惯用 55 AA FF 00 这种交替位模式,因为它能同时暴露数据线粘连和时钟相位问题。再向某段地址连续写入递增数,掉电后重新上电再读一次,确认持久性正常。这一步能在十分钟内排除绝大多数接线和驱动层低级错误。
5. 工业现场:如何让数据真正可靠
5.1 先处理掉电与复位时序
MRAM 本身不丢数据,但外部系统如果在上电或掉电瞬间对 MRAM 产生了不可靠访问,写入可能没有正确完成。这里有两个关键手段:第一,CS# 引脚加上拉到 VDD,并在单片机复位期间把 CS 引脚保持为输出高电平。我在初始化代码的开头就设置 CS 为输出并输出高电平,再去配置 SPI。如果 CS 在复位期间是默认输入,就可能出现低电平毛刺。第二,应用层访问 MRAM 之前,至少延时几毫秒,确保 VDD 已经稳定。对于 3.3V 系统,一般 10ms 足够,具体以电源芯片的上电时间和复位延时为准。
还要避免一个非常隐蔽的状况:写入命令进行到一半时 MCU 被复位。比如看门狗超时、外接复位芯片触发、调试中断。这会让 CS 在数据未写完时提前释放,写入可能被中止。MR25H40CDF 的单字节写入本身很快,依然建议在正式固件里给 MRAM 操作加“事务标志”:进入写入事务时置一个全局变量,完成后清除。根据这个标志决定是否处理复位看门狗,或者干脆在 MCU 硬件复位期间增加外部硬件延时,等当前命令结束后再拉高 CS。对要求极高的项目,可以用单片机的铁电库思路,把关键写操作放到临界区并关闭看门狗,实际操作中大部分团队都接受这种折中。
5.2 双缓存和校验机制
有人觉得 MRAM 寿命长、字节级覆盖,就可以像操作 SRAM 一样随便用。从可靠性角度讲,介质可靠不等于系统可靠。工业参数更新过程中,如果程序跑飞、总线受干扰、或者数据帧本身就是错的,写进去的就是坏数据。所以我在应用层做了双重保护:
- 每个参数块内部带版本号、长度和 CRC32。
- 同一个参数在 MRAM 中保存两份,分别放在不同地址区域。
更新流程是:先用新版本覆盖备份区,校验备份区成功后再更新主区。启动读取时,先读主区,校验通过且版本号不落后,就正常使用;如果主区校验失败,再用备份区恢复。这个策略很像 Flash 存储里的“双 Bank 冗余”,成本只是多占一倍 MRAM 空间。对 512KB 的 MR25H40CDF 来说,实际参数区通常只占几十 KB,完全承担得起。
写参数块时,我还会把 CRC 和版本号放在参数块末尾而不是开头。原因在于,典型写操作是连续写入,如果在写入中途发生总线异常,区块前半部分可能是新数据、后半部分是旧数据,只有等全部写完再校验,才能避免误把半成品当完整参数。数据块结构大致是这样:
typedef struct { uint16_t version; uint8_t payload[64]; uint32_t crc32; } ParamBlock;读写时都按这个结构整体处理。更新参数时,先组织好整个结构,再一次性 burst 写进目标地址。启动阶段只做读取和校验,不会反复擦写,MRAM 的容量和寿命优势就体现出来了。
5.3 布线和 EMI 层面的建议
工业现场往往有电机、变频器、继电器,干扰源比实验室多得多。SPI 信号是数字脉冲,沿很陡,一旦经过长走线就可能反射和串扰。我实测下来的经验是:
- SPI 走线越短越好,最好不超过 5cm。
- MRAM 的地和 MCU 的地址在同一块完整地平面上,不要跨区。
- SCK 和 SDO 可以各串一个 22 到 33 欧姆的电阻,放在源端,有抑制过冲的作用。
- 如果信号必须过排线或连接器,优先考虑把 SPI 速率降到 1MHz 以下,同时使用屏蔽地线包裹。
- WP# 和 HOLD# 的上拉电阻不要省,这是两处最容易受干扰的浮动引脚。
现场设备出现偶发数据异常时,别急着一口咬定是 MRAM 坏了,先看波形、再查软件流程,这是我反复强调的排查顺序。
6. 调试和故障排查实录
6.1 读出来全是 0xFF
第一次上板,SPI 读回数据全是 0xFF,这是最高频的问题。优先查 SPI 模式是不是 Mode 0,也就是时钟空闲是否为低电平、数据在第一个时钟边沿采样。如果 CPOL 或者 CPHA 反了,从机根本收不到正确的命令和地址,返回的就会是高电平默认值。
我遇到过更隐蔽的情况:MCC 生成的 SPI 引脚映射和实际接线不一致。PIC18F46K42 支持引脚重映射,很多人画板子时觉得“反正 PPS 能分配”,结果 MCC 里没把 SCK 分配到你实际走线的引脚上。这种问题查法很简单,把 CS 配置成输出低电平,然后手动触发 SPI 发送,用示波器看对应引脚有没有时钟信号。没时钟,先检查 PPS 配置和逻辑分析仪的通道接法。
6.2 写进去的数据掉电后恢复旧值
这个现象很迷惑:现场写数据,读回来是对的,一断电再上电,数据又变回旧版本。经历过几次之后,我总结了三个常见原因:
第一,CS# 在复位和上电期间出现了低电平毛刺,MRAM 把上面残留的旧地址和数据又重新写了一遍。处理办法就是加外部上拉电阻并在初始化时第一时间把 CS 置高。
第二,WP# 没有正确拉高,外部干扰导致 WP# 短暂拉低,写保护激活,WRITE 指令实际被芯片拒绝,但主机依然按正常流程执行完毕并返回成功。由于读操作不受写保护影响,你当时读到的可能是旧数据,而你以为写入成功了,必须通过 RDSR 查看 WEL 和 BP 位确认。
第三,状态寄存器里的 BP 位被误设置为保护区域。MR25H40CDF 的状态寄存器有块保护功能,如果 BP0、BP1 被误写,对应地址区间就被保护起来。很多工程师只关注 WEL,忽略 BP 位,结果数据写在受保护的区域里,怎么改都改不动。排查方式是在系统上电后读一次 RDSR,把状态寄存器的值打印到串口,正常情况应该是 0x00 或者 WEL 暂时置位。
6.3 个别板卡写失败,降低 SPI 时钟后正常
有一批板子在产线上做高低温循环测试时,偶尔出现参数写入失败,良率九成左右。查到最后,问题出在 SPI 时钟余量不足。PCB 上 MRAM 的封装引脚到 MCU 引脚之间的距离比预期长,且中间还过了两个过孔,信号完整性变差。加上温度升高后,芯片 IO 的翻转阈值变化,时序边缘就更紧张。
我不打算散布“所以工业产品 SPI 一定要低速率”的教条,但有一条原则是铁律:功能验证用高速,定型生产留余量。最终把 SPI 频率从 8MHz 降到 2MHz,再配合源端串阻,整批板子随便测都没再出现写入失败。MRAM 的写操作本来就不需要像 Flash 那样等内部状态机,频率降一点不影响用户感知,STM32、PIC 这类现代单片机处理参数保存任务也完全绰绰有余。
6.4 排错工具
没有逻辑分析仪就别谈 SPI 调试。我之前用示波器也能看,但抓命令字节和 CS 时序实在痛苦,后来特意买了一个带 SPI 协议解析的逻辑分析仪,抓一段波形直接解码出命令名和地址,效率提升非常明显。调试 MRAM 驱动时,我会重点抓几个信号:CS 的低电平区间、SCK 的连续包、SI 上的命令字节、SO 上读操作返回的数据。如果 CS 拉低后 SCK 没有按预期产生时钟,基本可以断定是 MCU 的 SPI 模块没发出数据;如果命令字节和地址都对,但数据仍不对,再看时序参数和模式配置。
7. 我个人实测的一些体会
这套方案如果现在让我重新选一次,我依然会坚持用 MRAM 做参数和日志存储,而且会更早地验证状态寄存器和写保护引脚细节。MR25H40CDF 在项目中表现出来的最大优势不是速度,而是“写就真的写了”这种确定性。做工业控制器的人最怕的就是“读回来好像对,重启后却是另外一回事”,MRAM 至少把介质层的悬念去掉了,剩下的可靠性问题完全可以通过软件冗余和硬件规矩来解决。
最后分享一个工作习惯:每次板卡打样回来,我会优先把 MRAM 初始化自检写成一个独立的测试函数,跑一遍全地址区的写读回来校验,并记录每个地址的读写耗时。这个自检虽然会增加一点生产测试时间,但能在出厂前把绝大多数虚焊、引脚错位和 SPI 连线异常过滤掉。等自检稳定跑过了,再把驱动接进正式业务逻辑,后面的坑就会少很多。
提示:如果你是第一次用 MRAM,建议先跳过掉电保持测试,直接把读写例程跑通;等确认 SPI 波形和状态寄存器都正常了,再去模拟掉电、复位、高低温这些现场条件,这样定位问题范围会小很多。