☰
STM32F745VG驱动MR25H40CDF MRAM:SPI读写与掉电保护实战
2026/10/5 6:19:16 网站建设 项目流程

1. 为什么在STM32F745VG上挂一颗MR25H40CDF值得单独写一篇

做工业控制和嵌入式数据采集的同行大概都有过这种经历:方案选型阶段,MCU的Flash够用、RAM够用、外设够用,偏偏在"掉电不能丢数据"这件事上卡住了。用EEPROM吧,写入速度慢、擦写寿命有限,高频采集场景下几个月就把寿命耗光;用SPI NOR Flash吧,写之前得擦除整个扇区,还得处理磨损均衡,代码复杂度直接上一个台阶;用带电池的SRAM吧,电池要维护、要更换,工业现场高温环境下电池鼓包漏液的事故并不少见。

MR25H40CDF这颗MRAM(磁性随机存储器)就是冲着这个痛点来的。它的容量是4Mbit,也就是512KB,SPI接口,和普通SPI Flash的引脚几乎完全兼容,但它的写入行为和SRAM一样——不需要擦除、按字节写、写入次数近乎无限。我第一次在STM32F745VG上把它跑通的时候,最直观的感受就是:终于不用再写那一堆擦除-等待-写入的状态机了。

这篇内容面向的是正在做工业数据记录、参数存储、黑匣子类应用的嵌入式工程师,尤其是那些已经用STM32F745VG做主控、正在纠结存储方案的人。我会把MR25H40CDF的硬件连接、SPI时序配置、读写驱动、掉电保护策略、以及我在实测中踩过的坑,完整地讲一遍。代码基于STM32CubeMX + HAL库,你可以直接拿去改。

2. MR25H40CDF到底和普通SPI Flash差在哪

2.1 从存储原理看写入行为的根本差异

普通SPI NOR Flash的存储单元是浮栅晶体管,写数据之前必须先把整个扇区擦成0xFF,擦除操作的最小单位通常是4KB,而且擦除一次要几十毫秒。这意味着你每改一个字节,理论上都要经历"读出整扇区→改内存→擦除扇区→写回整扇区"的流程。虽然实际工程中会用缓存和磨损均衡来优化,但底层物理限制摆在那里。

MR25H40CDF用的是磁性隧道结(MTJ),数据以磁化方向的形式存储,写入时通过电流改变磁化方向。这个过程不需要预先擦除,写入延迟在几十纳秒量级,而且没有擦写次数的实际限制(官方标称10^14次以上)。你可以把它理解成一颗"掉电不丢的SRAM"——读写命令和SRAM几乎一样直接。

这个差异带来的工程价值是巨大的。举个例子,我在一个振动采集项目里,需要每10ms记录一次三轴加速度数据,每次记录12字节。如果用SPI Flash,每写满一个扇区就要擦一次,擦除期间如果掉电,整个扇区的数据都可能丢失。换成MR25H40CDF之后,直接按地址写就行,掉电最多丢当前正在写的那几个字节,之前的数据全部安全。

2.2 容量、速度和接口参数的实际含义

MR25H40CDF的4Mbit容量对应512KB,地址范围0x00000到0x7FFFF。SPI接口最高支持40MHz时钟,但实际能跑多快取决于你的PCB布线和STM32F745VG的SPI外设配置。STM32F745VG的SPI1挂在APB2上,最高时钟可以到108MHz,分频后跑20MHz到40MHz完全没问题。

这里有个容易被忽略的点:MR25H40CDF支持SPI模式0和模式3,也就是CPOL=0/CPHA=0或者CPOL=1/CPHA=1。但它在上电后的默认状态需要你先发一个WREN(写使能)命令才能写入。读操作不需要WREN,直接发READ命令就行。这个细节在数据手册里写得很清楚,但很多人第一次用的时候会忘记发WREN,然后纳闷为什么写不进去。

参数MR25H40CDF典型SPI NOR Flash(如W25Q64)
容量512KB8MB
写入前擦除不需要必须按扇区擦除
写入粒度字节页(通常256字节)
擦除粒度无4KB扇区
写入寿命近乎无限约10万次
写入延迟纳秒级毫秒级(含擦除)
接口SPISPI
掉电数据保持20年以上20年以上

2.3 什么场景该选MRAM,什么场景不该选

MR25H40CDF适合的场景很明确:写入频繁、数据量不大、对掉电安全性要求高、不想在软件层面做磨损均衡。比如工业设备的参数配置区、故障录波的黑匣子、频繁更新的校准系数、实时采集的环形缓冲区。

