ARTICLE DETAIL

资讯详情

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

基于STM32与SPI MRAM的工业级掉电数据保存方案

基于STM32与SPI MRAM的工业级掉电数据保存方案 去年做一套工业数据采集终端的时候被“掉电数据保存”这件事折腾得够呛。现场要求设备状态、校准参数、故障日志都不能丢而且允许的技术人员一天改几十次参数。最开始用的 SPI EEPROM写一个字节要等内部擦写改一次参数列表卡顿明显换过 SPI NOR Flash写之前还得先擦整个扇区日志循环写根本扛不住。后来换到 MR25H40CDF 这颗 SPI MRAM配合手头现有的 STM32F302VC 主控整个存储链路终于清净了。这篇就把这个组合从选型、电路、驱动到现场排障的完整过程整理出来给同样在做工业和嵌入式数据存储的朋友做个参考。先说结论如果你也需要非易失存储、但不想处理擦写寿命、不想等页编程时间、又希望容量比常见 FRAM 大一点MRAM 这颗芯片会是很好的选择。MR25H40CDF 是 Everspin 的 4Mbit SPI MRAM容量 512KB接口简单读写速度接近 SRAM 的水平而且写次数基本不限制。STM32F302VC 是带 Cortex-M4F 内核的工业级 MCU72MHz 主频SPI 接口足够把这颗 MRAM 跑满。整套方案体积小、成本可控、代码量也不大非常适合工业控制器、仪表、能源管理设备这类场景。1. 为什么要在这种场景里选MRAM而不是EEPROM或Flash1.1 工业数据存储的三个硬指标工业设备里的数据存储和消费电子产品不太一样。消费级产品丢一两条日志顶多影响体验工业设备丢了校准参数可能整台设备直接不在精度范围内甚至停机。我归纳下来有三个硬指标第一是掉电不能丢。设备随时可能在写数据的中途断电存储介质必须是非易失的而且写入过程不能依赖一个漫长的等待窗口。第二是寿命要够长。产线设备一天可能记录几千条事件一个月就是十几万次写入。传统 EEPROM 标称擦写寿命常见值是 10 万次到 100 万次听上去不少但实际跑起来如果写策略设计得不好几个月就可能摸到寿命上限。第三是写入速度要跟得上现场节奏。工业触摸屏改一次参数上位机下发一组数据存储芯片必须在几十毫秒内完成应答不能让人感觉界面卡住。1.2 EEPROM、NOR Flash、FRAM各自卡在哪里EEPROM 的问题是慢和寿命。经典的 I2C EEPROM 写一个字节内部要经历一个擦写周期典型时间是 5 到 10 毫秒写 128KB 数据理论上要几十秒更别提频繁写入对寿命的消耗。SPI 接口的 EEPROM 好些但本质还是一个字节级别擦写的结构写大块数据依然受内部状态机限制。NOR Flash 的问题是擦除粒度太大。W25Q128 这类芯片写之前必须先擦扇区一个扇区通常是 4KB擦除时间动辄几百毫秒。你只想改其中 16 个字节的参数也得先读出整个 4KB、擦除、再写入。日志类应用更别扭因为写日志是高频小量追加Flash 的分区管理和磨损均衡做不好很快就出问题。FRAM 其实是一个很好的选择无限次写入、掉电不丢、速度也快。但 FRAM 的问题在于容量和价格常见型号容量段偏小做到 4Mbit 级别的 FRAM 选择很少单价也更贵。1.3 MRAM 的核心优势正好补上这几个缺口MRAM磁阻随机存取存储器的原理是用磁性隧道结来存储数据数据以磁化方向的形式存在而不是电荷形式。这意味着它天生具备三个特性掉电后磁性状态不会消失所以是非易失的写入过程是物理上改变磁化方向不需要先擦除所以没有写前擦除这个动作磁化翻转本身没有机械磨损和化学衰减写次数可以做到极高水平。MR25H40CDF 这颗芯片把这三个特性落到了具体产品里。容量 512KB对工业设备来说既能存参数表也能存一段时间的运行日志。SPI 接口让 MCU 侧接线非常直接不需要额外控制器。写一个字节和写连续数据块都不需要等待内部擦写完成只要 SPI 时钟在跑数据就进去了。2. MR25H40CDF 关键特性与选型依据2.1 容量、接口与封装细节MR25H40CDF 的容量是 4Mbit组织方式是 512K x 8bit。外部通过标准 SPI 接口访问支持 Mode 0CPOL0CPHA0和 Mode 3CPOL1CPHA1两种模式这点和大部分 SPI NOR Flash 兼容主控侧配置起来几乎零成本。封装是 8 引脚 DFN尺寸很小适合空间受限的工业板卡。供电电压典型值 3.3V工业级版本的工作温度范围通常能覆盖 -40℃ 到 105℃左右具体要以选定的批次为准。对户外机柜、设备内部靠近发热源这类场景来说这个温度范围比很多商用级存储芯片稳妥。需要注意的一个规格点是最高 SPI 时钟。MR25H40CDF 在 3.3V 供电下支持到几十 MHz 级别具体数值以官方数据手册为准。实际使用时我建议留出至少 2 到 4 倍裕量不是因为芯片跑不上去而是工业现场走线长、干扰多把时钟降下来对信号完整性非常有利。2.2 命令集速查比 Flash 简单得多MR25H40CDF 的命令集和 SPI NOR Flash 有点像但去掉了擦除命令。最常用的可以列一个表命令名命令字作用WREN0x06写使能写操作前必须发送WRDI0x04写禁止关掉写使能状态RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03从指定地址读数据可连续读WRITE0x02向指定地址写数据可连续写WREN 这个命令和 Flash 一样每次写数据之前都要先发一次写使能。这是最容易漏掉的一步漏掉之后写操作会被芯片忽略读出来的还是旧数据和全 0xFF 之类的内容。地址字段是 24 位但芯片有效地址范围只有 0x000000 到 0x07FFFF也就是 512KB。超出范围的地址会被截断或者回卷这一点后面会专门说因为现场出过问题。2.3 状态寄存器与写保护机制状态寄存器是 8 位上电默认是 0x00代表全片可写。寄存器里主要包含写保护使能位WPEN和块保护位BP2:BP0和外部 WP 引脚配合使用。生产环境中如果设备参数写好后不希望现场人员误改可以把块保护位置 1这样指定区域就不能写了。但我个人建议在开发调试阶段不要开保护因为每次改固件还要先去解除保护麻烦。量产出货前再根据实际要求决定要不要把整个存储区锁住。写状态寄存器本身也需要先发 WREN 命令流程是WREN然后发送 WRSR 命令和新的状态寄存器值。如果 WP 引脚被拉低写状态寄存器的操作会被硬件禁止这个机制和 SPI Flash 的写保护引脚类似。2.4 一个容易踩的坑连续读写时地址回卷这是 MR25H40CDF 使用中最容易忽略的行为。普通 SPI NOR Flash 在跨页读或写时有些芯片会在页边界停止有些会回卷。MR25H40CDF 的连续读写如果越过 0x07FFFF地址会自动回到 0x000000中间不会有任何报错。这意味着如果你的软件没有在驱动层做边界检查写日志写到最后一个字节后继续追加数据会直接写到文件头的位置把最早的参数覆盖掉。我在第一版驱动里没考虑这个问题结果运行日志模块把校准参数区域覆盖了整台设备的数据全乱套。后来驱动里加了严格的地址范围检查任何越界写入都直接返回错误码不再向下传输数据。3. STM32F302VC 侧硬件接线与电路设计3.1 STM32F302VC 的资源回顾STM32F302VC 是 ST 的工业级 MCUCortex-M4F 内核主频 72MHz带浮点运算单元。存储资源上它有 256KB 内部 Flash 和几十 KB SRAM跑一个中等复杂度的控制固件没有问题剩下的内部资源刚好可以用来接外部存储。外设方面F302VC 有多个串口、CAN、SPI、I2C、ADC 和 DAC 资源。对工业设备来说主控负责采样、通信、控制逻辑外部 MRAM 负责参数和日志任务边界非常清楚。选这颗 MCU 的另一个原因就是它和 MRAM 都是工业温度等级整块板子的温区不需要降档。3.2 SPI1 引脚映射与最小接线我在这套设计里用的是 SPI1因为 SPI1 在 F3 系列里挂在高速外设总线上时钟可以跑得比 SPI2 更高给以后提速留了空间。标准引脚映射是STM32F302VC 引脚功能接到 MR25H40CDFPA5SPI1_SCKSCKPA6SPI1_MISOMISODOPA7SPI1_MOSIMOSIDIPA4普通GPIOCS片选CS 我没有用硬件 NSS而是用了一个普通 GPIO 来软件控制。原因是软件控制 CS 的时序更灵活可以严格保证事务开始和结束都在正确的电平上不受 SPI 外设内部状态机影响。MRAM 的 HOLD 引脚接上拉到 VDDWP 引脚在不需要写保护时也接 VDD两个引脚都不能悬空。3.3 电源与去耦设计MRAM 供电用 3.3V建议在电源引脚旁边放一个 100nF 陶瓷电容和一个 4.7uF 电解电容位置尽量靠近芯片。工业板卡如果有大的继电器、电机等负载电源线上容易有毛刺可以在板级入口再加一个 TVS 管或者共模电感把浪涌挡在存储芯片之前。另外要强调的是接地。MRAM 和 MCU 之间的 SPI 走线属于高速数字信号信号回流路径必须经过完整的地平面。如果板上地平面被槽孔切开或者 SPI 走线绕了一个大圈MISO 信号在高时钟下可能采到错误的电平。我第一次画板就是图好看把 SPI 线拉得很长绕过了一个连接器结果 18MHz 时钟下数据偶发错误降频到 4.5MHz 才稳定后来重新布线才彻底解决。3.4 布局布线经验三条第一条SPI 四根线尽量并行等长长度控制在 5cm 以内。第二条CS 线可以稍微长一点但不能和 SCK 靠太近避免 CS 切换瞬间串扰到时钟。第三条如果 MISO 线上有明显的噪声毛刺可以在 MCU 引脚附近串联一个 33 到 100 欧姆的电阻或者在 MISO 和地之间加一个 10pF 到 20pF 的小电容牺牲一点上升沿换稳定。4. SPI 驱动完整实现与读写流程4.1 SPI 外设初始化配置我用的是 HAL 库初始化 SPI1 为模式 0、主模式、软件 NSS、8 位数据。关键的几个参数如下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_8; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; HAL_SPI_Init(hspi1);BaudRatePrescaler 选 8也就是把 SPI1 时钟 8 分频。如果 SPI1 的总线时钟是 72MHz那 SPI 时钟就是 9MHz。这个频率读写 512KB 数据大约几十毫秒对参数存储场景绰绰有余而且信号裕量很足。还有一个必须做的事是确认 SPI 引脚在 GPIO 初始化时配置为复用功能。很多人初始化完 SPI 外设忘了把 PA5、PA6、PA7 的 GPIO 模式设成 GPIO_MODE_AF_PP结果 SCK 根本没有时钟输出查半天查不出来。4.2 基础命令封装写使能与状态寄存器操作先写一个最简单的写使能函数它是一切写操作的前提#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) void MRAM_WriteEnable(void) { uint8_t cmd 0x06; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 10); MRAM_CS_HIGH(); }注意 CS 的操作方式是把每次事务隔离起来。发送写使能命令时 CS 拉低、发送完立即拉高形成完整的一个命令周期。不要在一次 CS 拉低周期里既发 WREN 又发后续写命令虽然有些芯片能容忍但严格的时序习惯可以避免很多潜在问题。读状态寄存器的函数类似只是需要用一个假的发送字节把 MISO 上的数据带出来uint8_t MRAM_ReadStatus(void) { uint8_t cmd 0x05; uint8_t dummy 0x00; uint8_t status 0x00; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 10); HAL_SPI_TransmitReceive(hspi1, dummy, status, 1, 10); MRAM_CS_HIGH(); return status; }初始化阶段我习惯先读一次状态寄存器确认能读到数值而不是全 0xFF。如果读出来全 0xFF基本上就是硬件没接对或者芯片供电有问题。4.3 数据读写函数从单字节到连续块单字节写和连续块写本质上是同一个动作。写操作流程是CS 拉低发送 WRITE 命令0x02再发送 24 位地址高字节在前然后发送数据字节。写单个字节之后 CS 拉高写多个字节时CS 可以在整个数据块发送完后再拉高。void MRAM_WriteData(uint32_t addr, uint8_t *buf, uint32_t len) { MRAM_WriteEnable(); uint8_t header[4]; header[0] 0x02; header[1] (addr 16) 0xFF; header[2] (addr 8) 0xFF; header[3] addr 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, header, 4, 20); HAL_SPI_Transmit(hspi1, buf, len, 100); MRAM_CS_HIGH(); }这里有一个关键点MRAM 的写操作没有“页缓冲”的概念不需要像 NOR Flash 那样按页对齐。理论上只要 CS 保持低电平可以连续写任意长度的数据芯片会按地址自动递增。实际写大量数据时我会强制每次调用不超过剩余有效地址长度比如先算出当前地址到 0x07FFFF 还剩多少字节超过部分分两次写完。这主要是防地址回卷不是芯片有什么限制。读数据流程更简单不需要写使能void MRAM_ReadData(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t header[4]; header[0] 0x03; header[1] (addr 16) 0xFF; header[2] (addr 8) 0xFF; header[3] addr 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, header, 4, 20); HAL_SPI_Receive(hspi1, buf, len, 100); MRAM_CS_HIGH(); }HAL 的 HAL_SPI_Receive 在接收时会自动发全 0 字节来产生时钟所以不需要额外维护 dummy 缓冲。这个函数在速度上不如 DMA 版但数据量不大时已经足够用。4.4 掉电保存策略与事务状态机MRAM 本身掉电不丢但“保存数据”这件事不能只依赖芯片还得保证写入过程的完整性。我的做法是在应用层设计一个简单的保存协议先写数据区再写一个 8 字节的完整标志区。标志区包含固定魔数、数据长度、CRC 校验值。上电读取时先读标志区魔数正确且 CRC 校验通过才认为数据区有效否则回退到备份区。这套协议解决了一个实际问题如果写入过程中系统掉电MRAM 内部可能已经写入了部分数据但标志区的魔数和 CRC 永远不会被写完整。下次上电读到一个不完整的标志就知道这次保存失败了不会拿残缺数据当有效参数用。另一个值得推荐的策略是双备份参数区放两份每次写入交替选择当前活动备份。这样即使某次写入把当前备份区写到一半断电另一个完整的备份还在系统可以自动回滚。代价是芯片空间多占一倍对 512KB 来说完全可接受。5. 现场实测、遇到的问题与排查技巧5.1 一个实际案例数据采集终端的存储分区我做过的那套采集终端MRAM 的 512KB 空间这样分配的前 64KB 放参数区分 A/B 两份每份 32KB中间 128KB 放运行日志采用环形覆盖末尾留作预留和固件配置信息。日志每 10 秒写一条每条约 32 字节128KB 能存 4000 多条覆盖一个多月的数据足够回溯现场了。这个分区方式的好处是日志区和参数区完全隔离即使日志环形写到了边界也不会影响参数区。上一版用 NOR Flash 时日志区的磨损均衡和参数备份是耦合在一起的代码复杂度高还出过擦除时序干扰导致参数丢失的问题。换 MRAM 之后这个问题从根上消失了。5.2 现场问题一写进去的数据读出来全是 0xFF第一次拿到新板子烧好代码后发现写进去再读出来全是 0xFF。排查过程并不复杂先量了 MRAM 供电电压3.3V 正常再用逻辑分析仪看 SPI 波形发现 MOSI 上确实有命令和数据但 MISO 一直是高电平。最后定位到原因PA6 的 GPIO 模式没有配置成复用功能导致 MISO 引脚一直是高阻态主控读回来的全是 1。这是一个很低级的错误但也很容易犯。后来我把 SPI 引脚的 GPIO 配置检查放进了初始化代码的断言里硬件调试时先通过一段自检代码把四个引脚的复用功能逐一确认才在第 一个环节堵住类似问题。5.3 现场问题二日志写到一半参数区被覆盖这个问题的根源就是前面说的地址回卷。日志模块用的是环形追加指针走到 0x07FFFF 之后继续写MR25H40CDF 直接把地址回卷到 0x000000开始往参数区覆盖。参数区的魔数被冲掉后设备重启时参数校验失败整机处于异常状态。修复方案很直接驱动层加了范围检查所有读写函数在进入 SPI 事务之前先判断地址和长度。如果 addr len 超过 0x080000函数直接返回错误码不再发任何 SPI 命令。应用层收到这个错误码会把日志指针重置到 0x10000开始新一轮环形覆盖。从那以后再没有出现过跨界覆写。5.4 现场问题三高低温循环后偶发读写失败设备在温箱里做 -40℃ 到 85℃ 循环测试时偶尔出现一次读数据超时概率大约千分之几。起初怀疑 MRAM 芯片本身有问题后来查下来是电源问题。低温下电源模块输出下跌到 3.0V 左右MRAM 工作在临界电压附近SPI 高电平阈值也不稳定导致通信失败。解决方法是两件事一是把 SPI 时钟从 18MHz 降到 9MHz给信号电平留出更多裕量二是在 MRAM 供电附近加了一个 100nF 的低 ESR 陶瓷电容改善瞬态响应。调整后温箱跑了一周没有再出现故障。这个案例让我养成一个习惯新设计中使用任何存储芯片第一步就是在极限温度下做读写压力测试用全 0x55、全 0xAA、递增地址模式各跑一轮确认芯片在最差条件下依然稳定。5.5 常见问题速查表我把这个组合调试过程中的常见问题整理成一个表方便现场快速定位现象可能原因排查方向读回全 0xFFSPI 引脚复用未配置检查 GPIO 复用设置用示波器看 MISO 波形写入后读回全 0x00WREN 未发送或 CS 时序不对检查写使能流程确认 CS 低电平时长足够数据偶发错位地址越界发生回卷检查读写函数边界判断高低温下偶发失败电源裕量不足加强电源滤波降低 SPI 时钟待机电流偏高MRAM 未进入睡眠模式发送 SLEEP 命令确认 CS 时序读状态寄存器超时MISO 线上有干扰串阻或小电容滤波检查走线长度5.6 低功耗场景的睡眠命令如果整机是电池供电或者有严格的待机功耗要求MR25H40CDF 的睡眠模式就很有用。发送 SLEEP 命令0xB9后芯片进入低功耗状态静态电流明显下降。唤醒需要发送 WAKE 命令0xAB唤醒后要等一小段时间才能继续正常读写。我实际测试下来睡眠模式对电池供电的手持设备很友好。主控进入 STOP 模式前先发 SLEEP 命令把 MRAM 也休眠整机待机电流比不睡眠时低得多。需要注意的是睡眠状态下的芯片不会响应常规的 READ 命令所以软件里要处理好唤醒时序不要刚发完 WAKE 就立刻读数据留出足够的时间窗口。6. 这套方案的扩展思路与量产经验6.1 把 MRAM 当普通 RAM 用MRAM 的读写速度和随机访问能力让它不只是参数存储器。我在后面的项目里扩展了一种用法运行中的关键状态变量镜像一份到 MRAM 的固定区域。正常运行时变量更新后同步写入 MRAM设备意外掉电后重新上电直接从 MRAM 恢复这些变量相当于掉电记忆。这种用法在传统 EEPROM 上根本不现实因为频繁写寿命受不了在 NOR Flash 上更不可能因为要擦除。用在 MRAM 上就很自然写次数不是瓶颈速度也够快。比如设备正在执行的运动流程掉电后恢复时可以直接从断点继续不必每次都从头开始。6.2 量产烧录与校验注意点批量生产时存储芯片里要烧录初始化数据。我的产线流程是先用一个单独的量产工具通过 SPI 把默认参数和魔数写入 MRAM再上电跑一遍自检读取并 CRC 校验。如果自检不过直接判定为不良品避免流到现场。这个环节还要注意产线的静电防护。DFN 封装芯片引脚小插拔和贴片过程中容易积累静电。如果产线干燥建议在测试工装接口加 ESD 保护器件操作人员佩戴防静电手环。我曾经在冬天遇到过一批芯片读写不稳定排查到最后是测试工装上静电积累导致的加了防护后问题立刻消失。6.3 芯片供应与替代方案MR25H40CDF 不是唯一选择。如果你需要更大容量可以看同系列的更高密度型号如果需要 1.8V 供电的版本也有对应产品线。选型时主要核对三点容量、电压、温度等级其他引脚兼容性基本一致。不过我不建议轻易在量产项目里临时替换存储芯片。不同厂家的 MRAM/FRAM 虽然都叫 SPI 非易失存储但命令集、状态寄存器定义、唤醒时间都有差异驱动移植和温度测试都要重新做。一个项目里验证过的方案除非有明确的成本或供货压力否则保持稳定更划算。回头看我在这套方案上踩过的坑最值钱的教训其实不是某一条命令怎么发而是“不把存储当普通外设对待”。MRAM 让好多存储管理问题消失了但地址边界、掉电一致性、低温电压裕量这些工程问题一个都不会少。把这些细节处理好MR25H40CDF 加 STM32F302VC 的组合在工业设备里会非常省心。
返回列表