
MRAM 这类存储介质在工业现场其实一直有点叫好不叫座的意思——参数漂亮价格劝退很多人评估完就换回 FRAM 或者带电池的 SRAM 了。但真到了那些掉电不能丢数据、写入又特别频繁、还要扛住高低温循环的场景MRAM 的优势就藏不住了。这次我拿 MR25H40CDF 这颗 4Mbit 的 SPI MRAM 配 TM4C129ENCPDT 这颗带以太网的 Cortex-M4F 主控从硬件连线到驱动读写完整跑了一遍中间踩了几个时序和状态位的坑整理出来给同样在做工业数据记录、参数存储、日志缓存的朋友参考。不管你是刚接触 SPI 外设的新手还是想给现有方案换存储介质的老手这篇应该都能帮你少走点弯路。1. 为什么是 MRAM 加 TM4C129 这个组合1.1 MRAM 到底解决了工业存储的哪个痛点先说清楚 MRAM 和常见存储的区别不然选型就是拍脑袋。工业设备里存数据无非几种选择EEPROM 写入慢、寿命有限NOR Flash 擦除块大、写之前要擦、掉电瞬间容易丢FRAM 写入快寿命长但容量小、价格高带电池的 SRAM 速度快但电池是隐患高温下电池寿命打折还要考虑运输和环保限制。MRAM 的定位正好卡在中间非易失、写入不需要擦除、字节级可写、写入次数近乎无限、读写速度接近 SRAM。MR25H40CDF 是 Everspin 的 4Mbit512KBSPI 接口 MRAM40MHz SPI 时钟工作电压 2.7V 到 3.6V工业级温度范围。它的核心价值在于你可以像操作普通 RAM 一样直接往任意地址写数据不用管擦除、不用管磨损均衡掉电数据还在。这对工业场景意味着什么举个例子一台设备要记录运行参数每秒写一次一年就是三千多万次写入。EEPROM 早就写废了NOR Flash 需要复杂的磨损均衡算法而 MRAM 直接写就行寿命根本不是问题。这就是它存在的意义。1.2 TM4C129ENCPDT 为什么适合带这颗存储TM4C129ENCPDT 是 TI 的 Cortex-M4F 芯片120MHz 主频1MB Flash256KB SRAM带 10/100 以太网 MACPHY还有多个 SSITI 对 SPI 的叫法外设。选它配 MRAM 有几个现实理由第一它有4 个 SSI 模块可以一个接 MRAM、一个接其他外设互不干扰。第二它的 SSI 支持 Motorola SPI 格式正好匹配 MR25H40CDF 的 SPI 模式 0 和模式 3。第三工业级型号温度范围够和 MRAM 的工业级温度能对上。第四带以太网意味着数据可以本地存 MRAM、远程传出去形成完整的工业数据链路。提示TM4C129 系列里带 C 的型号如 ENCPDT才有以太网 PHY选型时别只看主频和 Flash外设差异才是关键。1.3 这个组合的典型应用场景我把这个组合能覆盖的场景列一下方便你判断是否对得上自己的项目应用场景存储需求特点MRAM 的价值点工业设备参数存储频繁改参数、掉电不能丢字节级写入、无需擦除运行日志缓存高频写入、断电保护写入无延迟、寿命无限计量仪表数据记录累积数据、长期可靠非易失、宽温工作系统配置与校准值少量数据、随时更新直接覆盖写、简单可靠掉电瞬间状态保存极短时间窗口写入写入快、无擦除等待这些场景的共同点是数据量不大512KB 够用、写入频繁、可靠性要求高。如果你的项目是存大文件、存固件、存图片那 MRAM 不合适还是得上 Flash 或者 SD 卡。2. 硬件连接SPI 走线里那些容易翻车的地方2.1 引脚映射与 SSI 模块选择MR25H40CDF 是标准 SPI 从设备四根信号线加电源SCLK、SIMOSI、SOMISO、CS片选。TM4C129ENCPDT 这边我用的是 SSI2 模块映射到 PD 端口具体引脚分配如下MRAM 引脚TM4C129 引脚SSI 信号说明CSPD3SSI2Fss片选低有效SCLKPD0SSI2Clk时钟SIPD1SSI2Tx主出从入SOPD2SSI2Rx主入从出VCC3.3V-电源VSSGND-地TM4C129 的引脚复用是通过 GPIO 配置寄存器做的需要先把 PD0-PD3 配置成 SSI 功能而不是普通 GPIO。这一步很多人会漏结果 SPI 死活没波形。// 使能 SSI2 和 GPIOD 时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); // 等待外设就绪 while(!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI2)); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOD)); // 配置 PD0-PD3 为 SSI2 功能 GPIOPinConfigure(GPIO_PD0_SSI2CLK); GPIOPinConfigure(GPIO_PD1_SSI2TX); GPIOPinConfigure(GPIO_PD2_SSI2RX); GPIOPinConfigure(GPIO_PD3_SSI2FSS); GPIOPinTypeSSI(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);2.2 片选信号的硬件与软件之争SPI 片选有两种做法硬件片选SSI 外设自动控制 Fss 引脚和软件片选用普通 GPIO 手动拉低拉高。TM4C129 的 SSI 在 Freescale SPI 模式下会自动控制片选但在和 MRAM 配合时我建议用软件片选原因有两个一是 MRAM 的指令序列里有些操作需要 CS 在多个字节之间保持低电平硬件片选在某些配置下会在字节间隙抬高 CS导致指令被打断。二是软件片选调试起来直观示波器上看得清清楚楚出问题好定位。软件片选的做法是把 CS 单独配成 GPIO 输出SPI 传输前后手动控制// CS 用 PD3 之外的引脚比如 PN2 做软件片选 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPION); GPIOPinTypeGPIOOutput(GPIO_PORTN_BASE, GPIO_PIN_2); GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_2, GPIO_PIN_2); // 初始拉高 // 传输前拉低 GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_2, 0); // ... SPI 传输 ... // 传输后拉高 GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_2, GPIO_PIN_2);注意如果坚持用硬件片选务必确认 SSI 配置里的 Fss 极性、时钟相位和 MRAM 的时序要求完全一致否则会出现能读不能写或者写进去读出来是乱码的诡异现象。2.3 电源去耦与走线长度MRAM 虽然抗干扰能力比 Flash 强但 SPI 高速时钟下电源噪声照样会让读写出错。我的做法是在 MRAM 的 VCC 引脚旁边放一个0.1uF 陶瓷电容紧贴引脚再并一个 1uF 的做低频滤波。走线方面SCLK 尽量短如果板上距离超过 5cm建议在 SCLK 上串一个 22 到 33 欧姆的电阻做阻抗匹配抑制过冲。还有一点容易被忽略MRAM 的 SO 引脚是推挽输出不需要外部上拉。有些人习惯性给 MISO 加上拉电阻结果和 MRAM 的输出驱动打架反而让波形变差。这个坑我踩过去掉上拉之后波形立刻干净了。3. 驱动实现从寄存器操作到读写函数3.1 SSI 模块的初始化参数怎么定TM4C129 的 SSI 初始化有几个关键参数定错了直接通信失败。MR25H40CDF 支持 SPI 模式 0CPOL0, CPHA0和模式 3CPOL1, CPHA1我用的是模式 0配置如下// SSI2 配置主模式模式016位数据40MHz SSIConfigSetExpClk(SSI2_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 40000000, 8); // 使能 SSI2 SSIEnable(SSI2_BASE);这里有几个点要解释清楚时钟频率MR25H40CDF 最高支持 40MHz但实际用的时候建议先降到 10MHz 调通再往上提。我一开始直接上 40MHz结果因为板子走线一般读出来的 ID 都不对降到 10MHz 立刻正常后来优化走线才敢跑 40MHz。数据位宽虽然 MRAM 是 8 位数据总线但 TM4C129 的 SSI 支持 4 到 16 位。用 8 位最直观每次传一个字节。如果追求吞吐量可以用 16 位但要注意字节序问题。时钟极性相位模式 0 是空闲低电平、第一个边沿采样。这个必须和 MRAM 手册对上模式选错了数据全是错的。3.2 MRAM 的指令集与状态寄存器MR25H40CDF 的指令集很简洁常用的就几条指令操作码功能WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据状态寄存器只有最低位有意义WELWrite Enable Latch。写操作之前必须先发 WREN 把 WEL 置 1写完自动清零。这个机制和 EEPROM 类似但 MRAM 不需要等待写完成发完 WRITE 指令数据就进去了没有 busy 轮询这一说。读状态寄存器的函数uint8_t MRAM_ReadStatus(void) { uint8_t status; uint8_t cmd 0x05; CS_LOW(); SSIDataPut(SSI2_BASE, cmd); while(SSIBusy(SSI2_BASE)); SSIDataPut(SSI2_BASE, 0x00); // dummy byte 读回状态 while(SSIBusy(SSI2_BASE)); SSIDataGet(SSI2_BASE, (uint32_t*)status); CS_HIGH(); return status; }3.3 写操作的完整流程与常见错误写 MRAM 的流程是发 WREN、发 WRITE 指令、发 3 字节地址、发数据。这里最容易错的是地址字节数。MR25H40CDF 是 4Mbit需要 19 位地址所以用 3 字节地址24 位高位补零。有人按 2 字节地址发结果只能访问前 64KB后面的地址全乱套。void MRAM_Write(uint32_t addr, uint8_t *data, uint32_t len) { uint8_t cmd; // 步骤1写使能 cmd 0x06; CS_LOW(); SSIDataPut(SSI2_BASE, cmd); while(SSIBusy(SSI2_BASE)); CS_HIGH(); // 步骤2发写指令 3字节地址 数据 cmd 0x02; CS_LOW(); SSIDataPut(SSI2_BASE, cmd); while(SSIBusy(SSI2_BASE)); SSIDataPut(SSI2_BASE, (addr 16) 0xFF); while(SSIBusy(SSI2_BASE)); SSIDataPut(SSI2_BASE, (addr 8) 0xFF); while(SSIBusy(SSI2_BASE)); SSIDataPut(SSI2_BASE, addr 0xFF); while(SSIBusy(SSI2_BASE)); for(uint32_t i 0; i len; i) { SSIDataPut(SSI2_BASE, data[i]); while(SSIBusy(SSI2_BASE)); } CS_HIGH(); }提示每次 SSIDataPut 之后必须等 SSIBusy 清零再发下一个字节否则 FIFO 会溢出数据错位。这个等待在高速时钟下尤其重要。3.4 读操作与页边界问题读操作比写简单发 READ 指令加 3 字节地址然后连续读数据就行。MRAM 没有页边界限制可以一口气读完整个 512KB地址自动递增。void MRAM_Read(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd 0x03; uint32_t dummy; CS_LOW(); SSIDataPut(SSI2_BASE, cmd); while(SSIBusy(SSI2_BASE)); SSIDataPut(SSI2_BASE, (addr 16) 0xFF); while(SSIBusy(SSI2_BASE)); SSIDataPut(SSI2_BASE, (addr 8) 0xFF); while(SSIBusy(SSI2_BASE)); SSIDataPut(SSI2_BASE, addr 0xFF); while(SSIBusy(SSI2_BASE)); for(uint32_t i 0; i len; i) { SSIDataPut(SSI2_BASE, 0x00); while(SSIBusy(SSI2_BASE)); SSIDataGet(SSI2_BASE, dummy); buf[i] (uint8_t)dummy; } CS_HIGH(); }这里有个细节SSIDataPut 发 dummy 字节的同时SSIDataGet 把从设备返回的数据读出来。TM4C129 的 SSI 是全双工的发和收同时进行所以必须发一个字节才能收一个字节。这个发 dummy 收数据的模式是 SPI 读操作的通用套路。4. 实测中暴露的问题与排查过程4.1 读出来全是 0xFF 的排查链路第一次上电测试读 MRAM 的任意地址返回全是 0xFF。这个现象说明 MISO 线一直是高电平要么从设备没响应要么片选没生效。我的排查顺序是这样的第一步确认片选。用示波器看 CS 引脚发现传输期间 CS 确实拉低了排除片选问题。第二步确认时钟。看 SCLK有波形频率也对排除时钟问题。第三步确认 MOSI。看 SI 线指令和地址都发出去了波形正常。第四步怀疑 MISO。看 SO 线发现它一直是高电平没有任何翻转。这时候基本确定是从设备没驱动 MISO。第五步查电源和模式。量 MRAM 的 VCC3.3V 正常。再看 SPI 模式我一开始配的是模式 3改成模式 0 之后MISO 立刻有数据了。根因就是 SPI 模式配错MRAM 在错误的时钟边沿采样根本没识别到指令。这个坑的教训是SPI 模式一定要对着手册确认不能凭经验猜。不同厂家的器件默认模式不一样MRAM 支持模式 0 和模式 3但具体用哪个要看你的时序设计。4.2 写入后读回数据错位的定位模式改对之后读正常了但写入测试又出问题写进去的数据读回来整体偏移了一个字节。比如写0x11 0x22 0x33读回来是0x00 0x11 0x22。这种整体偏移的现象典型原因是多发或少发了一个字节。我检查代码发现 WREN 指令发完之后CS 拉高再拉低发 WRITE 指令中间没有等待 SSI 的发送完成。SSIDataPut 只是把数据放进 FIFO实际发送还没完成CS 就拉高了导致 WREN 指令没发完整。修复方法是在每次 CS 拉高之前加一个等待while(SSIBusy(SSI2_BASE)); // 等待发送完成 CS_HIGH();加上这个等待之后数据错位问题消失。这个细节在 TI 的例程里有时候会省略因为低速下 FIFO 发得快看不出问题但高速下必现。4.3 高速时钟下的数据偶发错误调到 40MHz 之后大部分读写正常但偶尔会出现单个字节错误。这种偶发问题最难查因为不是必现。我用逻辑分析仪抓了出错时的波形发现 SCLK 在高速下有明显的过冲和振铃尤其是在 CS 拉低的那一瞬间。这是典型的信号完整性问题。解决办法有三个在 SCLK 上串 33 欧姆电阻抑制过冲缩短 SCLK 走线减少反射在 MRAM 电源引脚增加去耦电容三个措施一起上之后40MHz 下连续读写 100 万次没有出现错误。如果板子空间有限做不了这些优化那就老老实实降到 20MHz工业场景下稳定性比速度重要。4.4 掉电测试与数据保持验证MRAM 的核心卖点是掉电不丢数据所以必须做掉电测试。我的测试方法是写入一组已知数据然后直接断电等 10 秒再上电读回数据比对。测试结果数据完整保持没有丢失。这里要注意的是掉电测试要在写入操作完成之后立刻断电验证的是写入即持久这个特性。如果写入过程中断电MRAM 的写入是原子性的要么写进去要么没写不会出现半写状态这也是它比 Flash 强的地方。我还做了高温测试把板子放到 85 度环境里跑了一整天读写正常。MRAM 的工业级温度范围是 -40 到 85 度这个范围内数据保持没问题。5. 性能实测与优化空间5.1 读写速度到底能跑多快我用 40MHz SPI 时钟实测了读写速度操作数据量耗时等效速率连续读512KB约 105ms约 4.9MB/s连续写512KB约 110ms约 4.7MB/s单字节写1 字节约 2.5us-40MHz SPI 理论速率是 5MB/s实测 4.7 到 4.9MB/s效率在 94% 以上主要开销在指令和地址字节上。单字节写 2.5us 这个数字很关键意味着掉电瞬间你有足够的时间把关键状态写进去。对比一下EEPROM 单字节写通常要 5ms 左右是 MRAM 的两千倍。这就是为什么掉电保存场景必须用 MRAM 或者 FRAM用 EEPROM 根本来不及。5.2 用 DMA 进一步压榨吞吐量如果数据量大、又不想占用 CPU可以用 TM4C129 的 uDMA 配合 SSI。配置好 DMA 通道之后SPI 传输由 DMA 搬运CPU 可以去干别的。对于连续读写大块数据的场景DMA 能把 CPU 占用从接近 100% 降到 10% 以下。不过 DMA 配置有个坑SSI 的 DMA 请求触发条件要配对发送用 TX DMA接收用 RX DMA两个通道都要配。只配一个的话要么发不出去要么收不回来。这个在 TI 的文档里写得比较分散需要对着 SSI 和 uDMA 两个章节一起看。5.3 地址空间规划的建议512KB 的 MRAM 怎么划分建议按用途分区避免数据互相覆盖地址范围大小用途0x00000 - 0x00FFF4KB系统配置区0x01000 - 0x01FFF4KB校准参数区0x02000 - 0x3FFFF248KB运行日志区0x40000 - 0x7FFFF256KB数据记录区分区的好处是配置区和日志区物理隔离日志写满循环覆盖的时候不会碰到配置。每个区还可以加一个简单的校验头比如 CRC16读的时候先校验再使用防止数据被意外篡改。6. 几个值得记住的实操经验6.1 上电初始化的顺序不能乱上电之后的操作顺序是先配 GPIO 复用、再配 SSI 参数、最后拉高 CS。如果顺序反了比如 CS 还没配置就先初始化 SSI可能会出现 CS 悬空导致 MRAM 误触发。我习惯在初始化最后加一个读 ID 的操作确认通信正常再进入主逻辑。6.2 写保护要善用MR25H40CDF 有硬件写保护引脚WP和状态寄存器里的块保护位。工业场景建议把 WP 引脚接到 GPIO 上正常运行时拉高允许写系统进入关键流程时拉低禁止写防止误操作覆盖重要数据。这个硬件级的保护比软件判断可靠得多。6.3 数据校验别省MRAM 虽然可靠但 SPI 传输过程可能受干扰。我在每个数据块后面加 2 字节 CRC16读的时候校验。多花 2 个字节换来的是数据可信度的提升。尤其是工业现场电磁环境复杂这个投入很值。6.4 调试工具的选择调 SPI 一定要有逻辑分析仪示波器只能看单根线的波形逻辑分析仪能同时看 CS、SCLK、MOSI、MISO 四根线直接解码出 SPI 数据。我用的是一款入门级 8 通道逻辑分析仪配合开源软件抓 SPI 时序足够用了。没有这个工具SPI 调试基本靠猜效率极低。6.5 温度对时序的影响工业级应用要过温度测试。MRAM 在 -40 度和 85 度下的时序参数和常温略有差异高速时钟下这个差异可能被放大。我的建议是在温度极限下重新验证 SPI 时序必要时降低时钟频率留出余量。常温跑 40MHz 没问题不代表高温下也没问题。这套 MRAM 加 TM4C129 的方案我前后调了大概两周从最初的读不出数据到后来 40MHz 稳定运行中间踩的坑基本都写在上面了。MRAM 的价格确实比 Flash 高但在那些写入频繁、掉电不能丢、维护成本高的场景里它省下的是整个系统的可靠性和后期维护的麻烦。选型的时候别只盯着单价算算全生命周期的账很多时候 MRAM 反而是更划算的那个。