拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32F745VG驱动MR25H40CDF MRAM:SPI配置与工业存储实战

STM32F745VG驱动MR25H40CDF MRAM:SPI配置与工业存储实战

1. 为什么工业现场还在用 MRAM,而不是继续堆 Flash

如果你拆过工业网关、PLC 扩展模块或者电力监测终端,大概率会在板子上看到一颗 8 脚的小芯片,旁边紧挨着一颗 STM32 或者类似的 MCU。过去十年,这个位置基本被 SPI Flash 和 EEPROM 占据,但从 2020 年前后开始,越来越多的设计开始换成 MRAM,MR25H40CDF 就是其中出镜率很高的一颗。

先说清楚它是什么。MR25H40CDF 是 Everspin 推出的 4Mbit(512K×8)磁性随机存储器,SPI 接口,工作电压 2.7V~3.6V,工业级温度范围 -40℃~85℃,DFN-8 封装。它的核心卖点不是容量,而是三个字:非易失加无限次写。Flash 的擦写寿命通常在 10 万次量级,EEPROM 好一点也就 100 万次,而 MRAM 的写寿命是 10^14 次以上,基本可以理解为"随便写"。

这就引出一个很现实的问题:工业设备里到底什么数据需要频繁写?举几个我实际遇到的场景。第一类是掉电前的状态快照,设备检测到母线电压跌落,需要在几毫秒内把当前运行参数、故障码、累计计数写进非易失存储,下次上电恢复。第二类是实时数据记录,比如每 100ms 记录一次温度、压力、流量,一天就是 86 万次写入,Flash 撑不过几个月。第三类是参数配置的频繁修改,产线调试阶段工程师反复改阈值,一天改几百次很正常。

用 Flash 做这些事,你要么加磨损均衡算法,要么外挂一颗超级电容做掉电保护,要么干脆用铁电存储器 FRAM。但 FRAM 容量小、价格高,而且很多型号已经停产。MRAM 在这个夹缝里就成了一个很务实的选择:容量够用、接口简单、写入不需要擦除、掉电数据不丢、寿命几乎无限。

STM32F745VG 这边,Cortex-M7 内核,216MHz 主频,1MB Flash,320KB RAM,带多个 SPI 外设,其中 SPI1 挂在 APB2 上,最高时钟可以到 108MHz(分频后实际可用 54MHz 左右)。用它来驱动 MR25H40CDF,硬件上只需要四根线:SCK、MISO、MOSI、CS,外加电源和地。软件上,HAL 库的 SPI 驱动已经足够,但有几个坑必须提前说清楚,否则你会遇到"读出来全是 0xFF"或者"写进去读出来不对"这类经典问题。

这篇文章不打算写成数据手册的中文翻译,而是把我从选型、画板、写驱动到实测踩坑的完整过程拆开讲。无论你是刚接触 SPI 存储的嵌入式新手,还是正在为工业项目选存储方案的老手,应该都能从里面找到能直接用的东西。

2. MR25H40CDF 的 SPI 时序里,数据手册没明说的几个细节

2.1 模式 0 和模式 3 都能用,但别混着用

MR25H40CDF 支持 SPI 模式 0(CPOL=0,CPHA=0)和模式 3(CPOL=1,CPHA=1)。数据手册上写得很简单,但实际配置的时候,STM32 的 HAL 库初始化结构体里有个CLKPolarity和CLKPhase,很多人随手填了SPI_POLARITY_LOW和SPI_PHASE_1EDGE,结果发现读 ID 能读出来,读写数据就出错。

原因在于 MRAM 的时序对建立时间和保持时间比较敏感。模式 0 下,数据在 SCK 上升沿采样,下降沿输出;模式 3 下,上升沿输出,下降沿采样。两种模式本身都没问题,但如果你在 PCB 上走了比较长的 SPI 走线,或者 SCK 频率拉得比较高,模式 0 的采样窗口会更窄一些。我的经验是:低速调试用模式 0,高速跑用模式 3。STM32F745 的 SPI 在 20MHz 以上时,模式 3 的波形更干净。

