ARTICLE DETAIL

资讯详情

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

MRAM掉电不丢数据:MR25H40CDF与STM32L162ZE工业存储实战

MRAM掉电不丢数据:MR25H40CDF与STM32L162ZE工业存储实战 1. 为什么偏偏选中 MR25H40CDF 这颗 MRAM1.1 从掉电就丢数据这个老问题说起做过工业设备的人大概都有过这种经历现场跑得好好的设备突然断电重新上电之后发现之前标定的参数、累计的运行时长、故障记录全没了。如果用的是 SRAM 加后备电池的方案电池几年一换换的时候数据照样丢如果用的是 EEPROM写入速度慢得让人抓狂而且擦写寿命就那么一百万次左右高频写入的场景下没几年就写废了。这就是我当初盯上 MR25H40CDF 的直接原因。MRAM磁阻随机存储器这个东西本质上是用磁性隧道结的磁化方向来存 0 和 1而不是靠电荷。带来的好处非常实在写入速度接近 SRAM掉电不丢数据擦写寿命号称无限次实际测试中 10^14 次以上量级而且不需要像 EEPROM 那样先擦后写字节级直接改写。MR25H40CDF 是 Everspin 家的 4Mbit512KBSPI 接口 MRAM工作电压 2.7V 到 3.6V工业级温度范围 -40℃ 到 85℃SPI 最高能跑到 40MHz。这几个参数放在工业嵌入式场景里基本就是刚刚好容量够存参数、日志、标定数据电压和 STM32L162ZE 的 IO 电平天然匹配温度范围覆盖绝大多数工业现场SPI 接口几乎任何 MCU 都能直接挂。1.2 和 FRAM、EEPROM、NOR Flash 的横向对比选型的时候我把几个候选都拉出来对比过这里直接给一张表省得你再去翻手册特性MR25H40CDF (MRAM)典型 FRAM典型 EEPROM典型 NOR Flash写入速度字节级无延迟字节级快慢需页写慢需擦除擦写寿命近乎无限10^12 次10^6 次10^5 次掉电保持20 年以上10 年以上10 年以上10 年以上写入功耗低极低中高擦除时接口SPII2C/SPII2C/SPISPI单位容量成本偏高高低低从表里能看出来MRAM 的定位很清晰它不是用来替代大容量存储的而是用来替代那些需要频繁写、又绝对不能丢的小数据存储场景。你要是拿它存固件、存图片那成本会劝退你但你要是拿它存设备参数、运行日志、故障快照那它几乎是目前最省心的选择。1.3 STM32L162ZE 为什么是合适的搭档STM32L162ZE 是 ST 家的超低功耗 Cortex-M3带 192KB Flash、16KB SRAML1 系列主打低功耗正好适合电池供电或者能耗敏感的工业节点。它自带多个 SPI 外设硬件 SPI 支持 DMA配合 MRAM 的 40MHz 速率读写吞吐完全够用。更关键的一点是L162ZE 的供电范围和 MR25H40CDF 高度重合都是 3.3V 系统不需要额外的电平转换。工业现场很多设备是 3.3V 单电源这一点省了不少事。另外 L162ZE 有独立的备份域和 RTC配合 MRAM 做数据记录可以做到主电源断了MRAM 里的数据还在下次上电接着用。2. 硬件连接与 SPI 模式的关键细节2.1 引脚连接与片选处理MR25H40CDF 是标准 8 脚 SOIC 封装引脚定义很清晰VCC、GND、SCK、SI、SO、CS#、WP#、HOLD#。和 STM32L162ZE 连接的时候我一般这样分配SCK 接 SPI 时钟比如 PA5SIMOSI接主出从入PA7SOMISO接主入从出PA6CS# 接一个普通 GPIO比如 PA4用软件片选WP# 和 HOLD# 直接上拉到 VCC这里有个很多人会踩的坑WP# 和 HOLD# 这两个脚如果你不用硬件写保护和暂停功能一定要上拉到 VCC不能悬空。悬空的话引脚电平不确定可能随机进入写保护或者暂停状态表现出来就是有时候能写有时候写不进去非常难查。我早期调试的时候就吃过这个亏折腾了大半天才发现是 HOLD# 悬空导致的。关于片选我强烈建议用软件片选而不是硬件 NSS。原因很简单MRAM 的时序里CS# 拉低到第一个时钟沿之间需要满足建立时间CS# 拉高到下一个操作之间需要满足保持时间。硬件 NSS 在多字节连续传输时中间可能会因为 DMA 或者中断产生抖动导致 CS# 意外翻转整个事务就废了。软件片选你能完全控制 CS# 的时序配合 SPI 的收发函数稳得多。2.2 SPI 模式选择Mode 0 还是 Mode 3MR25H40CDF 支持 SPI Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1。这两个模式的区别在于空闲时钟电平和采样边沿。Mode 0 是空闲低电平、上升沿采样Mode 3 是空闲高电平、下降沿采样。实际用下来我一般选 Mode 0。原因是 STM32 的 HAL 库默认配置就是 Mode 0而且 Mode 0 在 PCB 布线时对时钟线的要求相对宽松一点。当然如果你板子上已经有其他 SPI 从设备用了 Mode 3那 MRAM 跟着用 Mode 3 也完全没问题它两个都支持。配置的时候有几个参数必须对上时钟极性 CPOLMode 0 设为 Low时钟相位 CPHAMode 0 设为 1 Edge数据大小8 位MSB FirstMRAM 是高位先出时钟分频初始调试建议先分频到 1MHz 左右跑通之后再往上提2.3 上拉电阻与去耦电容的取值SPI 信号线上我一般会在 SCK、MOSI、CS# 上各加一个 10K 的上拉电阻。有人会问 SPI 是推挽输出为什么还要上拉答案是在设备上电初始化阶段MCU 的 GPIO 还没配置成 SPI 功能之前引脚是高阻态这时候上拉能保证从设备不会因为浮空引脚误触发。尤其是 CS#上拉能保证上电瞬间 MRAM 不会被误选中。去耦电容方面MR25H40CDF 的 VCC 脚旁边一定要放一个 0.1uF 的陶瓷电容紧贴引脚。如果 SPI 速率跑到 20MHz 以上再并一个 1uF 或者 2.2uF 的电容会更稳。我实测过没有去耦电容的情况下高速读写时偶发数据错误加上之后就再没出现过。3. 驱动实现从寄存器操作到 HAL 库封装3.1 MRAM 的指令集梳理MR25H40CDF 的指令集不复杂常用的就那么几条指令名称指令码功能说明WREN0x06写使能写操作前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据注意这里有个和 EEPROM 不一样的地方MRAM 的 WRITE 指令不需要先擦除直接写就行。但是每次写之前仍然需要发 WREN 写使能这是硬件层面的保护机制。写完之后 WREN 会自动复位下次写还得重新发。状态寄存器里主要关注 WEL写使能锁存和 BP0/BP1块保护这几个位。WEL 在发完 WREN 之后会置 1写完自动清 0。BP 位用来做块保护如果你不小心把 BP 位设错了会出现写不进去的情况调试的时候记得先读一下状态寄存器确认。3.2 底层读写函数的实现先给一个基于 HAL 库的底层读写函数这是整个驱动的基础#include stm32l1xx_hal.h extern SPI_HandleTypeDef hspi1; #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) #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_WriteEnable(void) { uint8_t cmd MRAM_CMD_WREN; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); MRAM_CS_HIGH(); } static uint8_t MRAM_ReadStatus(void) { uint8_t cmd MRAM_CMD_RDSR; uint8_t status 0; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_SPI_Receive(hspi1, status, 1, 100); MRAM_CS_HIGH(); return status; } void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { uint8_t cmd[4]; cmd[0] MRAM_CMD_WRITE; cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; MRAM_WriteEnable(); MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 4, 100); HAL_SPI_Transmit(hspi1, buf, len, 1000); MRAM_CS_HIGH(); } void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { uint8_t cmd[4]; cmd[0] MRAM_CMD_READ; cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 4, 100); HAL_SPI_Receive(hspi1, buf, len, 1000); MRAM_CS_HIGH(); }这段代码有几个细节值得说。第一地址是 24 位的因为 512KB 需要 19 位地址但 MRAM 的地址字段是 3 个字节所以高位补 0。第二写操作之前必须调MRAM_WriteEnable()而且 WREN 指令和 WRITE 指令之间 CS# 必须拉高一次不能连着发。第三读操作不需要 WREN直接发 READ 指令加地址就行。3.3 用 DMA 提升大批量读写效率如果你要一次性读写几 KB 的数据用上面那种阻塞式传输会占用大量 CPU 时间。STM32L162ZE 的 SPI 支持 DMA可以配上去。配置思路是SPI 的 TX 和 RX 各挂一个 DMA 通道传输完成之后触发中断在中断里拉高 CS#。这里有个坑要注意DMA 传输完成中断触发的时候SPI 的最后一个字节可能还在移位寄存器里没发完。如果你在 DMA 完成中断里立刻拉高 CS#最后一个字节可能会被截断。正确的做法是等 SPI 的 BSY 标志位清零或者用 SPI 的传输完成回调。我一般是在 DMA 完成中断里先等__HAL_SPI_GET_FLAG(hspi1, SPI_FLAG_BSY)变成 RESET再拉高 CS#。4. 数据存储结构设计与掉电保护策略4.1 存储区域划分512KB 看着不大但存参数和日志绰绰有余。我一般这样划分0x00000 - 0x00FFF4KB设备参数区存标定系数、配置项0x01000 - 0x01FFF4KB参数备份区参数区的镜像0x02000 - 0x0FFFF56KB运行日志区环形缓冲0x10000 - 0x7FFFF448KB预留存故障快照、历史数据参数区做双备份是有原因的写参数的时候如果正好断电可能写了一半。双备份加一个有效性标志位就能保证至少有一份完整的数据。具体做法是参数区头部放一个 4 字节的魔数和一个 CRC32写的时候先写备份区校验通过之后再写主区。读的时候先读主区CRC 不对再读备份区。4.2 环形日志缓冲的实现日志区用环形缓冲写指针到了区尾就绕回开头。每条日志记录包含时间戳、事件类型、数据负载和 CRC。写日志的时候先写数据最后写一个有效标志这样即使写到一半断电那条记录因为标志位没写下次上电会被识别为无效记录跳过。环形缓冲的读写指针也需要持久化。我的做法是把读写指针存在日志区头部的一个固定位置每次写日志之前先更新指针再写数据。这样即使指针更新后断电最多丢一条日志不会导致整个缓冲区错乱。4.3 掉电检测与紧急保存工业现场断电往往是突发的所以掉电检测很重要。STM32L162ZE 有 PVD可编程电压检测器可以设置一个阈值比如 2.9V。当 VCC 降到 2.9V 以下时PVD 触发中断在中断里赶紧把关键数据写进 MRAM。这里的时间窗口很关键。从 PVD 触发到 MCU 完全掉电通常只有几毫秒到几十毫秒取决于电源上的电容大小。你要在这段时间内完成数据写入。MRAM 的写入速度很快写几百字节也就几十微秒所以时间上是够的。但要注意PVD 中断里不要做复杂运算直接把预先准备好的数据缓冲区写进去就行。我一般会在 RAM 里维护一份待保存数据的镜像正常运行时定期更新PVD 触发时直接把这个镜像写进 MRAM。这样中断里的操作就是一次纯粹的 SPI 写时间可控。5. 实测性能与常见问题排查5.1 不同 SPI 速率下的读写耗时我在 STM32L162ZE 上实测过不同 SPI 分频下的读写速度数据如下SPI 速率写 1KB 耗时读 1KB 耗时备注1MHz约 8.2ms约 8.2ms调试用最稳4MHz约 2.1ms约 2.1ms常规使用10MHz约 0.85ms约 0.85ms推荐20MHz约 0.43ms约 0.43ms需注意布线40MHz约 0.22ms约 0.22ms对 PCB 要求高从表里能看出来10MHz 是个甜点位置速度够快对硬件要求又不苛刻。40MHz 虽然快但 PCB 走线稍微长一点、阻抗不匹配就容易出错。我一般建议客户量产用 10MHz 到 20MHz。5.2 常见问题速查表现象可能原因排查方法读出来全是 0xFFCS# 没拉低、SPI 模式不对示波器看 CS# 和 SCK 波形写不进去WREN 没发、WP# 悬空、BP 位设了保护读状态寄存器确认 WEL 和 BP偶发数据错误去耦电容缺失、SPI 速率过高降速测试加去耦电容上电后数据丢失掉电时没保存、MRAM 供电异常检查 PVD 配置和电源电路读写地址错位地址字节序搞反确认是 MSB First 还是 LSB First5.3 几个我踩过的坑第一个坑是 SPI 模式配错。有一次我用 CubeMX 生成代码默认给的是 Mode 0但我手动改成了 Mode 3结果读出来全是乱码。后来发现是 CPHA 设错了MRAM 在 Mode 3 下是下降沿采样我设成了上升沿。这种问题用逻辑分析仪一看波形就清楚了建议手边常备一个。第二个坑是 CS# 时序太紧。我早期写的代码里发完最后一个字节立刻拉高 CS#结果偶尔最后一个字节写不进去。后来加了 BSY 等待就好了。这个问题的隐蔽性在于它不是每次都出现可能一百次里错一次非常难查。第三个坑是电源纹波。有一批板子用的是开关电源纹波比较大MRAM 在高速写入时偶发错误。后来在 MRAM 的 VCC 脚旁边加了一个 10uF 的钽电容问题就解决了。所以电源质量对 MRAM 的稳定性影响很大别省这几个电容。6. 工业场景下的扩展思路6.1 多设备共用 SPI 总线的注意事项工业设备里经常一条 SPI 总线上挂好几个从设备比如 MRAM、Flash、显示屏、传感器。这时候片选管理就很重要。每个从设备一个独立的 CS# 引脚任何时候只能有一个 CS# 拉低。我一般会写一个 SPI 总线管理模块用互斥锁的方式保证同一时刻只有一个设备在访问总线。另外不同从设备的 SPI 模式可能不一样。比如 MRAM 用 Mode 0某个传感器用 Mode 3。这时候每次切换设备都要重新配置 SPI 的 CPOL 和 CPHA。切换之前要确保当前事务已经完成CS# 已经拉高否则会出现时序冲突。6.2 数据加密与完整性校验工业现场的数据有时候需要防篡改比如计量设备的累计流量。MRAM 里存的数据可以加一层校验我一般用 CRC32 加一个简单的异或加密。CRC32 用来检测数据是否被意外修改异或加密用来防止直接读取。如果安全要求更高可以用 STM32L162ZE 自带的硬件加密模块如果有的话或者软件 AES。不过对于大多数工业场景CRC32 加异或已经够用了毕竟攻击者要物理接触到设备才能读取 MRAM。6.3 低功耗场景下的优化STM32L162ZE 主打低功耗如果设备是电池供电MRAM 的功耗也要考虑。MR25H40CDF 的待机电流很小但读写时的电流在几毫安级别。优化思路是不需要读写的时候把 SPI 外设关掉CS# 保持高电平MRAM 进入待机模式。需要读写的时候再唤醒。另外写操作可以合并。比如日志记录不要每产生一条就写一次可以攒够一批再一次性写入。这样能减少 SPI 事务次数降低平均功耗。但要注意攒批的时候数据在 RAM 里如果这时候断电会丢。所以关键数据还是要即时写非关键数据可以攒批。6.4 固件升级时的数据保留设备做固件升级的时候MRAM 里的数据不能丢。我一般会在升级流程里加一步升级前把关键参数读出来备份到 RAM升级完成后写回 MRAM。或者更稳妥的做法是升级程序本身不去动 MRAM 的参数区只更新 Flash 里的固件。这样即使升级失败参数还在设备还能恢复到可用状态。MR25H40CDF 配合 STM32L162ZE 这套组合我在好几个工业项目里用过从环境监测到计量设备都有。它的价值不在于性能有多炸裂而在于省心——不用操心擦写寿命不用操心掉电丢数据不用操心写入延迟。对于工业嵌入式开发者来说这种省心比什么都重要。如果你正在选型数据存储方案又恰好是 SPI 接口、3.3V 系统、需要频繁写入的场景那这颗 MRAM 值得你花半天时间搭个最小系统试一试。
返回列表