ARTICLE DETAIL

资讯详情

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

MRAM+PIC单片机:工业级数据存储方案设计与实现

MRAM+PIC单片机:工业级数据存储方案设计与实现 之前在调试一台工业设备的数据记录功能现场条件挺苛刻设备要长期运行随时要断电不能丢数据还要求写操作足够快。一开始想的方案是用片上EEPROM加外部Flash结果发现EEPROM容量只有1K字节放不下历史曲线数据外部Flash容量倒是够但擦写寿命和扇区擦除机制又让人头疼尤其是记录次数一多坏块管理、磨损均衡全都得自己写工作量直接翻倍。后来换成了磁阻存储器方案Everspin的MR25H40CDF主控用Microchip的PIC18F66K40。这颗MRAM是4Mbit SPI接口的非易失性存储写一个字节和读RAM一样快不用先擦除寿命号称10的12次方次写基本不用考虑磨损问题。配合PIC18F66K40自带的MSSP模块做SPI主站整个数据存储和读取链路变得非常干净。这篇就聊聊我是怎么把这两颗芯片组合起来做工业级数据存储的包括硬件接线、固件实现、调试踩坑以及一些长时间运行后才看得出来的细节。1. 整体设计与器件选型思路1.1 为什么不用Flash和EEPROM偏偏选MRAM做嵌入式存储大家常用的无非是三类Flash、EEPROM、SRAM加电池。我做这个项目的时候先把需求列了一下数据量大概256KB左右每秒钟要记录一组工况数据掉电不能丢理论上设备生命周期内要写入上亿次字节还不能因为写操作阻塞实时采集。拿Flash来说W25Q系列虽然容量大但是写之前必须先擦除扇区。就算只改一个字节运气不好也得先把4KB整个扇区读出来改好再擦再写最坏情况下一次“写”操作耗时几十毫秒。这个延迟在工业实时控制里非常不友好。更麻烦的是Flash擦写寿命通常在一万到十万次之间一旦频繁覆盖同一个扇区坏块问题会很快暴露必须引入磨损均衡。做磨损均衡本身不是不行但为了简单的数据记录去维护一套FTL逻辑开发成本太高。再看EEPROM比如AT24C25632KB容量写字节要等5到10毫秒容量也不够。而且EEPROM同样有写寿命限制一般是百万次级别。虽然比Flash强但持续高频写入还是会有心理压力。MRAM就不一样了。MR25H40CDF本质上是一个SPI接口的4Mbit512KB非易失性存储器内部用磁隧道结存数据断电不丢失写操作不需要擦除也不需要等待。它和普通SRAM一样可以随便写不管写多少次都不会磨损。也就是说你可以把它当一块掉电不丢数据的RAM来用直接把变量映射到MRAM地址上都没有问题。官方标称数据保持20年以上写耐久度10的12次方工业级温度范围-40℃到125℃这些参数放在工业场合非常合适。1.2 PIC18F66K40的SPI资源能不能扛得住选PIC18F66K40倒不是因为它有多惊艳而是这个项目里它的资源刚刚好。这颗芯片属于Microchip的K40增强型中端系列工作电压1.8V到5.5V内部有64KB Flash、4KB SRAM、1KB数据EEPROM主频可以跑到64MHz。最关键是自带MSSP模块既可以做I2C也可以做SPI硬件上天然支持把MRAM挂在SPI总线上。如果用普通GPIO去模拟SPI也能工作但速度上不去还容易在时序上出问题。MSSP模块做SPI主站时能自动管理时钟和数据线的移位移位CPU只要往SSPBUF寄存器里丢数据硬件就会按设定好的极性把数据推出去。MR25H40CDF标称SPI时钟可以到40MHz实际上PIC18F66K40在64MHz主频下SPI时钟可以配置出合适的分频比我们在项目里最终用的是4MHz SPI频率兼顾稳定性和速率。另外还要看引脚资源。MR25H40CDF只需要6根有效信号线SCK、MOSI、MISO、CS#、HOLD#、WP#。PIC18F66K40的MSSP模块占用RC3SCK、RC4SDI、RC5SDOCS#随便选一个普通GPIO整个占用的IO引脚非常少后续扩展传感器接口完全够用。2. 硬件接线与信号完整性细节2.1 DFN8封装引脚映射与最小电路MR25H40CDF用的是8引脚DFN封装尺寸约5×6毫米别看着小引脚间距只有0.65mm手工焊接需要一点技巧。它的引脚定义是1脚CS#2脚SO也就是MISO3脚WP#4脚GND5脚SI即MOSI6脚SCLK7脚HOLD#8脚VDD。我这边实际接线是这样PIC18F66K40的RC5SDO接MR25H40CDF的SI数据从主控发给MRAMPIC18F66K40的RC4SDI接MR25H40CDF的SO数据从MRAM返回主控PIC18F66K40的RC3SCK接MR25H40CDF的SCLK提供SPI时钟随便选了一个普通GPIO比如RD0接CS#片选低有效MR25H40CDF的WP#引脚接VCC因为我们在固件里不打算用硬件写保护功能直接拉高最省事HOLD#引脚接VCC防止总线被其他设备占用时误暂停VDD接3.3VVSS接GND两个电源引脚旁边各放一个100nF陶瓷电容另外再在电源入口放一个10μF钽电容做低频滤波有个小地方需要注意PIC18F66K40是5V供电的典型器件但MR25H40CDF只支持3.3V供电。所以系统里必须做电平转换。我这里的做法是把PIC18F66K40的IO配置成开漏输出加上上拉电阻到3.3V或者外部加一片74LVC4245电平转换芯片。如果是单板3.3V供电PIC18F66K40可以直接在3.3V下运行那就不需要额外的转换了最省心。我推荐后者直接把MCU也跑3.3V少一级转换就少一个故障点。2.2 电源、去耦与高速布线心得工业应用和实验室还有个很大区别就是电源波动。MR25H40CDF的供电范围是2.7V到3.6V如果现场有电机启停、接触器吸合电源波动很容易超过这个范围。我一开始图省事直接从DC-DC输出引3.3V给整个板子供电结果在打耐压测试的时候发现偶尔会出现读写数据错误。后来查明白了电机启动瞬间压降到2.6V以下MRAM进入欠压状态。解决办法是在3.3V电源轨上加一个低压差稳压器后面再并一个470μF的电解电容和若干个陶瓷电容形成一个RC滤波结构保证瞬间压降不会击穿MRAM的工作电压下限。另外MRAM附近的PCB走线尽量短而粗SCK、SI、SO三根线不要穿过大电流区域避免磁场干扰磁存储单元。这里多说一句MRAM虽然靠磁畴存数据但普通数据线感应出来的噪声磁场强度还不至于破坏存储内容真正影响的是电源和逻辑电平所以不要过度担心电磁干扰会撕掉数据工业产品最该关注的是干净电源和正确时序。还有一个小坑DFN8底部有一个裸露焊盘规格书里通常建议接地。一开始我为了省事没处理结果模块工作温度偏高后来把底部焊盘接到GND散热和地回路都好了很多。手工焊接时底部焊盘可以开一个大点的过孔从背面灌锡能焊得很结实。3. 固件实现初始化、读写函数与状态机3.1 初始化SPI主站模块PIC18F66K40的MSSP模块配置SPI主模式很简单。主频我设定内部振荡器32MHzSPI时钟4MHz所以需要分频8。配置过程是这样void spi_init_master(void) { // 引脚复用设置为SPI功能 TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC4 1; // SDI 输入 TRISCbits.TRISC5 0; // SDO 输出 TRISDbits.TRISD0 0; // CS# 输出 SSP1CON1 0b00101000; // SPI使能主模式空闲时钟为低电平 SSP1CON1bits.SSPEN 1; SSP1CON1bits.CKP 0; // 空闲时SCK为低 SSP1STATbits.CKE 0; // 数据在SCK上升沿发送符合MRAM模式0要求 SSP1CON1bits.SSPM 0b0010; // FOSC/8 4MHz }这里最关键的是时钟极性配置。MR25H40CDF只支持SPI模式0也就是CPOL0CPHA0。意味着SCK空闲为低电平数据在第一个边沿被采样。PIC18F66K40里面CKP对应CPOLCKE对应CPHA的反向逻辑。我调试的时候曾经把CKE设成了1结果读回来的数据全乱了后来翻数据手册才意识到CKE和CPHA之间是相反的关系CKE0表示在时钟边沿前改变数据正好对应SPI Mode 0。这一点强烈建议在看代码时对应的手册页码旁边写上红字。CS#的管理用普通GPIO操作就行。注意SPI协议中每次事务前CS#拉低事务结束后CS#拉高不能一直保持低电平。MR25H40CDF的操作时序里CS#上升沿是用来锁存命令和数据的读操作时CS#拉高等同于结束事务。3.2 核心读写函数实现MR25H40CDF的命令集非常标准命令操作码说明WREN0x06设置写使能锁存器WRDI0x04清除写使能锁存器RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03从指定地址读数据WRITE0x02从指定地址写数据写数据之前必须先发WREN命令否则MRAM会静默拒绝写入状态寄存器的WEL位也一直是0。这个设计是为了避免总线噪声误写。读操作不需要WREN直接发READ命令。下面是我常用的两个函数直接放到工程里就能用unsigned char spi_transfer(unsigned char byte) { SSP1BUF byte; while (!PIR1bits.SSP1IF); PIR1bits.SSP1IF 0; return SSP1BUF; } void mram_write_byte(unsigned long addr, unsigned char data) { CS_LOW(); spi_transfer(0x06); // WREN CS_HIGH(); CS_LOW(); spi_transfer(0x02); // WRITE spi_transfer((unsigned char)(addr 16)); spi_transfer((unsigned char)(addr 8)); spi_transfer((unsigned char)(addr)); spi_transfer(data); CS_HIGH(); } unsigned char mram_read_byte(unsigned long addr) { unsigned char val; CS_LOW(); spi_transfer(0x03); // READ spi_transfer((unsigned char)(addr 16)); spi_transfer((unsigned char)(addr 8)); spi_transfer((unsigned char)(addr)); val spi_transfer(0x00); CS_HIGH(); return val; }地址是24位的最高字节实际用不到但命令格式里必须发送完整的24位地址。我刚开始做的时候只发了两个地址字节结果读写一直错位后来才反应过来是地址长度不对。MR25H40CDF是512KB容量地址范围0x00000到0x7FFFF0x80000以上访问会回绕也可以利用这一点做一些环形缓冲区测试。另外这个写函数是单字节写法。如果连续写多个字节MRAM支持像Flash一样的页面写吗实际上SPI MRAM可以把WRITE命令后紧跟的数据流连续写入不需要重新发地址时序上只要CS#不拉高每来一个字节就自动写到下一个递增地址。所以批量写的时候会更快我在工程里做了个优化连续写入64字节时只在开头发一次WREN和WRITE命令后面直接循环spi_transfer发送数据块性能提升明显。要注意的是CS#拉高的时候最后一个字节必须已经进入接收移位寄存器并锁存否则会丢失末尾数据。3.3 状态寄存器与写保护逻辑MR25H40CDF的状态寄存器里有一个WEL位写使能锁存和BP位、WPEN、XD位等等。大多数情况下不需要关心BP位因为上电默认是写保护关闭状态。但如果你的产品要防止误写可以在初始化时把状态寄存器设置为指定块保护然后在写操作之前先解除保护。读状态寄存器unsigned char mram_read_status(void) { unsigned char status; CS_LOW(); spi_transfer(0x05); status spi_transfer(0x00); CS_HIGH(); return status; }我通常在系统上电后读一次状态寄存器把结果存到一个全局变量里做日志时能看到MRAM是否处于保护模式。还有一点如果外部的WP#引脚接地而状态寄存器里的SRWD位被置1那么MRAM就会强制进入硬件保护模式写使能命令都无效。所以前面硬件设计里我提到把WP#拉高目的就是让固件能用WREN命令控制写入不至于把自己锁在外面。4. 现场调试实录与典型问题排查4.1 CS片选时序不规范导致写数据丢失这是我当时调试遇到最诡异的一个问题读出来的数据前面几十个字节都正常越往后错得越离谱。用示波器抓了SCK和CS#的波形发现每次写操作结束CS#拉高的时机比SCK最后一个上升沿晚了不到几十纳秒看起来好像没事但MRAM内部对CS#的建立时间有要求最后一个数据位采样完成后CS#至少要保持低电平一段时间然后拉高完成锁存。虽然官方标称的最大工作频率下这个时间窗口只有几纳秒但在恶劣的工业环境下IO口驱动能力不足或线路寄生电容过大就可能把这个窗口吃掉。解决办法是写操作结束前加两个NOP延时或者干脆把SCK频率从4MHz降到2MHz。我后来把主站的SPI时钟设置为2MHz用逻辑分析仪重新抓波形再也没有丢过数据。别以为把频率调低就是“性能下降”对于工业数据记录应用几十KB每秒的写入速度完全够用稳定压倒一切。4.2 读回数据全是0xFF像是MRAM没工作这个现象非常典型多半是MRAM根本没有进入SPI接收状态或者CS#没有正确拉低。有一次我把CS#接到了PIC18F66K40的RD0引脚初始化代码里也写了TRISDbits.TRISD00但最后发现这个引脚被复用成了模拟功能需要额外关掉ADCON1里的对应ANCON位。PIC18F66K40的引脚复用比较复杂数字IO和模拟输入复用同一个引脚时必须确保ANSEL寄存器对应位清零。否则引脚被当作模拟输入输出驱动器是断开的CS#信号根本送不出去。另外一个原因是WP#或HOLD#悬空。如果HOLD#悬空MRAM可能在读数据过程中进入暂停状态SO被置为高阻读出来的数据就是0xFF。我在第一次手工样板时漏接了HOLD#整整排查了两小时才想明白HOLD#低有效悬空状态下内部逻辑不确定很容易间歇性停摆。所以强烈建议把WP#和HOLD#都固定接到VDD不要偷懒。4.3 用逻辑分析仪快速定位SPI通信问题做工业嵌入式开发逻辑分析仪是必备神器。我习惯用Saleae逻辑分析仪或者国内的一些8通道USB逻辑分析仪采样率20MHz以上就能抓SPI信号。抓的时候直接连SCK、SI、SO、CS#四根线触发条件设为CS#下降沿然后看数据内容跟预期是否一致。SPI调试有一个通用经验先看CS#是不是干净地拉低拉高再看SCK有没有毛刺最后再看SO线上的数据窗口是否稳定。如果看到SO线上有毛刺或者数据位偏移明显那一般不是MRAM的问题而是主控的SCK驱动能力不够或者PCB线太长。工业长线场景里如果SCK频率超过10MHz还得考虑加终端匹配电阻但我实际上项目里用2MHz通信距离不超过10厘米板内走线根本不需要匹配。5. 工业环境下的数据可靠性和长期运行维护5.1 环形缓冲区与双备份存储策略当MRAM当作非易失RAM用时它的灵活度就体现出来了。我在项目里做了一个环形数据缓冲区把最近512KB的实时数据不断覆盖保存。以前用Flash做环形缓冲最害怕磨坏地址0的扇区用了MRAM之后没有这个顾虑直接定义一个结构体数组映射到MRAM地址上写满就回绕简单粗暴。地址规划上我分了三个区域区域A存放设备参数和校准参数区域B作为数据记录缓冲区域C存放系统日志。参数区写操作很频繁比如用户改一次参数就写一次MRAM理论上寿命足够用。但为了保险参数区还是做了双备份一个参数写两个地址读的时候比较校验字不一致就以备份为主。这个策略不是MRAM独有的但配合MRAM“读写速度一致、无擦除等待”的特性实现起来很轻松。有一点值得注意虽然MRAM的写寿命极高但工业设备长时间运行后依然可能出现单比特故障。所以我在每个记录项后面追加了一个CRC16校验。读取历史曲线时先校验CRC校验不过就标记该条数据无效而不是直接把错误数据发给上位机。用MRAM做数据存储相比Flash有个天然优势就是读到坏数据的概率极低但低不等于零设计冗余校验仍然是专业做法。5.2 异常掉电与状态一致性保护工业设备最怕的就是写数据写到一半突然断电。Flash掉电容易丢数据甚至丢整个扇区MRAM虽然数据不会丢但事务完整性需要软件保证。我的思路是在MRAM里给每个记录块加一个“写入状态”标志。写入流程是先把“状态标志”置为“正在写入”写入实际数据把“状态标志”置为“写入完成”上电启动时扫描所有记录块如果发现“正在写入”状态说明上次写入被掉电打断扔掉这个半截数据即可。这个方式配合MRAM的随机读写特性实现起来很干净。如果换成Flash掉电时可能连标志位都写着写着就变了必须做冗余标志设计。还有一个不太容易注意到的问题MRAM虽然是非易失的但它对过压和欠压很敏感。工业现场突然断电瞬间电源电压不是立刻到零而是慢慢下降如果MRAM电源在2V到2.5V之间时收到一个写命令内部逻辑可能进入不确定状态。解决方法是利用PIC18F66K40的BOR掉电复位功能设置欠压复位约为2.1V一旦VDD低于这个值MCU立刻复位程序在低电压下不会再执行任何写操作。同时把MRAM的VDD也接到这个受监控的电源域形成联动保护。6. 项目实测数据与个人经验总结这个方案做下来整板功耗很低PIC18F66K40在32MHz下跑MRAM的待机电流只有微安级别整体整机功耗不到20mA。我做了持续72小时的写入测试每100ms写一条64字节的记录累计写入约2百万条记录CRC校验错误数为零。也没有发现读写速度变慢的情况。最后说几个我实际用下来觉得值得分享的点第一SPI时钟不要一味追求高。MR25H40CDF标称支持40MHz但PIC18F66K40的MSSP模块在高速下信号质量下降明显工业应用里2MHz到4MHz最稳妥。第二MRAM不能像EEPROM那样等“写完”再去做别的。写指令发出后最后一个字节的CS#上升沿就是写入完成的时刻不需要任何等待时间。这个特性在实时系统里特别好用你可以把写操作嵌入到高速采样循环中。第三焊接DFN封装时底部焊盘一定要处理好。我的做法是先在PCB焊盘上刷适量锡膏放上芯片后用热风枪360度加热看到焊锡自动爬升到引脚边缘就停止。如果手头只有烙铁可以先用烙铁在四个角落固定芯片然后拖焊其他引脚最后从侧面观察有没有虚焊。总的来说MR25H40CDF加PIC18F66K40这套组合解决了我一直以来的痛点既要大容量非易失存储又要接近RAM的写入体验还要保证工业环境下的可靠性。如果你的应用也是数据记录、参数备份、日志存储这类需求而且主控正好是PIC18系列这套方案可以直接抄作业。
返回列表