尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

工业存储选型:用MRAM替代EEPROM和Flash,解决掉电数据保护难题

工业存储选型:用MRAM替代EEPROM和Flash,解决掉电数据保护难题 去年做一套工业电表的无线抄表模块项目组对存储方案提出了一个很硬性的要求设备每 5 分钟记录一次运行状态连续存 30 天而且电网掉电的那一瞬间最后一批数据必须保住。最开始用的是 EEPROM写入 3~5ms 还能忍但 10 万次擦写寿命一算下来连三年都撑不到换个 SPI NOR Flash寿命倒是好一些又卡在掉电时可能丢数据因为“先擦除再写入”的过程太长。几轮折腾之后我把 MR25H40CDF 这颗 4Mbit 的 MRAM 接到 PIC18F46K20 上存储问题才算真正解决。这篇文章把硬件连接、SPI 驱动、数据布局和实测数据一次性写清楚给正在做工业嵌入式存储选型的朋友一个参考。1. 为什么工业场景里我放弃了EEPROM和Flash改用MRAM1.1 工业存储绕不开的三个硬约束工业设备的数据存储和消费电子产品不太一样消费级芯片掉一帧数据顶多卡一下工业现场掉一个字节可能就会让产线停机。我总结下来工业存储必须同时满足三个约束高频次写入、瞬间掉电保护、宽温度范围下的稳定性。高频次写入主要考验擦写寿命。EEPROM典型寿命是 10 万次到 100 万次听起来不少但设备每 5 分钟写一次24 小时就是 288 次一年超过 10 万次刚好卡在寿命边缘。Flash 的寿命虽然能到几十万次但它有页擦除的概念写一个小数据也要先擦掉一整个扇区既耗时又容易在掉电时破坏相邻数据。掉电保护是另一个痛点。Flash 在写入过程中一旦断电可能不光是当前页数据损坏连整个块都会异常。工业现场电源波动是常态尤其是电机、变频器启动瞬间电压跌落是不可避免的。存储方案如果不能在毫秒级窗口内完成关键数据的落盘后面的故障分析根本没依据。温度范围也容易被忽略。很多消费级 Flash 标称 -40℃ 到 85℃但实际在低温下电荷泵效率下降读操作的误码率会明显升高。工业设备放在户外配电柜里冬天凌晨就是零下二三十度这时候存储可不能掉链子。1.2 MR25H40CDF 的参数为什么正好对上MR25H40CDF 是 Everspin 的 4Mbit MRAM结构上是 512K × 8 位SPI 接口3.3V 供电。MRAM 最核心的特性是磁阻存储写数据靠翻转磁极性而不是积累电荷所以没有“先擦除再写”的步骤也不存在擦写寿命限制。官方标称数据保持 20 年以上这个数据对我们设计 10~15 年寿命的工业设备来说余量是够的。实际用下来对我帮助最大的是它的字节级随机访问能力。任意地址随时读、随时写不需要管页对齐不需要管扇区状态。以前用 Flash 时我总得维护一张坏块表写日志还要先算页边界改用 MRAM 之后这些逻辑全部删掉了代码量少了一大截。当然MRAM 也不是什么都好。它的容量比同价位的 Flash 小得多4Mbit 在工业设备里只能做参数、日志这类小数据存储不适合存文件系统或者大图片。如果项目需要塞几百 MB 的数据还是老老实实用 eMMC 或 SD 卡。1.3 为什么搭配 PIC18F46K20 这颗 MCUPIC18F46K20 是 Microchip 的 8 位 MCU40 引脚两个 MSSP 硬件串行模块其中一个可以配成 SPI 主模式。它的工作电压是 2.0V 到 5.5V温度档位有工业级规格价格也合适在电表、传感器、控制器里很常见。我要的只是把 MR25H40CDF 搓得服服帖帖不需要跑 Linux不需要大容量 RAM这颗 MCU 足够。还有个现实原因团队里工程师对 PIC18 的寄存器很熟后续维护成本低。如果换成 ARM Cortex-M虽然性能更好但原来的老工程师不一定能上手。所以选型不光是看芯片参数还得看团队技术栈。2. 硬件连接与信号完整性问题第一次打板就差点翻车2.1 引脚连接和电平匹配是第一个坎MR25H40CDF 的 SPI 引脚很标准CS、SCLK、SI、SO外加 VCC、GND、WP 和 HOLD。PIC18F46K20 的 MSSP1 模块把 SCK 映射到 RC3SDO 映射到 RC5SDI 映射到 RC4CS 可以任意接一个 GPIO我习惯用 RD0。这个连接看起来简单但有一个大坑容易被忽略电平匹配。MR25H40CDF 的绝对最大额定电压是 3.6V如果 PIC18F46K20 用 5V 供电那么 SDO 输出的高电平就是 5V直接进 MRAM 的 SI 引脚属于违规操作。当时总觉得 MRAM 内部有保护二极管强行直连也能跑结果整机测试时就出现了偶发读回错误——同一个地址读十次偶尔会错一个字节。解决办法有两个一是把 PIC18F46K20 的供电也改成 3.3V这样两边电平匹配也是我最推荐的方式二是加一颗 TXB0104 电平转换芯片。我用示波器对比过直连方式在低速 SPI 下看着没问题但边沿上冲很严重改成电平转换后信号干净了读回错误率直接归零。信号PIC18F46K20 引脚MR25H40CDF 引脚连接备注SCLKRC3SCLK串联 22Ω 电阻MOSIRC5SI3.3V 电平匹配或经电平转换MISORC4SO可直接连接CSRD0CS普通 GPIO上电默认拉高2.2 WP 和 HOLD 脚不能悬空MRAM 的 WP 是写保护输入HOLD 是暂停传输输入这两个脚在不用的时候必须接高电平。HOLD 脚如果悬空现场只要有伺服电机启动、变频器切换空间电磁干扰就可能把 HOLD 拉低MRAM 会立刻暂停当前 SPI 传输后续字节全部错位。我第一次去客户现场排查时发现只要电机一启动日志区的 CRC 就开始报错但设备复位后又恢复正常。用示波器探头点在 HOLD 脚上能看到 2V 左右的毛刺。修复方式很简单HOLD 脚用 10kΩ 电阻上拉到 3.3V并在管脚旁并联一个 100pF 电容到地把高频毛刺滤掉。WP 脚同样上拉如果需要用软件写保护也可以由 MCU 的普通 GPIO 控制。2.3 PCB 布局电容位置比容量重要MRAM 对电源噪声比较敏感乱写数据时电流变化会引起 VCC 上的纹波。我最初在原理图上放了一颗 100nF 电容但布线时放在了距芯片 1cm 开外的地方实际测试中快速连续读写偶尔出错。后来把 100nF 和 1uF 电容都放在芯片 VCC 引脚的正下方并且再接一小块完整的地平面问题就消失了。SPI 走线也需要注意。SCK 是时钟信号边沿会有过冲我在 SCK、MOSI、MISO 三根线上各串联 22Ω 电阻可以压低边沿振铃。如果板子要通过工业 EMC 认证最好在靠近外部连接器的地方加 TVS 管到地优先保护 MRAM 那侧。2.4 上电顺序和掉电检测的硬件配合MRAM 本身没有内部电荷泵上电后几微秒就能访问比 Flash 快很多。但在工业设计里我习惯加一颗电源监控芯片监测 3.3V 电压低于阈值就先把 MCU 复位拉低同时把 MRAM 的 CS 脚拉高防止 MCU 在电压不稳时发出无效 SPI 命令。掉电检测也直接在硬件上处理。MCU 的供电前端用一个电阻分压采样电压接到模拟输入脚。当电压跌到 4.0V 以下时MCU 触发高优先级中断利用掉电缓冲时间把关键参数写进 MRAM。实测只要电源跌落不是瞬间短路从检测到写完成能控制在 30 微秒内这个时间窗对 MRAM 来说绰绰有余。3. 驱动层怎么把PIC18F46K20的SPI拧到高速还稳住3.1 MSSP 初始化相位和极性按 Mode 0 走MR25H40CDF 的数据手册里写明支持 SPI Mode 0 和 Mode 3。我统一用 Mode 0原因是多数 MCU 的例程默认都是 Clock 空闲低、数据在第一个边沿采样。PIC18F46K20 的 MSSP1 配置时需要设置 SSP1STAT 和 SSP1CON1 两个寄存器。在 MPLAB X IDE XC8 环境下我的初始化代码是这样写的void SPI_Init(void) { TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC5 0; // SDO 输出 TRISCbits.TRISC4 1; // SDI 输入 TRISDbits.TRISD0 0; // CS 输出 // 先关断 SPI SSP1CON1bits.SSPEN 0; // SPI Mode 0CKP0CKE0SMP1主模式时钟 Fosc/64 SSP1STAT 0x40; // SMP1, CKE0 SSP1CON1 0x20; // SPI Master, CKP0, SSPEN1, Fosc/64 MRAM_CS_HIGH; }Fosc/64 在系统主频 16MHz 下就是 250kHz这个速度跑 MRAM 没有任何压力。如果要追求速度可以把 SSPM 改成 Fosc/44MHz或者用 HSPLL 把时钟提到 64MHzSPI 能到 16MHz。但我量产时锁定了 10MHz原因是 PCB 走线没有做阻抗控制超过 10MHz 后读回误码率会上升下面细说。3.2 字节收发的基础函数PIC18 的 MSSP 是同时收发机制往 SSP1BUF 里写一个字节硬件同时会把接收移位寄存器里的内容装进同一个寄存器。读取的时候只要发送一个 0x00 就能把 SO 引脚上的电平采回来。基础函数uint8_t SPI_Exchange(uint8_t data) { SSP1BUF data; while (!SSP1STATbits.BF); // 等待硬件接收完成 return SSP1BUF; }这个函数在 10MHz 下执行一次大约 0.8 微秒足以支撑日志写入。3.3 WREN 写使能机制不能省MRAM 写入前必须先发 WREN 指令0x06将状态寄存器里的 WEL 置位。一次 SPI 写命令结束后WEL 会自动清零。很多从 Flash 或 EEPROM 转过来的工程师会把 WREN 放在初始化时只发一次然后后面就直接写结果发现第二个 WRITE 命令根本不生效。正确的做法是每次写操作前都发 WRENvoid MRAM_WriteEnable(void) { MRAM_CS_LOW; SPI_Exchange(0x06); // WREN MRAM_CS_HIGH; }CS 必须在 WREN 结束后拉高让 WEL 锁存生效。同样WRITE 指令也必须在 CS 拉低后发出完整的命令字节、地址字节和数据字节最后再拉高 CS。拉高 CS 是启动一次写操作的“触发”信号。3.4 单字节读写实现MR25H40CDF 的 READ 指令是 0x03后面跟 3 字节地址然后连续读数据。地址范围是 0x00000 到 0x7FFFF因为我用的是 4Mbit 容量字节地址需要 19 位。发送地址时按高、中、低三个字节顺序。uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t val; MRAM_CS_LOW; SPI_Exchange(0x03); // READ SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); val SPI_Exchange(0x00); MRAM_CS_HIGH; return val; } void MRAM_WriteByte(uint32_t addr, uint8_t dat) { MRAM_WriteEnable(); MRAM_CS_LOW; SPI_Exchange(0x02); // WRITE SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); SPI_Exchange(dat); MRAM_CS_HIGH; }WRITE 指令可以连续写任意数量的字节CS 保持低电平时地址自动递增一直到 0x7FFFF 后回绕到 0x00000。这个自动递增在日志写入时非常有用但也要小心别让地址越过边界跑回参数区。3.5 状态寄存器做诊断MR25H40CDF 支持 RDSR0x05指令可以读状态寄存器。其中 WEL 位可以确认写使能是否已经生效。在调试时我经常用它来判断是不是 WREN 漏发了。uint8_t MRAM_ReadStatus(void) { uint8_t st; MRAM_CS_LOW; SPI_Exchange(0x05); st SPI_Exchange(0x00); MRAM_CS_HIGH; return st; }如果发现写入不生效先读状态看 WEL 是否置位。如果置位了还写不进数据那就要检查 WP 脚是不是被拉低了或者硬件上有写保护。4. 工业设备上的数据布局参数区、日志区和双冗余校验4.1 初始地址空间规划512KB 空间在工业设备里不算大但用于参数和日志足够。我把地址分成四个区域确保不同类型的数据不会互相干扰0x00000 – 0x00FFF系统参数区存储设备编号、通信地址、校准系数0x01000 – 0x7EFFF循环日志区存储运行记录和异常事件0x7F000 – 0x7FFFF引导配置区存储启动标志、固件版本、双备份参数日志区给到接近 500KB按每条 64 字节算能存七千多条记录。如果 5 分钟写一条30 天是 8640 条容量刚好满足。如果嫌不够可以把日志压缩到 32 字节就翻倍了。4.2 参数区双备份与 CRC 校验设备参数一旦损坏会导致计量不准甚至停机所以我采用双备份策略。A 区存储当前参数加 CRC32B 区存储上一次有效的参数加 CRC32。上电读取流程是先读 A 区校验 CRC失败则读 B 区A、B 都失败就恢复出厂默认值。这样单个区的数据即便被写坏系统也能从另一区恢复。MRAM 单字节写入快所以每次修改参数时直接把 A、B 区各写一遍即可。以前用 EEPROM 要计算磨损均衡现在完全不需要因为 MRAM 没有擦写寿命限制。参数更新代码简写void Param_Save(param_t *p) { uint32_t crc CRC32(p, sizeof(param_t)); // 写参数结构体到 A 区 MRAM_WriteBuf(0x00000, (uint8_t *)p, sizeof(param_t)); MRAM_WriteBuf(0x00010, (uint8_t *)crc, 4); // 写镜像到 B 区 MRAM_WriteBuf(0x00100, (uint8_t *)p, sizeof(param_t)); MRAM_WriteBuf(0x00110, (uint8_t *)crc, 4); }4.3 循环日志区的具体实现日志区我用环形结构管理固定起始地址 LOG_DATA_START固定结束地址 LOG_DATA_END在 LOG_HEAD 区域保存读指针和写指针。每条记录开头放 2 字节长度和 2 字节 CRC后面是数据。写日志时先读写指针定位到目标地址写数据最后再更新写指针。这里有一个关键经验指针一定要最后写。如果一条日志才写了一半就掉电写指针还停留在旧位置上电后就能忽略这条不完整记录。如果反过来先更新指针再写数据掉电后指针指向一个空区域系统读到一堆无效字节恢复逻辑就会复杂很多。#define LOG_HEAD_ADDR 0x01000 #define LOG_WR_PTR_ADDR 0x01008 #define LOG_DATA_START 0x01020 #define LOG_DATA_END 0x7EFFC void Log_Append(uint8_t *buf, uint16_t len) { uint32_t wp MRAM_ReadByte(LOG_WR_PTR_ADDR) 16 | MRAM_ReadByte(LOG_WR_PTR_ADDR 1) 8 | MRAM_ReadByte(LOG_WR_PTR_ADDR 2); uint32_t addr LOG_DATA_START wp; if (addr len 4 LOG_DATA_END) { addr LOG_DATA_START; wp 0; } // 先写记录头长度和 CRC MRAM_WriteByte(addr, (len 8) 0xFF); MRAM_WriteByte(addr 1, len 0xFF); // 再写数据 MRAM_WriteBuf(addr 4, buf, len); // 最后更新写指针 wp len 4; MRAM_WriteByte(LOG_WR_PTR_ADDR, (wp 16) 0xFF); MRAM_WriteByte(LOG_WR_PTR_ADDR 1, (wp 8) 0xFF); MRAM_WriteByte(LOG_WR_PTR_ADDR 2, wp 0xFF); }上面的写法为了可读性省略了 CRC 计算和跨边界拆分实际项目里要注意一条记录不能跨越 LOG_DATA_END。如果跨过结束地址需要拆成两段写入否则会撞上参数区。4.4 读取日志与上传读取日志就是一个循回扫描。从读指针开始读记录头校验 CRC通过就上传然后跳到下一条记录直到读指针追上写指针就说明暂时没有新日志了。PIC18 没有 DMA连续读几千字节会占用大量 CPU所以我把读取放在后台定时器中断里每 10ms 只读 128 字节分片上传到上位机。这样前台照样执行采样和继电器控制不会卡顿。4.5 掉电保护的实际效果掉电处理写在了 MCU 的高优先级外部中断里。掉电瞬间MCU 先关全局中断然后把关键的运行计数值、校准参数、状态位写入 MRAM 的三个固定地址全部是单字节写总共也就 20 个 SPI 事务。我测过在 10MHz SPI 下整个过程需要约 30 微秒而稳压器在掉电后能维持 3.3V 约 1ms完全来得及。换成 Flash 就不行了。哪怕写一个字节也要先检查页状态、擦除页再编程最快也要几毫秒掉电窗口根本撑不住。MRAM 这种即写即得的特性是工业掉电保存的最强保障。5. 测试得到的性能数据以及和标称值的差距5.1 不同 SPI 时钟下的实测吞吐我在 PIC18F46K20 主频 16MHz 下做了轮询测试用 GPIO 翻转周期算有效吞吐。连续读 512KB 数据然后计算耗时SPI 时钟配置实测读吞吐实测写吞吐温度稳定性1 MHz124 KB/s122 KB/s很稳4 MHz495 KB/s490 KB/s很稳10 MHz1.21 MB/s1.18 MB/s偶发轻微波形劣化20 MHz2.2 MB/s1.9 MB/s读回误码率升高标称支持 40MHz但 PIC18 的 MSSP 模块在高频下采样窗口比较窄加上我板子上的走线并没有按 50Ω 阻抗设计所以 20MHz 时就已经有偶发错误。我最终量产跑 10MHz这个速度对日志记录和参数存取完全够用。5.2 温度循环与数据保持我拿三块板放进了温度箱从 -40℃ 到 85℃ 循环 72 小时每个温度点停留 30 分钟循环结束后再读回全部数据CRC 全部正确。我的日志区读指针和写指针都和测试前完全一致没有出现单比特翻转。这说明 MRAM 的温度特性比 Flash 更让人放心。数据保持方面标称 20 年是指 25℃ 下的计算值。工业设备设计寿命一般 10 到 15 年所以余量是够的。如果工作在 85℃ 以上保持时间会缩短但 MRAM 仍然比 Flash 在高温下要稳得多因为磁存储不依赖电荷泄漏。5.3 循环上电和掉电压力测试掉电压力测试是这样做的MCU 每 200ms 写一条 64 字节日志然后切掉电源再自动上电复位重复 1000 次。读回日志区有 998 次全部完整2 次最后一条记录 CRC 错误但头部指针没被污染错误记录直接被跳过。也就是说已经存进去的历史记录损坏率是 0。我还做了一次极端测试在写日志的第 40 个字节时用继电器瞬间掐断电源。恢复后发现这一条记录不完整但整块区域其它数据完好。这正是 MRAM 字节级写入的特点——每个字节写入是独立的不会像 Flash 那样擦除中断导致整片数据异常。5.4 对 MCU 实时性的影响PIC18F46K20 在 16MHz 下单条 SPI 数据传输占用 CPU 时间。如果只是读写参数对实时性没有感觉但连续读取日志时CPU 会长时间停在 while 循环里等 BF 位。我的做法是把日志上传拆成分批任务每次只处理 128 字节时间上分摊保证控制任务的中断延迟不受影响。MRAM 本身不会阻塞 CPU阻塞来自软件轮询方式如果介意可以考虑用带 SPI 环缓冲的外设或者换一个带 DMA 的 MCU。6. 在现场环境里踩过的坑和修复记录6.1 4.5V 逻辑直连引起偶发写错前面提到过的电平匹配问题我在第一版样机上实际烧过一次 MRAM。那是调试正欢的时候发现参数区某几个字节隔几天就跳变排查很久才发现是 SI 引脚输入范围超了。MRAM 是 3.3V 器件高电平只要 0.7×VCC 就能识别但 5V 信号会向内部保护二极管注入电流长时间下来就会损伤管脚。改成 3.3V 供电后一切恢复正常。切记工业板上电源种类多先检查逻辑电平再谈信号质量。6.2 HOLD 脚悬空日志区被电机干扰这个案例前面提过但值得再细说。客户现场有一台 7.5kW 伺服电机电机一启动日志区随即出现 CRC 错误。我原本以为是 SPI 时序受干扰把 SCK 和 MOSI 都加了 RC 滤波无效。最后拿逻辑分析仪抓 HOLD 脚发现它在一个 20kHz 的 PWM 噪声下反复抖动。解决办法很朴素HOLD 上拉到 VCC并 100pF 电容到地从此再没复发。6.3 30cm 飞线跑 20MHz读回全是乱码设计初期为了调试方便我引了一条 30cm 的排线把 MRAM 放在扩展板上排线还带着杜邦插头。SPI 在 10MHz 时偶尔错一两个 bit20MHz 时基本读不出来。究其原因排线没有作阻抗控制信号边沿过冲和串扰叠加采样点完全错位。后来把 MRAM 直接焊接在主板上走线长度压到 5cm 以内20MHz 也能稳定读写了。工业级产品里存储器的 SPI 线最好别飞线除非你愿意降到很低的时钟。6.4 拿 Flash 的坏块逻辑去管 MRAM纯属画蛇添足我第一次用 MRAM还在代码里保留了一个坏块表每次写之前模拟 Flash 的识别流程。结果没有任何异常反而占用了 100 多行的逻辑和一点点存储空间。后来又有一个同事把 EEPROM 驱动的“写后延时 5ms”带过来连续写日志时每一条都卡 5ms完全把硬件优势浪费了。新的存储介质就要把旧的思维惯性丢掉。6.5 代码移植时的核心差异提醒MR25H40CDF 在很多寄存器指令上兼容 SPI EEPROM比如 0x06、0x02、0x03 都一样但最关键的差异是没有“写周期时间”这个词。写 EEPROM 时CS 拉高后芯片内部要编程几毫秒软件必须等到时间结束才能操作下一字节MRAM 则不需要CS 拉高就是写完成。反过来如果拿 MRAM 驱动去写 EEPROM就会因为不等 t_WR 导致数据丢失。移植之前务必撕下数据手册看看“Write Cycle Time”这个参数。最后再分享一个我自己的小习惯画板子时MRAM 的 WP 和 HOLD 上拉电阻一定要靠近芯片管脚并且两个脚各加一个 100pF 到地能挡掉大部分现场传导干扰。数据布局上环形缓冲的头部指针永远最后写如果掉电时正在写日志最多丢一条但历史数据一条都不会坏。MR25H40CDF 我在量产板上跑了七个多月没有再因为存储本身出过故障。如果你也被工业存储的寿命、掉电、高温问题折磨过这颗 MRAM 值得放进备选清单里仔细测一轮。
返回列表