1. 为什么是 MR25H40CDF + STM32F215ZG
做工业设备的人,迟早会遇到一个尴尬:设备要断电保存数据,普通的 NOR Flash 擦写寿命绷不住,EEPROM 虽然按字节操作但容量小、速度慢,带电池的 SRAM 又三天两头被现场维护人员抱怨。我去年手上的一个项目就卡在这——设备需要记录长达数月的振动特征量、事件日志和工艺参数,现场温度从 -20℃ 到 70℃,一年要经历几百次断电上电,传统的存储方案是真的扛不住。最后定型的方案是 Everspin 的 MR25H40CDF,配一颗 STM32F215ZG,用 SPI 完成数据读写。
这篇文章不是数据手册的翻译,而是把这套组合从选型、接线、驱动、校验到现场排查的完整过程捋一遍。MR25H40CDF 是 Everspin 的串行 MRAM,容量 4Mbit(512KB),工业级工作温度,DFN 封装,非常适合 PCB 空间有限的工控板。STM32F215ZG 是 ST 的 Cortex-M3 主控,主频 120MHz,板载 1MB Flash 和 128KB SRAM,有多路 SPI、DMA 和丰富的外设资源。适合谁来参考?如果你也在做数据记录、掉电保存、配置文件存储、现场参数管理这类嵌入式功能,或者在 EEPROM、NOR Flash 之间纠结选型,这篇应该对你有用。
1.1 从一次工业数据记录需求说起
这个项目最早的需求其实很简单:设备每隔 100ms 采集一次振动特征量,每 10 秒形成一条带时间戳的记录,保存到非易失存储里。现场设备会频繁上下电,而且没有干净的下电流程——有时候就是直接拉闸。刚开始用的是一颗 256KB 的 SPI NOR Flash,硬件和驱动都不复杂,但跑了两个月就出状况了:设备重启后,最近一段时间的日志经常变成 0xFF,或者整页数据读出来是乱的。
排查下来原因也不复杂。NOR Flash 写入前必须先擦除,擦除以扇区或块为单位,而设备每 10 秒就要写一条记录,日志区很快就把某个扇区的擦写寿命用完了。加上现场是随机断电,掉电瞬间如果正好在擦除或写入过程中,数据损坏的概率更高。这不是 Flash 厂家的问题,而是应用场景的错配——频繁小数据量写入、随机掉电、需要长期可靠保存,这本就是 MRAM 最擅长的领域。
换了 MR25H40CDF 之后,这些问题基本消失了。MRAM 写入不需要擦除,可以按字节直接覆盖写,没有“先擦后写”的状态窗口,写入过程即使被打断,也不会导致整个扇区损坏。后续现场跑了半年,没有再出现日志丢失的问题。这个经历让我对存储选型有了更深的体会:不是所有非易失存储都能通用,得看你的写入频率、数据量、掉电场景和寿命要求。
1.2 MRAM 和 EEPROM、NOR Flash 的对比
很多工程师对 MRAM 的第一印象是“贵”,但如果把故障率、维护成本和开发时间算进去,它往往比省下来的那几块钱更值。这里用一张表把常见非易失存储做个工程层面的对比:
| 特性 | EEPROM | NOR Flash | MR25H40CDF (MRAM) |
|---|---|---|---|
| 写前擦除 | 不需要 | 需要,按扇区/块擦除 | 不需要 |
| 典型擦写寿命 | 10^5 ~ 10^6 次 | 10^5 次左右 | 10^14 次以上 |
| 按字节写 | 支持 | 不支持,页/字操作 | 支持 |
| 写速度 | 毫秒级 | 页编程几百微秒,擦除更慢 | 字节写跟随 SPI 时钟,基本无延迟 |
| 容量 | 小,常见几KB~几百KB | 大,MB 级常见 | 中等,典型的 4Mbit / 8Mbit |
| 掉电写入可靠性 | 一般,可能字节损坏 | 低,擦写窗口损坏概率高 | 高,无擦除窗口 |
| 价格 | 低 | 低 | 相对高 |
从表里能看出,MRAM 的特点正好补上了 Flash 和 EEPROM 在工业现场的短板:写入寿命长、不需要擦除、写速度快。它也有缺点——容量相对小、单价高。所以 MRAM 不适合拿来存大文件、音频、固件升级包之类的大块数据,它更适合的是“数据量不大但写入频繁”“断电后必须完整保留”的场景。
1.3 主控为什么选中 STM32F215ZG
项目主控是 STM32F215ZG,选它不是因为它是市面上最新的芯片,而是因为它在这个场景下足够稳。Cortex-M3 内核跑 120MHz,对日志记录、SPI 读写、CRC 校验、协议解析这些工作完全没有压力。1MB 片内 Flash 可以用来放固件和历史配置,128KB SRAM 足够缓存一批数据再写入 MRAM。
我看中的另一个点是 F2 系列的 SPI 外设比较完整。SPI1 挂载在 APB2 上,最高能跑到 60MHz,作为主机向 MR25H40CDF 发数据,分频后可以得到 30MHz 甚至更高的 SCK。再往下还有 DMA 支持,批量读写时不需要 CPU 一个一个字节地搬运。对于这个项目,SPI1 实际只跑 15MHz,留了很大的时序余量。
F2 系列的另一个好处是引脚资源丰富。LQFP144 封装,IO 数量充裕,除了 SPI1 之外还能同时带以太网、多个 UART、GPIO 点灯和按键扫描,后续扩展功能不用重新选型。工业级温度范围也是基本盘,-40℃ 到 +85℃ 的版本在户外设备和机柜里都能用。如果你手头有其他 STM32F1/F4 的板子,驱动思路是一样的,代码迁移成本也不高。
2. 硬件连接与布局要点
MR25H40CDF 是标准的 8 脚 SPI 存储芯片,DFN 封装,尺寸很小,但引脚功能一点也不含糊。在画原理图之前,我建议先仔细读一遍 Everspin 官方数据手册里的 Pin Assignment 图,确认 CS、SCK、SI、SO、HOLD、WP 这几个引脚的对应关系。不同封装的引脚顺序可能不完全一致,DFN 和 SOIC 虽然逻辑上相似,但机械尺寸和焊盘画法要注意。
2.1 MR25H40CDF 引脚和最小系统
这颗芯片的引脚定义,按最常见的 8 脚封装排布是:CS(片选)、SCK(时钟)、SI(串行输入,接主控 MOSI)、VSS(地)、SO(串行输出,接主控 MISO)、HOLD(暂停通信)、WP(写保护)、VDD(电源)。如果你的封装引脚顺序和我写的不一致,以你拿到的那份手册为准。
一个很容易忽略的点是 HOLD 和 WP。HOLD 引脚在低电平时会让芯片暂停当前通信,WP 在低电平时会锁定状态寄存器写保护。如果这两个引脚悬空,单片机正常工作时可能没问题,但现场电磁干扰强一点、或者上电时序稍微有点噪声,就容易出现偶发的不响应或写保护误触发。
我的做法是把 HOLD 和 WP 分别通过 10kΩ 电阻上拉到 3.3V,让它们在默认状态下处于无效电平。如果板子空间紧张,直接接到 VDD 也可以,反正这两个引脚在这个应用里没有动态使用的需求。但我不建议完全悬空,悬空引脚在工业环境里就是个不确定因素。
2.2 与 STM32F215ZG 的 SPI 接线
STM32F215ZG 的 SPI1 在默认复用功能下,有至少两组引脚映射。我在这个项目里用的是 PA4 做软件 CS,PA5 做 SCK,PA6 做 MISO,PA7 做 MOSI。这样接线的好处是和 STM32 常见的 SPI1 调试习惯一致,逻辑分析仪钩上去也好找信号。
| MR25H40CDF 引脚 | 连接到 STM32F215ZG | 说明 |
|---|---|---|
| CS | PA4(GPIO 输出) | 软件控制片选,电平空闲时为高 |
| SCK | PA5(SPI1_SCK) | SPI 时钟输出 |
| SI | PA7(SPI1_MOSI) | 主发从收,接存储芯片的串行输入 |
| SO | PA6(SPI1_MISO) | 主收从发,接存储芯片的串行输出 |
| VDD | 3.3V | 并联 0.1μF 去耦电容 |
| VSS | GND | 注意走线尽量粗、短 |
| HOLD | 10kΩ 上拉到 3.3V | 悬空容易受干扰 |
| WP | 10kΩ 上拉到 3.3V | 禁用硬件写保护 |
CS 我特意没有用 SPI 硬件 NSS,而是用普通 GPIO 控制。原因很简单:软件 CS 在时序控制上更灵活,读写操作的每个命令段之间可以精确控制片选高低,不受 SPI 外设自动 NSS 模式的限制。调试的时候,还能用 GPIO 翻转波形来判断代码执行到哪一步。实际上,几乎所有串行存储芯片的驱动代码也都是用软件 CS 写的,这样最稳。
2.3 电源、去耦、PCB 走线细节
MR25H40CDF 的工作电压是 3.3V,对电源纹波没有特别苛刻的要求,但工业现场的主控板上往往有电机、继电器这类强干扰源。我习惯在芯片 VDD 引脚旁边放一个 0.1μF 的陶瓷电容,电容尽量靠近芯片引脚,走线先经过电容再到芯片。对于更复杂的系统,还可以在 3.3V 主电源处再放一个 10μF 的电解电容做整体储能,防止设备断电瞬时电压跌落太快。
PCB 走线方面,SCK 和 SI 是输出信号,MISO 是输入信号,三者之间尽量保持平行且短距离。SCK 不要和 MISO 靠得太近,否则时钟翻转时可能通过寄生电容耦合到数据线上,导致高速模式下的误码。我第一版 PCB 把 SPI 走线拉到了四五厘米长,15MHz 时钟下波形的前后沿已经有明显圆角,后来缩短到两厘米以内,波形干净多了。
注意:MR25H40CDF 是 SPI 器件,但对时序还是有要求。手册里会给出 CS 建立时间、CS 保持时间、SCK 最高频率等参数。画板之前把这些参数和 STM32F215ZG 的 SPI 配置对照一遍,不要上来就跑最高频率。
3. SPI 驱动与读写实现
硬件焊接好之后,真正花时间的是驱动层。MR25H40CDF 的指令集整体上很像 SPI NOR Flash,但又有几个关键差异,这也是很多从 Flash 迁移过来的工程师容易掉坑的地方。
3.1 SPI 外设初始化
我基于 STM32CubeMX 生成的 HAL 工程来做初始化。SPI1 配置成主机模式、8 位数据、MSB 先发送、软件 NSS,时钟极性选择低电平空闲、第一个边沿采样。用 CubeMX 里的术语就是 SPI Mode 0。MR25H40CDF 同时支持 SPI Mode 0 和 Mode 3,两个模式都能正常工作,只是 SCK 的空闲电平不一样。只要你的驱动代码和自己的 SPI 配置保持一致,Mode 0 和 Mode 3 在实际使用中没有区别。
SPI 时钟我设置在 15MHz。理论上这颗 MRAM 最高能跑到 40MHz,STM32F215ZG 的 SPI1 在 APB2 为 60MHz 时,分频系数为 2 就能得到 30MHz 的 SCK。但我实际测量后,板子走线、逻辑分析仪探头、电源纹波这些因素叠加在一起,30MHz 下波形已经有振铃。15MHz 带来的性能损失在这个项目里完全可以接受——每 10 秒才写一条记录,根本用不到极限速率,稳定比速度更重要。
hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; HAL_SPI_Init(&hspi1);注意 HAL 库的BaudRatePrescaler是相对 APB 时钟来算的,我这里 APB2 是 60MHz,分频系数 4,折算得到 15MHz SCK。如果你的工程里 APB2 时钟不是 60MHz,要按实际时钟重新计算,别直接抄配置。
3.2 指令集与基本时序
MR25H40CDF 的关键指令不多,核心就是这几个:读取状态寄存器(0x05)、写使能(0x06)、写禁用(0x04)、读数据(0x03)、写数据(0x02)。和串行 NOR Flash 相比,少了擦除指令、页编程指令、JEDEC ID 这类。因为 MRAM 不需要擦除,也不需要按页划分写操作。
读写操作的时序大体是:
- 读:拉低 CS → 发送 0x03 → 发送 24 位地址 → 连续读取 N 字节 → 拉高 CS
- 写:发送 0x06 写使能 → 拉低 CS → 发送 0x02 → 发送 24 位地址 → 连续写入 N 字节 → 拉高 CS → 等待状态寄存器 WIP 位清零
芯片容量是 512KB,地址范围从 0x000000 到 0x07FFFF,所以需要 24 位地址。发送顺序是高字节在前、中字节在后、低字节在最后,这个顺序和大多数 SPI 存储芯片一致。
整个时序里最容易忽视的是“写使能”这一步。MR25H40CDF 在每次写操作前,必须先发送 0x06 将 WEL 位置 1,否则后续的写命令会被忽略。这个机制和 NOR Flash 一致,从 Flash 迁移过来的驱动可以直接用,但如果你之前用的是 EEPROM——EEPROM 通常不需要单独写使能——就要特别注意补上这一步。
3.3 写使能、读取和写入的完整驱动代码
下面这段代码是我在项目里实际在用的精简版,去掉了业务封装,只保留底层的地址读写逻辑。结构上用了一组静态函数做片选和等待,然后向外提供MR25_ReadBytes和MR25_WriteBytes两个接口。
#define MR25_CAPACITY (512 * 1024) #define MR25_CMD_WREN 0x06 #define MR25_CMD_RDSR 0x05 #define MR25_CMD_READ 0x03 #define MR25_CMD_WRITE 0x02 static void MR25_Select(void) { HAL_GPIO_WritePin(MR25_CS_GPIO_Port, MR25_CS_Pin, GPIO_PIN_RESET); } static void MR25_Deselect(void) { HAL_GPIO_WritePin(MR25_CS_GPIO_Port, MR25_CS_Pin, GPIO_PIN_SET); } static void MR25_WriteEnable(void) { uint8_t cmd = MR25_CMD_WREN; MR25_Select(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); MR25_Deselect(); } static void MR25_WaitBusy(void) { uint8_t cmd = MR25_CMD_RDSR; uint8_t sr; do { MR25_Select(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); HAL_SPI_Receive(&hspi1, &sr, 1, 100); MR25_Deselect(); } while (sr & 0x01); } int MR25_ReadBytes(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t hdr[4] = { MR25_CMD_READ, (uint8_t)(addr >> 16), (uint8_t)(addr >> 8), (uint8_t)(addr) }; if ((addr + len) > MR25_CAPACITY) { return -1; } MR25_Select(); HAL_SPI_Transmit(&hspi1, hdr, 4, 100); HAL_SPI_Receive(&hspi1, buf, len, 100); MR25_Deselect(); return 0; } int MR25_WriteBytes(uint32_t addr, const uint8_t *buf, uint32_t len) { uint8_t hdr[4] = { MR25_CMD_WRITE, (uint8_t)(addr >> 16), (uint8_t)(addr >> 8), (uint8_t)(addr) }; if ((addr + len) > MR25_CAPACITY) { return -1; } MR25_WriteEnable(); MR25_WaitBusy(); MR25_Select(); HAL_SPI_Transmit(&hspi1, hdr, 4, 100); HAL_SPI_Transmit(&hspi1, (uint8_t *)buf, len, 100); MR25_Deselect(); MR25_WaitBusy(); return 0; }这里有个细节:读操作时,CS 拉低后先发送 4 字节的命令头和地址,紧接着立刻开始读取数据;整个过程中间 CS 不能拉高。如果你用 HAL 的Transmit和Receive分开调用,只要在调用之间不操作 CS,就符合时序要求。SPI 是全双工总线,主机发送命令的同时,MISO 上其实也在返回数据,那些返回数据在命令阶段没有意义,不用管。
写操作也一样,发送完命令头后继续发数据,CS 保持低电平,直到所有数据发完再拉高。MRAM 没有“页缓冲区”的概念,所以可以连续写入任意长度,不需要像 NOR Flash 那样在页边界处做拆分。这个特性在写日志型数据时非常舒服,一个记录块可以一次写完。
3.4 用 DMA 提升批量读写效率
如果应用只是偶发写几条记录,纯轮询的 HAL 传输完全够用。但如果你要做类似“连续采集几秒数据后一次性写入 MRAM”的批量操作,轮询发送几百字节虽然也可以,但 CPU 会一直被占用。这个项目里我用的是 SPI DMA 方案。
DMA 的思路是:CPU 把待读取或待写入的数据缓冲区的地址、长度配置给 DMA 控制器,然后 SPI 自动完成搬运,传输完成后触发中断或者置一个标志位。HAL 库里对应的接口是HAL_SPI_Transmit_DMA、HAL_SPI_Receive_DMA。需要注意一点,DMA 传输完成后,要处理HAL_SPI_TxCpltCallback或HAL_SPI_RxCpltCallback回调,在回调里拉高 CS。不要在主循环里通过简单延迟来猜传输结束时间,那样既浪费 CPU 又容易出错。
DMA 模式下,我实际测过 15MHz SCK,单次读取 256 字节大约消耗 140μs 左右,而且 CPU 在传输过程中可以被释放去做解析、界面刷新等工作。如果你的系统里还需要同时处理多个任务,这一点收益还是很可观的。
4. 数据可靠性与校验设计
存储介质再稳,也不能完全指望硬件。工业现场的数据可靠性是一个完整链条,从应用层的记录格式、写入顺序、掉电恢复,到驱动层的回读校验,每一环都得考虑到。MRAM 解决了“介质本身容易坏”的问题,但数据链路里的错误是另外一回事。
4.1 回读校验和 CRC
MR25H40CDF 的写入过程在电气层面很可靠,但 SPI 通信可能受到干扰,代码逻辑也可能有缺陷。我习惯在写完一段数据之后立即回读,把读出来的内容和原始缓冲逐字节比较。如果一致,就认为这次写操作成功;如果不一致,立刻重试一次;重试还失败,就上报错误。
回读校验会增加一些时间开销,但在这个项目里完全可接受。更重要的是在记录格式层加上 CRC。我的每条记录结构设计成了这样:
| 偏移 | 字节数 | 内容 |
|---|---|---|
| 0 | 2 | 记录标识 Magic,比如 0x5AA5 |
| 2 | 4 | 记录长度或者时间戳 |
| 6 | N | 应用数据 |
| N+6 | 2 | CRC16 校验值 |
写入时先算好 CRC,再整块写入。读取时重新计算 CRC,如果和存储的 CRC 不一致,就丢弃这条记录或者尝试从备份区恢复。CRC 算法我用的是 CRC16-CCITT,查表法实现,120MHz 的 STM32F215ZG 算起来根本不耗时。有了 CRC,就算未来出现极端情况下的通信错误,应用层也能识别出来,不会把脏数据当成有效记录。
4.2 循环日志与掉电保护设计
日志系统里我用了循环覆盖的结构。日志区总共 512KB,规划成两块:一块是固定大小的“索引区”,存放当前写指针、当前读指针、日志版本号;另一块是“数据区”,按固定长度记录循环写入。每次上电先读索引区,定位到上次写的位置,然后继续写新日志。
掉电保护的关键在于索引区和数据区不能同时处于“中间状态”。我的做法是先写数据区,数据写完之后,最后再更新索引区的写指针。MRAM 的好处是写数据区时即使掉电,损坏的也只是当前这条数据记录,不会影响已存在的其他记录,更不会像 NOR Flash 那样可能需要整块擦除。上电后如果索引区显示的数据状态和实际数据区不匹配,系统会选择回退到上一组有效的写指针。
MRAM 写入过程中掉电,最坏情况是当前正在写的字节处于不确定状态,但不会破坏芯片本身。这个特性大大简化了工厂断电测试的验证工作。用 NOR Flash 时,我需要在写入前做擦除,擦除中掉电可能留下一个半擦除的扇区,恢复逻辑要复杂得多。
4.3 双备份与坏块处理
工业上对配置类数据,比如设备序列号、校准系数、运行模式,我会用双备份策略。同一份参数在 MRAM 的两个不同地址各存一份,每份都带 CRC。读取时先读第一份,如果 CRC 错误就检查第二份;如果两份都出错了,才认为参数区彻底损坏,进入出厂默认值模式。
MRAM 本身没有坏块概念,不存在像 NAND Flash 那样的坏块管理问题,所以双备份不是为了防止坏块,而是为了防止“单次写入被外部异常打断”这种极端情况。虽然 MRAM 很皮实,但双份存储的成本不高,带来的安全感很值得。
这里要注意一个边界条件:双备份区的地址间隔要错开,不要放在同一个电源域或者同一个页地址范围内。虽然 MRAM 不像 Flash 那样存在整块擦除风险,但为了避免极端情况下两个备份同时被掉电干扰的方法论,我还是把两份参数放在相距 128KB 的两个区间。
5. 常见问题与排查技巧
驱动写完之后,我花了不少时间在调试和稳定性测试上。这里把过程中遇到的和预料中的问题整理一下,基本覆盖了这套方案从原型到量产可能踩的坑。
5.1 读回全是 0xFF 或 0x00
这是 SPI 调试里最常见的问题,出现的原因大概率不是 MRAM 本身坏了,而是主控和芯片之间的链路没对上。优先检查顺序:MISO 和 MOSI 是不是接反了;CS 用软件控制但初始化后默认电平是低还是高;SPI 外设有没有启用;时钟极性和相位是不是和驱动代码设定一致。
我遇到过最隐蔽的一个问题是 GPIO 复用配置。STM32F2 系列的 SPI 引脚需要设置成复用功能(AF5),我第一版代码里把 PA5、PA6、PA7 初始化成了普通输出模式,主控发的数据自然进不了芯片。用逻辑分析仪看不到 SCK 波形的话,先回到 GPIO 配置这一步查起。
5.2 写进去的数据偶尔是乱的
如果读写小数据量没问题,大批量写入后出现乱掉的数据,先怀疑 SPI 速率过高。SCK 过快时,如果 SI 线上的数据在时钟边沿附近还在翻转,主控和芯片就可能采到错误电平。解决方案是降低 SPI 分频系数,或者把走线缩短、加串联电阻改善信号质量。
另一种可能是写使能时序没做到位。如果每次写操作前没有正确拉低 CS、发送 0x06、再拉高 CS,后续写命令会被芯片忽略。芯片忽略写命令时,你读到的是旧的随机数据,看起来就像“乱掉了”。在写函数开始加一个状态寄存器读取,确认 WEL 位已经置 1,再进入写命令,可以减少这类问题。
5.3 HOLD 和 WP 悬空的隐患
我前面提过,HOLD 和 WP 不能悬空。实际项目中我就吃过一次亏:因为 PCB 空间紧,这两个引脚没接任何东西,设备在继电器动作时偶发出现“写不进去”的情况。用示波器抓 HOLD 引脚,发现它被感应出了短暂的低电平脉冲,芯片以为主机要暂停通信,自然就不响应了。后来把两个引脚直接上拉到 3.3V,问题再没出现过。
如果你的板子需要支持现场固件升级,可能希望某些区域能被硬件写保护锁住,这时候 WP 可以由主控 GPIO 控制,但正常运行时必须保持高电平。
5.4 SPI Mode 0 和 Mode 3 的选择
MR25H40CDF 数据手册明确支持 Mode 0 和 Mode 3。我的代码用的 Mode 0(CPOL=0,CPHA=1 或 HAL 里的 POLARITY_LOW + PHASE_1EDGE),调试时改成 Mode 3 也能正常工作。但要注意:Mode 0 和 Mode 3 在首边缘和空闲电平上不同,如果换成别人的库或者直接用现成的 Flash 驱动,先确认双方配置是否一致,否则数据会偏移一个时钟周期,读出来的每个字节都错位。
顺手提一句,键盘扫描、逻辑分析仪协议解析和普通示波器观察时序时,用 Mode 0 的波形更容易理解,所以我默认推荐 Mode 0。
5.5 快速排查速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 读回全 0xFF | SPI 引脚复用没配好 / MISO 接反 | 检查 GPIO AF5 和片选默认电平 |
| 读回全 0x00 | CS 一直为低 / SCK 无波形 | 检查软件片选逻辑、SPI 外设时钟 |
| 写入后读回原值 | 没发写使能 / WP 拉低 | 检查 0x06 指令,WP 上拉 |
| 数据偶尔错位 | SPI 速率过高 / 时序边沿不稳 | 降频、减小走线长度、检查 Mode |
| 偶发不响应 | HOLD 引脚悬空受干扰 | HOLD 上拉到 VDD |
| 大批量写入失败 | DMA 回调未拉高 CS | 检查传输完成回调,确认 CS 电平 |
5.6 一点经验补遗
最后说个个人习惯:不管 MR25H40CDF 指标多好,我板子上还是会在 CS 和 HOLD 引脚预留了 0Ω 电阻焊位,方便调试时断开测试。量产程序里也保留了 CRC 校验和回读重试。存储介质再可靠,数据链路和软件状态机里犯的错也照样会让数据损坏。这套方案跑了大半年,现场没有因为存储介质本身出过问题,反而两次问题都出在我自己的驱动时序和引脚配置上。工程上“换颗芯片就解决所有问题”的情况很少,MRAM 解决的是存储介质层面的不靠谱,剩下的还是要靠代码和电路一起兜底。