配置代码大概是这样:

hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH; // 模式3 hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; // 软件片选 hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 108/4=27MHz hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;

这里NSS我强烈建议用软件片选。硬件片选在 STM32 上有个老问题:当 SPI 使能后,NSS 引脚的行为有时候不受控,尤其是在多主或者片选需要精确控制时序的场景下。软件片选就是随便找一个 GPIO,手动拉低拉高,时序完全由你掌控。

2.2 写操作之后的 tWRL 等待时间

MRAM 和 Flash 最大的区别之一是写入速度。Flash 写一个字节要几毫秒到几十毫秒,MRAM 写一个字节基本就是 SPI 时钟周期的时间,纳秒级。但数据手册里有一个参数叫tWRL,意思是写操作结束后到下一次操作开始之间的最小间隔,典型值是 0ns,最大值标的是 15ns。

15ns 是什么概念?STM32F745 在 216MHz 下,一个指令周期大约 4.6ns,也就是说你写完最后一个字节,拉高 CS,再拉低 CS 开始下一次操作,中间只要隔了三条指令以上,时间就足够了。所以实际写代码的时候,你不需要刻意加延时。但如果你用的是 DMA 传输,传输完成中断里立刻发起下一次传输,中间的中断响应时间可能只有几百纳秒,也远远大于 15ns,所以也不用担心。

真正需要注意的是页写边界。MR25H40CDF 支持页写,一页 256 字节。如果你从地址 0x0000FF 开始写 4 个字节,它会写到 0x0000FF、0x000100、0x000101、0x000102,还是回卷到 0x000000?答案是回卷。这是很多 SPI 存储器的通病,MRAM 也不例外。所以写跨页数据的时候,要么手动分页,要么保证你的写入起始地址和长度对齐。

2.3 状态寄存器的写保护位

MR25H40CDF 有一个状态寄存器,里面有 BP0、BP1、WEL、WIP 等位。WEL 是写使能锁存,每次写操作前必须先发 WREN 指令把 WEL 置 1,写完之后 WEL 自动清零。这个流程和 Flash 一样,但 MRAM 没有擦除操作,所以不需要发 WREN 之后再发擦除指令。

BP0 和 BP1 是块保护位,可以保护部分地址区域不被写入。工业项目里我一般会把配置参数区保护起来,防止程序跑飞误写。但注意,块保护一旦设置,解除保护也需要发 WREN,而且保护粒度是 1/4、1/2 或者整片,不是任意地址范围。所以如果你的参数区和数据区混在一起,块保护就不太好用,得靠软件层面的校验来兜底。

3. STM32F745VG 的 SPI 外设配置:从 CubeMX 到寄存器级调优

3.1 CubeMX 里那几个容易点错的选项

用 STM32CubeMX 配置 SPI1 的时候,界面上有一堆选项,我见过太多人在这里翻车。第一个是Baud Rate Prescaler,CubeMX 会显示实际时钟频率,比如你选 4 分频,它显示 27MBits/s。但注意,这个频率是理论最大值,实际能不能跑上去,取决于你的 PCB 走线、MRAM 芯片的响应速度、以及有没有其他设备挂在同一条总线上。

MR25H40CDF 的最高 SPI 时钟是 40MHz,所以 27MHz 是安全的。但如果你同时挂了别的 SPI 设备,比如一个最高只支持 10MHz 的 ADC,那你就得把时钟降下来,或者用不同的 SPI 外设分开挂。

第二个是Data Size,选 8 位。MRAM 是字节寻址的,8 位最自然。虽然 STM32 支持 16 位数据帧,但 MRAM 的指令集是按字节设计的,用 16 位反而要拆包,得不偿失。

第三个是First Bit,选 MSB First。MRAM 的指令和地址都是高位在前,这个不能错。

