STM32F745ZG 驱动 MR25H40CDF MRAM:SPI 配置与工业数据存储实战
发布时间:2026/10/5 20:43:39 作者:尧图编辑部 阅读量:1,286

1. 为什么 MRAM 在工业嵌入式场景里越来越受关注搞过工业设备数据记录的朋友应该都有体会选存储介质这件事看着简单实际上坑特别多。EEPROM 擦写寿命十万次频繁记录的场合撑不了多久NOR Flash 写入前必须擦除整个扇区掉电时机不对就丢数据FRAM 倒是好用但容量小、价格高大一点的型号不好买。这几年 MRAM 慢慢进入大家的视野MR25H40CDF 就是其中一颗很有代表性的芯片。MR25H40CDF 是 Everspin 出的 4Mbit 串行 MRAMSPI 接口512K 字节容量3.3V 供电工业级温度范围覆盖 -40 到 105 摄氏度。它最大的特点就是写入不需要擦除、没有写入延迟、擦写寿命理论上无限、掉电数据不丢。这几点加在一起对于工业数据记录、参数存储、日志缓存这类场景来说几乎是理想方案。我这次用的主控是 STM32F745ZGCortex-M7 内核216MHz 主频片上带 Quad-SPI 和多个普通 SPI 外设。选它是因为这个项目本身还要跑一些实时控制逻辑F7 的算力和外设资源都够用而且 STM32 的 HAL 库生态成熟SPI 驱动写起来快。整套方案的目标很明确用 MR25H40CDF 做非易失数据存储STM32F745ZG 通过 SPI 总线读写实现工业现场的参数保存和运行日志记录。这篇文章我会把整个链路拆开讲从 MRAM 的选型逻辑、SPI 硬件设计、CubeMX 配置、HAL 库驱动编写到实际调试中遇到的问题和排查方法。如果你手头正好有类似的存储需求或者单纯想了解一下 MRAM 怎么用这篇内容应该能帮你少走一些弯路。2. 方案整体设计与核心选型思路2.1 为什么选 MRAM 而不是 EEPROM 或 Flash先把这个事情说清楚因为选型选错了后面全是白费功夫。工业场景下的非易失存储常见选项就那么几个EEPROM、NOR Flash、FRAM、MRAM。我列个表对比一下这样更直观。特性EEPROMNOR FlashFRAMMRAM (MR25H40CDF)擦写寿命约100万次约10万次10^14次无限次写入前擦除不需要需要不需要不需要写入速度慢(ms级)慢(ms级)快(ns级)快(ns级)掉电保持10年10年10年20年容量范围小中小中接口I2C/SPISPII2C/SPISPI单位成本低低高中高从表里能看出来MRAM 在寿命和写入速度上优势明显。有人可能会问FRAM 不是也很快吗对但 FRAM 的容量通常偏小而且价格更贵供货也不如 MRAM 稳定。MR25H40CDF 的 512KB 容量对于大多数参数存储和日志记录场景已经够用了。还有一个很关键的点MRAM 的写入行为和普通 RAM 几乎一样你直接往地址里写数据就行不需要关心扇区边界、不需要先擦后写。这意味着驱动层可以做得非常简单不用维护复杂的擦除管理逻辑。对于工业设备这种要求长期稳定运行、维护成本高的场景简单就意味着可靠。2.2 STM32F745ZG 的 SPI 外设分配STM32F745ZG 有多个 SPI 接口SPI1 到 SPI6其中 SPI1/4/5/6 支持最高 50MHz 时钟SPI2/3 最高 25MHz。MR25H40CDF 的 SPI 最高支持 40MHz所以理论上用 SPI1 可以跑到比较高的速率。但实际选哪个 SPI 口不能只看速度。我当时的考虑是SPI1 通常被板上的其他高速外设占用比如某些传感器或显示屏需要确认引脚复用情况SPI2/3 速度够用而且引脚布局更方便走线如果后续要接多个 SPI 从设备需要提前规划片选引脚最后我选了 SPI2时钟配置到 20MHz 左右。为什么不上更高因为工业现场的 PCB 走线往往比较长信号完整性是个现实问题20MHz 在稳定性和速度之间是个比较好的平衡点。后面调试部分我会详细说这个取舍。2.3 硬件连接与片选设计MR25H40CDF 的引脚不多核心就是 SPI 四线加片选SCLKSPI 时钟SI数据输入MOSISO数据输出MISOCS片选低有效VDD3.3VVSS地片选这块有个细节值得说。STM32 的 SPI 外设支持硬件片选NSS和软件片选两种方式。硬件片选由 SPI 外设自动控制时序更精确但灵活性差多个从设备时管理麻烦。软件片选就是用普通 GPIO 手动拉低拉高时序由代码控制灵活但需要自己保证时序正确。我选的是软件片选。原因很简单工业板子上往往不止一个 SPI 从设备用软件片选可以自由管理多个设备的片选信号而且调试的时候更容易用逻辑分析仪抓波形。代价就是代码里要严格保证片选拉低和拉高的时机这个后面驱动部分会讲。硬件上还有两个容易忽略的点一是 CS 引脚最好加上拉电阻防止上电瞬间浮空导致误触发二是 SCLK 和 MOSI 走线如果比较长建议串接小电阻22到33欧姆做阻抗匹配减少过冲和振铃。3. MR25H40CDF 的 SPI 通信协议细节3.1 命令集与操作码MR25H40CDF 的 SPI 命令集比较简洁常用的就几条命令操作码功能WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据注意 WRITE 操作之前必须先发 WREN 命令这是很多 SPI 存储芯片的通用规则。MRAM 虽然写入不需要擦除但写保护机制还是有的WREN 就是用来解除写保护的。状态寄存器的 bit0 是 WELWrite Enable Latch发完 WREN 之后这位会置1写完数据后自动清零。调试的时候如果发现写不进去第一件事就是读状态寄存器看 WEL 位是不是1。3.2 读写时序分析读操作的时序是这样的CS 拉低发送 0x03然后发送 3 字节地址MR25H40CDF 是 19 位地址但用 3 字节传输高位补0接着时钟继续输出数据就从 SO 引脚出来了。可以连续读地址自动递增直到 CS 拉高。写操作的时序类似CS 拉低先发 WREN0x06CS 拉高然后 CS 再拉低发 WRITE0x02发 3 字节地址然后发数据。数据可以连续写地址自动递增。写完最后一个字节后 CS 拉高芯片内部开始写入周期。这里有个关键点MRAM 的写入是几乎无延迟的不像 Flash 需要等几毫秒的写入周期。但状态寄存器的 WEL 位在 CS 拉高后需要一点时间才能反映最新状态所以连续写操作之间建议稍微加一点延时或者每次写之前都重新发 WREN。3.3 SPI 模式选择MR25H40CDF 支持 SPI Mode 0 和 Mode 3。Mode 0 是 CPOL0、CPHA0时钟空闲低电平第一个边沿采样Mode 3 是 CPOL1、CPHA1时钟空闲高电平第二个边沿采样。我用的是 Mode 0这也是最常用的配置。在 CubeMX 里配置 SPI 的时候CPOL 和 CPHA 都设成 Low 就行。如果你发现读出来的数据全是 0xFF 或者乱码先检查一下模式设置对不对这是最常见的坑之一。4. STM32F745ZG 的 CubeMX 配置与驱动实现4.1 CubeMX 中的 SPI 参数配置打开 CubeMX选好 STM32F745ZG 之后找到 SPI2 进行配置。关键参数如下ModeFull-Duplex MasterData Size8 BitsClock PolarityLowClock Phase1 Edge也就是第一个边沿NSSSoftware软件片选Baud Rate Prescaler根据主频计算目标是 20MHz 左右First BitMSB First波特率这块说一下计算过程。STM32F745ZG 的 APB1 总线时钟我配置的是 54MHzSPI2 挂在 APB1 上。分频系数可选 2、4、8、16、32、64、128、256。54MHz 除以 4 等于 13.5MHz除以 2 等于 27MHz。27MHz 虽然没超过 MR25H40CDF 的 40MHz 上限但考虑到走线损耗我选了 4 分频实际时钟 13.5MHz。实测这个速率下读写都很稳定对于数据记录场景完全够用。另外别忘了配置一个 GPIO 输出引脚作为 CS 片选初始电平设为高。4.2 HAL 库驱动代码实现CubeMX 生成代码之后SPI 的初始化已经自动完成了。我们需要自己写的是 MRAM 的读写函数。下面是我实际用的代码基于 HAL 库。先定义片选操作宏#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)写使能函数void MRAM_WriteEnable(void) { uint8_t cmd 0x06; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi2, cmd, 1, 100); MRAM_CS_HIGH(); }读状态寄存器uint8_t MRAM_ReadStatus(void) { uint8_t cmd 0x05; uint8_t status 0; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi2, cmd, 1, 100); HAL_SPI_Receive(hspi2, status, 1, 100); MRAM_CS_HIGH(); return status; }写数据函数void MRAM_Write(uint32_t addr, uint8_t *data, uint16_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(hspi2, cmd, 4, 100); HAL_SPI_Transmit(hspi2, data, len, 1000); MRAM_CS_HIGH(); }读数据函数void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_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(hspi2, cmd, 4, 100); HAL_SPI_Receive(hspi2, buf, len, 1000); MRAM_CS_HIGH(); }这段代码看起来简单但有几个地方容易出问题。第一HAL_SPI_Transmit 和 HAL_SPI_Receive 的超时参数要设够尤其是写操作如果超时太短可能返回 HAL_TIMEOUT。第二CS 拉高之后最好加一个微秒级的延时再执行下一次操作给芯片内部状态机一点恢复时间。第三地址是 19 位的但用 3 字节传输高位补0就行MR25H40CDF 会自动忽略超出容量的地址位。4.3 读写验证与测试方法驱动写完之后第一步肯定是做基本的读写测试。我的做法是往地址 0x00000 写一串已知数据比如 0xAA、0x55、0x01、0x02 这样然后读回来对比。如果一致再测试跨页写入、连续读写、边界地址等场景。这里有个小技巧测试数据不要全用 0x00 或 0xFF因为这两种数据在 SPI 通信异常时最容易出现MISO 一直低或一直高用交替的 pattern 更容易发现问题。我一般会写一个简单的测试函数循环写入递增的数据然后读回来校验跑个几千次看看有没有偶发错误。这个测试能覆盖大部分硬件连接和时序问题。5. 实操过程中的问题排查与经验总结5.1 常见问题速查表在实际调试过程中我遇到和收集到的问题大概有这些现象可能原因排查方法读出来全是0xFFMISO未连接或CS未拉低检查硬件连接用逻辑分析仪抓CS和MISO读出来全是0x00MISO被拉低或SPI模式错误检查SPI模式设置确认CPOL/CPHA写入后读回数据不对WREN未发送或WEL位未置1读状态寄存器确认WEL位偶发数据错误时钟太快或走线干扰降低SPI时钟检查PCB走线完全无响应供电异常或芯片损坏测量VDD电压检查焊接写入成功但掉电丢失写保护引脚状态错误检查WP引脚电平5.2 逻辑分析仪抓波形的重要性调试 SPI 设备逻辑分析仪几乎是必备工具。我用的是一款入门级的 8 通道逻辑分析仪配合开源软件就能解码 SPI 协议。抓波形的时候重点看几个东西CS 拉低到第一个时钟边沿的建立时间够不够一般要求至少几纳秒实际看波形只要不是同时变化就行。时钟极性对不对Mode 0 下空闲应该是低电平。数据在时钟的哪个边沿变化、哪个边沿采样这个和 CPHA 设置直接相关。最后一个字节发完之后 CS 拉高的时机太早可能导致最后一个字节没发完太晚可能影响下一次操作。有一次我遇到读数据偶尔错一位的情况抓波形发现是 CS 拉高的时机比最后一个时钟边沿早了大概 10ns导致最后一个 bit 没被正确采样。后来在 CS 拉高之前加了一个 __NOP() 就好了。这种问题不看波形根本找不到。5.3 工业现场的可靠性加固实验室里跑通不代表现场能用。工业环境的电磁干扰、温度变化、电源波动都比实验室恶劣得多。我在实际项目中做了这几件事来加固电源端加 0.1uF 和 10uF 的退耦电容尽量靠近 MRAM 的 VDD 引脚。SPI 信号线上串接 33 欧姆电阻减少反射。CS 信号加上拉电阻防止浮空。PCB 布局上MRAM 尽量靠近主控走线短而直避免和功率线路平行走。软件层面每次写入之后都读回来校验如果校验失败就重试重试三次还失败就记录错误标志。这个机制在实际运行中确实抓到过几次偶发的写入异常虽然概率很低但工业设备要求长期无人值守运行这种保护是必要的。另外MRAM 虽然写入寿命无限但读操作理论上也有次数限制虽然极高所以不要在高频循环里反复读同一地址该用缓存的地方还是用缓存。6. 数据存储结构设计与实际应用场景6.1 存储区域划分512KB 的空间看着不大但规划好了能存不少东西。我的划分方式是前 4KB 作为配置参数区存储设备ID、校准参数、运行模式等。中间 252KB 作为日志区循环写入运行日志。最后 256KB 作为数据记录区存储采集到的历史数据。配置参数区每次修改都直接写入因为 MRAM 不需要擦除改哪个字节就写哪个字节非常方便。日志区用环形缓冲的方式管理写满之后从头覆盖。数据记录区按固定大小的记录块存储每条记录带时间戳和校验和。6.2 掉电保护策略MRAM 的一大优势就是掉电数据不丢但前提是数据已经写进去了。如果掉电发生在写入过程中可能会造成部分数据不完整。我的做法是每条记录写入之前先写一个标志字节表示正在写入写完之后再改成写入完成。上电初始化的时候扫描记录区遇到正在写入状态的记录就标记为无效。这个机制配合 MRAM 的快速写入特性实际上掉电丢失数据的窗口非常小基本可以忽略不计。相比 Flash 需要几毫秒的写入周期MRAM 的写入是纳秒级的掉电时正在写入的概率极低。6.3 与文件系统的配合使用如果数据量比较大需要更复杂的管理可以考虑在 MRAM 上跑一个轻量级文件系统比如 LittleFS 或者 SPIFFS。这两个都是为嵌入式设计的支持掉电保护、磨损均衡虽然 MRAM 不需要、目录结构。不过对于大多数工业参数存储场景直接按地址读写反而更简单可靠。文件系统会引入额外的复杂度和开销除非你确实需要文件管理功能否则没必要上。我个人的经验是数据量小于 100KB、结构固定的话直接地址映射最省事数据量大、结构灵活的话再考虑文件系统。7. 性能实测与优化建议7.1 读写速度实测数据我在 13.5MHz SPI 时钟下做了实际测试结果如下操作数据量耗时等效速率单字节写1字节约2us-连续写1KB约620us约1.65MB/s连续读1KB约610us约1.68MB/s单字节读1字节约2us-这个速度对于工业数据记录来说完全够用。如果你需要更高的吞吐量可以把 SPI 时钟提到 27MHz 甚至更高但要注意信号完整性问题。我试过 27MHz在短走线的板子上没问题但走线超过 10cm 之后误码率明显上升。7.2 DMA 传输的优化如果 CPU 需要同时处理其他任务可以用 DMA 来搬运 SPI 数据。STM32F745ZG 的 SPI2 支持 DMA 请求配置好 DMA 通道之后读写操作可以后台完成CPU 只需要处理完成中断。不过对于小数据量的读写比如几十字节DMA 的配置开销可能比直接传输还大。我的建议是数据量超过 256 字节再考虑 DMA小数据量直接阻塞传输更简单。7.3 实际项目中的经验参数最后分享几个我在实际项目中总结的参数可以直接参考SPI 时钟13.5MHz稳定优先或 27MHz速度优先需验证信号质量CS 建立时间拉低后至少延时 100ns 再发时钟CS 保持时间最后一个时钟后至少延时 100ns 再拉高写操作重试次数3次读校验每次写入后立即读回校验退耦电容0.1uF 10uF 并联靠近芯片引脚这套参数在我做的几个工业项目里都跑得很稳现场运行一年多没有出现过存储相关的故障。当然具体项目还要根据实际情况调整尤其是 SPI 时钟一定要用逻辑分析仪或者示波器确认波形质量之后再定。