尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F412RE驱动MR25H40CDF MRAM:硬件连接、SPI时序与掉电保护实战

STM32F412RE驱动MR25H40CDF MRAM:硬件连接、SPI时序与掉电保护实战 1. 为什么偏偏选 MR25H40CDF 这颗 MRAM1.1 从掉电就丢到掉电不丢的存储焦虑做嵌入式的人大概都经历过这种场景设备在现场跑了几个月突然断电重启之后发现标定参数、运行日志、故障记录全没了。用 EEPROM 吧写入速度慢得让人抓狂擦写次数也就百万次级别用 SPI Flash 吧写之前还得先擦除整个扇区掉电时机不对还会丢数据用 FRAM 吧容量小、价格高而且很多型号已经不太好买。这个矛盾在工业现场尤其突出——设备可能一天要记录几百次数据还要保证十年不坏。MR25H40CDF 这颗芯片就是冲着这个痛点来的。它是 Everspin 出的 4Mbit512KB串行 MRAM走 SPI 接口和普通 SPI Flash 的引脚几乎完全兼容。MRAM 的核心优势在于写入不需要擦除、写入速度接近 SRAM、擦写寿命几乎无限官方标称 10^14 次以上、掉电数据不丢。这几点组合在一起基本上把 EEPROM 和 Flash 的短板全补上了。我第一次接触这颗芯片是在一个电力监测项目里客户要求每 100ms 记录一次三相电流有效值设备寿命要求 10 年。算一下100ms 一次一天 864000 次十年就是 31 亿次写入。EEPROM 根本扛不住Flash 更不行。换成 MR25H40CDF 之后这个写入频率对它来说连热身都算不上。1.2 MR25H40CDF 的关键参数与选型逻辑先把这颗芯片的核心参数摆出来方便你判断它是否适合你的项目参数项数值说明容量4Mbit / 512KB按字节寻址不是按扇区接口SPIMode 0 和 Mode 3最高 40MHz 时钟供电电压2.7V ~ 3.6V典型 3.3V写入寿命10^14 次几乎可以忽略寿命问题数据保持20 年以上掉电不丢写入时间无等待字节级写入不需要擦除周期封装8-SOIC / 8-DFN和 W25Q 系列引脚兼容工作温度-40°C ~ 85°C工业级适合工业现场选型的时候有几个点容易被忽略。第一MR25H40CDF 的 SPI 模式和普通 Flash 略有不同它支持 Mode 0 和 Mode 3但不支持 Daisy Chain如果你打算多颗级联得用独立片选。第二它的写入没有页的概念你可以直接往任意地址写任意长度的数据不需要像 Flash 那样先擦后写。第三它的状态寄存器结构和 Flash 不一样没有 BUSY 位那种长时间等待写入几乎瞬间完成。注意虽然 MR25H40CDF 和 W25Q64 引脚兼容但软件驱动不能直接套用。Flash 的写使能、擦除、轮询 BUSY 那套流程在 MRAM 上完全不适用硬套会出问题。1.3 STM32F412RE 作为主控的匹配度分析STM32F412RE 是 ST 家的 Cortex-M4 芯片主频 100MHz带 512KB Flash 和 256KB SRAM外设资源丰富SPI 接口有多个。选它来配 MR25H40CDF主要看中几点一是 SPI 时钟可以跑到 50MHz分频后实际可用 25MHz 或 50MHz足够喂饱 MRAM 的 40MHz 上限二是它有硬件 CRC 单元如果做数据校验可以省 CPU三是工业级温度范围和 MRAM 匹配。在实际项目里我通常把 MR25H40CDF 挂在 SPI1 上因为 SPI1 的时钟源来自 APB2频率更高适合做高速数据记录。SPI2 和 SPI3 挂在 APB1 上速度稍低可以用来挂其他低速外设。片选信号用普通 GPIO 控制不用硬件 NSS这样更灵活也方便多片扩展。2. 硬件连接与 SPI 时序的坑2.1 引脚连接与 PCB 布局要点MR25H40CDF 是 8 脚封装引脚定义和标准 SPI Flash 一致引脚 1CS片选低有效引脚 2SO数据输出从机到主机引脚 3WP写保护低有效引脚 4GND引脚 5SI数据输入主机到从机引脚 6SCK时钟引脚 7HOLD保持低有效引脚 8VCC接线的时候WP 和 HOLD 这两个脚很多人直接悬空这在实验室里可能没问题但在工业现场就是隐患。WP 悬空时如果受到干扰被拉低芯片会进入写保护状态你的数据就写不进去了。HOLD 悬空被拉低通信会直接中断。我的做法是WP 和 HOLD 都通过 10K 电阻上拉到 VCC如果确定不需要写保护和保持功能也可以直接接到 VCC。PCB 布局上SPI 的四根信号线尽量等长SCK 走线要远离模拟信号和电源开关节点。MRAM 的电源脚旁边放一个 0.1uF 的陶瓷电容再并一个 1uF 的钽电容位置越靠近芯片越好。如果 SPI 时钟跑到 25MHz 以上建议在 SCK 和 SI 上串 22Ω 到 33Ω 的电阻做阻抗匹配减少过冲和振铃。实操心得我遇到过一批板子MRAM 读写偶尔出错查了半天发现是 SCK 走线太长超过 8cm而且和 DC-DC 的 SW 节点平行走了很长一段。后来把 SCK 改短、加地线隔离问题就消失了。SPI 虽然叫低速接口但时钟边沿很陡高频分量丰富布局不好照样出问题。2.2 SPI 模式选择与时序参数计算MR25H40CDF 支持 SPI Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1。STM32 的 SPI 外设配置成哪种模式都可以但要注意Mode 0 和 Mode 3 在时钟空闲电平和采样边沿上不同如果主从配置不一致读出来的数据会整体移位或者全错。我一般用 Mode 0因为这是最常用的配置调试起来方便。配置 STM32 SPI1 的时候关键参数如下时钟极性 CPOL 0空闲低电平时钟相位 CPHA 0第一个边沿采样数据大小 8 位波特率预分频 2 或 4对应 50MHz 或 25MHz首位顺序 MSB FirstNSS 管理 软件控制时钟频率的计算STM32F412 的 SPI1 挂在 APB2 上APB2 时钟默认 100MHz。如果预分频设为 2SPI 时钟就是 50MHz设为 4就是 25MHz。MR25H40CDF 的最高时钟是 40MHz所以预分频 2 的时候是超频使用的虽然实测大部分芯片能跑但工业级应用建议留余量用预分频 425MHz更稳妥。25MHz 下传输一个字节需要 8 个时钟周期也就是 320ns。512KB 全读一遍需要 512×1024×320ns ≈ 168ms。这个速度对于大多数数据记录场景已经足够了。2.3 片选信号的处理硬件 NSS 还是软件 GPIOSTM32 的 SPI 外设支持硬件 NSS 和软件 NSS 两种模式。硬件 NSS 模式下NSS 引脚由 SPI 外设自动控制但有个坑当 SPI 配置为 Master 时硬件 NSS 必须接高电平或者配置成输出否则会进入 Mode Fault 状态。而且硬件 NSS 在多从机场景下不够灵活。我的建议是一律用软件 GPIO 控制片选。具体做法是把 CS 配置成普通推挽输出初始拉高每次通信前拉低通信结束后拉高。这样你可以挂多个 SPI 从机每个用独立的 GPIO 控制片选互不干扰。代码上我习惯封装两个宏#define MRAM_CS_LOW() HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_RESET) #define MRAM_CS_HIGH() HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_SET)每次读写操作前后调用这两个宏确保片选信号干净。注意片选拉低到第一个时钟边沿之间要有足够的建立时间MR25H40CDF 要求最小 5nsSTM32 的 GPIO 翻转速度足够快一般不需要额外延时但如果你的 SPI 时钟很高可以在拉低片选后加一个__NOP()或者几纳秒的延时。3. 驱动实现从寄存器操作到 HAL 封装3.1 MR25H40CDF 的指令集解析MR25H40CDF 的指令集比 Flash 简单很多常用的就几条指令名称指令码功能说明WREN0x06写使能写入前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据RDID0x9F读设备 ID和 Flash 最大的区别是没有扇区擦除指令如 0x20、0xD8也没有页写限制。写入的时候发完 WRITE 指令和 3 字节地址后可以直接连续写入任意长度的数据地址会自动递增写到末尾会回卷到开头这点要注意别写越界了。状态寄存器只有几位有效WEL写使能锁存、WIP写进行中、BP0/BP1块保护、SRWD状态寄存器写保护。WIP 位在 MRAM 上几乎瞬间就清零因为写入不需要时间。但为了兼容性和保险我通常还是会在写入后读一下状态寄存器确认。3.2 底层读写函数的实现先看读数据的实现。读操作最简单发 0x03 3 字节地址然后连续读 N 字节void MRAM_Read(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; cmd[0] 0x03; cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 4, HAL_MAX_DELAY); HAL_SPI_Receive(hspi1, buf, len, HAL_MAX_DELAY); MRAM_CS_HIGH(); }写数据稍微复杂一点需要先发 WREN再发 WRITE 指令和地址最后写数据void MRAM_Write(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; uint8_t wren 0x06; // 第一步写使能 MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, wren, 1, HAL_MAX_DELAY); MRAM_CS_HIGH(); // 第二步写指令 地址 数据 cmd[0] 0x02; cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 4, HAL_MAX_DELAY); HAL_SPI_Transmit(hspi1, buf, len, HAL_MAX_DELAY); MRAM_CS_HIGH(); }这里有个细节WREN 和 WRITE 之间必须拉高片选再拉低否则 WREN 不会生效。我见过有人把 WREN 和 WRITE 放在同一个片选周期里结果写不进去查了半天以为是硬件问题。注意HAL_SPI_Transmit 和 HAL_SPI_Receive 是阻塞式调用在高速连续写入时会占用 CPU。如果你的数据量很大建议改用 DMA 模式把 CPU 解放出来做其他事情。3.3 状态寄存器与写保护处理状态寄存器的读取很简单uint8_t MRAM_ReadStatus(void) { uint8_t cmd 0x05; uint8_t status; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); HAL_SPI_Receive(hspi1, status, 1, HAL_MAX_DELAY); MRAM_CS_HIGH(); return status; }状态寄存器的 bit0 是 WIPbit1 是 WELbit2 和 bit3 是 BP0/BP1bit7 是 SRWD。正常情况下读出来应该是 0x00 或者 0x02如果刚发过 WREN。如果读出来是 0x0C 或者 0x08说明块保护被设置了这时候写入会被拒绝。块保护是通过 BP0 和 BP1 控制的可以保护 1/4、1/2 或全部存储区。如果你的应用不需要写保护建议在初始化的时候把 BP0 和 BP1 清零避免误保护导致写入失败。初始化流程我一般这样写void MRAM_Init(void) { // 读设备 ID 确认通信正常 uint8_t cmd 0x9F; uint8_t id[3]; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); HAL_SPI_Receive(hspi1, id, 3, HAL_MAX_DELAY); MRAM_CS_HIGH(); // MR25H40CDF 的 ID 应该是 0x18 0x03 0x02 之类 // 具体值查数据手册这里只做通信验证 // 清除块保护 uint8_t wrsr[2] {0x01, 0x00}; uint8_t wren 0x06; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, wren, 1, HAL_MAX_DELAY); MRAM_CS_HIGH(); MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, wrsr, 2, HAL_MAX_DELAY); MRAM_CS_HIGH(); }4. 数据存储策略与掉电保护设计4.1 环形缓冲区与磨损均衡的取舍MRAM 虽然寿命几乎无限但并不意味着可以随便乱写。合理的存储策略能提高数据可靠性也方便后续检索。我常用的方案是环形缓冲区 记录头。具体做法把 512KB 分成若干个记录块每个块比如 64 字节包含一个记录头时间戳、数据类型、长度、CRC和实际数据。写指针从 0 开始递增写到末尾回卷到开头覆盖最旧的数据。这样不需要擦除也不需要垃圾回收逻辑简单可靠。有人会问MRAM 需要磨损均衡吗严格来说不需要因为它的擦写寿命是 10^14 次按每秒写 1000 次算也能写 3000 多年。但如果你追求极致可以做一个简单的地址轮转把写入分散到不同区域进一步降低单点压力。不过在实际项目里我从来没见谁因为 MRAM 写坏过。4.2 掉电检测与数据完整性保障MRAM 的最大优势是掉电不丢数据但前提是数据已经写进去了。如果掉电发生在 SPI 传输过程中可能会写入不完整的数据。所以掉电检测和写入原子性还是要考虑。我的做法是在电源端加一个电压监测芯片比如 TLV3012 或者用 STM32 的 PVD 功能当电压降到阈值以下时触发中断在中断里完成当前记录的收尾工作。STM32F412 的 PVD 可以设置多个阈值我一般设 2.9V 触发这时候电源电容还能撑几毫秒足够写完一条记录。另外每条记录都带 CRC16 校验。读取的时候先校验 CRC如果不对就跳过这条记录读下一条。这样即使掉电导致某条记录损坏也不会影响其他数据。typedef struct { uint32_t timestamp; uint16_t type; uint16_t len; uint16_t crc; uint8_t data[56]; } MRAM_Record_t;写入的时候先算好 CRC再一次性写入。读取的时候先读记录头根据 len 读出数据再算 CRC 对比。4.3 多区域划分与参数存储实践在实际项目里我通常把 MRAM 分成三个区域参数区前 16KB存标定参数、设备配置、序列号等写入频率低但要求绝对可靠。这个区域我一般做双备份A/B 区交替写读的时候对比两份数据取 CRC 正确的那份。日志区中间 480KB存运行日志、故障记录用环形缓冲区写入频率高。保留区最后 16KB存一些临时数据或者未来扩展用。参数区的双备份逻辑很简单维护一个当前有效区标志写入的时候先写备份区再写主区最后更新标志。读取的时候如果主区 CRC 不对就从备份区恢复。这样即使写入过程中掉电至少有一份数据是完整的。实操心得我遇到过一个问题参数区写入的时候如果刚好在写主区的时候掉电主区数据损坏但备份区还是旧的。后来改成先写备份区再写主区最后写标志并且标志也做双份问题就解决了。这个逻辑看起来简单但真到现场出问题的时候没有备份就麻烦了。5. 调试过程中遇到的典型问题与排查5.1 SPI 通信失败的常见原因调试 SPI 通信最怕的就是什么都对就是不通。我整理了一个排查清单按顺序检查排查项检查方法常见问题电源万用表测 VCC 和 GND电压不足 2.7V或者纹波太大片选示波器看 CS 波形片选没拉低或者拉低时间不够时钟示波器看 SCK没有时钟输出或者频率不对模式对比 CPOL/CPHA主从模式不一致数据移位数据线示波器看 SI/SO线接反或者虚焊上拉电阻测 WP/HOLD 电压悬空导致误触发我印象最深的一次SPI 读出来全是 0xFF。查了半天发现是 SO 线虚焊示波器一看根本没有波形。重新焊了一下就好了。所以遇到问题先用示波器看波形比盯着代码强。5.2 数据写入后读出来不对怎么办写入后读出来不对通常有几种情况第一种写使能没生效。WREN 指令发完之后如果片选没有正确拉高再拉低WEL 位不会置位后续写入会被忽略。解决方法是严格按照WREN → 拉高 CS → 拉低 CS → WRITE的顺序操作。第二种地址越界。MR25H40CDF 的地址是 19 位512KB如果你传了一个超过 0x7FFFF 的地址它会回卷到开头覆盖之前的数据。检查你的地址计算逻辑确保不越界。第三种块保护没清除。如果状态寄存器的 BP0/BP1 被设置写入会被拒绝。初始化的时候读一下状态寄存器确认 BP 位是 0。第四种SPI 模式不对。如果 CPOL/CPHA 配置错误读出来的数据会整体移位。比如你写 0x55读出来可能是 0xAA 或者 0x2A。这时候检查 SPI 初始化代码确认模式和 MRAM 匹配。5.3 高速读写时的数据错误与信号完整性当 SPI 时钟跑到 25MHz 以上时信号完整性问题会变得明显。我遇到过几种典型情况SCK 过冲示波器上看 SCK 波形上升沿有明显的过冲和振铃。解决方法是串 22Ω 电阻或者降低时钟频率。SO 采样错误数据在时钟边沿附近跳变导致采样到错误的值。这时候可以调整 CPHA或者缩短走线长度。电源噪声MRAM 写入瞬间电流增大导致电源电压跌落。在 VCC 脚旁边加 0.1uF 和 1uF 电容问题就能缓解。如果条件允许用逻辑分析仪抓一下 SPI 波形看看数据在时钟边沿是否稳定。逻辑分析仪比示波器更适合看协议层面的问题能直接解析出指令和数据。6. 性能实测与优化建议6.1 读写速度实测数据我在 STM32F412RE MR25H40CDF 的平台上做了一组实测SPI 时钟 25MHzMode 0阻塞式传输操作数据量耗时等效速度连续读512KB约 175ms约 2.9MB/s连续写512KB约 180ms约 2.8MB/s单字节写1 字节约 2.5us-读设备 ID3 字节约 1.2us-写入速度比读取略慢因为每次写入都要先发 WREN。如果改成 DMA 模式速度还能再提升 10% 到 20%而且 CPU 占用率大幅降低。6.2 DMA 模式配置与优化用 DMA 的话SPI 的收发都可以交给 DMA 控制器CPU 只需要配置好缓冲区等传输完成中断就行。STM32F412 的 SPI1 有对应的 DMA 通道配置起来不复杂。关键配置SPI1_TX 使用 DMA2 Stream3 或 Stream5SPI1_RX 使用 DMA2 Stream0 或 Stream2DMA 模式设为 Normal 或 Circular优先级设为 High用 DMA 的时候要注意片选信号必须在 DMA 传输完成后才能拉高。如果在 DMA 还没传完就拉高片选数据会丢失。我一般用 DMA 传输完成中断来拉高片选或者在传输完成后轮询 DMA 标志位。6.3 不同应用场景下的参数调优不同的应用场景对 MRAM 的使用策略不一样高频数据记录比如每 10ms 写一次建议用 DMA 环形缓冲区减少 CPU 干预。参数存储写入频率低但要求可靠建议用双备份 CRC 校验。日志存储数据量大建议分块管理每块带时间戳和 CRC方便检索。启动配置读取频率高建议在启动时把配置读到 RAM 里后续直接读 RAM减少 SPI 访问。如果 SPI 总线上还挂了其他设备要注意片选信号的管理避免多个设备同时被选中。我一般会在片选 GPIO 上加上拉电阻确保默认状态是高电平。7. 一些零散但重要的经验MR25H40CDF 这颗芯片用起来整体很省心但有几个细节值得单独拎出来说。第一不要把它当 Flash 用。我见过有人直接套用 W25Q64 的驱动结果写不进去还以为是芯片坏了。MRAM 没有擦除指令没有页写限制状态寄存器也不一样驱动必须重写。第二WP 和 HOLD 不要悬空。这两个脚悬空的时候芯片可能进入意外状态。最稳妥的做法是上拉到 VCC如果不需要写保护直接接 VCC 也行。第三写入前一定要发 WREN。虽然 MRAM 写入很快但 WREN 这一步不能省。我试过连续写入的时候只在第一次发 WREN结果后面的写入全部失败。后来改成每次写入都发 WREN问题就没了。第四地址回卷要小心。MR25H40CDF 的地址是 19 位写到 0x7FFFF 之后会回卷到 0x00000。如果你的数据长度超过了剩余空间会覆盖开头的数据。写之前算一下剩余空间或者用环形缓冲区管理。第五CRC 校验不能省。虽然 MRAM 很可靠但 SPI 传输过程中可能受到干扰。每条记录带 CRC读取的时候校验一下能过滤掉大部分错误数据。第六电源要干净。MRAM 对电源噪声比较敏感尤其是在高速写入的时候。VCC 脚旁边一定要放去耦电容位置越近越好。如果电源纹波大可以考虑加一个 LDO 单独给 MRAM 供电。最后再分享一个小技巧如果你不确定 SPI 配置对不对可以先读设备 ID。MR25H40CDF 的 RDID 指令是 0x9F读出来应该是 3 个字节具体值查数据手册。如果读出来全是 0x00 或者 0xFF说明通信有问题先查硬件再查软件。这个方法比盲目调试快得多。
返回列表