ARTICLE DETAIL

资讯详情

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

MRAM与TM4C129工业级MCU的SPI存储设计实战

MRAM与TM4C129工业级MCU的SPI存储设计实战 先交代个背景。我手头这块TM4C129XKCZADCortex-M4F内核跑120MHz带网口带USB正经的工业级互联型MCU。但项目里最头疼的不是算力而是数据存哪儿——现场设备每隔几秒就要记一笔运行参数掉电不能丢写不能慢还得扛得住十年八年的反复擦写。一开始想用外挂NOR Flash测完直接放弃擦除慢、寿命短、还得做磨损均衡。后来换成了MR25H40CDF一颗4Mbit的SPI接口MRAM跟TM4C129XKCZAD的SSI外设搭起来问题一下子全解决了。这篇就聊聊这套组合在实际工业项目里怎么用从选型逻辑到硬件接线从SPI时序到驱动代码最后把我在现场踩过的坑和排查经验一并整理出来给打算在嵌入式系统里上MRAM的同行做个参考。1. 为什么是MRAM工业存储选型背后的逻辑1.1 从一次掉电丢数据事故说起之前做过一台现场仪表要求断电后把最近100条报警记录完整保存。用的是片内Flash模拟EEPROM坏就坏在“写中间掉电”这个场景。Flash写入前必须先擦除扇区擦除过程中一旦断电轻则数据全丢重则扇区变成“半擦除”状态后续怎么擦都擦不干净。当时客户现场就出了一次这种问题返修回来一查果然是擦除时序被断电打断整个扇区废了。从那以后我对工业数据存储就一条准则写入动作要么瞬间完成要么硬件本身就不怕写一半断电。MRAM恰好两个条件都占。1.2 五种非易失存储技术横向对比选型那阵子我把市面上常见的非易失存储方案全捋了一遍包括NOR Flash、EEPROM、FRAM铁电、MRAM磁阻以及老牌的BBSRAM带电池SRAM。方案写耐久性写速度是否需要擦除掉电写中断风险数据保持典型问题NOR Flash10万~100万次擦除慢几十ms/写快必须先擦后写高10~20年磨损均衡复杂掉电丢扇区EEPROM100万次写字节约5ms按字节擦写无需整片擦中10~20年写速度慢大容量昂贵FRAM铁电10^10~10^12次接近SRAM纳秒级不需要极低10~20年容量小价格贵MRAM磁阻理论上无限10^12次SRAM级纳秒级不需要极低20年以上价格仍偏高BBSRAM带电池无限SRAM级不需要电池耗尽即丢靠电池维持怕电池失效维护麻烦注意看两个关键指标写耐久性和掉电写中断风险。工业现场环境恶劣设备可能十年不关机按每秒写一次算十年下来就是3亿次写入普通Flash早就废了。MRAM和FRAM的10^12次级别等于写不死。而掉电写中断这一项MRAM和FRAM因为写入机制本身不需要擦除数据在微秒级内就固化下来了就算写一半断电已经写入的部分也不会损坏。1.3 MR25H40CDF TM4C129XKCZAD的匹配逻辑MR25H40CDF是Everspin的4Mbit SPI MRAM芯片工作电压3.3V8引脚小封装工业级温度范围。4Mbit折算下来是512KB字节对于存报警记录、标定参数、运行日志这类应用容量卡得刚刚好——比EEPROM大得多又不像大容量Flash那样需要文件系统。TM4C129XKCZAD这边本身自带4个SSI模块SSI在TI的语境里就是SPI。把SSI配置成主机模式接一颗MRAM完全可以做到“零外部逻辑、零电平转换、直接连线”。另外这颗MCU带完整的以太网MAC和PHY未来把存储数据通过网络上传一个芯片就搞定了不用额外挂协议栈芯片。这套组合打动我的核心原因有三点MCU和存储都是3.3V供电接口电平一致不需要电平转换电路硬件设计极简。SPI接口只有4根线CLK、MOSI、MISO、CS不占用太多GPIOTM4C还剩一堆引脚接传感器和通信口。MRAM的写入速度快到不需要等“完成时间”代码里写完就可以继续干别的实时性比等Flash擦除强太多。2. 硬件设计引脚处理与SPI连线2.1 MR25H40CDF 的关键引脚怎么接拿MR25H40CDF的数据手册对照着看核心引脚就8个VCC、GND、SI、SO、SCK、CS、WP、HOLD。SI/SO/SCK/CS这四根线接MCU的SSI接口标准接法不展开。值得重点说的是WP和HOLD这两个引脚很多人第一次画板就栽在这上面。WP是写保护输入低电平有效。如果这个引脚被拉低非易失状态寄存器的写保护位会被锁存整个MRAM会进入硬件写保护状态你发WREN写使能命令都不好使。我们实际接法是直接通过10k电阻上拉到VCC让WP处于高电平彻底关掉硬件写保护写操作全部由软件通过状态寄存器控制。HOLD引脚是通信暂停输入也是低电平有效。当HOLD拉低时芯片会暂停SPI通信SI、SCK上的信号全被忽略。如果这个引脚悬空现场干扰信号就可能把它拉低导致SPI通信莫名其妙中断。所以HOLD同样要10k电阻上拉到VCC。上拉电阻这里有个细节10k阻值不是随便定的。上拉太弱抗干扰能力差太强电平切换沿变缓SPI高速通信容易出错。工业环境我习惯用4.7k~10k之间实测在10MHz SPI时钟下波形干净没有毛刺。2.2 TM4C129XKCZAD 的SSI接口配置TM4C129XKCZAD的SSI模块在数据手册里叫Synchronous Serial Interface功能上完全兼容SPI。这颗芯片一共有4个SSI模块编号SSI0到SSI3引脚分布在不同的GPIO上。我项目里用的是SSI0具体引脚是通过GPIO配置复用功能打开的。选择SSI模块时注意避开与其它外设的引脚冲突。比如TM4C129XKCZAD的以太网、USB、UART都占用了大量引脚选SSI0之前先查好引脚复用表。我的经验是画原理图之前先把整板所有外设的引脚分配铺在一张Excel表里按功能分组检查冲突后面Layout能省不少事。SSI主机模式下的CS片选信号TI的SSI模块可以硬件自动控制也可以手动用普通GPIO控制。我强烈建议用普通GPIO手动控制CS。原因很实际自动片选在某些情况下会产生额外的时间间隙对于MRAM这种对时序敏感的命令序列尤其是WRENWRITE这种连续操作手动控制CS能精确保证命令字节之间CS低电平不断开兼容性更好。TM4C的GPIO翻转速度足够快几个微秒的片选切换延时根本不影响MRAM的整体性能。2.3 供电、去耦与布局细节MR25H40CDF正常工作电流很低但SPI通信时电流会有瞬态波动所以VCC引脚附近必须放一个100nF的陶瓷去耦电容这个电容尽量靠近芯片电源引脚走线短而粗。另外建议在VCC入口加一个10uF钽电容用来平滑系统级的电源纹波。很多MRAM读写异常的现场问题最后查出来都是电源纹波超标芯片在时钟沿采样时读到不确定电平。从MCU到MRAM的SPI走线长度要控制在合理范围内。10MHz SPI时钟下5cm以内的走线基本不用担心反射问题。如果因为结构限制走线超过10cm建议把SPI时钟降到5MHz以下或者用RC滤波在接收端整形。一个容易被忽视的细节是MISO和MOSI交叉。很多做硬件的新手在设计时把SI和SO画反了导致主机收不到数据。MRAM的SI是芯片输入接MCU的MOSI输出SO是芯片输出接MCU的MISO输入。我的做法是每次画完原理图后对一遍信号方向MCU输出到SI、MCU输入来自SOSCK走同一个方向CS走同一个方向。3. 驱动实现从寄存器到可用的读写函数3.1 SSI模块的初始化和SPI时钟计算TM4C129XKCZAD的SSI初始化用TivaWare库非常方便。先使能外设时钟再配置GPIO复用为SSI功能然后调用SSIConfigSetExpClk设置协议模式、工作模式、时钟速率和数据位宽。#include stdint.h #include inc/tm4c129xnczad.h #include driverlib/sysctl.h #include driverlib/gpio.h #include driverlib/ssi.h #include driverlib/pin_map.h #define MRAM_CS_PORT GPIO_PORTA_BASE #define MRAM_CS_PIN GPIO_PIN_3 #define MRAM_SPI_BASE SSI0_BASE #define MRAM_SPI_CLK 10000000 void MRAM_GPIO_Init(void) { SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOA)) ; GPIOPinTypeGPIOOutput(MRAM_CS_PORT, MRAM_CS_PIN); GPIOPinWrite(MRAM_CS_PORT, MRAM_CS_PIN, MRAM_CS_PIN); } void MRAM_SSI_Init(void) { SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0); while (!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI0)) ; GPIOPinConfigure(GPIO_PA2_SSI0CLK); GPIOPinConfigure(GPIO_PA4_SSI0RX); GPIOPinConfigure(GPIO_PA5_SSI0TX); GPIOPinConfigure(GPIO_PA3_GPIO); // PA3 手动配置为GPIO输出CS GPIOPinTypeSSI(GPIO_PORTA_BASE, GPIO_PIN_2 | GPIO_PIN_4 | GPIO_PIN_5); SSIConfigSetExpClk(MRAM_SPI_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, MRAM_SPI_CLK, 8); SSIEnable(MRAM_SPI_BASE); }SPI时钟速率的设置TM4C的SSI内部会按照SysClk / (CPSDVSR * (1 SCR))来分频。SSIConfigSetExpClk函数会替我们自动计算CPSDVSR和SCR我们只需要指定目标频率。我选了10MHz原因有两点一是MRAM数据手册标称最高40MHz10MHz有足够余量信号完整性完全可控二是10MHz的SPI时钟下512字节连续读写耗时不到0.5ms对绝大多数工业数据记录场景已经绰绰有余。3.2 MRAM命令集与状态寄存器解析MR25H40CDF的命令集非常简单实际项目中最常用的就四个命令命令操作码功能说明WREN0x06置位WEL写使能锁存位每个写操作前必须发送WRDI0x04复位WEL位写保护可防止误写RDSR0x05读状态寄存器查询WEL状态WRSR0x01写状态寄存器READ0x03读取数据跟随24位地址WRITE0x02写入数据跟随24位地址注意MR25H40CDF内部状态寄存器最重要的两个位是bit0的WEL和bit1的WP。WEL是写使能锁存位硬件上电默认是0每次执行WRITE命令之前必须发送WREN命令把WEL置1否则一切写操作都会被忽略。发送WREN之后WEL会一直保持直到芯片接收到WRDI命令或者芯片断电。这里有个容易踩坑的地方WREN命令的CS时序。正确顺序是CS拉低、发送0x06、CS拉高。这个CS从低到高的上升沿非常关键它会把WEL位真正锁存到状态寄存器里。如果你发完0x06后CS一直保持低电平WEL位实际上不会生效后续WRITE命令会被拒绝。我开始调驱动时就犯过这个错抓波形才发现CS根本没拉高。状态寄存器的读取方式也值得注意CS拉低、发RDSR命令0x05、然后主机发送一个哑字节比如0x00来提供时钟、芯片在这个哑字节期间把状态寄存器内容放到SO线上。读完后CS拉高。uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_LOW(); MRAM_SPI_Transfer(0x05); status MRAM_SPI_Transfer(0x00); MRAM_CS_HIGH(); return status; }3.3 读一个字节/写一个字节的完整时序SPI底层收发函数是全双工的基础。TM4C的SSI有内置FIFO最简单的做法是使用SSIDataPut和SSIDataGet搭配SSIBusy轮询。下面这个函数就是最核心的SPI单字节交换函数。static uint8_t MRAM_SPI_Transfer(uint8_t byte) { uint32_t rx; SSIDataPut(MRAM_SPI_BASE, byte); while (SSIBusy(MRAM_SPI_BASE)) ; SSIDataGet(MRAM_SPI_BASE, rx); return (uint8_t)rx; }注意SSIDataGet在SPI模式下会直接返回接收到的最新数据因为TM4C的SSI接收FIFO里存的就是收来的字节。读单个字节的完整时序是uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; MRAM_CS_LOW(); MRAM_SPI_Transfer(0x03); MRAM_SPI_Transfer((addr 16) 0xFF); MRAM_SPI_Transfer((addr 8) 0xFF); MRAM_SPI_Transfer(addr 0xFF); data MRAM_SPI_Transfer(0x00); MRAM_CS_HIGH(); return data; }MR25H40CDF的地址是19位有效但SPI框架里仍然用24位传输高5位直接填0。实际项目中习惯上把16、17、18这三个字节都补0方便后续排查对齐问题。写单个字节的时序要更讲究void MRAM_WriteByte(uint32_t addr, uint8_t data) { MRAM_WriteEnable(); MRAM_CS_LOW(); MRAM_SPI_Transfer(0x02); MRAM_SPI_Transfer((addr 16) 0xFF); MRAM_SPI_Transfer((addr 8) 0xFF); MRAM_SPI_Transfer(addr 0xFF); MRAM_SPI_Transfer(data); MRAM_CS_HIGH(); }其中MRAM_WriteEnable就是发送WREN命令void MRAM_WriteEnable(void) { MRAM_CS_LOW(); MRAM_SPI_Transfer(0x06); MRAM_CS_HIGH(); }3.4 任意地址连续读写与边界回绕处理MRAM不同于Flash它的存储结构就是一个SRAM阵列没有页大小、没有扇区、没有擦除块。读操作可以连续读取任意长度的字节序列地址在芯片内部自动递增。写操作同样可以在一个WRITE命令下连续写入任意字节长度直到地址递增到最大值后自动回绕到0x00000。利用这个特性我封装了两个批量读写函数直接对应工业日志和参数块的操作需求。读一块数据就是连续发送哑字节每个哑字节期间芯片返回一个数据字节void MRAM_ReadBuffer(uint32_t addr, uint8_t *buf, uint32_t len) { uint32_t i; MRAM_CS_LOW(); MRAM_SPI_Transfer(0x03); MRAM_SPI_Transfer((addr 16) 0xFF); MRAM_SPI_Transfer((addr 8) 0xFF); MRAM_SPI_Transfer(addr 0xFF); for (i 0; i len; i) { buf[i] MRAM_SPI_Transfer(0x00); } MRAM_CS_HIGH(); }写一块数据就是在一个WRITE命令后连续输出数据字节void MRAM_WriteBuffer(uint32_t addr, const uint8_t *buf, uint32_t len) { uint32_t i; MRAM_WriteEnable(); MRAM_CS_LOW(); MRAM_SPI_Transfer(0x02); MRAM_SPI_Transfer((addr 16) 0xFF); MRAM_SPI_Transfer((addr 8) 0xFF); MRAM_SPI_Transfer(addr 0xFF); for (i 0; i len; i) { MRAM_SPI_Transfer(buf[i]); } MRAM_CS_HIGH(); }地址回绕这件事要特别留个心眼。如果一次写入的地址从0x7FFFE开始写4个字节写到最后一位时地址会回绕到0x00000后两个字节会写到存储空间的开头。这在环形日志缓冲里反而方便但用在参数区就必须避免。我的做法是在调用端判断边界如果剩余空间不够一次写入就拆成两段写第一段写到空间末尾第二段从起始地址开始。4. 工程化经验排查清单与性能实测4.1 四个典型翻车场景第一个典型问题是“写入不生效”。代码跑起来读回来的数据永远是0xFF或者写进去什么都没变。排查方向按顺序走先量CS有没有正常拉低到拉高的动作再检查WREN有没有发成功最后看状态寄存器的WEL位有没有置位。我遇到过最隐蔽的情况是SPI时钟极性配置反了。MR25H40CDF支持SPI模式0和模式3但TM4C的SSI初始化如果配了模式2或模式1芯片完全不理你读写都没反应。换到模式0就一切正常。第二个问题是“首字节丢失”。连续读大量数据时读回来的整体错位第一个字节丢了后面跟着一串错位数据。原因是SPI是全双工在发送最后一个地址字节的时钟沿上芯片同时把一个无效字节放到了SO线上。很多人在发送完地址后立刻读MISO读到的其实是这个垃圾字节而不是真正的数据。解决办法就是多发送一个哑字节正如上面MRAM_ReadBuffer里做的那样把地址发送完后的第一个有效数据留到哑字节阶段接收。第三个问题是“偶发写入错误”。整机跑起来后几百次操作偶尔出现一个字节不对。排查到最后发现是电源纹波问题。MRAM在数据输入采样时要求VCC电平稳定板子上有大功率外设启停时瞬间电流冲击会让VCC跌落。后来加了去耦电容加上把SPI时钟从20MHz降到了10MHz问题消失。第四个问题是“中断导致SPI传输超时”。如果SPI传输过程中正好到来一个长时间中断服务函数MRAM那边CS还低着SCK却停住了芯片会一直等时钟不会有超时机制。传输时间被拉长后虽然多数情况下能恢复但如果中断嵌套严重接收FIFO会溢出直接导致数据错乱。解决方法是批量传输期间关中断或者用DMA。4.2 常见问题速查表现象可能原因排查建议读写全部无响应SPI模式配置错误确认SSI配置为MOTO_MODE_0或MOTO_MODE_3写不进去读全是FFWP引脚拉低或WEL未置位检查WP上拉确认WREN后CS有上升沿读数据全部错位首字节时序没处理地址发送完后补一个哑字节时钟偶发单bit错误电源纹波或SPI时钟过快增加去耦电容降低SPI频率高温下数据异常信号完整性余量不足检查走线长度考虑降低时钟频率中断后传输卡死SSI FIFO溢出传输期间关中断或使用DMA4.3 实测数据与调优方向我用逻辑分析仪实测过一组数据10MHz SPI时钟下操作耗时写一个字节含WREN命令约4.8us连续写512字节约430us连续读512字节约410us读状态寄存器约2.2us对比之前用NOR Flash方案擦除一个4KB扇区就需要几十毫秒MRAM在写入场景的优势不是一个量级是三个量级。对于应用层来说最直观的感受就是“写日志不再打断主逻辑了”以前写一条Flash日志可能要等扇区擦除现在MRAM写几百字节的时间忽略不计。调优方向上如果未来数据量变大有两条路可以走。一是把SPI时钟提高到30MHz以上但要注意走线阻抗匹配MCU和MRAM之间的连接需要控制寄生电容。二是换用DMA方式驱动SSI让数据搬运不占CPU批量读写几百字节时能把CPU占用率从百分之十降到接近零。我个人在实际项目中的体会是MRAM不是豪华配置而是针对工业现场的刚需选择。TM4C129XKCZAD加MR25H40CDF这套组合硬件极简代码量不大可靠性却远超Flash方案。如果你也在做类似的数据记录、参数存储、掉电保护这类的嵌入式产品不妨直接按这个路线试一把能省掉很多我在Flash方案上交过的学费。
返回列表