ARTICLE DETAIL

资讯详情

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

STM32F303VE 驱动 MR25H40CDF MRAM 工业存储实战

STM32F303VE 驱动 MR25H40CDF MRAM 工业存储实战 1. 项目缘起与方案选型思考1.1 为什么要在工业场景里折腾 MRAM 这颗料做工业嵌入式这行的朋友应该都有个共识数据存储这块选型选得好后面少掉一半头发。我这些年做过不少数据采集和参数保存的项目EEPROM 擦写寿命不够、Flash 掉电写入有风险、FRAM 容量小价格高这些坑基本都踩过一遍。直到接触到MR25H40CDF这颗磁阻随机存储器MRAM才算找到一个在工业环境下比较省心的方案。MR25H40CDF 是 Everspin 家的 4Mbit 串行 MRAM走的是标准SPI 接口最高时钟能跑到 40MHz。它最大的特点就是写入不需要擦除、没有写入延迟、擦写寿命几乎无限官方标称 10^14 次以上、掉电数据不丢。这几点放在工业现场简直就是救命稻草。你想想一个电表或者工控设备可能每分钟都在记录运行参数用 EEPROM 的话几年下来擦写次数就逼近上限了而 MRAM 根本不用担心这个问题。这次我用的主控是STM32F303VECortex-M4 内核72MHz 主频带 FPU片上资源对于这种存储读写任务来说绰绰有余。选它主要是因为它有多个 SPI 外设而且 HAL 库成熟开发效率高。整个项目的目标很明确用 STM32F303VE 通过 SPI 总线稳定地对 MR25H40CDF 进行数据的存储和读取并且要能扛住工业现场的电磁干扰和频繁读写。1.2 硬件连接方案与引脚分配先说说硬件怎么接。MR25H40CDF 是 8 脚封装引脚定义很标准VCC、GND、CS片选、SCK时钟、SI数据输入、SO数据输出、WP写保护、HOLD保持。实际用的时候WP 和 HOLD 如果不需要硬件写保护和暂停传输功能直接拉高就行。我这边用的是 STM32F303VE 的 SPI1引脚分配如下STM32 引脚功能MR25H40CDF 引脚PA5SPI1_SCKSCKPA6SPI1_MISOSOPA7SPI1_MOSISIPA4GPIO 输出CS3.3V电源VCCGND地GND3.3V拉高WP3.3V拉高HOLD这里有个细节要注意CS 片选我用的是普通 GPIO 而不是 SPI 硬件 NSS。原因很简单硬件 NSS 在多从机场景下容易出问题而且 STM32 的硬件 NSS 有时候行为不太符合预期用软件控制片选更灵活时序也更好把控。这个在后面的代码里会体现出来。提示MR25H40CDF 的供电范围是 2.7V 到 3.6V和 STM32F303VE 的 3.3V 系统完美匹配不需要额外电平转换。但如果你用的是 5V 系统一定要加电平转换芯片否则会烧掉 MRAM。1.3 SPI 模式与通信参数确认MR25H40CDF 支持 SPI 模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。我一般习惯用模式 0因为大部分 SPI 设备默认都是这个模式调试起来方便。时钟频率方面虽然芯片支持 40MHz但实际跑的时候要考虑 PCB 走线质量和线缆长度。我这次板子上 SPI 走线大概 5cm用的是 10MHz实测非常稳定。如果你走线比较长或者环境干扰大建议降到 5MHz 甚至更低。STM32F303VE 的 SPI1 挂在 APB2 总线上时钟源是 72MHz。要得到 10MHz 的 SCK分频系数设为 8 就行72/89MHz接近 10MHz。HAL 库里的配置是hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_8。这个分频值不用死记用 CubeMX 配置的时候直接选就行它会自动算好。2. MR25H40CDF 核心操作指令解析2.1 指令集概览与常用命令MR25H40CDF 的指令集不算复杂但有几个关键命令必须记牢。我整理了一个常用指令表方便大家查阅指令名称指令码功能说明WREN0x06写使能写入前必须发送WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据RDID0x9F读设备 ID这里重点说几个容易踩坑的地方。首先是WREN每次写操作之前都必须发一次否则写不进去。这个和 EEPROM 一样但很多人第一次用 MRAM 会忘记。其次是RDSR状态寄存器的 bit0 是 WIPWrite In Progress写操作之后要轮询这个位直到它变 0 才能进行下一次操作。虽然 MRAM 写入很快但状态机还是需要一点时间。RDID 指令读出来的数据可以用来验证通信是否正常。MR25H40CDF 的 Device ID 是 0x18后面跟着厂商代码。如果你读出来全是 0xFF 或者 0x00那基本就是接线有问题或者 SPI 配置不对。2.2 读写时序的关键细节MR25H40CDF 的读时序是这样的拉低 CS发送 0x03然后发送 3 个字节的地址24 位地址因为 4Mbit 512KB需要 19 位地址但芯片要求发 3 字节接着就可以连续读出数据了。地址会自动递增所以你可以一次性读一大片数据不用反复发指令。写时序稍微复杂一点拉低 CS发送 0x06WREN拉高 CS。然后再拉低 CS发送 0x02发送 3 字节地址然后发送要写的数据。注意 WREN 和 WRITE 之间必须拉高 CS这是很多人容易忽略的地方。我刚开始调的时候就是 WREN 之后直接发 WRITE结果死活写不进去查了半天手册才发现这个问题。注意WREN 指令之后必须拉高 CS 再拉低否则 WRITE 指令不会被识别。这个细节在数据手册的时序图里画得很清楚但文字描述里不太显眼容易看漏。另外MR25H40CDF 支持页写入模式一页是 256 字节。如果你要写的数据超过 256 字节地址会回卷到页首覆盖之前的数据。所以写跨页数据的时候要么分多次写要么自己处理好地址边界。我一般是在驱动层做一个封装自动处理跨页写入上层应用就不用操心了。2.3 状态寄存器与写保护机制状态寄存器这块MR25H40CDF 提供了块保护功能。状态寄存器的 bit2 到 bit4 是 BP0、BP1、BP2用来设置哪些区域是写保护的。默认情况下全是 0也就是整个存储区都可写。如果你有一些关键数据不想被误写可以设置块保护。不过在实际项目中我一般不用硬件块保护而是通过软件逻辑来管理。原因很简单硬件块保护一旦设置要改回来还得先发 WRSR 指令流程比较繁琐。而且工业设备通常有自己的一套参数管理机制软件层面控制更灵活。状态寄存器的 bit0 是 WIP 位这个必须关注。每次写操作之后要轮询 RDSR 直到 WIP 变 0。虽然 MRAM 写入速度是纳秒级的但芯片内部的状态机还是需要几个时钟周期来切换。我实测下来写一个字节之后大概等 1 微秒左右 WIP 就清零了。为了保险起见我在驱动里加了超时机制如果 1 毫秒还没清零就报错。3. STM32F303VE 驱动实现全流程3.1 CubeMX 配置与 SPI 初始化先说说 CubeMX 里的配置。打开 CubeMX选好 STM32F303VE 芯片然后配置 SPI1ModeFull-Duplex MasterHardware NSS SignalDisableData Size8 BitsFirst BitMSB FirstPrescaler8Clock PolarityLowClock Phase1 EdgeCRC CalculationDisabled这样配置出来就是 SPI 模式 010MHz 时钟。然后配置 PA4 为 GPIO Output初始电平设为 HighCS 默认拉高。生成代码之后HAL 库会自动初始化好 SPI 和 GPIO。这里有个小技巧我习惯在 CubeMX 里把 SPI 的 GPIO 速度设为 Very High这样信号边沿更陡峭高速通信时波形更好看。虽然 10MHz 不算特别高但养成好习惯没坏处。3.2 底层驱动函数编写接下来是驱动层。我一般会写几个基础函数MRAM_WriteEnable、MRAM_ReadStatus、MRAM_WaitForWriteEnd、MRAM_Read、MRAM_Write。这些函数封装了底层的 SPI 收发和 CS 控制。先看写使能函数void MRAM_WriteEnable(void) { MRAM_CS_LOW(); uint8_t cmd 0x06; HAL_SPI_Transmit(hspi1, cmd, 1, 100); MRAM_CS_HIGH(); }很简单就是拉低 CS发 0x06拉高 CS。注意这里的MRAM_CS_LOW()和MRAM_CS_HIGH()是我自己定义的宏对应 PA4 的输出电平控制。读状态寄存器uint8_t MRAM_ReadStatus(void) { uint8_t status; uint8_t cmd 0x05; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_SPI_Receive(hspi1, status, 1, 100); MRAM_CS_HIGH(); return status; }等待写入完成void MRAM_WaitForWriteEnd(void) { uint32_t timeout 0; while ((MRAM_ReadStatus() 0x01) (timeout 1000)) { timeout; HAL_Delay(1); } }这个超时设的是 1 毫秒实际用下来基本不会超时但加上保险。读数据函数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, 100); HAL_SPI_Receive(hspi1, buf, len, 1000); MRAM_CS_HIGH(); }写数据函数void MRAM_Write(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; cmd[0] 0x02; cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; MRAM_WriteEnable(); MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 4, 100); HAL_SPI_Transmit(hspi1, buf, len, 1000); MRAM_CS_HIGH(); MRAM_WaitForWriteEnd(); }这几个函数就是整个驱动的基础。实际用的时候我会在MRAM_Write里加上跨页处理逻辑把长数据拆成不超过 256 字节的块来写。3.3 跨页写入与地址管理前面提到过MR25H40CDF 一页是 256 字节写跨页数据时地址会回卷。所以我在驱动层加了一个自动分页的逻辑void MRAM_WriteSafe(uint32_t addr, uint8_t *buf, uint32_t len) { uint32_t pageRemain 256 - (addr % 256); uint32_t writeLen; while (len 0) { writeLen (len pageRemain) ? len : pageRemain; MRAM_Write(addr, buf, writeLen); addr writeLen; buf writeLen; len - writeLen; pageRemain 256; } }这个函数会先算出当前地址距离页尾还有多少字节然后按这个长度分段写入。这样就不用担心跨页问题了。实测下来写 1KB 数据大概耗时 2 毫秒左右包括状态轮询的时间速度完全够用。实操心得如果你要写的数据量很大比如几百 KB建议分块写每块之间加一点延时或者让出 CPU避免长时间占用 SPI 总线影响其他任务。我在一个项目里用 RTOS写大块数据的时候会在每页之间调用osDelay(1)效果很好。4. 工业场景下的可靠性设计与实测4.1 数据校验与冗余存储策略工业现场最怕的就是数据出错。MRAM 本身可靠性很高但 SPI 通信过程中可能受到干扰导致位翻转。所以我在数据存储上加了 CRC 校验和双备份机制。具体做法是每个数据块存储两份地址间隔开每份数据后面跟一个 CRC16 校验值。读取的时候两份都读出来分别校验哪份通过就用哪份。如果两份都通过就比对是否一致不一致的话用主份。如果两份都不通过就报错。这个策略听起来有点浪费空间但 512KB 的容量对于参数存储来说绰绰有余。我算过一笔账假设每个参数块 64 字节双备份加 CRC 也就 132 字节存几千个参数都没问题。CRC 计算我用的是 STM32 硬件 CRC 外设速度很快不占 CPU。配置 CubeMX 的时候把 CRC 使能就行HAL 库有现成的HAL_CRC_Calculate函数。4.2 掉电保护与写入原子性MRAM 的一大优势就是掉电不丢数据而且写入速度快基本不存在写到一半掉电导致数据损坏的问题。但为了万无一失我还是加了一个简单的写入原子性保护。思路是这样的在数据区前面留一个标志字节写入数据之前先把标志字节改成 0x00写完数据再改成 0xAA。读取的时候先看标志字节如果是 0xAA 才认为数据有效。这样即使写入过程中掉电标志字节还是 0x00下次上电就知道上次写入没完成可以丢弃或者恢复。这个机制在 EEPROM 上很常见但在 MRAM 上其实有点多余因为 MRAM 写入是纳秒级的掉电窗口极小。不过加上也不费事图个心安。4.3 实测数据与性能评估我在实验室里做了一轮实测条件如下STM32F303VE 主频 72MHzSPI 时钟 10MHz室温 25 度。测试项目包括连续写入 100KB 数据、随机读取 1000 次、以及反复擦写 100 万次。测试项目耗时备注连续写入 100KB约 210ms包括状态轮询随机读取 1000 次约 45ms每次读 16 字节擦写 100 万次无异常数据校验全部通过写入速度换算下来大概是 476KB/s读取速度更快。这个性能对于工业参数存储来说完全够用。擦写 100 万次之后数据依然正确MRAM 的寿命优势体现得很明显。注意测试擦写寿命的时候我是连续对同一地址进行写入每次写入后读回校验。100 万次下来芯片温度几乎没有变化功耗也很低。如果是 EEPROM这个测试早就把芯片写废了。5. 常见问题排查与避坑指南5.1 通信失败问题速查表调试 SPI 设备最常见的就是通信失败。我整理了一个速查表按概率从高到低排列现象可能原因排查方法读出来全是 0xFFMISO 没接好或 CS 没拉低检查接线用示波器看 CS 波形读出来全是 0x00MOSI 没接好或时钟没输出检查 SCK 和 MOSI 波形Device ID 不对SPI 模式或时钟极性错误确认 CPOL/CPHA 设置写入不成功忘记发 WREN 或 CS 时序不对检查 WREN 和 WRITE 之间 CS 是否拉高偶尔出错时钟太快或走线太长降低 SPI 时钟加屏蔽线这个表基本覆盖了 90% 的 SPI 调试问题。我遇到最多的就是 CS 时序问题特别是 WREN 和 WRITE 之间忘记拉高 CS这个坑我踩过不止一次。5.2 硬件设计注意事项PCB 设计这块也有几个要点。SPI 走线尽量短最好不超过 10cm。如果实在要走长线建议用屏蔽线或者加缓冲芯片。SCK 和 MOSI 是高速信号走线旁边最好包地减少干扰。电源去耦也很重要。MR25H40CDF 的 VCC 引脚旁边一定要放一个 0.1uF 的陶瓷电容越近越好。我见过有人省这个电容结果通信偶尔出错查了半天才发现是电源纹波太大。另外WP 和 HOLD 引脚如果不用直接拉高就行不要悬空。悬空的话可能引入干扰导致芯片误进入写保护或者保持状态。5.3 软件调试技巧与工具推荐调试 SPI 的时候逻辑分析仪是神器。我用的是 Saleae Logic 8能同时抓 CS、SCK、MOSI、MISO 四路信号时序一目了然。如果没有逻辑分析仪用示波器也行但只能看一两路信号效率低一些。软件层面我习惯在驱动里加一些调试打印。比如每次读写之后把地址、长度、返回值打印出来。这样一旦出错很容易定位是哪个环节的问题。当然正式发布的时候要把这些打印去掉或者用宏控制。还有一个技巧先用低速时钟比如 1MHz调通再逐步提高。这样如果高速出问题你就知道是时序或者信号完整性问题而不是逻辑问题。我调 SPI 设备基本都是这个套路先求通再求快。6. 项目扩展与个人经验分享6.1 从单机存储到数据管理框架这个项目做下来我觉得 MR25H40CDF 加 STM32F303VE 的组合很适合做工业设备的数据管理。后来我在这个基础上封装了一个小型的参数管理框架支持按 ID 读写参数、参数版本管理、默认值恢复等功能。上层应用只需要调用Param_Read(id, value)和Param_Write(id, value)就行底层的 MRAM 操作完全透明。这个框架的核心是一个参数表存在 MRAM 的固定区域。每个参数有 ID、类型、长度、当前值、默认值、CRC 校验。上电的时候把参数表加载到 RAM 里读写都在 RAM 里操作定期或者掉电前同步回 MRAM。这样既保证了速度又保证了可靠性。6.2 我踩过的那些坑说几个我实际踩过的坑希望大家能避开。第一个坑是 SPI 时钟相位搞错。MR25H40CDF 支持模式 0 和模式 3我一开始看手册没注意配成了模式 1结果读出来数据全是乱的。后来用逻辑分析仪抓波形才发现数据在时钟上升沿采样但芯片是在下降沿输出完全对不上。改成模式 0 之后立马就好了。第二个坑是 CS 控制用错了。我一开始想用 STM32 的硬件 NSS结果发现硬件 NSS 在多主机或者某些模式下行为很奇怪CS 信号拉低之后不自动拉高导致通信卡死。后来改成软件控制 GPIO问题就解决了。所以我现在做 SPI 项目一律用软件 CS省心。第三个坑是写数据之后忘记轮询 WIP。有一次写参数之后立马读回来发现数据没更新以为是芯片坏了。后来查手册才发现写操作之后要等 WIP 清零才能读。加上MRAM_WaitForWriteEnd之后问题消失。6.3 后续可以怎么玩这个项目的基础打好了后面可以扩展的方向很多。比如加一个文件系统把 MRAM 当成一个小型磁盘来用或者加一个日志系统记录设备的运行状态和故障信息再或者结合 RTC做带时间戳的数据记录。我还想过用 MRAM 做配置参数的掉电保存替代传统的 EEPROM。MRAM 的擦写寿命和写入速度都比 EEPROM 强太多虽然价格贵一点但在高端工业设备上完全值得。特别是那些需要频繁保存参数的应用比如电力监测、医疗设备、汽车电子MRAM 的优势非常明显。最后分享一个小技巧如果你手头没有 MR25H40CDF可以用 MR25H256 或者 MR25H10 来替代调试它们的指令集和引脚基本兼容只是容量不同。这样万一买不到 4Mbit 的型号也不耽误开发进度。
返回列表