ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MRAM替代EEPROM:MK20DN128VFM5与MR25H40CDF工业存储方案

2026/10/5 1:32:59 拓冰建站 浏览量
MRAM替代EEPROM:MK20DN128VFM5与MR25H40CDF工业存储方案 1. 项目缘起与整体设计思路工业现场的数据存储有个很尴尬的现状一边是PLC、传感器、数控机床这些设备源源不断产生运行状态数据另一边是很多老设备还在用EEPROM或者带电池的SRAM做本地缓存。EEPROM写入寿命有限擦写次数一上来就报废带电池的SRAM又怕断电、怕高温、怕电池漏液维护成本高得离谱。我前两年接过一个环境监控的项目客户现场在南方夏天机柜内温度能到70度那批带电池的SRAM模块平均三个月换一次电池运维人员跑现场跑到崩溃。后来接触到MR25H40CDF这颗芯片才算找到比较靠谱的替代方案。MR25H40CDF是Everspin出的4Mbit磁阻随机存储器SPI接口最关键的是它属于非易失性存储断电数据不丢而且写入寿命几乎是无限的——官方标称写入次数没有上限读次数也是无限。这一点跟FRAM有点像但MRAM的读写速度更快功耗也更低。MK20DN128VFM5则是NXP的Kinetis K20系列MCUCortex-M4内核128KB Flash主频50MHz带SPI、I2C、UART等常用外设工业温度范围-40到105度正好跟MR25H40CDF搭配做数据采集和存储的控制器。这个组合的核心思路很直接MK20DN128VFM5负责采集设备运行状态数据通过SPI总线把数据写入MR25H40CDF需要的时候再读出来。整个方案没有文件系统没有复杂的协议栈就是裸机SPI读写简单粗暴但极其可靠。适合的场景包括工业设备运行日志记录、传感器数据缓存、掉电前的关键参数保存、以及需要频繁写入但又不方便换电池的场合。为什么选MR25H40CDF而不是FRAM或者铁电存储器FRAM虽然写入寿命也高但容量普遍偏小而且价格随容量增长很快。MR25H40CDF是4Mbit也就是512KB对于大多数工业数据缓存场景够用了。另外MRAM的写入速度比FRAM快SPI接口可以跑到40MHz写入一个字节的时间在微秒级别对于需要快速保存掉电数据的场景很关键。MK20DN128VFM5的SPI模块支持DMA可以进一步降低CPU占用让MCU有时间处理其他任务。整体方案的设计原则就三条第一数据写入要快掉电瞬间能保存关键数据第二写入寿命要够不能因为频繁写就把芯片写坏第三接口要简单SPI四根线搞定不需要额外的地址总线或者控制线。MR25H40CDF正好满足这三点MK20DN128VFM5的SPI外设也足够灵活可以配置成各种模式来适配。2. 核心器件解析与硬件设计要点2.1 MR25H40CDF的存储结构与访问机制MR25H40CDF的内部存储阵列是512K x 8bit也就是512KB的线性地址空间。跟EEPROM不同它没有页的概念不需要擦除操作直接按字节或者按页写入都行。SPI接口支持模式0和模式3最高时钟频率40MHz。指令集很精简常用的就几条WREN写使能、WRDI写禁止、RDSR读状态寄存器、WRSR写状态寄存器、READ读数据、WRITE写数据。没有扇区擦除、没有块擦除写之前也不需要先擦除这一点比Flash方便太多。状态寄存器里有一个WEL位写使能锁存每次写操作之前必须先发WREN指令把WEL置1写完自动清零。还有一个BP0和BP1位用于写保护可以保护部分地址空间不被写入。我一般会把这两个位设成0不启用写保护因为工业现场的数据写入很频繁写保护反而容易导致数据丢失。如果确实需要保护某些关键参数可以在软件层面做校验而不是依赖硬件写保护。读写时序上MR25H40CDF跟标准SPI Flash兼容但有一点要注意它的写入没有等待周期。Flash写完一个字节后需要等几毫秒让内部编程完成MR25H40CDF不需要写完立即可以读。这一点在掉电保存场景下特别重要MCU检测到掉电信号后可以连续写入多个字节而不需要等待大大缩短了保存时间。2.2 MK20DN128VFM5的SPI外设配置MK20DN128VFM5的SPI模块功能很全支持主从模式、DMA、FIFO。我一般把它配置成主机模式时钟极性CPOL0时钟相位CPHA0也就是SPI模式0。时钟频率设成10MHz到20MHz之间太高了走线不好的话容易出错太低了写入速度跟不上。实际项目中我用15MHz配合DMA传输写入512字节的数据大概需要0.3毫秒左右。SPI的片选信号可以用硬件自动控制也可以软件控制GPIO。我倾向于用硬件片选因为MK20的SPI模块支持自动片选发送数据前自动拉低发送完自动拉高不需要软件干预。这样代码更简洁时序也更稳定。如果非要用GPIO做片选注意在片选拉低和第一个时钟沿之间要留足够的建立时间MR25H40CDF要求至少5纳秒一般GPIO操作都能满足。DMA配置上MK20的SPI支持发送和接收DMA请求。我一般配置两个DMA通道一个用于发送一个用于接收。发送DMA的源地址是数据缓冲区目标地址是SPI数据寄存器接收DMA的源地址是SPI数据寄存器目标地址是接收缓冲区。DMA传输完成后再处理数据这样CPU占用率很低。实测下来用DMA方式读写MR25H40CDFCPU占用率不到5%剩下的算力可以用来跑Modbus协议栈或者做数据处理。2.3 硬件电路设计中的几个关键细节电源去耦这块MR25H40CDF的VDD引脚旁边必须放一个0.1uF的陶瓷电容越近越好。我见过有人把电容放在离芯片两厘米远的地方结果读写偶尔出错查了半天才发现是电源纹波太大。另外MR25H40CDF的工作电流在读写时大概10mA左右待机时只有几微安所以电源的瞬态响应要够快建议在电源入口再并一个10uF的钽电容。SPI走线上如果PCB空间允许尽量让SCK、MOSI、MISO、CS这四根线等长并且远离高频信号线。MR25H40CDF在40MHz下对走线阻抗比较敏感走线太长或者有分支的话信号反射会导致读写错误。我一般把走线控制在5厘米以内如果实在要走长线就在源端串一个22欧姆的电阻做阻抗匹配。CS片选线上拉一个10K电阻到VDD保证在上电初始化之前CS是高电平防止误触发。MK20DN128VFM5的SPI片选引脚在复位后默认是输入状态如果没有上拉CS可能浮空导致MR25H40CDF误进入写模式。这个坑我踩过现场设备偶尔上电后数据被清空查了一周才发现是CS浮空导致的。3. 软件实现与核心代码解析3.1 SPI底层驱动初始化MK20DN128VFM5的SPI初始化分几步先使能SPI模块时钟配置引脚复用功能然后设置SPI控制寄存器。下面是我常用的初始化代码基于NXP的SDK但去掉了多余的封装直接操作寄存器。void SPI_Init(void) { SIM_SCGC6 | SIM_SCGC6_SPI0_MASK; // 使能SPI0时钟 SIM_SCGC5 | SIM_SCGC5_PORTC_MASK; // 使能PORTC时钟 // 配置PTA14为SCKPTA15为MOSIPTA16为MISOPTA17为CS PORTC_PCR4 PORT_PCR_MUX(2); // SCK PORTC_PCR6 PORT_PCR_MUX(2); // MOSI PORTC_PCR7 PORT_PCR_MUX(2); // MISO PORTC_PCR5 PORT_PCR_MUX(2); // CS SPI0_MCR SPI_MCR_MSTR_MASK | SPI_MCR_PCSIS(0x01) | SPI_MCR_CLR_TXF_MASK | SPI_MCR_CLR_RXF_MASK; SPI0_CTAR0 SPI_CTAR_FMSZ(7) | SPI_CTAR_CPOL_MASK | SPI_CTAR_CPHA_MASK | SPI_CTAR_BR(2); // FMSZ7表示8位数据CPOL1CPHA1BR2表示时钟分频 }这里CPOL和CPHA都设成1对应SPI模式3。MR25H40CDF支持模式0和模式3我选模式3是因为在模式3下时钟空闲是高电平片选拉低后第一个时钟沿是下降沿数据在上升沿采样抗干扰能力稍好一些。BR分频系数根据总线时钟来算如果总线时钟是60MHzBR2对应分频4SPI时钟就是15MHz。3.2 MR25H40CDF的读写函数实现写数据之前必须先发WREN指令然后发WRITE指令加地址和数据。地址是24位分三次发送高字节在前。下面这个函数一次写入任意长度的数据用DMA传输。void MRAM_Write(uint32_t addr, uint8_t *data, uint32_t len) { uint8_t cmd[4]; cmd[0] 0x02; // WRITE指令 cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; CS_LOW(); SPI_SendByte(0x06); // WREN CS_HIGH(); CS_LOW(); for (int i 0; i 4; i) { SPI_SendByte(cmd[i]); } for (uint32_t i 0; i len; i) { SPI_SendByte(data[i]); } CS_HIGH(); }读数据更简单发READ指令加地址然后连续读就行。void MRAM_Read(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; cmd[0] 0x03; // READ指令 cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; CS_LOW(); for (int i 0; i 4; i) { SPI_SendByte(cmd[i]); } for (uint32_t i 0; i len; i) { buf[i] SPI_ReceiveByte(); } CS_HIGH(); }注意SPI_SendByte和SPI_ReceiveByte这两个函数发送的时候要等TXF标志置位接收的时候要等RXF标志置位。如果用DMA的话这些等待都可以省掉直接配置DMA传输长度就行。3.3 数据存储格式与校验机制工业现场的数据存储不能只存原始值还得存时间戳、设备ID、校验码。我一般用下面这种数据结构每个记录32字节前4字节是时间戳接下来2字节是设备ID然后24字节是数据区最后2字节是CRC16校验。typedef struct { uint32_t timestamp; uint16_t device_id; uint8_t data[24]; uint16_t crc; } Record_t;写入的时候先算CRC然后一次性写入32字节。读取的时候先读出来再算一遍CRC跟存储的CRC对比不一致就说明数据损坏。MR25H40CDF本身出错的概率极低但SPI传输过程中可能受干扰加CRC校验能过滤掉绝大部分错误。存储地址的管理我用环形缓冲区的方式。MR25H40CDF的512KB空间分成16384个记录每个记录32字节。写指针从0开始写到最后一个记录后回到0继续写覆盖最老的数据。读指针单独维护读取的时候从读指针开始读读到写指针为止。这样不需要文件系统也不需要擦除操作实现简单而且寿命无限。4. 实际应用中的问题排查与经验总结4.1 常见问题速查表问题现象可能原因排查方法解决方案读写数据全为0xFFCS片选未拉低或SPI模式不对用示波器看CS和SCK波形检查CS上拉电阻确认CPOL/CPHA设置写入后读出来不一致电源纹波太大或走线太长测量VDD纹波检查SPI走线长度加去耦电容缩短走线降低SPI时钟偶尔写入失败WREN指令未生效读状态寄存器看WEL位确保WREN后立即发WRITE中间不要插入其他指令上电后数据丢失CS浮空导致误写测量上电时CS电平CS加10K上拉电阻DMA传输不完成DMA通道配置错误检查DMA源地址和目标地址确认SPI的DMA请求已使能4.2 几个容易踩的坑第一个坑是SPI时钟相位搞反。MR25H40CDF支持模式0和模式3但如果你用模式1或模式2数据就会错位。我一开始没注意读出来的数据总是偏移一位查了半天才发现是CPHA设错了。后来养成习惯每次调试新板子先用逻辑分析仪抓一下SPI波形确认模式对了再往下走。第二个坑是写入速度太快导致电源跌落。MR25H40CDF在写入瞬间电流会跳到10mA左右如果电源内阻大的话VDD会瞬间跌落导致写入失败。我在一个项目里用LDO供电LDO的输出电容只有1uF结果连续写入时偶尔出错。后来在VDD引脚旁边加了10uF钽电容问题就解决了。所以电源设计不能只看平均电流要看瞬态电流。第三个坑是地址对齐问题。MR25H40CDF没有页的概念但如果你用DMA传输DMA的源地址和目标地址最好4字节对齐否则在某些MK20的硅版本上会出现传输错误。我一般把数据缓冲区定义成__attribute__((aligned(4)))确保对齐。4.3 提升可靠性的几个实用技巧第一个技巧是写入前先读一遍目标地址的数据跟要写入的数据对比如果相同就跳过写入。虽然MR25H40CDF写入寿命无限但减少不必要的写入可以降低功耗也能减少SPI总线占用时间。这个技巧在数据变化不频繁的场景下特别有用。第二个技巧是掉电检测。MK20DN128VFM5有一个低电压检测模块可以配置成在VDD低于某个阈值时触发中断。我在中断里立即把关键数据写入MR25H40CDF因为MRAM写入不需要等待所以能在电源完全掉下去之前完成保存。实测从检测到掉电到写入完成大概需要200微秒只要电源电容够大完全来得及。第三个技巧是定期做读写测试。工业现场环境恶劣温度、湿度、振动都可能影响存储可靠性。我在固件里加了一个自检功能每次上电后往MR25H40CDF的保留区域写入一组测试数据读出来对比确认存储正常后再开始正式的数据记录。这个自检区域不参与环形缓冲区专门用来做健康检查。5. 方案扩展与不同场景下的适配建议5.1 与Modbus协议栈的配合使用工业现场很多设备用Modbus RTU或者Modbus TCP通信MK20DN128VFM5可以同时跑Modbus协议栈和数据存储任务。我的做法是用一个定时器产生10毫秒的时基在时基中断里处理Modbus报文解析和响应主循环里处理数据存储。MR25H40CDF的SPI读写很快一次32字节的写入不到0.1毫秒完全不会影响Modbus的响应时间。如果数据量比较大比如要存储数控机床的运行状态数据每个采样点有几十个参数那就可以用DMA把数据从SPI直接搬到MR25H40CDFCPU只负责触发DMA和计算CRC。这样即使主频只有50MHz也能轻松处理每秒上千个采样点的存储需求。5.2 多设备数据汇聚场景有些项目需要把多个传感器的数据汇聚到一个节点上存储这时候MK20DN128VFM5的UART、I2C、SPI都可以用来接传感器。我一般用UART接Modbus RTU传感器用I2C接温湿度传感器用SPI接MR25H40CDF。数据汇聚后统一打时间戳然后写入MRAM。MRAM的512KB空间可以存16384条记录如果每条记录32字节按每秒一条记录算可以存4个多小时的数据。如果采样频率更低比如每分钟一条那就能存11天以上。5.3 数据导出与上位机对接MR25H40CDF里的数据最终要导出来给上位机分析。最简单的方式是用UART把数据读出来通过串口转USB发给电脑。如果设备支持以太网也可以用MK20的ENET模块把数据打包成TCP流发出去。我一般在上位机端写一个简单的Python脚本通过串口读取数据解析成CSV文件然后用Excel或者Python做进一步分析。import serial import struct import csv ser serial.Serial(COM3, 115200, timeout1) records [] while True: data ser.read(32) if len(data) 32: break timestamp, device_id struct.unpack(IH, data[:6]) crc struct.unpack(H, data[30:32])[0] # 校验和解析逻辑 records.append([timestamp, device_id] list(data[6:30])) with open(data.csv, w, newline) as f: writer csv.writer(f) writer.writerow([timestamp, device_id] [fdata{i} for i in range(24)]) writer.writerows(records)这个脚本很简单但很实用。实际项目中我会加上CRC校验和错误处理确保导出的数据是完整的。5.4 低功耗场景下的优化如果设备是电池供电的那功耗就很关键。MR25H40CDF的待机电流只有几微安读写时10mA左右但读写时间很短所以平均功耗很低。MK20DN128VFM5可以配置成低功耗模式在不需要采集数据的时候进入VLPR或者LLS模式需要采集的时候用定时器或者外部中断唤醒。我实测过每秒采集一次数据并存储平均电流大概200微安用一节2000mAh的锂电池可以跑一年多。低功耗模式下要注意SPI的引脚状态。MK20进入低功耗后SPI引脚可能会保持最后的状态如果CS一直是低电平MR25H40CDF会一直处于选中状态功耗会增加。所以在进入低功耗之前要把CS拉高并且把SPI模块关掉。唤醒后再重新初始化SPI。6. 个人实操体会与后续扩展方向这个方案我在三个项目里实际用过最长的已经连续运行了两年多没有出现过数据丢失或者存储芯片损坏的情况。MR25H40CDF的可靠性确实比EEPROM和带电池SRAM好太多虽然单价贵一些但省下来的维护成本远超过芯片本身的差价。有一个细节值得注意MR25H40CDF的写入次数虽然没有上限但如果你在同一个地址反复写同一个值理论上磁阻元件的状态不会改变所以也不存在磨损问题。这一点跟Flash完全不同Flash即使写相同的值也会导致电荷注入最终把氧化层击穿。所以用MRAM做数据记录完全不需要做磨损均衡想怎么写就怎么写。后续如果要做功能扩展可以考虑几个方向。一是把MR25H40CDF的容量用满现在只用了512KB如果换成MR25H40CDF的更大容量版本或者用多颗芯片级联可以存更多的数据。二是在MK20上跑一个轻量级的文件系统比如LittleFS或者SPIFFS这样数据管理会更方便但会牺牲一些实时性。三是加一个无线模块比如LoRa或者NB-IoT把存储的数据定期上传到云端实现远程监控。最后分享一个小技巧MR25H40CDF的SPI接口在40MHz下工作很稳定但前提是PCB走线要短。如果你实在需要把MRAM放在离MCU比较远的地方比如10厘米以上那就把SPI时钟降到10MHz以下并且在SCK和MOSI上串22欧姆电阻。我试过在15厘米的排线上跑20MHz误码率大概千分之一降到10MHz后就完全没问题了。所以走线长度和时钟频率要权衡不能只看芯片手册上的最高频率。