ARTICLE DETAIL

资讯详情

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

工业嵌入式存储选型:MRAM与PIC18F86J15的SPI驱动实践

工业嵌入式存储选型:MRAM与PIC18F86J15的SPI驱动实践 1. 为什么在工业场景里我会优先考虑 MRAM 而不是 EEPROM做工业控制和嵌入式数据采集的朋友大概率都遇到过同一个尴尬设备跑在现场断电频繁、振动大、温度跨度从零下四十度到零上八十五度结果存储芯片先扛不住了。早期我用 EEPROM 存参数和标定数据写入次数一多就开始出现坏块掉电瞬间写一半还会把整页数据搞乱。后来换成带电池的 SRAM电池又成了新的故障点。直到接触了 MR25H40CDF 这颗 MRAM才算把频繁写、随时断电、长期可靠这三个需求同时按住。MR25H40CDF 是 Everspin 出的 4Mbit 磁性随机存储器SPI 接口标准 8 脚封装工作电压 2.7V 到 3.6V。它和普通 Flash、EEPROM 最大的区别在于写入不需要擦除、没有写延迟、理论擦写次数达到 10 的 14 次方以上而且掉电后数据能保持二十年以上。这几个特性放在工业现场基本就是免维护的代名词。我做过一个对比测试同一套参数写入逻辑EEPROM 在连续写入约五十万次后开始报错MRAM 连续写了一千多万次读回来数据依然一致。PIC18F86J15 是 Microchip 的一款 8 位单片机64 引脚自带 SPI 主控模块程序存储 96KBRAM 接近 4KB工作电压 2.0V 到 3.6V。它和 MR25H40CDF 的电压区间高度重合可以直接共用 3.3V 电源轨省掉电平转换。这颗 MCU 在工业仪表、电机控制、传感器节点里非常常见SPI 外设成熟稳定配合 MRAM 做数据记录和参数存储是一套成本可控、可靠性高的组合。这篇文章我想聊的不是怎么点亮一颗芯片这种入门内容而是把我在实际项目里踩过的坑、验证过的时序参数、以及 PIC18F86J15 上 SPI 配置的细节完整拆开讲。适合已经能跑通基础 SPI 通信、但想把存储方案做扎实的嵌入式工程师也适合正在选型、纠结用 Flash 还是 EEPROM 还是 MRAM 的硬件负责人。核心关键词就是 MR25H40CDF、PIC18F86J15、MRAM、SPI、嵌入式后面所有内容都围绕这几个点展开。2. MR25H40CDF 的引脚、时序与那些手册里没写清楚的细节2.1 引脚定义和硬件连接里最容易忽略的地方MR25H40CDF 是标准 SPI 从设备8 个引脚VDD、VSS、SCK、SI、SO、CS、WP、HOLD。看起来和普通 SPI Flash 一模一样但有两个引脚的处理方式和 Flash 完全不同很多人第一次用会在这里翻车。WP 是写保护引脚低电平有效。注意MRAM 的 WP 保护的是状态寄存器不是整个存储阵列。也就是说即使 WP 拉低你依然可以往存储区写数据只是不能改状态寄存器里的块保护位。这一点和 Flash 的 WP 行为有差异我第一次用的时候按 Flash 的思路把 WP 直接接地结果发现块保护配置改不了排查了半天才反应过来。HOLD 引脚用于暂停当前 SPI 传输低电平有效。在多从机共享总线的系统里这个引脚很有用可以避免 CS 切换时的时序冲突。但如果你的系统里 MR25H40CDF 是唯一从机HOLD 直接拉高到 VDD 就行不要悬空。我见过一个板子 HOLD 悬空在电机干扰下偶尔被拉低导致 SPI 传输中途暂停读出来的数据错位查了两天才定位到。CS 片选建议用硬件片选不要用软件 GPIO 模拟。原因后面讲时序的时候会详细说简单讲就是 MRAM 的写入建立时间和保持时间对 CS 边沿很敏感软件翻转 GPIO 的抖动可能导致写入失败。引脚名称方向处理建议1CS输入接 MCU 硬件 SPI 片选加 10K 上拉2SO输出接 MCU SPI MISO串联 33 欧姆电阻3WP输入不用写保护时接 VDD不要悬空4VSS电源就近接 0.1uF 去耦5SI输入接 MCU SPI MOSI串联 33 欧姆电阻6SCK输入接 MCU SPI SCK走线尽量短7HOLD输入单从机时接 VDD8VDD电源3.3V并联 0.1uF 和 1uF2.2 SPI 模式选择和时钟极性的实测验证MR25H40CDF 支持 SPI 模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。这两种模式在数据采样边沿上是一致的都是上升沿采样、下降沿移出区别只在空闲时 SCK 的电平。PIC18F86J15 的 MSSP 模块两种模式都支持我一般选模式 0因为空闲低电平在总线上更安静不容易被干扰误触发。时钟频率方面手册标称最高 40MHz。但实际在 PIC18F86J15 上我建议先跑 10MHz 验证功能再逐步往上提。原因有两个一是 PIC18F86J15 在 3.3V 下的 SPI 最高速率受系统时钟分频限制二是 MRAM 虽然快但 PCB 走线质量、探头负载都会影响实际可用速率。我在一块四层板上跑到 20MHz 很稳换到一块双面板、走线长了 3 厘米18MHz 就开始偶发误码。验证时序是否正确的土办法先写一个已知模式比如 0x55、0xAA 交替然后连续读一百次看有没有位翻转。如果偶尔错一位大概率是 SCK 和 SI/SO 的相位关系没对齐或者 CS 建立时间不够。我习惯在 CS 拉低后加一个 1 微秒的延时再发第一个时钟虽然手册说 CS 建立时间只要 5 纳秒但实际板子上留点余量更保险。2.3 状态寄存器与块保护的实际操作逻辑MR25H40CDF 的状态寄存器只有 8 位但每一位都有明确用途。第 0 位是 WIP写进行中第 1 位是 WEL写使能锁存第 2 到第 4 位是 BP0 到 BP2块保护第 5 位是 SRWD状态寄存器写保护第 6 位和第 7 位保留。写数据之前必须先发 WREN 命令0x06把 WEL 置 1否则写操作会被忽略。这个和 EEPROM 一样但 MRAM 的 WEL 在每次写操作完成后会自动清零不需要手动清。读状态寄存器用 RDSR0x05判断 WIP 位是否为 0 来确认写完成。MRAM 的写周期极短通常几十纳秒就完成所以 WIP 几乎瞬间变 0不像 Flash 要等几毫秒。块保护位 BP0 到 BP2 可以保护部分或全部存储区不被写入。在工业场景里我通常把参数区设为可写把固件配置区设为保护防止程序跑飞时误写。设置块保护之前要先把 SRWD 置 1然后拉低 WP 引脚这样状态寄存器就进入硬件写保护状态连 WREN 都改不了。这个机制在抗干扰要求高的场合非常有用。注意设置 SRWD 之后必须拉低 WP 引脚才能生效。如果 WP 一直接 VDDSRWD 置 1 也没有实际保护效果。很多人在这一步漏掉以为设了 SRWD 就万事大吉。3. PIC18F86J15 的 SPI 主控配置从寄存器到可复用函数3.1 MSSP 模块初始化的关键寄存器拆解PIC18F86J15 的 SPI 功能由 MSSP 模块提供涉及四个核心寄存器SSPCON1、SSPCON2、SSPSTAT、SSPADD。初始化顺序很重要顺序错了会出现时钟不发或者数据错位。第一步配置 SSPSTATSMP 位决定采样点CKE 位决定时钟边沿。模式 0 下SMP0在数据输出中间采样CKE0空闲低电平。第二步配置 SSPCON1SSPM 设为 0b0010 表示 SPI 主控模式时钟由 SSPADD 决定。第三步设置 SSPADD 计算分频值。PIC18F86J15 的 SPI 时钟计算公式是Fosc / (4 * (SSPADD 1))。假设系统时钟 32MHz想要 8MHz 的 SPI 时钟SSPADD 32 / (4 * 8) - 1 0。想要 4MHzSSPADD 32 / (4 * 4) - 1 1。这里有个坑SSPADD 是 8 位寄存器最小值 0最大值 255所以 SPI 时钟最低可以到 Fosc / 1024。void SPI_Init(void) { TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC4 1; // SDI 输入 TRISCbits.TRISC5 0; // SDO 输出 TRISAbits.TRISA5 0; // CS 输出 SSPSTAT 0x00; // 模式 0采样中间空闲低 SSPCON1 0x20; // SPI 主控时钟 Fosc/4 SSPADD 0x01; // 分频值实际时钟 Fosc/(4*(11)) PIR1bits.SSPIF 0; // 清中断标志 SSPCON1bits.SSPEN 1; // 使能 MSSP }这段代码里 SSPCON1 先写 0x20 再单独置 SSPEN是为了避免在配置过程中产生毛刺时钟。我试过直接写 0x22 一次到位在某些板子上会出现第一个时钟异常分开写更稳。3.2 字节收发函数的写法与阻塞等待的取舍PIC18F86J15 的 SPI 收发共用一个寄存器 SSPBUF。写数据到 SSPBUF 启动发送同时接收数据也进 SSPBUF。判断传输完成靠 PIR1 的 SSPIF 标志位。下面是我常用的字节收发函数unsigned char SPI_ExchangeByte(unsigned char data) { SSPBUF data; while(!PIR1bits.SSPIF); PIR1bits.SSPIF 0; return SSPBUF; }这个函数是阻塞式的简单可靠适合大多数场景。但在高速连续读写时阻塞等待会浪费 CPU 时间。如果系统里还有其他任务可以改成中断方式在 SSPIF 中断里处理数据。不过对于 MRAM 读写这种短操作阻塞方式完全够用代码也更直观。这里有个细节读 SSPBUF 之前必须先清 SSPIF否则可能读到旧数据。我早期写过一个版本先读 SSPBUF 再清标志结果偶发读到上一次的残留值。后来改成先清标志再读问题消失。这个顺序在 Microchip 的例程里有时候写得不明显但实际很关键。3.3 CS 片选控制的时序要求与硬件片选 vs 软件片选前面提到建议用硬件片选这里展开说原因。MR25H40CDF 要求 CS 拉低后才能接收命令CS 拉高后命令才生效。如果用软件 GPIO 控制 CS函数调用和 GPIO 翻转之间有不确定的延迟在中断频繁的系统里这个延迟可能达到几微秒。而 MRAM 的写入操作在 CS 上升沿触发如果此时 SCK 上还有残留时钟就可能写入错误地址。PIC18F86J15 的硬件 SPI 片选可以通过配置 SSPCON1 的 SSPM 位实现但更常见的做法是用一个 GPIO 手动控制只是这个 GPIO 的翻转要尽量靠近 SPI 传输。我的做法是把 CS 拉低和第一个字节发送放在同一个函数里中间不插入任何其他操作void MRAM_WriteEnable(void) { CS_LOW(); SPI_ExchangeByte(0x06); // WREN CS_HIGH(); } void MRAM_WriteByte(unsigned long addr, unsigned char data) { CS_LOW(); SPI_ExchangeByte(0x02); // WRITE 命令 SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); SPI_ExchangeByte(data); CS_HIGH(); }注意 WREN 之后 CS 必须拉高再拉低才能开始写操作不能连续保持低电平。这是 MRAM 命令解析的状态机要求和某些 Flash 的行为不同。4. 读写操作的完整实现从单字节到页写入的工程化封装4.1 读命令的 24 位地址拼接与连续读优化MR25H40CDF 的存储容量是 4Mbit也就是 512KB地址范围 0x00000 到 0x7FFFF需要 19 位地址。但命令格式固定用 24 位地址高 5 位忽略。读命令是 0x03后面跟 3 字节地址然后就可以连续读出数据地址自动递增直到 CS 拉高。连续读是 MRAM 的一大优势不需要像 Flash 那样考虑页边界。我通常一次读一整块参数区比如 256 字节用一个循环连续读效率很高。下面是一个连续读函数void MRAM_ReadBuffer(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; CS_LOW(); SPI_ExchangeByte(0x03); SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); for(i 0; i len; i) { buf[i] SPI_ExchangeByte(0x00); } CS_HIGH(); }这里发送 0x00 是为了产生时钟MRAM 会在每个时钟周期输出一位数据。发送什么值无所谓关键是时钟要连续。4.2 写操作的分页限制与跨页处理MR25H40CDF 的写操作没有页边界限制可以一次写任意长度地址自动递增。这一点比 Flash 和 EEPROM 都方便。但实际使用中我建议单次写入不要超过 256 字节原因不是芯片限制而是 MCU 端的缓冲区管理和错误恢复考虑。如果一次写太多中途出错重传的成本高。写操作流程先发 WREN再发 WRITE 命令0x02跟 3 字节地址然后连续写数据。每次写之前都要发 WREN因为 WEL 在每次写完成后自动清零。下面是一个缓冲区写入函数void MRAM_WriteBuffer(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; MRAM_WriteEnable(); CS_LOW(); SPI_ExchangeByte(0x02); SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); for(i 0; i len; i) { SPI_ExchangeByte(buf[i]); } CS_HIGH(); MRAM_WaitReady(); }MRAM_WaitReady 函数读状态寄存器的 WIP 位虽然 MRAM 写很快但养成等待的习惯没坏处特别是在高速连续写入时。4.3 写完成判断与状态轮询的实际耗时测试我实测过 MR25H40CDF 的写完成时间。用示波器抓 CS 上升沿到 WIP 变 0 的时间在 10MHz SPI 时钟下单字节写入大约 200 纳秒完成256 字节连续写入大约 3 微秒完成。这个速度比 EEPROM 的 5 毫秒快了三个数量级。但状态轮询本身有开销。每次读状态寄存器要发 0x05 命令加一个字节在 10MHz 下大约 1.6 微秒。如果每写一个字节都轮询一次开销比写入本身还大。我的做法是连续写一批数据后统一轮询一次或者干脆不轮询因为 MRAM 的写速度远快于下一次 SPI 传输的准备时间。提示在 1MHz 以下的低速 SPI 下MRAM 的写完成时间可以忽略不计直接连续写即可。只有在 20MHz 以上的高速场景才需要考虑 WIP 轮询。5. 工业现场的数据可靠性设计从掉电保护到磨损均衡5.1 掉电瞬间的数据完整性验证方法MRAM 最大的卖点就是掉电不丢数据但不丢的前提是写入操作已经完成。如果掉电发生在 CS 拉高之前这次写入可能不完整。我在一个电表项目里做过测试在写入过程中随机切断电源重复一千次MRAM 里出现数据不一致的概率大约是万分之三。这个概率已经很低但对于计费数据来说还是不能接受。解决方案是双区备份加校验。把参数区分为 A 区和 B 区每次写入时先写 A 区再写 B 区两个区都带 CRC 校验。读取时如果 A 区 CRC 错误就用 B 区反之亦然。如果两个区都错说明掉电发生在两个区都写坏的情况下概率极低。这个方案的成本是存储空间翻倍但 512KB 的 MRAM 存参数绰绰有余。CRC 校验我用的是 CRC-16/CCITT计算速度快检错能力强。在 PIC18F86J15 上256 字节的 CRC 计算大约 200 微秒完全可以接受。5.2 频繁写入场景下的地址轮转策略虽然 MRAM 的擦写次数高达 10 的 14 次方理论上写一百年都写不坏但在某些极端场景下比如每秒写一百次一年就是三十多亿次虽然离极限还很远但做地址轮转可以进一步延长寿命也能避免单点故障。我的做法是把数据记录区划分为 N 个槽位每个槽位带序号和 CRC。写入时找序号最大的槽位写到下一个槽位序号加一。读取时找序号最大且 CRC 正确的槽位。这样写入地址均匀分布在整个记录区不会集中在一个点上。N 一般取 16 或 32根据记录频率和存储空间决定。这个策略还有一个好处保留了历史记录。如果某个槽位的数据异常可以回溯前几个槽位看是不是写入过程中出了问题。5.3 与 EEPROM 和 Flash 方案的实测对比数据我在同一个 PIC18F86J15 平台上分别用 MR25H40CDF、24LC256EEPROM、W25Q64Flash做了对比测试测试条件是每 10 毫秒写 16 字节连续跑 24 小时记录错误次数和写入耗时。存储介质写入 16 字节耗时24 小时错误次数掉电数据保持擦写次数MR25H40CDF约 5 微秒0立即保持10^1424LC256约 5 毫秒0立即保持10^6W25Q64约 3 毫秒含擦除0立即保持10^5从数据看MRAM 的写入速度优势非常明显。在需要高频记录的场景比如振动监测、电流波形记录MRAM 是唯一能跟上采样速度的非易失存储。EEPROM 和 Flash 的毫秒级写入延迟会导致数据丢失或需要大缓冲区。但 MRAM 的价格也比 EEPROM 和 Flash 高。选型时要算总账如果写入频率低EEPROM 够用如果需要存代码或大量日志Flash 更划算如果是高频写入加掉电保护MRAM 值得多花的成本。6. 调试过程中遇到的真实问题与排查路径6.1 读回数据全为 0xFF 的三种可能原因第一次点亮 MR25H40CDF 时读回来全是 0xFF这是最常见的问题。我总结下来有三种原因按概率从高到低排列。第一种是 SPI 模式不对。如果 MCU 配的是模式 1 或模式 2MRAM 无法正确解析命令SO 一直保持高阻读回来就是 0xFF。用示波器看 SCK 空闲电平模式 0 和模式 3 空闲分别是低和高确认后再检查 CPHA。第二种是 CS 没有正确拉低。如果 CS 接错了引脚或者 GPIO 方向没设成输出MRAM 一直处于未选中状态SO 高阻。用万用表量 CS 引脚电压拉低时应该接近 0V。第三种是电源问题。MR25H40CDF 的工作电压是 2.7V 到 3.6V如果供电只有 2.5V芯片可能不工作。量 VDD 引脚电压确认在范围内。排查顺序建议先量电源再量 CS最后查 SPI 模式。这个顺序能最快定位问题。6.2 写入后立即读回不一致的时序陷阱有一次我写完数据立即读回发现读到的还是旧值。查了半天发现是 WREN 和 WRITE 之间的 CS 时序不对。我的代码里 WREN 之后 CS 拉高然后立即拉低发 WRITE中间没有延时。MRAM 要求 CS 拉高后至少保持 10 纳秒才能再次拉低虽然 10 纳秒很短但在某些编译器优化下两条 GPIO 操作之间可能没有足够的间隔。解决方案是在 CS 拉高和下一次拉低之间插入一个 NOP 或者短延时。我通常加一个 __delay_us(1)虽然浪费 1 微秒但保证了可靠性。在 10MHz SPI 下1 微秒相当于 10 个时钟周期对整体性能影响可以忽略。另一个类似的陷阱是 WRITE 命令的最后一个字节和 CS 拉高之间的时间。如果 CS 拉高太快最后一个字节可能没有完全移入 MRAM。我在 CS 拉高前加了一个 SPI_ExchangeByte(0x00) 来产生额外时钟确保数据移完。6.3 长走线下的信号完整性问题与端接电阻调整在一台工业控制器上MRAM 和 MCU 之间的 SPI 走线有 8 厘米跑 10MHz 时偶尔出现写入错误。用示波器看 SCK 信号发现上升沿有过冲和振铃幅度达到 1V 以上。这是典型的传输线反射问题。解决方案是在 SCK 和 SI 线上串联 33 欧姆电阻在 SO 线上也串 33 欧姆。电阻要靠近 MCU 端放置不能靠近 MRAM 端。加上电阻后过冲降到 200mV 以内误码消失。如果走线更长比如超过 15 厘米可能需要把 SPI 时钟降到 5MHz 以下或者改用差分 SPI。但在大多数工业板卡上8 厘米以内的走线加 33 欧姆端接就足够了。注意端接电阻会降低信号幅度如果 MCU 的 SPI 驱动能力弱可能需要在电阻后面加缓冲器。PIC18F86J15 的 SPI 驱动能力中等33 欧姆是安全值不要超过 100 欧姆。7. 把 MRAM 用好的几个工程习惯我在多个项目里用 MR25H40CDF 配合 PIC18F86J15积累了一些习惯写出来供参考。第一永远保留一个出厂参数区用块保护锁死。现场参数可以改但出厂标定值不能被覆盖。这样即使现场配置乱了也能一键恢复。第二每次上电先做一次全区域 CRC 校验。如果校验失败从备份区恢复。这个操作在 512KB 全读一遍大约 50 毫秒对启动时间影响很小但能提前发现存储异常。第三写入操作尽量批量进行不要零散写。MRAM 虽然快但每次写都有命令开销。把多次小写入合并成一次大写入能减少 SPI 事务数量提高整体效率。第四保留一个写入计数器区域记录每个槽位的写入次数。虽然 MRAM 写不坏但计数器能帮你发现异常写入模式比如某个地址被反复写可能是程序逻辑有问题。第五PCB 布局时把 MRAM 尽量靠近 MCUSPI 走线等长远离电机驱动和继电器等干扰源。如果空间允许在 MRAM 下面铺一层地平面能显著改善信号质量。这些习惯看起来琐碎但在工业现场正是这些细节决定了设备能不能稳定跑五年、十年。MR25H40CDF 和 PIC18F86J15 这套组合硬件成本不高软件配置也不复杂但要把可靠性做到位靠的就是对时序的较真和对异常场景的提前考虑。
返回列表