第四个是CRC Calculation,除非你有特殊需求,否则关掉。开了之后 SPI 硬件会自动算 CRC,但 MRAM 不支持 CRC 校验,多出来的字节会被当成数据写进去,直接导致数据错乱。

3.2 软件片选的 GPIO 速度配置

软件片选用一个普通 GPIO 就行,比如 PA4。但 GPIO 的输出速度要配置成GPIO_SPEED_FREQ_VERY_HIGH。为什么?因为 CS 的拉低和拉高时机直接影响 SPI 时序。如果 GPIO 翻转太慢,CS 拉低之后 SCK 已经开始跳变了,MRAM 会漏掉第一个时钟沿,读出来的数据就整体偏移一位。

我实测过,默认的GPIO_SPEED_FREQ_LOW在 27MHz SPI 下,CS 的上升沿和下降沿会有明显的圆角,用示波器看大概有 20ns 的过渡时间。改成VERY_HIGH之后,过渡时间降到 5ns 以内,波形干净很多。

另外,CS 的操作顺序也有讲究。正确的流程是:

  1. 拉低 CS
  2. 发送指令字节
  3. 发送地址(如果是读写操作)
  4. 发送或接收数据
  5. 拉高 CS

注意第 1 步和第 2 步之间不要加延时,越紧凑越好。第 5 步拉高 CS 之后,如果要发起下一次操作,中间至少隔一个 SCK 周期的时间,这个 HAL 库会自动处理,不用手动加。

3.3 DMA 传输在 MRAM 读写中的实际收益

STM32F745 的 SPI1 支持 DMA,用 DMA 读写 MRAM 可以解放 CPU,尤其是在批量读写的时候。但这里有个坑:MRAM 的读操作是"发指令+地址,然后连续读数据",写操作是"发写指令+地址,然后连续写数据"。用 DMA 的时候,发送和接收是分开配置的,读操作需要先发指令和地址,再启动接收 DMA。

我的做法是封装两个函数:MRAM_Read和MRAM_Write。读的时候,先用阻塞方式发指令和地址,然后启动 DMA 接收;写的时候,把指令、地址、数据拼成一个缓冲区,一次性用 DMA 发出去。这样比纯阻塞方式快很多,实测读 512 字节,阻塞方式大概 200us,DMA 方式只要 60us 左右。

但 DMA 也有代价:代码复杂度上升,调试难度增加。如果你的项目对读写速度要求不高,比如只是上电读一次配置、掉电写一次状态,那用阻塞方式完全够用,没必要上 DMA。我一般是在需要连续记录数据的场景才用 DMA,比如每 1ms 记录一次传感器数据。

4. 驱动层封装:把 MRAM 当成一个可靠的参数存储区

4.1 指令集其实只有六条

MR25H40CDF 的指令集非常精简,常用的就六条:

指令名称指令码功能说明
WREN0x06写使能,写操作前必须发
WRDI0x04写禁止,一般不用
RDSR0x05读状态寄存器
WRSR0x01写状态寄存器
READ0x03读数据
WRITE0x02写数据

没有擦除指令,没有扇区擦除,没有整片擦除。这是 MRAM 和 Flash 最大的区别,也是它用起来最舒服的地方。你不需要维护擦除计数,不需要做磨损均衡,直接写就行。

读数据的流程:拉低 CS,发 0x03,发 24 位地址(MR25H40CDF 是 19 位地址,但按 24 位发,高位补 0),然后连续读 N 个字节,拉高 CS。

写数据的流程:拉低 CS,发 0x06(WREN),拉高 CS;再拉低 CS,发 0x02,发 24 位地址,发 N 个字节数据,拉高 CS。

注意 WREN 是一个独立的操作,必须单独拉低再拉高 CS。有些 SPI 设备允许 WREN 和 WRITE 在同一个 CS 周期内完成,但 MR25H40CDF 不行,数据手册里明确写了 WREN 之后要拉高 CS。

4.2 地址对齐与页边界处理

