ARTICLE DETAIL

资讯详情

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

工业数据记录仪存储选型:MRAM替换Flash与EEPROM的实战

工业数据记录仪存储选型:MRAM替换Flash与EEPROM的实战 做工业数据记录仪那段时间我最头疼的不是传感器采集而是“数据到底往哪存”。一开始用 SPI NOR Flash容量够、价格便宜跑了一个月后发现日志里开始出现坏块查下来是 4KB 对齐的擦除块在频繁写日志时根本扛不住。换 EEPROM 后寿命上来了但大一点的参数表放不下写一页数据还要等内部写周期。最后我换成了 MR25H40CDF 这颗 4Mbit 的串行 MRAM配 STM32F101ZG 做控制这才把嵌入式系统里存储和读取数据这件事在工业场景里理顺了。下面这套从选型、硬件连接到驱动、再到掉电保护的完整过程适合做设备参数存储、数据日志、采集缓存的兄弟参考内容全部来自实际项目不是单纯抄数据手册。1. 为什么工业存储选 MRAM寿命、速度和温度的三角平衡1.1 我为什么会在数据记录仪上放弃 NOR Flash 和 EEPROM先重现一下当时的真实需求设备每 100ms 要记录一条温湿度数据每 50ms 要刷新一次参数表断电瞬间还要把现场状态快速保存到非易失存储里。一开始用 NOR Flash 方案最大的问题不在读而在写之前必须做扇区擦除日志写到一定程度时“先擦后写”的耗时会突然拉长而且擦写寿命上限大概是十万次按每天存 2 万条日志算一个月出头就接近极限。EEPROM 的单字节写寿命虽然高一点但容量通常只有几十到几百 Kbit对完整参数表和运行历史来说明显偏小再加上写一个页面有固定的内部写周期频繁写参数时会有可见的等待时间。MRAM 则完全避开了这两个毛病不需要擦除写入就是写入没有所谓“磨损坏”的逻辑单元。工业现场还有一层考虑是温度很多消费级 Flash 在 85 度以上可靠性就开始下滑而 MR25H40CDF 的工作温度范围能覆盖 -40 到 105 度用在户外柜、高温机架、潮湿环境的设备里心里踏实得多。1.2 MR25H40CDF 的关键参数与磁隧道结原理MR25H40CDF 是 Everspin 的 4Mbit 串行 MRAM通过标准 SPI 接口访问容量换算下来是 512KB。对一般工业设备来说这个容量存参数表、配置项、几千条日志完全够用。它的存储单元不是浮栅电荷而是磁隧道结数据由自由层磁化方向决定表现为高阻和低阻两种状态。读操作就是检测电阻状态写操作就是翻转磁化方向所以不存在电子泄漏、也没有擦写磨损这个说法。下面这张表是我自己整理的选型对比参考价值比单看某个器件的数据手册更直接特性MR25H40CDFMRAMNOR FlashEEPROM存储原理磁隧道结电阻变化浮栅电荷浮栅电荷写前擦除不需要需要不需要但有页面结构限制写入寿命无限次约 10 万次约 100 万次数据保持20 年以上10~20 年10~20 年写操作耗时微秒级毫秒级含擦除时间毫秒级典型容量4Mbit1~128Mbit1Kbit~2Mbit工业级温度-40~105℃需选工业级型号需选工业级型号我的结论很明确如果要频繁写、少量读、还要扛恶劣环境MRAM 是最省心的选择。如果只是存一个极少改动的 BOOT 配置NOR Flash 完全够用不必为了用 MRAM 而用 MRAM。2. 硬件连接从 STM32F101ZG 到 MR25H40CDF 的 SPI 接法2.1 引脚映射与最小系统接线STM32F101ZG 是 144 引脚的 Cortex-M3 芯片主频最高 36MHz片上有多个 SPI 外设。我选了 SPI1因为它的引脚在 PA5/PA6/PA7PCB 布局上离 MCU 供电脚近走线容易控制。SPI1 的引脚和 MR25H40CDF 对应关系如下STM32F101ZG 引脚功能连接目标方向PA5SCKMRAM SCK输出PA6MISOMRAM MISO输入PA7MOSIMRAM MOSI输出PA4GPIO做 CSMRAM CS输出注意 CS 我没有用 SPI1 的硬件 NSS而是用普通 GPIO 手动控制。原因很简单硬件 NSS 在自动模式下容易受 SPI 外设状态影响在多设备和异常复位时不好掌控而手动拉 CS 在工业代码里调试起来最直观。CS 引脚上最好加上拉电阻默认保持高电平防止 MCU 复位期间 MRAM 被误选中。电源方面MR25H40CDF 是 3.3V 供电VDD 和 VSS 之间要放一个 0.1uF 陶瓷电容位置尽量靠近芯片电源引脚。如果 MRAM 离 MCU 稍远我还会在 PCB 上额外加一个 4.7uF 的钽电容做低频去耦实测对 SPI 信号稳定性有帮助。2.2 CAP 引脚、写保护和去耦容易被忽略的三个细节MR25H40CDF 有一个 CAP 引脚这个不是普通滤波引脚它是内部电荷泵相关的外置电容引脚必须在 CAP 和 VSS 之间接一个 0.01uF 到 0.1uF 的电容否则写入会不稳定甚至完全失败。我第一次画板时差点把这个电容省掉后来看数据手册的典型应用图才发现它是必须的。WP 脚是写保护引脚低电平有效。正常工作时我会把它直接通过 10kΩ 电阻上拉到 3.3V这样状态寄存器不会被意外修改。如果要更严格地防止固件误写也可以把 WP 接到 MCU 的 GPIO只有需要更新保护位时才拉低但这种做法会多占一个 IO一般设备用不到。HOLD 脚同样要上拉到高电平否则 MRAM 会在总线传输中途被意外挂起。很多人在做原理图时只接 SCK/MOSI/MISO/CS忽略了 HOLD 和 WP结果板子回来后各种奇怪问题不断。把这两个脚上拉能省掉后面非常多排查时间。3. 软件驱动SPI 初始化与 MRAM 读写命令的完整实现3.1 STM32F101ZG 的 SPI1 配置代码软件部分第一步是配置 SPI1。这里要注意 STM32F101ZG 的 APB2 时钟最高 36MHz而 MRAM 支持最高 50MHz 级别的 SPI 时钟所以瓶颈在 MCU 这边而不是存储芯片那边。调试初期我建议先把 SCK 压到 1~4MHz排除时序问题后再提频。实际我最终用的是 4.5MHz 左右SOP-8 封装短走线下很稳。void SPI1_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // PA5 SCK, PA7 MOSI - 复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // PA6 MISO - 浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); // PA4 CS - 普通推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); // 拉高 CS进入空闲状态 GPIO_SetBits(GPIOA, GPIO_Pin_4); }这里选择 SPI Mode 0也就是 CPOL0、CPHA0MR25H40CDF 同时支持 Mode 0 和 Mode 3用 Mode 0 和大多数 SPI 设备的默认习惯一致。预分频取 8是为了把 SCK 控制在安全范围如果你的主频已经被 RCC 降低过记得按实际总线频率重新算原则是先慢后快。3.2 MR25H40CDF 的读、写、写使能命令时序MRAM 的指令集跟 SPI NOR Flash 很像但少了一堆擦除指令。实际用到的核心命令就这几个命令操作码说明WREN0x06写使能写数据前必须发RDID0x9F读器件 IDRDSR0x05读状态寄存器READ0x03读数据24 位地址WRITE0x02写数据24 位地址关键点在第 1 条向 MR25H40CDF 发 WRITE 命令之前必须先发 WREN否则写入被直接忽略。这是我调驱动时第一个被坑到的地方后面会专门说。读数据不需要写使能CS 拉低后发操作码 0x03再跟 3 字节地址然后连续读字节即可。MRAM 的地址是 24 位格式但实际容量只有 512KB所以最高字节只需要补 0。写入完成后数据立即生效不需要像 NOR Flash 那样等待“擦除完成”或“编程完成”这是物理原理决定的。对驱动来说这意味着写操作非常快可以放心地在中断上下文里写一小段日志。3.3 一个可以直接用的驱动骨架下面是精简过的驱动代码可以直接在项目里扩展。SPI 发送函数同时用做接收这是 SPI 全双工的标准做法。#define MRAM_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define MRAM_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4) uint8_t SPI1_SendByte(uint8_t byte) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, byte); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); return SPI_I2S_ReceiveData(SPI1); } void MRAM_WriteEnable(void) { MRAM_CS_LOW(); SPI1_SendByte(0x06); MRAM_CS_HIGH(); } void MRAM_ReadData(uint32_t addr, uint8_t *buf, uint32_t len) { MRAM_CS_LOW(); SPI1_SendByte(0x03); SPI1_SendByte((addr 16) 0xFF); SPI1_SendByte((addr 8) 0xFF); SPI1_SendByte(addr 0xFF); for (uint32_t i 0; i len; i) { buf[i] SPI1_SendByte(0x00); } MRAM_CS_HIGH(); } void MRAM_WriteData(uint32_t addr, const uint8_t *buf, uint32_t len) { MRAM_WriteEnable(); MRAM_CS_LOW(); SPI1_SendByte(0x02); SPI1_SendByte((addr 16) 0xFF); SPI1_SendByte((addr 8) 0xFF); SPI1_SendByte(addr 0xFF); for (uint32_t i 0; i len; i) { SPI1_SendByte(buf[i]); } MRAM_CS_HIGH(); }一个小经验如果项目里 MRAM 不只一片可以把 CS 换成数组管理的多个 GPIO读写函数增加一个 chip_id 参数就行。SPI 外设本身不用重复初始化靠 CS 切换片选即可。4. 掉电保护与数据一致性真正干活的人才会关心的事4.1 掉电瞬间数据被写坏的根因分析用 Flash 做数据记录时最怕的就是写入过程中掉电。Flash 的写操作是先擦除再编程过程中一旦掉电可能留下一个既不是旧数据也不是新数据的中间状态下次上电读到的内容不确定轻则参数异常重则整个存储区逻辑混乱。EEPROM 虽然少了擦除动作但内部写周期长掉电窗口同样存在。MRAM 的写入机制不同它是磁化方向翻转写操作在极短时间内同步完成没有“正在擦除中间态”这种说法所以在存储介质层面天然更抗掉电。但“介质不会坏”不等于“应用层不会坏”。如果程序在写一条长记录的过程中掉电比如写了帧头还没写数据和 CRC那这条记录就是不完整的系统上电后必须能识别出来并丢弃或重拷。这就是数据一致性要解决的问题。4.2 CRC 校验、双备份区和事务化写入设计我在项目里用了一套比较朴素但很实用的方案所有结构化写入都必须带 CRC16 校验不允许裸写裸读。每条记录结构如下帧头类型长度数据CRC160xAA551 字节2 字节N 字节2 字节写入流程是先把内容组织到 RAM 缓冲算出 CRC然后一次性写入 MRAM。读取时先检查帧头再重算 CRC不匹配就丢弃这条记录。CRC 多项式用的是标准 CCITT 0x1021代码网上很多关键是必须把长度、数据、类型全算进去不能只算数据部分。参数区我一般采用双备份A 区和 B 区各存一份完整参数表。写入时先写备用区写完后读回来校验 CRC确认无误后再更新有效标志。标志字也有讲究先写成 0x0000表示“正在更新”全部完成后写 0x55AA上电后只认 0x55AA 为有效。这样即使掉电发生在 A 区备份过程中B 区仍是旧的有效数据系统不会出现“全部参数丢失”的情况。对于日志区我用了环形缓冲加序号的方式。头部固定存写指针、读指针和最新序号每跳一条日志写指针 1序号 1。上电后如果发现头部 CRC 不对就把日志区标记为“非法”从空日志开始重新记录。这个设计不是最优的但对工业设备来说保证系统能启动比保留所有历史更有价值。5. 实测踩坑记录从 EEPROM 迁移到 MRAM 最容易翻车的几个点5.1 把 EEPROM 的“先擦后写”习惯带过来写操作踩坑我第一次调 MRAM 时直接在原有 EEPROM 驱动上改习惯了先发“擦除”命令、再写数据、再轮询写完成标志。结果 MRAM 根本没有擦除命令我发的 0x20 扇区擦除指令被当成无效指令吞掉了程序表面上没报错但数据一直写不进去。查了大半天才发现问题不在时序而在我脑子里还带着 Flash/EEPROM 的旧思维。MRAM 的写入套路就三步WREN、CS 拉低发 0x02 和地址、连续发数据。写完立刻生效没有轮询状态寄存器这一步。刚开始调试时我用逻辑分析仪抓波形看到 WREN 之后 CS 拉高、又拉低、发 0x02数据传输都没问题唯独忘了先查器件 ID。后来补了一句读 ID确认返回的数据和手册一致才彻底排除“芯片没焊好”和“指令理解错”两个方向。5.2 波形和布线问题长 SPI 线导致的随机读错在实验室用杜邦线连着玩完全没问题的代码一到工业样机上就偶发读错而且错误是随机性的有时连续读一千次都不出错有时突然冒出来一个 0xFF。这个问题最隐蔽。查下来根因在 SPI 走线太长MISO 线上反射严重加上 MR25H40CDF 的输入电容和 MCU 引脚寄生电容一叠加高频下信号边沿就乱了。我的处理办法是第一把 SCK 从 4.5MHz 降到 2.25MHz先确认逻辑正确第二把 MRAM 尽量靠近 MCU 放置走线控制在 3~5 厘米以内第三在 MISO 线上串了一个 33Ω 电阻抑制振铃。这三个动作做完之后样机连续跑了一周没有再出现读错。还有一个容易被忽略的细节CS 拉低后不要立刻发第一个字节在长线环境下让 CS 信号先稳定一小段时间。硬件 SPI 本身没有这个要求但如果用 GPIO 模拟 SPI这种建立时间必须写进代码里否则不同批次的板子表现会差很多。5.3 温度、静电与焊接注意事项MR25H40CDF 这类磁存储芯片内部是微小的磁隧道结和 CMOS 结构对焊接温度和静电比普通 EEPROM 敏感一点。手工焊接时建议用恒温烙铁温度调到 300 度左右每个引脚一次焊完不要反复烫。如果必须返修等芯片完全冷却后再二次焊接连续高温加热对内部结构和引线键合都不友好。另外在干燥环境下用手直接碰芯片引脚有可能产生静电放电。工业设备如果前面板有金属接口SPI 线上最好预留 TVS 管位没有条件的话至少在 MISO/MOSI/SCK 上各串一个 22Ω 电阻做保护。不要以为只有通信接口才需要防静电存储芯片电源引脚的 ESD 防护同样重要。6. 落地场景参考把 MRAM 用在数据记录器里的整体设计6.1 一个可复用的逻辑分区与地址规划MR25H40CDF 的 512KB 空间我习惯切成三段来用。系统参数区放 CONF 标志、设备地址、校准系数、运行模式备份区做双份镜像日志区做环形记录。具体规划如下区域地址范围大小用途参数区 A0x00000 ~ 0x07FFF32KB当前参数参数区 B0x08000 ~ 0x0FFFF32KB参数备份系统信息区0x10000 ~ 0x10FFF4KB版本号、启动次数日志区0x11000 ~ 0x7FFFF444KB运行日志环形缓冲参数区为什么只留 32KB因为工业设备的参数表通常只有几十个变量32KB 足够放几千组带 CRC 的结构化参数。日志区占大头按每条 128 字节算可以存三千多条完整日志按 100ms 一条能记 5 分钟如果只是记录异常事件那基本能用很久。6.2 后续可以扩展的玩法MRAM 的无限写特性在数据缓存场景里非常好用。比如在 STM32F101ZG 上采集 ADC 波形可以先把一段数据连续写入 MRAM等上位机空闲时再整批读出这样不需要外扩 SRAM也不需要复杂的 DMA 乒乓缓冲。如果项目上电后需要快速恢复现场利用 MRAM 的字节可写特性还可以把它当成一个“掉电不掉内容的 RAM”来用很多全局变量初始化值可以直接从里面加载。多个 MRAM 芯片做扩展也简单一个 SPI 接口挂多片CS 用 GPIO 分开选地址空间逻辑上独立代码里给读写函数加片选参数就行。对于功耗敏感的设备MRAM 读操作不需要像 Flash 那样整页读入 SRAM直接按地址随机读指定字节效率高很多。最后分享一个我自己的体会MRAM 不是万能的它容量有限、价格比 Flash 贵不适合做大文件系统或者超大存储池。但在“频繁写、必须可靠、环境恶劣”这个细分场景里它的综合成本反而是最低的因为省掉了大量的坏块管理、磨损均衡和掉电恢复代码。选型前先把需求里的写入频率、掉电窗口、工作温度列清楚再决定用哪种存储比拿到芯片就写驱动靠谱得多。
返回列表