ARTICLE DETAIL

资讯详情

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

MRAM工业数据采集实战:TM4C123与MR25H40CDF驱动开发与掉电保护

MRAM工业数据采集实战:TM4C123与MR25H40CDF驱动开发与掉电保护 MRAM 这东西我第一次用的时候心里是打鼓的。EEPROM 和 Flash 用了十几年突然换一种既像 RAM 又像 Flash的存储器最直接的疑问就是它到底靠不靠谱掉电真能保住数据写入寿命真有那么夸张后来在一个工业数据采集项目里主控选了 TM4C123GH6PZ存储介质用 MR25H40CDF跑了大半年现场数据才算把这对组合摸透。这篇就把我从选型、接线、SPI 时序调试到掉电保护、磨损管理的完整过程摊开讲尤其是那些手册上不会写、只有实际调过才知道的细节。如果你正在做工业仪表、电力监测、医疗设备或者任何需要频繁写小数据 掉电不能丢的嵌入式项目这套方案值得认真看一遍。MR25H40CDF 是一颗 4Mbit 的 SPI MRAMTM4C123GH6PZ 是 TI 的 Cortex-M4 主控两者通过 SPI 对接逻辑不复杂但坑都藏在时序、片选和写入策略里。1. 为什么这个场景值得放弃 EEPROM 和 Flash1.1 工业现场对存储的三个硬要求先说说我为什么会被逼着去找 MRAM。那个项目是给配电柜做数据记录仪每 100ms 采一次三相电压电流算完有效值、功率、谐波之后要落盘。原始需求写得很轻描淡写数据需掉电保存保存间隔不大于 200ms。 真做起来才发现这是个要命的需求。EEPROM 的写入寿命典型值是 100 万次按 200ms 写一次算一天就是 43.2 万次两天多就把一个扇区写废了。就算做磨损均衡把数据摊到整个芯片上一颗 256Kbit 的 EEPROM 也就撑几个月。Flash 更麻烦写之前要擦除擦除是按扇区来的小数据频繁写会导致大量读改写操作寿命和实时性都扛不住。MRAM 的核心优势在这里就体现出来了写入不需要擦除字节级可写写入寿命标称 10^14 次以上MR25H40CDF 手册给的是无限次写入的表述实际按 10^14 量级理解。这意味着你可以像写 RAM 一样直接往里面怼数据不用管擦除、不用管均衡这对高频小数据写入场景是降维打击。1.2 MR25H40CDF 到底是个什么东西MR25H40CDF 是 Everspin 的 4Mbit SPI 非易失 MRAM注意几个关键参数这些直接决定你怎么用它参数数值实际意义容量4Mbit 512KB存工业数据绰绰有余接口SPI最高 40MHz速度接近 SRAM 读取供电2.7V ~ 3.6V3.3V 系统直接对接写入寿命10^14 次量级基本可以当无限次用数据保持20 年以上工业设备生命周期够用写入方式无需擦除字节可写这是和 Flash 最大的区别封装8-SOIC / 8-DFN布板友好它内部结构是磁隧道结MTJ靠磁化方向存数据不是电荷。所以它没有 Flash 那种擦除-编程的电荷注入过程写入延迟极低而且不存在擦除块的概念。这一点你在写驱动的时候会感受特别明显Flash 驱动要处理扇区擦除、页编程、忙等待MRAM 驱动基本就是发命令、写数据、完事。1.3 TM4C123GH6PZ 的 SPI 资源盘点TM4C123GH6PZ 是 TI Tiva C 系列的 Cortex-M480MHz 主频带 4 个 SSITI 管 SPI 叫 SSISynchronous Serial Interface模块。我选它是因为工业项目里 TI 的料稳定、供货好、外设文档全。它的 SSI 模块有几个特性需要提前搞清楚支持 Motorola SPI、TI SSI、National Microwire 三种帧格式MRAM 用标准 Motorola SPI 模式 0 或模式 3。FIFO 深度 8 字节收发各 8突发传输时能减少中断次数。时钟分频是整数分频主频 80MHz 下要得到合适的 SPI 时钟需要算一下。片选可以硬件自动控制也可以软件 GPIO 控制这个选择后面会专门讲。我用的 SSI0映射在 PA2~PA5CLK、FSS、RX、TX这是 TM4C123 的固定引脚映射不用像 STM32 那样折腾复用。这一点对硬件工程师很友好布板时直接照着 datasheet 的表格走就行。2. 硬件连接那些让你多打一版板子的细节2.1 引脚映射与最小系统接线先把接线说清楚。TM4C123GH6PZ 的 SSI0 引脚是固定的TM4C123 引脚功能接到 MR25H40CDFPA2SSI0ClkSCKPA3SSI0FssCS片选PA4SSI0RxSOMISOPA5SSI0TxSIMOSI3.3V电源VDDGND地VSSMR25H40CDF 还有两个引脚要注意WP写保护和 HOLD保持。这两个引脚在标准 SPI Flash 上也有但 MRAM 上的行为略有不同。WP 拉低时禁止写状态寄存器和写数据HOLD 拉低时暂停当前传输。我的做法是 WP 直接上拉到 3.3V不用的功能就别留隐患HOLD 也上拉除非你确实需要多设备共享总线时用 HOLD 做仲裁。注意MR25H40CDF 的 WP 和 HOLD 是低有效上拉电阻建议 10K不要悬空。我见过有人悬空导致偶发写入失败的案例查了两天才发现是 WP 引脚被干扰拉低了。2.2 去耦电容不是走过场MRAM 的写入电流比读取大虽然绝对值不大但瞬态电流变化会在电源上产生纹波。我在 VDD 和 GND 之间放了两个电容一个 100nF 的陶瓷电容紧贴芯片引脚一个 1uF 的放在附近。这不是玄学实测下来去掉 100nF 之后高速连续写入时偶发校验错误加上就没了。PCB 布局上SPI 的四根线尽量等长、短、远离高频干扰源。如果板上有大功率开关电源或者继电器SPI 走线要避开它们的回流路径。工业板子电磁环境恶劣这一步省不得。2.3 硬件片选还是软件片选这是个值得单独说的问题。TM4C123 的 SSI 支持硬件自动片选FSS 引脚由硬件在传输时自动拉低拉高也支持你把 FSS 当普通 GPIO 用软件控制。两种方式我都试过结论是单设备场景用硬件片选多设备或者需要精确控制片选时序的场景用软件片选。硬件片选的问题是TM4C123 的 FSS 在两次传输之间会拉高如果你要连续读一大块数据中间会有片选抖动。MRAM 对片选抖动其实不敏感它不像某些 Flash 那样片选一拉高就复位内部状态机但如果你总线上挂了多个设备硬件片选就没法区分了。软件片选的做法是把 PA3 配置成 GPIO 输出传输前拉低传输后拉高。代价是每次传输多几条指令但控制权完全在你手里。我在这个项目里最终用的是软件片选因为后面要扩展到双 MRAM 冗余存储硬件片选搞不定。// 软件片选宏定义 #define MRAM_CS_LOW() (GPIO_PORTA_DATA_R ~(1 3)) #define MRAM_CS_HIGH() (GPIO_PORTA_DATA_R | (1 3)) // 初始化时把 PA3 配成 GPIO 输出 void MRAM_CS_Init(void) { SYSCTL_RCGCGPIO_R | (1 0); // 使能 GPIOA 时钟 while ((SYSCTL_PRGPIO_R (1 0)) 0); GPIO_PORTA_DIR_R | (1 3); // PA3 输出 GPIO_PORTA_DEN_R | (1 3); // 数字使能 GPIO_PORTA_DATA_R | (1 3); // 默认拉高 }3. SPI 时序调试从读不到 ID 到稳定跑 20MHz3.1 先读 ID读不到就别往下走任何 SPI 存储器的调试第一步都是读 ID。MR25H40CDF 的 RDID 命令是 0x9F读回来应该是 0x18厂商 ID加上容量信息。我第一次上电读回来全是 0xFF说明 MISO 没数据或者时序不对。排查顺序是这样的先用示波器看 SCK 有没有波形有波形说明主控在发时钟再看 MOSI 上有没有 0x9F有说明数据发出去了最后看 MISO如果 MISO 一直是高电平要么是片选没拉低要么是 MRAM 没上电要么是模式不对。我那次的问题是 SPI 模式选错了。MR25H40CDF 支持模式 0CPOL0, CPHA0和模式 3CPOL1, CPHA1我一开始配成了模式 1数据采样边沿不对读回来自然全是垃圾。改成模式 0 之后ID 立刻读对了。// TM4C123 SSI0 初始化模式 020MHz void SSI0_Init(void) { SYSCTL_RCGCSSI_R | (1 0); // 使能 SSI0 时钟 SYSCTL_RCGCGPIO_R | (1 0); // 使能 GPIOA 时钟 while ((SYSCTL_PRGPIO_R (1 0)) 0); // PA2CLK, PA3FSS(软件片选时配GPIO), PA4RX, PA5TX GPIO_PORTA_AFSEL_R | 0x3C; // PA2~PA5 复用 GPIO_PORTA_PCTL_R (GPIO_PORTA_PCTL_R 0xFFF0000F) | 0x00022220; GPIO_PORTA_DEN_R | 0x3C; SSI0_CR1_R 0; // 先关闭 SSI SSI0_CC_R 0; // 使用系统时钟 // 80MHz / (2 * (1 CPSDVSR)) 目标时钟 // CPSDVSR 1 时SSI0Clk 80 / (2 * 2) 20MHz SSI0_CPSR_R 2; SSI0_CR0_R (0 7) | // SCR 0 (0 6) | // SPH 0 (模式0) (0 4) | // SPO 0 (模式0) (0 0); // DSS 0, 8位数据 SSI0_CR1_R (1 1); // SSE 1, 使能 SSI }3.2 时钟频率不是越高越好MR25H40CDF 标称最高 40MHz但实际能跑多高取决于你的板子。我在实验室的板子上跑到 40MHz 没问题但换到现场那批板子走线长了 3cm旁边有继电器30MHz 以上就开始偶发误码。最后定在 20MHz稳定跑了大半年没出过问题。这里有个经验SPI 时钟的余量要留给温度和老化的。实验室常温跑得通不代表现场 -20℃ 到 70℃ 都跑得通。我一般会把最高频率打个六折用40MHz 的器件跑 20~25MHz稳妥。TM4C123 的 SSI 时钟计算要注意它的分频是 CPSDVSR 和 SCR 两级。公式是SSI0Clk SysClk / (2 * (1 CPSDVSR) * (1 SCR))SysClk 是 80MHz。要得到 20MHz可以 CPSDVSR1, SCR1或者 CPSDVSR2, SCR0。我上面代码用的是后者。注意 CPSDVSR 必须是偶数这是 TM4C123 的硬件限制写奇数进去行为未定义。3.3 读写命令的完整流程MR25H40CDF 的命令集和标准 SPI Flash 类似但少了擦除相关的命令。常用的几个命令编码作用WREN0x06写使能写之前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据RDID0x9F读厂商和容量 ID写数据的流程是拉低片选 → 发 WREN → 拉高片选 → 拉低片选 → 发 WRITE → 发 3 字节地址 → 发数据 → 拉高片选。注意 WREN 和 WRITE 之间必须拉高片选这是很多人第一次写 MRAM 会踩的坑以为可以连着发。// 写一个字节到指定地址 void MRAM_WriteByte(uint32_t addr, uint8_t data) { // 第一步发 WREN MRAM_CS_LOW(); SSI0_SendByte(0x06); MRAM_CS_HIGH(); // 第二步发 WRITE 命令 地址 数据 MRAM_CS_LOW(); SSI0_SendByte(0x02); SSI0_SendByte((addr 16) 0xFF); SSI0_SendByte((addr 8) 0xFF); SSI0_SendByte(addr 0xFF); SSI0_SendByte(data); MRAM_CS_HIGH(); // 第三步等待写入完成MRAM 很快但保险起见查状态 while (MRAM_IsBusy()); }读数据就简单多了发 READ 命令加地址然后连续读就行地址会自动递增。4. 掉电保护MRAM 不是万能药4.1 掉电时序才是真正的敌人很多人以为用了 MRAM 就高枕无忧了反正写入快、不用擦除。但掉电保护的核心矛盾不在存储器而在你怎么知道电要掉了。如果主控在掉电瞬间还在算数据、还没发起写操作那 MRAM 再快也没用。我的方案是加一个电源监测电路用比较器监测 3.3V 主电源当电压跌到 3.0V 时触发中断主控在中断里把关键数据写进 MRAM。从 3.0V 跌到主控停止工作假设 2.7V中间的时间取决于你的电容容量。算一下假设板上有 100uF 的储能电容负载电流 50mA从 3.0V 跌到 2.7V 的时间是t C * ΔV / I 100e-6 * 0.3 / 0.05 600us600us 对 MRAM 来说绰绰有余写 100 字节数据也就几十微秒。但前提是你的中断响应要快别在中断里做浮点运算或者等别的外设。4.2 双缓冲写入策略即使有掉电中断也不能保证每次都能写完。我的做法是双缓冲MRAM 里划两块区域 A 和 B交替写。每块前面加一个头包含序号和 CRC。上电时读两块选序号大且 CRC 正确的那个。这样即使掉电时正在写 AB 还是完整的下次上电读 B 就行。MRAM 的字节写入特性让这个方案特别简单不需要像 Flash 那样考虑擦除块对齐。typedef struct { uint32_t seq; // 序号 uint32_t crc; // 数据 CRC uint8_t data[252]; // 实际数据 } Record_t; // 总共 260 字节 #define RECORD_A_ADDR 0x00000 #define RECORD_B_ADDR 0x01000 void SaveRecord(Record_t *rec) { static uint32_t seq 0; rec-seq seq; rec-crc CalcCRC(rec-data, sizeof(rec-data)); // 交替写 A 和 B uint32_t addr (seq 1) ? RECORD_A_ADDR : RECORD_B_ADDR; MRAM_WriteBuffer(addr, (uint8_t *)rec, sizeof(Record_t)); }4.3 上电恢复的校验逻辑上电恢复时读 A 和 B 两个记录比较序号和 CRC。序号大的优先但如果 CRC 错了就退回到另一个。两个都错的话说明数据彻底损坏这时候要有降级策略比如用默认值或者报警。这里有个细节CRC 校验要覆盖序号字段本身否则可能出现序号对但数据错的情况。我一开始只校验 data 部分结果有一次掉电时序号写进去了但数据没写完上电后序号是新的、数据是旧的CRC 还对导致读到了错误数据。后来把序号也纳入 CRC 计算范围就解决了。5. 性能实测MRAM 到底比 Flash 快多少5.1 写入速度对比我在同一块板子上对比了 MR25H40CDF 和一颗常见的 SPI FlashW25Q64的写入性能。测试条件是写 256 字节数据SPI 时钟都设 20MHz。操作MR25H40CDFW25Q64写 256 字节含擦除约 180us约 45ms写 256 字节无擦除仅编程约 180us约 1.2ms读 256 字节约 110us约 110us写前是否需要擦除否是差距是数量级的。Flash 写之前要擦除一个 4KB 扇区擦除时间典型 40~50ms这期间 CPU 要么等着要么去干别的。MRAM 没有这个过程写完就完事。对于 200ms 周期写数据的场景Flash 的擦除时间会吃掉四分之一的时间预算而 MRAM 几乎不占时间。5.2 连续写入的稳定性我做了个压力测试连续写 100 万次每次写 64 字节记录误码率。MRAM 跑下来零误码Flash 在擦除次数多了之后开始出现写入失败需要重试。这也印证了 MRAM 的写入寿命优势不是纸面参数是实际能感受到的。不过要注意MRAM 的写入虽然快但如果你用软件片选每次写的片选拉低拉高也有开销。批量写的时候尽量一次片选内写完别一个字节一个字节地发。5.3 读取性能的真相读取方面 MRAM 和 Flash 差不多都受 SPI 时钟限制。MRAM 的优势在于随机读取没有页边界问题Flash 跨页读会有额外延迟。但说实话读取性能不是这个场景的瓶颈写入才是。6. 那些手册上不会告诉你的坑6.1 状态寄存器的写保护位MR25H40CDF 的状态寄存器里有块保护位BP0、BP1和写保护使能位。出厂默认可能是保护状态这时候你发 WRITE 命令数据写不进去但读回来还是旧数据很容易误以为写成功了。我的做法是初始化时先读状态寄存器确认保护位是关闭的如果不是就发 WRSR 关掉。void MRAM_CheckWriteProtect(void) { uint8_t sr MRAM_ReadStatus(); if (sr 0x0C) { // BP0 或 BP1 置位 MRAM_CS_LOW(); SSI0_SendByte(0x06); // WREN MRAM_CS_HIGH(); MRAM_CS_LOW(); SSI0_SendByte(0x01); // WRSR SSI0_SendByte(sr ~0x0C); // 清除块保护位 MRAM_CS_HIGH(); } }6.2 上电后的首次访问延迟MRAM 上电后需要一点时间才能响应 SPI 命令手册上叫 tPUPower-Up Time典型值 1ms 左右。如果你上电后立刻去读 ID可能读到垃圾。我的做法是在初始化里加 5ms 延时稳妥。6.3 温度对写入的影响工业级 MRAM 标称 -40℃ 到 85℃但实际在极端温度下写入电流会变化。我在 -20℃ 的冷库里测过写入功能正常但建议在低温环境下降低 SPI 时钟频率给时序留余量。高温 70℃ 以上时MRAM 的保持特性会略降但 20 年的标称值在 85℃ 下也有 10 年以上够用。6.4 别把 MRAM 当 RAM 用虽然 MRAM 写入快、寿命长但它毕竟是通过 SPI 访问的不是内存映射。每次访问都有命令开销和片选开销。我见过有人想用它做数据缓存频繁读写结果 SPI 带宽成了瓶颈。正确的用法是关键数据落盘用 MRAM高频缓存还是用 SRAM。7. 完整驱动框架与移植建议7.1 分层设计我把驱动分成三层底层 SPI 收发、中间命令层、上层数据管理层。底层只负责字节收发中间层封装 READ/WRITE/WREN 等命令上层处理双缓冲、CRC、掉电恢复。这样换主控的时候只需要改底层中间层和上层不动。// 底层字节收发 uint8_t SSI0_SendByte(uint8_t data) { while (!(SSI0_SR_R (1 1))); // 等 TX FIFO 不满 SSI0_DR_R data; while (!(SSI0_SR_R (1 2))); // 等 RX FIFO 不空 return SSI0_DR_R; } // 中间层读数据 void MRAM_ReadBuffer(uint32_t addr, uint8_t *buf, uint32_t len) { MRAM_CS_LOW(); SSI0_SendByte(0x03); SSI0_SendByte((addr 16) 0xFF); SSI0_SendByte((addr 8) 0xFF); SSI0_SendByte(addr 0xFF); for (uint32_t i 0; i len; i) { buf[i] SSI0_SendByte(0x00); } MRAM_CS_HIGH(); }7.2 移植到其他主控这套驱动移植到 STM32 或者别的 Cortex-M 上主要改三处SPI 初始化寄存器、片选 GPIO 操作、时钟分频计算。命令层和数据层完全不用动。我在 STM32F4 上移植过一版半天就调通了因为 MRAM 的协议是标准的不挑主控。7.3 量产时的测试建议量产测试要覆盖几个点读 ID 确认芯片在位、写读回环测试、掉电恢复测试。掉电恢复测试最容易被忽略但恰恰是现场最容易出问题的地方。我的做法是产线上用可编程电源模拟掉电在写入过程中随机断电上电后检查数据完整性。这套 MRAM TM4C123 的组合我从选型到量产走了一遍最大的体会是MRAM 的价值不在于它有多快而在于它把写入这件事变得像写 RAM 一样简单让你不用再为擦除、寿命、均衡这些事分心。对于工业嵌入式项目这种省心是值得为它多花的那点成本的。如果你也在被 EEPROM 寿命或者 Flash 擦除时间折磨不妨试试这颗芯片调通之后你会发现存储这块的代码量能砍掉一半。
返回列表