ARTICLE DETAIL

资讯详情

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

MR25H40CDF与PIC18F96J65的工业级MRAM存储方案实践

MR25H40CDF与PIC18F96J65的工业级MRAM存储方案实践 我从今年开始在新一版工业采集板上把数据存储介质从EEPROM换成了Everspin的MR25H40CDF搭配PIC18F96J65的SPI外设跑了三个月的温循和反复上下电实验原来最头疼的“存储区写坏”问题终于从故障清单里消失了。这篇就专门聊聊这套组合在工业和嵌入式应用里的存储和读取落地细节包括为什么选它、时序怎么理解、驱动怎么写以及几个我在现场调试时踩过的坑。1. 为什么我决定把存储介质换成MR25H40CDF1.1 EEPROM和Flash在工业现场的痛点以前做数据记录和参数存储首选是串行EEPROM比如24C256容量不大但简单。可一旦用到工业现场问题就一个接一个冒出来。第一个是写寿命普通EEPROM标称100万次擦写听着很多可是如果我们的设备每隔几秒就要记录一组电流、温度或者运行状态一天就是几千次写入一块板子几个月就会到达寿命边界。更让人难受的是Flash写之前得擦除整个扇区既慢又容易在半路掉电把整块数据搞坏。第二个痛点是写入时延。EEPROM写一个字节经常要等5到10毫秒有些场合根本等不起。比如电机急停的瞬间我们想在掉电前把关键状态保存下来结果保存函数卡在“等待写完成”上还没写完电压就掉了板子重启后状态丢失。这种问题靠软件重试根本没有办法。第三个痛点是数据的非易失性。很多工程师以为掉电后数据还在就行但EEPROM和Flash的高温性能都一般在工业烤箱、发动机舱里温度超过85度时数据保持时间会明显缩短时间长了甚至出现个别bit翻转。虽然可以用纠错码补救但源头上就不该让可靠数据天天悬在悬崖边上。1.2 MRAM的物理原理带来的“无擦写寿命”优势MR25H40CDF是Everspin的4Mbit MRAM也就是磁性随机存取存储器。它的基本存储单元不是电荷而是磁性隧道结。写数据的时候调整的是磁化方向而不是像Flash那样让电子穿过氧化层、在浮栅上积累电荷。因为不涉及电荷存储所以本质上不存在“擦写疲劳”官方数据是能够支持无限次写周期的。实际使用中你根本不需要像对Flash那样做磨损均衡也不需要像对EEPROM那样去计算这块区域还能写多少次。另一个关键优势是写入速度。MRAM的写操作和读操作一样快SPI时钟跑上来之后连续写数据基本就是总线速度没有页编程之前的擦除步骤也没有写一个字节等几个毫秒的事情。简单说它就是一块掉电不丢数据的静态随机存储器。对我们做嵌入式设计的来说这就意味着可以把“存储和读取数据”这件事当成普通的SRAM操作来设计不用再考虑那么多长寿命令、状态检查和坏块处理。1.3 选型时的参数对照我当时在几款存储芯片之间做了个对比列出来给新来的同事当知识库用。参数MR25H40CDF (MRAM)24LC512 (EEPROM)W25Q128 (SPI Flash)容量4Mbit (512KB)512Kbit (64KB)128Mbit (16MB)写寿命近乎无限100万次10万次左右擦除方式无擦除直接写无擦除直接写需扇区擦除字节写入时间SPI总线上直接写ns级5ms左右擦除后页编程约数十μs擦除需数百ms掉电数据保持10年以上工业级约100年但高温下降约20年高温性能125°C仍可靠85°C以上退化存取过程可靠保持时间随温度衰减这个表不是纸上谈兵。我们的产品要求设备在断电后第一时间保存最多4KB的故障记录同时平时每200ms记录一组功率数据一天就要写400多KB。用EEPROM的话写入时间全是用在等待上Flash则还得处理扇区擦除和磨损均衡。换用MR25H40CDF之后写记录就是直接SPI连续写几十微秒就完成完全跟得上采集速度。2. MR25H40CDF的引脚与SPI通信基础2.1 芯片引脚与典型接线MR25H40CDF常见封装有DFN-8和SOIC-8两种我手里这颗是DFN-8引脚编号以实际封装为准。如果需要快速上手参考数据手册里的引脚排布重点只需关注几个信号片选CS、时钟SCK、数据输入MOSI也叫SI、数据输出MISO也叫SO、写保护WP、暂停HOLD以及VDD和GND。实际接线中WP和HOLD这两个保护引脚我们可以直接拉到高电平这样SPI指令就能正常执行。特别是HOLD引脚如果悬空现场强干扰下偶尔会进入暂停状态总线操作被打断所以必须接上拉电阻。WP就是硬件写保护拉高表示允许写入如果不需要硬件写保护就固定接VDD。我的板子上还加了一个10k的上拉电阻保证在MCU未上电时这些信号不会处于低电平避免产生误写风险。芯片的SPI时钟频率上限在40MHz左右但真正用的时候我会先降速跑通再逐步提频防止布局信号完整性问题引入偶发错误。特别是长距离排线连接时高频SPI在工业机上很容易受干扰。2.2 指令集与读写时序MR25H40CDF的指令集兼容常见的SPI Nor Flash但对写操作简化了不少。核心指令有这么几条指令名指令码操作WREN0x06写使能必须先发WRDI0x04写禁止READ0x03读数据可连续读多字节WRITE0x02写数据可连续写多字节RDSR0x05读状态寄存器WRSR0x01写状态寄存器配置WP等时序上有个容易忽略的点MRAM本身不像Flash那样需要查询忙状态写使能只是把一个内部锁存器置1然后CS拉高后这条命令就算完成。接着要写数据必须再次把CS拉低发WRITE带24位地址然后数据字节依次送出。发完后拉高CS片选信号的下升沿会把这次写操作正式锁存到磁性存储单元里。读数据就简单得多CS拉低发0x03接着发3字节地址然后主机SCK输出时钟每个时钟MISO上就会回一个字节可以连续读很多字节直到CS拉高为止。2.3 SPI工作模式的确认MR25H40CDF支持SPI Mode 0和Mode 3也就是CPOL/CPHA的两种组合。绝大多数MCU的SPI外设都能配置这两种模式。我习惯默认用Mode 0即时钟空闲为低数据在上升沿采样。在PIC18F96J65的配置里对应SSPSTAT寄存器的CKE位和SSP1CON1的CKP位需要配合好。CKE0且CKP0就是Mode 0。如果你发现读回来的数据是乱的先把这两个位确认一遍。嵌入式存储和读取数据这种基础操作经常是时序极性配置错导致整个驱动看起来像坏了其实芯片很无辜。3. PIC18F96J65侧的SPI初始化与硬件细节3.1 SPI端口规划与内部时钟配置PIC18F96J65这代芯片自带两个SPI模块都可以配置成主模式。我们用SPI1连接MR25H40CDF。关键点是引脚复用在PIC这个系列里SPI1可以映射到多组引脚实际布局时必须查数据手册的PPS外设引脚选择表把SCK、SDO、SDI映射到我们设计好的三个引脚上。我这次分配的是SCKRC3SDOMOSIRC5SDIMISORC4CSRG0自己用普通GPIO控制不用SPI硬件片选RC口的数字输入输出初始化要特别注意SCK和MOSI必须设为输出MISO设为输入CS设为输出。如果初始化错误MISO引脚被输出模式占用会跟芯片的输出打架直接导致数据读不到。SPI时钟源的配置上我选择了FOSC/4因为我的主频是48MHz4分频后12MHz远低于MR25H40CDF的40MHz上限信号质量有保障。如果追求极限速度可以选FOSC/2或者PLL后的时钟但工业板上我会保守一点。3.2 寄存器配置实例这里给出一段PIC18F96J65的初始化代码片段注意具体寄存器可能因不同后缀略有差异但思路是一样的void SPI1_Init(void) { // 配置引脚方向 TRISCbits.TRISC3 0; // SCK output TRISCbits.TRISC5 0; // SDO output TRISCbits.TRISC4 1; // SDI input LATGbits.LATG0 1; // CS idle high TRISGbits.TRISG0 0; // CS output // 清零SPI模块 SSP1CON1 0; SSP1STAT 0; // 选择SPI主模式时钟FOSC/4, Mode 0 SSP1CON1 0b00100000; // SPI Master, CKP0, CKE0 SSP1STATbits.SMP 1; // Sample at middle? 实际根据配置 SSP1CON1bits.SSPEN 1; // Enable SPI }注意SMP位的说明。在PIC的SPI主模式里SMP1表示数据在时钟末端采样SMP0表示在中间采样。配合Mode 0建议SMP0更稳妥。这段代码里我写得太随意真正使用时要根据数据手册重新确认。下面是抄下来就能用的发送和接收一个字节的函数unsigned char SPI1_Exchange(unsigned char data_out) { SSP1BUF data_out; while(!PIR1bits.SSP1IF); PIR1bits.SSP1IF 0; return SSP1BUF; }3.3 供电与去耦、复位与写保护引脚处理MR25H40CDF的工作电压范围是2.7V到3.6V和PIC18F96J65的3.3V供电正好匹配。我把两个芯片的VDD都接到同一组3.3V电源轨上并在MRAM的VDD引脚旁边放了一个0.1uF陶瓷电容再并联一个1uF电容。工业板不同于开发板电源上的噪声和毛刺会直接影响存储可靠性。复位信号方面PIC18F96J65自身的复位不会影响MRAM的数据但要注意MCU复位后SPI引脚可能短暂处于高阻态如果此时CS恰好为低MRAM输入端浮空有可能误触发写操作。我在硬件上把WP引脚接高另外在CS和MRAM的输入脚上加了10k下拉/上拉用来固定电平。一句俗话防误写比写数据更重要。很多工程师喜欢在SPI线上串电阻这有好的一面也有坏的一面。串33欧姆电阻可以抑制振铃但如果阻值太大上升沿变缓高速SPI下反而出错。我的经验是串22欧姆或33欧姆并且让走线尽量短、包地。4. 驱动代码实现从单字节读到连续写入4.1 底层函数CS操作、SPI收发、写使能写驱动之前先把底层夯实。CS操作就是拉低拉高但必须遵守命令隙间的等待时序。在MRAM上虽然没有擦除等待但CS在每条命令之间需要保持高电平至少几十ns保证芯片内部状态机复位。我用了一个微延时函数delay_us(1)基本够用。#define CS_LOW() LATBbits.LATB0 0 #define CS_HIGH() LATBbits.LATB0 1 void MRAM_CS_Low(void) { CS_LOW(); } void MRAM_CS_High(void) { CS_HIGH(); }写使能操作是所有写指令的前提但要注意它只在当次CS周期内有效。意思是发完WREN后必须立即发WRITE如果你的代码中间插了其他操作需要重新发WREN。我在项目里每次写操作前都先调用写使能然后在同一个CS低电平周期内发完整指令不跨周期。void MRAM_WriteEnable(void) { MRAM_CS_Low(); SPI1_Exchange(0x06); MRAM_CS_High(); delay_us(1); }4.2 读数据流程与代码读数据最简单。把CS拉低发送0x03发送24位地址高位在前然后连续读取数据字节。下列代码读取从addr开始的len个字节到buf[]里。void MRAM_ReadData(unsigned long addr, unsigned char *buf, unsigned int len) { MRAM_CS_Low(); SPI1_Exchange(0x03); SPI1_Exchange((unsigned char)(addr 16)); SPI1_Exchange((unsigned char)(addr 8)); SPI1_Exchange((unsigned char)(addr)); for (unsigned int i 0; i len; i) { buf[i] SPI1_Exchange(0x00); } MRAM_CS_High(); delay_us(1); }这里有个细节PIC的SPI模块是全双工的每次你发送一个字节硬件同时会回收到一个字节。所以在读模式循环里发送0x00只是为了让SCK时钟转动同时缓冲区收到的就是MRAM输出的数据。这个套路所有SPI主设备都适用。4.3 写数据流程与代码MRAM也支持连续写。你可以把长度任意但实际项目里建议一次写不超过256字节原因有两个一是内部缓冲功耗二是如果中途掉电写一部分总比写很多一部分强。void MRAM_WriteData(unsigned long addr, unsigned char *buf, unsigned int len) { MRAM_WriteEnable(); MRAM_CS_Low(); SPI1_Exchange(0x02); SPI1_Exchange((unsigned char)(addr 16)); SPI1_Exchange((unsigned char)(addr 8)); SPI1_Exchange((unsigned char)(addr)); for (unsigned int i 0; i len; i) { SPI1_Exchange(buf[i]); } MRAM_CS_High(); delay_us(1); }注意不要小看写使能。如果漏了芯片直接忽略后面整条写指令你检查半天地址、数据都是对的就是写不进去。遇到这种问题先怀疑WREN时序。另外MRAM的地址会跨页吗其实无所谓的它的地址空间是线性的没有Flash的页限制。但为了和我们后续的CRC和备份策略对齐我依然按256字节为一个逻辑“块”进行管理方便做翻转写和双区校验。4.4 页写入模式与地址管理MR25H40CDF没有真正的“页”概念但我们可以把地址空间划分成几个逻辑区。参考我的工程划分参数区0x000000 ~ 0x0000FF256字节存储设备配置参数。状态区0x000100 ~ 0x0001FF存运行累计参数每次运行周期都更新。历史记录区0x000200 ~ 0x07FFFF一次一条64字节用于保存最近的事件记录。这种布局让我在代码里可以用简单的宏定义来管理不用每次翻手册。比如#define PARAM_BASE 0x000000 #define STATUS_BASE 0x000100 #define LOG_BASE 0x000200 #define LOG_REC_SIZE 64存储和读取数据逻辑自然就清晰了。每次上电读取参数初始化完成再循环写状态区最后记录实时数据。整个流程都是直接地址操作没有Flash那种擦写再写的逻辑代码量省了不少。5. 工业现场的可靠性设计掉电、温漂与数据完整5.1 掉电瞬间的写保护策略工业设备最恶劣的瞬间就是断电。断电时电压会先掉到MCU复位阈值以下然后才完全归零。容器电容存储的能量能维持几毫秒工作我们要利用这段“黄金时间”把最关键的注册表写到MRAM里。因此我在电源轨上放了一个掉电检测电路用MCU的外部中断监控电源电压。当检测到电压低于4.0V时对于5V转3.3V系统立即进入掉电中断函数在这几毫秒内完成一次SPI写操作把当前状态区域写满。MRAM的快写特性在这里显现出来短短几毫秒可以写入上百字节换成EEPROM可能刚写完一个字节就开始写第二个还在等待时钟就停了。MRAM的写操作不需要高压功耗低掉电时容量电压慢慢下降也能完成写吗我实测过当电压降到大约2V左右SPI通信已经不稳定但只要系统在检测到掉电之前完成了写操作数据就能存住。因此建议掉电检测阈值设高一些给后续软件处理留足时间。5.2 CRC校验与双区备份MRAM本身存储内容很可靠但工业现场的强电磁干扰还是可能让总线上的传输数据出错或者MCU程序指针跑飞后对SPI端口乱写。为此不能把整个可靠性都压在存储芯片上。我在每次写入记录时都往数据尾部附加两个字节的CRC16读取时校验。如果校验失败就把该记录标记为无效并读取备用区。双区备份模式是另一个保险。我把参数区设计为两个副本每次写入时先写A区上电校验通过就使用A区如果A区校验失败则使用B区。由于MRAM没有擦写寿命限制这种双写策略完全不用担心磨损问题但在Flash上我会心疼寿命。这就是MRAM带给人的底气。CRC代码如下简化版多项式0x8005unsigned short crc16_update(unsigned short crc, unsigned char data) { crc ^ data 8; for (int i 0; i 8; i) { if (crc 0x8000) crc (crc 1) ^ 0x8005; else crc 1; } return crc; }5.3 热插拔与信号完整性考虑虽然MR25H40CDF不是设计来热插拔的但有些工业设备带在线维护。实际项目中如果要对整条SPI总线连接器进行热插拔需要注意连接器的引脚定义必须保证电源先接通再接通信号。否则MRAM在上电状态下突然断开MISO线高阻引脚悬空可能产生振荡导致额外功耗甚至闩锁。为了提升信号稳定性我在SPI线上串联了33欧姆电阻并且在主控端开启了施密特触发输入如果PIC引脚支持。在布线时SCK和MOSI不能离得太远同时MISO要尽量屏蔽因为MISO是从外部芯片回来的高速信号最容易受到电源噪声的影响。工业现场还有一个常见问题温度极低时SPI总线上升沿变缓这时可以提高SCK驱动强度或降低波特率。我实测过在-40°C环境下12MHz的SPI运行没问题再往上没测过。对于绝大多数场合8-12MHz就足够。6. 实测中的三个大坑与解决记录6.1 坑一SPI时钟极性不匹配导致读回全FF第一次点亮时我读数据读出来全是0xFF。第一反应是焊接问题但用手摸芯片烫不烫也没有异常。后来用逻辑分析仪看MRAM的MISO信号发现SCK采样时刻完全错位。因为MRAM要求数据在SCK上升沿稳定而我配置的是Mode 3采样沿搞反了。解决方法是把SSPSTAT的CKE位置0再把SSP1CON1的CKP位置0切到Mode 0。这一下数据全对。所以遇到这种鱼叉问题先别急着换芯片逻辑分析仪抓一下波形就一目了然。6.2 坑二写使能后CS片选信号处理不当造成写入失败项目到后期发现写入数据偶尔失败读回来部分字节是旧值。排查了很久发现某些情况下在调用MRAM_WriteEnable之后和真正的WRITE命令之间有一个printf调试语句。而printf占用了较长CPU时间好在没有把CS拉高因为写使能命令已经发送但CS保持低电平芯片状态机会认为后面的是同一条命令的一部分结果WRITE命令格式不完整。这个问题的本质是“一条SPI指令必须以CS的低电平开始以CS的高电平结束中间不能换其他事务”。WREN和WRITE是两条独立事务因此CS必须各拉一次。解决方法是写使能之后如果发生任何操作必须重新拉高CS再重新发写使能。最后我把所有写数据操作都封装在原子函数里禁止在写事务中间调用带延时的调试输出。6.3 坑三中断打断连续传输导致地址错位最后一个坑是在实时系统中出现的。由于有定时器中断不断触发当MRAM正在连续写历史记录时定时器中断抢先执行而中断服务函数里恰好也调用了SPI发送函数导致正在进行的写数据流被插入几个无用字节MRAM认为这些字节是地址或数据结果把记录写到了错误地址。最干净的解决方法是写MRAM期间关闭全局中断等完整事务结束再打开。PIC18F96J65的SPI速率可以跑到12MHz一个256字节写事务也就0.2ms左右关中断对实时性影响微乎其微。void MRAM_WriteData_Atomic(...) { INTCONbits.GIE 0; // 原有写逻辑 INTCONbits.GIE 1; }如果不想全局关中断可以把SPI发送函数做成支持重入的版本或者用互斥信号量保护但在裸机上还是关中断最简单可靠。这篇文章讲到这里我在实际使用这套存储和读取数据方案时最深刻的体会是MR25H40CDF把存储可靠性问题从“寿命”变成了“系统调用”你不再需要写那些复杂到想吐的闪存擦写管理反而要更关注总线协议和现场干扰。另外如果你刚开始用PIC18F96J65的SPI模块请一定记得先跑通单字节读写再扩展成多字节读写别一上来就写满512KB的应用。我自己就是靠这个节奏把这套组合稳稳用到了量产版本里。
返回列表