STM32F215RE 与 MR25H40CDF MRAM 的 SPI 驱动实战

发布时间:2026/10/5 20:42:16
STM32F215RE 与 MR25H40CDF MRAM 的 SPI 驱动实战 1. 为什么偏偏选 MR25H40CDF 这颗 MRAM1.1 从一次掉电丢数据的现场说起前两年做一个工业数据采集终端主控用的是 STM32F215RE外挂一颗常见的 SPI NOR Flash 存配置和运行日志。设备装在配电柜里现场偶尔会瞬断结果每次断电重启后最近几分钟的计量数据就没了——因为 NOR Flash 写之前要先擦除一个扇区擦除过程中掉电整个扇区就废了。后来换成带超级电容的方案成本上去了板子也变大了客户还不满意。再后来接触到 MR25H40CDF 这颗磁阻随机存储器MRAM才算把这个问题从根上解决。它最大的特点就是写入不需要擦除、字节级可写、掉电不丢、写入次数近乎无限。这几点对工业现场那种“随时可能断电、又要频繁记录”的场景来说几乎是量身定做的。所以这篇东西我就围绕“STM32F215RE MR25H40CDF 通过 SPI 存取数据”这条主线把选型逻辑、硬件连接、SPI 时序、驱动代码、踩坑经验完整讲一遍。适合正在做工业采集、嵌入式数据记录、需要频繁小数据量写入的同行参考也适合刚接触 SPI 外设、想找一个靠谱非易失存储方案的朋友。1.2 MR25H40CDF 到底是个什么东西先把这颗芯片的底细说清楚。MR25H40CDF 是 Everspin 家的 4Mbit512K × 8串行 MRAM接口是标准 SPI工作电压 2.7V ~ 3.6V最高 SPI 时钟可以跑到 40MHz。它的存储单元是磁性隧道结MTJ靠磁化方向存数据不是靠电荷。这个物理原理决定了它和 Flash、EEPROM 的本质区别不需要擦除Flash 写之前必须擦MRAM 直接覆盖写像 SRAM 一样。字节级写入可以只写一个字节不用整页操作。写入寿命几乎无限官方标称 10^14 次以上实际用起来基本不用考虑寿命。写入速度快没有擦除等待单字节写入时间在纳秒到微秒级。掉电即存写进去就稳定了不需要等待内部编程完成。对比一下常见的几种非易失存储差距一目了然特性MR25H40CDF (MRAM)SPI NOR FlashSPI EEPROM写入前擦除不需要必须整扇区擦不需要最小写入单位1 字节1 页通常 256B1 字节写入寿命10^14 次约 10^5 次约 10^6 次写入速度极快无擦除等待慢擦除几十 ms较慢约 5ms/字节容量4Mbit常见 8~128Mbit常见 1K~512Kbit成本偏高低中容量上 MR25H40CDF 只有 512KB比不了大容量 Flash但工业场景里存配置、存关键日志、存掉电前的现场快照这个容量完全够用。它的定位不是替代大容量存储而是替代“频繁写、怕掉电、怕擦坏”的那一小块关键区域。1.3 为什么主控选 STM32F215RESTM32F215RE 是 ST 家的 Cortex-M3主频 120MHz带 512KB Flash、128KB SRAM外设资源很全SPI 有 3 个SPI1/2/3都支持硬件 NSS 和 DMA。选它配 MR25H40CDF主要看中几点第一SPI 速率够。F215 的 SPI 在 120MHz 主频下分频后轻松跑到 30MHz 以上能发挥 MRAM 的高速优势。第二DMA 支持完善做大数据块搬运时 CPU 占用低。第三工业温度范围版本-40~85℃ 或 105℃好买和 MRAM 的工业级温度匹配。第四生态成熟HAL 库、标准库资料多调试方便。提示MR25H40CDF 有工业级和汽车级版本选型时注意看后缀工业现场建议选 -40~85℃ 以上的型号别图便宜用商业级。2. 硬件连接与 SPI 模式的关键细节2.1 引脚连接与片选处理MR25H40CDF 是 8 脚封装SPI 接口引脚定义很标准VDD / VSS电源和地就近放 0.1uF 去耦电容。SCKSPI 时钟接 STM32 的 SPIx_SCK。SIMOSI主机输出接 SPIx_MOSI。SOMISO主机输入接 SPIx_MISO。CS#片选低有效。WP# / HOLD#写保护和保持引脚不用的话上拉到 VDD。片选这块我要多说一句。STM32 的 SPI 支持硬件 NSS 和软件片选两种方式。工业项目里我强烈建议用软件片选也就是随便找一个 GPIO 手动拉低拉高。原因很简单硬件 NSS 在多主或者时序要求严格的场景下容易出幺蛾子而且一旦配置不当NSS 会在字节间隙自动抖动MRAM 可能把中间的抖动误判成命令边界。软件片选虽然多写两行代码但时序完全可控调试时也方便用示波器抓。// 软件片选宏定义假设 CS 接在 PA4 #define MRAM_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define MRAM_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)2.2 SPI 模式选择Mode 0 还是 Mode 3MR25H40CDF 支持 SPI Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1。这两种模式的区别在于时钟空闲电平和采样边沿Mode 0时钟空闲低电平第一个边沿上升沿采样。Mode 3时钟空闲高电平第二个边沿上升沿采样。实际用哪个我一般选Mode 0因为它是绝大多数 SPI 器件的默认模式调试时不容易和其他外设冲突。配置的时候在 STM32CubeMX 里把 CPOL 设成 Low、CPHA 设成 1 Edge 就行。这里有个容易踩的坑STM32 的 CPHA 配置和很多资料里的描述是反的。HAL 库里CLKPolarity对应 CPOLCLKPhase对应 CPHA但SPI_PHASE_1EDGE表示第一个边沿采样对应 CPHA0。很多人照着“CPHA1”去配SPI_PHASE_2EDGE结果读出来全是 0xFF 或者 0x00查半天查不出来。记住Mode 0 CPOL Low 1 Edge。2.3 时钟速率与信号完整性MRAM 支持 40MHz但实际能跑多快取决于你的 PCB 走线和 STM32 的分频。STM32F215 的 SPI1 挂在 APB2 上假设 APB2 是 120MHz分频系数选 4 就是 30MHz选 2 就是 60MHz超了 MRAM 上限不行。所以 30MHz 是个稳妥的高速选择。但我要提醒一句别一上来就冲 30MHz。先用低速比如 1MHz 或 2MHz把驱动调通确认读写正确再逐步往上提。走线长、没有阻抗控制、旁边有开关电源的板子30MHz 下 MISO 上很容易出现振铃和过冲读出来的数据高位会随机翻转。我遇到过一块板子2MHz 下一切正常提到 20MHz 就偶尔读错最后在 SCK 和 MISO 上各串了一个 22Ω 电阻才稳住。注意SPI 走线尽量等长、远离高频干扰源MISO 是输入线最容易受干扰必要时加串联电阻或小电容滤波。3. 驱动实现从命令码到读写函数3.1 MR25H40CDF 的命令集这颗芯片的命令集很简洁常用的就几条命令编码作用WREN0x06写使能写之前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据注意虽然 MRAM 不需要擦除但写操作前仍然要发 WREN0x06这是芯片的写保护机制不是 Flash 那种擦除逻辑。发完 WREN 后状态寄存器的 WEL 位会置 1写完自动清零。如果你忘了发 WREN写命令会被忽略数据写不进去而且不会报错很容易误以为驱动有问题。3.2 底层字节收发函数STM32 HAL 库提供了HAL_SPI_Transmit和HAL_SPI_Receive但做 MRAM 驱动时最方便的是自己封装一个“收发一个字节”的函数因为 SPI 是全双工的发命令的同时就在收数据。uint8_t MRAM_SPI_Transfer(uint8_t txData) { uint8_t rxData 0; HAL_SPI_TransmitReceive(hspi1, txData, rxData, 1, 100); return rxData; }这个函数是整个驱动的地基。发命令、发地址、发数据、读数据全靠它。注意超时时间别设太短虽然 MRAM 快但调试阶段给个 100ms 比较稳。3.3 写使能与状态查询写之前先发 WREN然后可以读状态寄存器确认 WEL 位。虽然实际项目里为了速度经常省略确认但调试阶段建议加上能快速定位问题。void MRAM_WriteEnable(void) { MRAM_CS_LOW(); MRAM_SPI_Transfer(0x06); // WREN MRAM_CS_HIGH(); } uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_LOW(); MRAM_SPI_Transfer(0x05); // RDSR status MRAM_SPI_Transfer(0xFF); MRAM_CS_HIGH(); return status; }状态寄存器 bit1 是 WEL写完 WREN 后读一下如果是 0x02 就说明写使能成功了。3.4 单字节与多字节写入MRAM 的写命令格式是命令码 0x02 16 位地址高字节在前 数据。地址是 19 位512KB 2^19但用 3 个字节发地址时最高几位是无关位直接填 0 就行。void MRAM_WriteByte(uint32_t addr, uint8_t data) { MRAM_WriteEnable(); MRAM_CS_LOW(); MRAM_SPI_Transfer(0x02); // WRITE MRAM_SPI_Transfer((addr 16) 0xFF); MRAM_SPI_Transfer((addr 8) 0xFF); MRAM_SPI_Transfer(addr 0xFF); MRAM_SPI_Transfer(data); MRAM_CS_HIGH(); }多字节写入就是把数据循环发出去地址自动递增不需要每字节都发命令。这是 MRAM 比 EEPROM 爽的地方——EEPROM 跨页写要处理页边界MRAM 完全不用管一口气写到末尾都行。void MRAM_WriteBuffer(uint32_t addr, uint8_t *buf, uint32_t len) { MRAM_WriteEnable(); MRAM_CS_LOW(); MRAM_SPI_Transfer(0x02); MRAM_SPI_Transfer((addr 16) 0xFF); MRAM_SPI_Transfer((addr 8) 0xFF); MRAM_SPI_Transfer(addr 0xFF); for (uint32_t i 0; i len; i) { MRAM_SPI_Transfer(buf[i]); } MRAM_CS_HIGH(); }3.5 读取实现读命令 0x03格式和写一样发完地址后连续读地址自动递增。void MRAM_ReadBuffer(uint32_t addr, uint8_t *buf, uint32_t len) { MRAM_CS_LOW(); MRAM_SPI_Transfer(0x03); // READ MRAM_SPI_Transfer((addr 16) 0xFF); MRAM_SPI_Transfer((addr 8) 0xFF); MRAM_SPI_Transfer(addr 0xFF); for (uint32_t i 0; i len; i) { buf[i] MRAM_SPI_Transfer(0xFF); } MRAM_CS_HIGH(); }读的时候发 0xFF 是惯例因为 MOSI 上的数据无所谓MISO 上才是芯片返回的数据。4. 工业场景下的数据组织与可靠性设计4.1 存储分区规划512KB 看着不大但规划好了很够用。我一般这么分0x00000 ~ 0x00FFF4KB设备参数区存序列号、校准系数、通信配置。0x01000 ~ 0x01FFF4KB参数备份区双份存储读的时候校验。0x02000 ~ 0x7FFFF约 504KB循环日志区按记录结构循环覆盖。参数区做双备份是工业项目的常规操作。主份读出来 CRC 不对就切到备份区同时把主份修复。MRAM 写入快双写几乎不增加时间开销。4.2 掉电保护与写入原子性MRAM 虽然掉电不丢但多字节写入过程中掉电仍然可能出现“写了一半”的情况。比如你要更新一条 32 字节的记录写到第 16 字节时断电这条记录就残缺了。解决办法是加一个“提交标志”或者用双缓冲。我的做法是每条记录末尾加 2 字节 CRC再加 1 字节有效标志。写入顺序是先写数据体再写 CRC最后写有效标志。读的时候先看有效标志再看 CRC都对了才认这条记录。这样即使写一半掉电有效标志没写上去这条记录会被判为无效不会污染数据。typedef struct { uint32_t timestamp; float value[4]; uint16_t crc; uint8_t valid; } LogRecord_t;提示有效标志一定要最后写而且单独一次写操作完成别和数据混在一个 buffer 里一次性发出去否则掉电时标志可能先于数据落盘。4.3 CRC 校验的选型工业数据校验我一般用 CRC-16/CCITT 或者 CRC-16/MODBUS。MRAM 本身出错率极低但 SPI 传输过程可能受干扰加上 CRC 能兜住传输错误。CRC 表可以预先算好放 Flash也可以运行时算STM32F215 的算力跑 CRC-16 毫无压力。uint16_t CRC16_Modbus(uint8_t *buf, uint32_t len) { uint16_t crc 0xFFFF; for (uint32_t i 0; i len; i) { crc ^ buf[i]; for (int j 0; j 8; j) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; }5. 实操调试与常见问题排查5.1 上电后读出来全是 0xFF 或 0x00这是最常见的现象八成是 SPI 没通。排查顺序先用示波器看 SCK 有没有波形。没有波形检查 SPI 初始化和时钟使能。看 CS 有没有拉低。CS 一直是高说明片选代码没执行或者 GPIO 配错。看 MOSI 上有没有命令码。没有检查HAL_SPI_TransmitReceive的返回值。看 MISO 有没有反应。MISO 一直不动检查 MRAM 供电和焊接。我遇到过一块板子MRAM 的 VDD 焊盘虚焊上电后芯片根本没工作MISO 一直是高阻读出来全 0xFF。补焊后立刻正常。5.2 写入后读出来不对先确认 WREN 有没有发。忘了发 WREN 是新手最常犯的错写命令被静默忽略。其次检查地址有没有越界512KB 的地址范围是 0x00000 ~ 0x7FFFF超过这个范围行为未定义。再检查 SPI 模式Mode 0 和 Mode 3 搞反了读出来的数据会整体移位。5.3 高速下偶发读错前面提过这是信号完整性问题。降速测试如果低速正常高速出错基本就是走线或者干扰问题。处理办法SCK 和 MISO 串 22Ω~33Ω 电阻缩短走线远离电源电感必要时在 MISO 对地加 10pF 小电容。5.4 常见问题速查表现象可能原因排查方法读出全 0xFF芯片未供电/虚焊/CS 未拉低查供电、补焊、示波器看 CS读出全 0x00MISO 短路到地/模式错误查 MISO 走线、确认 SPI 模式写入无效未发 WREN读状态寄存器确认 WEL数据错位SPI 模式或 CPHA 配错改 Mode 0 重试高速偶发错误信号完整性降速、串阻、缩短走线地址越界地址超过 0x7FFFF检查地址计算5.5 几个实测有效的经验第一调试阶段一定用逻辑分析仪抓一次完整时序。命令码、地址、数据、CS 边沿一眼就能看出问题比盲猜快十倍。第二先写一个已知模式比如 0x55、0xAA 交替再读回对比比写随机数据更容易发现位翻转。第三MRAM 不需要延时等待写完立刻就能读如果你在代码里加了 Flash 那种while(忙等待)纯属浪费时间。第四批量写的时候尽量一次 CS 拉低写完减少 CS 翻转次数既快又稳。6. 性能实测与方案对比6.1 实测写入速度在 STM32F215RE 30MHz SPI 下我实测连续写 1KB 数据含 WREN 和命令开销大约耗时 280 微秒平均每字节不到 0.3 微秒。同样条件下SPI NOR Flash 写 1KB 需要先擦一个 4KB 扇区约 40ms再编程约 1ms总共 41ms 左右。差距是 140 倍以上。对于需要高频记录的场景这个差距是决定性的。读取速度方面30MHz 下连续读 1KB 约 270 微秒和写入相当因为 SPI 是全双工的读写速率都受时钟限制。6.2 和 EEPROM 方案的对比有人会问用 I2C EEPROM 不是更便宜吗确实便宜但 EEPROM 单字节写入要 5ms 左右写 1KB 要 5 秒而且寿命只有 100 万次。工业设备如果每秒记录一次EEPROM 几个月就写坏一个扇区了。MRAM 的 10^14 次寿命按每秒写一次算可以写几百万年。在“频繁写”这个维度上MRAM 是碾压性的。6.3 成本与容量的取舍MR25H40CDF 单价确实比同容量 Flash 贵不少这是它的唯一短板。所以我的建议是关键数据用 MRAM大容量历史数据用 Flash 或 SD 卡。比如设备参数、掉电快照、最近一段时间的核心日志放 MRAM几个月的历史曲线放大容量存储。这样既保证了关键数据的可靠性又控制了整体成本。7. 代码组织与工程化建议7.1 驱动分层我习惯把 MRAM 驱动分成两层底层mram_hal.c只负责 SPI 字节收发和 CS 控制上层mram.c提供MRAM_Read、MRAM_Write、MRAM_ReadRecord、MRAM_WriteRecord这些业务接口。这样换主控或者换 SPI 外设时只改底层上层不动。7.2 参数默认值与恢复设备第一次上电时 MRAM 是空的或者全是随机值需要有默认参数恢复机制。我的做法是在参数区头部放一个魔数比如 0x5A5AA5A5读出来不等于魔数就加载默认参数并写入。这样既处理了首次上电也处理了参数区被意外破坏的情况。7.3 循环日志的读写指针循环日志区用两个指针写指针和读指针。写指针每次写完一条记录后递增到区尾回绕到区头。读指针由上位机读取时更新。为了防止读写指针本身丢失我把它们也存两份带 CRC。指针更新频率不高双写开销可以忽略。typedef struct { uint32_t write_ptr; uint32_t read_ptr; uint16_t crc; } LogCtrl_t;8. 一些容易被忽略的细节WP# 和 HOLD# 引脚别悬空。虽然内部有上拉但工业环境干扰大悬空可能导致误触发写保护或者保持状态。我一般直接上拉到 VDD不用这两个功能。电源去耦要到位。MRAM 虽然功耗不高但写入瞬间电流会有跳变VDD 上并一个 0.1uF 加一个 1uF离芯片越近越好。SPI 的 GPIO 速度等级要配对。STM32 的 GPIO 有 Low/Medium/Fast/High 四档跑 30MHz SPI 时 SCK 和 MOSI 要设成 HighMISO 设成 High 或者 Medium 都行。设成 Low 的话波形上升沿会变缓高速下直接读错。初始化顺序要注意。先初始化 GPIO 和 SPI再把 CS 拉高最后才操作 MRAM。如果 CS 初始化前是浮空低电平上电瞬间可能被 MRAM 误认为片选有效收到一堆乱码命令。提示MRAM 上电后不需要任何初始化命令就能读写这点比 Flash 省事Flash 有时候要发释放掉电或者读 ID 的命令。9. 这套方案适合什么场景工业数据采集终端、电力监测设备、车载记录仪、医疗设备参数存储、任何需要“频繁写小数据 怕掉电 长寿命”的嵌入式项目这套 STM32F215RE MR25H40CDF 的组合都很合适。它的核心价值不在于容量而在于把“关键数据的可靠写入”这件事做到极致简单——不用擦除、不用等待、不用管寿命、掉电即存。我自己在实际项目里用这套方案跑了两年多现场断电几十次没有丢过一条关键记录。踩过的坑主要集中在 SPI 模式和信号完整性上把这两块处理好剩下的就是常规的代码组织问题。如果你正在为掉电丢数据发愁不妨试试 MRAM 这条路前期多花的那点成本比起现场返修和客户投诉真的不算什么。