1. 为什么选 MR25H40CDF 搭配 STM32L4S5ZI 做数据存储
1.1 这个组合到底解决什么问题
工业现场的数据记录仪、PLC 扩展模块、电力监测终端这类设备,有一个共同的痛点:频繁掉电,但关键数据不能丢。传统方案要么用 EEPROM(写入慢、寿命有限),要么用带后备电池的 SRAM(电池是隐患),要么用 SPI Flash(写之前要擦除,掉电瞬间容易丢数据)。MR25H40CDF 这颗 MRAM 芯片,恰好卡在一个很舒服的位置上——它像 RAM 一样可以按字节随机写,又像 Flash 一样掉电不丢数据,写入次数几乎无限,而且 SPI 接口简单,STM32L4S5ZI 直接挂上去就能用。
我最早接触这个组合是在一个配电柜监测项目里,客户要求每 100ms 记录一次三相电流值,断电后重新上电要能读出最近 10 分钟的数据。用 FRAM 成本太高,用 Flash 又怕掉电写坏,最后选了 MRAM,实测下来非常稳。这篇文章就把我踩过的坑、调过的参数、写过的驱动,完整地摊开讲一遍。
MR25H40CDF 是 4Mbit(512KB)容量的磁阻随机存储器,工作电压 2.7V~3.6V,SPI 接口最高 40MHz。STM32L4S5ZI 是 ST 的 Cortex-M4 低功耗系列,带 2MB Flash、640KB RAM,SPI 外设资源丰富,两者搭配在工业和嵌入式场景里属于“门当户对”。适合谁看?做过 STM32 但没碰过 MRAM 的嵌入式工程师、需要做掉电数据保护的硬件开发者、以及正在选型非易失存储方案的项目负责人。
1.2 MRAM 和 Flash、EEPROM 的本质区别
很多人第一次听说 MRAM 会懵:它到底算 RAM 还是 ROM?答案是——它两者都像,但都不是。MRAM 用磁性隧道结(MTJ)存储数据,靠电子自旋方向表示 0 和 1,不需要电荷保持,所以断电不丢;同时它没有 Flash 的“擦除块”概念,可以直接覆盖写任意字节。
这个区别在实际操作中影响巨大。举个例子:你要把 4 字节的计数器值从 100 改成 101。用 SPI Flash(比如 W25Q64),你得先把整个 4KB 扇区读到 RAM,改掉那 4 字节,擦除扇区,再写回去——整个过程几十毫秒,期间掉电就全没了。用 MR25H40CDF,直接发一条写命令,4 字节写进去,耗时不到 10 微秒,掉电也不怕。
| 特性 | MR25H40CDF (MRAM) | SPI Flash (W25Q64) | EEPROM (24C02) |
|---|---|---|---|
| 写入方式 | 按字节随机写 | 按扇区擦除后写 | 按字节写 |
| 擦除需求 | 无需擦除 | 必须擦除 | 无需擦除 |
| 写入寿命 | 几乎无限 | 约 10 万次 | 约 100 万次 |
| 写入速度 | 极快(无擦除延迟) | 慢(擦除耗时) | 中等 |
| 掉电风险 | 极低 | 擦除期间高 | 低 |
| 容量 | 512KB | 8MB | 256B |
| 成本 | 较高 | 低 | 低 |
选型逻辑很清晰:数据量不大、写入频繁、掉电要求高,就选 MRAM;数据量大、写入少、成本敏感,还是 Flash 划算。MR25H40CDF 的 512KB 对于参数存储、日志记录、状态快照这类需求绰绰有余。
1.3 STM32L4S5ZI 的 SPI 资源分配思路
STM32L4S5ZI 有 3 个 SPI 外设(SPI1/SPI2/SPI3),其中 SPI1 挂在 APB2 上,时钟最高 80MHz;SPI2/SPI3 挂在 APB1 上,最高 40MHz。MR25H40CDF 最高支持 40MHz SPI 时钟,所以三个 SPI 都能满足。
我的分配习惯是:SPI1 留给高速外设(如显示屏),SPI2 挂 MRAM,SPI3 留给其他低速传感器。原因有两个:一是 SPI2 在 APB1 上,配置灵活,分频后容易得到干净的 20MHz 或 10MHz;二是把 MRAM 单独挂一个 SPI 总线,避免和其他外设争抢片选和总线仲裁,调试时逻辑分析仪抓波形也清爽。
片选引脚我一般选 PA4 或 PB12 这类普通 GPIO,用软件片选。为什么不推荐硬件片选(NSS)?因为 STM32 的硬件 NSS 在多主机或需要精确控制片选时序的场景下反而添乱,软件片选一个HAL_GPIO_WritePin()就能搞定,时序完全可控。这一点在后面讲时序的时候还会展开。
2. 硬件连接与 SPI 时序关键细节
2.1 引脚连接与去耦电容布置
MR25H40CDF 是 8 引脚 SOIC 封装,引脚定义很标准:1 脚 CS(片选)、2 脚 SO(MISO)、3 脚 WP(写保护)、4 脚 GND、5 脚 SI(MOSI)、6 脚 SCK、7 脚 HOLD(保持)、8 脚 VCC。
和 STM32L4S5ZI 的连接方式如下:
- CS 接 PA4(GPIO 输出,软件控制)
- SCK 接 PB13(SPI2_SCK)
- MISO 接 PB14(SPI2_MISO)
- MOSI 接 PB15(SPI2_MOSI)
- WP 和 HOLD 都接 VCC(禁用写保护和保持功能,简化控制)
这里有个容易翻车的点:WP 和 HOLD 绝对不能悬空。我见过有人把这两个脚空着,结果写入偶尔失败,查了半天以为是时序问题,其实是 HOLD 脚被干扰拉低,芯片进入了保持状态。直接上拉到 VCC 最省心。
去耦电容方面,MR25H40CDF 的 VCC 和 GND 之间必须放一颗 0.1μF 的陶瓷电容,位置尽量靠近芯片引脚,走线越短越好。如果 SPI 时钟跑到 20MHz 以上,建议再并一颗 1μF 的电容。这不是玄学——MRAM 在写入瞬间电流会有小幅波动,去耦不好会导致写入数据偶发错误,而且这种错误很难复现,排查起来极其痛苦。
PCB 走线还有几个经验:SCK 和 MOSI 尽量等长,MISO 单独走,避免和 SCK 平行太长导致串扰。如果板子上有多个 SPI 从设备,每个设备的 CS 走线单独拉,不要共用。地平面尽量完整,SPI 信号线下面不要有分割。
2.2 SPI 模式选择与时钟极性相位
MR25H40CDF 支持 SPI 模式 0(CPOL=0,CPHA=0)和模式 3(CPOL=1,CPHA=1)。这两个模式的区别在于时钟空闲电平和采样边沿。模式 0 是时钟空闲低电平,上升沿采样;模式 3 是时钟空闲高电平,下降沿采样。
我一般选模式 0,因为 STM32 的 HAL 库默认配置就是模式 0,而且大多数逻辑分析仪和示波器对低电平空闲的波形解读更直观。配置代码大概是这样:
hspi2.Instance = SPI2; hspi2.Init.Mode = SPI_MODE_MASTER; hspi2.Init.Direction = SPI_DIRECTION_2LINES; hspi2.Init.DataSize = SPI_DATASIZE_8BIT; hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL = 0 hspi2.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA = 0 hspi2.Init.NSS = SPI_NSS_SOFT; // 软件片选 hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 80MHz/4 = 20MHz hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; // 高位先发 hspi2.Init.TIMode = SPI_TIMODE_DISABLE; hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;时钟分频这里要算一下:STM32L4S5ZI 的 SPI2 挂在 APB1 上,APB1 时钟默认 80MHz(取决于时钟树配置)。分频系数 4 得到 20MHz,分频系数 2 得到 40MHz。MR25H40CDF 手册标称最高 40MHz,但实际在工业温度范围(-40°C~85°C)内,我建议降到 20MHz 使用,留足余量。我实测过 40MHz 在常温下没问题,但高温环境下偶发读取错误,降到 20MHz 后彻底稳定。
提示:SPI 时钟不是越高越好。MRAM 的读取是随机的,时钟太高会导致信号完整性下降,尤其是在排线较长或板间连接的场景。20MHz 对于 512KB 数据来说,读满整个芯片也就 200ms 左右,完全够用。
2.3 片选时序与命令间隔
MR25H40CDF 的片选时序有两个硬性要求:CS 拉低到第一个 SCK 上升沿之间至少 5ns,最后一个 SCK 下降沿到 CS 拉高之间至少 5ns。5ns 听起来很短,但如果你的 GPIO 翻转速度配置不对,或者代码里 CS 操作和 SPI 传输之间插了其他指令,就可能违反。
用 HAL 库的时候,HAL_SPI_Transmit()和HAL_SPI_Receive()内部会等 TXE 和 RXNE 标志,函数返回时最后一个字节其实已经发完了,但 SCK 可能还有余波。稳妥的做法是在 CS 拉高之前加一个极短的延时,或者直接依赖 HAL 库的阻塞式传输——它返回时 SCK 已经停止。我一般这样写:
void MRAM_CS_Low(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); } void MRAM_CS_High(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } uint8_t MRAM_Transfer(uint8_t data) { uint8_t rx; HAL_SPI_TransmitReceive(&hspi2, &data, &rx, 1, 100); return rx; }还有一个细节:两条命令之间 CS 必须拉高至少 10ns。如果你连续读两个地址,不能一直把 CS 拉低然后接着发命令,必须中间拉高一次。我见过有人为了省事把 CS 一直拉低,结果第二次读出来的数据全是 0xFF。原因是 MR25H40CDF 的状态机在 CS 持续低电平时不会复位,命令解析出错。
3. 驱动实现:从读 ID 到页写完整流程
3.1 读设备 ID 验证通信
上电第一件事不是急着读写数据,而是读设备 ID,确认 SPI 通信正常。MR25H40CDF 的 RDID 命令是 0x9F,发送后会返回 3 字节:厂商 ID(0xE0)、产品 ID 高字节(0x48)、产品 ID 低字节(0x03)。等等,这里要纠正一下——MR25H40CDF 的实际返回是厂商 ID 0xE0,后面两字节是 0x48 和 0x03,组合起来标识容量和型号。
uint32_t MRAM_ReadID(void) { uint8_t tx[4] = {0x9F, 0x00, 0x00, 0x00}; uint8_t rx[4] = {0}; MRAM_CS_Low(); HAL_SPI_TransmitReceive(&hspi2, tx, rx, 4, 100); MRAM_CS_High(); return ((uint32_t)rx[1] << 16) | ((uint32_t)rx[2] << 8) | rx[3]; }如果读出来是 0x000000 或 0xFFFFFF,先查硬件:CS 有没有接对、MISO 和 MOSI 有没有接反、供电是不是 3.3V。我遇到过最离谱的一次是 MISO 虚焊,读出来一直是 0xFF,用万用表量通断才发现。
3.2 写使能与状态寄存器操作
MR25H40CDF 的写操作需要先发 WREN(0x06)命令,把内部的写使能锁存器置位。这个锁存器在每次写操作后自动复位,所以每次写之前都要发 WREN。读操作不需要 WREN。
状态寄存器(RDSR,0x05)里有一个 WEL 位(bit 1),可以用来确认 WREN 是否生效。调试阶段我建议每次写完都读一下状态寄存器,确认 WEL 被正确置位和清除。正式代码里可以省掉,但初期排查问题时这个步骤能省很多时间。
void MRAM_WriteEnable(void) { uint8_t cmd = 0x06; MRAM_CS_Low(); HAL_SPI_Transmit(&hspi2, &cmd, 1, 100); MRAM_CS_High(); } uint8_t MRAM_ReadStatus(void) { uint8_t tx[2] = {0x05, 0x00}; uint8_t rx[2] = {0}; MRAM_CS_Low(); HAL_SPI_TransmitReceive(&hspi2, tx, rx, 2, 100); MRAM_CS_High(); return rx[1]; }状态寄存器还有一个 bit 0 是 WIP(写进行中),MRAM 的写入速度极快,WIP 位几乎瞬间就清零,所以一般不需要轮询等待。但如果你在极端低温环境下使用,建议还是加一个超时轮询,保险起见。
3.3 单字节读写与页写操作
单字节写命令是 WRITE(0x02),后跟 3 字节地址(24 位,因为 512KB 需要 19 位地址,但协议用 24 位对齐),然后是要写的数据。单字节读命令是 READ(0x03),同样后跟 3 字节地址,然后时钟继续输出数据。
void MRAM_WriteByte(uint32_t addr, uint8_t data) { uint8_t tx[5]; tx[0] = 0x02; tx[1] = (addr >> 16) & 0xFF; tx[2] = (addr >> 8) & 0xFF; tx[3] = addr & 0xFF; tx[4] = data; MRAM_WriteEnable(); MRAM_CS_Low(); HAL_SPI_Transmit(&hspi2, tx, 5, 100); MRAM_CS_High(); } uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t tx[4] = {0x03, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF}; uint8_t rx[4] = {0}; MRAM_CS_Low(); HAL_SPI_TransmitReceive(&hspi2, tx, rx, 4, 100); uint8_t data; HAL_SPI_Receive(&hspi2, &data, 1, 100); MRAM_CS_High(); return data; }页写(Page Write)不是 MRAM 特有的,但 MR25H40CDF 支持连续写——发一次 WRITE 命令后,可以连续写多个字节,地址自动递增,直到 CS 拉高。这比单字节写效率高得多。写 256 字节的缓冲区,用页写一次搞定,耗时不到 100 微秒。
void MRAM_WriteBuffer(uint32_t addr, uint8_t *buf, uint16_t len) { uint8_t header[4]; header[0] = 0x02; header[1] = (addr >> 16) & 0xFF; header[2] = (addr >> 8) & 0xFF; header[3] = addr & 0xFF; MRAM_WriteEnable(); MRAM_CS_Low(); HAL_SPI_Transmit(&hspi2, header, 4, 100); HAL_SPI_Transmit(&hspi2, buf, len, 1000); MRAM_CS_High(); }注意地址边界:MR25H40CDF 的地址空间是 0x00000 到 0x7FFFF(512KB)。如果起始地址加上长度超过 0x7FFFF,地址会回卷到 0x00000。写之前一定要检查边界,否则会覆盖开头的数据。我一般会在驱动层加一个断言或者返回错误码。
3.4 掉电保护的数据结构设计
MRAM 的最大价值在于掉电保护,但光有硬件不够,数据结构设计才是关键。我的做法是在 MRAM 里划两个区域:一个是参数区(固定地址,存配置参数),一个是日志区(环形缓冲区,存运行记录)。
参数区用“双备份 + 校验”的方式:同一份参数存两份,每份带 CRC16 校验。写入时先写备份区,再写主区。读取时如果主区 CRC 错误,自动切到备份区。这样即使写入过程中掉电,至少有一份数据是完整的。
日志区用环形缓冲区,头指针和尾指针也存在 MRAM 里。每次写日志前先更新数据,再更新尾指针。掉电后重新上电,根据头尾指针恢复日志。这里有个技巧:尾指针的更新要用“先写数据,再写指针”的顺序,而且指针写入要用单字节写,确保原子性。MRAM 的单字节写是原子的,不会出现写了一半的情况。
typedef struct { uint32_t magic; // 0x4D52414D ("MRAM") uint32_t version; uint8_t params[64]; uint16_t crc; } ParamBlock; void SaveParams(ParamBlock *p) { p->magic = 0x4D52414D; p->crc = CRC16((uint8_t*)p, sizeof(ParamBlock) - 2); // 先写备份区 MRAM_WriteBuffer(ADDR_PARAM_BACKUP, (uint8_t*)p, sizeof(ParamBlock)); // 再写主区 MRAM_WriteBuffer(ADDR_PARAM_MAIN, (uint8_t*)p, sizeof(ParamBlock)); }4. 实测性能与常见问题排查
4.1 读写速度实测数据
我用 STM32L4S5ZI 在 20MHz SPI 时钟下做了实测,数据如下:
| 操作 | 数据量 | 耗时 | 备注 |
|---|---|---|---|
| 单字节写 | 1B | 约 8μs | 含 WREN 命令 |
| 页写 | 256B | 约 110μs | 连续写 |
| 单字节读 | 1B | 约 6μs | 含地址发送 |
| 连续读 | 512KB | 约 210ms | 全片读取 |
| 参数保存 | 72B | 约 35μs | 双备份写入 |
这个速度对于工业数据记录完全够用。作为对比,同容量 SPI Flash 写 256 字节需要先擦除 4KB 扇区(约 50ms),再写入(约 1ms),总共 51ms 左右。MRAM 快了将近 500 倍。
4.2 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 读 ID 返回 0xFF | MISO 未连接或虚焊 | 万用表量通断 | 重新焊接 |
| 读 ID 返回 0x00 | MOSI 和 MISO 接反 | 检查原理图 | 交换接线 |
| 写入后读回数据错误 | WP 或 HOLD 悬空 | 量电压 | 上拉到 VCC |
| 偶发写入失败 | 去耦电容不足 | 示波器看 VCC 纹波 | 加 0.1μF 电容 |
| 高速时钟下数据错误 | SPI 时钟过高 | 降低分频 | 降到 20MHz |
| 连续读第二段出错 | CS 未拉高 | 逻辑分析仪看 CS | 命令间拉高 CS |
| 地址回卷覆盖数据 | 未检查边界 | 检查地址范围 | 加边界断言 |
4.3 调试经验与避坑技巧
第一个坑:逻辑分析仪是必备的。SPI 通信出问题,光靠 printf 调试效率极低。一个几百块的单片机逻辑分析仪,能直接解码 SPI 波形,看到命令、地址、数据对不对,五分钟定位问题。我早期不用逻辑分析仪,靠猜,一个 CS 时序问题查了两天。
第二个坑:MRAM 不需要擦除,但需要 WREN。很多人从 Flash 转过来,习惯性地想“先擦除再写”,结果发现 MRAM 根本没有擦除命令。但 WREN 是必须的,而且每次写之前都要发。我见过有人只在初始化时发一次 WREN,后面写入全失败。
第三个坑:高温环境下降频使用。工业级应用温度范围宽,MRAM 在 85°C 时 40MHz 时序余量很小。我现在的习惯是不管什么环境,统一用 20MHz,稳定性优先。速度损失可以接受,数据丢了才是大问题。
第四个坑:电源上电顺序。如果 MRAM 和 STM32 用不同的 LDO 供电,要确保 MRAM 先上电或同时上电。如果 STM32 先上电,MRAM 还没准备好,初始化读 ID 会失败。简单做法是共用一个 3.3V 电源,或者加一个电源监控芯片。
提示:MR25H40CDF 的数据保持时间在 85°C 下标称 10 年,在 25°C 下超过 20 年。对于大多数工业设备来说,这个寿命足够覆盖整个产品周期。但如果你做的是需要 20 年以上数据保持的设备,建议定期做数据刷新(读出来再写回去),虽然 MRAM 理论上不需要,但刷新能发现潜在的位翻转问题。
4.4 与 STM32 HAL 库的配合技巧
STM32CubeMX 生成 SPI 初始化代码很方便,但有几个地方需要手动改。一是NSS 必须设为 SPI_NSS_SOFT,否则 HAL 库会尝试控制硬件 NSS 引脚,和你的软件片选冲突。二是BaudRatePrescaler 要根据实际时钟树计算,CubeMX 里显示的时钟频率是理论值,实际 APB1 时钟可能因为低功耗模式降频。
还有一个 HAL 库的坑:HAL_SPI_TransmitReceive()在传输大量数据时,如果超时时间设得太短,会返回 HAL_TIMEOUT。我一般把超时设成 1000ms,对于 512KB 连续读,分成多次 256 字节的传输,每次超时 100ms,这样既不会误判超时,也不会因为一次传输太大导致看门狗复位。
低功耗场景下,STM32L4S5ZI 进入 STOP 模式前,要确保 SPI 传输完成,CS 拉高。唤醒后重新初始化 SPI 外设,因为 STOP 模式会关闭 SPI 时钟。这一点在电池供电的数据记录仪里很关键,我做过一个项目,唤醒后没重新初始化 SPI,读 MRAM 全是 0,查了一天才发现是时钟没开。
5. 工业场景下的扩展应用与选型建议
5.1 数据记录仪的完整实现思路
把 MR25H40CDF 和 STM32L4S5ZI 组合成一个数据记录仪,核心逻辑是:定时器触发 ADC 采样,采样数据经过简单处理后写入 MRAM 环形缓冲区,同时通过 UART 或无线模块上传。掉电时,MRAM 里的数据自动保留,重新上电后从环形缓冲区读取未上传的数据。
这里的关键是写入频率和缓冲区大小的平衡。假设每 100ms 记录一次,每次 16 字节,一天的数据量是 16 × 10 × 3600 × 24 = 13.8MB,超过了 512KB 的容量。所以要么降低频率,要么只记录变化量或异常值。我的做法是:正常数据每 1 秒记录一次,异常数据(超过阈值)立即记录,这样 512KB 能存大约 3 天的数据,足够覆盖大多数工业场景的维护周期。
环形缓冲区的实现要注意读写指针的原子性。写指针在写入数据后更新,读指针在读取数据后更新。两个指针都存在 MRAM 的固定地址,每次更新用单字节写。如果掉电发生在数据写入后、指针更新前,那部分数据会被视为无效,下次写入时覆盖。这个逻辑保证了数据的一致性。
5.2 多设备共用 SPI 总线的注意事项
如果板子上有多个 SPI 从设备,MRAM 和其他设备共用 SCK、MISO、MOSI,各自有独立的 CS。这种情况下,CS 的管理是核心。任何时刻只能有一个 CS 拉低,否则总线冲突,数据全乱。
我的做法是在驱动层加一个互斥锁(RTOS 环境下用信号量,裸机环境下用全局标志位)。每次操作 MRAM 前获取锁,操作完释放。这样即使多个任务同时访问,也不会冲突。另外,SPI 的初始化只在系统启动时做一次,切换设备时只切换 CS,不重新初始化 SPI。
还有一个细节:MISO 引脚在 CS 拉高后可能处于高阻态,如果其他设备的 MISO 也是高阻,总线会浮空。解决办法是在 MISO 上加一个 10kΩ 的上拉电阻,确保空闲时是高电平。这个电阻很多原理图上会漏掉,导致读取数据偶尔出现 0xFF。
5.3 选型对比:什么场景选 MRAM,什么场景选别的
MRAM 不是万能的,选型要看具体需求。我整理了一个决策逻辑:
- 写入频繁(每秒多次)、数据量小(<1MB)、掉电要求高:选 MRAM,比如 MR25H40CDF。
- 写入少(每天几次)、数据量大(>1MB)、成本敏感:选 SPI Flash,比如 W25Q64。
- 写入极少(每月几次)、数据量极小(<1KB)、成本极敏感:选 EEPROM,比如 24C02。
- 需要极高读写速度、数据量中等、有后备电源:选 SRAM + 电池,但电池是隐患。
MR25H40CDF 的定位很明确:工业级、高频写入、掉电保护。它的成本比 Flash 高,但比 FRAM 低,在两者之间找到了一个平衡点。如果你的项目需要每秒钟写几十次数据,而且不能丢,那 MRAM 是当前最务实的选择。
5.4 长期运行的可靠性验证
工业设备要求长期稳定运行,MRAM 的可靠性验证不能省。我一般做三项测试:高温老化测试(85°C 连续读写 72 小时)、掉电测试(随机断电 1000 次,检查数据完整性)、振动测试(模拟运输和现场振动)。
掉电测试最麻烦,需要做一个自动断电装置,用 MOS 管控制电源,MCU 控制通断。每次断电后重新上电,读取 MRAM 里的数据,和预期值对比。我做过一个项目,1000 次掉电测试中出现了 3 次数据错误,最后发现是电源下降太慢,MRAM 在电压低于 2.7V 时还在写入。解决办法是加一个电压监控芯片,电压低于 2.9V 时禁止写入。
这个经验很值钱:MRAM 虽然掉电不丢数据,但写入过程中掉电仍然可能出错。所以电源监控和写入保护是必须的。不要以为用了 MRAM 就万事大吉,硬件设计上的保护措施一个都不能少。
最后分享一个我在实际项目中的体会:MR25H40CDF 和 STM32L4S5ZI 这个组合,硬件连接简单,驱动不复杂,真正的难点在数据结构和掉电保护逻辑的设计。把这两块想清楚了,剩下的就是调时序和做测试。我踩过的坑基本都写在上面了,希望能帮你少走弯路。如果你也在做类似的项目,建议先用逻辑分析仪把 SPI 波形抓出来,确认命令和时序都对,再往上写应用逻辑,这样效率最高。