前面提到页写回卷的问题,实际写代码的时候,我一般会封装一个MRAM_WriteBuffer函数,内部自动处理跨页:

void MRAM_WriteBuffer(uint32_t addr, uint8_t *buf, uint32_t len) { while (len > 0) { uint32_t page_remain = 256 - (addr % 256); uint32_t write_len = (len < page_remain) ? len : page_remain; MRAM_WritePage(addr, buf, write_len); addr += write_len; buf += write_len; len -= write_len; } }

这个函数逻辑很简单,但能避免 90% 的写入错误。我见过一个项目,工程师直接写 300 字节,结果后 44 字节覆盖了前面的数据,查了两天才发现问题。

读操作没有页边界问题,可以连续读整片,地址自动递增,读到 0x7FFFF 之后会回卷到 0x00000。但一般不会读满,除非你在做整片校验。

4.3 数据完整性校验:CRC 还是双备份

MRAM 本身可靠性很高,但 SPI 传输过程中可能受干扰,尤其是工业现场电机启停、继电器动作的时候,SPI 线上会有毛刺。我遇到过几次读出来的配置参数莫名其妙变了,后来查出来是 SPI 走线太长,没有加屏蔽,旁边就是 24V 继电器。

解决办法有两个:一是硬件上缩短走线、加地线隔离、必要时加磁珠;二是软件上加校验。校验方式我推荐 CRC16,计算量小,检错率高。存储的时候,数据后面跟两个字节的 CRC;读取的时候,先读数据,再算 CRC,对比一致才使用。

如果对可靠性要求极高,可以用双备份:把同一份数据写到两个不同的地址区,读取的时候两份都读出来对比,一致才用,不一致就用默认值并报警。这种方式占用双倍空间,但 512Kbit 的容量对于参数存储来说绰绰有余。

5. 实测中遇到的三个典型问题与排查过程

5.1 读出来全是 0xFF:从 CS 时序查到电源

第一次调试的时候,STM32 发完读指令,读回来的数据全是 0xFF。0xFF 在 SPI 里通常意味着 MISO 线一直是高电平,要么是从设备没响应,要么是 MISO 没接好。

排查步骤是这样的:

第一步,用示波器看 CS、SCK、MOSI、MISO 四根线。CS 拉低正常,SCK 有波形,MOSI 有数据,MISO 一直是高电平。说明 STM32 这边没问题,问题在 MRAM 那边。

第二步,检查 MRAM 的电源。万用表量 VCC 引脚,3.3V 正常。但用示波器看纹波,发现上电瞬间有比较大的过冲,而且稳态下纹波有 100mV 左右。MRAM 对电源纹波比较敏感,后来在 VCC 和 GND 之间加了一个 0.1uF 和一个 10uF 的电容,纹波降到 20mV 以内。

第三步,检查 CS 的极性。MR25H40CDF 的 CS 是低有效,我一开始看数据手册的时候没注意,以为和某些 Flash 一样是高有效,结果 CS 一直拉高,MRAM 根本不响应。改成低有效之后,读 ID 正常了。

第四步,检查 SPI 模式。前面配置的是模式 0,但实际波形显示 SCK 空闲是高电平,应该用模式 3。改成模式 3 之后,读写都正常了。

这个问题折腾了大半天,核心原因就是三个:电源纹波、CS 极性、SPI 模式。任何一个错了,读出来都是 0xFF。

5.2 写入成功但读出来不对:地址发错了

第二个问题是写进去的数据,读出来只有第一个字节对,后面的全错。这种情况一般是地址发送的位数不对。

MR25H40CDF 的地址是 19 位,但 SPI 传输的时候按 24 位发,也就是 3 个字节。我一开始只发了 2 个字节,以为 16 位地址够了,结果 MRAM 把第三个字节当成了数据,整个数据流偏移了一个字节。

正确的地址发送方式:

