ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32F091RC 驱动 MR25H40CDF MRAM 实战:SPI 接口与掉电保护

STM32F091RC 驱动 MR25H40CDF MRAM 实战:SPI 接口与掉电保护 1. 项目缘起与方案选型思考1.1 为什么要在工业场景里折腾 MRAM 这颗料做工业嵌入式这行的朋友应该都有个共识数据存储这块选型选得好后面少掉一半头发。我这些年经手的项目里EEPROM 写穿、Flash 掉数据、掉电瞬间丢参数的事故几乎每隔一段时间就要碰上一次。尤其是那些要求“掉电前最后一笔数据必须保住”的场合比如电表抄表记录、工控设备的故障快照、医疗设备的运行日志传统方案总有点捉襟见肘。这次项目里我用的是MR25H40CDF一颗 4Mbit 的 MRAM磁性随机存储器搭配STM32F091RC这颗 Cortex-M0 内核的 MCU通过SPI接口做数据存储和读取。为什么选这个组合先说 MRAM 本身的特性它不像 Flash 那样需要先擦后写也不像 EEPROM 那样写入速度慢、寿命有限。MR25H40CDF 的写入次数理论上没有上限写入速度是纳秒级的掉电后数据能保持二十年以上。这几个特性放在工业环境里简直就是“刚需”。STM32F091RC 这边主频 48MHz256KB Flash32KB SRAM带 2 个 SPI 接口其中一个支持最高 24MHz 的时钟。对于 MR25H40CDF 来说它支持最高 40MHz 的 SPI 时钟所以 STM32F091RC 的 SPI 速率完全够用不会成为瓶颈。而且 F0 系列在工业现场用得非常广价格便宜、供货稳定、HAL 库成熟拿来配 MRAM 属于“门当户对”。提示MRAM 不是“更快的 Flash”它的读写机制和 Flash 完全不同。Flash 是按块擦除、按页写入MRAM 是随机访问、按字节写入不需要擦除操作。这一点在写驱动的时候会直接影响你的命令设计。1.2 这个方案到底解决了哪些实际问题我总结了一下这套组合主要解决三类问题。第一类是频繁写入导致存储介质寿命不足。传统 EEPROM 标称 100 万次擦写Flash 标称 10 万次擦写在数据采集频率高的场景下几年就能把寿命耗光。MRAM 没有这个限制你哪怕每秒写一次写十年也不会坏。第二类是掉电瞬间数据丢失。很多方案靠加超级电容或者备份电池来撑住掉电后的写入窗口但硬件成本上去了可靠性还依赖电容寿命。MRAM 的写入是即时完成的不需要等待掉电前只要 SPI 时序走完数据就已经在存储单元里了。第三类是系统启动速度要求高。有些设备要求上电后 100ms 内进入工作状态如果用 Flash 存配置参数启动时读取需要时间MRAM 可以直接随机读取省掉等待周期。1.3 硬件连接的整体思路MR25H40CDF 是 8 引脚 SOIC 封装引脚定义很清晰VCC、GND、CS、SCK、SI、SO、WP、HOLD。其中 WP 是写保护HOLD 是暂停通信。在工业环境里我一般会把 WP 接到 MCU 的一个 GPIO 上软件控制写保护防止误写。HOLD 如果不用直接拉高就行。和 STM32F091RC 的连接方式我用的是 SPI1具体引脚分配如下MR25H40CDF 引脚STM32F091RC 引脚功能说明CSPA4片选软件控制SCKPA5SPI 时钟SIPA7MOSI主出从入SOPA6MISO主入从出WPPB0写保护GPIO 控制HOLD3.3V直接拉高不暂停VCC3.3V供电GNDGND共地这里有个细节STM32F091RC 的 SPI1 默认引脚就是 PA4~PA7但 PA4 同时也是 SPI1 的 NSS 硬件片选。我选择用软件片选把 PA4 配成普通 GPIO原因是硬件片选在多从机场景下不够灵活而且 MRAM 的片选时序要求比较严格软件控制更可控。注意MR25H40CDF 的 VCC 范围是 2.7V~3.6VSTM32F091RC 是 3.3V 供电两者可以直接对接不需要电平转换。但如果你的系统里有 5V 器件一定要加电平转换芯片否则会打坏 MRAM。2. MR25H40CDF 的核心机制与驱动设计要点2.1 MRAM 的存储原理和 SPI 命令集MR25H40CDF 内部是 512K×8 位的存储阵列总共 4Mbit。它的存储单元是磁性隧道结MTJ通过改变磁化方向来存储 0 和 1。这个过程不需要电荷泵不需要擦除写入就是直接改变磁化状态。所以它的写入电流很小速度极快而且没有擦除周期这个概念。它的 SPI 命令集和标准 SPI Flash 很像但有几个关键区别。我列一下常用的命令命令名称命令码说明WREN0x06写使能写入前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据RDID0x9F读设备 ID和 Flash 最大的不同是没有扇区擦除命令。Flash 写之前要先发 0x20 或 0xD8 擦除MRAM 不需要直接发 WRITE 就能写。这一点在驱动设计上省了很多事但也意味着你不能用现成的 Flash 驱动直接套得自己改。RDID 命令返回的 ID 是 0x7F7F7F7F7F7F7F7F 之类的固定值具体要看手册。我实测读出来是 0x7F 开头的一串用来确认 SPI 通信是否正常。2.2 STM32F091RC 的 SPI 配置细节STM32F091RC 的 SPI1 配置我用的是 CubeMX 生成初始化代码然后手动调整了几个参数。关键配置如下ModeFull-Duplex MasterData Size8 BitsClock Polarity (CPOL)LowClock Phase (CPHA)1 EdgeNSSSoftwareBaud Rate Prescaler4First BitMSB First这里 CPOL 和 CPHA 的组合决定了 SPI 模式。MR25H40CDF 支持 SPI Mode 0 和 Mode 3我选的是 Mode 0CPOL0CPHA0。但 CubeMX 里 CPHA 的选项是“1 Edge”和“2 Edge”对应的是第一个边沿和第二个边沿。Mode 0 就是 CPOLLowCPHA1 Edge。波特率分频系数设为 4STM32F091RC 的 SPI1 时钟来自 APB2默认 48MHz分频后是 12MHz。MR25H40CDF 最高支持 40MHz12MHz 留了足够余量在工业现场长走线的情况下更稳定。实操心得如果你发现 SPI 通信不稳定先把波特率降下来试。我遇到过 PCB 走线过长导致 24MHz 下误码率飙升的情况降到 6MHz 就稳了。工业现场电磁干扰大速度不是越高越好。2.3 片选和写保护的控制逻辑软件片选的关键是每次 SPI 传输前拉低 CS传输完成后拉高 CS。MR25H40CDF 要求 CS 在命令字节和后续数据之间保持低电平不能中间拉高。所以你不能用 HAL_SPI_Transmit 分两次发命令和数据得用 HAL_SPI_TransmitReceive 或者把命令和数据拼成一个缓冲区一次性发。写保护 WP 引脚的控制逻辑上电默认拉高允许写入。在不需要写入的时候把 WP 拉低硬件层面禁止写操作。这样即使软件跑飞了也不会误写数据。我在初始化的时候会把 WP 拉低只有在写数据前才拉高写完立刻拉低。// 写保护控制 #define MRAM_WP_ENABLE() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET) #define MRAM_WP_DISABLE() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET) // 片选控制 #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)这两个宏在驱动里会反复用到定义成宏比写函数调用效率高也更容易读。3. 完整驱动实现与读写流程拆解3.1 初始化流程和 ID 校验初始化的第一步是配置 GPIO 和 SPI 外设这个用 CubeMX 生成就行。第二步是上电后的 ID 校验用来确认 MRAM 是否正常工作。读 ID 的流程是拉低 CS发送 0x9F然后连续读 8 个字节最后拉高 CS。MR25H40CDF 的 ID 是固定的如果读出来全是 0xFF 或者全是 0x00说明 SPI 通信有问题。uint8_t MRAM_ReadID(void) { uint8_t cmd 0x9F; uint8_t id[8] {0}; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_SPI_Receive(hspi1, id, 8, 100); MRAM_CS_HIGH(); // 检查 ID 是否符合预期 if (id[0] 0x7F) { return 1; // 正常 } return 0; // 异常 }这里有个坑HAL_SPI_Transmit 和 HAL_SPI_Receive 之间 CS 不能拉高否则命令会中断。我一开始用两个函数分开调用CS 在中间被拉高了读出来全是 0xFF。后来改成 CS 手动控制问题解决。3.2 写数据的完整时序写数据的流程比 Flash 简单但比 SRAM 多一步写使能。完整步骤如下拉高 WP允许写入拉低 CS发送 WREN 命令0x06拉高 CS拉低 CS发送 WRITE 命令0x02发送 3 字节地址MR25H40CDF 是 19 位地址但用 3 字节表示发送要写入的数据拉高 CS拉低 WP禁止写入注意第 3 步和第 5 步之间 CS 要拉高再拉低这是 WREN 命令的要求。WREN 是一个独立命令发完之后 CS 必须拉高才能生效。void MRAM_Write(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd; MRAM_WP_ENABLE(); // 发送 WREN cmd 0x06; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); MRAM_CS_HIGH(); // 发送 WRITE 命令和地址 cmd 0x02; MRAM_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_Transmit(hspi1, data, len, 1000); MRAM_CS_HIGH(); MRAM_WP_DISABLE(); }地址是 19 位但 MR25H40CDF 的地址空间是 512K 字节需要 19 位地址线。用 3 字节表示时最高字节只有低 3 位有效其余位忽略。我一般直接按 24 位地址处理反正高位是 0。3.3 读数据的时序和注意事项读数据比写数据简单不需要 WREN也不需要 WP 控制。流程是拉低 CS发送 READ 命令0x03发送 3 字节地址然后连续读数据最后拉高 CS。void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { uint8_t cmd 0x03; MRAM_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); MRAM_CS_HIGH(); }读操作可以跨页MR25H40CDF 没有页边界限制地址会自动递增。这一点比 Flash 方便Flash 读到页尾会回卷到页首MRAM 不会。注意读数据的时候 WP 引脚状态无所谓但为了统一管理我一般保持 WP 拉低。只有在写操作前才拉高。3.4 状态寄存器的读取和写保护配置MR25H40CDF 的状态寄存器只有一位有效就是 WEL写使能锁存。读状态寄存器的命令是 0x05返回一个字节bit0 是 WELbit1 是 WEL 的镜像。其他位保留。写状态寄存器可以配置块保护但 MR25H40CDF 的块保护功能比较弱我一般不用直接用 WP 引脚做硬件保护。如果你需要软件块保护可以写状态寄存器的 BP0 和 BP1 位但要注意这些位是非易失的写错了要改回来比较麻烦。uint8_t MRAM_ReadStatus(void) { uint8_t cmd 0x05; uint8_t status; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_SPI_Receive(hspi1, status, 1, 100); MRAM_CS_HIGH(); return status; }这个函数我一般用来调试确认 WREN 是否生效。如果 WREN 发了之后 WEL 还是 0说明 SPI 通信有问题。4. 工业现场常见问题与排查实录4.1 SPI 通信失败的几种典型表现在实际调试中SPI 通信失败是最常见的问题。我整理了几种典型表现和对应的排查思路现象可能原因排查方法读 ID 全是 0xFFMISO 没接好或 CS 没拉低用示波器看 MISO 波形读 ID 全是 0x00MOSI 没接好或时钟没输出检查 SCK 是否有波形写入后读出来不对WREN 没生效或 WP 没拉高读状态寄存器确认 WEL偶尔读写错误波特率太高或走线太长降低波特率到 6MHz 测试上电后第一次读写失败上电时序问题加 10ms 延时再初始化我遇到最多的是第一种和第四种。第一种通常是 PCB 焊接问题MISO 虚焊或者 CS 走线断了。第四种是信号完整性问题工业现场电缆长SPI 时钟频率高了之后反射严重降速是最简单的解决办法。4.2 掉电数据保护的实测验证MRAM 的掉电保护能力是我最看重的。为了验证我做了一个简单的测试在 while 循环里连续写一个递增的计数器到 MRAM然后随机断电上电后读出来看数据是否完整。测试结果连续断电 100 次每次读出来的数据都是断电前最后一次写入的值没有出现半写状态。这说明 MRAM 的写入是原子性的要么写成功要么没写不会出现中间状态。对比之前用 Flash 的方案同样测试条件下有大约 3% 的概率读出来是 0xFF因为 Flash 写入过程中断电会导致扇区数据损坏。MRAM 完全没有这个问题。实操心得虽然 MRAM 掉电保护很强但 SPI 传输过程中断电还是可能丢数据。所以我在写关键数据时会先写一个备份区再写主区上电后对比两个区取有效的那个。这个做法在工业设备里很常见成本低可靠性高。4.3 写入速度的实测数据和优化MR25H40CDF 的 SPI 最高 40MHzSTM32F091RC 的 SPI1 最高 24MHz。我实测了不同波特率下的写入速度SPI 时钟写入 1KB 耗时读取 1KB 耗时6MHz1.8ms1.5ms12MHz0.9ms0.8ms24MHz0.5ms0.4ms写入和读取的耗时差不多因为 MRAM 没有擦除等待。Flash 写入 1KB 需要先擦除 4KB 扇区耗时大约 50ms差距非常明显。优化建议如果数据量大可以用 DMA 传输。STM32F091RC 的 SPI1 支持 DMA配置好之后 CPU 可以腾出来做其他事。我试过用 DMA 写 4KB 数据CPU 占用率从 30% 降到 5% 以下。4.4 多字节跨页写入的边界处理MR25H40CDF 没有页边界限制但如果你一次写超过 256 字节要注意 SPI 传输的超时设置。HAL_SPI_Transmit 的超时参数我设的是 1000ms写 4KB 数据大约需要 2ms1000ms 足够。但如果你的 SPI 时钟只有 1MHz写 4KB 需要 32ms也在范围内。真正需要注意的是地址回卷。MR25H40CDF 的地址空间是 512K 字节如果你从 0x7FFFF 开始写写到 0x80000 会回卷到 0x00000。这个行为在手册里有说明但很容易忽略。我在驱动里加了一个检查如果 addr len 超过 0x80000就分两次写。void MRAM_WriteSafe(uint32_t addr, uint8_t *data, uint16_t len) { if (addr len 0x80000) { uint16_t first_len 0x80000 - addr; MRAM_Write(addr, data, first_len); MRAM_Write(0, data first_len, len - first_len); } else { MRAM_Write(addr, data, len); } }这个函数在实际项目里救过我一次当时数据记录指针跑到末尾了没做边界检查结果把开头的配置参数覆盖了。后来加上这个检查再也没出过问题。4.5 常见问题速查表问题排查步骤解决方案SPI 无响应检查 CS、SCK、MOSI、MISO 波形修复焊接或更换引脚写入无效读状态寄存器确认 WEL确保 WREN 后 CS 拉高数据偶尔错误降低 SPI 时钟从 24MHz 降到 12MHz上电初始化失败增加上电延时延时 10ms 再初始化写保护误触发检查 WP 引脚电平写前拉高写后拉低DMA 传输不完成检查 DMA 通道配置确认 SPI 的 DMA 请求使能这张表我贴在工位上调试的时候对照着看能省不少时间。5. 方案扩展与个人经验总结5.1 从单机存储到多设备共享的扩展思路这套方案目前是单 MCU 配单 MRAM如果你需要多设备共享存储可以考虑用多个 CS 引脚挂多个 MRAM 到同一个 SPI 总线上。STM32F091RC 的 SPI1 支持多从机只要 CS 引脚够用就行。另一种扩展方式是换用更大容量的 MRAM比如 MR25H40CDF 的升级型号 MR25H256容量 256Kbit引脚兼容驱动不用改。如果你需要更大容量可以考虑并行的 MRAM但那就不是 SPI 接口了得换方案。5.2 我在这个项目里踩过的坑第一个坑是 WREN 命令的 CS 时序。我一开始以为 WREN 和 WRITE 可以连续发中间不用拉高 CS。结果写进去的数据全是错的。后来查手册才发现WREN 是一个独立命令发完之后 CS 必须拉高否则 WRITE 命令不会被执行。第二个坑是 WP 引脚的上电默认状态。MR25H40CDF 的 WP 是低电平有效但 STM32F091RC 的 GPIO 上电默认是浮空输入WP 引脚的电平不确定。我在初始化的时候先把 WP 配成推挽输出并拉低确保上电后写保护是生效的。第三个坑是 SPI 的 CPHA 配置。CubeMX 里 CPHA 的选项是“1 Edge”和“2 Edge”我一开始选了“2 Edge”结果读出来全是 0xFF。后来改成“1 Edge”就正常了。MR25H40CDF 支持 Mode 0 和 Mode 3Mode 0 对应 CPOL0、CPHA1 Edge。5.3 给准备上手的同行的几点建议如果你正准备用 MR25H40CDF 做项目我的建议是先把 SPI 通信调通读 ID 确认无误后再写数据。写数据的时候一定要加 WP 控制不要图省事直接接地。工业现场电磁干扰大SPI 时钟不要超过 12MHz走线尽量短必要时加串联电阻匹配。另外MRAM 虽然写入寿命无限但读取次数也是无限的不用担心读多了会坏。这一点比 Flash 强Flash 读多了会有读干扰问题MRAM 没有。最后说一句MR25H40CDF 的价格比同容量 Flash 贵不少但如果你的项目对数据可靠性要求高这个钱花得值。我算过一笔账一个工业设备用 Flash 方案三年内因为数据丢失导致的售后成本足够买几十颗 MRAM 了。
返回列表