
1. 为什么 MRAM 在工业嵌入式场景里越来越受关注搞过工业数据采集或者电力终端的朋友应该都有体会选存储芯片这件事很多时候比选主控还让人头疼。EEPROM 写入慢、擦写寿命有限NOR Flash 虽然容量大但写入前必须擦除整个扇区而且擦写次数也就十万次量级频繁记录运行日志或者掉电保存关键参数的时候总担心哪天把扇区写坏。FRAM 倒是不错可惜容量小、价格高而且供货一直不太稳定。这几年 MRAM 慢慢进入大家的视野MR25H40CDF 就是其中比较有代表性的一颗4Mbit 容量、SPI 接口、号称近乎无限的擦写次数配合 STM32F407VGT6 这种带硬件 SPI 和丰富外设的主控在工业现场数据记录、参数存储、掉电保护这些场景里确实很能打。我最早接触 MR25H40CDF 是在一个配电终端的项目上当时的需求是每 100ms 记录一次三相电压电流的有效值还要保存最近 24 小时的曲线数据算下来每天写入量相当可观。一开始用的 EEPROM结果不到半年就出现了个别地址写入失败的情况后来换成 FRAM 容量又不够最后选了 MR25H40CDF 才把这个问题彻底解决。这篇文章就把我在这个项目里积累的经验完整梳理一遍从芯片选型、硬件设计、SPI 驱动配置到实际读写测试尽量把每个环节的坑都讲清楚。不管你是刚接触 MRAM 的新手还是已经在用 STM32 做存储方案的老手应该都能从中找到一些有用的东西。MR25H40CDF 这颗芯片的核心卖点其实就三个第一它是真正的非易失性存储掉电数据不丢这点和 Flash 一样第二它的写入不需要擦除可以按字节直接写这点像 RAM第三它的擦写寿命标称 10^14 次以上基本上你随便写这辈子都写不坏。这三点加起来就决定了它在需要频繁写入、又要求掉电不丢数据的场景里几乎没有对手。当然它也有缺点容量只有 512KB价格比同容量的 Flash 贵不少所以怎么用、用在哪还是得看具体需求。2. MR25H40CDF 与 STM32F407VGT6 的硬件设计要点2.1 MR25H40CDF 的关键参数与选型考量先把这颗芯片的基本参数过一遍方便后面理解硬件和软件设计。MR25H40CDF 是 Everspin 家的 4Mbit MRAM注意是 4Mbit 不是 4MByte换算过来就是 512KByte。供电范围 2.7V 到 3.6V和 STM32F407 的 3.3V 系统完全兼容不需要额外的电平转换。SPI 接口最高支持 40MHz 时钟不过实际用的时候建议先跑 10MHz 到 20MHz稳定之后再往上提。工作温度范围有工业级和汽车级可选工业级是 -40°C 到 85°C汽车级能到 125°C一般工业场景选工业级就够了。封装方面MR25H40CDF 常见的是 8 引脚 DFN 或者 8 引脚 SOICDFN 体积小但焊接要求高手工调试不太方便SOIC 相对好焊一些打样阶段建议用 SOIC 封装。引脚定义和标准的 SPI Flash 基本一致CS、SCK、SI、SO、VCC、GND另外还有 WP 和 HOLD 两个引脚。这里要注意MR25H40CDF 的 WP 和 HOLD 功能和 Flash 不太一样具体怎么接要看数据手册不能想当然地照搬 Flash 的电路。选型的时候有几个点需要特别留意。第一是容量512KB 听起来不大但如果只是存参数和日志其实很够用了。我那个配电终端项目每条记录 32 字节24 小时的数据也就 27MB 左右当然这是原始数据实际存的时候做了压缩和抽稀最后只用了不到 200KB。第二是速度MRAM 的写入速度比 EEPROM 快几个数量级但比 SRAM 还是慢写一个字节大概需要几十纳秒的时序实际受 SPI 时钟限制。第三是价格MR25H40CDF 单颗价格大概是同容量 Flash 的十几倍所以如果写入不频繁用 Flash 就够了没必要上 MRAM。2.2 STM32F407VGT6 的 SPI 外设配置思路STM32F407VGT6 这颗主控大家应该很熟了Cortex-M4 内核168MHz 主频带 3 个 SPI 接口其中 SPI1 挂在 APB2 总线上最高时钟能到 84MHzSPI2 和 SPI3 挂在 APB1 上最高 42MHz。用 SPI1 接 MR25H40CDF 是比较常见的选择因为 APB2 时钟高分频后容易得到合适的 SPI 时钟。不过要注意SPI1 的引脚和 JTAG 有复用如果用 SWD 调试问题不大用 JTAG 的话可能会冲突这个后面讲引脚分配的时候再细说。STM32F407 的 SPI 支持四种模式由 CPOL 和 CPHA 组合决定。MR25H40CDF 支持模式 0 和模式 3也就是 CPOL0/CPHA0 或者 CPOL1/CPHA1。我一般用模式 0因为大部分 SPI Flash 也是模式 0代码移植方便。数据位宽固定 8 位MSB 先出这个和 MRAM 的要求一致。NSS 引脚可以硬件管理也可以软件管理我建议用软件管理也就是普通 GPIO 控制片选这样更灵活也方便在一个 SPI 总线上挂多个从设备。时钟分频这块需要算一下。SPI1 挂在 APB2 上如果系统时钟 168MHzAPB2 不分频的话就是 168MHzSPI1 的时钟源就是 168MHz。分频系数可以选择 2、4、8、16、32、64、128、256对应 SPI 时钟 84MHz、42MHz、21MHz、10.5MHz、5.25MHz、2.625MHz、1.3125MHz、0.65625MHz。MR25H40CDF 最高支持 40MHz所以分频系数最小只能选 4得到 42MHz稍微超了一点保险起见选 8得到 21MHz这个速度已经很够用了。实际调试的时候可以先从 128 分频开始也就是 1.3MHz 左右确认通信正常后再逐步提高。2.3 硬件连接与 PCB 布局注意事项硬件连接本身不复杂但有几个细节容易出问题。先看引脚分配我一般这样接SPI1_SCK 用 PA5SPI1_MISO 用 PA6SPI1_MOSI 用 PA7片选 CS 用 PA4 或者任意一个普通 GPIO。这里要注意 PA4 同时也是 SPI1 的 NSS 引脚如果配置成普通 GPIO 输出需要先把 SPI1 的 NSS 功能关掉否则可能会有冲突。另外 PA13、PA14、PA15 和 JTAG 复用如果用 SWD 调试PA15 可以释放出来做普通 IO但需要先关闭 JTAG 功能这个在 GPIO 初始化的时候配置一下就行。电源去耦很重要。MR25H40CDF 的 VCC 引脚旁边一定要放一个 0.1uF 的陶瓷电容尽量靠近芯片引脚另外再并一个 1uF 或者 10uF 的钽电容做低频滤波。MRAM 在写入瞬间电流会有波动去耦不好可能导致写入失败或者数据出错。地线要尽量粗SPI 信号线尽量等长如果走线比较长比如超过 10cm建议在 SCK 和 MOSI 上串一个 22 欧姆到 33 欧姆的电阻做阻抗匹配减少反射和过冲。PCB 布局方面MRAM 芯片尽量靠近主控SPI 走线不要跨分割地平面远离高频或者大电流的走线。如果板子上有电机驱动或者继电器MRAM 要尽量远离这些干扰源。CS 信号线对干扰比较敏感因为它是控制信号一旦误触发就可能导致通信错误所以 CS 走线尽量短必要时可以在 CS 上并一个 10k 的上拉电阻保证空闲时处于高电平。我之前有个项目就是因为 CS 走线太长旁边又走了 PWM 线结果偶尔出现误写入后来加了上拉电阻并且重新布线才解决。3. SPI 驱动开发与 MRAM 读写实现3.1 STM32CubeMX 配置与底层驱动生成用 STM32CubeMX 配置 SPI1 比较省事但有几个选项需要手动改一下。打开 CubeMX选好 STM32F407VGT6 芯片在 Connectivity 里找到 SPI1Mode 选 Full-Duplex MasterHardware NSS Signal 选 Disable因为我们用软件片选。Parameter Settings 里Frame Format 选 MotorolaData Size 选 8 BitsFirst Bit 选 MSB FirstClock Polarity 选 LowClock Phase 选 1 Edge这样就是模式 0。Clock Prescaler 先选 128 分频后面再调。CRC Calculation 选 DisabledMRAM 不需要 CRC。GPIO 这边PA5、PA6、PA7 会自动配置成 SPI 复用功能PA4 需要手动配置成 GPIO Output初始电平设为 High也就是片选无效状态。如果要用 PA15 做其他用途需要在 SYS 里把 Debug 改成 Serial Wire这样 JTAG 的引脚就释放出来了。时钟树配置里确保 APB2 时钟是 168MHz这样 SPI1 的时钟源就是 168MHz。生成代码的时候Toolchain 选 MDK-ARM 或者 STM32CubeIDE看个人习惯。生成的代码里SPI 初始化函数 MX_SPI1_Init() 会自动调用我们主要关注的是 HAL_SPI_Transmit() 和 HAL_SPI_Receive() 这两个函数。HAL 库的 SPI 函数用起来简单但效率不如直接操作寄存器如果对速度要求高可以后面再优化。片选控制需要自己写我一般封装两个宏或者内联函数CS_LOW() 和 CS_HIGH()分别对应 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) 和 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)。3.2 MR25H40CDF 的指令集与读写时序MR25H40CDF 的指令集和 SPI Flash 类似但简化了不少因为它不需要擦除。常用的指令有这几个WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据。注意 MRAM 没有扇区擦除指令也不需要页编程直接 WRITE 就能写任意字节这是它和 Flash 最大的区别。读数据的时序是这样的拉低 CS发送 READ 指令 0x03然后发送 3 个字节的地址24 位地址高位在前接着就可以连续读取数据了每读一个字节地址自动加一直到拉高 CS 结束。写数据的时序类似拉低 CS先发送 WREN 指令 0x06拉高 CS然后再拉低 CS发送 WRITE 指令 0x02发送 3 字节地址然后发送要写入的数据最后拉高 CS。注意 WREN 和 WRITE 之间必须拉高 CS这是 MRAM 的要求和 Flash 一样。这里有个细节容易搞错MRAM 的写操作不需要等待忙状态因为它是非易失性存储但写入是即时完成的不像 Flash 需要等待擦除或者编程周期。不过为了保险写完以后可以读一下状态寄存器确认 WEL 位被清零了说明写操作已经完成。状态寄存器的 bit0 是 WIP 位MRAM 里这个位一般读出来是 0bit1 是 WEL 位写使能后为 1写完成后自动清零。3.3 底层读写函数的封装与实现先写几个基础函数。第一个是写使能函数发送 0x06 指令就行void MRAM_WriteEnable(void) { CS_LOW(); uint8_t cmd 0x06; HAL_SPI_Transmit(hspi1, cmd, 1, 100); CS_HIGH(); }第二个是读状态寄存器函数用来检查 WEL 位uint8_t MRAM_ReadStatus(void) { uint8_t cmd 0x05; uint8_t status 0; CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_SPI_Receive(hspi1, status, 1, 100); CS_HIGH(); return status; }第三个是写数据函数注意要先发 WREN再发 WRITEvoid MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_WriteEnable(); CS_LOW(); uint8_t cmd 0x02; HAL_SPI_Transmit(hspi1, cmd, 1, 100); uint8_t addr_buf[3]; addr_buf[0] (addr 16) 0xFF; addr_buf[1] (addr 8) 0xFF; addr_buf[2] addr 0xFF; HAL_SPI_Transmit(hspi1, addr_buf, 3, 100); HAL_SPI_Transmit(hspi1, buf, len, 1000); CS_HIGH(); }第四个是读数据函数void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { uint8_t cmd 0x03; CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); uint8_t addr_buf[3]; addr_buf[0] (addr 16) 0xFF; addr_buf[1] (addr 8) 0xFF; addr_buf[2] addr 0xFF; HAL_SPI_Transmit(hspi1, addr_buf, 3, 100); HAL_SPI_Receive(hspi1, buf, len, 1000); CS_HIGH(); }这几个函数是最基础的版本实际用的时候可以加上超时处理和错误返回。HAL_SPI_Transmit 和 HAL_SPI_Receive 的最后一个参数是超时时间单位是毫秒根据数据长度调整。如果数据量大比如一次写几百字节超时时间要设长一点否则可能返回 HAL_TIMEOUT。3.4 提高读写效率的优化技巧上面那套函数能用但效率一般主要瓶颈在 HAL 库的函数调用开销和逐字节传输。如果对速度有要求可以改成 DMA 传输或者直接操作 SPI 寄存器。DMA 方式适合大批量数据读写配置好 DMA 通道后CPU 只需要启动传输剩下的交给 DMA 完成传输结束后触发中断。SPI1 的 TX 可以用 DMA2 Stream3 或者 Stream5RX 可以用 DMA2 Stream0 或者 Stream2具体看数据手册的 DMA 请求映射表。另一个优化点是减少片选切换次数。如果连续写多个不连续的地址可以合并成一次写操作只要地址是递增的。MRAM 的写操作支持地址自动递增所以如果数据结构设计得好可以把多个参数放在连续的地址空间里一次写进去。读操作也一样连续读比多次单字节读快得多。还有一个技巧是用 SPI 的 16 位或者 8 位数据帧。STM32F407 的 SPI 支持 8 位和 16 位数据帧MRAM 本身是按字节操作的但用 16 位帧传输可以减少一半的传输次数前提是数据长度是偶数。不过要注意字节序问题STM32 是小端模式SPI 发送 16 位数据时先发低字节还是高字节需要根据 MRAM 的要求调整搞错了数据就乱了。4. 实际项目中的问题排查与经验总结4.1 常见通信故障与排查思路调试 SPI 通信的时候最常见的问题就是读出来全是 0xFF 或者全是 0x00。全 0xFF 一般是 MISO 线没接好或者从设备没响应全 0x00 可能是 MOSI 或者 SCK 有问题。先用示波器或者逻辑分析仪抓一下波形看 SCK 有没有输出MOSI 有没有数据CS 有没有拉低。如果没有逻辑分析仪用万用表测一下电压也行SCK 空闲时应该是低电平模式 0CS 空闲时应该是高电平。另一个常见问题是读写地址错位。比如写进去的数据读出来不对或者读出来的数据整体偏移了一个字节。这种情况一般是地址发送的字节序搞错了MRAM 要求 24 位地址高位在前也就是先发 A23-A16再发 A15-A8最后发 A7-A0。如果搞反了地址就完全乱了。还有可能是 CS 拉高拉低的时机不对比如在发送指令之前就拉高了 CS或者数据还没发完就拉高了 CS。还有一种情况是写入成功但读出来是旧数据。这个一般是写使能没生效或者写操作被中断了。检查一下 WREN 指令有没有正确发送WEL 位有没有置起来。如果系统里有中断写操作过程中被中断打断可能导致 CS 被意外拉高写操作就失败了。解决办法是在写操作前后关中断或者用互斥锁保护。4.2 数据完整性与掉电保护策略MRAM 虽然写入速度快、寿命长但也不是完全不会出错。工业现场电磁干扰大SPI 通信可能受到干扰导致数据出错。为了提高数据完整性可以在数据后面加校验比如 CRC16 或者简单的累加和。写入的时候计算校验值一起写进去读取的时候重新计算并比对不一致就说明数据出错了。掉电保护是另一个需要考虑的问题。MRAM 本身是非易失性的掉电数据不丢但如果掉电发生在写入过程中可能只写了一半数据就不完整了。解决办法是用双备份加标志位的方式准备两个存储区域 A 和 B写入的时候先写 A写完后在标志区写一个标志表示 A 有效然后再写 B写完后更新标志表示 B 有效。读取的时候根据标志判断哪个区域的数据是完整的。这样即使掉电发生在写入过程中至少有一个区域的数据是完整的。还有一个细节是写入顺序。如果数据结构里有多个字段写入顺序会影响掉电后的数据一致性。比如先写数据再写校验如果写完数据掉电了校验还是旧的读出来校验就不对。所以应该先写校验再写数据或者用版本号机制每次写入递增版本号读取的时候选版本号最新的那组数据。4.3 常见问题速查表问题现象可能原因排查方法解决办法读出全 0xFFMISO 未连接或从设备无响应检查 MISO 连线测 CS 是否拉低重新焊接 MISO确认 CS 控制正常读出全 0x00MOSI 或 SCK 无输出用逻辑分析仪抓 SCK 和 MOSI 波形检查 SPI 初始化配置确认引脚复用正确数据错位一个字节地址字节序错误核对地址发送顺序改为高位在前先发 A23-A16写入后读出旧数据写使能未生效读状态寄存器检查 WEL 位确保 WREN 指令在 WRITE 之前发送偶尔写入失败中断打断写操作检查写操作期间是否有中断写操作前后关中断或用互斥锁高速时钟下通信不稳定SPI 时钟太快或走线太长降低时钟分频检查走线降低 SPI 时钟加阻抗匹配电阻数据偶尔出错电磁干扰增加校验检查 PCB 布局加 CRC 校验远离干扰源4.4 实操心得与避坑建议第一个心得是不要一上来就跑最高速。我刚开始用的时候直接把 SPI 时钟设到 42MHz结果读写都不稳定后来降到 21MHz 才正常。再后来换了块板子走线短了42MHz 也能跑。所以 SPI 时钟跟硬件设计关系很大没有统一的标准只能实测。建议先用低速调通再逐步提高找到稳定工作的最高频率。第二个心得是 CS 信号一定要干净。CS 是控制信号一旦有毛刺或者误触发整个通信就乱了。我有个项目因为 CS 走线旁边有 PWM 线偶尔出现误写入后来在 CS 上加了 10k 上拉电阻并且把走线远离 PWM 线问题就解决了。另外 CS 的拉低和拉高之间要有足够的延时太快了从设备可能反应不过来一般加个几微秒的延时比较保险。第三个心得是地址空间要规划好。512KB 看着不大但如果乱用很快就满了。建议把地址空间分成几个区域参数区、日志区、备份区、保留区。参数区存配置参数日志区存运行记录备份区存关键数据的副本保留区留着以后扩展。每个区域的起始地址和大小在头文件里定义好用宏或者枚举管理不要直接在代码里写死地址否则以后改起来很麻烦。第四个心得是写操作尽量批量做。MRAM 虽然写入寿命长但每次写操作都有开销频繁的单字节写会浪费 CPU 时间。如果能把多个参数攒在一起写效率会高很多。比如每 100ms 采集一次数据不要每次采集完就写可以攒够 1 秒的数据再一次性写入这样写次数减少到十分之一CPU 占用也降下来了。5. 性能测试与方案对比5.1 读写速度实测数据我在 STM32F407VGT6 加 MR25H40CDF 的平台上做了一组读写测试SPI 时钟分别设为 1.3MHz、5.25MHz、10.5MHz、21MHz测试内容是连续读写 1KB 数据取多次平均值。结果如下SPI 时钟写入 1KB 耗时读取 1KB 耗时写入速度读取速度1.3MHz8.2ms7.9ms125KB/s130KB/s5.25MHz2.1ms2.0ms488KB/s512KB/s10.5MHz1.1ms1.0ms931KB/s1024KB/s21MHz0.6ms0.5ms1707KB/s2048KB/s从数据可以看出读写速度基本和 SPI 时钟成正比21MHz 下写入速度能到 1.7MB/s读取能到 2MB/s。这个速度对于大部分工业应用来说完全够用了。注意这里的耗时包含了 CS 切换和指令开销如果数据量更大平均速度还会更高因为指令开销被摊薄了。5.2 与 EEPROM、NOR Flash、FRAM 的对比为了更直观地说明 MR25H40CDF 的定位我把它和几种常见的存储方案做了个对比特性MR25H40CDF (MRAM)EEPROM (如 AT24C512)NOR Flash (如 W25Q64)FRAM (如 FM25V05)容量512KB64KB8MB64KB接口SPII2CSPISPI写入前擦除不需要不需要需要不需要擦写寿命10^14 次10^6 次10^5 次10^14 次写入速度快慢慢快掉电保持是是是是价格高低低高适用场景频繁写入、掉电保护参数存储、低频写入大容量数据存储高频写入、小容量从表里能看出来MRAM 的核心优势就是擦写寿命长、写入速度快、不需要擦除缺点是容量小、价格高。所以选型的时候如果写入频率不高比如一天写几次用 EEPROM 就够了如果需要存大量数据比如音频或者图像用 NOR Flash 更合适如果写入非常频繁但数据量不大FRAM 和 MRAM 都是好选择FRAM 容量更小但价格稍低MRAM 容量更大但价格更高。5.3 不同场景下的选型建议工业数据采集场景比如每秒钟采集几次数据需要保存最近几天的记录这种场景写入频率中等数据量中等MR25H40CDF 很合适。512KB 容量能存不少数据擦写寿命完全不用担心SPI 接口速度也够快。电力终端或者电表场景需要频繁保存电量数据掉电不能丢这种场景写入频率高但每次写入的数据量很小MRAM 和 FRAM 都合适。如果容量需求超过 64KB选 MRAM如果容量需求很小FRAM 更便宜。汽车电子场景比如记录行车数据或者故障码需要宽温工作MR25H40CDF 有汽车级型号能到 125°C比 EEPROM 和 FRAM 的温宽更好。不过汽车级价格更贵看具体需求。消费电子场景如果只是存个配置参数写入不频繁用 EEPROM 或者 Flash 模拟 EEPROM 就够了没必要上 MRAM成本太高。5.4 长期运行的可靠性观察我在那个配电终端项目上连续跑了半年多每天写入大概 86 万次每 100ms 一次半年下来累计写入超过 1.5 亿次。MR25H40CDF 标称寿命 10^14 次1.5 亿次连零头都不到所以完全不担心写坏。实际运行中也没有出现数据丢失或者写入失败的情况稳定性很好。温度方面那个终端装在户外机柜里夏天内部温度能到 60°C 以上冬天能到 -20°CMR25H40CDF 在工业级温度范围内工作正常没有出现数据保持问题。不过要注意MRAM 的数据保持时间在高温下会缩短虽然标称 20 年但那是常温下的数据高温下可能只有几年。如果应用场景温度很高建议定期刷新数据或者选汽车级型号。电磁兼容方面那个机柜里有接触器、继电器干扰不小但 MRAM 通信没有出现过问题。一方面是 SPI 走线短另一方面是加了 CRC 校验即使偶尔出错也能检测出来并重试。如果干扰特别严重可以考虑在 SPI 线上加共模电感或者 TVS 管进一步提高抗干扰能力。6. 进阶应用与扩展思路6.1 用 MRAM 做文件系统或者日志系统512KB 的 MRAM 虽然不大但如果用来做一个小型文件系统或者日志系统其实很够用。比如可以做一个简单的环形日志每条日志固定长度写满一圈就覆盖最旧的数据。MRAM 不需要擦除所以覆盖写就是直接写没有 Flash 那种先擦后写的麻烦。日志的读写指针可以存在 MRAM 的固定地址里掉电后指针不丢重启后接着写。如果要存变长数据可以做一个简单的索引结构每条数据前面加一个长度字段和校验字段读取的时候先读长度再读数据。索引可以放在 MRAM 的开头记录每条数据的起始地址和长度。这种结构比文件系统简单但足够应付大部分嵌入式场景。6.2 结合 RTC 做时间戳数据记录工业场景经常需要带时间戳的数据记录比如记录故障发生的时间。STM32F407 有内置 RTC配合 MRAM 可以做一个带时间戳的日志系统。每条记录包含时间戳、数据类型、数据内容和校验。RTC 的时间可以定期从外部同步比如通过串口或者网络。MRAM 掉电不丢所以时间戳数据也不会丢。这里有个细节RTC 的电池如果没电了时间会乱所以记录时间戳的时候最好同时记录一个递增的序列号这样即使时间乱了也能通过序列号判断数据的先后顺序。序列号存在 MRAM 里每次写入递增掉电后从 MRAM 里读出来接着用。6.3 多设备共享 SPI 总线的注意事项如果 SPI 总线上挂了多个从设备比如 MRAM 加 Flash 加显示屏需要注意片选管理。每个设备有独立的 CS 引脚同一时刻只能有一个 CS 有效。切换设备的时候要先拉高当前设备的 CS再拉低目标设备的 CS中间加一点延时确保总线稳定。另外不同设备的 SPI 模式可能不同比如 MRAM 是模式 0有些 Flash 是模式 3切换设备的时候要重新配置 SPI 的 CPOL 和 CPHA这个很容易忘。还有一个问题是总线冲突。如果两个设备的 CS 同时有效它们的 MISO 输出会打架可能损坏芯片。所以 CS 的初始状态一定要设成高电平上电初始化的时候先把所有 CS 拉高再逐个初始化。如果某个设备的 CS 引脚在复位期间是浮空的最好加一个上拉电阻保证复位期间 CS 是高电平。6.4 未来升级方向与替代方案如果以后容量不够用了可以考虑更大容量的 MRAM比如 16Mbit 或者 32Mbit 的型号接口还是 SPI驱动改动不大。如果成本压力大可以考虑用 MRAM 加 Flash 的组合方案频繁写入的关键数据存 MRAM大量只读数据存 Flash这样兼顾了性能和成本。另一个方向是用带 ECC 的 MRAM有些型号内置了纠错码能进一步提高数据可靠性。不过带 ECC 的型号价格更高容量可能也更小看具体需求。如果对可靠性要求极高比如航天或者医疗场景可以考虑这种。软件方面可以移植一个轻量级的文件系统比如 LittleFS 或者 SPIFFS它们对擦写寿命和掉电保护都有考虑配合 MRAM 使用效果很好。不过这些文件系统原本是为 Flash 设计的用在 MRAM 上需要改一下擦除相关的接口因为 MRAM 不需要擦除直接返回成功就行。我在实际项目里还试过用 MRAM 做参数的双备份存储主备两个区域交替写入每次写入递增版本号读取的时候选版本号最新的。这种方式简单可靠不需要文件系统代码量很小适合资源紧张的嵌入式场景。踩过的坑主要是版本号回绕的问题用 32 位版本号的话即使每秒写一次也要 100 多年才回绕所以基本不用担心。但如果用 8 位或者 16 位版本号就要处理回绕比较麻烦建议直接用 32 位。