不适合的场景同样明确:需要存大量历史数据(512KB不够)、对成本极度敏感(MRAM单价确实比同容量Flash高)、只需要偶尔写一次配置(那用EEPROM或Flash的模拟EEPROM就够了)。我一般会建议:如果你的应用里"写操作"是高频事件,或者你已经被Flash擦除逻辑折磨过,那就值得上MRAM;如果只是存个开机次数、设备序列号,那没必要花这个钱。

3. 硬件连接与STM32F745VG的SPI外设配置

3.1 引脚连接和PCB布局的注意事项

MR25H40CDF是8引脚封装,引脚定义和标准SPI Flash一致:CS、SO、SI、SCK、VCC、GND,另外还有WP和HOLD(有些封装是NC)。在STM32F745VG上,我一般用SPI1,引脚分配如下:

  • PA5 → SCK
  • PA6 → MISO(对应MR25H40CDF的SO)
  • PA7 → MOSI(对应MR25H40CDF的SI)
  • PA4 → CS(用GPIO软件控制片选)

这里我强烈建议用软件片选而不是硬件片选。原因很简单:MR25H40CDF的CS时序要求比较严格,命令之间需要拉高CS来结束当前操作。用HAL库的硬件NSS有时候会在字节间产生不必要的抖动,导致命令解析出错。用GPIO手动控制CS,代码里明确地在每次操作前后拉低和拉高,反而更可靠。

PCB布局上,SCK和MOSI这两根线如果跑20MHz以上,尽量走短线、少过孔,旁边包地。MRAM对时序比Flash更敏感,因为它的读写速度更快,信号完整性问题会直接表现为读写错误。我在第一版板子上SCK走了将近8cm,20MHz下误码率明显上升,后来缩短到3cm以内就稳定了。

3.2 CubeMX里的SPI参数怎么填

在STM32CubeMX里配置SPI1,关键参数如下:

  • Mode: Full-Duplex Master
  • Data Size: 8 Bits
  • Clock Polarity: Low
  • Clock Phase: 1 Edge(对应SPI模式0)
  • NSS: Software
  • Baud Rate Prescaler: 根据你的APB2时钟来算,目标是10MHz到20MHz之间
  • First Bit: MSB First

假设APB2时钟是108MHz,分频系数选8,得到13.5MHz;选4得到27MHz。我一般先用13.5MHz跑通,再往上调。MR25H40CDF在40MHz下也能工作,但STM32F745VG的SPI在高速下对PCB要求更高,没必要一上来就拉满。

还有一个细节:HAL库的SPI初始化里有个CLKPhase和CLKPol的组合,对应关系是:

hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;

这就是模式0。MR25H40CDF也支持模式3,但模式0更常用,我建议统一用模式0。

3.3 片选信号的时序控制要点

MR25H40CDF的CS信号控制有几个硬性要求:每次命令序列开始前CS要拉低,命令序列结束后CS要拉高。CS拉高之后,芯片内部需要一点时间来完成写操作(虽然MRAM写入很快,但状态机需要时间)。如果你连续写多个字节,可以在一次CS拉低期间连续发地址和数据,不需要每个字节都切换CS。

我的做法是封装两个宏:

#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)

然后在每个操作函数里成对使用。注意不要在中断里调用这些函数,因为SPI传输本身是阻塞的,在中断里做长传输会影响系统实时性。如果确实需要在中断里写MRAM,建议用DMA或者把数据放到队列里在主循环处理。

4. 读写驱动的完整实现与命令解析

4.1 MR25H40CDF的命令集和操作流程

MR25H40CDF的命令集很精简,常用的就几条:

命令编码功能
WREN0x06写使能
WRDI0x04写禁止
READ0x03读数据
WRITE0x02写数据
RDSR0x05读状态寄存器
WRSR0x01写状态寄存器

读操作的流程是:拉低CS → 发0x03 → 发3字节地址 → 连续读数据 → 拉高CS。写操作的流程是:拉低CS → 发0x06(WREN)→ 拉高CS → 拉低CS → 发0x02 → 发3字节地址 → 连续写数据 → 拉高CS。

注意WREN是一个独立命令,必须在每次写操作之前发送。如果你连续写多个不连续的区域,每次都要重新发WREN。这个和Flash是一样的。

4.2 读函数的实现细节

读函数相对简单,但有几个地方要注意。地址是24位的,高位在前。HAL库的HAL_SPI_Transmit和HAL_SPI_Receive可以分开发,也可以用HAL_SPI_TransmitReceive一次完成。我习惯用分步的方式,代码更清晰:

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(); }

这里HAL_MAX_DELAY在实际项目中要改成合理的超时值,比如100ms。工业现场如果SPI线被干扰,阻塞在HAL_MAX_DELAY会导致看门狗复位。我一般设50ms到100ms。

