1. 为什么工业现场还在用并行SRAM的思路选存储做工业控制和嵌入式数据采集的同行大概都有过这种纠结设备要频繁记录运行参数、故障快照、标定系数掉电不能丢写入次数还特别高。用EEPROM吧写一次要等好几毫秒擦写寿命也就百万次级别用NOR Flash吧写之前得擦整个扇区频繁小数据写入很快就磨穿了用FRAM呢容量小、价格高而且很多型号供货周期飘忽不定。我前几年做一个电力监测终端采样率不高但要求每200毫秒存一次带时间戳的电流电压值一天下来就是四十多万次写操作用传统方案根本扛不住。后来接触到MRAM磁性随机存储器才算是找到了一个比较舒服的平衡点。MR25H40CDF这颗芯片就是Everspin出的4Mbit SPI MRAM注意是4Mbit换算成字节是512KB不是4MB这个坑后面会细说。它的核心卖点很直接写入不需要擦除、写入速度接近SRAM、擦写寿命理论上无限官方给的是10的14次方次以上、掉电数据保持二十年以上。这几个特性凑在一起对于工业现场那种频繁写、小数据、不能丢的场景来说几乎是量身定做的。而STM32F722ZE这颗MCUCortex-M7内核216MHz主频带FPU和DSP指令本身定位就是高性能嵌入式控制。它有两路SPI外设SPI1到SPI4具体看封装支持硬件NSS、DMA、最高时钟可以跑到fPCLK/2。用F722ZE去驱动MR25H40CDF算是一个比较典型的高性能主控非易失存储的组合。这篇文章我就把从硬件连接到软件驱动、从SPI模式配置到实际读写测试的完整过程梳理一遍顺带把我在调试中踩过的几个坑讲清楚尤其是SPI模式选错导致读出来全是0xFF这种经典问题。提示MR25H40CDF是4Mbit容量即512KByte地址线需要19位A0到A18这一点在写地址的时候必须注意否则会绕回开头覆盖数据。2. MR25H40CDF的SPI协议细节与模式选择2.1 命令集与地址长度MR25H40CDF的命令集不复杂但有几个地方和普通SPI Flash不一样第一次用容易想当然。它的基本操作命令包括命令名称命令码说明WREN0x06写使能写操作前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据SLEEP0xB9进入低功耗WAKE0xAB唤醒关键点在于地址长度。MR25H40CDF是4Mbit需要19位地址所以READ和WRITE命令后面跟的是3个字节的地址但最高字节只有低3位有效因为19位地址 2字节 3位。很多人第一次写驱动的时候直接按24位地址处理把高5位填0其实也能工作但如果你地址计算错了比如以为它是4MB容量那就会出问题。读操作时序是这样的拉低CS发送0x03然后发送3字节地址A23到A0实际只用A18到A0然后就可以连续读出数据地址会自动递增。写操作类似拉低CS先发0x06WREN拉高CS再拉低CS发0x02发3字节地址然后发数据。注意WREN和WRITE之间必须有一次CS的上升沿这是很多人容易漏掉的。2.2 SPI模式0还是模式3这是我最想强调的一点。MR25H40CDF支持SPI模式0CPOL0CPHA0和模式3CPOL1CPHA1。这两种模式的区别在于时钟空闲电平和采样边沿。模式0是时钟空闲低电平上升沿采样模式3是时钟空闲高电平下降沿采样。我在第一次调试的时候STM32CubeMX里默认给SPI1配的是模式0但我的PCB上CS引脚接了一个上拉电阻同时MRAM的时钟线走线比较长结果读出来的ID全是0xFF。排查了半天最后用示波器看波形才发现时钟空闲电平虽然是低但上升沿有振铃导致MRAM在错误的边沿采样了。后来改成模式3问题消失。所以我的建议是如果你的板子走线不是特别短、特别干净优先用模式3因为下降沿采样对振铃的容忍度更高一些。当然这不是绝对的。如果你PCB布局很紧凑模式0也完全没问题。关键是你要用示波器确认时钟信号的质量不要盲目相信CubeMX的默认配置。2.3 状态寄存器的意义MR25H40CDF的状态寄存器只有几位有效但写操作前检查WELWrite Enable Latch位是个好习惯。RDSR命令返回的字节里bit0是WELbit1是WIPWrite In Progress。MRAM的写操作非常快通常WIP位不会长时间置1但如果你在写完之后立刻读状态可能会看到WIP。不过实际测试下来MRAM的写入延迟在几十纳秒级别基本上你发完数据它就已经写完了不需要像Flash那样轮询等待。注意虽然MRAM写入快但WREN命令之后如果CS被意外拉高WEL位会复位后续的WRITE命令会被忽略。所以在多任务环境下操作MRAM的SPI总线一定要加互斥锁否则任务切换可能导致写失败。3. STM32F722ZE的SPI外设配置与DMA优化3.1 时钟树与SPI时钟计算STM32F722ZE的SPI时钟来源于APB总线。假设你的系统时钟配置为216MHzAPB1和APB2的分频系数需要根据你的时钟树来定。以常见的配置为例HCLK216MHzAPB154MHzAPB2108MHz。SPI1挂在APB2上所以SPI1的输入时钟是108MHz。SPI的波特率分频系数可以设为2到256所以SPI1最高时钟是54MHz。但MR25H40CDF的最高SPI时钟是40MHz读操作和40MHz写操作所以你不能直接把SPI1设到54MHz。我一般会把分频系数设为4得到27MHz这个速度足够快而且信号完整性比较好。如果你非要跑到40MHz那PCB走线必须做阻抗匹配否则误码率会上升。计算过程是这样的SPI_BaudRatePrescaler 4则SPI时钟 108MHz / 4 27MHz。如果你用SPI2挂在APB1上输入时钟54MHz分频2就是27MHz分频4就是13.5MHz。所以选哪个SPI外设要根据你的时钟树和需要的速度来定。3.2 硬件NSS还是软件NSSSTM32的SPI支持硬件NSS和软件NSS两种模式。硬件NSS模式下MCU自动控制CS引脚但有个问题硬件NSS在多主模式下容易出问题而且如果你用DMA传输硬件NSS的时序可能不够灵活。我一般推荐用软件NSS也就是把CS当作普通GPIO来控制这样你可以精确控制CS的拉低和拉高时机尤其是在WREN和WRITE之间需要插入CS上升沿的时候软件控制更可靠。具体做法是在CubeMX里把SPI的NSS设为Disable然后手动配置一个GPIO为输出模式初始电平为高。每次操作前拉低操作完拉高。注意拉高和拉低之间要加一点延时通常1微秒就够了但如果你SPI时钟很快可以适当增加。3.3 DMA传输的配置要点对于大批量数据读写用DMA可以解放CPU。STM32F722ZE的SPI支持TX和RX的DMA请求。配置DMA时要注意几点DMA通道要和SPI的TX/RX请求对应具体看参考手册的DMA请求映射表。DMA模式设为Normal还是Circular取决于你的应用。如果是单次读写用Normal如果是连续采集用Circular。数据宽度设为Byte因为SPI是8位数据帧。优先级根据你的系统来定一般设为Medium或High。我实测下来用DMA读写512KB数据从发起传输到完成中断大概需要200毫秒左右27MHz时钟。如果不用DMACPU轮询的话时间差不多但CPU被占满了没法干别的事。所以如果你的系统有RTOS强烈建议用DMA。提示DMA传输完成后记得检查SPI的TXE和RXNE标志确保数据真的发出去了。有时候DMA完成中断触发了但SPI的最后一个字节还在移位寄存器里这时候拉高CS会导致最后一个字节丢失。4. 从零编写MR25H40CDF的读写驱动4.1 底层字节收发函数不管上层怎么封装底层都需要一个字节收发函数。这个函数用轮询方式实现简单可靠uint8_t MRAM_SPI_TransferByte(uint8_t txData) { uint8_t rxData 0; while (!(SPI1-SR SPI_SR_TXE)); *(__IO uint8_t *)SPI1-DR txData; while (!(SPI1-SR SPI_SR_RXNE)); rxData *(__IO uint8_t *)SPI1-DR; return rxData; }注意这里直接操作寄存器没有用HAL库。HAL库的SPI_TransmitReceive函数在高速下会有额外开销如果你追求性能直接操作寄存器更直接。当然如果你用CubeMX生成了HAL代码也可以用HAL_SPI_TransmitReceive但要注意HAL库的超时机制可能会在高速下误判。4.2 写使能与写操作写操作之前必须发WREN而且WREN和WRITE之间要有CS的上升沿。代码大概长这样void MRAM_WriteEnable(void) { MRAM_CS_LOW(); MRAM_SPI_TransferByte(0x06); MRAM_CS_HIGH(); } void MRAM_Write(uint32_t addr, uint8_t *data, uint32_t len) { MRAM_WriteEnable(); MRAM_CS_LOW(); MRAM_SPI_TransferByte(0x02); MRAM_SPI_TransferByte((addr 16) 0x07); MRAM_SPI_TransferByte((addr 8) 0xFF); MRAM_SPI_TransferByte(addr 0xFF); for (uint32_t i 0; i len; i) { MRAM_SPI_TransferByte(data[i]); } MRAM_CS_HIGH(); }注意地址的高字节只取低3位因为19位地址。如果你写成(addr 16) 0xFF那高5位会被当成地址的一部分虽然MRAM内部会忽略但逻辑上不严谨。4.3 读操作与连续读读操作更简单不需要WRENvoid MRAM_Read(uint32_t addr, uint8_t *buffer, uint32_t len) { MRAM_CS_LOW(); MRAM_SPI_TransferByte(0x03); MRAM_SPI_TransferByte((addr 16) 0x07); MRAM_SPI_TransferByte((addr 8) 0xFF); MRAM_SPI_TransferByte(addr 0xFF); for (uint32_t i 0; i len; i) { buffer[i] MRAM_SPI_TransferByte(0xFF); } MRAM_CS_HIGH(); }连续读的时候地址会自动递增所以你不需要每次重新发地址。但要注意如果你读的地址超过了0x7FFFF512KB边界它会绕回到0x00000。这个行为在环形缓冲区应用里其实挺有用的但如果你不想绕回就要自己判断边界。4.4 状态寄存器读取与写保护状态寄存器的bit7是SRWDStatus Register Write Disable配合WP引脚可以硬件写保护。如果你不需要写保护可以把WP引脚接高电平。RDSR命令返回的字节里bit0是WELbit1是WIP。我一般会在写操作后读一次状态确认WEL已经复位表示写操作被接受了。uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_LOW(); MRAM_SPI_TransferByte(0x05); status MRAM_SPI_TransferByte(0xFF); MRAM_CS_HIGH(); return status; }5. 实测中的三个典型问题与排查过程5.1 读出来全是0xFF这个问题我遇到过两次。第一次是SPI模式配错了CubeMX默认模式0但我的板子走线不好改成模式3就好了。第二次是CS引脚配置错了我用了硬件NSS但NSS引脚没有正确初始化导致CS一直处于高电平MRAM根本没被选中。排查方法很简单用示波器看CS引脚如果它一直是高那就是配置问题如果它有拉低但数据还是0xFF那就是模式或时钟问题。还有一种可能是MISO引脚被其他外设占了。STM32F722ZE的SPI1_MISO和某些复用功能冲突如果你在CubeMX里没注意可能MISO被配成了别的功能。检查方法是在CubeMX的引脚视图里看SPI1_MISO是不是绿色的。5.2 写入后读出来数据不对这个问题的根源通常是WREN和WRITE之间的CS上升沿没处理好。我见过有人把WREN和WRITE写在同一个CS低电平周期里结果写不进去。正确的做法是WREN之后拉高CS再拉低CS发WRITE。另外如果你在写操作之后立刻读可能会读到旧数据因为MRAM的写入虽然快但也不是零延迟。我一般会在写完之后加一个1微秒的延时或者读一次状态寄存器确认WIP为0。还有一种可能是地址算错了。比如你要写0x1000但你的地址计算里把高字节写成了0x01实际地址变成了0x011000超出了512KB范围绕回到开头了。所以写地址的时候一定要确认高字节只取低3位。5.3 DMA传输丢最后一个字节这个问题很隐蔽。DMA传输完成中断触发的时候SPI的移位寄存器里可能还有最后一个字节没发完。如果你在DMA完成中断里立刻拉高CS最后一个字节就丢了。解决方法是在DMA完成中断里等待SPI的BSY标志清零再拉高CSwhile (SPI1-SR SPI_SR_BSY); MRAM_CS_HIGH();或者你可以在DMA传输完成后加一个小的延时但等待BSY更可靠。我实测下来等待BSY的时间通常在几十纳秒到几百纳秒之间对整体性能影响可以忽略。注意如果你用HAL库的HAL_SPI_Transmit_DMAHAL库会在传输完成后自动处理BSY标志但如果你直接操作寄存器就必须自己处理。6. 工业场景下的数据存储策略与寿命考量6.1 为什么MRAM适合高频写入工业现场的数据记录有个特点单次数据量小但写入频率高。比如一个温度采集节点每100毫秒记录一次温度值一天就是86万次写操作。如果用EEPROM按100万次擦写寿命算一天就差不多把寿命用完了。而MRAM的擦写寿命是10的14次方次以上按每天100万次算可以用几百年。这个差距是数量级的。另外MRAM的写入不需要擦除所以写入延迟非常低。EEPROM写一个字节要几毫秒MRAM写一个字节只要几十纳秒。这意味着你可以在同样的时间内记录更多的数据或者用更低的功耗完成同样的写入。6.2 环形缓冲区的实现对于高频数据记录我通常会在MRAM里实现一个环形缓冲区。具体做法是在MRAM的开头几个字节存一个写指针每次写入数据后更新指针。当指针到达末尾时绕回到数据区开头。这样你不需要擦除任何东西直接覆盖旧数据就行。环形缓冲区的容量是512KB减去指针和元数据的空间。假设你每条记录32字节那可以存大约16000条记录。如果每条记录带8字节时间戳那可以存大约13000条。对于工业现场来说这个容量足够存几天的数据了。实现的时候要注意写指针的更新必须是原子的。如果你用RTOS最好在写指针更新的时候关中断或者用互斥锁保护。否则任务切换可能导致指针写了一半掉电后数据就乱了。6.3 掉电保护与数据完整性MRAM虽然掉电不丢数据但如果你在写数据的过程中掉电可能会写了一半。为了保证数据完整性我一般会用双备份或者校验和的方式。具体做法是每条记录后面加一个CRC16校验读的时候先校验如果校验失败就跳过这条记录。另外写指针可以用两个副本交替更新读的时候取最新的那个。还有一种做法是用提交标志在数据区写完之后再写一个标志字节表示这条记录有效。这样即使掉电导致数据写了一半标志字节没写读的时候就知道这条记录无效。这个方法简单可靠我用了很多年。7. 性能实测数据与优化建议7.1 读写速度实测我在STM32F722ZE上以27MHz SPI时钟做了实测结果如下操作数据量耗时平均速度单字节写1字节约2微秒0.5MB/s连续写512KB约180毫秒2.8MB/s单字节读1字节约1.5微秒0.67MB/s连续读512KB约160毫秒3.2MB/s连续读写速度受限于SPI时钟和命令开销。如果你把SPI时钟提到40MHz连续读写速度可以到4MB/s左右。但单字节操作的耗时主要花在CS拉低拉高和命令字节上提升时钟帮助不大。7.2 优化建议如果你需要更高的写入速度可以考虑以下几点用DMA传输减少CPU干预。把多个小数据合并成一个大块写入减少CS切换次数。如果SPI时钟可以跑到40MHz尽量跑40MHz但要注意信号完整性。用双SPI或者QSPIMR25H40CDF只支持标准SPI不支持QSPI所以这条路走不通。如果你需要更高速度可以考虑Everspin的QSPI MRAM型号。另外如果你用RTOS建议把MRAM操作放在一个独立的任务里用消息队列接收写请求。这样可以避免多个任务同时操作SPI总线导致的冲突。7.3 功耗考量MR25H40CDF的写入电流大约10mA待机电流几微安。对于电池供电的设备可以在不写的时候让MRAM进入SLEEP模式需要写的时候用WAKE命令唤醒。唤醒时间大约1微秒对大多数应用来说可以忽略。STM32F722ZE的SPI外设在空闲时也可以关闭时钟进一步降低功耗。如果你用DMA传输完成后DMA会自动停止不需要额外操作。8. 几个容易忽略的硬件设计细节8.1 去耦电容的放置MR25H40CDF的电源引脚旁边必须放一个0.1微法的去耦电容而且越近越好。我见过有人把电容放在板子另一面结果读写不稳定。MRAM对电源噪声比较敏感尤其是写操作的时候。如果条件允许再并一个1微法的电容。8.2 CS引脚的上下拉CS引脚建议加上拉电阻防止在MCU复位期间CS浮空导致误操作。上拉电阻一般用10K欧姆。如果你用软件NSSCS引脚在MCU初始化之前是浮空的这时候MRAM可能会被意外选中。加上拉电阻可以避免这个问题。8.3 时钟线的走线SPI时钟线尽量短不要和其他高速信号平行走线。如果时钟线比较长可以考虑串联一个22欧姆的电阻做阻抗匹配。我实测下来串联电阻之后27MHz时钟的振铃明显减小读写稳定性提高。8.4 WP和HOLD引脚的处理如果你不用写保护和HOLD功能WP和HOLD引脚都要接高电平。不要悬空否则可能误触发。我一般直接把这两个引脚接到VCC简单可靠。提示MR25H40CDF的HOLD功能可以在SPI传输过程中暂停时钟但如果你不需要直接接高电平就行。悬空的话噪声可能让它误触发导致数据传输中断。9. 从MRAM到系统级数据管理的一点经验我在实际项目里用MR25H40CDF做了几年的数据记录最大的体会是MRAM的硬件操作本身不复杂难的是系统级的数据管理。比如你怎么组织数据结构、怎么处理掉电、怎么在RTOS里安全地访问SPI总线。这些问题不是MRAM特有的但MRAM的高写入频率会把它们放大。我的建议是在项目初期就把数据存储的格式定好留好扩展空间。比如每条记录加一个版本号以后改格式的时候可以兼容旧数据。另外写一个简单的测试程序反复读写MRAM跑个几天几夜看看有没有数据错误。我一般会跑一个72小时的连续读写测试确认稳定性之后再上正式产品。还有一点MRAM虽然寿命长但也不是无限的。官方给的10的14次方次是理论值实际使用中如果电源不稳、温度过高寿命会打折扣。所以如果你的应用写入频率特别高还是建议做磨损均衡虽然MRAM不需要擦除但均匀使用可以延长寿命。最后说一个实际案例。我之前做一个电梯控制器需要记录每次开关门的时间、楼层、故障码。原来用EEPROM每半年就要换一次芯片。换成MR25H40CDF之后跑了三年没出过问题。这个案例让我对MRAM在工业场景的可靠性有了信心。当然成本比EEPROM高不少但考虑到维护成本和停机损失这个投入是值得的。
阅读完成 · 觉得有帮助?