ARTICLE DETAIL

资讯详情

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

STM32F745VG驱动MR25H40CDF MRAM:SPI读写与掉电保护实战

STM32F745VG驱动MR25H40CDF MRAM:SPI读写与掉电保护实战 1. 为什么在STM32F745VG上挂一颗MR25H40CDF值得单独写一篇做工业控制和嵌入式数据采集的同行大概都有过这种经历方案选型阶段MCU的Flash够用、RAM够用、外设够用偏偏在掉电不能丢数据这件事上卡住了。用EEPROM吧写入速度慢、擦写寿命有限高频采集场景下几个月就把寿命耗光用SPI NOR Flash吧写之前得擦除整个扇区还得处理磨损均衡代码复杂度直接上一个台阶用带电池的SRAM吧电池要维护、要更换工业现场高温环境下电池鼓包漏液的事故并不少见。MR25H40CDF这颗MRAM磁性随机存储器就是冲着这个痛点来的。它的容量是4Mbit也就是512KBSPI接口和普通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也就是CPOL0/CPHA0或者CPOL1/CPHA1。但它在上电后的默认状态需要你先发一个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 → SCKPA6 → MISO对应MR25H40CDF的SOPA7 → MOSI对应MR25H40CDF的SIPA4 → CS用GPIO软件控制片选这里我强烈建议用软件片选而不是硬件片选。原因很简单MR25H40CDF的CS时序要求比较严格命令之间需要拉高CS来结束当前操作。用HAL库的硬件NSS有时候会在字节间产生不必要的抖动导致命令解析出错。用GPIO手动控制CS代码里明确地在每次操作前后拉低和拉高反而更可靠。PCB布局上SCK和MOSI这两根线如果跑20MHz以上尽量走短线、少过孔旁边包地。MRAM对时序比Flash更敏感因为它的读写速度更快信号完整性问题会直接表现为读写错误。我在第一版板子上SCK走了将近8cm20MHz下误码率明显上升后来缩短到3cm以内就稳定了。3.2 CubeMX里的SPI参数怎么填在STM32CubeMX里配置SPI1关键参数如下Mode: Full-Duplex MasterData Size: 8 BitsClock Polarity: LowClock Phase: 1 Edge对应SPI模式0NSS: SoftwareBaud 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 → 发0x06WREN→ 拉高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的时机写函数比读函数多一步WRENvoid 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.8ms13.5MHz约0.8ms约0.9ms27MHz约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.3VMR25H40CDF也是3.3V直接连接没问题。但如果你的系统里有1.8V的器件要注意电平匹配。另外STM32F745VG的SPI引脚在复位后默认是浮空输入如果你在MRAM初始化之前就使能了SPI可能会在总线上产生毛刺。建议先配置GPIO为输出高电平CS拉高再初始化SPI。9. 从项目落地角度看的选型建议如果你正在做方案选型我的建议是先算一下你的写入频率和数据量。如果每天写入次数超过1万次或者单次写入数据量小于4KBMRAM的优势非常明显。如果只是偶尔写一次配置那用STM32内部的Flash模拟EEPROM就够了没必要外挂MRAM。成本方面MR25H40CDF的单价确实比同容量SPI Flash高不少但考虑到它省去了磨损均衡代码、省去了擦除等待、提高了掉电安全性整体BOM成本和开发成本未必更高。我算过一笔账一个中等复杂度的Flash磨损均衡掉电保护方案开发时间大概2到3周而MRAM方案3天就能跑通。时间成本也是成本。最后说一个我自己的习惯在MRAM的地址空间里我会把前4KB划成元数据区存设备序列号、固件版本、配置参数、写指针这些关键信息后面的区域做数据记录。元数据区用双备份加版本号数据区用环形缓冲区。这套结构在好几个项目里复用稳定跑了两年多没出过数据丢失的问题。
返回列表