4.3 写函数的实现与WREN的时机

写函数比读函数多一步WREN:

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, 100); 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, 100); HAL_SPI_Transmit(&hspi1, buf, len, 100); MRAM_CS_HIGH(); }

实测下来,WREN和WRITE之间CS拉高的时间不需要很长,几百纳秒就够了。但如果你发现写入不稳定,可以适当加一点延时,比如__NOP()几个周期。

4.4 状态寄存器的读取与写保护处理

MR25H40CDF的状态寄存器里有一位是写保护使能位。上电默认状态下,整个存储区是可写的。但如果你之前写过状态寄存器开启了块保护,那对应区域就写不进去了。我遇到过一个问题:某次调试时误发了WRSR命令,把状态寄存器设成了保护模式,结果后面所有写操作都失败,查了半天才发现是状态寄存器的问题。

读状态寄存器的代码:

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; }

如果你怀疑写保护被开启了,读一下状态寄存器,看看BP0和BP1位是不是被置位了。如果是,发WRSR命令写0x00清除保护。

5. 掉电保护与数据完整性策略

5.1 为什么MRAM仍然需要数据完整性设计

虽然MRAM写入是原子的(单个字节的写入不会被打断),但如果你一次写多个字节,掉电可能发生在写到一半的时候。比如你要更新一个结构体,包含时间戳、数据值、校验和,如果写到校验和的时候掉电,那这个记录就是无效的。

所以即使有了MRAM,仍然需要设计数据完整性策略。我的做法是:每条记录加一个"有效标志"字节,写入顺序是"先写数据,再写有效标志"。读取时先检查有效标志,如果无效就跳过这条记录。这样即使掉电导致数据写了一半,有效标志没写进去,这条记录也不会被误读。

5.2 双缓冲区和版本号机制

对于配置参数这种需要频繁更新的数据,我推荐用双缓冲区加版本号的方式。在MRAM里划两个区域,每个区域存一份配置,外加一个版本号。写入时先写备用区,写完更新版本号,再切换主备。读取时比较两个区域的版本号,取版本号大的那个。

这个机制的好处是:即使写入过程中掉电,至少有一个区域的配置是完整的。版本号用32位,每次写入加1,几乎不可能溢出。

5.3 环形缓冲区在数据记录中的应用

对于连续采集的数据记录,环形缓冲区是最合适的结构。在MRAM里划一块区域,维护一个写指针和一个读指针。写指针每次写完一条记录后递增,到达区域末尾就回绕到开头。读指针用于导出数据。

这里有个细节:写指针本身也需要掉电保护。我的做法是把写指针存在MRAM的固定位置,每次写完数据后更新写指针。因为MRAM写入快,这个额外开销可以忽略。如果担心写指针更新时掉电,可以用两个写指针交替写,读取时取有效的那个。

6. 实测中遇到的坑和排查过程

6.1 第一次上电读出来全是0xFF

这个问题我遇到过两次。第一次是因为CS引脚配置成了复用功能而不是GPIO输出,导致CS一直处于浮空状态,芯片根本没被选中。第二次是因为SPI的时钟极性配错了,配成了模式3但芯片实际工作在模式0,读出来的数据全是0xFF。

排查思路:先用示波器看CS、SCK、MOSI三根线。CS应该在每次操作时有一个明显的拉低脉冲;SCK应该有对应数量的时钟脉冲;MOSI上应该能看到命令字节。如果CS没动,查GPIO配置;如果SCK没动,查SPI使能和分频配置;如果MOSI数据不对,查SPI的MSB/LSB配置。

6.2 写入后读出来数据对不上

这个问题通常有三个原因:一是WREN没发或者发在了错误的位置;二是地址算错了,比如把字节地址当成了页地址;三是SPI速度太快,PCB信号完整性有问题。

我的排查顺序是:先把SPI速度降到最低(比如1MHz),如果低速下正常,那就是信号完整性问题,需要检查PCB或者降低速度;如果低速下也不对,那就是命令序列或地址的问题,用逻辑分析仪抓一次完整的写-读过程,逐字节对比。

6.3 高温环境下偶发读写失败

工业现场温度可能到70度以上。MR25H40CDF的工业级版本支持-40到85度,但如果你用的是商业级版本(0到70度),高温下就可能出问题。我有个项目在夏天现场调试时,中午机箱内温度到了75度,MRAM开始偶发读写错误,后来换成工业级型号就稳定了。

另外,高温下SPI时序余量会变小。如果你在常温下跑40MHz没问题,高温下可能就不行了。建议在最高工作温度下留20%的时序余量,比如常温跑40MHz,高温下降到30MHz。

