ARTICLE DETAIL

资讯详情

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

MRAM实战:MR25H40CDF+TM4C129实现掉电不丢失的工业存储

MRAM实战:MR25H40CDF+TM4C129实现掉电不丢失的工业存储 很多做工业控制器的朋友应该都遇到过这种尴尬参数明明写进存储芯片了设备一断电重启全部变成乱码或者因为频繁记录运行数据Flash很快就被擦写寿命拖垮。我之前在做一个工业数据采集设备时也卡在这个问题上。后来换成 Everspin MR25H40CDF 这颗 SPI 接口的 MRAM配合 TI TM4C129XNCZAD 主控才真正把“存储和读取数据”这件事做得省心又稳定。这篇文章就从头到尾聊聊这个组合的选型思路、底层原理、驱动实现和我在实际调试中踩过的坑。如果你想在嵌入式项目里做掉电不丢失的数据记录又受够了 Flash 和 EEPROM 的各种限制这篇应该能给你一份可以直接抄作业的参考。1. 为什么是 MR25H40CDF TM4C129XNCZAD工业存储的痒点与破局1.1 传统存储方案的三大痛点先说说我原来的方案。设备需要保存传感器校准系数、运行累计量、故障事件日志这些数据更新频率不低尤其是故障记录可能每分钟都要写一笔。当时用的是串行 NOR Flash容量够便宜但用起来真的很别扭。第一Flash 写入前必须擦除扇区而扇区擦除动辄几十毫秒甚至更久。你要是刚写完一包数据下一包又来程序就得卡在擦除流程里高频率数据记录根本应付不过来。第二Flash 擦写寿命通常在十万次级听起来不少但如果你每秒钟写一次关键参数一天就是八万六千次一块新芯片连两天都撑不住。第三掉电保护难做。Flash 写入采用“程序”脉冲电压跌落时容易导致扇区数据损坏哪怕你加了掉电检测也还是提心吊胆。我也考虑过 EEPROM比如 24C512容量才 64KB而且用 I2C 接口速度撑死几百 KHz写一个 4KB 的配置文件要一秒多这在现场简直不可接受。正因为如此我开始认真看 MRAM。W25Q128 和 AT24C512 各自的问题没办法克服那就换存储介质。MR25H40CDF 正好满足1Mbit 容量、SPI 接口、按字节写、几乎无限写寿命。它简直是为这种场景量身定做的。1.2 MR25H40CDF 何许芯片MR25H40CDF 是 Everspin 旗下的 SPI MRAM 芯片容量 1Mbit也就是 128KB按字节编址。它最大的特点是“非易失 随机访问”既能像 SRAM 一样按字节读写速度跟普通 SPI Flash 读取差不多又能在断电后长期保存数据保存时间标称能到 20 年。耐写方面就不用担心了。MRAM 的写机制不依赖电荷积累也就没有“磨损”的概念标称写循环达到近乎无限次你把它当 128KB 的 RAM 用都行。工作温度范围也宽工业级版本通常支持 -40℃ 到 105℃ 甚至 125℃放在户外机柜或高温车间里没问题。实际使用中我这边把校准参数和事件记录都放进去访问速度明显快于 I2C EEPROM而且不用担心反复写坏。可能有人觉得 128KB 不够大但存我的需求绰绰有余。如果以后数据量膨胀同系列的 2Mbit、4Mbit 型号引脚基本相同驱动代码改动很小就能升级。1.3 TM4C129XNCZAD 为什么可以做这套系统的大脑TM4C129XNCZAD 是 TI Tiva C 系列的成员核心是 ARM Cortex-M4F最高主频 120MHz带浮点单元。这颗芯片最吸引我的是外设组合很“干活”集成双 SPITI 叫 SSI、以太网 MACPHY、USB、大量标准定时器而且支持 -40℃ 到 105℃ 的工业级版本。对嵌入式现场设备来说既有本地存储又能通过网络远程管理整个架构一下就通了。再说软件支持。TI 提供的 TivaWare 固件库已经把 SSI、UART、以太网这些外设封装得很干净初始化代码几乎不需要自己碰寄存器。我只需要在 SSI 配置里把模式设为 SPI Master选定管脚就能开始和 MRAM 通信。开发效率比对着寄存器手册一行行啃快很多。还有一个小优势TM4C129 的 GPIO 灌电流和驱动能力都不错直接带动 MRAM 的 CS、WP、HOLD 引脚毫无压力不需要额外的电平转换芯片。整板 3.3V 供电电源系统也简单。1.4 整体方案连接方式硬件连线并不复杂我把 MR25H40CDF 挂在 TM4C129 的 SSI0 上管脚对应关系是SCKPK0 或 PA2 等 SSI0Clk 引脚MOSIPK1 或 PA3 等 SSI0Tx 引脚MISOPK2 或 PA4 等 SSI0Rx 引脚CS随便挑一个 GPIO 引脚软件控制拉低拉高WP写保护直接接高电平允许写操作HOLD直接接高电平避免总线上的意外电平把主控“冻住”。这里我特别强调一下 HOLD 引脚。如果 HOLD 被拉低芯片会忽略时钟和数据线保持当前输出状态不动。很多人第一次用 MRAM 没注意这引脚把它悬空结果现场一有干扰通信就莫名其妙卡住。我的做法是 PCB 上直接把它连到 3.3V并且让 MCU 的那个 GPIO 在初始化时也输出高电平双保险。2. 存储的底层逻辑与 SPI 细节2.1 MRAM 存储技术到底神奇在哪传统的 Flash 用浮栅晶体管存电荷写 0 和写 1 需要不同电压去“搬”电子写之前还得先擦成 1所以注定有损耗和延迟。MRAM 的原理是磁性隧道结它有两层磁性材料中间隔着一层极薄的绝缘层。当两层磁化方向一致时电阻小方向相反时电阻大。电阻的两个状态正好对应 0 和 1读数据就是量电阻写数据就是改变其中一层的磁化方向整个过程不需要擦除也没有电荷漏电。打个比方Flash 就像在黑板上写字想改其中一小块得把整块黑板擦干净再重写MRAM 像是用磁力写字板每个格子单独可以用磁笔改不牵连其他格子。这也是它写寿命特别大的根本原因。实际项目里我用 GPIO 模拟时序去验证驱动逻辑时发现地址线、数据线随便翻完全没有等待时间顺手得很。2.2 摸清 MR25H40CDF 的指令集与状态机驱动一个 SPI 接口的存储芯片第一步是查明白它的指令集。MR25H40CDF 的指令不多我在工程里实际用到的基本就是下面这些指令名操作码功能说明WREN0x06设置写使能锁存器WRDI0x04清除写使能锁存器RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03从指定地址读取数据WRITE0x02向指定地址写入数据RUID0x9F读唯一设备 ID其中 WREN 特别关键。MRAM 跟 SRAM 不一样虽然写操作没有擦除延迟但为了安全芯片要求你每次写数据和写状态寄存器之前必须先发一个 WREN 指令把内部锁存器打开。如果漏掉这一步芯片会把 WRITE 指令忽略掉但读操作一切正常。这就造成一种很迷惑的现象代码看起来没问题读出来的全是旧数据。时序上和普通 SPI NOR Flash 几乎一模一样先把 CS 拉低发送操作码再跟地址和时钟最后拉高 CS。所有多字节命令都是 MSB 先发。我习惯把 CS 的拉低和拉高用 GPIO 手动控制而不是用 SSI 硬件的自动片选。这样时序上更可控尤其当芯片挂过长的杜邦线时可以随时加延迟。如果你用 TivaWare 的 SSI 可配置自动片选也能跑但项目里我仍倾向软件 CS 的灵活性。2.3 TM4C129 的 SSI 外设如何组成 SPI在 TivaWare 里SSI0 的初始化代码大致是这样的#include tm4c129x.h #include inc/hw_memmap.h #include driverlib/ssi.h #include driverlib/sysctl.h #include driverlib/gpio.h void SSI0_Init(void) { SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI0)){} SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOK); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOK)){} GPIOPinConfigure(GPIO_PK0_SSI0CLK); GPIOPinConfigure(GPIO_PK1_SSI0TX); GPIOPinConfigure(GPIO_PK2_SSI0RX); GPIOPinTypeSSI(GPIO_PORTK_BASE, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2); SSIConfigure(SSI0_BASE, SSI_FRF_MOTO_MODE_0, SSI_CLOCK_MASTER, 1000000); // 1MHz 保守起步 SSIEnable(SSI0_BASE); }这里 SSI_FRF_MOTO_MODE_0 对应 SPI 模式 0CPOL0CPHA0。MR25H40CDF 还支持模式 3也就是 CPOL1CPHA1只要主从两边对得上就行。我建议新板子先用 1MHz 跑通再慢慢往上提时钟不要一上来就冲 40MHz。工业设备很多线材和接插件质量参差不齐高速容易把问题放大。还有一点SSI 的时钟是基于系统时钟分频得到的TM4C129 系统时钟可以是 120MHz但 SSI 外设并非在所有分频下都能到最高速度。我实际用 20MHz 跑都很稳定再高没有特别大的收益毕竟 MRAM 的工作电流不大瓶颈往往在杜邦线或 PCB 走线的寄生电容上。2.4 电源与信号完整性设计MR25H40CDF 工作电流很低但毕竟是磁性存储芯片写入时内部会有一个短暂的电流脉冲。为了不让这个脉冲污染模拟电源我在芯片电源引脚边放了 0.1uF 陶瓷电容外加 10uF 钽电容做储能。TM4C129 的 VDDA 和 VDD 也要独立退耦不然 SPI 高速切换时地弹会很大。信号线方面我给 SCK、MOSI、MISO 都串了 22Ω 电阻。这个电阻成本极低却能把边沿变缓减少振铃。CS 信号不太需要串阻但走线要短。HOLD 和 WP 如果接 3.3V最好也各加一个 10kΩ 下拉不对我这里是接高应该加 10kΩ 上拉到 3.3V直接连电源也可以但考虑到有时候飞线调试我习惯用 10kΩ 上拉。如果将来想用软件控制写保护也方便拆电阻。四个 SPI 信号尽量走底层别跟继电器、电机驱动线交叉。3. 撸一个真实的读写驱动3.1 驱动分层接口清晰才好维护写驱动不是为了炫技而是为了以后换芯片、换平台时不至于推倒重来。我分了两个文件mram_hal.c管 SPI 底层字节收发mram_drv.c管状态读取、写使能、数据块读写。接口定义成void MRAM_Init(void); uint8_t MRAM_ReadStatus(void); void MRAM_WriteEnable(void); void MRAM_WriteDisable(void); int32_t MRAM_ReadBytes(uint32_t addr, uint8_t *buf, uint32_t len); int32_t MRAM_WriteBytes(uint32_t addr, const uint8_t *buf, uint32_t len);为什么要把底层收发单独拆开因为 TM4C129 的 SSI 有硬件 FIFO不同芯片可能有 DMA拆开之后上层代码只需要调用MRAM_SpiExchangeByte逻辑就会非常清晰。如果哪天我把主控换成别的 MCU底层函数重写上层存储协议一行都不用动。3.2 手写底层 SPI 收发Tiva 的 SSI 收发都靠 FIFO核心就是一个函数发一个字节的同时收一个字节。我用的是阻塞方式static uint8_t MRAM_SpiExchangeByte(uint8_t byte) { uint32_t rxData; SSIDataPut(SSI0_BASE, byte); while(SSIBusy(SSI0_BASE)) {} SSIDataGet(SSI0_BASE, rxData); return (uint8_t)rxData; }注意SSIBusy不是判断有没有返回数据的它判断发送缓冲区和移位寄存器是否为空。我踩过坑刚开始没等它直接连续SSIDataPut结果把 FIFO 写溢出后面的字节全乱了。加了这个等待之后读写都稳了。如果你做的是批量读 128 字节还可以用 FIFO 优化先写好一部分数据到 FIFO再开始读取能省不少 CPU 等待时间。但刚开始调试老老实实逐字节换最好等逻辑没问题再优化。3.3 实现状态读取与写使能状态寄存器默认值通常只包含写保护位和保留位。读取状态很简单uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_LOW(); MRAM_SpiExchangeByte(0x05); // RDSR status MRAM_SpiExchangeByte(0x00); MRAM_CS_HIGH(); return status; }写使能更直接void MRAM_WriteEnable(void) { MRAM_CS_LOW(); MRAM_SpiExchangeByte(0x06); // WREN MRAM_CS_HIGH(); }我经常看到有人问“写完 WREN 之后要不要等状态寄存器里的非易失位变成 0”。注意MRAM 不吃这一套它不是 Flash 那种需要擦除等待的器件。你只要保证 CS 的时序完整WREN 后立刻发 WRITE 就能写。这在高速连续记录数据时非常有用省去了轮询忙状态的开销。3.4 按地址读写数据块读数据块的核心是发送 READ 指令和 24 位地址。MR25H40CDF 内部按字节编址128KB 只用了低 17 位地址但协议要求还是发送完整 3 字节。代码这样写int32_t MRAM_ReadBytes(uint32_t addr, uint8_t *buf, uint32_t len) { if(addr len 0x20000UL) return -1; // 128KB 边界检查 MRAM_CS_LOW(); MRAM_SpiExchangeByte(0x03); // READ MRAM_SpiExchangeByte((uint8_t)(addr 16)); MRAM_SpiExchangeByte((uint8_t)(addr 8)); MRAM_SpiExchangeByte((uint8_t)(addr)); for(uint32_t i 0; i len; i) { buf[i] MRAM_SpiExchangeByte(0x00); } MRAM_CS_HIGH(); return (int32_t)len; }写数据块类似但前面要多一个写使能int32_t MRAM_WriteBytes(uint32_t addr, const uint8_t *buf, uint32_t len) { if(addr len 0x20000UL) return -1; MRAM_WriteEnable(); MRAM_CS_LOW(); MRAM_SpiExchangeByte(0x02); // WRITE MRAM_SpiExchangeByte((uint8_t)(addr 16)); MRAM_SpiExchangeByte((uint8_t)(addr 8)); MRAM_SpiExchangeByte((uint8_t)(addr)); for(uint32_t i 0; i len; i) { MRAM_SpiExchangeByte(buf[i]); } MRAM_CS_HIGH(); return (int32_t)len; }这里有个细节MRAM 允许在任意字节边界开始写长度也不受页大小限制这比 Flash 幸福多了。但你仍要保证一次 WRITE 操作的地址范围不越界否则地址会回卷到 00000h把开头的配置数据盖掉。我在驱动里加了边界检查返回负值让上层及时发现。3.5 读写数据验证与实测结果跑通驱动后我先做了一次全地址写读校验往 0x00000 到 0x1FFFF 地址写 0xA5、0x5A 交替值再读回来比对一次通过。后来又做随机地址的 4KB 块读写SPI 时钟跑到 20MHz读写 4KB 数据耗时大约 2ms 左右这个速度如果给 24C512 写起码要一秒多。读操作满速时TM4C129 的 CPU 占用率不高因为阻塞等待的时间很短。实际工程里我在一个 250ms 的定时任务里把最新的 64 字节运行数据写入 MRAM同时还在以太网上跑 TCP/IP 服务完全没有互相拖累。4. 实战中的那些坑读写异常与工业可靠性4.1 乱码排查SPI 模式与速率第一版测试板刚焊好程序跑起来之后读 MRAM 的 Device ID 总是读到 0xFF 或者乱码。我先怀疑焊接但用万用表量了各引脚电压都正常。后来发现是 SPI 模式不匹配TivaWare 的SSI_FRF_MOTO_MODE_0对应 CPOL0、CPHA0而那时我用的初始化函数里误抄了SSI_FRF_MOTO_MODE_3。两种模式差别不大但采样沿错了哪怕一点点数据就全乱。排查方法很简单把速率降到 100kHz然后用逻辑分析仪抓 MISO 上的波形和芯片数据手册里的时序图对比。如果 MISO 数据在时钟下降沿变得不稳定多半是采样沿不对。另外杜邦线超过 10cm 时时钟超过 10MHz 也会出现类似现象。工业级产品里我建议把 SPI 时钟控制在 10MHz 到 20MHz并且用完整的地平面。4.2 写不进去多半是写使能的坑以前做 Flash 驱动时写操作前必须把块擦干净而 MRAM 根本不需要擦但它要求 WREN。我一度犯了个低级错误在写函数里调用了MRAM_WriteEnable()但是拉低 CS 之后先把地址发了然后才发 WREN不对WREN 必须在 CS 拉低后第一时间发送并且紧接着拉高 CS再开始写序列。如果你把 WREN 和 WRITE 放在同一个 CS 有效周期里部分芯片也接受但数据手册上明确建议先拉高 CS 结束 WREN 命令再发 WRITE 命令。为了兼容最稳妥的做法我现在的写法是MRAM_WriteEnable()独立完成CS 自然拉高然后再调用写数据函数。另外写保护寄存器 WP 位要检查。如果状态寄存器里的 WPEN 置 1 且 WP 引脚为低芯片就会忽略写操作。我默认通过 WRSR 把状态寄存器设成 0x00也就是开放写。如果你的产品在烧录阶段希望防止意外改写可以在这里开启保护量产前再关闭。4.3 工业现场干扰导致读取数据跳跃设备装到现场之后有一次客户反映某些历史记录数据偶尔会变成 0xFF。MRAM 芯片本身很可靠但我排查下来发现是 SPI 走线离 AC 电源线太近导致 CS 信号在高频干扰下出现毛刺芯片有时候被错误地拉低读出的数据自然不对。解决方案分了三层。硬件上把 SPI 信号移到了底层并且加了 47Ω 串联电阻软件上每条记录前面加 4 字节 CRC32 校验码读取时如果校验不过就丢弃该记录并重新采样。再往上我加了“连续读两次比对”的逻辑。虽然多花一点时间但胜在稳定。工业应用不像开发板跑 demo不能指望环境干干净净冗余校验是必须的成本。4.4 掉电瞬间数据拯救方案这类设备经常遇到现场突然断电。传统 EEPROM 和 Flash 在电压跌落时很可能写了一半。MRAM 写操作速度快但也不能拿芯片的极限开玩笑。我利用 TM4C129 内部掉电监测模块或者外接一个简单的电阻分压比较器检测到 3.3V 跌到 2.8V 以下时触发中断。掉电中断里只做一件事把当前累计量、最近一条故障码等关键数据打包为 32 字节写入 MRAM。整个过程包含 SPI 发送大约需要 50us而掉电瞬间主控寄存器和 SRAM 还能维持几毫秒完全来得及。这个方案在 Flash 上是很难做到的因为写一页之前还要擦除扇区真等擦完电压早就没了。MRAM 的按字节写在这里显得特别珍贵。4.5 维护一张问题速查表我把调试过程中遇到的问题整理成了一张表后面做新版时直接对照现象可能原因解决办法读返回全 0xFFSPI 模式不匹配或芯片未上电用 100kHz 抓波形检查供电读返回全 0x00CS 空闲电平不对或 MISO 接错确认 CS 初始化为高电平写后读仍是旧数据没发 WREN或 WP 引脚为低先写使能检查 WP 电平偶发数据错误干扰或走线过长加长延时、CRC 校验、改善地线写大量数据后越界地址边界处理疏忽在驱动里加范围检查HOLD 引脚悬空导致死锁干扰信号拉低 HOLD上拉 10kΩ 到 3.3V或直连高电平这张表现在成了项目组新人培训的入门材料比看芯片手册直观得多。5. 这个组合还能怎么玩5.1 扩展成工业数据记录仪128KB 说大不大说小不小。如果每 10 秒记录一条 32 字节的运行记录可以存 4096 条覆盖超过 11 个小时。对于故障前后数据记录我习惯分配成循环缓冲预留 4KB 做配置文件剩下 124KB 平均分成两个环形区一个存事件一个存波形快照。MRAM 的随机访问特性让环形缓冲区实现非常顺畅不用像 Flash 那样先擦一整块再写。5.2 与以太网结合实现远程配置TM4C129XNCZAD 自带以太网我把它和 MRAM 结合起来做了一个很实用的功能设备通过 DHCP 拿到 IP上位机通过 TCP 把新的配置文件发过来程序先写到 MRAM 的一个临时区校验通过之后再搬到主配置区最后软复位生效。因为 MRAM 不需要等待擦写整个升级过程几十毫秒就完成现场几乎感觉不到重启间隔。如果你做产品这个思路远比离线烧录方便。5.3 功率预算和成本考量MRAM 价格普遍高于 Flash但很多工程师没算过“总拥有成本”Flash 因为寿命短可能在客户现场一年就报废了维护人员和物流成本远超芯片本身的价格差。我现在选型时如果数据更新频率大于每 10 秒 1 次就会倾向 MRAM如果只是偶尔存一次配置依然用 Flash。这算是一个合理的取舍。我个人始终觉得存储方案选型不能只看数据手册上“容量大、价格低”这两条。MR25H40CDF 这类 MRAM 芯片可能不是最便宜的选择但它让我在掉电保存、快速读写、无限次擦写上同时交了满分答卷。实际项目做完我最想提醒大家的一点是别把 HOLD 和 WP 当摆设这两个引脚的接法直接决定了你的系统在恶劣环境下的稳定性。如果你正在做工业控制器、采集终端或者需要高可靠日志记录的设备认真考虑一下 MRAM 和 TM4C129 的组合它会帮你少走很多弯路。
返回列表