ARTICLE DETAIL

资讯详情

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

MRAM替代Flash:工业控制器高频数据存储与掉电保护实战

MRAM替代Flash:工业控制器高频数据存储与掉电保护实战 去年做一套工业控制器的时候被Flash的页擦除和时间换空间问题折腾得够呛——既要频繁记录运行参数又怕写坏块还要处理掉电瞬间的数据丢失。后来在嵌入式存储这一环把方案换成了 MR25H40CDF 这颗SPI接口的MRAM搭配 TM4C129XNCZAD 主控重新设计了存储与读取链路。这套组合在工业现场跑了几个月数据稳定性和读写体验都比我预想的好。这篇文章不打算做理论梳理就完整记录我在这个项目里怎么选型、怎么画电路、怎么写驱动、怎么设计存储协议以及实测中遇到的那些坑给同样在工业和嵌入式应用里折腾数据存取的朋友做个参考。1. 工业存储选型MRAM从一堆非易失方案里胜出的原因1.1 Flash的页擦除与EEPROM的字节寿命为什么在频繁记录场景里都不省心先说工业现场最典型的存储需求设备运行参数要周期刷新比如变频器电流、温控曲线、累计运行时间、报警事件频率可能是每秒一次甚至更高。数据量不算大但特点是写得多、单次小、要求掉电立即保留。这类场景如果用NOR Flash最头疼的是擦除粒度。NOR Flash按扇区擦除动不动就是4KB、64KB哪怕只改一个字节也得先把整个扇区搬到RAM里改完再擦回来。频繁擦写还会带来块磨损问题工业级NOR Flash的擦写寿命通常是10万到100万次看着不少但如果你每秒写一次撑不过几天到几个月。写进Flash后还有页编程时间微秒到毫秒级不等掉电时机没掐好写一半的数据直接废掉。EEPROM倒是按字节写没有擦除整块的问题但寿命更短常见的是100万次擦写高温下情况更差。而且EEPROM容量普遍偏小SPI接口的也就几KB到几十KB存个运行曲线根本不够。这时候MRAM的特点就非常对路它本质是SRAM单元加磁性隧道结属于非易失存储但写操作不需要擦除也没有编程等待时间理论上读写寿命无限制。对每秒更新一次、每次几十字节的工业参数来说MRAM几乎是为这个场景量身定做的。1.2 MR25H40CDF的核心参数与特性拆解MR25H40CDF是Everspin的4Mbit SPI接口MRAM换算下来512KB容量3.3V供电工业级温度范围。我选它的时候比较看重的几个点写前无需擦除按字节改写任何地址随时可以写写入周期极短没有Flash那种tPP编程时间数据在CS拉高时已经锁存数据保持能力很强工业级温度下数据保持20年以上读耐久和写耐久没有实际磨损限制寿命指标和SRAM同级指令集和常见的SPI Flash高度相似READ(0x03)、WRITE(0x02)、RDSR(0x05)基本一一对应迁移成本低。MR25H40CDF的地址范围是0x00000到0x7FFFF刚好19位地址线用3字节地址字段时最高字节只有bit18到bit16有效。封装形式常见的是SOIC-8或DFN-8具体看后缀和订货型号画PCB之前一定要让代理商确认实物封装这个坑后面会细说。1.3 和SRAM加电池、FRAM、NOR Flash的横向对比选型阶段我还认真对比过另外几个方案。SRAM加电池后备是老传统断电瞬间切到电池供电维持数据问题是要管电池电压、要维护更换在高温振动的工业环境里电池本身就是一个不稳定因素而且SRAM的电池方案通常只能保持几年远不如MRAM省心。FRAM也是非易失存储写速度极快、寿命高和MRAM很多特性类似但FRAM容量普遍偏小串行接口的常见型号做到几KB就算大的了。我要存运行曲线和设备档案这点容量不够。FRAM的另一个缺点是写入电流和功耗特性在一些低功耗便携设备里更敏感对工控主板来说倒不是大问题容量瓶颈才是关键。性能对比上MRAM赢在容量和零擦除等待上。NOR Flash吞吐不慢但擦写算法复杂EEPROM容量和寿命双双受限SRAM加电池要维护电池。综合下来MR25H40CDF这种512KB容量的MRAM显得很均衡既能塞下中等规模的存储分区又能承受高频改写还不引入额外维护负担。2. MR25H40CDF与TM4C129XNCZAD的硬件连接设计2.1 引脚分配SPI四线加软件片选为什么不用硬件FSSTM4C129XNCZAD这片MCU属于TM4C129x系列Cortex-M4F内核120MHz主频外设非常丰富。它有多个SSI模块也就是同步串行接口可以直接工作在SPI主模式。我这边用的是SSI0默认引脚是PA2作为SCLK、PA3作为FSS、PA4作为MISO、PA5作为MOSI。在片选处理上有一个关键选择是使用SSI硬件自动片选FSS还是用普通GPIO软件控制片选。第一次接触Tiva芯片的工程师容易直接挂上PA3的FSS信号给MRAM的CS脚然后发现读写数据错乱。原因在于Tiva的SSI模块在MOTO模式下FSS会在每个字节传输完成时自动拉高而MRAM的指令帧是命令加地址加数据的多字节序列中途片选一旦释放整帧序列就被打断MRAM会把这几个字节当成零散操作来处理。所以我的做法是把真正的片选线放到普通GPIO上比如PD3软件拉低开始传输全部字节传完后软件拉高结束。PA3的FSS信号留空或者配置成普通GPIO输出高电平。控制权完全在自己手里时序想怎么掐就怎么掐MRAM也只需要看到一次干净的低脉冲覆盖完整指令帧。引脚分配建议如下信号MCU引脚说明SCLKPA2SSI0ClkSPI时钟由SSI0产生MOSIPA5SSI0Tx主出从入接到MRAM SIMISOPA4SSI0Rx主入从出接到MRAM SOCSPD3普通GPIO软件片选低有效VDD3.3VMRAM供电VSSGND共地2.2 电源、去耦与电平匹配的几个实用细节TM4C129XNCZAD的GPIO电平和MR25H40CDF都是3.3V可以直接相连不需要电平转换。但如果你的系统里MCU是5V供电而GPIO是5V电平那就千万不要直接怼MR25H40CDF不是5V容忍器件必须加电平转换或者用串阻分压。电源去耦方面MRAM的VDD引脚旁边要就近放一个100nF的陶瓷电容厂家手册上一般会建议这个容量。我习惯再加一个1uF到10uF的去耦电容并联覆盖高频噪声和中频波动。PCB布局时电容要尽量靠近VDD引脚走线短粗回流路径直接到GND过孔。还有一个容易忽略的点MRAM的SCK时钟脚在低温或者高湿度环境下如果输入信号有振铃容易造成误采样。SPI时钟频率跑到20MHz的时候信号边缘质量很重要。项目里如果只能用杜邦线飞线调试频率先降到1MHz到4MHz再跑通功能最后再上高速验证。2.3 上电时序和复位策略MR25H40CDF是纯数字SPI器件没有复杂的初始化时序但MCU侧的GPIO状态在复位期间要注意。TM4C129XNCZAD复位后GPIO默认状态是输入引脚呈高阻这时候如果外设还连着MRAM是不存在驱动的MRAM的CS引脚需要通过外部上拉电阻固定在无效高电平避免在VDD爬升期间CS处于不确定状态导致误写入。我一般在CS线上加一个10kΩ上拉电阻到3.3V。这样无论MCU是复位、程序跑飞还是正在下载调试MRAM的片选都不会被意外拉低。上电后软件先把GPIO配置成输出并置高CS再做SSI外设初始化这个顺序不能反。试想一下如果先初始化SSISCLK线上已经有了时钟脉冲而CS还是高阻状态对应关系不明确MRAM内部状态机可能被不可预期的边沿触发虽然不会直接损坏芯片但会引入一次不确定的寄存器状态。3. TM4C129XNCZAD端SSI驱动实现从寄存器到可用的读写函数3.1 SSI外设初始化时钟、引脚复用、模式与分频计算我用的开发环境是TivaWare SDKTM4C129XNCZAD在库里的定义很完整。初始化SSI0的步骤如下#include stdint.h #include stdbool.h #include tm4c129xnczad.h #include inc/hw_memmap.h #include driverlib/ssi.h #include driverlib/gpio.h #include driverlib/sysctl.h #include driverlib/pin_map.h #define MRAM_CS_PORT GPIO_PORTD_BASE #define MRAM_CS_PIN GPIO_PIN_3 static void mram_cs_assert(void) { GPIOPinWrite(MRAM_CS_PORT, MRAM_CS_PIN, 0); } static void mram_cs_deassert(void) { GPIOPinWrite(MRAM_CS_PORT, MRAM_CS_PIN, MRAM_CS_PIN); } void spi_bus_init(void) { // 使能外设时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); while (!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI0)) {} while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOA)) {} while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOD)) {} // 引脚复用PA2SCLKPA5MOSIPA4MISO // PA3FSS在软件片选方案下不用配置成普通GPIO输出高即可 GPIOPinConfigure(GPIO_PA2_SSI0CLK); GPIOPinConfigure(GPIO_PA5_SSI0TX); GPIOPinConfigure(GPIO_PA4_SSI0RX); GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5); GPIOPinTypeGPIOOutput(GPIO_PORTA_BASE, GPIO_PIN_3); GPIOPinWrite(GPIO_PORTA_BASE, GPIO_PIN_3, GPIO_PIN_3); // 软件片选脚 GPIOPinTypeGPIOOutput(MRAM_CS_PORT, MRAM_CS_PIN); mram_cs_deassert(); // SSI0主模式SPI模式08位数据20MHz SSIConfigSetExpClk(SSI0_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 20000000, 8); SSIEnable(SSI0_BASE); }这里的时钟配置有一个细节值得说一下。TM4C129XNCZAD主频120MHzSSI时钟由系统时钟分频而来公式是SPI时钟等于SysClk除以CPSDVSR再除以1加SCR。CPSDVSR必须是偶数最小是2。如果我要得到20MHz最简单的组合是CPSDVSR等于6SCR等于0也就是120除以6再除以1刚好20MHz。如果后续想降到4MHz调试CPSDVSR设为30即可。TivaWare的SSIConfigSetExpClk内部会自动计算分频系数但理解这个计算过程有助于排查时钟不对的奇怪现象。3.2 MRAM基本读写时序与函数实现MR25H40CDF的读操作很简单CS拉低发0x03命令字节接着发3字节地址高位在前高字节只有低3位有效然后连续读取数据读完拉高CS。写操作类似CS拉低发0x02命令字节后跟3字节地址再跟待写数据全部发完后拉高CS。有一点和SPI Flash不一样MRAM写数据前不需要先执行写使能WREN 0x06也不需要擦除直接发WRITE命令就能改写任意地址。这也是MRAM的底层机制决定的每个MRAM位单元就是SRAM加磁性隧穿结天然支持字节级随机改写所以不需要Flash那种先擦后写和写保护机制。我在实际驱动里就直接发0x02省掉了WREN这一步。核心单字节交换函数如下static uint8_t mram_spi_readwrite(uint8_t tx_data) { uint32_t rx_data 0; SSIDataPut(SSI0_BASE, tx_data); while (!SSIDataGetNonBlocking(SSI0_BASE, rx_data)) {} return (uint8_t)rx_data; } static void mram_spi_wait_idle(void) { uint32_t dummy; while (SSIBusy(SSI0_BASE)) {} while (SSIDataGetNonBlocking(SSI0_BASE, dummy)) {} }再封装成读和写函数void mram_read_bytes(uint32_t addr, uint8_t *buf, uint32_t len) { mram_cs_assert(); mram_spi_readwrite(0x03); // READ mram_spi_readwrite((addr 16) 0xFF); mram_spi_readwrite((addr 8) 0xFF); mram_spi_readwrite(addr 0xFF); while (len--) { *buf mram_spi_readwrite(0x00); } mram_spi_wait_idle(); mram_cs_deassert(); } void mram_write_bytes(uint32_t addr, const uint8_t *buf, uint32_t len) { mram_cs_assert(); mram_spi_readwrite(0x02); // WRITE mram_spi_readwrite((addr 16) 0xFF); mram_spi_readwrite((addr 8) 0xFF); mram_spi_readwrite(addr 0xFF); while (len--) { mram_spi_readwrite(*buf); } mram_spi_wait_idle(); mram_cs_deassert(); }注意在拉高CS之前一定要等SSIBusy返回false也就是SSI模块内部的移位寄存器已经把最后一个字节的最后一个bit发出去了。如果在BSY状态下直接拉高CS最后几个bit还悬在总线上没到达MRAM写入数据就会缺尾巴。3.3 连续读写时FIFO的管理和片选控制上面这个单字节版本逻辑最清晰但每字节都要等待RX FIFO吞吐量被函数调用和循环开销拖慢。实测在20MHz时钟下用这个版本连续读整片512KB大约耗时230ms左右换算过来约2.1MB/s对大部分工业记录场景完全够用。如果想压榨速度可以用批量模式。TM4C129XNCZAD的SSI带16级FIFO可以把发送数据和接收数据交错处理避免RX FIFO溢出。我的做法是每次批量16字节先把16字节的TX数据填充再从RX FIFO取出16字节结果。这样SPI总线几乎不空闲速率能接近理论值2.5MB/S。需要特别提醒的是FIFO溢出问题。SSI的RX FIFO深度只有16如果你一口气往TX FIFO塞几十个字节却没有同步取走RX数据RX FIFO会溢出旧数据还没读就被新数据覆盖读回来的数据就会错位。实测现象就是连续读超过16字节之后前16字节对后面连续错位。解决办法就是批量处理或者用DMA搬运但DMA会引入更复杂的配置我建议先跑通批量16字节版本再考虑DMA。4. 工业数据落地从裸读写到可靠存储协议4.1 参数区、日志区、事件区的分区设计把MRAM当成一块字节可写的存储介质后真正的难点反而不是驱动而是怎么组织数据。工业应用里存储的数据可以分成几类频繁更新的运行参数、持续追加的运行日志、带时间戳的事件记录、偶尔更新的设备配置。如果统统堆在一个区里后期维护会非常痛苦。我按照MRAM的512KB容量做了这样的分区规划分区地址范围大小用途配置区0x00000 - 0x03FFF16KB设备参数、校准数据启动时读入RAM参数刷新区0x04000 - 0x0BFFF32KB运行参数高频刷新双槽备份事件记录区0x0C000 - 0x1BFFF64KB报警事件、开关机记录环形覆盖运行日志区0x1C000 - 0x7FFFF400KB温度曲线、电流曲线环形覆盖分区大小可以根据实际项目调整关键是每个分区头都要有一份分区元数据包括魔数、版本号、长度、CRC校验值。这样即使以后调整固件也能识别出旧数据的版本做迁移。4.2 双备份槽位与启动校验高频参数刷新区我做了双槽位机制。简单说就是把参数区域分成两个相同的槽位每次写入交替使用。每个槽位内部结构是魔数4字节、参数版本4字节、参数正文、CRC16校验2字节。写入流程是先写槽A如果完整写完把槽A的CRC写正确下次写入槽B保持至少一个槽是完整的。启动时先读槽A魔数对、CRC对就采用槽A否则读槽BCRC对就采用槽B如果两个槽都坏了才使用默认配置。这个设计主要解决两个问题一是写一半掉电导致单份参数损坏双槽保证另一个可用二是启动时验证数据完整性避免踩到魔术数字和数据不对齐的坑。因为MRAM支持无限次写双槽交替不会引入Flash那种磨损平衡的考虑写哪个槽完全是软件策略。CRC校验用CRC16就够了注意CRC计算范围要把魔数和版本号也算进去别只算参数正文否则字段头被篡改也会逃过检查。我在实际项目里用查表法算CRC16-MODBUS和上位机工具对得上调试也方便。4.3 掉电、VOL和写循环的真实表现掉电保护是这个方案最让我满意的地方。MRAM的写入在CS拉高的瞬间已经完成没有Flash那种写命令结束后的内部编程窗口。所以只要确保在掉电瞬间MCU还有最后几个微秒把CS拉高数据就是安全的。TM4C129XNCZAD检测掉电可以用外部比较器加PFI引脚或者用供电电压的分压检测但这个项目里我更依赖电容储能在3.3V轨上加一个足够大的储能电容让MCU在检测到掉电后还有几毫秒时间完成关键数据写入和CS拉高。VOL电压过低情况下MRAM本身有内部掉电检测只要VDD降到阈值以下内部写电路会被禁止不会允许半写入。不过外部电路还是要确保CS在此状态下不会被拉低。我之前测试过在VDD从3.3V缓慢降到2.0V的过程中反复写入并没有发现数据损坏这个结果和MRAM的内部保护机制是吻合的。写循环方面我在长期老化测试里让高频率参数区每秒写一次连续跑了三个月估算大概800万次写入读出来的数据和写入值完全一致。这个量级对Flash来说已经接近寿命边缘MRAM基本上处于卧推热身的状态。5. 实测数据、调试技巧与踩坑记录5.1 一组室温与高温下的读写速率实测在整个系统跑通后我做了一组简单但能说明问题的实测测试项结果20MHz SPI时钟下连续读512KB约230ms约2.1MB/s20MHz SPI时钟下连续写512KB约235ms约2.0MB/s单字节随机写含CS开销约30us每秒约3万次单字节随机读含CS开销约28us80℃连续读写72小时数据无错误CRC全过-20℃读写测试数据正常无异常连续写比连续读慢一点差异主要来自循环结构和等待逻辑MRAM本身没有写等待。单字节随机写的30us里绝大部分是SPI传输和函数调用开销20MHz下8字节帧理论只要3.2us所以优化空间还是有的但工业现场对单次随机写的实时性要求并没有那么苛刻30us完全够用。5.2 调试中遇到的两个典型坑片选毛刺和FIFO丢失第一个坑是CS引脚的毛刺。刚开始我把CS放在一个没有上拉的GPIO上MCU复位瞬间CS电平不确定偶尔会见到MRAM状态寄存器里出现一些奇怪的值怀疑是复位期间CS低毛刺触发了写指令。后来加了10kΩ上拉电阻并且把CS初始化的顺序调整到其他外设之前这个现象就消失了。第二个坑是FIFO读取丢失。用单字节交换函数时没问题但我优化成批量填充时踩过坑。一次性往TX FIFO塞32字节RX FIFO只有16级中间一段数据被覆盖读回来的结果从第17字节开始错位。排查方式是在调试器里同时看TX FIFO和RX FIFO状态位很快就发现了RX FIFO溢出标志被置位。后来改成16字节一批处理或者使用更精细的边发送边接收循环问题解决。5.3 长期稳定性观察与工作温度边界长期稳定性的表现可以说是MR25H40CDF最没存在感的一个环节。存储器的存在感越弱说明它越可靠。三个月的连续记录验证中没有因为存储介质产生过一次数据错误所有故障都出在别的环节比如上位机软件时间戳混乱、通信线接头松动。温度方面我在温箱里做过80度高温和负20度低温的简单验证。MRAM的工作温度范围对工业用户来说很关键MR25H40CDF按工业级后缀选型能覆盖常见的设备工作范围。但要注意同样是MR25H40系列不同后缀对应的温度等级和封装可能不同默认按高可靠性工业版本选型准没错。如果你需要车载级或更高温度范围选型时要单独核对数据手册里的温度曲线别只盯着容量和接口。最后再分享一个小技巧给MRAM的写入操作做一个统一的入口函数把所有写前CS拉低、写后等待空闲并拉高CS的逻辑放在这个入口里统一控制业务代码只管组织数据。这样即使以后换了存储介质驱动和业务之间的接口也不会乱。我在这套系统里就是靠这个方式把存储模块重构了几次都没有影响上层逻辑。实际用下来MR25H40CDF加TM4C129XNCZAD的组合给我最大的感受是省心不用管擦除、不用管磨损、不用担心掉电丢失把精力留给真正需要调试的业务逻辑。
返回列表