我最早接触MR25H40CDF这颗芯片是因为一个工业数据采集项目里需要频繁记录传感器校准值和运行日志。当时用的MCU是TM4C123GH6PZ内部Flash虽然够存代码但拿来做数据存储太憋屈了——写一次要按页擦除日志写多了还担心寿命耗尽。后来把MR25H40CDF挂在SPI总线上整套方案才真正顺了。这篇就把我实际的接线方式、驱动代码和踩过的一些细节问题整理出来给需要在嵌入式项目里做非易失存储的朋友一个参考。MR25H40CDF是Everspin的4Mb串行MRAM接口上和我们熟悉的SPI NOR Flash几乎一样但内部存储原理完全不同。它用磁阻状态保存数据写入速度接近SRAM又没有Flash那种擦写块限制和写寿命问题。如果你之前只在Nor Flash和EEPROM之间纠结过这颗芯片很值得加进选项清单里。对128KB到512KB量级的工业数据存储需求来说它算是一个很干净的解决方案。1. 为什么在TM4C123GH6PZ上MRAM比Flash和EEPROM更适合存经常改的数据先聊一个容易被忽略的前提。TM4C123GH6PZ是TI Tiva C系列里的Cortex-M4F主频80MHz片上有256KB Flash和32KB SRAM还有2KB EEPROM。很多人一看到自带的EEPROM就以为够了但实际做项目时会发现2KB太小存几个配方表和故障记录就满了。而片上Flash虽然容量大却有两个硬伤一是擦除按扇区来最小1KB改一个字节也要先把整个扇区搬走再擦再写麻烦且耗时二是写寿命和擦写时间摆在那里频繁记录日志时很容易把扇区写穿。MR25H40CDF就不一样。它是磁阻式随机存取存储器不是电荷存储所以没有擦除后再写的概念任何一个地址都可以直接用写命令覆盖。写入一个字节和写满整个芯片耗时差别不大也没有写后等待时间。这一条对工业控制里的日志记录、计数器、参数在线修改这类场景是很关键的。再对比一下EEPROM。SPI接口的EEPROM虽然也能单字节覆盖写但密度做到4Mb的不多就算有价格也偏高。MRAM在4Mb这个容量点的价格已经比较友好加上温度范围覆盖-40到105℃工业现场用起来心里踏实。另外它的读接口是高速SPI和TM4C123GH6PZ的SSI控制器对接非常顺时钟跑到10M或20M都没压力代码也好写。还有一点容易忽视的是掉电写操作。工业设备经常在掉电瞬间要保存现场状态用Flash的话掉电前那几百微秒往往不够完成一扇区的擦写。MRAM因为不需要擦除直接发写命令几微秒就完成了配合TM4C123GH6PZ的低功耗模式和电容储能成功率会高很多。2. 硬件连接TM4C123GH6PZ与MR25H40CDF的SPI引脚规划MR25H40CDF是标准的8脚SOIC封装引脚包括CS#、SCK、SI、SO、VDD、GND、WP#还有一个NC脚。SPI接口支持Mode 0和Mode 3和TM4C123的SSI外设很好配合。我用的连接是系统电源3.3VSSI0那组引脚作为主通道。引脚对应关系如下TM4C123GH6PZ引脚功能MR25H40CDF引脚说明PA2SSI0ClkSCKSPI时钟10MHzPA4SSI0RxSO芯片输出接主控制器MISOPA5SSI0TxSI主控制器输出接芯片输入PD7普通GPIOCS片选CS#软件控制拉低有效3.3V电源VDD并联0.1uF去耦电容GND地GND完整铺地连接3.3V写保护关闭WP#直接接高电平不使用写保护关于片选我建议用普通GPIO控制而不是SSI的FSS自动片选。原因很简单MR25H40CDF的一个完整读写事务需要CS#在整个命令、地址、数据期间一直保持低电平而TM4C123的SSI如果开了FSS自动模式它会在每发送一个字节后自动翻高这会导致MRAM事务错乱。所以我把FSS功能丢弃直接用另一个GPIO口拉CS。这个改动是我第一次调通SPI时反复怀疑接线后得出的结论后面驱动代码也都是按手工拉CS来写的。电源方面MR25H40CDF的VDD工作范围是2.7V到3.6V直接接3.3V没问题。但要注意去耦电容我习惯在VDD和GND之间放一个0.1uF陶瓷电容最好靠近引脚高频噪声会少很多。如果板子上还有电机、继电器这类干扰源建议在VDD入口再并一个4.7uF或10uF的钽电容防止供电跌落导致芯片进入不确定状态。WP#引脚是硬件写保护低电平时状态寄存器的写入会被禁止。我不需要用状态寄存器的持久化保护所以直接接了高电平。如果你的项目需要防止运行中的误改写可以把WP#接到MCU的一个GPIO平时输出高电平仅在需要解锁时拉低这算是一个额外的保险。3. SSI驱动与MR25H40CDF命令集初始化到读写完整实现软件这部分我用的是TivaWare固件库它把SSI的寄存器操作封装得很干净。一开始的关键是把SSI0配置成SPI主机、Mode 0、8位数据长度。参考System Clock是80MHz我给的是10MHz位速率这个速率对PCB普通走线和排线连接都很稳。初始化代码大致是这样的#include tm4c123gh6pm.h #include driverlib/sysctl.h #include driverlib/gpio.h #include driverlib/ssi.h #include driverlib/pin_map.h #define MRAM_CS_GPIO_PERIPH SYSCTL_PERIPH_GPIOD #define MRAM_CS_GPIO_BASE GPIO_PORTD_BASE #define MRAM_CS_PIN GPIO_PIN_7 void MRAM_InitGPIO(void) { SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0); SysCtlPeripheralEnable(MRAM_CS_GPIO_PERIPH); GPIOPinConfigure(GPIO_PA2_SSI0CLK); GPIOPinConfigure(GPIO_PA4_SSI0RX); GPIOPinConfigure(GPIO_PA5_SSI0TX); GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5); GPIOPinTypeGPIOOutput(MRAM_CS_GPIO_BASE, MRAM_CS_PIN); GPIOPinWrite(MRAM_CS_GPIO_BASE, MRAM_CS_PIN, MRAM_CS_PIN); } void MRAM_InitSSI(void) { SSIConfigSetExpClk(SSI0_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 10000000, 8); SSIEnable(SSI0_BASE); }注意SysCtlClockSet要先在整个系统初始化里设置成80MHzSysCtlClockGet()才能返回正确的时钟频率。SSI的位速率是SSIConfigSetExpClk函数自动计算分频的不用我们手工处理CPSDVSR和SCR寄存器这对减少出错的概率很有帮助。下面说命令集。MR25H40CDF的SPI寄存器命令和普通SPI Flash很接近读数据是0x03写数据是0x02写使能是0x06读状态寄存器是0x05。地址是24位的因为4Mb除以8位等于512KB地址空间从0x00000到0x7FFFF三字节地址刚刚好覆盖。任何写操作之前必须发WREN命令否则芯片会忽略写数据命令。这个机制有点像某些EEPROM的写保护锁但区别在于它不是一次性的而是每次写入前都要重新使能。下面是我用的两个核心函数一个读一个写void MRAM_CS_Low(void) { GPIOPinWrite(MRAM_CS_GPIO_BASE, MRAM_CS_PIN, 0); } void MRAM_CS_High(void) { GPIOPinWrite(MRAM_CS_GPIO_BASE, MRAM_CS_PIN, MRAM_CS_PIN); } static void MRAM_SendByte(uint8_t data) { SSIDataPut(SSI0_BASE, data); while(SSIBusy(SSI0_BASE)); } static uint8_t MRAM_RecvByte(void) { uint32_t rx; SSIDataGet(SSI0_BASE, rx); return (uint8_t)rx; } void MRAM_WriteEnable(void) { MRAM_CS_Low(); MRAM_SendByte(0x06); MRAM_CS_High(); } void MRAM_Read(uint32_t addr, uint8_t *buf, uint32_t len) { uint32_t i; MRAM_CS_Low(); MRAM_SendByte(0x03); MRAM_SendByte((addr 16) 0xFF); MRAM_SendByte((addr 8) 0xFF); MRAM_SendByte(addr 0xFF); for(i 0; i len; i) { buf[i] MRAM_RecvByte(); } MRAM_CS_High(); } void MRAM_Write(uint32_t addr, const uint8_t *buf, uint32_t len) { uint32_t i; MRAM_WriteEnable(); MRAM_CS_Low(); MRAM_SendByte(0x02); MRAM_SendByte((addr 16) 0xFF); MRAM_SendByte((addr 8) 0xFF); MRAM_SendByte(addr 0xFF); for(i 0; i len; i) { MRAM_SendByte(buf[i]); } MRAM_CS_High(); }读操作里MRAM_RecvByte这个写法要注意SSI是全双工接口每次读必须同时发起一次写操作来产生时钟所以读的时候要先确认RX FIFO里有数据。上面的代码里我简化了实际工程上你发完地址字节后要连续调用SSIDataPut填充0x00或0xFF再SSIDataGet取数。推荐的方法是发送和接收独立处理每收到一个字节前先启动一次伪写。写操作有个值得注意的细节MRAM写入不需要页对齐也不需要等待内部编程完成。但一次写事务的长度不要超过你的实际数据量太大比如512KB全芯片一次性写入会让CS#低电平保持时间很长期间如果主控发生看门狗复位总线状态会卡住。尽量按实际数据块分次写每包256字节以内是比较稳妥的做法。4. 掉电保存、页跨越与状态寄存器现场问题排查实录跑通基本读写之后真正麻烦的是各种边界场景。我整理一下自己遇到过的几个问题按排查链路写出来你可以对照自己的代码检查。第一个问题是写完回读部分数据不对。一开始我怀疑芯片坏了后来用逻辑分析仪抓波形才发现问题出在发送完地址之后立即进入连续发送数据的环节时SSI的TX FIFO出现了一次空档导致时钟短暂停顿。MRAM本身对时序是有容限的但CS#在整个事务期间是连续拉低的如果时钟停顿时间过长芯片状态机会误判。解决方法是连续发送时先预填一个字节到TX FIFO再启动总线确保数据流不间断。工程上也可以用DMA发送效果更好。第二个问题是读出来的数据全部是0xFF。这种情况一般不是芯片坏了而是CS#没拉低或者电源有问题。排查链路先查VDD是不是3.3V且纹波在100mV以内再查CS#有没有被外部上拉电阻拖住最后查SSI是不是被配置成了3线模式但板子上MISO没接对。另外注意MR25H40CDF的SI和SO不能接反SI是主发从收SO是主收从发接反了就是全0xFF或全0x00。第三个问题更隐蔽是掉电时数据被破坏。现象是系统正常断电之后再上电读某个区域的数据发现偶发的一个字节变了。这不是MRAM的特性缺陷而是掉电瞬间MCU的GPIO状态乱跳。如果CS#在主控掉电过程中变成了低电平同时SCK线上又有残余时钟脉冲MRAM可能把随机数据误写入当前地址。解决办法有两个方向一是硬件上给CS#加一个100kΩ的上拉电阻让它在掉电时保持高电平二是让MRAM的VDD和MCU的VDD由同一路电源控制确保一起掉电并在主控检测到掉电后立即把CS#拉高。还有可以在软件里给重要数据区块加上CRC校验和双副本上电时先校验再使用这个我在工业项目里是必做的。关于状态寄存器MR25H40CDF的Status Register里有一个WEL位当WREN命令成功执行后WEL会置1完成写命令后自动清0。我用来做过自检每次写操作前读一下Status Register确认WEL1再发起写数据命令。这样能提前发现总线异常避免闷头写半天实际一个字都没写进去。读取Status Register代码如下uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_Low(); MRAM_SendByte(0x05); status MRAM_RecvByte(); MRAM_CS_High(); return status; }注意读Status Register时也要用伪写产生时钟MRAM_SendByte和MRAM_RecvByte的配合要到位。我在调试时卡了将近半天就是因为读Status Register的函数里漏了发伪字节导致时钟没产生状态永远是0x00。5. 在真实工业场景中组织数据日志缓冲区、配置镜像和镜像校验策略硬件驱动稳定之后数据怎么组织就是另一个层面的事了。MRAM虽然可以无限次写但一个好的数据组织方案能让整个系统更可靠。我讲三个我实际用过的模式。第一个是循环日志区。假设分配0x60000到0x7FFFF共128KB作运行日志每条日志固定32字节包含时间戳、事件类型、数据载荷。用一个起始指针记录当前写入位置写满后回卷到区域头。由于MRAM没有擦除操作回卷时直接覆盖旧数据即可。这个方案在Flash上会有磨损均衡的麻烦但在MRAM上非常简单只要维护好写指针并定期把指针也存到MRAM里即可。下次上电时把指针读出来判断是否在合法范围内不在就从区域头开始。第二个是配置参数双镜像区。设备的校准参数、通信地址、PID参数这类数据我建议在MRAM里存两份一份主镜像一份备份镜像每条记录前面放一个16位CRC。更新参数时先写备份镜像校验成功后把主镜像也更新。上电时先读主镜像CRC不对就自动切备份镜像这样可以抵抗写入中途掉电造成的半个数据包问题。很多人觉得MRAM比Flash可靠就不用CRC了这个想法不保险因为掉电瞬间系统和芯片之间的SPI通信可能出现毛刺CRC是最便宜的保险。第三个是写后回读校验。MRAM不会磨损但总线噪声、时钟抖动这些外界因素仍然可能造成读写不一致。在重要的写操作之后我习惯性地回读刚刚写入的数据块做比较。由于MRAM的写速度很快回读校验也不会占用多少时间。实测在10MHz位速率下读512字节大概0.4ms比起Flash擦写动不动几十毫秒这点代价完全可接受。关于具体配置还有一个小技巧是区分频繁上电记录和普通变量保存。TM4C123GH6PZ的SRAM只有32KB有些场合下我们需要让设备在复位后尽量恢复之前的运行状态。把系统不断变化的运行状态比如当前步序、累计运行时间、电机位置以结构体的形式周期性地写入MRAM掉电后重新上电就能恢复到最近一次保存的状态。这其实是用MRAM当伪掉电保持SRAM用在工业控制器里非常实用。6. 性能实测和边界情况提醒我手上这块板子的实测数据可以给你一个参考SSI位速率配置为20MHz时读取4KB数据大约耗时1.7ms写入4KB大约1.6ms基本接近纯总线传输时间。写入之后无需等待立即读回第一个字节也稳定。对比同样的SPI Nor Flash写入一个4KB页之前往往要先擦除一个扇区整个过程要几十毫秒到上百毫秒MRAM的优势非常明显。当然MRAM也不是完全没有短板。第一它的工艺决定功耗比同密度的NOR Flash略高一点待机电流在毫安级不能和那种深度睡眠只有几微安的EEPROM比。如果你的设备用电池供电且长期休眠需要专门设计电源开关给它断电。第二MRAM的价格比普通Flash贵一些所以工程项目里不需要那么高写寿命的地方用普通Flash加合理磨损均衡也不会错。第三MR25H40CDF这颗料虽然符合工业温度范围但极端工况下仍建议通过降额使用和外壳散热保证结温在安全范围内。再提醒一个选型上的小细节MR25H40CDF作为SPI器件地址是三字节这一点和很多2Mb级别的MRAM比如MR25H40的早期版本或MR2A16A的并口版本不太一样。写驱动的时候一定要确认好地址宽度如果只发了16位地址高位默认值不确定读写位置就会漂掉这种bug特别难查。最后说说这颗芯片和TM4C123GH6PZ的搭配感受。TM4C123的SSI外设虽然名字叫SSI但它完全兼容SPI时序配合TivaWare库初始化不费劲MR25H40CDF的SPI logic又非常标准两者组合起来几乎不需要额外的电平转换或适配逻辑算是低风险方案。唯一要花心思的就是CS#的手工控制但只要你理解了SPI事务的本质这部分反而是整个方案里最灵活的地方。如果你的项目需要大批量数据掉电保存或者正在被Flash擦除时间困扰可以考虑把MRAM加进硬件设计里试过之后大概率就不想再回到Flash那套擦除流程了。
阅读完成 · 觉得有帮助?