ARTICLE DETAIL

资讯详情

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

基于MRAM与TM4C1294的工业数据采集存储方案设计与实现

基于MRAM与TM4C1294的工业数据采集存储方案设计与实现 1. 项目缘起与方案选型为什么是 MRAM 加 TM4C12941.1 一个真实的需求场景前阵子接了个工业数据采集终端的活儿客户的要求很朴素设备装在配电柜里每天要记录几百条运行参数断电不能丢现场电磁环境还特别脏。一开始我下意识想用 SPI Flash 加铁电存储器的组合但算了一下账Flash 擦写寿命十万次这个坎儿过不去——按每天写五百次算半年就得换芯片这显然不现实。后来把目光转向了 MRAM具体型号就是MR25H40CDF4Mbit 容量SPI 接口关键是它标称的擦写次数是 10^14 这个量级基本上等于无限次写入。主控这边选的是TM4C1294NCPDTTI 的这款 Cortex-M4F 芯片在工业圈子里口碑一直不错120MHz 主频1MB Flash256KB SRAM自带 4 个 SSI 模块TI 对 SPI 的叫法而且有 EEPROM 和看门狗这些工业场景常用的外设。选它主要是看中 SSI 模块的灵活性和 TI 那套 TivaWare 库的成熟度寄存器操作和库函数调用两条路都能走调试起来心里有底。这个组合的核心价值在于MRAM 的非易失性加上近乎无限的写入寿命配合 TM4C1294 稳定的 SPI 主机能力构成了一套适合高频写入、断电敏感场景的数据存储方案。适合谁看做工业仪表、电力监控、医疗设备数据记录、车载黑匣子这类产品的嵌入式工程师尤其是那些被 Flash 寿命和掉电丢数据折磨过的朋友。1.2 MRAM 到底是个什么东西很多人第一次听到 MRAM 会懵这里用大白话解释一下。传统的存储介质Flash 靠浮栅晶体管里 trapped 的电荷来表示 0 和 1写之前得先擦除擦除靠的是高压隧道效应这个过程对氧化层有物理损伤所以寿命有限。EEPROM 类似只是擦写粒度更细。FRAM 用的是铁电材料的极化方向寿命长但容量做不大而且价格偏高。MRAM 走的是另一条路——磁隧道结MTJ。每个存储单元是一个三明治结构两层铁磁材料夹一层极薄的绝缘隧道势垒。其中一层是固定磁化方向的参考层另一层是自由层。自由层的磁化方向与参考层平行时单元呈现低电阻代表逻辑 0反平行时呈现高电阻代表逻辑 1。读取的时候通一个小电流测电阻写入的时候用自旋极化电流产生的自旋转移矩STT来翻转自由层方向。这个原理带来的好处很直接写入是磁化方向翻转没有电荷注入和抽出没有氧化层损伤所以寿命极长写入速度是纳秒级比 Flash 的毫秒级擦除快了几个数量级而且不需要擦除操作直接覆盖写就行。MR25H40CDF 就是基于这个技术的 4Mbit SPI 非易失存储器工作电压 2.7V 到 3.6V工业级温度范围 -40 到 85 摄氏度SPI 最高时钟 40MHz。1.3 为什么不用现成的方案有人会问TM4C1294 自己带 6KB 的 EEPROM为什么还要外挂 MRAM这个问题我当初也纠结过。6KB 对于存几个校准参数确实够用但这个项目要存的是带时间戳的运行日志每条记录 32 字节一天五百条就是 16KBEEPROM 根本装不下。而且 TM4C 的 EEPROM 写入需要专门的 EEPROM 编程时序写入期间 CPU 会停顿对实时性有影响。那用外部 SPI Flash 呢容量是够了但前面说了寿命问题。有人会说加文件系统做磨损均衡但磨损均衡算法本身要占资源而且均衡只能延缓不能根治在每天几百次写入的场景下算法复杂度带来的收益并不划算。MRAM 直接把这个维度的问题消掉了代码里想写就写不用管擦除块、不用管均衡逻辑简单很多。至于 FRAM容量和价格是硬伤。4Mbit 的 FRAM 价格比同容量 MRAM 贵不少而且供货周期不稳定。综合下来MR25H40CDF 在这个容量段和可靠性要求下是性价比最平衡的选择。2. 硬件连接与 SPI 时序要点2.1 引脚连接与片选策略MR25H40CDF 是标准的 8 引脚 SOIC 封装引脚定义很清晰VCC、GND、SCK、SI、SO、CS、WP、HOLD。WP 是写保护HOLD 是暂停通信这两个引脚在常规使用中直接拉高就行除非你要做硬件写保护或者多主机共享总线。和 TM4C1294 的连接我用的是 SSI2 模块映射到 PB4 到 PB7 这组引脚。具体对应关系是PB4 接 CSPB5 接 SCKPB6 接 SI主机输出从机输入PB7 接 SO主机输入从机输出。这里有个细节要注意TM4C1294 的 SSI 引脚是固定映射的不能随便换选 SSI2 就得用这组。如果你用 SSI0 就是 PA2 到 PA5SSI1 是 PE0 到 PE3SSI3 是 PF0 到 PF3具体查数据手册的引脚复用表。片选这块我强烈建议用硬件片选也就是让 SSI 模块自己控制 CS 引脚。TM4C 的 SSI 在配置为 SPI 主机模式时FSS 引脚会自动在传输期间拉低。用硬件片选的好处是时序精确不会因为软件操作 GPIO 的延迟导致 CS 和 SCK 的相位关系出问题。我见过有人用软件片选在高速时钟下偶尔出现第一个字节丢失的情况就是 CS 拉低和第一个 SCK 边沿之间的建立时间不够。注意MR25H40CDF 的 CS 建立时间要求是 5ns 以上保持时间也是 5ns。TM4C 的硬件片选在 40MHz 下完全满足但如果你用软件 GPIO 控制要确保在拉低 CS 之后至少插入几个 NOP 或者一个短延时再启动 SPI 传输。2.2 SPI 模式选择与时钟计算MR25H40CDF 支持 SPI 模式 0 和模式 3。模式 0 是 CPOL0、CPHA0时钟空闲低电平数据在第一个边沿上升沿采样模式 3 是 CPOL1、CPHA1时钟空闲高电平数据在第二个边沿上升沿采样。两种模式都能用我习惯用模式 0因为大部分逻辑分析仪和示波器默认就是按模式 0 解码的调试方便。时钟频率方面MR25H40CDF 最高支持 40MHz。TM4C1294 的 SSI 时钟来源于系统时钟分频系统时钟 120MHz 的情况下要得到 40MHz 需要分频系数为 3即 120/340MHz。但实际配置时要注意SSI 的时钟分频寄存器 CPSR 和 SCR 是两级分频最终时钟 系统时钟 / (CPSR × (1 SCR))。要得到 40MHz可以设 CPSR2、SCR0得到 120/(2×1)60MHz这超了设 CPSR3、SCR0得到 120/(3×1)40MHz正好。但 40MHz 是芯片的极限值实际布线如果走线较长或者有干扰建议降到 20MHz 甚至 10MHz 先调通再逐步往上提。我实测下来在普通双面板、走线 5cm 左右的情况下40MHz 偶尔会出现误码降到 25MHz 就非常稳定。工业环境里电磁干扰大我最终产品上用的是 20MHz牺牲一点速度换可靠性这笔账划算。2.3 电源与去耦MR25H40CDF 的电源引脚旁边必须放一个 0.1μF 的陶瓷电容越靠近芯片越好这是标配。但很多人忽略了一点MRAM 在写入瞬间的电流需求比读取大虽然不像 Flash 编程那样有大的尖峰但在高频写入时电源纹波会变大。我在 VCC 和 GND 之间额外并了一个 1μF 的电容实测写入时的电源噪声明显降低。另外WP 和 HOLD 引脚不要悬空。虽然内部有上拉但工业环境下悬空引脚容易受干扰导致误触发写保护或暂停。我都是直接通过 10K 电阻上拉到 VCC确保状态确定。3. 软件驱动开发从寄存器到库函数3.1 TivaWare 初始化流程TM4C1294 的开发环境我用的是 CCS 加 TivaWare 库。初始化 SSI2 的步骤大致如下先使能 SSI2 和 GPIOB 的时钟然后配置 PB4 到 PB7 为 SSI 复用功能接着配置 SSI2 为 SPI 主机模式、模式 0、16 位数据宽度MRAM 支持 8 位和 16 位访问用 16 位传输效率更高最后使能 SSI2。这里有个坑要提醒TivaWare 的SSIConfigSetExpClk函数里有个参数是数据宽度可以设 4 到 16 位。MR25H40CDF 的指令集是基于 8 位字节的但数据阶段可以用 16 位传输来提速。我一般设 8 位因为指令、地址、数据混在一起用 8 位处理逻辑最清晰不容易出错。等调通了再考虑优化成 16 位。初始化代码大概长这样// 使能外设时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); // 配置引脚复用 GPIOPinConfigure(GPIO_PB4_SSI2FSS); GPIOPinConfigure(GPIO_PB5_SSI2CLK); GPIOPinConfigure(GPIO_PB6_SSI2XDAT0); GPIOPinConfigure(GPIO_PB7_SSI2XDAT1); GPIOPinTypeSSI(GPIO_PORTB_BASE, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); // 配置 SSI2 为主机模式模式 08 位数据20MHz 时钟 SSIConfigSetExpClk(SSI2_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 20000000, 8); // 使能 SSI2 SSIEnable(SSI2_BASE);SysCtlClockGet()返回的是系统时钟频率TM4C1294 默认是 120MHz。SSIConfigSetExpClk会自动计算分频系数你只需要给目标频率就行比手动算寄存器省事。3.2 MRAM 指令集与读写操作MR25H40CDF 的指令集和标准 SPI Flash 类似但更简洁因为它不需要擦除。核心指令有这几个指令名称指令码功能说明WREN0x06写使能写入前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据读操作的流程是拉低 CS发送 0x03发送 24 位地址MR25H40CDF 是 4Mbit需要 19 位地址但按 24 位发送高位补零然后连续读取数据地址自动递增。写操作的流程是拉低 CS发送 0x06 写使能拉高 CS再拉低 CS发送 0x02发送 24 位地址然后发送要写入的数据最后拉高 CS 触发内部写入。这里的关键点是写使能指令和写指令之间必须有一次 CS 的上升沿这是很多新手容易漏掉的。WREN 是一个独立的事务发完之后 CS 要拉高然后再开始 WRITE 事务。我当初调试的时候就是忘了这一步数据死活写不进去查了半天时序图才发现。写操作完成后MRAM 内部有一个写入周期典型值 13ms 左右数据手册标的是最大 13ms。但和 Flash 不同的是MRAM 的写入周期内你不需要轮询状态寄存器等待完成因为它的写入是纳秒级的那个 13ms 是内部错误校正和刷新逻辑的时间。实际上你发完数据拉高 CS 之后数据就已经在存储单元里了状态寄存器的 WIP 位会很快清零。我实测下来连续写入之间几乎不需要等待直接发下一笔就行。3.3 地址管理与数据组织4Mbit 等于 512KB地址范围 0x00000 到 0x7FFFF。这个空间怎么规划取决于你的应用。我的做法是分成几个区域前 4KB 存设备参数和校准数据中间 500KB 存运行日志最后 8KB 存掉电保护的关键变量。日志区的管理我用了一个简单的环形缓冲区思路。维护一个写指针每次写新记录时指针递增写到末尾就回绕到日志区起始地址。同时维护一个记录计数器存在参数区里每次写入后更新。这样即使掉电重启后读计数器就知道写了多少条写指针也能推算出来。因为 MRAM 写入不需要擦除回绕的时候直接覆盖旧数据就行不用做任何特殊处理。实操心得MRAM 虽然写入寿命近乎无限但地址空间的磨损均衡仍然值得考虑。虽然不像 Flash 那样有物理损伤但长期只写同一地址区域理论上存在局部老化差异。我的做法是日志区用环形缓冲天然实现了地址轮转参数区因为写入频率低直接固定地址就行。4. 完整读写实现与性能实测4.1 单字节读写函数实现先看最基础的字节读写。读函数uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; // 拉低 CS SSIDataPut(SSI2_BASE, 0x03); // READ 指令 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)); SSIDataPut(SSI2_BASE, 0x00); // 发送哑数据以产生时钟 while(SSIBusy(SSI2_BASE)); SSIDataGet(SSI2_BASE, (uint32_t*)data); return data; }这里用的是 TivaWare 的SSIDataPut和SSIDataGet配合SSIBusy轮询。注意SSIDataGet的参数是uint32_t*但 SSI 配置为 8 位时实际有效数据在低 8 位所以用uint8_t接收时要强制转换一下。写函数void MRAM_WriteByte(uint32_t addr, uint8_t data) { // 写使能 SSIDataPut(SSI2_BASE, 0x06); while(SSIBusy(SSI2_BASE)); // 写指令 SSIDataPut(SSI2_BASE, 0x02); 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)); SSIDataPut(SSI2_BASE, data); while(SSIBusy(SSI2_BASE)); // 等待写入完成 SysCtlDelay(SysCtlClockGet() / 3000); // 约 1ms 延时 }那个延时是保险起见加的实际上 MRAM 写入很快但数据手册建议在连续写入之间留一点时间让内部逻辑完成。1ms 对于日志记录场景完全可接受。4.2 页写入与批量传输优化单字节读写效率太低每次都要发指令和地址。MR25H40CDF 支持连续读写地址自动递增所以批量传输时只需要发一次指令和起始地址后面连续发数据就行。我封装了一个页写入函数一次最多写 256 字节跨页也没问题MRAM 不像 Flash 有页边界限制void MRAM_WritePage(uint32_t addr, uint8_t *buf, uint16_t len) { SSIDataPut(SSI2_BASE, 0x06); // WREN while(SSIBusy(SSI2_BASE)); SSIDataPut(SSI2_BASE, 0x02); // WRITE 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(uint16_t i 0; i len; i) { SSIDataPut(SSI2_BASE, buf[i]); while(SSIBusy(SSI2_BASE)); } SysCtlDelay(SysCtlClockGet() / 3000); }实测下来20MHz 时钟下写 256 字节大约耗时 130μs加上 1ms 延时总共约 1.13ms。如果去掉延时连续写 256 字节只要 130μs 左右。对于日志记录1ms 的间隔完全够用。读操作更快256 字节连续读在 20MHz 下约 120μs没有额外延时。4.3 掉电保护与数据完整性工业场景最怕的是写到一半掉电数据变成半新半旧。MRAM 的写入是原子性的吗严格来说单个存储单元的写入是原子的但多字节传输过程中掉电已经写入的字节会保留未写入的不会。所以如果你写一条 32 字节的记录写到第 16 字节时掉电前 16 字节是新数据后 16 字节是旧数据这条记录就废了。我的解决方案是双缓冲加校验。每条记录写两份分别存在两个区域每份带一个 CRC16 校验和和一个序列号。读取时比较两份的序列号和校验和取有效的那份。如果两份都有效且序列号相同取任意一份如果序列号不同取序列号大的如果某份校验失败取另一份。这样即使写入过程中掉电最多丢失当前这条记录不会破坏历史数据。这个逻辑听起来复杂但代码实现也就几十行。关键是 CRC16 的计算要快我用的是查表法256 字节的表存在 Flash 里计算 32 字节的 CRC 只要几十个时钟周期。注意双缓冲会占用双倍空间4Mbit 的 MRAM 实际可用日志容量减半。如果容量紧张可以改成单缓冲加“写入完成标志”的方案先写数据再写一个标志字节表示本条有效。掉电时如果标志没写成功这条记录就丢弃。这个方案省空间但逻辑上稍微复杂一点要处理好标志字节本身的原子性。5. 常见问题排查与避坑指南5.1 读写失败问题速查表现象可能原因排查方法解决方案读回全 0xFFCS 未正确拉低示波器看 CS 引脚检查片选配置确认硬件片选使能读回全 0x00MISO 未连接或上拉万用表测 SO 引脚检查 PB7 连接确认无虚焊写入后读回旧数据WREN 未发送或 CS 时序错误逻辑分析仪抓时序确认 WREN 和 WRITE 之间有 CS 上升沿偶尔误码时钟太快或走线干扰降低时钟测试降到 20MHz 或加屏蔽线写入后数据错位地址发送位数错误检查地址移位逻辑确认发送 24 位地址高位补零状态寄存器读不到指令码错误核对数据手册RDSR 是 0x05不是 0x045.2 那些年我踩过的坑第一个坑是忘记发 WREN。MRAM 上电后默认是写禁止状态任何 WRITE 指令都会被忽略。我当初调了半天读出来全是旧数据还以为芯片坏了。后来用逻辑分析仪抓波形发现 WRITE 指令发出去了但数据没变才想起来查状态寄存器发现 WEL 位是 0。加上 WREN 之后一切正常。第二个坑是CS 控制方式。我一开始用软件 GPIO 控制 CS在 20MHz 下偶尔出现第一个字节丢失。后来改成硬件片选问题消失。TM4C 的 SSI 在硬件片选模式下FSS 引脚会在传输开始前自动拉低传输结束后自动拉高时序由硬件保证比软件可靠得多。第三个坑是电源噪声。在实验室用 USB 供电一切正常装到设备上用工控电源就偶尔出错。后来用示波器看电源纹波发现写入瞬间有 200mV 的尖峰。加了 1μF 和 0.1μF 并联去耦之后解决。工业环境的电源质量参差不齐去耦电容宁多勿少。第四个坑是地址溢出。MR25H40CDF 是 512KB地址范围 0x00000 到 0x7FFFF。我一开始用 32 位地址变量写到了 0x80000 以上结果回绕到了 0x00000把参数区覆盖了。后来加了地址范围检查写入前先判断地址是否合法避免越界。5.3 调试工具与技巧逻辑分析仪是调 SPI 的必备工具。我用的是 Saleae 的 8 通道版本配合它的 SPI 解码器能直接看到指令、地址、数据的十六进制值比看波形直观得多。设置触发条件为 CS 下降沿就能抓到每一次 SPI 事务。如果没有逻辑分析仪用示波器看 SCK 和 MOSI 也能大致判断。重点看 CS 拉低之后第一个 SCK 边沿之前MOSI 上的数据是否稳定。如果数据在 SCK 边沿附近跳变说明建立时间不够需要降低时钟或调整相位。软件层面我习惯在驱动里加一个自测函数上电时先写一个已知模式到测试地址再读回来比较。如果自测失败就点亮错误指示灯或者通过串口输出错误码。这个自测函数在产线测试和现场排查时特别有用能快速定位是硬件问题还是软件问题。6. 性能优化与扩展思路6.1 从 20MHz 到 40MHz 的提速尝试前面提到我最终产品用的是 20MHz但在实验室里我试过推到 40MHz。方法很简单把SSIConfigSetExpClk的频率参数改成 40000000然后确保系统时钟是 120MHz。实测在短走线小于 3cm的情况下40MHz 可以稳定工作读写 256 字节的时间缩短到约 65μs。但要注意40MHz 下对 PCB 布线要求更高。SCK 走线要尽量短最好包地MOSI 和 MISO 不要平行走太长避免串扰CS 走线也要短确保片选信号的边沿陡峭。如果做不到这些还是老老实实用 20MHz。6.2 用 DMA 进一步降低 CPU 占用TM4C1294 的 SSI 支持 DMA 传输。对于大批量数据读写比如一次读 4KB 日志用 DMA 可以把 CPU 解放出来。配置 DMA 的步骤是使能 DMA 时钟配置 DMA 通道的源地址、目的地址、传输长度然后使能 SSI 的 DMA 请求。TivaWare 里有SSIDMAEnable函数可以打开 SSI 的 DMA 使能位。不过 DMA 配置相对复杂而且对于小数据量小于 256 字节的传输DMA 的配置开销可能比直接轮询还大。我的建议是如果单次传输超过 512 字节用 DMA否则直接轮询就行代码简单不容易出错。6.3 数据压缩与存储密度提升512KB 听起来不小但如果每条日志 32 字节一天 500 条一年就是 5.8MB远超容量。所以要么定期清理旧数据要么做数据压缩。我的做法是日志记录只存变化量比如温度、电压这些模拟量如果和上一条相比变化小于阈值就不存新记录只更新时间戳。这样实际存储量能降到原来的三分之一左右。另外时间戳可以用相对时间而不是绝对时间。上电时从 RTC 读一次绝对时间之后每条记录只存相对于上电时刻的秒数用 4 字节表示比存完整的年月日时分秒省 2 字节。别小看这 2 字节一年下来能省几百 KB。6.4 多芯片级联扩展容量如果 512KB 还不够可以用多个 MR25H40CDF 级联。每个芯片独立片选共用 SCK、MOSI、MISO。软件上维护一个芯片索引根据地址范围选择对应的片选引脚。TM4C1294 有 4 个 SSI 模块每个模块可以接一个 MRAM这样就是 2MB 容量。如果还不够用 GPIO 扩展片选理论上可以接很多个但走线会变复杂信号完整性要仔细设计。级联的代价是地址管理逻辑变复杂而且每个芯片的片选走线要等长否则高速下时序不一致。我的建议是能用大容量单芯片就用单芯片级联是最后的选择。7. 实际项目中的经验沉淀这个项目从选型到量产花了大概三个月中间经历了两版硬件改版和无数次软件调试。回过头看最大的体会是存储方案的选择不能只看容量和价格写入寿命、掉电行为、接口复杂度这些隐性成本往往更关键。MRAM 贵是贵了点但它省掉了磨损均衡算法、省掉了擦除等待、省掉了掉电保护电路综合下来总成本反而更低。另一个体会是 SPI 调试一定要有趁手的工具。逻辑分析仪看起来贵但它能把你从“猜问题”变成“看问题”调试效率提升不是一点半点。我见过太多人用 printf 调试 SPI折腾几天找不到原因逻辑分析仪一挂上去五分钟就定位了。最后分享一个小技巧MRAM 的写保护引脚 WP 可以接到 MCU 的一个 GPIO 上平时拉高允许写入在关键参数更新完成后拉低锁定。这样即使程序跑飞误写参数区硬件写保护也能兜底。这个设计在工业产品里很实用成本几乎为零但可靠性提升明显。这个方案后续还可以往两个方向扩展一是结合 TM4C1294 的 AES 硬件加密模块对存储的数据做加密适合对数据安全有要求的场景二是把 MRAM 作为文件系统介质移植一个轻量级的 FAT 或 LittleFS方便和上位机交换数据。这两个方向我都在预研等有成熟结果再另开一篇聊。
返回列表