uint8_t addr_buf[3]; addr_buf[0] = (addr >> 16) & 0xFF; addr_buf[1] = (addr >> 8) & 0xFF; addr_buf[2] = addr & 0xFF;

发完这三个字节,再发数据。读的时候也一样,发完 0x03 和三个地址字节,再读数据。

这个问题在逻辑分析仪上看得最清楚。抓一下 SPI 波形,数一数指令和地址的位数,一眼就能看出来。没有逻辑分析仪的话,用示波器看 MOSI 的波形也能大致判断,但不如逻辑分析仪直观。

5.3 高温下读写失败:温度范围和时序余量

第三个问题出现在老化测试阶段。常温下读写都正常,放到 85℃ 烘箱里跑了两小时,开始出现偶发的读写错误。错误率不高,大概每几千次出现一次,但工业产品不能接受这种概率。

排查下来,问题出在 SPI 时钟频率上。常温下 27MHz 没问题,但高温下 MRAM 的响应速度会变慢,建立时间和保持时间的余量变小。把 SPI 时钟降到 13.5MHz 之后,高温下连续跑 24 小时没有出现错误。

这件事给我的教训是:数据手册上的最高频率是常温下的典型值,工业级应用要留足余量。我现在的做法是,如果产品要求 -40℃~85℃ 全温区工作,SPI 时钟不超过芯片最高频率的 60%。MR25H40CDF 标称 40MHz,我一般跑到 20MHz 左右,高温下很稳。

另外,MRAM 的写入电流在高温下会略有增加,如果电源设计余量不足,也可能导致写入失败。所以电源芯片的选型要留至少 30% 的电流余量,电容要选温度特性好的 X7R 或 X5R,不要用 Y5V。

6. 工业场景下的数据存储策略:什么时候用 MRAM,什么时候不用

6.1 MRAM、FRAM、Flash、EEPROM 的选型对照

特性MRAM (MR25H40CDF)FRAMSPI FlashEEPROM
写寿命10^14 次10^14 次10^5 次10^6 次
写入速度纳秒级纳秒级毫秒级毫秒级
是否需要擦除否否是否
容量范围4Kbit~16Mbit4Kbit~1Mbit1Mbit~1Gbit1Kbit~2Mbit
接口SPI/QSPII2C/SPISPI/QSPII2C/SPI
价格中高高低低
工业温度-40~85℃-40~85℃-40~85℃-40~85℃

从表里能看出来,MRAM 的定位很明确:需要频繁写入、掉电不能丢、容量要求不大、预算允许的场景。如果你的数据是每天写几次的配置参数,用 EEPROM 就够了,没必要上 MRAM。如果是记录大量历史数据,比如几兆字节的日志,那还是得用 Flash 或者 SD 卡,MRAM 的容量和价格都不合适。

6.2 掉电保护的实际做法

工业设备最怕掉电丢数据。用 MRAM 做掉电保护,硬件上需要一个掉电检测电路,比如用比较器监测母线电压,低于阈值时触发 MCU 的外部中断。中断服务程序里,立刻把关键数据写进 MRAM。

这里的关键是中断响应时间和写入数据量的平衡。STM32F745 的中断响应大概十几个时钟周期,加上 SPI 传输 100 字节的时间(27MHz 下大约 30us),总共不到 50us。所以掉电检测的阈值要留够 100us 以上的余量,靠电源上的大电容撑住。

我一般会在电源输入端放一个 470uF 的电解电容,配合掉电检测,能撑住 5ms 左右。5ms 足够写 1KB 的数据了。如果数据量更大,要么加大电容,要么只写最关键的部分,其他数据靠定期备份。

6.3 多设备共享 SPI 总线的片选管理

一个 STM32F745 有多个 SPI 外设,但有时候引脚不够用,需要把多个设备挂在同一条 SPI 总线上,用不同的 CS 区分。这时候要注意几点:

第一,每个设备的 SPI 模式可能不同。比如 MRAM 用模式 3,某个 ADC 用模式 0,那你在切换设备的时候,必须重新配置 SPI 的 CPOL 和 CPHA。STM32 的 HAL 库允许运行时修改 SPI 配置,但修改之后要重新使能 SPI。

