ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于SPI MRAM的工业掉电数据保存方案:MR25H40CDF与TM4C1299实战

基于SPI MRAM的工业掉电数据保存方案:MR25H40CDF与TM4C1299实战 直接讲正事。最近在做一块工业控制板需要在掉电瞬间把运行参数、故障日志和校准数据可靠地存下来。项目里选了 MR25H40CDF 这颗 4Mbit SPI MRAM搭配 TM4C1299KCZAD 主控。两个器件搭起来的这套存储方案在工业和嵌入式场景里用起来很顺手但也埋了不少值得展开说的细节——从数据手册上的电气参数到 SPI 驱动时序的取舍再到掉电保护策略都有很多坑可以聊。这篇文章就把我在实际项目中选型、接线、调驱动、做数据完整性设计的完整过程写出来给正在做类似方案的读者一个可直接参考的基线。1. 为什么工业存储场景我选了 SPI MRAM——MR25H40CDF 的核心优势与产品定位1.1 MRAM 和 NOR Flash 的本质差异决定工业选型的关键先聊一个基础问题工业现场的数据存储为什么不能继续用 NOR Flash 了。NOR Flash 的编程机制决定了写操作必须先擦除再写入擦除以扇区为单位一个扇区往往是 4KB 甚至更大。这意味着哪怕只是修改一个字节逻辑上也要经历读出整个扇区→在RAM中修改→擦除扇区→写回整个扇区的过程。如果中途掉电这个扇区的数据大概率损坏而且擦写次数有上限典型 NOR Flash 的 endurance 是 10 万次左右。对于频繁记录运行参数的工业设备尤其是每隔几十毫秒就更新一次状态数据的场景NOR Flash 的寿命和掉电一致性都很勉强。MR25H40CDF 是 Everspin 的 4Mbit512KB串行 SPI MRAM它解决了上面两个最痛的点。MRAM 的存储单元本质上是磁阻随机存储器写操作直接翻转磁化方向不依赖电荷擦写所以不存在擦除周期也没有写寿命限制规格书上写的是无限次读写。写操作可以按字节任意寻址一个 WRITE 指令发 8 位地址加数据数据就写进去了不需要先擦后写也不存在写放大问题。更关键的是它是非易失存储掉电不丢数据。这样一眼看过去MRAM 几乎就是能像 SRAM 一样读写、但断电数据保持的存储器件。1.2 MR25H40CDF 的具体参数解读这颗芯片具体的规格参数直接决定驱动的设计方式。我列一下项目里用到的几个关键数字容量4Mbit 512KB对于存储参数区、日志区、配置表这种用途很够用。接口SPI 串行接口支持 Mode 0CPOL0CPHA0和 Mode 3支持最高 40MHz 时钟。供电2.7V~3.6V典型 3.3V。我们的板子主控和存储都跑 3.3V不需要电平转换外围省事。温度范围工业级 -40℃~85℃。数据保持常规数据手册标称超过 20 年而且不受擦写次数衰减影响。写时序每个字节的写入在内部是即时完成的没有类似 Flash 的页编程等待时间tPP状态寄存器里的 WIP 位几乎瞬间清零。但为了兼容性和可靠性驱动里依然保留了写完成轮询。这套参数组合的实际收益是什么一是不需要文件系统/磨损均衡/坏块管理这种Flash专用软件层二是掉电保护逻辑可以做得更简单三是写入速度上限很高。很多用过串行Flash的老工程师第一次用MRAM会下意识去找页大小和擦除时间实际上这颗芯片完全没有这些概念。这一点值得单独说用 MR25H40CDF 写数据只要把 CS 拉低发地址发数据再拉高 CS数据就落进去了。整个流程和写 SRAM 一样直白。1.3 TM4C1299KCZAD 的 SSI 模块适配性TM4C1299KCZAD 是 TI 的 Cortex-M4F 主控主频 120MHz内部资源很丰富。我需要它通过 SPI 接口访问 MRAM用到的是它的 SSISynchronous Serial Interface模块。这颗芯片有 4 个 SSI 模块可以灵活配置为 SPI、MICROWIRE 或 TI 同步串行协议。我的方案里用 SSI0工作在主模式CPOL0、CPHA0对应 MRAM 的 SPI Mode 0。配置 SSI 的时候有个关键点TM4C1299 的 SSI 时钟来自系统时钟经过分频得到。我用 120MHz 系统时钟把 SSI 的时钟分频配置成 10MHz 或 20MHz。20MHz 已经能满足应用40MHz 满速虽然也可以但 PCB 布线、寄生电容、杜邦线等因素都会影响信号质量。嵌入式项目里跑 SPI 外设优先求稳再求快。选 TM4C1299KCZAD 还有一个实际考量它有丰富的 GPIO 和硬件片选控制逻辑软件上可以用 SSI 自带的 FSSFrame Select引脚做片选也可以把任意 GPIO 拉低做片选。我项目里为了方便逻辑控制和掉电时序管理用的是 GPIO 片选方式这样在异常复位时可以直接把所有外设片选拉高避免半途中的数据被继续写入。2. 硬件连接与评估板准备——先把 SPI 物理层搭对2.1 引脚分配和常见接法先说物理连接。MR25H40CDF 是标准 8 脚封装SOIC-8 或 TSSOP-8 视具体后缀而定引脚定义基本固定引脚名称功能接入 TM4C1299KCZADCS#片选低有效GPIO 或 SSI FSS 引脚SCKSPI 时钟SSI0Clk例如 PB4/PH1SI串行输入从机视角SSI0Tx例如 PD3/PH0SO串行输出从机视角SSI0Rx例如 PD2/PH1WP#写保护低有效接 3.3V禁用保护或 GPIO 控制HOLD#暂停通信低有效接 3.3V禁用 HOLD 功能VCC3.3V 电源3.3VVSS地GND在实际项目中WP# 和 HOLD# 这两个引脚特别容易忽略。WP# 的作用是硬件层面禁止写状态寄存器和被保护区域如果我们的应用要频繁写数据直接把 WP# 拉高即可。HOLD# 是暂停 SPI 通信的引脚当它被拉低时器件会忽略 SCK 和 SI 上的信号并保持当前输出状态。日常应用中 HOLD# 拉高就行。有些工程师会把这两个引脚悬空这在逻辑上也许能用但浮空引脚在工业环境里接收噪声后可能产生不可预期的行为所以我都建议显式接上拉电阻到 VCC。SCK、SI、SO 三个信号和主控之间是点对点连接信号完整性处理相对简单。SRAM 类接口不像高速 DDR 那样讲究阻抗匹配但 SPI 时钟 20MHz 以上时串联一个 22Ω~33Ω 的电阻做源端阻尼可以明显减少振铃。我做样板时一开始没加串阻用示波器看 CS 上升沿和 SCK 沿附近有明显过冲后来在 SCK、SI 和 CS 路径上都串了 22Ω 电阻波形干净了很多。2.2 评估板上的实际接线方式如果你的开发环境是 EK-TM4C1294XL 这类评估板板上主控就是 TM4C1294NCPDT基本兼容 TM4C1299KCZAD 的 SPI 外设使用方式或者打板之前先用面包板/杜邦线验证我建议第一版调试时先不要跑 40MHz。杜邦线和面包板的寄生电容很大20MHz 波形就已经开始畸变。我在面包板验证阶段用的是 10MHz板级调试阶段才切到 20MHz。这个节奏很实用——先用低速打通读写流程再逐步提升速率能最大程度减少一上来信号就乱的排查成本。另外MR25H40CDF 的电源去耦不能省。VCC 和 VSS 之间放一个 0.1μF 陶瓷电容尽量靠近芯片引脚放置。如果 PCB 面积允许再加一个 4.7μF 或 10μF 的钽电容做低频去耦防止 SPI 突发写操作时电源电压跌落。工业现场电源噪声普遍比实验室大去耦做好了能省掉很多莫名其妙的读写失败问题。2.3 上电时序和电源设计细节MRAM 本身的上电时序不像有些 Flash 那样苛刻但我仍然按照电源稳定后至少等待几毫秒再访问的策略来设计软件启动流程。具体做法是在 main 函数初始化阶段先读取一个魔法数验证 SPI 通信是否正常。如果读取到的值和预设不一致就说明通信链路有问题系统会通过状态 LED 或日志上报异常而不是带着坏配置继续跑。这种自检逻辑每套产品我都建议加上。还有一点值得注意如果系统掉电保护需要靠一个大电容维持几毫秒的主控运行时间那么 MRAM、主控、电容的供电拓扑要规划好。掉电瞬间我们希望主控检测到电压跌落然后在一个极短的时间窗口内把关键数据写入 MRAM。具体做法我在第 4 章详细展开。这里只提硬件层面的核心掉电检测信号例如通过 GPIO 输入接一个电压比较器或主控内置 BOR 中断要能触发中断同时确保 MRAM 的 VCC 至少在中断触发后再维持 1ms 以上。这个 1ms 窗口足够 MRAM 写完几十个字节的应急数据区块。3. SPI 驱动初始化与 MRAM 状态机——不像 Flash 那样简单的读写流程3.1 SSI 模块的配置参数在 TM4C1299KCZAD 上使用 SSI0 访问 MR25H40CDF我用的是 TI DriverLib 的 API。配置的核心是 SSIConfigSetExpClk它的参数直接决定 SPI 模式、时钟速率和位宽。#include hw_memmap.h #include ssi.h #include gpio.h #include sysctl.h #define MRAM_CS_BASE GPIO_PORTD_BASE #define MRAM_CS_PIN GPIO_PIN_1 void MRAM_SSI0_Init(void) { // 使能 SSI0 和 GPIO 外设时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI0); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); // 配置 PD0SSI0Clk, PD1GPIO CS(手动控制), PD2SSI0Tx, PD3SSI0Rx GPIOPinConfigure(GPIO_PD0_SSI0CLK); GPIOPinConfigure(GPIO_PD2_SSI0TX); GPIOPinConfigure(GPIO_PD3_SSI0RX); GPIOPinTypeSSI(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_2 | GPIO_PIN_3); // 片选引脚配置为输出默认拉高 GPIOPinTypeGPIOOutput(MRAM_CS_BASE, MRAM_CS_PIN); GPIOPinWrite(MRAM_CS_BASE, MRAM_CS_PIN, MRAM_CS_PIN); // SSI0 配置主模式SPI Mode 0 (CPOL0, CPHA0)8位数据20MHz SSIConfigSetExpClk(SSI0_BASE, SysCtlClockGet(), SSI_CLOCK_20MHZ, SSI_MODE_MASTER, SSI_FORMAT_SPI, 0, 8); SSIEnable(SSI0_BASE); }这段代码有几个容易踩的点。第一个是 GPIOPinConfigure 必须和 GPIOPinTypeSSI 配套使用。如果只调用了 GPIOPinTypeSSI 而不调用 GPIOPinConfigure引脚可能停留在默认的外设功能上导致 SSI 信号没接到想要的引脚。第二个是 FSS片选信号如果不用 SSI 硬件 FSS 而改用 GPIO 控制片选那么 FSS 引脚可以不用配置为 SSI 功能甚至可以挪作他用。我在代码里用 PD1 做 GPIO 片选这样每次 SPI 事务的 CS 拉高拉低完全由软件控制时序可控性更强。3.2 指令集与状态机设计MR25H40CDF 的指令集和常见的串行 NOR Flash 很像但语义上有微妙差别。我项目里实际用到的指令不多就这几条指令操作码功能说明WREN0x06写使能几乎所有写操作前都需要执行WRDI0x04写禁止很少主动用READ0x03读数据从指定地址开始连续读FAST_READ0x0B快速读多一个 dummy 字节高速读时用WRITE0x02写数据按字节写无页写入限制WRSR0x01写状态寄存器配置 WP# 保护区域RDSR0x05读状态寄存器检查 WIP、SRWD、块保护位这里重点说 WREN。不少初学者会误以为 MRAM 写数据不需要写使能因为 MRAM 写入没有擦写损耗。但数据手册的写时序图明确要求执行 WRITE 指令之前必须先发 WREN0x06指令将状态寄存器中的 WELWrite Enable Latch置 1。如果不发 WREN 而直接发 WRITE器件会忽略写入操作。这就是一个非常典型的读手册时一晃而过、调试时卡半天的坑。我一开始复用过去 NOR Flash 的驱动框架保留了先 WREN 再 WRITE的习惯所以没踩到但如果你是从零写驱动建议把 WREN 前置作为一个强制流程。另一个和 Flash 明显不同的点MRAM 没有页写限制。串行 Flash 通常限制一个页编程指令最多写 256 字节超过会地址回绕。MR25H40CDF 的 WRITE 指令没有这个限制理论上你可以在 CS 拉低期间连续写入任意多个字节地址到末尾会回绕到 0。但为了代码可读性和可维护性我仍然人为规定单次写事务不超过 256 字节。这个限制纯粹是软件层面的目的是和日志包结构对齐而不是器件要求。3.3 驱动代码从写使能到页写回读验证基础读写函数void MRAM_CS_Low(void) { GPIOPinWrite(MRAM_CS_BASE, MRAM_CS_PIN, 0); } void MRAM_CS_High(void) { GPIOPinWrite(MRAM_CS_BASE, MRAM_CS_PIN, MRAM_CS_PIN); } uint8_t MRAM_SpiTransfer(uint8_t data) { SSIDataPut(SSI0_BASE, data); uint8_t rx; while(SSIBusy(SSI0_BASE)); SSIDataGet(SSI0_BASE, rx); return rx; } void MRAM_WriteEnable(void) { MRAM_CS_Low(); MRAM_SpiTransfer(0x06); // WREN MRAM_CS_High(); } void MRAM_WriteByte(uint32_t addr, uint8_t val) { MRAM_WriteEnable(); MRAM_CS_Low(); MRAM_SpiTransfer(0x02); // WRITE MRAM_SpiTransfer((addr 16) 0xFF); MRAM_SpiTransfer((addr 8) 0xFF); MRAM_SpiTransfer(addr 0xFF); MRAM_SpiTransfer(val); MRAM_CS_High(); } uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t rx; MRAM_CS_Low(); MRAM_SpiTransfer(0x03); // READ MRAM_SpiTransfer((addr 16) 0xFF); MRAM_SpiTransfer((addr 8) 0xFF); MRAM_SpiTransfer(addr 0xFF); rx MRAM_SpiTransfer(0x00); MRAM_CS_High(); return rx; }多字节页写实际项目里的主力函数void MRAM_WriteBuffer(uint32_t addr, const uint8_t *buf, uint32_t len) { uint32_t i; MRAM_WriteEnable(); MRAM_CS_Low(); MRAM_SpiTransfer(0x02); MRAM_SpiTransfer((addr 16) 0xFF); MRAM_SpiTransfer((addr 8) 0xFF); MRAM_SpiTransfer(addr 0xFF); for (i 0; i len; i) { MRAM_SpiTransfer(buf[i]); } MRAM_CS_High(); }这里有个值得解释的细节为什么每个写操作前都要单独发一次 WREN。从数据手册时序来看WREN 的作用是置位 WEL而 WEL 在每个写操作完成之后会自动清零。所以你不能幻想刚才写过一次现在还可以继续写。每次事务必须完整执行WREN → CS低 → WRITE... → CS高这个过程。有的驱动优化会把 WREN 放在 CS 拉低之后紧接着发另一种则在 CS 拉低之前发两者在时序上都可以接受但要保证 CS 从高到低的跳变发生在 WREN 之后。我用的是先拉高 CS → 发 WREN → 再拉低 CS 发 WRITE的模式避免 WREN 在主控和从机之间的 CS 状态产生歧义。3.4 一个容易忽略的细节读状态寄存器轮询策略MRAM 写入虽然是即时完成但稳妥起见驱动上还是保留了读状态寄存器的操作。RDSR 返回的状态字节中bit0 是 WIPWrite In Progress。对于 MR25H40CDFWIP 通常读出就是 0因为写入内部操作极快。这是 MRAM 和 Flash 最大的体验差异Flash 发一个页编程指令后可能需要等几毫秒甚至几十毫秒而 MRAM 理论上发完数据CS 拉高数据就已经稳定了。那为什么我仍然建议保留轮询两个原因。第一状态寄存器里还有块保护位BP3~BP0和 SRWD 位这些位的状态会影响写操作是否成功。如果 WP# 引脚被拉低并且状态寄存器里启用了块保护写操作会被直接拒绝。这时通过 RDSR 读取状态字节就能快速定位问题。第二如果 SPI 通信链路本身有问题比如时钟相位配置错了读到的状态寄存器数值也可能异常这本身就是一个有效的链路诊断手段。我的驱动程序里写了一个 MRAM_WaitReady()实际实现是读 RDSR 最多循环 100 次检查 WIP 位是否清零bool MRAM_WaitReady(void) { uint8_t status; uint32_t timeout 1000; do { MRAM_CS_Low(); MRAM_SpiTransfer(0x05); status MRAM_SpiTransfer(0x00); MRAM_CS_High(); if ((status 0x01) 0) return true; } while (--timeout); return false; }这个函数在初始化后第一次访问 MRAM 时调用一次后续正常读写时基本不会阻塞。它更像一个看门狗角色一旦出现异常状态能很快暴露问题。4. 数据完整性设计与掉电保存机制——工业现场真正考验人的地方4.1 规划 512KB 空间块区、目录区和数据区的划分MR25H40CDF 有 512KB 空间说大不大说小不小。如果只是当一块超级 EEPROM用随意散放数据后面扩展会很痛苦。我的建议是在软件层把存储空间切分成几个明确用途的区域类似一个轻量级文件系统但不引入完整文件系统那样重的依赖。我项目里的空间布局如下区块地址范围大小用途设备信息区0x00000 ~ 0x00003128B魔数、版本号、序列号、硬件版本运行参数区0x01000 ~ 0x01FFF4KB用户配置参数、网络配置、PID 参数等日志区0x02000 ~ 0x2FFFF192KB环形缓冲区记录事件日志和故障日志升级暂存区0x30000 ~ 0x7FFFF320KB固件升级时暂存固件包或专用数据文件每个分区起始地址都对齐到 4KB 或者至少 256B。MRAM 没有擦除粒度限制对齐纯粹是为了让软件里的地址计算更直观。设备信息区放一个魔数比如 0x5A5AA5A5和版本号每次上电时读取校验如果读出来不对就说明数据区可能被破坏或者芯片更换过系统进入恢复模式。4.2 掉电保护脏位标记、CRC、双缓冲工业设备最怕的是在写入过程中掉电留了个半残的数据块。MRAM 虽然写入速度快但如果恰好写到一半 CS 还没拉高时断电那一个字节或几个字节可能就是不一致状态。要解决这个问题单纯的 MRAM 硬件特性还不够软件上需要配合事务化写入机制。我采用的方案是脏位标记 CRC 双缓冲。以运行参数区为例将参数区划分为 A、B 两个槽位各 512B外加一个 4 字节的事务状态标记区。系统正常运行时始终从 A 槽读取参数。需要更新参数时先把新数据写入 B 槽计算整个 B 槽数据的 CRC32写在 B 槽末尾。全部写完后在事务状态标记区写入一个当前有效槽位B的标记。下一次上电时读取事务状态标记区确定有效槽位然后从对应槽位读取参数并校验 CRC32校验通过才加载。这个方案的成本是参数数据占用翻倍加上一次额外的标记区写入。但收益非常大无论掉电发生在哪个瞬间最多只是某个槽位的部分数据损坏另一个槽位还保留着上一次完整有效的版本加上标记区只有在整个槽位写完并校验之后才会更新软件永远不会加载到一个半成品参数。这就是数据库领域所说的事务的持久性和原子性在嵌入式场景下的落地方案。日志区的掉电处理更简单一些。日志是追加写模式每条日志记录包含一个 16 位长度、一个 16 位 CRC、一个 32 位时间戳和数据。读取日志时遇到 CRC 校验失败的记录就认为该条记录不完整停止向后读取。因为日志本身允许最后一条不完整只要前序数据完好后面覆盖写就行。4.3 掉电瞬间的应急写窗口设计很多工程师会担心一个问题就算方案再好掉电瞬间电压瞬间归零我哪里来得及写数据这里的核心在于硬件掉电检测电路和储能电容的配合。 我在板子上用一个比较器监测 3.3V 电源当电压跌落到 3.0V 以下时产生一个下降沿中断给 TM4C1299KCZAD 的 GPIO。在此之前MRAM 和主控的电源都已经被储能电容维持住。从中断触发到主控完全失去工作能力通常有几百微秒到几毫秒的时间窗口。在这个中断服务函数里我做的工作极其克制只做把当前运行的关键参数打包成一条 64 字节记录写入日志区的最后这一件事。这里有个经验——掉电保存逻辑不能贪多。你不能在中断里做大量计算、写多个区域、更新索引时间窗口太短做多了反而写不完。64 字节写入按 20MHz SPI 时钟算大约只需要 40μs 左右非常充裕。关键参数从内存拷贝到 buffer 中、计算 CRC、发 WREN、发 WRITE 指令、CS 拉高一气呵成。需要注意的是在掉电中断里不要再依赖 RTOS 的调度器或者锁机制直接用裸函数操作寄存器。TM4C1299KCZAD 的 Cortex-M4F 中断响应很快但如果你用的 RTOS 在中断里搞复杂的同步机制掉电那段窗口内可能因为上下文切换导致写不完。我的原则是掉电保存代码保持裸机风格不依赖任何操作系统服务。4.4 一个移植 FatFS 的简要思路可选项目里产生了一个新需求需要在设备上用 PC 机通过 USB 读取数据日志。如果日志区只是一个裸的环形缓冲区PC 端的解析程序就必须完全自定义格式维护成本高。后来我做了个折中在日志区的最前面放一个 FatFS 兼容的微型 FAT12 文件系统头然后把 MRAM 变成一块 192KB 的 FAT12 卷。FatFS 提供了对 SPI MRAM 的抽象层接口写磁盘的函数本质上是把读扇区/写扇区的接口映射到我刚写的 MRAM_ReadBuffer / MRAM_WriteBuffer 上。这样 PC 端插入 USB 读卡器如果板上有 USB MSC 功能就能直接读出日志文件。不过说实话对于纯嵌入式日志场景FAT 文件系统的日志写入效率并不高因为每写一条日志就要更新 FAT 表和目录项会产生大量额外写入。虽然 MRAM 不怕写但多事务操作会占用更多 CPU 时间。所以我实际是两套机制并存日志区用自定义环形缓冲到了产线测试或返厂诊断时通过一个配置位把环形缓冲批量转换成 FAT 文件导出。对 MRAM 来讲这种批量转换也只是普通的读写操作无损耗压力。5. 实测中踩过的坑与调试心得——数据手册不会写的内容5.1 时钟相位误配导致的奇偶字节错位第一个坑是 CPOL/CPHA 配置问题。MR25H40CDF 支持 SPI Mode 0 和 Mode 3但我在 SSI 初始化时第一版代码写成了 CPOL0、CPHA1Mode 1。结果是读出来的数据奇偶字节错位第一个字节偶然正确第二个字节高 4 位和低 4 位颠倒整个数据流像被吃掉半拍一样。排查过程其实有点折磨人。用逻辑分析仪抓 CS、SCK、SI、SO 波形能看到 SO 上的数据和主控发出的地址并不是完全对齐的总是在时钟沿附近错开半拍。后来重新细看数据手册里的时序图才意识到 Mode 0 的采样沿和输出沿是明确的数据在 SCK 上升沿被采样在 SCK 下降沿改变。把 SSIConfigSetExpClk 的相位参数从 1 改回 0问题马上消失。 这条经验其实很适合刚接触 SPI 的朋友当你发现读回的数据大部分对个别字节错先查时钟极性和相位再去怀疑器件本身。5.2 CS 控制时序和 SPI 事务之间的间隔第二个坑发生在高速连续读写时。MCU 的 SSI 外设在每个字节传输之间需要一点时间但硬件 SSI 会把字节无缝连起来。问题是软件在发完一个 WRITE 指令的最后一个字节后马上拉高 CS。由于 SSI 的移位寄存器有流水线延迟最后那个字节可能还没真正从 SO 引脚送完CS 就已经拉高了导致最后一个字节被截断。解决办法是在写末尾加一个哑巴等待在拉高 CS 之前确保 SSIBusy() 返回 false即 SSI 移位寄存器已经完全发送完毕。这是一个非常经典的外设驱动细节。我的原始代码里 MRAM_SpiTransfer 函数虽然内部有 SSIBusy 等待但那只是等待当前这一个字节发送完成。当最后一个字节写入 MRAM 后如果直接拉高 CS从机可能还没有锁存到这个字节。我后来在 MRAM_WriteBuffer 函数末尾添加了一个 while(SSIBusy(SSI0_BASE)); 的等待再执行 CS 拉高写覆盖率就到了 100%。5.3 状态寄存器里的块保护位和 WP# 的配合第三个坑是关于 Everspin MRAM 状态寄存器的块保护位。MR25H40CDF 支持通过状态寄存器的 BP 位设置写保护区域。默认状态下BP 位都是 0即全芯片可写。但如果你之前配置过状态寄存器或者初始化时序异常导致 BP 位非零那么对被保护区域的写入请求会被拒绝而且 WREN 指令也不会解除这种保护。这在我们项目中实际发生过一次。产测环节有一个脚本会往设备信息区写入序列号第一次运行正常第二次运行却写不进去。排查了一整个下午最后用 RDSR 读状态寄存器发现 BP3~BP0 变成了非零。原因很诡异产测脚本在掉电的过程中SPI 信号线上出现了毛刺恰好被 MRAM 识别为 WRSR 指令把错误值写进了状态寄存器。 从此以后我在初始化代码里强制调用一次 WRSR把状态寄存器置 0清掉所有块保护并且把 WP# 引脚用 10kΩ 电阻上拉。这个处理之后再也没出现过写保护问题。5.4 数据手册之外的实测表现温度特性和数据保持工业温度范围是我选这颗芯片的重要原因。实测中我把板子放在高低温箱里跑 -40℃ 到 85℃ 的循环在极端温度下反复读写 MRAM 数据没有出现数据翻转或读写失败。这比起某些消费级 EEPROM 在 85℃ 边缘出现写失败的情况稳健得多。MRAM 的磁存储机制本身对温度不太敏感这也是磁存储相比电荷存储的一个优势在极端温度下电荷泄漏的机制会导致 Flash 等器件的数据保持时间明显缩短而 MRAM 不存在这种电荷泄漏。数据保持这个参数短期内无法验证但从原理上讲MRAM 靠磁化方向保持数据不需要刷新数据保持能力应该远超普通铁电存储器FRAM。如果项目对长期无人值守运行有极高要求MRAM 是一个更让人放心的选择。6. 如果重新做一次我会怎么改进——设计复盘产品已经跑了一段时间整体方案稳定。但如果现在让我重新设计一次有几个点我会调整。第一片选的控制方式。当前用 GPIO 控制 CS这在低速和中等速率下完全没问题但如果要跑 40MHzGPIO 拉低拉高的软件时序会和 SSI 的数据发送节奏产生冲突。更好的做法是用 SSI 硬件 FSS 控制片选这样 FSS 会在硬件层面与 SCK 对齐时序更精确。代价是 FSS 信号的灵活度下降但换来的是更稳定的高速时序。第二写缓冲的 DMA 化。现在的 MRAM_WriteBuffer 是一字节一字节地调用 SSIDataPutCPU 占用较高。如果日志数据量大了比如每秒写 10 条记录CPU 负荷就会明显上升。TM4C1299KCZAD 的 SSI 支持 DMA 传输把要写的数据交给 DMA 控制器CPU 就可以去做其他任务。这个优化对大多数场景不是必须的但如果你要把日志记录频率推到极致DMA 是必须考虑的。第三增加一个独立的外部看门狗在出现死循环或 SPI 通信卡死时自动复位。MRAM 本身可靠性很高但主控死机后一切外设都可能异常。工业设备长期无人值守复位机制是保底。这只是产品设计层面的补充并不是 MRAM 本身的问题。从最终效果上看MR25H40CDF 和 TM4C1299KCZAD 的组合确实把传统NOR Flash 存储 磨损均衡 掉电保护的方案简化了一大截。不再需要复杂的 Flash 磨损计算不再担心擦除失败不需要页编程等待开发周期至少节省一周。工业现场的数据存储稳定和简单往往是正相关的。选一颗对的存储器件能让下游所有软件工作都变得顺理成章。如果你正在做类似的工业数据存储设计我建议把手头的 Flash 方案认真和 MRAM 对比一次重点考察掉电保护逻辑的复杂度、擦写寿命上限和日志写入实时性这三个维度结论应该会很明显。
返回列表