免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

STM32F469II 与 MR25H40CDF MRAM 工业存储方案实战

STM32F469II 与 MR25H40CDF MRAM 工业存储方案实战 1. 为什么 MRAM 在工业嵌入式场景里越来越受关注搞过工业数据采集或者电力终端的朋友应该都有体会选存储芯片这件事很多时候比选主控还让人头疼。EEPROM 擦写寿命不够、FRAM 容量太小、NOR Flash 写入速度慢还怕掉电、SRAM 又得配电池。这几年 MRAM 慢慢进入大家的视野尤其是 Everspin 的 MR25H40 系列4Mbit 容量、SPI 接口、号称无限次擦写、掉电数据不丢听起来像是把上面几个痛点一次性解决了。我最近在一个工业数据记录器的项目里用 STM32F469II 搭配 MR25H40CDF 做了一套数据存储方案跑了大概三个月积累了一些实操经验。这篇文章就把整个选型思路、硬件设计、驱动实现、踩过的坑都梳理一遍。不管你是刚接触 MRAM 的嵌入式新手还是正在做存储方案选型的老手应该都能从中找到有用的东西。MR25H40CDF 是 Everspin 出的 4Mbit512KBSPI MRAM工作电压 2.7V 到 3.6VSPI 时钟最高 40MHz支持标准 SPI Mode 0 和 Mode 3。跟传统存储方案比它的核心优势在于写入不需要擦除、写入速度接近 SRAM、擦写次数理论上无限官方标称 10^14 次以上、数据保持时间超过 20 年。这些特性放在工业现场那种高温、震动、频繁断电的环境里价值就非常明显了。STM32F469II 是 ST 家的高性能 M4 系列180MHz 主频带 Chrom-ART 图形加速器2MB Flash、384KB SRAM外设资源丰富SPI 接口有好几个。选它主要是因为项目需要驱动一块 RGB 屏做实时数据显示同时还要跑数据记录和通信协议栈F469 的性能和存储空间都够用。MR25H40CDF 挂在 SPI2 上跟屏幕的 LTDC 接口不冲突整体架构比较干净。2. 方案整体设计与关键选型考量2.1 存储介质对比为什么最终选了 MRAM在确定 MR25H40CDF 之前我对比了几种常见的非易失存储方案。这个对比过程其实挺关键的因为不同场景下最优解完全不一样。存储类型写入速度擦写寿命掉电保持容量范围写入前是否需擦除典型功耗EEPROM慢ms级100万次是KB级否字节级低NOR Flash中等10万次是MB级是块级中FRAM快10^12次是KB~MB否低MRAM快接近SRAM10^14次是KB~MB否中SRAM电池最快无限依赖电池MB级否高含电池维护从表里能看出来MRAM 在写入寿命和写入速度这两个维度上优势明显。工业数据记录器需要每秒写入几十条记录每条记录几十字节一天下来写入次数轻松上万。如果用 EEPROM100万次的寿命大概几个月就耗尽了NOR Flash 虽然容量大但块擦除机制导致小数据频繁写入时效率极低还需要额外的磨损均衡算法。MRAM 的字节级写入和无擦除特性让固件设计大大简化。FRAM 其实也是候选但容量做到 512KB 以上的型号选择少价格也不便宜。MR25H40CDF 的 512KB 容量刚好够存一周的原始数据加上 SPI 接口简单最终就定了它。2.2 STM32F469II 的 SPI 外设分配与引脚规划STM32F469II 有 6 个 SPI/I2S 接口但不是每个都适合挂 MRAM。SPI1 通常留给调试或者高速外设SPI2 和 SPI3 比较灵活。我最终把 MR25H40CDF 挂在 SPI2 上原因有几个SPI2 的引脚PB13/PB14/PB15在 PCB 布局上离 MRAM 芯片近走线短SPI2 不跟 LTDC 的引脚冲突SPI2 的时钟源来自 APB1配置起来跟系统时钟的耦合关系清晰。片选信号用的是软件片选GPIO 选的是 PB12。这里有个细节STM32 的硬件 NSS 在某些模式下会有时序问题尤其是多从机场景。软件片选虽然多占一个 GPIO但控制灵活调试的时候也方便用逻辑分析仪抓波形。MR25H40CDF 的 CS 建立时间和保持时间要求不苛刻软件片选完全能满足。引脚分配如下SPI2_SCK - PB13SPI2_MISO - PB14SPI2_MOSI - PB15MRAM_CS - PB12GPIO 输出MRAM_WP - 接 VDD不用的写保护直接拉高MRAM_HOLD - 接 VDD不用的保持功能直接拉高注意MR25H40CDF 的 WP 和 HOLD 引脚内部没有上拉悬空会导致状态不确定。即使不用这两个功能也必须明确拉到 VDD 或 GND。2.3 电源与去耦设计要点MRAM 的写入电流比读取电流大不少尤其是在高频写入的时候。MR25H40CDF 的数据手册标称写入电流典型值 25mA峰值可能到 40mA。如果电源去耦没做好写入瞬间的电压跌落可能导致写入失败或者数据错误。我在 MRAM 的 VDD 引脚旁边放了两个电容一个 100nF 的陶瓷电容紧贴引脚另一个 1uF 的陶瓷电容稍微远一点。这两个电容的作用不一样100nF 负责滤高频噪声1uF 负责提供写入瞬间的电荷缓冲。PCB 走线上电源线尽量粗从 LDO 到 MRAM 的路径上不要有大的电感或者过孔。另外STM32F469II 和 MR25H40CDF 的电源域要一致。如果 STM32 用 3.3VMRAM 也用 3.3VSPI 电平匹配就没问题。如果 STM32 用 1.8V 核心电压但 IO 是 3.3V那也没问题因为 F469 的 IO 电压是可配的。但千万别出现 STM32 IO 是 3.3V 而 MRAM 供电是 2.5V 的情况这样高电平识别可能出问题。3. SPI 驱动实现与 MRAM 操作细节3.1 STM32CubeMX 配置 SPI2 的关键参数用 CubeMX 配置 SPI2 的时候有几个参数需要特别注意。我先把配置截图的关键项列出来然后解释每个参数为什么这么选。Mode: Full-Duplex MasterData Size: 8 BitsClock Polarity (CPOL): LowClock Phase (CPHA): 1 EdgeNSS Signal Type: SoftwarePrescaler: 4APB1 时钟 45MHzSPI 时钟 11.25MHzFirst Bit: MSB FirstCRC Calculation: DisabledCPOL 和 CPHA 的组合决定了 SPI 的工作模式。MR25H40CDF 支持 Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1。我选的是 Mode 0也就是 CPOL Low、CPHA 1 Edge。这个模式下时钟空闲为低电平数据在第一个边沿上升沿采样。用逻辑分析仪抓波形的时候Mode 0 的波形最直观调试方便。预分频系数选 4SPI 时钟跑到 11.25MHz。MR25H40CDF 最高支持 40MHz为什么不用更高的时钟因为 PCB 走线长度和信号完整性限制。我的板子上 SPI 走线大概 3cm没有做阻抗匹配11.25MHz 下波形还很干净再高就开始出现振铃了。工业环境里稳定性比速度重要11.25MHz 的读取速度已经能到 1.4MB/s足够用了。提示如果你的 PCB 走线很短1cm且做了阻抗控制可以尝试 20MHz 甚至 40MHz。但建议先用示波器看波形确认没有过冲和振铃再提高频率。3.2 MR25H40CDF 的命令集与读写时序MR25H40CDF 的命令集跟标准 SPI Flash 很像但有几个关键区别。先看常用命令命令名称命令码功能说明地址字节数数据字节数WREN0x06写使能00WRDI0x04写禁止00RDSR0x05读状态寄存器01WRSR0x01写状态寄存器01READ0x03读数据31~NWRITE0x02写数据31~N跟 NOR Flash 最大的区别是MRAM 没有擦除命令。写入之前不需要发擦除指令直接 WREN 然后 WRITE 就行。这个特性让固件逻辑简化了很多不用维护擦除块管理。写操作的完整时序是这样的拉低 CS发送 WREN0x06拉高 CS拉低 CS发送 WRITE0x02发送 3 字节地址24位高位在前发送要写入的数据字节可以连续多个拉高 CS注意第 3 步和第 4 步之间必须拉高 CS这是 WREN 命令生效的必要条件。我一开始没注意这个细节WREN 和 WRITE 连在一起发结果写入一直失败。后来看数据手册才发现WREN 需要 CS 的上升沿来锁存。读操作简单一些拉低 CS发送 READ0x03发送 3 字节地址读取数据字节可以连续多个拉高 CS读操作不需要 WREN也不需要等待。MRAM 的读取是即时的没有 NOR Flash 那种等待周期。3.3 HAL 库驱动代码实现基于 STM32 HAL 库我封装了几个核心函数。先看初始化部分#include stm32f4xx_hal.h extern SPI_HandleTypeDef hspi2; #define MRAM_CS_PIN GPIO_PIN_12 #define MRAM_CS_PORT GPIOB #define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRDI 0x04 #define MRAM_CMD_RDSR 0x05 #define MRAM_CMD_WRSR 0x01 #define MRAM_CMD_READ 0x03 #define MRAM_CMD_WRITE 0x02 static void MRAM_CS_Low(void) { HAL_GPIO_WritePin(MRAM_CS_PORT, MRAM_CS_PIN, GPIO_PIN_RESET); } static void MRAM_CS_High(void) { HAL_GPIO_WritePin(MRAM_CS_PORT, MRAM_CS_PIN, GPIO_PIN_SET); } static uint8_t MRAM_WriteEnable(void) { uint8_t cmd MRAM_CMD_WREN; MRAM_CS_Low(); HAL_SPI_Transmit(hspi2, cmd, 1, 100); MRAM_CS_High(); return 0; } static uint8_t MRAM_ReadStatus(void) { uint8_t cmd MRAM_CMD_RDSR; uint8_t status 0; MRAM_CS_Low(); HAL_SPI_Transmit(hspi2, cmd, 1, 100); HAL_SPI_Receive(hspi2, status, 1, 100); MRAM_CS_High(); return status; }写数据函数uint8_t MRAM_Write(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd; uint8_t addr_buf[3]; if (addr len 0x80000) { return 1; // 超出512KB范围 } addr_buf[0] (addr 16) 0xFF; addr_buf[1] (addr 8) 0xFF; addr_buf[2] addr 0xFF; MRAM_WriteEnable(); MRAM_CS_Low(); cmd MRAM_CMD_WRITE; HAL_SPI_Transmit(hspi2, cmd, 1, 100); HAL_SPI_Transmit(hspi2, addr_buf, 3, 100); HAL_SPI_Transmit(hspi2, data, len, 1000); MRAM_CS_High(); return 0; }读数据函数uint8_t MRAM_Read(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd; uint8_t addr_buf[3]; if (addr len 0x80000) { return 1; } addr_buf[0] (addr 16) 0xFF; addr_buf[1] (addr 8) 0xFF; addr_buf[2] addr 0xFF; MRAM_CS_Low(); cmd MRAM_CMD_READ; HAL_SPI_Transmit(hspi2, cmd, 1, 100); HAL_SPI_Transmit(hspi2, addr_buf, 3, 100); HAL_SPI_Receive(hspi2, data, len, 1000); MRAM_CS_High(); return 0; }这几个函数跑下来读写都正常。但有个性能问题每次读写都要重新拉 CS、发命令、发地址开销不小。如果频繁读写小数据效率会很低。后面我会讲怎么优化。3.4 写入速度实测与优化我用逻辑分析仪抓了写入 256 字节的波形算了一下时间。SPI 时钟 11.25MHz每个字节 8 位加上命令和地址开销写 256 字节大概需要 240 微秒左右。理论计算256 字节 × 8 位 / 11.25MHz ≈ 182 微秒加上命令和地址的 4 字节开销总共约 185 微秒。实测 240 微秒多出来的部分是 CS 拉高拉低的间隔和 HAL 库的函数调用开销。如果要提高写入速度有几个方向提高 SPI 时钟到 20MHz 以上需要确认信号完整性用 DMA 传输替代阻塞式传输减少 CS 的切换次数连续写入时保持 CS 低电平我试过用 DMA 传输写入 256 字节的时间降到了 150 微秒左右。但 DMA 的配置复杂一些而且需要处理传输完成中断。对于数据记录器这种应用写入频率大概每秒几十次阻塞式传输完全够用没必要上 DMA。实操心得如果你的应用需要高频写入比如每秒上千次建议用 DMA 加双缓冲。如果只是中低频记录阻塞式传输更简单可靠出问题也容易排查。4. 数据存储架构与可靠性设计4.1 环形缓冲区设计512KB 的 MRAM 看起来不大但如果每条记录 32 字节能存 16384 条。按每秒一条算能存 4.5 小时。对于工业现场的数据记录这个容量需要配合环形缓冲区来用覆盖最旧的数据。环形缓冲区的设计思路很简单维护一个写指针每次写入后指针后移写到末尾就回到开头。同时维护一个记录计数器用来判断缓冲区是否已满。但实际实现的时候有几个细节要注意。第一个细节是写指针的持久化。如果写指针只存在 RAM 里掉电后就丢了重启后不知道从哪里继续写。所以写指针必须定期存到 MRAM 里。我是在 MRAM 的固定地址比如 0x00000存一个头部结构体包含写指针、记录总数、校验和。每次写入新记录后更新头部。但这样每次写入都要写两次 MRAM一次数据一次头部效率减半。优化方案是批量更新头部。比如每写入 100 条记录才更新一次头部。掉电时最多丢失 100 条记录的指针信息但数据本身还在 MRAM 里可以通过扫描恢复。这个取舍要看应用对数据完整性的要求。第二个细节是记录格式的设计。每条记录除了数据本身还需要一个时间戳和一个校验字段。时间戳可以用 RTC 提供校验字段用 CRC16 或者简单的累加和。我用的结构体是这样的typedef struct { uint32_t timestamp; uint16_t data_len; uint8_t data[24]; uint16_t crc; } Record_t;这个结构体 32 字节刚好对齐。CRC 计算覆盖 timestamp、data_len 和 data 字段。读取的时候先校验 CRC校验失败就跳过这条记录。4.2 掉电保护与数据完整性MRAM 的掉电保护能力比 Flash 强很多因为写入是即时的不需要等待擦除或者编程完成。但系统级掉电保护还是要做因为 STM32 本身掉电后 GPIO 状态不确定可能产生错误的 SPI 时序。我的做法是在电源输入端加一个电压检测芯片当电压低于 3.0V 时触发中断STM32 在中断里完成当前写入操作并拉高 CS然后进入停机模式。这个窗口大概有几百微秒足够完成一次 32 字节的写入。另外MRAM 的 WP 引脚在正常工作时拉高但在系统初始化阶段我建议先拉低再拉高确保 MRAM 处于已知状态。有些批次的 MRAM 在上电瞬间状态寄存器可能不确定虽然概率很低但工业产品要考虑到。注意MR25H40CDF 的状态寄存器里有一个 WEL 位写入使能后该位为 1写入完成后自动清零。调试的时候可以通过读状态寄存器来判断 WREN 是否生效。如果 WEL 一直是 0说明 WREN 命令没有正确执行检查 CS 时序。4.3 数据读取与校验流程读取流程比写入简单但校验不能省。我的读取函数会先读头部获取写指针和记录总数然后从最旧的记录开始读逐条校验 CRC。如果某条记录 CRC 失败就标记为无效并跳过。实际跑下来MRAM 的读取误码率极低三个月运行没有出现一次 CRC 失败。但校验逻辑还是要保留因为工业环境的电磁干扰不可预测而且校验开销很小读 32 字节多算一个 CRC16 大概多花几微秒。如果应用需要更高的可靠性可以在 MRAM 里存两份数据一份原始数据一份备份。读取的时候两份对比不一致就报错。但这样容量减半需要根据实际需求权衡。5. 常见问题与排查技巧实录5.1 SPI 通信失败的排查思路SPI 通信失败是最常见的问题表现可能是读出来全是 0xFF、全是 0x00或者数据随机。排查的时候按以下顺序来先看 CS 信号用示波器或者逻辑分析仪抓 CS 引脚确认每次通信前有正确的拉低和拉高。CS 如果一直是低电平MRAM 会认为一直在通信中状态机可能错乱。再看 SCK 和 MOSI确认时钟极性、相位跟 MRAM 要求的一致。Mode 0 和 Mode 3 的波形差别很大抓一次波形就能看出来。检查 MISO如果 MISO 一直高阻可能是 MRAM 没有供电或者 CS 没有正确拉低。如果 MISO 有信号但数据不对检查地址和数据顺序。读状态寄存器发 RDSR 命令看返回的状态字节。正常情况应该是 0x00 或者有 WEL 位。如果返回 0xFF说明 MISO 没有连通。我遇到过一次诡异的问题读出来的数据偶尔会错一位。查了半天发现是 SPI 时钟太快PCB 走线又没有做阻抗匹配导致 MISO 上的信号有反射。把时钟从 20MHz 降到 11.25MHz 后问题消失。所以遇到偶发错误先降速试试。5.2 写入不生效的几种原因写入不生效通常有这几个原因WREN 没有正确执行WREN 命令后必须拉高 CS否则写使能不生效。这个前面强调过了。WP 引脚状态不对如果 WP 引脚被拉低状态寄存器的 WPEN 位如果使能了写入会被阻止。检查 WP 引脚的电平。地址越界MR25H40CDF 的地址范围是 0x00000 到 0x7FFFF超出这个范围的行为未定义。写入前一定要检查地址。电源电压不足MRAM 写入时需要的最低电压是 2.7V如果电源跌落到 2.5V写入可能失败。用示波器看写入瞬间的 VDD 波形。5.3 常见问题速查表现象可能原因排查方法解决方案读出全 0xFFMISO 未连通检查 MRAM 供电和 CS确认供电和片选读出全 0x00MOSI 未连通检查 MOSI 走线修复走线或换引脚写入后读回不变WREN 未生效读状态寄存器 WEL 位确保 WREN 后拉高 CS偶发数据错误SPI 时钟过快降低时钟测试降速或做阻抗匹配写入失败电源跌落示波器看 VDD增加去耦电容CS 一直低GPIO 配置错误检查 GPIO 初始化重新配置推挽输出5.4 调试工具与技巧调试 SPI 设备逻辑分析仪是必备的。我用的是 Saleae 的 8 通道逻辑分析仪配合它的 SPI 解码器可以直接看到命令、地址、数据的解析结果。比用示波器一个字节一个字节地数方便太多了。另外STM32CubeIDE 的调试功能也可以利用。在 SPI 传输函数里打断点看 HAL_SPI_Transmit 的返回值。如果返回 HAL_TIMEOUT说明 SPI 外设配置有问题如果返回 HAL_OK 但数据不对那就是时序或者硬件问题。还有一个技巧在 MRAM 的 CS 引脚上接一个 LED通过三极管驱动每次 CS 拉低时 LED 闪一下。这样不用仪器也能大致判断通信是否在发生。当然这个方法只适合低频通信高频下 LED 根本反应不过来。6. 性能优化与扩展思路6.1 批量读写与页模式MR25H40CDF 支持连续读写也就是一次 CS 周期内可以读写多个字节。利用这个特性可以批量读写数据减少 CS 切换开销。我的数据记录器每次写入 32 字节如果改成攒够 256 字节再写CS 切换次数减少到原来的八分之一整体写入效率提升明显。但批量写入有个风险如果攒数据的过程中掉电这部分数据就丢了。所以批量写入适合对数据完整性要求不那么苛刻的场景或者配合掉电检测使用。6.2 与文件系统的结合如果应用需要更复杂的数据管理可以在 MRAM 上跑一个轻量级文件系统比如 LittleFS 或者 SPIFFS。这两个文件系统都是为嵌入式设计的支持掉电保护。但 MRAM 的擦写寿命无限文件系统的磨损均衡机制其实没必要反而增加了开销。所以用不用文件系统要看应用的数据组织需求。我的项目里数据格式固定直接用环形缓冲区就够了没必要上文件系统。但如果要存日志文件、配置文件、固件备份等多种类型的数据文件系统会更方便。6.3 多片 MRAM 扩展容量512KB 不够用的时候可以挂多片 MR25H40CDF。SPI 总线可以挂多个从机每片用一个独立的 CS 引脚。STM32F469II 的 GPIO 足够多挂 4 片 MRAM 也就多占 4 个引脚。多片 MRAM 的地址管理有两种方式一种是线性扩展把 4 片当成一个 2MB 的连续空间地址的高 2 位用来选片另一种是分片管理每片存不同类型的数据。线性扩展对上层软件透明但需要改读写函数分片管理更灵活但上层要维护分片映射。实操心得多片 MRAM 的时候SPI 总线的负载电容会增加高速通信时波形可能变差。建议每片 MRAM 的 CS 走线尽量等长SCK 和 MOSI 走线用菊花链或者星型拓扑具体看 PCB 布局。6.4 低功耗设计考量工业设备很多是电池供电或者需要低功耗运行。MR25H40CDF 的待机电流典型值 10uA写入电流 25mA读取电流 15mA。如果系统大部分时间在休眠MRAM 的待机功耗可以接受。低功耗设计的关键是减少 MRAM 的活跃时间。我的做法是数据先缓存在 STM32 的 SRAM 里攒够一批再写入 MRAM。STM32 在休眠时MRAM 的 CS 保持高电平SPI 外设关闭。唤醒后先初始化 SPI再操作 MRAM。另外MRAM 的 HOLD 引脚可以用来暂停通信但实际用处不大因为 CS 拉高本身就相当于暂停。HOLD 引脚在低功耗设计里可以直接拉高不用管。7. 实际项目中的经验与教训这个项目从选型到量产前后大概花了四个月。中间踩了不少坑有些是技术问题有些是流程问题。挑几个有代表性的说说。第一个坑是 PCB 布局。第一版板子把 MRAM 放在了 STM32 的背面SPI 走线穿过板子长度大概 5cm。结果 SPI 时钟跑到 10MHz 以上就开始出错。第二版把 MRAM 挪到 STM32 同一面走线缩短到 2cm问题解决。所以 SPI 设备的布局能近就近别为了省空间把走线拉长。第二个坑是电源去耦。第一版板子只在 MRAM 电源引脚放了一个 100nF 电容写入的时候偶尔失败。后来加了 1uF 电容问题消失。MRAM 的写入电流是脉冲式的100nF 的电荷量不够需要更大的电容来缓冲。第三个坑是固件升级。MRAM 里存的数据格式在固件升级后可能不兼容。我的做法是在 MRAM 头部加一个版本号固件启动时检查版本号不匹配就执行数据迁移或者清空。这个机制在第一次升级的时候没做导致旧数据读出来全是乱码后来补上了。第四个坑是测试覆盖。实验室测试的时候MRAM 读写都正常。到了现场设备装在变频器旁边电磁干扰很强偶尔出现写入失败。后来在固件里加了写入后回读校验校验失败就重写问题才解决。所以工业产品的测试一定要考虑电磁兼容场景。8. 写在最后的一些个人体会MR25H40CDF 加 STM32F469II 这套组合在工业数据存储场景里算是比较省心的方案。MRAM 的无限擦写和即时写入特性让固件设计简化了很多不用再纠结磨损均衡和擦除管理。STM32F469II 的性能和存储资源也足够跑复杂的应用逻辑。如果让我给正在选型的朋友一个建议我会说先明确你的数据写入频率和可靠性要求。如果写入频率低、数据量小EEPROM 或者 FRAM 可能更便宜如果需要大容量、低成本NOR Flash 加磨损均衡也能用。但如果你的场景是高频写入、频繁掉电、工业环境MRAM 多花的那些钱省下来的调试时间和维护成本绝对值得。最后分享一个小技巧MRAM 的读写函数封装好之后可以写一个自测试函数上电时跑一遍全片读写校验。这个自测试大概花几百毫秒但能提前发现硬件问题避免设备装到现场才发现存储坏了。我在量产固件里加了这个自测试返修率明显下降。
返回列表