资讯动态

基于MR25H40CDF MRAM与STM32F413RH的嵌入式数据存储方案实战

发布时间:2026/10/4 7:41:57 来源:尧图企业网站定制
做嵌入式最烦的一件事就是数据到底该存哪。以前用 EEPROM容量小不说写多了还怕掉寿命用 NOR Flash擦除慢、还得分区管理。最近我在一个工业数据采集项目里换了思路用 MR25H40CDF 这颗 SPI 接口的 MRAM 搭配 STM32F413RH 来做存储与读取数据效果出乎意料地顺。这套方案不仅能掉电保存运行参数、故障日志还能承受高频次写入非常适合那些需要连续记录设备状态的嵌入式场景。如果你正在为存储选型纠结或者想把现有 EEPROM、Flash 方案升级成高速非易失存储这篇实战记录可以给你不少参考。1. 项目背景与存储选型思考1.1 工业应用为什么需要这种存储组合在工业现场设备运行状态的可追溯性越来越重要。比如数控机床需要记录主轴电流、进给速度、报警代码PLC 控制系统要保存掉电瞬间的输入输出状态传感器节点要定期存储采集到的温度、振动、压力数据。这些数据的特点就是“高频写入、小包突发”而且不能因为突然断电就消失。我用 STM32F413RH 做主控采集各种工业协议的数据比如 Modbus RTU、Modbus TCP甚至用 OPC UA 网关把 PLC、传感器、数控机床的运行状态数据统一收上来。解析之后一部分数据用于实时判断设备是否健康另一部分则要完整保存下来方便后期回溯和分析。这就对存储介质提出了几个硬性要求写入速度要快因为采集频率可能很高掉电不能丢数据因为现场不可能随时优雅关机写入寿命要长因为工业设备动辄运行十多年普通 EEPROM 根本扛不住持续写日志。MRAM 恰好是这几个需求的交集。MR25H40CDF 是 Everspin 的 4Mbit 串行 MRAM容量虽然不算夸张但作为配置存储和日志缓冲完全够用。更关键的是它可以在不擦除的情况下逐字节改写真正意义上的无限多次写入写入速度又接近 SRAM这让它在工业应用里显得相当“皮实”。搭配 STM32F413RH 这种带多个 USART、SPI、CAN 外设主频 100MHz 的 Cortex-M4F 芯片数据从采集到存储再到转发整条链路都能跑得很顺畅。1.2 MR25H40CDF 与 EEPROM、NOR Flash 的对比很多人第一反应是为什么不用 FRAM为什么不用更便宜的 SPI NOR Flash我实际对比过各有优劣但在“高频率随机写 掉电保存”这个场景下MRAM 的性价比反而最合理。项目MR25H40CDF (MRAM)常见 I2C EEPROMSPI NOR Flash接口SPI最高约 40MHzI2C通常 1MHzSPI可达 104MHz 读写慢写入方式直接写入无需擦除直接写入但按页写先擦除再写页/扇区操作写寿命极长无限次可写通常 100 万次/字节通常 10 万次擦写随机写速度微秒级毫秒级擦除几百毫秒页写毫秒级数据保持 20 年 100 年通常 20 年典型容量4Mbit2Kbit ~ 1Mbit8Mbit ~ 128Mbit从表里能看出来EEPROM 的问题是太慢了而且容量做不大NOR Flash 的问题是写前必须擦除如果用在做实时日志记录需要一套复杂的擦写均衡算法否则某个扇区被写穿就只能返修。而 MRAM 就像一颗“不会丢的 SRAM”你往地址写多少就保存多少不需要管擦写块更不用做擦写均衡代码负担小很多。当然MRAM 也不是没有缺点。它的成本比 EEPROM 和 NOR Flash 高容量目前在工业常用型号里偏小适合保存关键数据和短期日志不适合存大文件。如果项目需要几百兆的存储那还是老老实实挂 SD 卡或 eMMC。但在这类嵌入式数据记录场景里MR25H40CDF 作为“快照区”和“日志区”定位非常准确。1.3 STM32F413RH 这颗 MCU 的优势STM32F413RH 这款芯片是 LQFP64 封装属于 STM32F413 系列主频最高 100MHz带硬件 FPU 和 DSP 指令内置 Flash 能到 1.5MBSRAM 有 320KB。拿它做协议解析和数据处理性能远高于传统 168MHz 级的单片机吗其实 Cortex-M4F 在这个定位上主要赢在执行效率和浮点运算尤其是在处理传感器浮点数据、PID 计算和 Modbus 报文解析时优势很明显。但对我来说选 STM32F413RH 更看重的是它的外设资源。它有多个 SPI 接口其中一个专门用来接 MRAM完全不会占用调试用的 SWD 引脚同时还有多个串口和 CAN 口方便同时接 PLC、传感器、触摸屏和上位机。而且这个系列在 STM32CubeMX 里支持非常好初始化代码直接生成驱动层我再封装一层操作的存储接口就行不用从寄存器一点点抠。还有一点很实用STM32F413RH 的 SPI 时钟可以配到 50MHz虽然 MR25H40CDF 标称最快大约 40MHz但能把时钟余量留出来SPI 通信更稳定。再加上它内部有丰富的中断优先级我可以把掉电检测、SPI 传输、Modbus 帧处理分开避免在紧急情况下互相干扰。2. 硬件电路与接口设计2.1 芯片引脚分析与最小系统连接MR25H40CDF 是一个标准的 8 脚 SPI MRAM引脚不多接起来很直观。核心引脚包括CS#片选、SCK时钟、SI数据输入、SO数据输出、WP#写保护、HOLD#保持、VCC 和 GND。我把 MR25H40CDF 挂到了 STM32F413RH 的 SPI3 上连接关系是这样的SCK 接 PB3SPI3_SCKSIMOSI接 PB5SPI3_MOSISOMISO接 PB4SPI3_MISOCS# 接 PD3这里我用普通 GPIO 控制不用硬件 NSS。这样片选时序完全由软件保证后续调试时序问题更方便。WP# 引脚我直接通过 10k 电阻上拉到 VCC默认使能写保护不冲突因为每次写入前我会先发 WREN 指令打开写使能WP# 保持高电平不会影响这个操作。如果你希望永远不保护也可以直接接 VCC。HOLD# 同样上拉到 VCC保证存储器不会因为外部干扰误进入保持模式。电源部分给 MRAM 单独加了 0.1uF 和 1uF 两个去耦电容尽量靠近 VCC 引脚。工业环境里电源波动会比较明显去耦电容不能省。为了防止 SPI 信号线上的过冲我在 SCK、SI、CS# 这几根信号线上串联了 22 欧姆的电阻虽然 MRAM 频率不算极致但加上之后波形明显干净很多。2.2 原理图设计的坑看着这么简单的连接其实原理图里有几个容易踩坑的地方。第一个坑是封装。MR25H40CDF 的后缀“CDF”我理解是 DFN 封装而不是普通 SOIC。DFN 封装小而薄PCB 布局必须留好散热焊盘焊接时温度控制要格外小心。如果手工贴片建议先涂好锡膏用热风枪吹焊焊完用万用表量一下各引脚对地电阻避免虚焊。第二个坑是 WP# 和 HOLD# 不能悬空。有些工程师图省事觉得这两个脚不接也能跑实际上悬空的输入脚在工业现场很容易受干扰偶发进入保持模式或者写保护状态导致数据读不出来、写不进去。这两个引脚必须接上拉电阻这是我在实板调试时吃过大亏才记住的。第三个坑是 SPI 信号长走线。如果 MRAM 和 MCU 分板放置中间用排线连接那么 20MHz 以上的 SPI 信号很容易反射读回来的数据会随机出错。我建议尽量让 MRAM 靠近 MCU走线控制在 5cm 以内。如果实在要跨板必须在两端加上缓冲器或者降低 SPI 时钟到 5MHz ~ 8MHz 来换取可靠性。2.3 电气隔离与防护细节在工业环境里如果 MRAM 所在的控制板要和外部强电设备共处那就必须考虑浪涌和静电问题。常规做法是在 SPI 信号的输入端并联 TVS 管并在电源入口加磁珠和 TVS。不过我这里 MRAM 是直接焊在主板上和 MCU 同一块 PCB所以外部风险主要靠整板的电源防护解决了。需要注意的是如果 MCU 板与传感器板之间通过长线连接传感器板上的静电可能通过地线回流产生瞬间电位差。这时候在 MCU 和 MRAM 之间加隔离就不太现实因为 SPI 隔离芯片会拖慢时序。所以较好的办法是保证整机可靠接地把 SPI 线远离继电器、接触器等开关器件。3. 软件驱动与存储层实现3.1 SPI 初始化配置要点软件上第一步是初始化 STM32F413RH 的 SPI3。我习惯用 STM32CubeMX 生成基础代码然后在生成的MX_SPI3_Init()里调整参数。以我最终使用的配置为例SPI_HandleTypeDef hspi3; void MX_SPI3_Init(void) { hspi3.Instance SPI3; hspi3.Init.Mode SPI_MODE_MASTER; hspi3.Init.Direction SPI_DIRECTION_2LINES; hspi3.Init.DataSize SPI_DATASIZE_8BIT; hspi3.Init.CLKPolarity SPI_POLARITY_LOW; hspi3.Init.CLKPhase SPI_PHASE_1EDGE; hspi3.Init.NSS SPI_NSS_SOFT; hspi3.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_4; hspi3.Init.FirstBit SPI_FIRSTBIT_MSB; hspi3.Init.TIMode SPI_TIMODE_DISABLE; hspi3.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; if (HAL_SPI_Init(hspi3) ! HAL_OK) { Error_Handler(); } }这里有个非常重要的细节CPOL 和 CPHA 必须匹配 MRAM 的工作模式。MR25H40CDF 支持 SPI Mode 0CPOL0, CPHA1和 Mode 3CPOL1, CPHA1但我这里写的是 CPOL0, CPHA1也就是 Mode 0 配置空闲时时钟为低数据在第一个边沿采样。如果你的代码写得是 Mode 1读出来的数据大概率是乱的。我在调试中见过很多人一开始忽略了这两个参数浪费了不少时间。时钟频率我也没敢直接拉满。虽然 MRAM 理论上能跑 40MHz但为了让 SPI 数据稳定我用的是主时钟 100MHz 除以 4也就是 25MHz。这个频率在普通 PCB 布线下已经很快了实际测试读写正确率 100%。如果你的项目追求极致速度可以试试除以 2但一定要用逻辑分析仪看波形确认没有过冲。3.2 MR25H40CDF 指令系统速览MR25H40CDF 是 SPI 接口的标准 MRAM指令集非常简单属于那种看一遍数据手册就能记住的类型。最常用的几条指令是06hWREN写使能。每次写入或者写状态寄存器前必须先发这条。04hWRDI写禁止。一般不常用但可以用来防止误写。05hRDSR读状态寄存器。01hWRSR写状态寄存器。03hREAD读数据。02hWRITE写数据。这里比 Flash 简单太多了。Flash 写数据之前要 EraseMRAM 不需要直接往任意地址写字节就行。哪怕是同一个地址反复写一万次它也能承受。这在软件设计上省下了巨大心力。来看一段最简单的读函数void MRAM_Read(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4] { MRAM_CMD_READ, (uint8_t)(addr 16), (uint8_t)(addr 8), (uint8_t)(addr 0xFF) }; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi3, cmd, 4, 100); HAL_SPI_Receive(hspi3, buf, len, 1000); MRAM_CS_HIGH(); }注意地址是 24 位格式但 MR25H40CDF 容量只有 4Mbit也就是 512KB地址空间 0x000000 到 0x7FFFF。高字节地址 0x00 其实就够了但为了指令格式的完整性我还是发送三个地址字节这样以后如果换更大容量的 MRAM驱动也能无缝兼容。写函数稍微复杂一点但也不难void MRAM_Write(uint32_t addr, const uint8_t *buf, uint32_t len) { uint8_t cmd[4] { MRAM_CMD_WREN }; // 发送 WREN 指令 MRAM_CS_LOW(); HAL_SPI_Transmit(hspi3, cmd, 1, 100); MRAM_CS_HIGH(); // 发送 WRITE 指令 地址 数据 cmd[0] MRAM_CMD_WRITE; cmd[1] (uint8_t)(addr 16); cmd[2] (uint8_t)(addr 8); cmd[3] (uint8_t)(addr 0xFF); MRAM_CS_LOW(); HAL_SPI_Transmit(hspi3, cmd, 4, 100); HAL_SPI_Transmit(hspi3, (uint8_t *)buf, len, 1000); MRAM_CS_HIGH(); }这里有个细节WRITE 指令发出后只要 CS# 保持低电平就可以连续写入任意长度的数据。MRAM 没有页限制这是它跟 Flash 最舒服的区别。你可以像操作普通 RAM 一样一次性写入 1 字节到 512KB 都行只要地址别溢出。实际测试下来25MHz SPI 时钟下一次写入 1KB 数据的时间大概就是几百微秒如果按字节写也很快。跟 EEPROM 那种每页几毫秒的体验完全不是一个级别。3.3 状态寄存器与写保护MR25H40CDF 有一个状态寄存器里面包含 WPEN、CMP、BP1、BP0、WEL、WIP 等位。大多数时候我们根本不用管这些因为默认状态是块保护禁用、写使能不锁定、写入不查询忙标志。不过我要提醒一个点WIP 位Write In Progress在 MRAM 上通常不需要轮询因为写入速度太快指令发完数据就已经写完了。但如果你是靠软件延时来控制 CS# 时序还是建议写一个简单函数读一下状态寄存器的 WEL 位确认写使能被正确置位避免因为时序问题导致数据没写进去。另外如果实际应用中担心某个存储区域被意外改写可以设置状态寄存器里的块保护位。比如我只想保护前 64KB就把 BP0 和 BP1 组合设置为保护范围。这个功能对保留出厂校准等固定数据很有用。3.4 封装为通用存储 API驱动底层是 SPI 读写真正用起来肯定不能直接在应用层调MRAM_Write()这种原始函数需要封装成更友好的存储接口。我在项目里建立了一个mram_drv.c对外提供以下接口void MRAM_Init(void); void MRAM_ReadBytes(uint32_t addr, uint8_t *buf, uint32_t len); void MRAM_WriteBytes(uint32_t addr, const uint8_t *buf, uint32_t len); void MRAM_EraseAll(void); // 其实就是把全地址写0xFF省得客户以为要擦除然后我又基于这一层做了两个上层模块一个是参数存储模块用来存设备配置、校准系数另一个是日志存储模块用环形缓冲方式记录最近一段时间的事件。这样应用层完全不需要关心存储介质是什么以后想换成 FRAM 或者 Flash 只需要修改底层驱动上层文件和接口不用动。4. 工业场景实战记录设备状态与掉电保护4.1 需求说明采集 PLC/传感器数据并判断设备状态这个项目的实际需求是用 STM32F413RH 作为一款工业数据采集网关通过串口和以太网同时采集 PLC、传感器以及数控机床的数据。比如 PLC 侧用 Modbus RTU 读取寄存器机床侧用自定义 TCP 协议读取运行状态传感器用 485 接口轮询采集数值。拿到这些原始数据后我在 STM32F413RH 上做一轮简单的逻辑判断比如比较当前电流是否超过阈值转速是否在合理区间通信是否超时。判断出的结果正常、警告、故障会连同原始数据一起打包成一条记录写入 MR25H40CDF。上位机后面再通过 Modbus TCP 或 OPC UA 来读取这些记录用于大屏展示、故障分析或者归档。这个场景完美的用上了 MRAM 的高写入寿命。因为设备可能每 100 毫秒就要存储一条运行记录一天下来就是几十万条如果用带擦除限制的 Flash可能几个月就把某个扇区写穿了。而 MRAM 几乎可以无视这个问题。4.2 数据格式与环形缓冲区设计我把存储区划分成两个区域参数区从地址 0x00000 开始长度 64KB用来保存设备配置和校准数据日志区从地址 0x10000 开始按环形队列方式写入。日志区的记录格式设计得非常紧凑typedef struct { uint32_t timestamp; // 时间戳 uint16_t device_id; // 设备标识 float value; // 关键数值 uint8_t status; // 判断结果 } log_record_t;这个结构体加上字节对齐和校验一共 16 字节。也就是说MRAM 日志区如果分 256KB 给日志存储就能存 16K 条记录对工业设备来说已经很充裕了。环形缓冲的实现不复杂我在 MRAM 头部固定保存两个 4 字节值分别是写指针和读指针。每次写入新记录时读取当前写指针write_idx写入 log_record_t 到write_idx * 16地址更新时间戳将write_idx 1后写回指针区由于 MRAM 的写入速度很快这个操作可以在几十微秒内完成配合 Modbus 轮询或者 UART 中断完全没问题。如果写指针越过了读指针说明缓冲区满了此时可以选择滚动覆盖或者把读指针也往前推看业务需求。4.3 掉电检测与最后一条数据完整性工业设备最怕的不是运行中写数据而是写一半突然断电。MRAM 虽然速度快但也不是瞬间写完的。SPI 命令发送到一半掉电存储区可能留下半截数据。我用的方案是 STM32F413RH 的 PVD可编程电压检测中断。当 VDD 跌到预设阈值以下时PVD 中断触发。我把 PVD 配置为下降沿检测在中断里立刻执行一个紧急写操作把最后一条完整记录的最后校验值写入 MRAM 的一个特殊标志区同时将逻辑上“已提交”的位置更新到指针区。由于 PVD 中断到 MCU 执行代码还有几十微秒而 MRAM 写入一次只要几微秒所以只要电压跌落不是一瞬间切断这个时间窗口完全够用。实际测试中我在电源输入端加了一个大电容让掉电后 MCU 还能维持约 50ms 的供电足够完成最后一条数据的保存。当然如果你追求极端可靠性还可以做双缓冲两条记录之间写校验和读的时候校验 CRC失败就回退上一条记录。我在这个项目里只做了单记录加 CRC也能满足需求。4.4 如何让上位机读取这些数据数据存在 MRAM 里如果不上位机读那跟不存在没什么区别。我在 STM32F413RH 上跑了一个轻量级的 Modbus 从站协议把存储区映射成若干个保持寄存器。比如把最近一条日志记录的device_id、value、status映射到寄存器地址 100~103。上位机通过 Modbus TCP 直接读这些寄存器就能实时看到设备状态。更进一步我也实现了 OPC UA 服务器端的一个数据源封装。OPC UA 的好处是上层组态软件可以直接用不需要关心底层的 Modbus 协议细节。当然OPC UA 协议栈比较重在 STM32F413RH 上跑需要裁剪内存但 320KB SRAM 足够撑起一个小型服务器。这里我主要把 MRAM 中存的数据包装成 OPC UA 节点上位机订阅之后就能周期获取效果很不错。5. 常见问题与排查技巧实录5.1 数据写不进去检查写使能和片选时序很多朋友第一次调 MRAM 会遇到写入无效、读出来全是 FF 的情况。最首要的是确认 WREN 指令是否真的生效。WREN 时序要求 CS# 先拉低发送 06h然后 CS# 拉高。这个“CS# 拉高”很关键否则写使能不会锁存。还要检查 WP# 引脚。如果它被拉到低电平即使发送 WRENMRAM 也会禁止写入。我在调试工具板上特意引出这两个信号就是为了随时测量。5.2 读回来全 0xFF 多半是 SPI 模式错了读出来全是字节 FF大概率是 SPI CPOL/CPHA 不匹配。MR25H40CDF 数据手册上支持 Mode 0 和 Mode 3但如果你发送指令用 Mode 0、读数据却变成 Mode 1就会导致数据采样错位结果全是 FF。遇到这种情况先用逻辑分析仪抓一遍 SCK 和 SI 的时序看看所发送的字节和 MRAM 期望的采样沿是否一致。如果 SPOL 写错改初始化代码里的CLKPolarity和CLKPhase重试即可。5.3 SPI 接收数据错位检查时钟极性和相位错位现象比较微妙。比如写数据正确读数据时低位跑到高位去。这时候不要急着改代码先检查 MISO 数据线上的负载电容。如果 MISO 线上加了太大的电容比如几百 pF信号上升沿变慢在 25MHz 时钟下就可能采样错误。我测试时发现 MISO 上串联了 22 欧姆电阻和一点负载电容导致读数据偶尔错位后来把电阻改小或移除后问题消失。5.4 工业现场干扰导致数据错误增加校验与重试工业现场有电机、变频器、继电器这些设备产生的电磁干扰很容易耦合到 SPI 线路上。我实测遇到过一种情况正常使用时数据正确一旦旁边的接触器动作MRAM 读回来的数据就会偶尔出现单个字节错误。解决思路是“软件冗余”。我给每一条日志记录末尾加了一个 16 位 CRC读取时发现校验失败就标记为坏记录并尝试重新读取一次。如果连续两次都失败说明存储区可能被篡改就上报给协议栈处理。同时还可以在关键数据上额外做一次重复存储两条记录内容相同读的时候如果第一条 CRC 错误就自动用第二条这种双备份方案对关键参数很有效。5.5 其他避坑心得最后再说几个零碎经验。一是 MRAM 的功耗并不高但工业高温环境下数据保持性能会受到影响所以系统设计时一定要保证散热不要把 MRAM 放在靠近散热器的高温区。二是测量 SPI 信号时探针尽量夹在芯片引脚根部不要夹在线束上否则测出来的波形象噪声没区别。三是如果发现用 STM32CubeMX 生成的 SPI 初始化代码里没有启用 DMA而你又希望快速搬运大块数据可以考虑加上 SPI 的 DMA 通道。MRAM 本来就支持无缝连续读写加上 DMA 后 CPU 几乎是空闲的这在需要同时处理多路通信时优势特别明显。我个人在实际操作中的体会是MRAM 这类存储器的存在让嵌入式工程师多了一种很省心的选择。它不需要你去考虑擦写寿命、擦写块、磨损均衡这些麻烦事只管往地址里写数据就好了。配合 STM32F413RH 强大的外设和性能整个数据采集、存储、上报的链路可以设计得非常干净。如果你也在规划类似的产品尤其面对高频次、小数据量、掉电保存的场景不妨考虑一下这套组合。最后再分享一个小技巧第一次上板调试时别急着写应用逻辑先写一段简单的测试程序把全地址逐个写入递增模式再读回来比对确认整条链路无误后再往上叠功能。这个习惯能帮你省掉后面很多排查问题的痛苦。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