6.4 多任务环境下的SPI总线竞争

STM32F745VG上如果跑了RTOS,多个任务可能同时访问SPI总线。如果没有互斥保护,一个任务的读操作可能被另一个任务的写操作打断,导致数据错乱。我的做法是给SPI总线加一个互斥信号量,所有MRAM操作前先获取信号量,操作完释放。

osMutexWait(spiMutex, osWaitForever); MRAM_Write(addr, buf, len); osMutexRelease(spiMutex);

如果不用RTOS,那就在主循环里统一处理MRAM操作,避免在中断里直接调用。

7. 性能实测数据与优化建议

7.1 不同SPI时钟下的读写速度

我在STM32F745VG上实测了一组数据,SPI时钟分别设为6.75MHz、13.5MHz、27MHz,读写1KB数据的耗时如下:

SPI时钟读1KB耗时写1KB耗时
6.75MHz约1.6ms约1.8ms
13.5MHz约0.8ms约0.9ms
27MHz约0.45ms约0.5ms

写操作比读操作略慢,因为多了WREN命令和CS切换的开销。但整体来说,这个速度已经比SPI Flash快了一个数量级——Flash写1KB数据加上擦除时间,通常要几十毫秒。

7.2 连续写入时的CS保持策略

如果你要连续写多个不连续的区域,每次都要发WREN和切换CS,开销会累积。一个优化策略是:如果多个写操作在地址上是连续的,可以合并成一次写操作,只发一次WREN和一次CS切换。我在数据记录场景里就是这么做的:把多条记录先缓存在RAM里,攒够一批再一次性写入MRAM。

7.3 DMA传输的可行性分析

STM32F745VG的SPI支持DMA。对于大块数据的读写,用DMA可以释放CPU。但MRAM的CS控制需要软件干预,DMA传输完成后要手动拉高CS。我的建议是:小于64字节的数据用阻塞传输就行,大于64字节的可以考虑DMA。但要注意DMA传输期间不能有其他任务访问SPI总线,否则会冲突。

8. 几个容易被忽略的工程细节

8.1 上电初始化时的延时要求

MR25H40CDF上电后需要一段时间才能接受命令,数据手册里写的是tPU,典型值100微秒。如果你在系统上电后立刻初始化SPI并读MRAM,可能会失败。我的做法是在MRAM初始化函数开头加一个1ms的延时,确保芯片内部电源稳定。

8.2 状态寄存器的写保护位

前面提过状态寄存器的写保护位,这里再强调一下:除非你确实需要保护某些区域,否则不要随意写状态寄存器。上电默认状态是全区域可写,这通常就是你想要的。如果你不小心开启了保护,读一下状态寄存器确认,然后写0x00清除。

8.3 电源去耦和复位电路

MRAM对电源噪声比Flash更敏感,因为它的写入电流较大。建议在VCC引脚旁边放一个0.1uF和一个1uF的电容,尽量靠近芯片。如果电源纹波较大,可以考虑加一个LDO单独给MRAM供电。复位引脚如果不用,建议接一个10K上拉到VCC,不要悬空。

8.4 与STM32F745VG的电源域匹配

STM32F745VG的IO电压是3.3V,MR25H40CDF也是3.3V,直接连接没问题。但如果你的系统里有1.8V的器件,要注意电平匹配。另外,STM32F745VG的SPI引脚在复位后默认是浮空输入,如果你在MRAM初始化之前就使能了SPI,可能会在总线上产生毛刺。建议先配置GPIO为输出高电平(CS拉高),再初始化SPI。

9. 从项目落地角度看的选型建议

如果你正在做方案选型,我的建议是:先算一下你的写入频率和数据量。如果每天写入次数超过1万次,或者单次写入数据量小于4KB,MRAM的优势非常明显。如果只是偶尔写一次配置,那用STM32内部的Flash模拟EEPROM就够了,没必要外挂MRAM。

成本方面,MR25H40CDF的单价确实比同容量SPI Flash高不少,但考虑到它省去了磨损均衡代码、省去了擦除等待、提高了掉电安全性,整体BOM成本和开发成本未必更高。我算过一笔账:一个中等复杂度的Flash磨损均衡+掉电保护方案,开发时间大概2到3周,而MRAM方案3天就能跑通。时间成本也是成本。

最后说一个我自己的习惯:在MRAM的地址空间里,我会把前4KB划成"元数据区",存设备序列号、固件版本、配置参数、写指针这些关键信息;后面的区域做数据记录。元数据区用双备份加版本号,数据区用环形缓冲区。这套结构在好几个项目里复用,稳定跑了两年多,没出过数据丢失的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询