ARTICLE DETAIL

资讯详情

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

MRAM替代Flash的工业存储方案:R7FA2E2A72DNK+MR25H40CDF实战

MRAM替代Flash的工业存储方案:R7FA2E2A72DNK+MR25H40CDF实战 年初做一台工业数据记录仪日志区写到第三个月突然写不进去了。查了半天不是代码问题是外置NOR Flash的擦写寿命到了。后来我把存储介质换成了一颗MR25H40CDFEverspin的4Mbit SPI MRAM主控用瑞萨的R7FA2E2A72DNKCortex-M23内核这套组合在嵌入式应用里几乎解决了我所有的工业存储痛点写得快、耐久、掉电不丢、驱动简单。这篇文章就把这套方案的选型理由、硬件连接、驱动实现和踩坑经验完整展开给正在做工业数据采集、运行日志、参数存储的工程师一个可以直接抄作业的参考。先说结论工业场景里频繁写小数据 掉电保护 长期不维护这几个需求放在一起传统Flash和EEPROM都挺难受MRAM正好是那个存储特种兵。R7FA2E2A72DNK作为主控低功耗、宽压、自带RSPI跟MR25H40CDF的SPI接口天然搭配。下面我从为什么选这两颗芯片讲起再逐步到代码和电路细节。1. 选型逻辑工业数据存储的痛点与这对芯片的定位1.1 为什么是MRAM而不是Flash、EEPROM、FRAM或电池SRAM工业设备里最常见的存储需求无非这么几类设备参数、校准数据、运行日志、故障记录、掉电时抢救的关键变量。这些数据的共同点是写入次数多、单次数据量小、绝对不能因为掉电损坏。如果用传统方案会遇到什么NOR Flash读快写慢写入前必须先擦除擦除最小单位是扇区动不动4KB、64KB。频繁写日志时你得维护擦写均衡否则某一扇区先写坏。典型擦写寿命10万次工业设备一年写几十万次很正常说废就废。EEPROM字节擦写方便但容量小、速度慢。I2C接口的EEPROM写一个字节要等5ms写几KB数据能拖到天荒地老不适合做日志缓冲。FRAM速度和耐久都不错但容量上限一般就几百KB型号选择少价格也不友好。电池SRAM速度无敌但要电池、要充电管理、要担心电池寿命和高温漏电维护成本太高。再看MR25H40CDF这颗MRAM非易失、SPI接口、写入不需要擦除、写入耐久按Everspin的说法是无限次规格书上常见的是10^14次量级工程上可以当成没有磨损这回事、数据保持20年以上、写入速度就等于SPI时钟速度没有页编程等待时间。它本质上是一种掉电不丢的RAM不是能按字节写的Flash。这一点非常重要后面所有驱动逻辑都建立在它上面。1.2 两颗芯片在系统里各自扮演什么角色R7FA2E2A72DNK属于瑞萨RA2E2系列Arm Cortex-M23内核最高主频48MHz工作电压范围1.6V到5.5V72引脚封装内置了RSPI瑞萨的串行外设接口、LVD低压检测、DMAC这些外设。它在这套系统里负责什么就是标准的嵌入式主控活采样传感器、跑控制逻辑、把人机交互和通信协议接起来然后把需要保存的数据通过SPI丢给MRAM。MR25H40CDF则纯粹是外置存储。你可能会问R7FA2E2A72DNK内部不是有Flash吗为什么不用内部Flash存内部Flash同样有擦写寿命问题而且很多MCU的Flash在写操作时会阻塞代码执行掉电时写一半很容易破坏整页数据。把存储独立出来的好处是换MCU型号不影响数据格式存储磨损不影响主控寿命掉电时主控可以专心执行保存流程。所以这套架构很简单一颗通用MCU负责干活一颗MRAM负责死记硬背。MCU选R7FA2E2A72DNK是因为它的RSPI接口、宽电压、LVD功能都契合工业场景MRAM选MR25H40CDF则是因为它在容量、接口、耐久三者之间找到了工业应用的最佳平衡点。2. MR25H40CDF 硬核拆解指令集、状态寄存器与读写特性2.1 容量、接口与引脚功能细节MR25H40CDF的容量是4Mbit也就是512KB组织方式是512K x 8bit正好是一个完整字节寻址空间。它通过标准SPI接口通信支持SPI Mode 0CPOL0, CPHA0和Mode 3CPOL1, CPHA1这个兼容性很关键因为有些MCU的SPI外设对Mode 3支持更顺滑。8引脚DFN封装除了VDD和VSS核心信号就是CS#、SCK、SI、SO外加WP#和HOLD#。CS#是片选低有效SI是数据输入SO是数据输出SCK是时钟。WP#是硬件写保护引脚低电平有效拉低时配合状态寄存器的BP位可以锁死整个芯片。HOLD#则是暂停引脚拉低时芯片暂停对外通信相当于把SPI时钟冻结主要用于多主机总线仲裁。实际用的时候HOLD#在不使用的场景下必须固定接高否则它一旦被干扰拉低总线上的数据就会莫名丢失。引脚连接可以按下表来MR25H40CDF引脚功能连接目标说明CS#片选MCU GPIO软件控制低有效默认拉高外部上拉SCKSPI时钟MCU RSPI_CLK支持Mode 0/3SI数据入MCU MOSI主发从收SO数据出MCU MISO主收从发WP#硬件写保护3.3V 或 GPIO拉高允许写拉低锁定HOLD#暂停3.3V固定绝对不能浮空VDD电源3.3V就近放0.1uF去耦电容紧贴引脚VSS地GND2.2 状态寄存器与写保护机制MR25H40CDF有一个8bit状态寄存器通过RDSR命令0x05读取通过WRSR命令0x01写入。这个寄存器的布局是这样的bit0是WEL写使能锁存bit1到bit3是BP0、BP1、BP2块保护位bit6是WPEN写保护使能位。WEL这个位很关键它和很多Flash一样任何写操作包括写数据、写状态寄存器之前必须先发WREN0x06命令把WEL置1写完后硬件自动清零。如果你忘了发WREN就直接发写数据命令芯片会静默忽略你还在SPI总线上看到数据正常移位但MRAM内部一个字都没动。BP0-BP2和WPEN配合WP#引脚构成硬件写保护体系。当BP位为非零状态且WPEN1或WP#引脚为低时对应地址区域就会被锁定写命令无效。对大多数应用来说最安全的默认配置是上电后主动发一次WRSR把状态寄存器清成0x00确保没有任何意外的块保护生效。等系统进入稳定运行后如果你想防止某个地址段被随机改写再按需配置BP位。2.3 没有擦除也没有页编程意味着什么读MR25H40CDF的数据手册你会发现整个指令表里没有Erase指令也没有Page Program这种需要按页操作的指令。它只有READ0x03、FAST_READ0x0B、WRITE0x02这么几个核心命令。这意味着什么传统NOR Flash写入一条数据前你得像搬家前先腾屋子一样把目标扇区整个擦除成0xFF再编程写入。这个擦除动作几百毫秒到几秒不等而且会造成闪存磨损。MRAM则完全没有这个概念你发WRITE命令地址指到哪儿数据就覆盖到哪儿原来存的旧数据直接被新数据顶掉。这不光省了擦除时间更重要的是让驱动代码从根上变得简单——不需要擦写均衡算法不需要先读后写不需要维护坏块表。循环日志可以用最简单的当前位置写、写满从头覆盖的方式实现这在Flash上简直不可想象。3. R7FA2E2A72DNK 侧的准备硬件连接与FSP外设配置3.1 最小系统连接与引脚分配R7FA2E2A72DNK属于RA2E2系列72引脚的封装留足了GPIOSPI功能引脚有多个可选复用点具体选哪一组引脚需要对照数据手册的Pin Multiplexing表。我的习惯是先定CS用普通GPIO控制不依赖RSPI硬件自动片选。原因后面讲。其余SCK、MOSI、MISO三根线接RSPI模块的对应引脚。PCB布线层面有几点注意。SCK和MOSI是输出信号MISO是输入信号。MRAM的VDD旁一定要放一个0.1uF陶瓷电容而且必须紧贴引脚。CS#上建议加一个10kΩ上拉电阻到3.3V这个电阻不是为正常通信用的是为MCU复位瞬间准备的。复位时GPIO会进入高阻态如果CS#悬空任何一点耦合噪声都可能把它拉到低电平让MRAM误认为片选有效产生伪命令。工业现场干扰多这个上拉能省掉很多诡异问题。WP#和HOLD#的处理也很容易翻车。WP#可以接到GPIO也可以直接接高。如果你想工程师能现场解锁MRAM就引到GPIO上但多数产品里直接接3.3V更稳妥省掉一个软件谜团。HOLD#则必须结结实实接3.3V悬空等于埋雷。3.2 FSP里配置RSPI的正确姿势RA系列用瑞萨的FSPFlexible Software Package开发环境在e2 studio里通过Stacks配置外设。我用的配置大致是这样的在Stacks中添加r_spi模块选择Master模式传输宽度8bit比特率设置到10Mbps或20MbpsMR25H40CDF手册标称最高工作频率通常到40MHz级别但考虑到PCB走线和工业温度余量20Mbps是我实际使用的保守值稳。时钟极性CPOL和时钟相位CPHA都设成0对应SPI Mode 0。然后关键一步片选信号不要启用RSPI硬件自动片选而是留一个GPIO手动控制CS#。为什么因为MRAM的读操作和写操作都要求CS#在整个命令数据期间保持低电平而WREN和WRITE是两个必须分成两段完成的独立事务先拉低CS#发WREN拉高再拉低CS#发WRITE命令地址数据再拉高。如果用硬件自动片选它往往会把一个SPI事务内的所有字节动作当成一段连续操作很难自然表达WREN结束、WRITE开始这种边界。手动CS#虽然代码里多几行拉高拉低但逻辑完全可控也不会被RSPI的自动片选时序带偏。配置完RSPI后FSP会自动生成初始化代码。之后所有SPI收发都通过R_SPI_Write、R_SPI_Read、R_SPI_WriteRead这类API完成。注意R_SPI_Write的最后一个参数是wait标志真机调的时候可以设成true简单轮询等完成掉电处理时反而比中断和DMA更可靠。3.3 上电时序与CS隔离的坑工业设备最烦的就是上电掉电瞬间的随机状态。MCU内核还没跑起来时GPIO的默认电平由硬件决定这时候如果MRAM的CS#被意外拉低、又恰好收到几个时钟沿它可能会进入某个写状态。虽然概率不高但一旦发生状态寄存器可能被改写。处理办法有两个一是CS#引脚外部加10kΩ上拉让高阻期间它稳在高电平二是如果可能给MRAM的VDD加一个RC延迟让它比MCU晚一点上电等MCU GPIO状态稳定后再供电。两种方案不冲突可以一起用。另一个容易忽略的是RSPI引脚复用配置。RA2E2的GPIO有的默认是模拟功能有的默认是数字功能如果你在FSP里配了SPI功能但没有在引脚配置界面把对应的Pxxx设置为外设功能初始化代码不会报错但引脚就是不出波形。我调试时曾经在这个问题上浪费了半天逻辑分析仪一挂上去发现SCK完全没信号最后打开FSP的Pins标签页逐一确认才好。4. 读写驱动实现从指令封装到数据校验4.1 基本指令封装与SPI抽象层写驱动之前先把指令宏定义好。为了在不同厂商的SDK间好移植我把SPI底层抽象成三个函数SPI发送、SPI收发、SPI接收。真机上是RA的RSPI三个函数内部其实就是对R_SPI_Write和R_SPI_WriteRead的封装。下面这段代码是完整的核心指令层#define MRAM_WREN 0x06 #define MRAM_WRDI 0x04 #define MRAM_READ 0x03 #define MRAM_WRITE 0x02 #define MRAM_RDSR 0x05 #define MRAM_WRSR 0x01 #define MRAM_FAST_READ 0x0B static void mram_cs_low(void) { R_GPIO_PinWrite(g_cs_ctrl, g_cs_pin, 0); } static void mram_cs_high(void) { R_GPIO_PinWrite(g_cs_ctrl, g_cs_pin, 1); } /* 读取状态寄存器 */ static uint8_t mram_read_status(void) { uint8_t cmd MRAM_RDSR; uint8_t status 0; mram_cs_low(); spi_transmit(cmd, 1); spi_receive(status, 1); /* SPI全双工实际底层是WriteRead发送任意字节同时收 */ mram_cs_high(); return status; } /* 写使能 */ static void mram_write_enable(void) { uint8_t cmd MRAM_WREN; mram_cs_low(); spi_transmit(cmd, 1); mram_cs_high(); }这里有个细节值得强调SPI是全双工的收数据的时候主机必须在同一位时间发出一个字节通常发0x00或0xFF。你收到的SO数据其实对应的是你发出的那个字节。所以在R_SPI的封装里没有单独的spi_receive而是用WriteRead来实现。写一个读状态寄存器函数底层就是发送0x05命令字节、再发送一个哑字节、同时收一个状态字节。4.2 连续读写与地址管理MR25H40CDF的读和写都是批量连续的。发完命令和24bit地址后芯片会从该地址开始随SPI时钟连续输出/接收数据地址自动递增直到CS#拉高为止。这意味着你可以一次读完整个512KB中间不需要任何页间隙。写数据的代码这样写int mram_write(uint32_t addr, const uint8_t *data, uint32_t len) { uint8_t cmd[4]; cmd[0] MRAM_WRITE; cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; mram_write_enable(); /* 先置WEL */ mram_cs_low(); spi_transmit(cmd, 4); spi_transmit(data, len); mram_cs_high(); /* MRAM写入即时生效没有tPP等待 */ return 0; }注意两点。第一发WRITE命令前必须调用mram_write_enable()这个不能省否则数据进不了内存阵列。第二MRAM写入真的没有内部编程延迟数据字节在SPI时钟的驱动下逐个移位进入芯片当CS#拉高的瞬间数据已经是新的了不需要像Flash那样等待tPP。这在掉电保存场景下是巨大优势你只需要保证最后一个字节移进芯片 CS#拉高这两步做完数据就是安全的。读数据的代码int mram_read(uint32_t addr, uint8_t *data, uint32_t len) { uint8_t cmd[4]; cmd[0] MRAM_READ; cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; mram_cs_low(); spi_transmit(cmd, 4); spi_receive(data, len); /* 底层是WriteRead连续收发 */ mram_cs_high(); return 0; }读比写简单不需要WRENCS#拉低发命令和地址后面就一个字节一个字节把数据收回来。4.3 写后回读与CRC校验MRAM虽然可靠性高但存储电路设计上我依然遵循写后回读的铁律尤其是在参数保存这类关键场景。写完后立刻启动一次读操作把同一地址的数据读回来逐字节比对。MRAM出错的概率极低但SPI链路本身可能出错线序接反、电压不稳、接触不良。写后回读不能保证数据在断电后依然正确但至少能当场暴露MCU写进去的和实际存储的不一致这类问题。CRC校验则用于系统运行时的完整性检查。最简单的做法每个数据块尾部附加CRC16读取时先算一遍再比对这个CRC。比如存设备校准时数据区格式如下偏移内容大小0数据版本号2字节2校准参数A4字节6校准参数B4字节10预留6字节16CRC162字节写的时候一次性把数据CRC算好通过一次MRAM写事务写进去。读的时候同样读整个18字节校验CRC。这样无论数据是没写全、被外部干扰改写还是主控读错了都能通过CRC拦下来。5. 工业落地的可靠性设计掉电保存与写保护策略5.1 掉电检测与紧急数据保存工业设备最容易被吐槽的场景之一就是突然断电后数据全丢。传统做法是MCU检测到掉电趁着电容里的余电把RAM数据写进Flash。但Flash写入要擦除、要等页编程几百字节可能要好几十毫秒电容根本撑不了那么久写一半断电还会损坏原数据。MRAM就不一样了。RA2E2内部有LVD低电压检测模块。把它配置成掉电检测源当VDD降到设定阈值比如2.8V时产生NMI中断。NMI优先级最高掉电瞬间能抢到CPU。在NMI回调里我做的事很克制关掉所有无关外设中断把关键变量打包到一个固定的紧急保存区域调用mram_write()写入CS#拉高然后死循环等待系统彻底掉电。这里有几个操作上的要求。掉电NMI里绝对不能用DMA和中断驱动的SPI传输因为掉电瞬间时钟树已经不稳DMA可能拿到错误描述符。就用最土最可靠的轮询方式一次一个字节地搬。第二紧急保存区在MRAM里要单独划分不要跟日志区混在一起确保寻址不冲突。第三如果掉电电容余量足够可以在NMI里先做写后回读再决定是否补写但更稳妥的做法是默认相信MRAM的写时序因为写操作本身没有写到一半卡死的状态机。实测下来在VDD掉到2.8V触发NMI后MCU的余电窗口大约有50ms以上取决于电容容量和负载。这段时间足够把256字节的关键数据写完并回读确认。要在Flash上实现同样功能你得在电路上加一个超级电容保证擦除写入的时间窗口成本和复杂度都上去了。5.2 用BP位和WP#防止异常改写工业设备还有一个很实际的隐患程序跑飞、看门狗复位瞬间、或者现场电磁干扰可能导致主控在错误的时间向存储发写命令。为了防这个MRAM的状态寄存器提供了一套块保护机制。以最简单的方式说明你想保护整个存储空间不被乱写可以按下面的流程操作void mram_protect_all(void) { uint8_t status; mram_write_enable(); status mram_read_status(); status | 0x0E; /* BP01, BP11, BP21, WPEN1 */ mram_cs_low(); uint8_t cmd[2] {MRAM_WRSR, status}; spi_transmit(cmd, 2); mram_cs_high(); /* CS#拉高后保护立即生效 */ }设置完成后只有在WP#引脚为高且状态寄存器允许的情况下才可能解除。如果硬件上把WP#固定拉低那整个芯片就处于硬件锁定状态谁也写不了包括主控自己。所以在设计上要留好出口WP#接GPIO解锁时拉高锁定时拉低或者WP#直接接高靠软件BP位做区域锁定。实践中的折中方案是把空间分区系统参数区加BP保护日志区不加保护。因为日志区本来就允许反复覆盖不需要保护参数区则通过BP位锁定一段高地址区域防止失控程序改掉校准数据。这比任何软件标志都可靠因为这完全是芯片内部硬件行为。5.3 分区管理目录区、数据区与备份区有了512KB空间我建议做一个简单的分区规划哪怕一开始用不了那么多也要留出扩展余地。一个比较通用布局如下0x00000-0x03FFF启动参数区16KB。存放设备号、通讯参数、校准数据。每次写入前先写主区再写镜像区读的时候同时读两份不一致时以CRC正确的为准。0x04000-0x07FFF目录区16KB。存日志的写指针、序列号、日志块起始地址。这个区写得最频繁正是因为MRAM不怕写才可以放心让目录区承担高频写入。0x08000-0x7FFFF日志数据区约480KB。就是纯粹的循环缓冲写指针到末尾就回卷到起始。目录区日志区的组合很有意思。在Flash时代因为磨损你需要定期搬移整个日志区来均摊擦写。在MRAM上完全不用指针越界直接回卷覆盖老日志就行。目录区记录当前写指针和累计写入条数上电后从目录区恢复写指针就能无缝继续记录。这个逻辑用Flash实现要费不少心思做均衡磨损在MRAM上十几行代码就搞定了。这也是为什么我敢说MRAM从根上简化了工业存储的软件设计。6. 实测结果与踩坑实录6.1 读写速率实测MRAM快在哪用逻辑分析仪抓RSPI总线20Mbps时钟下实测数据如下连续写256字节总耗时约0.12ms。其中4字节命令地址约占1.6us256字节数据约占0.1msCS切换开销几十纳秒。换句话说MRAM的写吞吐率基本等于SPI时钟频率折算的数值没有隐藏的编程时间。连续读1KB总耗时约0.41ms同样没有额外的等延时。写后回读256字节并CRC校验写0.12ms 读0.12ms 校验耗时整体在0.3ms以内。对比一下同期测试的某品牌W25Q64 NOR Flash同样写256字节先擦扇区约60ms再页编程约0.8ms总共60ms以上两者的差距在200倍级别。EEPROM更是没法比I2C的AT24C256写一个字节要5ms写256字节要1.28秒就算用页写模式也要接近40ms。MRAM的功耗比Flash稍高是事实工业设备如果不计较待机电流这点功耗换来的可靠性完全值得。电源设计时按手册检查ICC留足余量别把MRAM和WiFi模块共用一条细长电源线就行。6.2 五个高频坑的完整排查过程这个项目前后调试了将近三周踩过的坑整理出来每一个都值得说细节。第一个坑CS#毛刺导致状态寄存器被改。现象是程序正常运行几天后写入突然全部失效。排查过程连上逻辑分析仪观察上电瞬间发现MCU复位时CS#引脚出来一个约200ns的低电平毛刺此时SCK上恰好有噪声时钟沿MRAM把毛刺当成了有效命令序列。解决方法是CS#外接10kΩ上拉电阻并确认MCU复位期间该引脚不输出低电平。从那以后我所有SPI从机芯片的CS#都强制要求外部上拉绝不裸奔。第二个坑忘发WREN写命令被静默忽略。现象是写函数执行完没有任何错误读回来全是0xFF。排查过程单步执行到写数据后立刻回读发现数据没进去。查阅数据手册才发现MR25H40CDF的WEL机制和Flash完全一致任何写操作必须先WREN。这个坑对从Flash平台转过来的人不算事但对第一次用MRAM的人几乎必踩。解决方法是把写数据和使能写封装成同一个接口从API层面杜绝漏调用。第三个坑HOLD#悬空导致偶发丢字节。现象是MISO线上偶尔多出一个高电平数据流里时不时跳一个字节。排查过程示波器检查MISO波形发现SPI通信期间SO脚偶发异常顺着PCB追到HOLD#引脚发现它悬空现场干扰把它拉低过几百微秒。解决方法HOLD#直接接3.3V从此再没出现过。第四个坑SPI模式配置不一致。现象是第一次初始化后读状态寄存器返回0x00看起来正常但写数据就是写不进去。排查过程拿示波器对比SCK和SI波形发现MCU配置的SPI模式是CPOL0、CPHA1而MRAM在Mode 0下才能正常采样。FSP里把RSPI配成CPOL0、CPHA0后一切正常。这提醒我们MRAM虽然同时支持Mode 0和Mode 3但主控侧必须严格匹配不能想当然。第五个坑拿Flash的思路去擦除MRAM。现象是接手项目的同事在代码里调了一个厂商Flash库的erase函数发现没报错但后续读写数据全乱。原因很简单MRAM没有擦除指令这条擦除命令被当成普通数据写进了MRAM的某个地址把原数据覆盖了。这个坑暴露的是认知问题不是芯片问题。MRAM是直接覆盖写驱动接口设计上根本不应该存在erase这个词。6.3 一些经验小结这套方案跑下来我最满意的地方是它把工业存储变成了一件无聊的事。原来做Flash存储要操心擦写均衡、掉电恢复、扇区搬移现在这些统统不用管。MR25H40CDF配合R7FA2E2A72DNK写命令发出去CS#一拉高事情就结束了不需要等待不需要重试不需要担心哪个扇区提前寿终正寝。个人体会最深的还是选型思维的变化。以前选存储芯片第一反应永远是容量够不够、价格贵不贵很少主动问我的数据写入频次是多少、掉电窗口多大。这次做记录仪被Flash写坏逼着换MRAM之后我养成一个习惯先在纸上估算目标产品的写入寿命需求。一天写1000条日志一条256字节一年就是几百万次写入这种产品从一开始就该选MRAM而不是Flash。MR25H40CDF的成本确实比普通Flash高但跟它省下的开发时间、现场维护成本、售后返修成本一比完全不亏。如果还要往深了做这套组合还能扩展不少配合RA2E2的DMAC做大数据量连续记录、用FAST_READ命令边上边长读边取模、设计双MRAN镜像备份做容灾。不过那是另一个话题了先把基础读写和可靠性兜住工业现场已经能放心跑上好几年。
返回列表