STM32F107VC驱动MR25H40CDF MRAM:工业数据存储实战 1. 项目缘起与方案选型1.1 为什么要在工业场景里折腾 MRAM工业现场的数据存储有个很尴尬的处境用 EEPROM 吧写入速度慢得让人着急擦写次数也就百万级高频记录的场景下没几年就写废了用 SRAM 加后备电池吧电池在高温高湿环境下寿命堪忧换电池还得停机维护成本高用 NOR Flash 吧写之前得擦除整个扇区掉电瞬间如果正好在擦写数据直接丢而且擦写寿命也就十万次量级。我做过一个电力监测终端要求每 100ms 记录一次关键参数一天就是 86 万次写入用 EEPROM 方案算下来不到三个月就得换芯片这显然不现实。MR25H40CDF 这颗芯片正好卡在这个痛点上。它是 Everspin 家的 4Mbit 串行 MRAMMRAM 的存储机理是磁性隧道结靠电子自旋方向存储数据不需要电荷保持所以写入速度是纳秒级擦写寿命号称无限次官方给的是 10^14 次以上掉电数据不丢还不像 Flash 那样需要先擦后写。这几个特性凑在一起对工业数据记录来说几乎是量身定做的。4Mbit 换算过来是 512KB存一些关键参数、故障录波、运行日志绰绰有余。STM32F107VC 是 ST 家带以太网 MAC 的互联型 MCU72MHz 主频256KB Flash64KB SRAM外设资源丰富SPI 接口有三个拿来驱动 MR25H40CDF 很顺手。选它还有个现实原因很多工业板卡上已经在用这颗片子做通信主控加一颗 MRAM 做数据存储不用重新设计主控架构改动量最小。1.2 SPI 接口的取舍与硬件连接思路MR25H40CDF 支持 SPI 模式 0 和模式 3最高时钟频率 40MHz。STM32F107VC 的 SPI1 挂在 APB2 上最高 36MHzSPI2 和 SPI3 挂在 APB1 上最高 18MHz。如果追求读写速度优先用 SPI1。但实际布线时还得看 PCB 布局如果 MRAM 离 SPI1 的引脚比较远走线绕来绕去反而影响信号完整性这时候用 SPI2 也未尝不可18MHz 对大多数工业记录场景已经够用了。硬件连接上除了标准的 SCK、MISO、MOSI、CS 四根线MR25H40CDF 还有几个引脚需要留意。WP 引脚是写保护低电平有效如果不需要硬件写保护直接上拉到 VCC。HOLD 引脚用于暂停通信不用的话也上拉。这两个引脚千万别悬空悬空状态下容易受干扰误触发我见过一个案例就是 HOLD 脚悬空导致偶发通信失败查了两天才定位到。电源引脚旁边要放 0.1uF 的去耦电容尽量靠近芯片放置MRAM 在写入瞬间电流会有波动去耦不好会导致写入错误。CS 片选信号建议用硬件片选也就是直接接 STM32 的 SPI_NSS 引脚由硬件自动控制。软件片选虽然灵活但在高速通信时 GPIO 翻转的时序不好保证容易出现片选建立时间不够的问题。如果非要用软件片选记得在拉低 CS 之后加几个 NOP 延时再发时钟。2. MR25H40CDF 的核心机制与操作要点2.1 存储结构与地址空间划分MR25H40CDF 的 512KB 空间按字节寻址地址从 0x00000 到 0x7FFFF需要 19 位地址线。SPI 协议里地址是按字节传输的所以发地址要发 3 个字节高字节在前。这里有个容易踩的坑很多人习惯性地把地址当成 16 位处理结果访问超过 64KB 的区域时地址回绕写进去的数据跑到低地址去了。我第一次用的时候就犯过这个错调试了半天才发现是地址位数没搞对。从应用角度我一般把这 512KB 分成几个区域来管理。前 64KB 做参数区存设备配置、校准系数这些不常变但很重要的数据中间 256KB 做日志区环形缓冲的方式记录运行数据最后 192KB 做故障录波区检测到异常时把关键波形存进去。这样划分的好处是各区域互不干扰日志写满了覆盖旧数据也不会影响到参数区。2.2 读写指令与时序细节MR25H40CDF 的指令集不复杂常用的就几条READ0x03读数据WRITE0x02写数据WREN0x06写使能RDSR0x05读状态寄存器。写操作之前必须先发 WREN 指令这是硬性要求忘了这一步写不进去任何数据。读操作不需要 WREN直接发 READ 指令加地址就能读。时序上有个关键参数叫 tWRL是写使能到写操作之间的最小延迟MR25H40CDF 这个值是 0也就是说 WREN 之后可以立刻发 WRITE不用等。但读状态寄存器判断写完成的时候要注意状态寄存器的 WEL 位在写操作开始后会自动清零而 WIP 位在写周期内为 1。MRAM 的写周期极短通常几十纳秒就完成了所以实际编程时几乎不用轮询 WIP 位发完写指令直接发下一条就行。这一点和 Flash 完全不同Flash 写一页要等几毫秒必须轮询或者用中断。SPI 模式选择上MR25H40CDF 支持模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。STM32 的 SPI 配置成对应模式即可。我一般用模式 0因为大多数 SPI 从设备都支持模式 0统一配置省事。时钟极性相位搞错的话读出来的数据全是 0xFF 或者 0x00这是新手最常见的翻车点。2.3 写保护机制的正确使用MR25H40CDF 有两级写保护硬件 WP 引脚和软件状态寄存器。硬件 WP 拉低时状态寄存器的块保护位生效被保护的地址区域拒绝写入。软件保护通过写状态寄存器来设置可以保护全部、上半部分、下半部分或者四分之一区域。工业应用里我强烈建议启用写保护。参数区设成硬件保护需要修改时再临时解除这样即使程序跑飞了也不会误改参数。日志区不保护随便写。故障录波区在录波完成后设成保护状态防止后续误操作覆盖掉珍贵的故障数据。这个策略在实际项目中救过我好几次有一次程序指针异常跳到了写参数区的代码因为硬件 WP 拉着数据安然无恙。3. STM32F107VC 端的驱动实现3.1 SPI 外设初始化与 GPIO 配置STM32F107VC 的 SPI1 引脚是 PA5SCK、PA6MISO、PA7MOSINSS 可以用 PA4。GPIO 配置成复用推挽输出速度选 50MHz。这里注意 MISO 要配置成浮空输入或者上拉输入不能配成推挽输出否则会跟从设备的输出打架。我见过有人把 MISO 也配成推挽结果读数据全是错的因为两个输出在互相驱动。SPI 初始化结构体里几个关键参数波特率预分频器选 2 分频的话72MHz 的 APB2 时钟分频后是 36MHzMR25H40CDF 支持 40MHz所以 36MHz 没问题。但实际跑的时候如果 PCB 走线比较长或者有干扰建议降到 18MHz 甚至 9MHz稳定性优先。数据大小选 8 位MSB 先行模式 0软件 NSS 管理。CRC 不用开MRAM 本身可靠性很高加了 CRC 反而增加开销。初始化代码大概长这样void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_2; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }3.2 底层读写函数的封装SPI 收发一个字节的函数是基础所有上层操作都靠它uint8_t SPI1_ReadWriteByte(uint8_t txData) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, txData); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); return SPI_I2S_ReceiveData(SPI1); }这个函数看着简单但有个细节发送和接收是同时进行的发一个字节的同时也收一个字节。读 MRAM 的时候先发读指令和地址这些字节的返回值丢弃然后再发 dummy 字节来产生时钟同时接收数据。写的时候发完指令和地址后直接发数据字节返回值同样丢弃。基于这个字节函数封装 MRAM 的读和写void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t i; MRAM_CS_LOW(); SPI1_ReadWriteByte(0x06); // WREN MRAM_CS_HIGH(); MRAM_CS_LOW(); SPI1_ReadWriteByte(0x02); // WRITE SPI1_ReadWriteByte((addr 16) 0xFF); SPI1_ReadWriteByte((addr 8) 0xFF); SPI1_ReadWriteByte(addr 0xFF); for (i 0; i len; i) { SPI1_ReadWriteByte(buf[i]); } MRAM_CS_HIGH(); } void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t i; MRAM_CS_LOW(); SPI1_ReadWriteByte(0x03); // READ SPI1_ReadWriteByte((addr 16) 0xFF); SPI1_ReadWriteByte((addr 8) 0xFF); SPI1_ReadWriteByte(addr 0xFF); for (i 0; i len; i) { buf[i] SPI1_ReadWriteByte(0xFF); } MRAM_CS_HIGH(); }注意 WREN 和 WRITE 之间 CS 要拉高再拉低因为 WREN 是一个独立的命令周期。有些 SPI 设备允许 WREN 和 WRITE 在同一个 CS 周期内连续发但 MR25H40CDF 的数据手册明确要求 WREN 之后 CS 要有一个上升沿来锁存写使能状态。这个细节不注意的话写操作会静默失败读回来还是旧数据。3.3 数据组织与掉电保护策略工业数据记录最怕的就是掉电丢数据。MRAM 本身掉电不丢但 MCU 在掉电瞬间可能正在写一个数据结构写到一半断电就会留下不完整的数据。我的做法是给每个记录块加一个状态标志和 CRC 校验。写数据之前先把状态标志置为“写入中”写完数据再改成“有效”最后更新 CRC。读的时候先检查状态标志和 CRC只有两者都通过才认为数据有效。具体实现上每个记录块设计成 64 字节前 4 字节是块头包含状态标志2 字节和 CRC162 字节后面 60 字节是实际数据。写流程是写状态标志为 0xAAAA写数据计算 CRC写 CRC写状态标志为 0x5555。读流程是读状态标志如果是 0x5555 就继续读数据和 CRC校验通过则返回数据否则返回错误。这个方案有个前提MRAM 的写入是原子性的单个字节的写入不会被打断。MR25H40CDF 确实保证单字节写入的原子性所以状态标志的更新是安全的。但如果你的应用需要跨多个字节的原子操作那就得用双缓冲或者日志结构来保证。4. 实际项目中的调试与优化4.1 常见通信故障排查SPI 通信出问题排查思路要系统化。我一般按这个顺序来先看电源和地用示波器量 VCC 是不是干净的 3.3V纹波大不大再看时钟SCK 有没有波形频率对不对然后看 CS片选信号有没有正常拉低拉高最后看数据线MOSI 和 MISO 上有没有数据翻转。有个很隐蔽的问题STM32 的 SPI 在配置成主机模式后如果 NSS 配置成硬件模式但引脚又被拉低SPI 会进入从机模式时钟就停了。我遇到过一块板子NSS 引脚被外部电路拉低导致 SPI 完全不工作查了半天才发现是硬件设计的问题。所以如果 SPI 没反应先确认 NSS 引脚的状态。另一个常见问题是读出来的数据整体偏移一个字节。这通常是时序相位搞错了把 CPHA 从 1Edge 改成 2Edge 或者反过来试试。还有一种可能是 CS 拉低太早或太晚导致第一个时钟沿采到了错误的数据。用逻辑分析仪抓一下波形对照数据手册的时序图一眼就能看出来。4.2 提升读写效率的实用技巧MR25H40CDF 支持连续读写地址会自动递增所以读写大块数据时不用反复发指令和地址。我实测过用 36MHz 时钟连续读 512 字节耗时不到 150 微秒比一字节一字节地读快了一个数量级。写操作也是同理连续写 256 字节大概 200 微秒左右。如果数据量特别大可以考虑用 DMA。STM32F107VC 的 SPI1 有 DMA 通道配置好之后 CPU 只需要发起传输剩下的交给 DMA 搬运CPU 可以去处理其他任务。不过 MRAM 的读写速度已经很快了DMA 带来的收益在大多数场景下并不明显除非你的数据量真的很大或者 CPU 负载很重。还有个技巧是合并写操作。如果短时间内要写多个不连续的小数据块可以在 RAM 里先攒着攒够一个较大的块再一次性写入 MRAM。这样减少 CS 翻转次数和指令开销整体效率会高不少。但要注意掉电风险攒在 RAM 里的数据掉电就没了所以只适合那些可以容忍少量丢失的非关键数据。4.3 与 Flash 方案的对比实测我在同一个板子上同时焊了 W25Q64 Flash 和 MR25H40CDF做了个对比测试。写 256 字节数据Flash 需要先擦除 4KB 扇区约 50ms再写入约 1ms总共 51ms 左右MRAM 直接写耗时约 200 微秒。差距是 250 倍。擦写寿命方面Flash 标称 10 万次按每天 1 万次写入算10 天就报废MRAM 标称 10^14 次按同样频率算能用几万年。功耗方面MRAM 写入电流约 10mAFlash 擦除时约 15mA差别不大。但 Flash 擦除时间长整体能耗反而更高。待机功耗两者都在微安级MRAM 略低一些。成本上 MRAM 确实贵不少4Mbit 的 MR25H40CDF 价格大概是同容量 Flash 的 5 到 8 倍。所以选型逻辑很清晰高频写入、高可靠性要求的场景用 MRAM低频写入、成本敏感的场景用 Flash。对比项MR25H40CDFW25Q64 Flash写入前擦除不需要需要4KB 扇区约 50ms写入 256 字节耗时约 200us约 51ms含擦除擦写寿命10^14 次以上约 10 万次接口速度最高 40MHz最高 104MHz单位成本高低掉电数据保持是是5. 工业场景下的可靠性设计5.1 温度与电磁兼容考量工业现场的温度范围通常是 -40 到 85 度MR25H40CDF 的工业级版本支持这个范围。但要注意MRAM 的写入电流在低温下会略微增大高温下磁性隧道结的保持力会下降。虽然官方标称数据保持时间在 85 度下超过 20 年但如果你的设备长期工作在 85 度以上建议做加速老化测试验证一下。电磁兼容方面SPI 信号线尽量短最好走内层并包地。如果 MRAM 和 MCU 不在同一块板子上用排线连接时要在每根信号线上串 22 到 33 欧姆的电阻抑制反射和振铃。CS 信号尤其重要因为它控制着通信的起止一旦受干扰误触发整个通信就乱了。我一般会在 CS 上并一个 100pF 的电容到地滤掉高频毛刺。5.2 数据完整性校验方案除了前面提到的 CRC 校验我还建议在关键数据结构里加一个魔数Magic Number和版本号。魔数用来快速判断这个区域是否被初始化过版本号用来兼容不同固件版本的数据格式。比如参数区的头部设计成魔数 0x4D52414D“MRAM”的 ASCII版本号 0x0100后面跟参数数据。固件升级后如果发现版本号不匹配可以走数据迁移流程而不是直接丢弃旧数据。对于故障录波这种关键数据我还会做双备份。同样的数据写到两个不同的地址区域读取时两个都读出来对比一致才采用不一致就报错。MRAM 的容量足够大双备份带来的空间开销完全可以接受。这个策略在强电磁干扰环境下特别有用单份数据被干扰的概率虽然低但双份同时被干扰的概率几乎为零。5.3 长期运行的数据管理设备运行几年后日志区会积累大量数据。如果只是环形覆盖旧数据会被新数据挤掉。我的做法是给每条日志加时间戳然后定期把重要日志转存到外部存储或者上传到服务器。MRAM 里的日志区只保留最近一段时间的记录这样既保证了本地查询的快速响应又不会丢失历史数据。日志的索引结构也很重要。如果每次查询都从头遍历数据量大时效率很低。我在日志区头部维护一个索引表记录每条日志的起始地址和时间戳查询时先查索引表定位再读具体数据。索引表本身也放在 MRAM 里掉电不丢。索引表的更新频率比日志低可以攒一批再更新减少写入次数。6. 踩坑记录与经验总结6.1 那些年我踩过的 SPI 坑第一个坑是 CS 时序。STM32 的 SPI 在发送完最后一个字节后TXE 标志会置位但此时数据可能还在移位寄存器里没发完。如果这时候立刻拉高 CS最后一个字节就会丢失。正确的做法是等 BSY 标志清零再拉高 CS。我当初没注意这个写进去的数据总是少最后一个字节查了好久才定位到。第二个坑是时钟相位。MR25H40CDF 支持模式 0 和模式 3但 STM32 的 SPI 配置里 CPOL 和 CPHA 的组合有四种。我一开始配成了模式 1CPOL0CPHA1读出来全是 0xFF。后来对照数据手册的时序图才发现模式 1 的采样沿和 MRAM 的输出沿对不上。改成模式 0 就正常了。第三个坑是地址对齐。MRAM 支持任意地址读写不像 Flash 有页对齐限制。但如果你用 DMA 传输DMA 的源地址和目的地址可能需要对齐。我遇到过 DMA 传输奇数长度数据时最后一个字节丢失的问题后来改成偶数长度传输就没事了。所以用 DMA 的时候数据长度尽量凑成偶数。6.2 写保护引发的“灵异事件”有一次调试发现参数写不进去读回来还是旧值。查了 SPI 时序、地址、数据都没问题。最后发现是 WP 引脚被硬件设计拉低了而状态寄存器里的块保护位默认是保护全部区域的。解除保护的方法是按特定时序写状态寄存器但那个时序比较绕我试了好几次才成功。后来我在初始化代码里加了一段解除保护的逻辑确保上电后参数区是可写的。还有一次更诡异日志区写了几条之后突然写不进去了。查了半天发现是日志区的地址范围落在了状态寄存器保护的区域内。因为状态寄存器的保护区域是按地址划分的我当初划分区域的时候没注意边界日志区的起始地址正好在保护范围内。调整了区域划分之后问题解决。所以划分存储区域时一定要对照状态寄存器的保护粒度来规划别让功能区域跨在保护边界上。6.3 从实际项目中学到的经验第一个经验是初始化要彻底。上电后先读一遍 MRAM 的状态寄存器确认写使能状态和保护状态然后根据应用需求重新配置。不要假设 MRAM 上电后的状态是确定的虽然数据手册说上电后 WEL 是 0但实际测试中发现有些批次的芯片上电后 WEL 是 1可能是上电时序导致的。所以初始化时显式地发一次 WREN 或者 WRDI 来设定状态比较稳妥。第二个经验是读写函数要加超时。SPI 通信如果从设备没响应while 循环会死等导致整个系统卡死。我在每个 while 循环里都加了超时计数超过一定次数就返回错误。这样即使 MRAM 坏了或者没焊好系统也不会挂掉可以走降级逻辑比如用默认参数继续运行。第三个经验是保留一块“安全区”。我在 MRAM 的最后 4KB 划了一块安全区存的是设备的唯一 ID、生产日期、硬件版本这些出厂信息。这块区域在固件里设成只读任何写操作都会被拒绝。这样即使固件有 bug 或者被恶意篡改出厂信息也不会丢。这个做法在售后维护时特别有用能快速确认设备的原始配置。6.4 常见问题速查表现象可能原因排查方法解决方案读数据全 0xFFSPI 模式不对检查 CPOL/CPHA 配置改成模式 0 或模式 3读数据全 0x00MISO 未配置为输入检查 GPIO 模式配置为浮空或上拉输入写数据不生效未发 WREN 或 CS 时序不对用逻辑分析仪抓波形确保 WREN 后 CS 有上升沿最后一个字节丢失CS 拉高太早检查 BSY 标志等 BSY 清零再拉高 CS地址回绕地址超过 19 位检查地址计算确保地址在 0x00000-0x7FFFF偶发通信失败电源纹波大或 HOLD 悬空示波器量电源和 HOLD 脚加去耦电容HOLD 上拉写入被拒绝写保护生效读状态寄存器解除块保护或拉高 WP这个表是我这几年调试 MRAM 攒下来的基本上覆盖了 90% 以上的问题。遇到新问题的时候先对照这个表排查一遍大部分情况都能快速定位。如果表里没有那就得用示波器或者逻辑分析仪抓波形对照数据手册的时序图逐项检查。7. 扩展思路与后续优化方向7.1 文件系统与数据管理如果应用需要更复杂的数据管理比如按文件名存取、目录结构、权限控制可以在 MRAM 上跑一个小型文件系统。FatFS 是个不错的选择移植工作量不大STM32 上有很多现成的例程。但 FatFS 的写入操作比较频繁会频繁更新 FAT 表和目录项对 MRAM 来说虽然寿命不是问题但会增加写入开销。如果只是存一些定长记录自己写一个简单的块管理就够了没必要上文件系统。另一种思路是用日志结构文件系统Log-Structured File System的思想所有写入都是追加不覆盖旧数据。这样写入效率最高而且天然支持掉电恢复。缺点是空间利用率低需要定期做垃圾回收。对于 MRAM 这种大容量存储来说空间换效率是划算的。7.2 多设备共享 SPI 总线工业板卡上往往有多个 SPI 从设备比如 MRAM、Flash、ADC、显示屏等。它们共享 SCK、MISO、MOSI各自有独立的 CS。这种情况下要注意两点一是每个设备的 SPI 模式可能不同切换设备时要重新配置 SPI 的 CPOL 和 CPHA二是 CS 信号要严格互斥任何时候只能有一个 CS 有效。如果设备数量多GPIO 不够用可以用译码器来扩展 CS。比如 74HC138 可以把 3 根 GPIO 译成 8 个 CS 信号。但译码器的输出有竞争冒险的风险切换 CS 时可能会产生毛刺。我一般会在译码器输出和 CS 之间加一个 RC 滤波或者用带使能端的译码器切换时先禁用输出再改地址。7.3 面向未来的升级路径MR25H40CDF 是 4Mbit 的如果以后数据量增大需要更大容量Everspin 有 16Mbit 的 MRAM 型号引脚和指令集基本兼容升级时主要改一下地址位数和容量定义就行。STM32F107VC 的 SPI 最高 36MHz如果换用支持更高 SPI 时钟的 MCU比如 STM32H7 系列SPI 可以跑到 100MHz 以上MRAM 的读写速度还能再提升。另一个方向是把 MRAM 和实时操作系统结合起来。在 RTOS 里可以把 MRAM 的读写封装成独立的线程通过消息队列接收其他线程的读写请求。这样多个任务可以并发访问 MRAM而不会互相干扰。但要注意 SPI 总线是共享资源需要加互斥锁来保护。我在 FreeRTOS 上做过这个方案用二值信号量做互斥运行很稳定。7.4 测试与验证的自动化产品量产前需要做批量测试验证每块板子上的 MRAM 都能正常工作。手动测试效率太低我写了一个自动化测试脚本通过串口发送命令给板子板子执行 MRAM 的全片读写测试然后把结果返回。测试内容包括全片写 0x55 再读回校验全片写 0xAA 再读回校验随机地址读写校验边界地址读写校验。整个测试跑下来大概 10 秒左右比人工测试快了几十倍。测试脚本用 Python 写的通过 pyserial 库和板子通信。板子端的测试固件用 C 写编译后烧录到 STM32 里。测试时板子进入测试模式等待串口命令收到命令后执行对应的测试项返回 PASS 或 FAIL。这个方案在产线上跑了一年多没出过误判。最后分享一个我在实际项目中总结的小技巧MRAM 的写入速度极快但 SPI 总线的传输速度是瓶颈。如果应用允许可以把多个小数据块攒在一起用一次连续写操作完成。我做过测试把 10 个 16 字节的数据块合并成一次 160 字节的连续写总耗时从 10 次独立写的 2 毫秒降到了 0.3 毫秒效率提升非常明显。这个优化在数据记录频率高的场景下效果尤其显著。