ARTICLE DETAIL

资讯详情

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

MRAM替代电池备份:基于MR25H40CDF与PIC24的工业掉电数据保护方案

MRAM替代电池备份:基于MR25H40CDF与PIC24的工业掉电数据保护方案 刚接手一个工业记录仪项目时甲方提了一个很朴素的需求设备断电之后最近一组标定数据不能丢。当时板子上用的是普通 SRAM 加电池备份电池一老化问题就成片出现。后来我把方案整体换成了 MR25H40CDF MRAM 加 PIC24F16KA102 单片机用 SPI 接口直接读写再没出过掉电丢数据的毛病。这篇文章就把这套组合的选型原因、硬件接线、底层驱动封装、数据管理策略和现场排障经验完整写出来给做嵌入式存储方案的工程师一个可以直接抄作业的参考。很多人一听到“存储”第一反应是 EEPROM 或者 SPI Flash。但在工业场景里这两类器件的短板非常明显EEPROM 写寿命只有几十万次擦写慢Flash 要按页擦除写一个字节还得先做整块搬移而且程序区频繁写入会加速磨损。MR25H40CDF 属于 MRAM也就是磁性随机存储器它把磁性状态作为存储介质写寿命达到 10 的 12 次方次写入速度接近 SRAM断电数据又不丢失。这套组合特别适合数据记录、参数保存、掉电保护、工业仪表校准值存储这类嵌入式应用。接下来按项目推进顺序从选型到驱动到排障逐个拆解。1. 方案背景与选型思路1.1 为什么是 MRAM从掉电丢失说起老式的工业设备常做两套存储一套用 Flash 存固件和配置一套用 SRAM 加电池保存运行参数。问题是电池总有关键时刻电压不足的时候设备一断电校准数据和累计产量全部归零甲方心情不好现场工程师只能挨骂。MRAM 的出现其实就是为了解决这种尴尬它本质上是一种 RAM 存储结构但存储单元里的数据不是靠电荷保持而是靠磁性方向保持所以外部电源断掉之后数据依然安安稳稳躺在那里。对比一下主流非易失存储器件就能很清楚看到 MRAM 的定位。EEPROM 写入一个字节要等 3 到 5 毫秒写次数上限大约 100 万次级别SPI NOR Flash 写入前要先擦除擦除粒度是 4KB 甚至 64KB一个 4KB 扇区的擦写寿命通常在 1 万到 10 万次之间。这些参数在消费类产品里够用但在工业频繁读写场景里很容易被耗尽。MR25H40CDF 的写耐久性在 10 的 12 次方级别十六个字基本不用考虑磨损。同时它写数据不需要先擦除任意地址可以按字节直接写随机写入时间在几十纳秒量级比 EEPROM 快几个数量级。这里放一张我自己做选型时整理的对比表方便大家直接参考存储类型写耐久性写入前是否需要擦除典型写入速度数据保持适用场景EEPROM10^5 ~ 10^6 次否但按字节写很慢毫秒级10 年以上低频率参数保存SPI NOR Flash10^4 ~ 10^5 次是按扇区擦除页编程毫秒级10 年以上固件、批量数据MRAM10^12 次以上否纳秒级10 年以上频繁读写、掉电保护SRAM 电池无限否纳秒级依赖电池不推荐工业使用MR25H40CDF 容量是 4Mbit也就是 512KB工业级温度范围SPI 接口最高可以跑到 40MHz。对大部分嵌入式记录任务来说这个容量足够存几千条带时间戳的日志或者几百组标定参数。更关键的是数据写入之后立刻生效不需要像 Flash 那样等“写完成”标志这给我们做掉电保护省下很大麻烦。1.2 PIC24F16KA102 的角色PIC24F16KA102 是 Microchip 的 16 位低功耗单片机程序空间 16KB 级别工作在 1.8V 到 3.6V 范围工业级温度规格自带多个串行外设和 10 位 ADC。选它并不是因为它性能多强而是因为这个项目原本的主控就是这颗芯片外围代码已经成熟我们只需要在上面加一段 SPI 驱动即可。这颗芯片做主控和 MRAM 的搭配有几个实际好处。第一PIC24F 系列的 SPI 模块可以做主模式配置简单寄存器少适合在裸机上直接操作。第二它的 GPIO 推挽能力足够直接驱动 MRAM 的片选引脚不需要额外加缓冲器。第三这颗芯片在工业环境里很皮实ESD 抗性、上电时序都比普通消费级 MCU 稳妥。如果你手头项目的主控是别的芯片也完全可以用这套思路移植本质上就是 SPI 总线操作逻辑完全一致。1.3 整体数据流与职责划分系统里 PIC24F16KA102 承担三件事一是接收上位机或传感器过来的数据二是将数据按预定义格式组织成帧三是通过 SPI 接口写入 MR25H40CDF读操作则正好反过来。MRAM 在这里扮演的是“掉电不掉数据”的中间仓库它不和主控抢逻辑也不参与传感器采集就是老老实实按地址存取。实际项目里我会把存储区域分成参数区和数据区。参数区保存设备地址、量程、校准系数数据区保存运行日志、事件记录。每次上电时主控先读参数区校验帧头帧尾如果 CRC 校验失败再用备份区恢复。这套“主区加备份区”的玩法在后面第 4 章展开。整体数据链路是主控 → SPI 命令封装 → MRAM 地址空间 → 回读校验 → 主控确认。2. 硬件连接与电路设计2.1 MR25H40CDF 引脚与接线表MR25H40CDF 是 8 脚封装引脚定义很标准几乎和普通 SPI Flash 一致。下面是工程中实际的接线表我以 3.3V 系统为例主控选择 PIC24F16KA102 的 SPI1 模块片选用普通 GPIO 控制MR25H40CDF 引脚功能连接目标1 号引脚 CS#片选低电平有效主控 GPIO比如 RB72 号引脚 SCKSPI 时钟主控 SCK1 引脚3 号引脚 SISPI 数据输入主发从收主控 SDO1 引脚4 号引脚 SOSPI 数据输出主收从发主控 SDI1 引脚5 号引脚 VSS地GND6 号引脚 VCC电源3.3V7 号引脚 HOLD#暂停传输低电平有效接 VCC 上拉8 号引脚 WP#写保护低电平有效接 VCC 上拉或 GPIO 控制这里有一个特别容易踩的坑很多人把 HOLD# 和 WP# 直接悬空结果调试时发现数据偶尔丢一两个字节。原因是 HOLD# 悬空时引脚电平如果被外部干扰拉低芯片会暂停 SPI 通信主控还在继续发时钟双方状态机就错位了。我的习惯是调试阶段把这两个引脚都用 10k 电阻上拉到 VCC批量生产时再根据防写保护策略决定是否把 WP# 接到 GPIO 控制但 HOLD# 永远不允许悬空。2.2 电源与去耦设计MR25H40CDF 工作电压 3.3V电源设计不过分要求但既然用在工业环境就不能太随便。我通常会靠近芯片 VCC 引脚放一个 100nF 陶瓷电容再在 PCB 电源入口放一个 10uF 钽电容或者陶瓷电容做蓄能。这样从电源平面到芯片引脚的高频阻抗和低频储能都照顾到了。假如设备旁边有继电器、电机、变频器这类干扰源建议在供电入口加一个 TVS 管方向对地用来吸收瞬态浪涌。另外要注意 SPI 信号走线。SCK、SI、SO 的走线尽量短不要穿过大电流区域更不能和 220V 交流线平行走超过 2 厘米。工业设备布局紧凑时信号线和电源线绕行宁可多过几个孔也不要贪图方便直接拉长线。实测经验是MRAM 的 SPI 时钟在 8MHz 以下时走线长度控制在 5 厘米内基本不会有串扰问题超过 8MHz 后就要开始考虑终端匹配和层间参考平面了。2.3 焊接与上电检查清单MR25H40CDF 有些封装是 DFN 类型焊接时要控制好温度曲线手工焊接用烙铁 350 度左右每个引脚加热时间控制在 3 秒内避免反复加热导致塑料封装变形。焊接完成后不要急着上电跑程序先做三步检查第一步用万用表量 VCC 到 GND 之间的电阻排除短路第二步上电后用示波器看 3.3V 电压纹波确认不超过 50mV第三步把 CS# 手动拉低用逻辑分析仪或者示波器看 SCK 和 SI 上是否只有主控发出的信号没有异常毛刺。这三步做完硬件基本可以确定没有问题再往下写代码。我见过不少同事一上来就写驱动结果调了半天发现是芯片虚焊白白浪费一天时间。3. 底层驱动与读写实现3.1 SPI 初始化与模式选择PIC24F16KA102 的 SPI1 模块配置不复杂直接操作寄存器就可以完成。MR25H40CDF 默认支持 SPI Mode 0 和 Mode 3绝大多数项目用 Mode 0 就行也就是 CPOL0、CPHA0。在 Microchip 的 SPI 模块里Mode 0 对应的寄存器配置是 CKP0、CKE1。这里代码注释里我会写清楚方便大家按实际器件微调。#define SPI_CS_TRIS TRISBbits.TRISB7 #define SPI_CS_LOW() LATBbits.LATB7 0 #define SPI_CS_HIGH() LATBbits.LATB7 1 void mram_spi_init(void) { // 片选引脚配置为推挽输出默认拉高 SPI_CS_TRIS 0; SPI_CS_HIGH(); // 复位 SPI1 模块 SPI1STAT 0; SPI1STATbits.SPIROV 0; // 主模式Mode 0CKP0, CKE1 // PPRE0b10 一级分频 /4SPRE0b011 二级分频 /2合计 8 分频 SPI1CON1 0x0130; // 使能 SPI1 SPI1STATbits.SPIEN 1; }SPI 波特率需要看系统时钟频率。PIC24F16KA102 如果跑 16MHz 主频上面的 8 分频配置可以得到 2MHz 的 SCLK这个频率对 MR25H40CDF 来说绰绰有余因为芯片支持 40MHz。实际项目中我会把关频率设置在 1MHz 到 8MHz 之间优先保证信号完整性而不是一上来就追求最高速度。稳定性比峰值速度重要得多。3.2 MR25H40CDF 命令集与读写封装MR25H40CDF 的命令集和常规 SPI Flash 类似但少了擦除命令多了一些 MRAM 特有的状态控制。下面列出最常用的命令命令操作码说明WREN0x06写使能写命令前必须执行WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03普通读地址为 24 位FSTRD0x0B快速读带 dummy 字节WRITE0x02写数据地址为 24 位写使能这一步特别重要MRAM 虽然不需要擦除但芯片内部的写使能锁存器必须被置位后WRITE 命令才生效。发送 WREN 后最好把 CS# 拉高再拉低然后再发写命令。中间这两个片选周期在时序上有讲究具体就是写使能命令发出后CS# 要有一个高电平的过程锁存器才会真正置位。下面是我常用的读写封装函数直接处理字节级操作参数就是 32 位地址和 8 位数据。MR25H40CDF 容量只有 512KB地址最多到 0x7FFFF所以 32 位地址里高字节实际不用但保留 32 位接口方便以后换更大容量 MRAM。uint8_t mram_spi_transfer(uint8_t data) { SPI1BUF data; while (!SPI1STATbits.SPIRBF); return SPI1BUF; } void mram_write_enable(void) { SPI_CS_LOW(); mram_spi_transfer(0x06); SPI_CS_HIGH(); } uint8_t mram_read_status(void) { uint8_t status; SPI_CS_LOW(); mram_spi_transfer(0x05); status mram_spi_transfer(0x00); SPI_CS_HIGH(); return status; } void mram_write_byte(uint32_t addr, uint8_t data) { mram_write_enable(); // 发送 WRITE 命令和 24 位地址 SPI_CS_LOW(); mram_spi_transfer(0x02); mram_spi_transfer((addr 16) 0xFF); mram_spi_transfer((addr 8) 0xFF); mram_spi_transfer(addr 0xFF); mram_spi_transfer(data); SPI_CS_HIGH(); } uint8_t mram_read_byte(uint32_t addr) { uint8_t data; SPI_CS_LOW(); mram_spi_transfer(0x03); mram_spi_transfer((addr 16) 0xFF); mram_spi_transfer((addr 8) 0xFF); mram_spi_transfer(addr 0xFF); data mram_spi_transfer(0x00); SPI_CS_HIGH(); return data; }连续读写也很常用。MRAM 不像 Flash 有页缓冲限制只要地址不跨过芯片末尾WRITE 命令后面可以连续送入多个字节READ 命令后面也可以连续读任意长度。这对记录一长串日志非常方便。连续读写的实现就是在上面的字节函数基础上把单个数据收发循环执行注意地址自动加一即可。3.3 状态寄存器与写保护逻辑MR25H40CDF 的状态寄存器里有一个写使能锁存位 WEL这个位在 WREN 命令后会被置 1在 WRDI 命令后会被清 0。工程上不强制要求查询 WEL 位因为芯片内部没有像 Flash 那样的擦写忙状态只要命令时序正确数据就是立即写入的。但为了调试方便在写操作前读一下状态寄存器确认 WEL 置位能更容易定位时序问题。WP# 引脚是硬件写保护输入当 WP# 为低时状态寄存器的某些保护位会被锁定WREN 命令不再有效。调试时如果发现 WEL 一直置不上去先检查 WP# 电平这是最容易忽视的地方。HOLD# 引脚的作用则是在传输过程中暂停通信它不影响已写入的数据只影响总线的当前状态。在没有多主机仲裁需求的系统里直接上拉保持高电平即可省心。4. 数据管理策略与可靠性设计4.1 地址映射与存储布局4Mbit 的 MR25H40CDF 对应 512KB 地址空间我习惯在最开始规划任务时就划分好区域。一个好的分区方案能在后续维护时省掉大量麻烦比如参数升级、日志读取、固件升级之间不会互相干扰。下面是我在这个项目里用到的分区表可以直接参考按需修改起始地址结束地址大小用途0x0000000x0000FF256B产品识别信息区0x0001000x0007FF1.75KB标定参数区主区0x0008000x000FFF2KB标定参数区备份区0x0010000x07FFFF508KB运行日志与数据记录区为什么参数区要单独留备份区因为标定参数一旦损坏设备就要重新标定这在生产线上是很大的损失。有了备份区每次写入参数时先写备份再写主区启动时校验主区 CRC如果失败就从备份区恢复等于给关键参数上了双保险。数据记录区虽然很大但在频繁写入时也建议划分为多个子块循环使用比如分成 64 个 8KB 子块每块写满后写下一块配合时间戳判定最新块。4.2 写入序列与掉电保护MRAM 的写入序列比起 Flash 要简单太多。Flash 需要先擦除整个扇区期间掉电可能会把整个扇区变成垃圾数据MRAM 则是直接按地址改数据掉电只会影响正在写入的那一笔而且没有擦除损坏累积问题。项目的完整写入序列是关闭全局中断防止写入过程中被中断服务函数插队。发送 WREN 写使能。拉低 CS#发送 WRITE 命令、24 位地址和数据。拉高 CS#结束本次写入。恢复全局中断。回读数据与写入数据对比确认无误。但“掉电保护”这个词不是靠 MRAM 本身就能解决的。掉电瞬间如果主控供电已经跌出正常工作范围SPI 时序可能乱掉这时就算 MRAM 能保存数据写入的也可能是垃圾数据。所以工业设计里必须配合掉电检测机制。PIC24F16KA102 内部有欠压复位模块可以设置 BOR 阈值当电压低于阈值时强制复位阻止主控继续执行写操作。更稳妥的方案是外置一个电压监控芯片一旦检测到掉电就立刻给主控一个中断让主控在电容余电时间内把关键数据写入 MRAM然后优雅停机。4.3 校验、回读与冗余方案数据写入后回读校验是我在这个项目里坚持死磕的一条规矩不讨论成本因为工业现场一次返修可能比一百片 MRAM 都贵。回读有全量回读和抽样回读两种参数区写入量小全量回读没问题数据记录区写入量大可以每写 16 字节回读 1 个字节抽样。抽样毕竟是概率问题如果追求更高可靠性我建议增加一个 CRC16 校验把整个记录的 CRC 值放在记录末尾。CRC16 实现代码量不大这里贴一个标准的多项式 0x8005 查表实现思路。启动时先读记录区按帧头找到最新记录再对数据区域计算 CRC和记录尾部的 CRC 比较不一致就认为这条记录损坏跳到上一个有效记录。配合双备份区这个方案在实际项目中已经扛住了多次人为断电测试。uint16_t mram_crc16(const uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; uint16_t i; for (i 0; i len; i) { crc ^ (uint16_t)data[i] 8; for (uint8_t j 0; j 8; j) { if (crc 0x8000) { crc (crc 1) ^ 0x8005; } else { crc 1; } } } return crc; }磨损均衡在 MRAM 上不是必须的因为 10 的 12 次方写寿命已经超出绝大部分产品生命周期。但如果做的是高频率数据记录比如每秒写几百条日志我还是建议把记录区做成环形缓冲让写入压力平均分布在多个子块上。这不仅是保护内存芯片也是为了方便数据管理时间上旧数据被新数据覆盖的规则很清楚。5. 现场问题与排查实录5.1 常见问题速查表嵌入式项目调存储出错的大头其实就那么几类。我把这几年遇到过的现象、可能原因和检查顺序整理成一张速查表现场调试时对着表查基本半小时内能定位现象可能原因检查顺序写数据后回读全是 0xFFWP# 拉低、SPI Mode 不对、写使能没生效先测 WP# 电平再看 SPI 波形最后查时序数据部分正确部分错误地址线接错、CS# 毛刺、SCK 与数据线串扰查硬件原理图用示波器看 CS# 边沿偶发多出或丢失 1 字节HOLD# 悬空、干扰导致片选抖动示波器抓 HOLD# 引脚电平确认上拉掉电后数据有一笔损坏掉电瞬间电压过低导致时序错乱确认 BOR 配置增加掉电检测逻辑连续读写越界导致相邻数据被覆盖地址没有做边界判断在写函数里增加地址上限判断5.2 问题一回读全是 0xFF数据根本没写进去这个现象在联调第一天最容易出现。排查时先不要怀疑芯片是坏的MRAM 产品不良率很低。第一件事是把 CS# 引脚信号用示波器抓出来确认主控把片选拉低的时间和 SPI 时钟是否对齐。我遇见最多的情况是代码里 WREN 和 WRITE 之间 CS# 没有拉高导致写使能锁存器始终没有置位。这个细节在数据手册里写得很清楚WREN 命令结束后CS# 必须有一个高电平的时间芯片才会把 WEL 置 1。第二件事是检查 SPI 模式。MR25H40CDF 的 Mode 0 和 Mode 3 都支持但如果你主控初始化配错了相位时钟边沿和数据变化时刻错开写进去的数据可能整包偏移一位读出来自然也不对。用逻辑分析仪同时抓 SCK、SI、SO 三根线对照数据手册的时序图一眼就能看出问题在哪。第三件事才是检查 WP#。调试阶段我通常把 WP# 直接上拉等所有功能验证完再考虑是否需要接 GPIO 控制。因为 WP# 一旦拉低不仅仅禁止了写入还会把部分状态寄存器锁存排查起来非常绕。5.3 问题二偶发错位或者多读一个字节偶发错误比固定错误难查因为它可能是硬件干扰也可能是时序余量不足。我在一个项目里遇到过读取日志时多读出一个字节导致后面每条记录都偏移一位。排查过程很折磨后来用示波器长时间抓 CS# 和 SCK发现是 CS# 拉高瞬间SCK 线上有一个负向毛刺把时钟状态搞乱了。原因找到后解决也简单CS# 拉高前SCK 必须已经处于空闲状态并保持至少半个时钟周期。SPI 时序上这叫片选保持时间工程师经常忽略。解决办法是在代码里拉高 CS# 之前加一个小的延时比如几个 NOP保证时钟线完全静止。另外如果主控 GPIO 驱动能力过强片选信号上升沿也可能耦合到 SCK 上可以在 CS# 引脚串联一个 22 欧姆电阻或者适当降低 SPI 时钟频率。5.4 问题三掉电后偶发数据损坏这是工业项目最不愿意看到的现象。MRAM 本身不会因为掉电丢数据但如果掉电瞬间主控处于“半死不死”的状态SPI 时钟可能只发送了一半正在写入的数据自然就不完整。解决思路有两个层面。第一层是硬件层面。PIC24F16KA102 的 BOR 模块一定要配置好电压阈值设置得比 MRAM 最低工作电压略高一些这样电压不足时主控直接复位而不是带着扭曲的时序继续跑。第二层是软件层面。关键记录不要只写一个区域采用主区和备份区交替写入并且每条记录都带 CRC。上电初始化时先扫描两个区域选择 CRC 正确且序号最新的一条生效。这套方案实测下来在人为连续断电 50 次之后数据都能恢复到断电前的最后一条完整记录。我个人在实际操作中的体会是MR25H40CDF 和 PIC24F16KA102 这套组合真正舒服的点不是某一个器件参数多惊艳而是把“复杂的事变简单了”。MRAM 让写操作回归到“读和写”本身不用去管理页擦除、坏块、写放大这些破事PIC24F 的 SPI 寄存器又足够透明出了问题可以直接在逻辑分析仪上看到每一步。如果你正在做类似的工业存储项目记住一句话先把片选时序和上电复位搞干净再谈性能优化。这样即使现场出了问题也能一眼定位不用瞎猜。
返回列表