第二,每个设备的最高时钟不同。共享总线的时候,时钟要取所有设备中的最低值。或者你在访问不同设备时动态调整分频系数,但这样代码会复杂一些。

第三,CS 的默认状态要拉高。上电初始化的时候,先把所有 CS 引脚配置成推挽输出并拉高,然后再初始化 SPI。否则 SPI 使能的瞬间,CS 可能处于低电平,导致某个设备被误触发。

7. 从原理图到量产:几个容易忽略的硬件细节

7.1 去耦电容的位置比容量更重要

MR25H40CDF 的电源引脚旁边必须放一个 0.1uF 的陶瓷电容,这个大家都知道。但位置很关键:电容要尽量靠近 VCC 引脚,走线要短而粗,地线要直接打到地平面。我见过一个板子,电容放在芯片背面,过孔绕了一大圈,结果高频下等效电感太大,去耦效果大打折扣。

如果板子空间允许,再并一个 1uF 或 10uF 的电容,放在稍远一点的位置,负责低频纹波。0.1uF 管高频,10uF 管低频,搭配使用。

7.2 SPI 走线的等长和包地

SPI 在 27MHz 下,波长大约是 11 米,PCB 上几十厘米的走线远远不到波长,所以一般不需要严格的阻抗匹配。但如果你跑到了 40MHz 以上,或者走线超过 20cm,就要考虑信号完整性问题了。

我的做法是:SCK、MOSI、MISO 三根线尽量等长,误差控制在 5mm 以内;走线下面要有完整的地平面;如果旁边有高频或者大电流的走线,中间加地线隔离。CS 线可以稍微随意一点,因为它不是时钟同步信号,但也不要和 SCK 平行走太长。

7.3 封装和焊接的注意事项

MR25H40CDF 是 DFN-8 封装,底部有散热焊盘。这个焊盘必须焊到 PCB 的对应焊盘上,而且要和 GND 连接。有些工程师画封装的时候忘了底部焊盘,或者焊接的时候没焊上,导致芯片散热不良,高温下工作不稳定。

DFN 封装的焊接比 SOP 难一些,手工焊接的话,建议用热风枪,底部焊盘先上锡,芯片放上去之后热风加热,让锡融化。用烙铁的话,容易虚焊。量产的话,钢网开孔要注意底部焊盘的锡量,太多会顶起芯片,太少会虚焊。

8. 写在最后:一些零散但有用的经验

MR25H40CDF 这颗芯片我用在好几个项目里了,从电力监测到运动控制,整体表现很稳。但每次新项目画板子,我还是会重新检查一遍那几个关键点:电源去耦、CS 极性、SPI 模式、地址位数、页边界。这五个地方任何一个出问题,都会让你调试到怀疑人生。

关于 STM32F745VG 的 SPI 配置,我现在的习惯是先用低速(比如 1MHz)把读写调通,确认数据正确之后,再逐步提高时钟频率,每提高一档就跑一遍全片读写测试。这样能快速定位是时序问题还是逻辑问题。

还有一个细节:MRAM 的写入不需要擦除,但 WREN 指令每次写之前都要发。如果你连续写多个不连续的地址,每次都要重新发 WREN。有些工程师想省事,发一次 WREN 然后连续写多个地址,结果只有第一个地址写成功,后面的都失败了。这个坑我踩过,希望你别再踩。

最后说一句关于选型的话。MRAM 不是万能的,它的容量和价格决定了它只适合做参数存储和关键数据记录。如果你的项目需要存大量历史数据,还是老老实实用 Flash 或者 eMMC。但如果你受够了 Flash 的擦写寿命和掉电保护电路,MRAM 值得一试。尤其是现在国产 MRAM 也在起来,价格比前几年降了不少,工业项目里用起来没那么肉疼了。

返回列表