ARTICLE DETAIL

资讯详情

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

MRAM在工业嵌入式中的SPI驱动与可靠性设计

MRAM在工业嵌入式中的SPI驱动与可靠性设计 1. 为什么 MRAM 在工业嵌入式场景里越来越受关注如果你做过工业现场的数据采集设备大概率遇到过这样的场景设备运行在户外配电柜里夏天柜内温度能到 70 度以上冬天在北方野外又能跌到零下 30 度。这时候如果还用传统的 EEPROM 或者带电池的 SRAM 来保存关键参数掉电丢失、电池低温失效、写入寿命不够这些问题会一个接一个找上门来。我自己就经历过一个项目现场跑了半年之后陆续有设备报参数丢失拆回来一查后备电池在低温下容量衰减得厉害换电池的成本比设备本身还高。这也是为什么这几年MRAM磁性随机存储器在工业嵌入式圈子里被讨论得越来越多。MR25H40CDF 就是这类器件里比较有代表性的一颗4Mbit 容量、SPI 接口、号称近乎无限的读写寿命而且不需要后备电池。把它和 PIC32MZ2048EFH144 这种带 SPI 外设的高性能 MCU 搭配使用可以搭出一套相当皮实的数据存储方案。这篇内容我就围绕这两个器件的组合把选型逻辑、硬件连接、SPI 时序配置、读写代码实现、以及实际调试中踩过的坑完整地梳理一遍。先明确一下这套方案适合谁看如果你正在做工业控制、电力监测、医疗设备或者任何需要频繁记录参数、又对掉电可靠性有要求的嵌入式项目并且主控选型在 PIC32MZ 系列或者类似的 32 位 MCU 上那这篇内容基本可以直接拿去参考。即使你用的是 STM32 或者其他平台SPI 操作 MRAM 的核心逻辑也是通用的只是寄存器配置部分需要按你的平台调整。MR25H40CDF 的核心卖点可以归纳成几条非易失性、无写入延迟、高耐久度、宽温工作。这四条恰好对应了工业场景里最痛的几个点。传统 EEPROM 写入慢、寿命有限Flash 有擦除块限制、写入前要整块擦带电池的 SRAM 又怕电池失效。MRAM 的存储原理是磁性隧道结写入靠的是电子自旋方向的变化不涉及电荷的充放电所以它既没有 Flash 那种擦写寿命焦虑也没有电池依赖。这一点在需要每秒记录几十次数据的场景里优势非常明显。2. MR25H40CDF 与 PIC32MZ2048EFH144 的搭配逻辑2.1 两颗器件的定位与互补关系PIC32MZ2048EFH144 是 Microchip 旗下 PIC32MZ EF 系列的一款高性能 MCU200MHz 主频、2MB Flash、512KB RAM带多个 SPI 外设模块。它的定位是工业控制、音频处理、复杂协议转换这类对算力和外设资源都有要求的场景。这颗芯片本身有足够的内部存储但内部 Flash 的擦写寿命和写入速度都不适合做高频数据记录所以外挂一颗 MRAM 来承担频繁写、不能丢的任务是一个很自然的分工。MR25H40CDF 是 4Mbit512KB容量的 SPI MRAM工作电压 2.7V 到 3.6V支持最高 40MHz 的 SPI 时钟。它的引脚很少CS、SCK、SI、SO加上电源和地标准 SPI 四线接口。没有地址锁存、没有等待忙状态、没有擦除命令写操作就是直接发地址加数据写完就完事。这种简单粗暴的接口特性让它在软件层面的操作比 Flash 和 EEPROM 都要清爽。两者搭配的核心逻辑是PIC32MZ 负责业务逻辑和数据处理MR25H40CDF 负责可靠存储。MCU 通过 SPI 总线把需要持久化的数据写进 MRAM需要的时候再读出来。因为 MRAM 没有写入延迟和擦除周期MCU 可以在任意时刻发起写入不需要像操作 Flash 那样先判断扇区状态、先擦后写、等待忙标志。2.2 SPI 模式选择为什么是 Mode 0MR25H40CDF 支持 SPI Mode 0CPOL0CPHA0和 Mode 3CPOL1CPHA1。这两种模式的区别在于时钟空闲电平和数据采样边沿。Mode 0 下SCK 空闲为低电平数据在 SCK 上升沿被采样Mode 3 下SCK 空闲为高电平数据在上升沿采样。实际项目里我一般选Mode 0原因有两个。第一PIC32MZ 的 SPI 模块在 Mode 0 下配置最直观时钟极性位和相位位都清零即可不容易出错。第二大多数逻辑分析仪和示波器在调试时默认按 Mode 0 解析抓波形的时候省事。当然如果你的 PCB 上 SCK 走线有特殊要求或者从设备端有其他 SPI 器件强制要求 Mode 3那也可以切过去MRAM 本身两种都支持。需要特别注意的是SPI 模式必须在初始化阶段就确定好运行过程中不要动态切换。我见过有项目为了兼容不同从设备在运行时反复改 CPOL 和 CPHA结果导致某次写入时序错乱数据写飞了。正确的做法是如果总线上挂了多个 SPI 从设备且模式要求不同用不同的 CS 引脚分时复用或者在硬件上做隔离而不是靠软件动态改模式。2.3 硬件连接中的几个关键细节硬件连接看起来简单CS 接 CS、SCK 接 SCK、SI 接 SI、SO 接 SO但有几个细节如果忽略调试的时候会很痛苦。CS 引脚的上拉。MR25H40CDF 的 CS 是低电平有效如果 MCU 复位期间 IO 处于高阻态CS 可能被干扰拉低导致 MRAM 误触发。建议在 CS 线上加一个 10K 的上拉电阻到 VCC确保空闲时 CS 稳定在高电平。电源去耦。MRAM 在写入瞬间会有电流波动虽然不大但在高频 SPI 下电源纹波会影响数据完整性。在 MRAM 的 VCC 引脚旁边放一个 0.1uF 的陶瓷电容尽量靠近引脚再并一个 1uF 的电容做低频滤波。SPI 走线长度。如果 MRAM 和 MCU 在同一块板子上走线控制在几厘米以内40MHz 时钟下一般不会有问题。如果 MRAM 在另一块板子上通过排线连接建议把 SPI 时钟降到 10MHz 以下并且 SCK 和 SO 线旁边走地线做屏蔽。WP 和 HOLD 引脚。MR25H40CDF 有些封装会带 WP写保护和 HOLD保持引脚。如果不用这两个功能WP 直接拉高到 VCCHOLD 也拉高。不要悬空悬空会导致状态不确定。3. PIC32MZ 的 SPI 外设配置与 MRAM 初始化3.1 SPI 模块的寄存器级配置思路PIC32MZ 的 SPI 模块配置涉及几个关键寄存器SPIxCON、SPIxBRG、SPIxSTAT。我以 SPI2 为例假设系统时钟 200MHz外设总线时钟 100MHz目标 SPI 时钟 20MHz。SPIxBRG 的计算公式是SPI 时钟 外设总线时钟 / (2 × (BRG 1))。要得到 20MHzBRG 100MHz / (2 × 20MHz) - 1 1.5取整为 1实际时钟为 100 / (2 × 2) 25MHz。如果要用 10MHzBRG 100 / (2 × 10) - 1 4实际 10MHz。SPIxCON 的配置要点MSTEN 置 1设为主机模式CKP 清零选 Mode 0 的空闲低电平CKE 置 1表示数据在时钟上升沿采样注意 PIC32MZ 的 CKE 定义和标准 CPHA 略有差异CKE1 对应 Mode 0 的采样边沿SMP 清零表示在数据输出中间采样MODE32 清零使用 8 位模式ON 置 1使能 SPI 模块。这里有个容易踩的坑PIC32MZ 的 CKE 位和常规 SPI 的 CPHA 定义是反的。很多从 STM32 转过来的工程师会习惯性地把 CPHA0 对应 CKE0结果时序完全对不上。正确的对应关系是Mode 0 对应 CKP0、CKE1。这个我在第一次用 PIC32MZ 调 SPI 的时候卡了大半天波形抓出来发现数据采样边沿偏了一个周期后来翻数据手册才确认。3.2 MRAM 的写使能与时序要求MR25H40CDF 的写操作之前需要先发WREN0x06命令使能写入。这个命令发完之后CS 拉高然后再拉低发写命令。写命令是0x02后面跟 3 字节地址和要写入的数据。地址是 24 位的因为 4Mbit 容量对应 19 位地址但器件要求发满 3 字节。读操作更简单发READ0x03命令跟 3 字节地址然后就可以连续读出数据。MRAM 支持连续读地址会自动递增读完整片都不需要重新发命令。时序上需要注意几个参数CS 建立时间CS 拉低到第一个 SCK 上升沿最小 5nsCS 保持时间最后一个 SCK 下降沿到 CS 拉高最小 5ns数据输出有效时间在 40MHz 下大约 8ns。这些参数在 20MHz 以下操作时基本不用刻意关心但如果跑到 40MHzPCB 走线和 IO 驱动能力就要留意了。还有一个细节WREN 命令之后如果 CS 拉高写使能状态会被清除。也就是说每次写操作之前都要重新发 WREN。不能发一次 WREN 然后连续写多个地址除非你在写操作之间保持 CS 为低。但保持 CS 为低连续写的话地址会自动递增适合批量写入场景。3.3 初始化代码的完整实现下面这段代码是基于 PIC32MZ 的 SPI2 初始化配置成 Mode 0、20MHz 时钟并且把 CS 引脚配置为普通 GPIO 输出。// SPI2 初始化Mode 0约 20MHz void MRAM_SPI_Init(void) { // 关闭 SPI2 SPI2CONbits.ON 0; // 配置 CS 引脚为输出默认拉高 TRISDbits.TRISD9 0; // 假设 CS 接在 RD9 LATDbits.LATD9 1; // 配置 SCK、SDO 为输出SDI 为输入 TRISGbits.TRISG6 0; // SCK TRISGbits.TRISG7 1; // SDI TRISGbits.TRISG8 0; // SDO // 外设引脚映射 RPG6R 0b0110; // SCK 映射到 SPI2 RPG7R 0b0110; // SDI 映射到 SPI2 RPG8R 0b0110; // SDO 映射到 SPI2 // SPI2 配置 SPI2CON 0; SPI2CONbits.MSTEN 1; // 主机模式 SPI2CONbits.CKP 0; // 空闲低电平 SPI2CONbits.CKE 1; // 上升沿采样Mode 0 SPI2CONbits.SMP 0; // 中间采样 SPI2CONbits.MODE32 0; // 8 位模式 SPI2CONbits.MODE16 0; // 波特率配置目标约 20MHz SPI2BRG 1; // 使能 SPI2 SPI2CONbits.ON 1; }这段代码里我特意把引脚映射部分写出来是因为 PIC32MZ 的 PPS外设引脚选择功能很灵活但也容易配错。如果你用的引脚和我的不一样需要查数据手册里的 PPS 输入输出映射表把对应的 RPVAL 填对。配错 PPS 的表现是 SPI 时钟和数据都正常但从设备就是没反应因为信号根本没接到 SPI 模块上。4. MRAM 读写操作的代码实现与验证4.1 单字节写入与读取函数先看最基础的写一个字节和读一个字节。写操作需要发 WREN、发写命令、发地址、发数据每一步都要控制 CS。// 发送一个字节并返回接收到的字节 static uint8_t SPI_Transfer(uint8_t data) { SPI2BUF data; while (!SPI2STATbits.SPIRBF); return SPI2BUF; } // 写使能 static void MRAM_WriteEnable(void) { LATDbits.LATD9 0; // CS 拉低 SPI_Transfer(0x06); // WREN 命令 LATDbits.LATD9 1; // CS 拉高 } // 写一个字节到指定地址 void MRAM_WriteByte(uint32_t addr, uint8_t data) { MRAM_WriteEnable(); LATDbits.LATD9 0; SPI_Transfer(0x02); // WRITE 命令 SPI_Transfer((addr 16) 0xFF); // 地址高字节 SPI_Transfer((addr 8) 0xFF); // 地址中字节 SPI_Transfer(addr 0xFF); // 地址低字节 SPI_Transfer(data); // 数据 LATDbits.LATD9 1; } // 从指定地址读一个字节 uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; LATDbits.LATD9 0; SPI_Transfer(0x03); // READ 命令 SPI_Transfer((addr 16) 0xFF); SPI_Transfer((addr 8) 0xFF); SPI_Transfer(addr 0xFF); data SPI_Transfer(0x00); // 读数据 LATDbits.LATD9 1; return data; }这里有个细节读操作的时候最后一个 SPI_Transfer 发的是 0x00这是为了产生时钟让从设备把数据移出来。发什么内容无所谓因为 MRAM 在读模式下会忽略 SI 上的数据。4.2 批量读写与页边界处理MR25H40CDF 内部没有页的概念地址是线性递增的写操作可以跨任意边界。这一点比 Flash 和 EEPROM 都方便不需要考虑页对齐。批量写入的时候发完命令和起始地址之后可以连续发多个数据字节地址会自动递增。// 批量写入 void MRAM_WriteBuffer(uint32_t addr, uint8_t *buf, uint32_t len) { MRAM_WriteEnable(); LATDbits.LATD9 0; SPI_Transfer(0x02); SPI_Transfer((addr 16) 0xFF); SPI_Transfer((addr 8) 0xFF); SPI_Transfer(addr 0xFF); for (uint32_t i 0; i len; i) { SPI_Transfer(buf[i]); } LATDbits.LATD9 1; } // 批量读取 void MRAM_ReadBuffer(uint32_t addr, uint8_t *buf, uint32_t len) { LATDbits.LATD9 0; SPI_Transfer(0x03); SPI_Transfer((addr 16) 0xFF); SPI_Transfer((addr 8) 0xFF); SPI_Transfer(addr 0xFF); for (uint32_t i 0; i len; i) { buf[i] SPI_Transfer(0x00); } LATDbits.LATD9 1; }批量写入的时候要注意地址范围。4Mbit 对应 512KB地址范围是 0x00000 到 0x7FFFF。如果写入长度超过剩余空间地址会回卷到 0 继续写覆盖之前的数据。这个行为在数据手册里有说明但实际写代码的时候很容易忽略。建议在批量写入函数里加一个边界检查或者确保上层调用不会越界。4.3 用状态寄存器做写入验证MR25H40CDF 有一个状态寄存器可以通过RDSR0x05命令读取。状态寄存器的 bit0 是 WEL写使能锁存bit1 是 WIP写进行中。虽然 MRAM 的写入速度极快几乎不需要等待但在高可靠性场景下读一下状态寄存器确认写入完成是一个好习惯。// 读取状态寄存器 uint8_t MRAM_ReadStatus(void) { uint8_t status; LATDbits.LATD9 0; SPI_Transfer(0x05); status SPI_Transfer(0x00); LATDbits.LATD9 1; return status; } // 等待写入完成 void MRAM_WaitReady(void) { while (MRAM_ReadStatus() 0x01); // 等待 WIP 清零 }实测下来MRAM 的 WIP 位在写入命令结束后几乎立刻就清零了等待时间在微秒级别。但如果你在写完之后立刻读同一个地址不加等待的话读出来的可能是旧数据。这是因为 SPI 事务之间需要一点时间让内部逻辑完成更新。加一个 MRAM_WaitReady 或者简单的微秒级延时可以避免这个问题。5. 实际调试中遇到的坑与排查过程5.1 读出来全是 0xFF 的排查链路第一次调通这套方案的时候我遇到的问题是读出来全是 0xFF。这个现象很典型说明 SPI 通信本身可能没问题但从设备没有正确响应。我的排查顺序是这样的第一步确认 CS 信号。用示波器看 CS 引脚发现 CS 在每次操作时确实拉低了但拉低的时间非常短只有几百纳秒。检查代码发现SPI_Transfer 函数里等待 SPIRBF 标志的循环有问题SPI2STATbits.SPIRBF 在发送完成后没有及时置位导致函数提前返回CS 很快就拉高了。后来改成等待 SPITBF 清零再等 SPIRBF 置位问题解决。第二步确认 SPI 时钟。CS 正常之后读出来还是 0xFF。用逻辑分析仪抓 SCK发现时钟频率只有 1MHz 左右和预期的 20MHz 差很多。检查 SPI2BRG 的计算发现外设总线时钟不是我以为的 100MHz而是 50MHz。重新计算 BRG 之后时钟恢复正常。第三步确认 SPI 模式。时钟对了之后读出来变成了 0x00。这个现象说明数据线可能有短路或者模式不对。检查 CKE 和 CKP 配置发现 CKE 设成了 0改成 1 之后读出了正确的数据。这个排查过程告诉我一个经验SPI 调试一定要有逻辑分析仪或者至少双通道示波器。只看代码和读返回值很难定位是时序问题、配置问题还是硬件问题。逻辑分析仪可以同时看 CS、SCK、SI、SO 四根线一眼就能看出是命令发错了、地址发错了还是采样边沿不对。5.2 写入后数据偶尔丢失的原因项目跑了一段时间之后现场反馈偶尔有参数丢失。这个问题比较隐蔽因为不是每次都丢大概几百次写入出现一次。我做了几件事来定位首先在写入函数里加了状态寄存器检查确认每次写入后 WIP 都清零了。然后在写入前后各读一次数据做比对发现丢失的那次写入后的读回值就是错的。这说明问题出在写入过程中不是存储介质本身的问题。继续排查把 SPI 时钟从 20MHz 降到 10MHz丢失频率明显下降。再降到 5MHz基本不丢了。这说明是时序余量的问题。回头看 PCBMRAM 和 MCU 之间的 SPI 走线大概有 8 厘米而且 SCK 和 SO 没有并行走地线。在 20MHz 下信号反射和串扰导致 SO 上的数据在采样时刻不稳定。解决方案有两个一是降低 SPI 时钟到 10MHz二是重新 layout 把走线缩短并加地线屏蔽。最终我选了降频方案因为改板成本太高而 10MHz 对数据记录来说完全够用。每次写入 256 字节的数据10MHz 下大概 200 微秒对系统实时性没有影响。5.3 多设备共用 SPI 总线的 CS 管理如果 SPI 总线上还挂了其他从设备比如 Flash、传感器、显示屏CS 的管理就很重要。我见过一个项目MRAM 和一颗 SPI Flash 共用总线结果 Flash 操作的时候 MRAM 的数据被误写。原因是 Flash 的驱动在操作时没有正确拉高 MRAM 的 CS导致 MRAM 把 Flash 的命令当成了自己的命令。正确的做法是每个 SPI 从设备有独立的 CS 引脚操作哪个设备就拉低哪个 CS其他 CS 保持高电平。在代码层面建议把每个设备的 CS 控制封装成独立的函数不要直接在业务代码里操作 GPIO。这样即使总线上挂了多个设备也不会互相干扰。另外如果总线上有多个设备SPI 模式要统一。如果某个设备只支持 Mode 3而 MRAM 用 Mode 0那就不能挂在同一条总线上或者需要用软件模拟 SPI 分别控制。这一点在硬件设计阶段就要确认好不然后期改起来很麻烦。6. 工业场景下的可靠性设计与经验总结6.1 数据完整性校验的落地方式MRAM 本身可靠性很高但在工业现场电磁干扰、电源波动、MCU 异常复位都可能导致写入过程中断。为了保证数据完整性我在实际项目里会加两层保护。第一层是写入前后的 CRC 校验。每次写入一块数据同时写入这块数据的 CRC32 值。读取的时候先读数据再读 CRC比对一致才认为数据有效。如果 CRC 不匹配说明数据在写入或存储过程中出了问题可以触发重写或者报警。第二层是双备份加版本号。关键参数存两份每份带一个递增的版本号。写入的时候先写备份区再写主区。读取的时候比较两个版本号取版本号大的那份。如果主区 CRC 错误自动切换到备份区。这种方式可以容忍单次写入失败代价是存储空间翻倍。对于 512KB 的 MRAM 来说存关键参数完全够用。6.2 写入频率与寿命的实际考量MR25H40CDF 的数据手册标称写入寿命是 10^14 次以上这个数字在实际使用中基本可以认为是无限的。但要注意这个寿命是针对每个存储单元的不是整片。如果你反复写同一个地址那个地址的寿命会先耗尽。虽然 10^14 次意味着每秒写一次也要写三百万年但在设计上还是建议做磨损均衡尤其是日志型的数据记录场景。我的做法是把 MRAM 分成多个扇区轮流写入每个扇区写满之后切换到下一个。这样每个地址的写入次数被均匀分摊整片寿命得到充分利用。对于参数存储这种写入频率不高的场景其实不需要磨损均衡直接固定地址写就行。6.3 宽温测试中的实际表现工业级 MRAM 的标称工作温度是 -40 到 85 度有些型号能到 105 度。我在高低温箱里做过测试-40 度下 SPI 通信正常读写数据无误85 度下连续写入 100 万次没有出现数据错误。但有一个现象值得注意在温度极端变化的时候SPI 时钟的稳定性会受影响。从 -40 度快速升到 85 度的时候如果 SPI 跑在 40MHz偶尔会出现通信错误。降到 20MHz 之后整个温变过程都很稳定。所以我的建议是如果设备要经历剧烈的温度变化SPI 时钟留足余量不要贴着器件标称的上限跑。20MHz 对于绝大多数数据存储场景都够用了没必要为了追求速度牺牲可靠性。6.4 从项目实践中提炼的几条硬经验第一SPI 模式一定要在初始化时确定运行中不要改。我见过太多因为动态改模式导致通信异常的案例这个问题排查起来非常费时间。第二CS 信号用 GPIO 控制不要用 SPI 外设的硬件 CS。PIC32MZ 的 SPI 模块有硬件 CS 功能但它的时序和手动控制有差异在某些从设备上会出现 CS 提前拉高的问题。手动控制 GPIO 虽然多几行代码但时序完全可控。第三写入之后加一个短延时或者状态检查。MRAM 虽然快但 SPI 事务结束到内部存储更新完成之间有一个极短的时间窗口。在这个窗口内读同一个地址可能读到旧数据。加一个 1 微秒的延时或者读状态寄存器可以彻底避免这个问题。第四批量写入时注意地址回卷。MRAM 的地址是线性递增的写到末尾会自动回到 0。如果你的数据长度可能超过剩余空间一定要在代码里做边界检查否则会覆盖开头的数据。第五调试阶段一定要抓波形。SPI 的问题看代码只能看出配置对不对看不出时序对不对。逻辑分析仪抓一次波形比读十遍数据手册都管用。这套 MR25H40CDF 加 PIC32MZ2048EFH144 的方案我在两个工业项目里实际用过累计出货几千台现场运行最长的已经超过三年没有出现过数据丢失的问题。MRAM 的价格比 EEPROM 和 Flash 高一些但在需要频繁写入、掉电不能丢数据、维护成本高的场景里这个差价是值得的。尤其是那些设备安装在偏远地区、维护一次成本很高的项目用 MRAM 省掉的售后成本远超器件本身的差价。
返回列表