ARTICLE DETAIL

资讯详情

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

MRAM在工业存储中的工程实践:STM32F415ZG+MR25H40CDF掉电保存方案

MRAM在工业存储中的工程实践:STM32F415ZG+MR25H40CDF掉电保存方案 前阵子做电力监控终端需要在断电瞬间把电能数据、运行状态和几组关键曲线存下来。原来用的是NOR Flash频繁写入时的磨损和擦除等待时间实在让人头疼换EEPROM容量又不够只能存几个参数。最后在STM32F415ZG旁边挂了一颗Everspin的MR25H40CDF MRAM跑了大半年存储读写这块几乎没再操心过。这篇把整个选型和落地过程完整写下来硬件上怎么接、软件里怎么配、掉电保护怎么做、有哪些坑要躲一次说清楚。如果你正在做类似的工业数据采集、设备日志记录或者需要频繁擦写的参数存储需求这篇应该能帮你少走不少弯路。1. 选型思路为什么工业存储最终选了MRAM1.1 项目需求与原先方案的痛点这个项目的需求其实挺简单一块4Mbit左右的外部存储用来存放三类数据——设备校准参数、运行日志、断电瞬间的关键快照。但要求不算低参数偶尔更新但每次更新必须立即生效不能等擦除。日志按秒级频率写入一天下来几万次写入普通Flash扛不住几年。断电瞬间要把最后几十个字节写进去从检测掉电到系统彻底没电通常只有几毫秒窗口。工作环境有温度变化还有一定振动存储芯片得皮实。最开始用的2Mbit SPI NOR Flash写之前要整个扇区擦除一次擦除要几十毫秒。日志数据本来只想改一个字节结果得先把整个扇区读出来、改完、再擦掉、再写回去。时间开销大不说擦写寿命从理论上算下来连一年都撑不住。后来试过板载EEPROM寿命倒是能接受但容量太小日志稍长就得压缩最终决定换MRAM。1.2 MRAM原理与Flash/EEPROM的差距MRAM的核心存储单元是磁隧道结MTJ简单说就是两层铁磁材料夹着一层极薄的绝缘层。其中一层磁化方向固定另一层可以翻转。两层磁化方向平行时电阻低反平行时电阻高读出电阻状态就知道存的是0还是1。这个状态由磁性维持不需要电荷所以断电不丢也不需要像Flash那样定时刷新。关键是写操作MRAM写数据是直接翻转磁化方向新数据写上去旧数据自然就被覆盖完全不需要先擦除。而NOR Flash写之前必须把整个扇区擦成0xFF再逐字节写入EEPROM虽然没有整块擦除但单字节写入周期也要几毫秒。MRAM的写入速度几乎是即时完成SPI接口下就跟普通寄存器读写一样时序上完全不需要考虑擦除等待。另外Flash的擦写寿命通常在10万次左右EEPROM稍好一些约100万次。MRAM的标称写次数是10的13次方也就是100万亿次高出好几个数量级。这对高频日志记录场景来说基本等于不用考虑寿命问题。1.3 MR25H40CDF和STM32F415ZG的搭配优势MR25H40CDF是Everspin公司的一款4Mbit串行MRAM8引脚封装SPI接口容量512KB。它和标准SPI NOR Flash的引脚定义接近指令集也基本兼容软件迁移成本很低。几个关键参数参数MR25H40CDF容量4Mbit512KB接口SPI最高40MHz工作电压2.7V ~ 3.6V工作温度-40℃ ~ 85℃工业级写耐久10^13次数据保持20年写前擦除不需要STM32F415ZG是Cortex-M4内核主频最高168MHz片上资源非常充裕带FPU、1MB Flash和192KB SRAM。选它有两个原因一是内置了多个SPI外设SPI1挂APB2总线上最高可以跑到42Mbit/s跟MRAM的40MHz速率匹配得很好二是它支持PVD可编程电压检测中断在电源跌落时可以快速响应正好配合MRAM做掉电保存。这种组合还有一个隐含好处MRAM不需要擦除特性让驱动代码比Flash简单很多没有坏块管理没有擦写均衡没有写保护状态机代码量至少省三分之一。2. 硬件连接MR25H40CDF怎么接到STM32F415ZG2.1 引脚分配与原理图关键点我这边的接法很常规用SPI1外设四个引脚直接连STM32F415ZG引脚MR25H40CDF引脚功能说明PA4CS#片选由GPIO软件控制PA5SCKSPI时钟PA6MISOMRAM数据输出PA7MOSIMRAM数据输入3.3VVDD供电GNDGND共地这里有个细节必须注意MR25H40还有两个控制引脚WP#写保护和HOLD#保持。WP#拉低时硬件层面禁止写操作即使软件发了写使能指令也写不进去HOLD#在通信过程中被拉低芯片会冻结当前输出状态SPI主设备会读到莫名其妙的数据。这两个引脚如果不用千万不能悬空必须分别接上拉到VCC。我从一开始就把WP#和HOLD#都通过10kΩ电阻上拉并且引到了MCU的两个GPIO上。这样调试时如果想临时打开写保护或者测试保持功能直接软件控制就行不用改板子。实测下来上拉电阻最直接的作用是避免上电瞬态和邻近信号串扰导致误触发。片选CS#为什么要用GPIO模拟而不是SPI硬件NSS因为工业环境下多个SPI设备共线的情况很常见软件片选更灵活也方便在关键操作时手动拉高拉低不受SPI外设状态机限制。STM32F415ZG的SPI1默认NSS引脚在PA4但用了软件NSS模式后PA4就是纯GPIO想怎么操作都行。2.2 电源、上拉与布局细节电源部分不能马虎。MR25H40CDF工作电压2.7V~3.6V跟STM32F415ZG的3.3V完全兼容不需要电平转换。但供电引脚必须加去耦电容我在VDD和GND之间放了100nF瓷片电容靠近芯片引脚放置另外在PCB电源入口处加了一颗10μF钽电容应对日志写入时的电流尖峰。SPI信号线在工业产品上建议串22Ω或33Ω电阻放在MCU和MRAM之间。这个电阻不是为了降压而是抑制信号反射特别是在时钟频率超过20MHz之后走线长度稍微长一点就会出现振铃串个电阻能显著改善波形质量。我一开始为了省几个器件没串SPI速率拉到21MHz时示波器上SCK的过冲直接超过了3.6V极限值后来补上33Ω电阻后波形干净了很多。还有一点MRAM毕竟是非易失存储芯片在电磁环境复杂的工业现场建议在电源入口加TVS管SPI线如果引出到可插拔模块还要考虑ESD防护。我实际出货的板子上在VDD端加了SMBJ3.3A线缆接口处加了ESD阵列几个月运行下来没有出现存储数据损坏的情况。3. 驱动实现SPI配置与MRAM读写代码详解3.1 SPI外设初始化的关键参数驱动代码用STM32的标准外设库或者HAL库都行但核心初始化参数必须配置对。我用的是HAL库SPI1配置如下/* 使能时钟 */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_SPI1_CLK_ENABLE(); /* PA4: CS推挽输出 */ GPIO_InitTypeDef gpio {0}; gpio.Pin GPIO_PIN_4; gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Pull GPIO_PULLUP; gpio.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, gpio); /* PA5: SCK, PA6: MISO, PA7: MOSI */ gpio.Pin GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; gpio.Mode GPIO_MODE_AF_PP; gpio.Alternate GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, gpio); /* SPI1 主模式8bitMode 0软件NSS */ 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_LOW; hspi1.Init.CLKPhase SPI_PHASE_1EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; HAL_SPI_Init(hspi1);几个地方重点解释。SPI工作模式选择的是Mode 0CPOL0、CPHA0也就是空闲时SCK为低电平数据在第一个时钟沿采样。MR25H40CDF的datasheet支持Mode 0和Mode 3两种Mode 0是嵌入式世界最通用的约定跟绝大部分SPI从机都能配合强烈建议统一用Mode 0。时钟预分频选的是4分频。STM32F415ZG的APB2时钟是84MHzSPI1挂在APB2上4分频后就是21MHz低于MR25H40CDF的最高40MHz。留一倍余量是故意的实际PCB走线、寄生电容、温度变化都会影响信号质量跑到极限速率一旦时序裕量不足读出来的数据就是随机数。21MHz下波形干净调试省心数据吞吐对日志记录场景也绰绰有余。软件NSS模式一定要显式声明。如果不配SPI外设会进入硬件NSS模式PA4的GPIO状态会干扰SPI片选逻辑导致通信时好时坏。这个坑我见不少人踩过。3.2 读写函数的完整实现含状态寄存器MR25H40CDF的指令集和标准SPI NOR Flash很像核心指令就几条指令操作码说明WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据SLEEP0xB9进入深度睡眠WAKE0xAB唤醒状态寄存器注意几个位bit0是WIP写进行中bit1是WEL写使能锁存bit5/bit6/bit7是块保护相关位。MRAM虽然写数据不需要擦除但写入命令仍然有建立过程严谨起见写完后轮询WIP位直到它清零才算写操作完整结束。最基础的传输函数我用寄存器方式实现省去HAL库每次调用的开销也方便在中断里用#define CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) static uint8_t SPI1_Xfer(uint8_t byte) { while (!(SPI1-SR SPI_SR_TXE)); SPI1-DR byte; while (!(SPI1-SR SPI_SR_RXNE)); return (uint8_t)SPI1-DR; }写使能和等待WIPstatic void MRAM_WriteEnable(void) { CS_LOW(); SPI1_Xfer(0x06); CS_HIGH(); } static void MRAM_WaitWIP(void) { uint8_t sr; do { CS_LOW(); SPI1_Xfer(0x05); sr SPI1_Xfer(0x00); CS_HIGH(); } while (sr 0x01); }这里有个细节读状态寄存器时发完0x05之后要再发一个字节通常是0x00来产生时钟从机的数据才会从MISO线上移出来。一开始我不会的时候随手写了个只发送不接收的函数调试了半天才发现读出来的永远是0xFF因为根本没给对方提供时钟。单字节读写uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; CS_LOW(); SPI1_Xfer(0x03); SPI1_Xfer((addr 16) 0xFF); SPI1_Xfer((addr 8) 0xFF); SPI1_Xfer(addr 0xFF); data SPI1_Xfer(0x00); CS_HIGH(); return data; } void MRAM_WriteByte(uint32_t addr, uint8_t data) { MRAM_WriteEnable(); CS_LOW(); SPI1_Xfer(0x02); SPI1_Xfer((addr 16) 0xFF); SPI1_Xfer((addr 8) 0xFF); SPI1_Xfer(addr 0xFF); SPI1_Xfer(data); CS_HIGH(); MRAM_WaitWIP(); }连续读写多个字节时地址自增就是顺理成章的事void MRAM_WriteBuf(uint32_t addr, const uint8_t *buf, uint32_t len) { MRAM_WriteEnable(); CS_LOW(); SPI1_Xfer(0x02); SPI1_Xfer((addr 16) 0xFF); SPI1_Xfer((addr 8) 0xFF); SPI1_Xfer(addr 0xFF); for (uint32_t i 0; i len; i) { SPI1_Xfer(buf[i]); } CS_HIGH(); MRAM_WaitWIP(); }值得注意的是MR25H40CDF没有Flash那样的页缓冲限制。用NOR Flash时一页通常是256字节跨页连续写必须先换页地址MRAM是字节寻址直接写任意长度连续写都不存在页边界问题。驱动代码里不需要处理跨页逻辑干净利落。3.3 数据区规划与DMA日志写入512KB空间怎么划分这是项目管理层面的问题。我按功能分了三个区区间地址范围用途参数区0x00000 ~ 0x0FFFF校准参数、设备配置日志区0x10000 ~ 0x6FFFF运行日志、事件记录快照区0x70000 ~ 0x7FFFF掉电快照、启动标志参数区放的是出厂校准数据和使用中可能调整的配置每次写入同时做双份备份防止写到一半断电导致参数损坏。日志区是循环覆盖的FIFO写满后再从头开始。快照区固定放最后时刻的数据每次写入先写备份再写主区配合CRC校验读取时判断哪份完整用哪份。日志写入如果频率高建议走DMA。SPI1的DMA配置并不复杂发送方向用DMA1的Channel3接收方向用DMA1的Channel2。初始化之后HAL库一行就能触发HAL_SPI_Transmit_DMA(hspi1, buf, len);DMA的好处是日志写入不占CPU主程序可以继续处理采集和控制等传输完成再触发中断更新日志游标。在21MHz速率下64字节日志的SPI传输时间大约30μsDMA把这段等待完全隐藏掉了。4. 工业场景落地掉电保存、校验与寿命评估4.1 掉电瞬间把数据救回来PVD触发快速写工业设备特别是电力监控这类应用最怕的就是设备突然断电。掉电瞬间系统还能靠电容储能撑几毫秒到几十毫秒这个窗口足够做很多事前提是外设响应够快、存储器够快。STM32F415ZG内置了PVD模块可以监测VDD电压。设定阈值比如3.0V当电源降到阈值以下时触发PVD中断。这个中断的响应时间在微秒级远快于系统真正崩溃的时间。在PVD中断服务程序里把采集到的最后一批数据写入MRAM快照区。void PVD_IRQHandler(void) { if (EXTI-PR EXTI_PR_PR21) { EXTI-PR EXTI_PR_PR21; MRAM_WriteBuf(SNAPSHOT_ADDR, (uint8_t *)g_snapshot, sizeof(g_snapshot)); } }PVD中断里调用MRAM写入会不会不靠谱要点有两个一是PVD中断优先级一定要设为最高不能被其他中断打断二是MRAM的写操作本身极快没有擦除等待4字节的数据在21MHz SPI下传输不到10μs掉电瞬间完全来得及。如果用NOR Flash擦除一个扇区要几十毫秒这活根本没法干。硬件上还要配合一个条件MCU的供电脚要加足够大的储能电容。我实测下来1000μF电解电容搭配几个陶瓷电容从检测到断电到系统彻底停摆能维持约20ms。写入64字节快照只需要不到50μs供电余量非常充足。写完快照后我在固定地址写一个特定的启动标志位。下次上电时固件先读这个标志如果标志有效就说明上次掉电保存完整把快照数据恢复到运行区如果标志无效说明至少发生过一次写入中断需要走另一套恢复流程。这个方法简单可靠已经稳定运行了大半年。4.2 CRC校验与双份冗余工业数据存储不能靠运气MRAM虽然本身可靠性很高但SPI链路、电源毛刺、外部干扰都可能让数据出错。所以我在数据帧设计上做了两层保护。第一层是Magic Number加CRC32。每帧数据开头放4字节魔数比如0xA55A5AA5帧尾放4字节CRC32校验值。写数据时先算好CRC读数据时再算一次对不上就丢弃。STM32F4系列内置CRC外设算CRC32不需要软件查表速度极快uint32_t crc HAL_CRC_Calculate(hcrc, (uint32_t *)data, len / 4);第二层是双份冗余交替写。参数区每个条目分配两个槽位写入时交替使用。读取时先读A槽校验通过就用A校验不通过就试B槽。这样即使一次写入被掉电打断没写完的槽位被标记为无效另一份数据必然还是完整的。这套方案和ST官方AN3965里描述的双备份Flash方案思路一致MRAM的写入速度让整个冗余机制的开销几乎可以忽略。双份写入时间也就几十微秒如果是Flash还要擦除双份就意味着双倍擦除等待。4.3 那点写入寿命用多久才会写完很多人一听到MRAM就觉得贵觉得是不是没必要。抛开成本因素我们先算算寿命这笔账。MR25H40CDF写耐久是10^13次。假设设备每秒写100次一年大约是100 × 86400 × 365 3.1536 × 10^9次。10^13 ÷ 3.1536 × 10^9 ≈ 3171年。每秒写100次什么概念一天864万次写入绝大多数工业设备到不了这个频率。就算瓶颈到极端情况每秒写1000次也还能用三百多年。对比一下NOR Flash写耐久10万次按每秒10次计算10^5 ÷ (10 × 86400 × 365) ≈ 0.32年三个月就磨损完了而且每次写之前还有擦除等待。EEPROM寿命100万次也才三年左右。MRAM的寿命在应用层面基本就是一个不需要考虑的参数这省掉了很多软件设计的复杂度。在嵌入式软件里做擦写均衡、坏块管理等逻辑对Flash是必需品对MRAM反而是多余的。代码少了出bug的机会自然就少了。5. 处理过的坑与调试速查表5.1 HOLD与WP两个引脚最容易出问题我在调试阶段遇到过一回极其诡异的现象MRAM偶尔能读能写偶尔无论怎么写都读不出来原样数据。用示波器抓SPI波形命令和地址都正确MISO上也有数据回来但内容就是错的。排查到最后发现是HOLD#引脚没有上拉。旁边有一条时钟线时钟信号串扰过来把HOLD#短暂拉低芯片进入保持模式输出被冻结后续数据就乱了。HOLD#和WP#这两个引脚悬空就是埋雷必须上拉最好再加一个小电容滤波。WP#的坑也值得说。块保护状态寄存器里bit5、bit6、bit7和WP#引脚配合当WP#被拉低时任何写保护指令都不能被解除。有一次我改了状态寄存器配置之后写操作一直失败查了半天才发现是WP#被某种情况下拉低了写使能指令发了也白发。调试时先把块保护寄存器清零同时确保WP#维持高电平可以排除大半个问题域。5.2 SPI时钟只出不进的排查第二种典型故障是MCU的SCK和MOSI波形都正常但MISO线上永远是高电平读出来全是0xFF。按照经验优先排查以下顺序MISO连接是否可靠有没有虚焊。MRAM是并行封装的8脚芯片手工焊接容易在MISO引脚堆锡过多形成桥接用放大镜或万用表确认。PA6是否配置成复用功能。如果PA6还是默认的输入模式不是AF5复用那SPI外设根本接收不到引脚电平读出来必然是0xFF。检查从机是否真的被片选选中。CS#必须在我们发命令之前拉低发完后拉高。如果CS#一直为高从机内部逻辑根本不会响应命令。MRAM是否处于睡眠模式。SLEEP命令发出后芯片会一直睡到掉电或收到WAKE命令。如果上一次调试结束时正好发过SLEEP下一次上电后需要先发WAKE才能唤醒。这里特别提醒MR25H40上电后默认是工作状态但如果代码里用了低功耗管理就要注意休眠指令的使用。我最终在每轮初始化开头固定发一条WAKE命令再加一个短延时保证芯片一定处于活跃状态。5.3 偶发数据翻转/全F问题有用户反馈设备运行一段时间后个别扇区的日志变成了全0xFF。第一反应可能是MRAM数据保持出了问题但实测后发现读出来的全0xFF其实是SPI传输失败的表现MRAM内部数据依然正常。这类问题的排查思路是这样的症状可能原因处理方式读出来全是0xFFSPI模式不对从机没被片选MRAM睡眠检查CPOL/CPHA检查CS时序发WAKE偶尔读错字节SPI速率过高走线过长信号反射降低预分频加串阻优化走线写不进去没有写使能WP#拉低块保护开启每次写前发WREN检查WP引脚和状态寄存器掉电后数据丢失PVD阈值太低储能电容不足提高PVD阈值增加电容容量偶发数据翻转还要注意电源质量。MRAM内部是磁存储对辐射干扰不像Flash对手写干扰那么敏感但如果供电电压抖动超过数据手册要求的范围还是可能造成读写时序混乱。我在电源输入端加了一颗0.1μF和一颗10μF电容并且避开大电流走线问题就没有再出现过。5.4 焊接与温度影响工业级芯片对焊接工艺有要求。MRAM虽然是磁存储理论上没那么娇气但焊接温度过高或反复加热超过datasheet规定的峰值时间应力可能影响内部结构。手工焊接时用恒温烙铁350℃左右每脚停留不超过3秒回流焊则严格按照规格书推荐的温度曲线。我有一批板子做环境试验-40℃到85℃循环500次MRAM数据全程没有出现异常。另外MRAM在使用时如果靠近强磁场源比如大功率电感、变压器绕组要考虑磁屏蔽或者调整布局。虽然MTJ的矫顽力经过优化普通工业环境的磁场不会影响但在设计阶段避开强磁元件总归更稳妥。我亲眼见过一个客户把MRAM贴着功率电感放日志数据偶尔错乱拉开距离后故障消失。5.5 调试速查表最后整理一张平时调试直接对照的表方便新手快速定位问题检查项预期结果常见错误VDD引脚2.7V ~ 3.6V供电不足导致复位WP# / HOLD#上拉到VCC悬空导致随机故障CS#时序命令期间保持低电平提前拉高导致命令不完整SPI时钟极性Mode 0CPOL0, CPHA0模式不匹配读回全0xFF先写使能再写数据每次写前发WREN漏发WREN导致写入无效地址范围0x000000 ~ 0x07FFFF超范围地址回绕导致数据写错位置读状态寄存器WIP位清零后操作完成不轮询WIP紧接着发下一条写命令最后再说一个我自己的使用体会MR25H40CDF和STM32F415ZG这套组合最值钱的不是某一条指令或某一个外设而是它把“存储可靠性”从需要小心翼翼维护的事情变成了可以完全信任的背景条件。写日志不用考虑磨损掉电保存不用抢时间固件升级掉电也不会变砖省出来的精力全都可以投入到业务逻辑本身。我另一块板子把同样的驱动代码从STM32F4移植到STM32G0改动量几乎为零这也算是SPI接口MRAM的一个隐性红利。如果你手头的项目正好卡在Flash寿命或者掉电保存的瓶颈上MRAM绝对值得试一次。
返回列表