ARTICLE DETAIL

资讯详情

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

工业数据采集掉电保存方案:MRAM芯片与PIC32微控制器实战

工业数据采集掉电保存方案:MRAM芯片与PIC32微控制器实战 MR25H40CDF 和 PIC32MX795F512L一个来自 Everspin一个来自 Microchip把它们放在一起做非易失存储是我在一个工业数据采集项目里验证过的组合。当时要做的是工业现场环境监控记录仪需要把温度、振动、压力这些过程数据高频写入同时保证设备突然断电时不丢最后一段现场信息。一开始的方案用 SPI EEPROM测下来寿命和写入速度都顶不住后来换成 MR25H40CDF 这颗 4Mbit 的 SPI MRAM主控用 PIC32MX795F512L整套系统才算真正稳定下来。这篇文章不准备念数据手册而是讲清楚几个实操层面的问题为什么工业存储要选 MRAM而不是 EEPROM/FlashMR25H40CDF 和 PIC32MX795F512L 之间怎么接线、怎么初始化 SPI、怎么写驱动以及掉电保存和双备份这类数据完整性设计是怎么落地的。适合谁看做仪器仪表、工控板卡、车载记录设备的嵌入式工程师还有想在 MCU 项目里引入 MRAM 但还没动手的人应该都能从这里拿到可直接照抄的方案。1. 选型核算为什么工业现场要 MRAM 而不是 EEPROM/Flash1.1 一次掉电事件暴露的问题最开始用的是一颗 256Kbit 的 SPI EEPROM型号不说了实际测试中遇到的第一个大问题是写入速度。工艺数据每 500ms 要保存一次一次写一条 100 字节左右的记录按页写还好但 EEPROM 内部编程时间实在磨人写一页要等 5ms 左右某些批次甚至能到 7ms。如果赶上数据缓冲满要把整块 4KB 写进 EEPROM一次要几百毫秒主循环直接被拖死。第二个问题是寿命。EEPROM 写入次数上限一般是 100 万次看起来挺多但算账就发现根本不够用。500ms 写一次一天就是 172800 次100 万次寿命只够跑 5.8 天。就算改成 10s 写一次也不过 58 天。工业设备要求连续运行几年这种方案从寿命上就是死路一条。后来有人建议换 SPI NOR Flash容量大、价格也便宜但 NOR Flash 有个更头疼的点写入前必须先擦除而且擦除以扇区为单位一个 4KB 扇区擦除一次要几十毫秒。频繁小数据写入要做擦写均衡、要维护坏块表、要处理掉电擦除中断等一堆问题对一个小型工业记录仪来说这套复杂度有点过度。MRAM 的思路完全不一样。它用磁存储单元不需要擦除、不需要页编程每个字节都能独立直接写写入次数标称 10^14 次级别可以把“擦写均衡”“搬运页”“计划擦除”这些 Flash 里的麻烦事全部扔掉。1.2 三种非易失存储的对比把三种方案放在一张表里对比选型理由会清楚很多。特性传统 SPI EEPROMSPI NOR FlashMR25H40CDF (SPI MRAM)容量256Kbit 常见64Mbit 常见4Mbit写入单位页/字节页且需先擦除扇区字节级直接写页面编程/擦除耗时页写约 3~5ms擦除 4KB 约 40~100ms无需擦除典型写入次数10^5 ~ 10^6 次10^4 ~ 10^5 次10^14 次复杂驱动逻辑简单但要管理等页复杂要替换逻辑坏块管理简单按地址直读直写掉电保持几十年典型 20 年典型 20 年以上从上表能看出来MRAM 在读写模式上最接近 SRAM驱动最简单的同时寿命又最长。代价是单位容量价格确实比 Flash 贵但在工业设备里可靠性优先于成本尤其数据记录功能是整个设备的“黑匣子”这部分不能省。1.3 从需求反推容量和方案项目需求是保存三类数据设备参数、运行日志、报警记录。算下来单条日志 100 字节、每天大约 10 万条事件保留 7 天的量大概需要 70MB这个量级 MRAM 装不下所以设计上做了分层日志量大的部分用大容量 SD 卡MRAM 只保存最关键的那一部分——设备配置参数、最近的报警状态、断电瞬间的最后一段过程数据。MR25H40CDF 的 4Mbit 等于 512KB用来放设备参数16KB、报警历史128KB和掉电紧急保存区16KB完全够用剩余空间还能做双备份。这也给后来做数据完整性留下了充足余量。选型时的一个关键判断是不是所有数据都需要 MRAM把最有价值的高频小数据、断电关键数据放进 MRAM方案性价比最高。2. MR25H40CDF 硬件接入引脚、供电与信号完整性2.1 芯片最小电气连接MR25H40CDF 是 Everspin 的 SPI 接口 MRAM最小系统相当简单本质就是一个四线 SPI 从设备。芯片上除了 SPI 的 SCK、SI、SO、CS还有 HOLD 和 WP 两个控制脚。这些引脚的实际连接经验如下CS片选接主控任意 GPIO。工业环境干扰多CS 建议加上拉 10kΩ 到 VCC避免悬空时进入不定状态。SCK时钟接 PIC32 的 SPI SCK 输出脚。SI串行输入接主控的 SDO/SDOx 输出脚。SO串行输出接主控的 SDI/SDIx 输入脚。HOLD这是暂停通讯的控制脚低电平时芯片忽略 SCK 和 CS 上的状态。整个系统里没有需要用它的场景直接 10kΩ 上拉到 VCC 最省心。WP写保护输入低电平时禁止写状态寄存器。一般不需要动态控制同样 10kΩ 上拉。DFN 封装没有封装体大引脚手工焊接时尤其要注意 WP 和 HOLD 这两个脚很多板子画完图没检查结果这两个脚悬空设备运行一段时间后偶发写入失败排查起来非常折磨人。2.2 PIC32MX795F512L 侧引脚与模块分配PIC32MX795F512L 是 100 引脚的 MIPS32 M4K 内核 MCU主频 80MHz512KB Flash、128KB RAM片上带上 USB、CAN、以太网外设资源很丰富。这颗片子上的 SPI 模块有多个PIC32MX 的串口引脚可以通过 PPS外设引脚选择映射到不同引脚上布线自由度很高。我实际在项目里把 MR25H40CDF 挂在 SPI1 上SD 卡挂在 SPI2 上两块存储互不干扰。PPS 映射具体怎么填寄存器不需要背表直接用 Microchip 的 MCC 工具生成即可。要提醒的是如果 SPI 没有走到固定功能脚而是走 RPn 引脚需要确认对应 RPn 引脚的 TRIS 方向配置正确SDI 必须置输入、SDO/SCK 置输出。很多初学 PIC32 的人在这里翻车方向配反调了半天 SPI 始终读不到数据。MRAM 的 CS 我用了一个普通 GPIO不使用 SPI 模块自带的 SS 功能这样方便软件控制片选时序。工业设备里 CS 时序需要比较灵活的控制比如掉电保存时可以先拉高 CS 中断当前操作再重新发起关键写入用普通 GPIO 最直接。2.3 布局走线与上电时序处理SPI 走线在 PCB 层面要做几件小事都属于成本很低但能省去后期大量调试麻烦的做法。第一SCK、SI、SO 三条信号线在源端各串 33Ω 电阻。这个电阻作用是抑制振铃。工业环境长走线加上连接器SPI 信号很容易过冲串一个小电阻能把波形拉干净。实际示波器上看不串电阻时 3.3V 信号过冲能到 4V 以上串了电阻后基本贴着 3.3V 走。第二去耦电容靠近 VCC 引脚放一个 0.1μF再加上一颗 10μF 钽电容。MRAM 写入电流不大但这个组合对供电稳定性有帮助。上电时序上需要等 3.3V 稳定后再操作 MRAMPIC32 复位后延时几十毫秒再初始化 SPI基本就避开了电源爬坡阶段。第三MRAM 与 PIC32 之间如果通过连接器跨板连接尽量把 SPI 信号和地线绑在一起走。地回路太长是工业现场通讯异常的隐形杀手这个问题不解决后面软件写得再好也是白搭。3. PIC32 SPI 驱动与读写实现3.1 SPI 模块初始化的配置要点PIC32MX795F512L 的 SPI 初始化并不复杂关键是把模式、位宽、波特率设对。MR25H40CDF 的数据手册明确支持 SPI Mode 0 和 Mode 3我在项目里选了 Mode 3因为同一块板上另一颗 SPI Flash 也用 Mode 3共用一套底层初始化逻辑驱动代码少一半。波特率配置要用到 PIC32 SPI 的公式SCK 频率等于外设时钟除以两倍的波特率寄存器值加一。项目里外设时钟是 40MHz我把 SPI1BRG 设成 4算下来 SCK 频率是 4MHz。这个速度对 MR25H40CDF 来说非常保守芯片标称支持到几十 MHz4MHz 只是为了避免长线干扰。初始化代码示意如下核心寄存器操作保留实际使用中可以放到 MCC 生成的外设初始化文件里void SPI1_Init(void) { // 关闭 SPI 模块进行配置 SPI1CON 0; // 波特率F_SCK F_PB / (2 * (SPI1BRG 1)) // F_PB 40MHz, SPI1BRG 4 - F_SCK 4MHz SPI1BRG 4; // 主模式、8 位数据、Mode 3 SPI1CONbits.MSTEN 1; SPI1CONbits.MODE16 0; // CKP 1, CKE 按 Mode 3 配置 SPI1CONbits.CKP 1; SPI1CONbits.CKE 0; // 使能 SPI 模块 SPI1CONbits.ON 1; }注意一点PIC32MX 的 SPI 是同步收发结构主模式发送一个字节的同时会接收一个字节。写驱动时不能像某些 MCU 那样只关心发送完成还要把接收缓冲读掉否则下一次传输会触发接收溢出标志导致后续数据错乱。3.2 SPI 字节交换的底层函数PIC32MX 的 SPI 收发通过 SPI1BUF 这一个寄存器完成写它表示发送读它表示取走刚收到的字节。底层交换函数写成这样static uint8_t spi_xfer(uint8_t byte) { SPI1BUF byte; while (!(SPI1STAT 0x01)) // SPIRBF 置位说明收到一个字节 { ; } return SPI1BUF; }这段代码是所有 MRAM 命令的基础。发送读命令时后续要连续发假字节来产生时钟才能把数据从 SO 脚移位出来。不少人第一次接触 PIC32 SPI 会把“发一个字节等一个回包”的逻辑弄混其实只要记住spi_xfer()发什么不重要、回什么才重要就顺了。3.3 MRAM 命令封装与读写流程MR25H40CDF 的 SPI 指令集很直观主要用到六条写使能0x06、写失能0x04、写数据0x02、读数据0x03、读状态寄存器0x05、写状态寄存器0x01。写入操作有个强制性前提每次写数据前必须先发写使能否则写命令直接被忽略。标准流程是这样拉低 CS发 0x06WREN拉高 CS再次拉低 CS发 0x02WRITE发 3 字节地址高位在前连续发待写入数据拉高 CS。这里有个容易踩的坑写完数据后芯片内部 WIP 位会短暂置位虽然 MRAM 不像 EEPROM 有几十毫秒编程时间但连续发起下一条写命令前还是建议查一下状态寄存器确认上次写操作完成。代码实现如下#define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRITE 0x02 #define MRAM_CMD_READ 0x03 #define MRAM_CMD_RDSR 0x05 static void mram_cs_low(void) { LATBbits.LATB10 0; } static void mram_cs_high(void) { LATBbits.LATB10 1; } static void mram_write_enable(void) { mram_cs_low(); spi_xfer(MRAM_CMD_WREN); mram_cs_high(); } static void mram_wait_busy(void) { mram_cs_low(); spi_xfer(MRAM_CMD_RDSR); while (spi_xfer(0x00) 0x01) // bit0 为 WIP { ; } mram_cs_high(); } void mram_write_bytes(uint32_t addr, uint8_t *buf, uint32_t len) { mram_write_enable(); mram_cs_low(); spi_xfer(MRAM_CMD_WRITE); spi_xfer((addr 16) 0xFF); spi_xfer((addr 8) 0xFF); spi_xfer(addr 0xFF); for (uint32_t i 0; i len; i) { spi_xfer(buf[i]); } mram_cs_high(); mram_wait_busy(); }读取数据的流程省掉了写使能直接拉低 CS、发 0x03、发 3 字节地址然后连续spi_xfer(0x00)把数据读出来void mram_read_bytes(uint32_t addr, uint8_t *buf, uint32_t len) { mram_cs_low(); spi_xfer(MRAM_CMD_READ); spi_xfer((addr 16) 0xFF); spi_xfer((addr 8) 0xFF); spi_xfer(addr 0xFF); for (uint32_t i 0; i len; i) { buf[i] spi_xfer(0x00); } mram_cs_high(); }这段驱动直接拿到实际项目里就能用。MRAM 没有页边界限制连续写几百字节不需要像 Flash 那样关心是否跨页地址可以一直递增这是设计上最省心的地方。3.4 关于 DMA 和高速写入的延伸PIC32MX795F512L 的 SPI 支持 DMA如果一次要批量写几百字节可以配置 DMA 把内存数据直接搬运到 SPI1BUFCPU 只在传输完成后处理一个中断。工业记录仪掉电保存时数据量不大普通中断写就够了但如果是高频采集系统DMA 模式能显著降低 CPU 占用。我自己做批量写入时给 DMA 留了接口分两档关键的小块数据几十字节走查询方式逻辑简单可靠批量日志走 DMA通过定时触发搬运。这里一个小小的建议MRAM 虽然写入快但 MCU 和 MRAM 之间的时钟毕竟是串行链路批量传输要估算好时间别在中断服务里做太多事。4. 掉电保存与数据完整性设计4.1 存储区规划与启动校验512KB 的 MRAM 如果不做规划很容易写成一锅粥。我在项目里把存储区划分为下面几个部分地址范围大小用途0x00000 ~ 0x03FFF16KB设备参数区双备份0x04000 ~ 0x07FFF16KB设备参数镜像区0x08000 ~ 0x1FFFF96KB报警历史循环记录0x20000 ~ 0x2FFFF64KB掉电紧急保存区0x30000 ~ 0x3FFFF64KB运行日志临时缓冲0x7FFE0 ~ 0x7FFFF32B存储区状态标志启动后先读状态标志区这个区域里存放一个预先定义的魔数和存储格式版本号。魔数匹配了才认为自己读到的配置有效否则说明可能是首次上电或者上次运行中发生了严重异常直接回退默认参数。这个设计和嵌入式领域经典的启动 loader 校验思路完全一致成本低、收益大。4.2 掉电检测与紧急保存流程设备掉电时电源会从正常值跌落到 0。问题是 MRAM 写入也需要时间虽然很快但也不能在主控已经掉到欠压点才开始保存。我的做法是加一个掉电检测电路把输入电源经过电阻分压后送进 PIC32 的 INT 引脚再配合一个大一点的储能电容。当输入电压低于某个阈值时INT 引脚触发中断。此时 PIC32 依靠供电电容上储存的电量还能正常跑几毫秒到十几毫秒而这段窗口足够完成一次紧急保存。比如在 4MHz SPI 下往 MRAM 写一条 64 字节的掉电记录算上命令头大约 68 字节传输时间大约 136μs加一点软件开销也就几百微秒完全来得及。掉电中断服务函数里要做的动作很明确关掉其他中断把关键全局状态结构体拷进一个静态区然后调用 MRAM 写入函数把这个结构体写到紧急保存区。为了尽可能可靠写完后再读回校验一致了才拉高一个掉电完成标志volatile uint32_t g_globalState[16]; void __ISR(_EXTERNAL_0_VECTOR, IPL2SOFT) Int0_Handler(void) { // 清除中断标志 IFS0CLR _IFS0_INT0IF_MASK; // 临时状态拷贝到本地变量 uint8_t tmp[64]; memcpy(tmp, (uint8_t *)g_globalState, sizeof(tmp)); // 写入紧急保存区并回读校验 mram_write_bytes(SAVE_AREA_ADDR, tmp, sizeof(tmp)); mram_read_bytes(SAVE_AREA_ADDR, (uint8_t *)g_globalState, sizeof(tmp)); // 设置保存完成标志 saveDoneFlag 1; }这里有个非常关键的经验中断处理中不能再依赖系统滴答、延时函数或者任何需要调度机制的 API所有操作必须是顺序执行、不阻塞的。所以紧急保存用的就是最简单直接的状态机式调用不用 RTOS不用队列。4.3 双备份与 CRC 校验掉电那一下不可控因素很多就算 MRAM 本身是非易失的也保不齐写了一半就去电了。为了把这种“半个数据”的风险也兜住重要的参数区要做双份备份。参数写入采用 A/B 区交替策略第一次写 A 区第二次写 B 区第三次再写 A 区。每个区头部都放一个 CRC 校验和、数据长度和递增序列号。上电读取时优先读 A 区校验通过就采用A 区校验失败就试 B 区两个区都失败才回退默认参数。CRC 的计算用软件实现数据量不大用 CRC16 就够了。这个双备份策略带来的额外写入量不大因为参数区的写入频率本身很低通常只有在设备调试和参数修改时才会触发。报警历史区则用循环覆盖的方式写成环形缓冲满了就从头覆盖最旧的一条。报警记录不要求每一条都永久保存但要求最后写入的若干条能完整读取。环形缓冲区的头尾指针也放到 MRAM 里每次写记录时先更新指针再写数据能保证即使掉电下次启动也能根据指针定位到最后一条可用记录。5. 实测数据与问题排查5.1 读写性能实测记录在 PIC32MX795F512L 跑 4MHz SPI 时实测数据如下操作数据量耗时读 64 字节64B约 140μs写 64 字节含回读校验64B约 300μs写 100 字节掉电保存场景100B约 220μs启动时读取全部参数区16KB约 30ms这个耗时完全在项目预算内。相比 EEPROM 每次页写至少 5ms 的等待MRAM 写 64 字节只要一两百微秒手工把 SPI 时钟提到 10MHz 后还能再快一倍多。不过 4MHz 够用我在量产固件中保持了保守配置。5.2 SPI 数据错位和进程卡死的排查过程调试中遇到过两次比较典型的故障。第一次是 MRAM 回读大量数据时前几个字节全是 0xFF。检查波形发现 CS 拉低后第一字节时钟就来了但此时 SI 线上可能还没稳定芯片把第一个字节的移位时钟吃了半个。解决方法是 CS 拉低后加几个微秒延时再发起首个命令字节。这在数据手册里叫 CS 建立时间不能省。第二次是连续写较长数据时SPI 总线上的设备偶发卡死。示波器看波形SCK 末端有一串振铃超过逻辑阈值导致多时钟信号被打进芯片。后来把 SPI 时钟从 8MHz 降到 4MHz并在 SCK 源端串了 33Ω 电阻问题就不再出现。这里想说的是SPI 速度不是越高越好稳定才是工业设备的第一诉求。5.3 HOLD 与 WP 悬空的坑这个坑最具隐蔽性。有一版试制板因为 DFN 封装体积小HOLD 引脚没引出上拉结果设备运行一段时间后MRAM 偶发“卡住”不动RDSR 读状态也读不到。最终定位到是 HOLD 引脚受到邻近信号线干扰瞬间拉低触发了 hold 功能芯片把 SCK 和 CS 都忽略了。修复很简单把 HOLD 和 WP 都加上 10kΩ 上拉电阻后续再没复现。这算是 MRAM 应用里最经典的“芯片本身没问题、封装引脚坑人”的案例画 PCB 时多留意一眼就能避免。5.4 排查技巧速查现象可能原因排查方法首字节读出 0xFFCS 建立时间不足CS 拉低后延时几个 μs数据偶发错位SPI 信号振铃/线太长降速串 33Ω 电阻写使能后写命令无效WREN 与 WRITE 之间 CS 没拉高严格按指令时序执行芯片“死掉”无响应HOLD 悬空受干扰HOLD/WP 上拉到 VCC上电偶发读校验失败3.3V 未稳定就操作上电延时数十 ms 再初始化掉电保存数据不完整掉电检测阈值太晚加电容储能阈值提前触发这些排查经验在实际项目中帮了大忙也说明了一个道理MRAM 本身很可靠但外围时序和封装细节一样会影响它的表现。我在实际项目里还留了一手把 MRAM 的读写驱动单独抽象成一个模块接口只有读一段、写一段、读状态三个函数。这样后续如果要迁移到其他 MCU 平台只需要替换底层spi_xfer和引脚操作上层的数据机构、双备份逻辑、掉电保存策略全部可以复用。工业存储这块一旦把数据完整性机制跑通后续就是稳定的复制和经验积累这也是 MRAM 相比普通存储方案给我最大的感触。
返回列表