拓冰建站拓冰建站
首页 / 资讯中心 / 正文

MRAM与PIC18F85J50实战:SPI驱动、掉电保护与工业存储选型

1. 为什么 MRAM 在工业嵌入式场景里越来越香搞过工业数据采集或者电力终端的朋友应该都有体会选存储芯片这件事很多时候比选主控还让人头疼。EEPROM 写入慢、擦写寿命有限Flash 有写前擦除的麻烦掉电窗口稍微没处理好就丢数据铁电存储器容量又上不去。我前两年做一个配电监测终端项目要求每秒记录一次三相电参数断电后最近 10 分钟的数据不能丢同时设备预期寿命 10 年以上算下来单颗存储芯片的擦写次数要扛住上亿次当时把市面上主流的非易失存储翻了个遍最后锁定了 MRAM 这条路线。MR25H40CDF 就是这类需求下非常典型的一颗料。它是 Everspin 家的 4Mbit 串行 MRAMSPI 接口SOIC-8 封装工作电压 2.7V 到 3.6V工业级温度范围覆盖 -40℃ 到 85℃部分批次能到 105℃。和传统存储最大的区别在于MRAM 的写入不需要擦除动作字节级随机写入写入时间在纳秒量级理论擦写次数是无限的厂商给的 endurance 指标是 10^14 次以上实际上你根本写不到那个量级。这意味着什么意味着你可以把它当成一个掉电不丢的 SRAM来用写数据的时候不用再搞什么页缓冲、磨损均衡、掉电保护电容那一套复杂逻辑。而 PIC18F85J50 是 Microchip 的一款 8 位单片机PIC18 系列里的高配型号128KB Flash、3904 字节 RAM、带硬件 SPI 模块、自带 12 位 ADC、还有 CTMU 和比较器这些外设工业控制里用得非常广。它和 MR25H40CDF 搭配基本就是低成本主控 高可靠存储的经典组合适合做数据记录仪、智能电表、工业传感器节点、医疗设备参数存储这类场景。这篇文章我打算把这两颗芯片怎么配合、SPI 怎么配、读写时序怎么处理、掉电场景怎么保证数据完整性从头到尾捋一遍。不管你是刚接触嵌入式存储的新手还是想从 EEPROM 迁移到 MRAM 的老手应该都能从里面找到能直接抄的东西。2. 方案整体设计与选型思路拆解2.1 为什么是 MRAM 而不是 EEPROM 或 Flash先把选型逻辑讲清楚不然后面很多设计决策你会看不懂。工业场景对存储的核心诉求无非这么几条掉电不丢、写入次数够多、写入速度快、接口简单、宽温可靠。我们拿三种方案对比一下对比项EEPROM如 24LC512NOR Flash如 W25Q64MRAMMR25H40CDF写入前是否需擦除不需要需要按扇区擦不需要字节写入时间约 5ms页写约 0.7ms页编程 擦除时间无等待纳秒级擦写寿命约 100 万次约 10 万次10^14 次以上掉电保护需外部电路需外部电路天然安全容量小KB 级大MB 级中512KB单位成本低低高接口I2C/SPISPISPI从表里能看出来MRAM 唯一的劣势就是贵。但如果你算总账用 EEPROM 要加掉电检测电路、要写磨损均衡算法、要处理写等待开发成本和故障风险都上去了用 Flash 更麻烦擦除块管理、坏块处理、掉电窗口代码量翻几倍。MRAM 贵出来的那点钱很多时候在工程成本上就省回来了。我当时的判断标准很简单如果单台设备的数据写入频率超过每秒 1 次且要求断电后数据零丢失那就直接上 MRAM别犹豫。2.2 PIC18F85J50 的 SPI 资源怎么分配PIC18F85J50 有两个 MSSP 模块MSSP1 和 MSSP2都支持 SPI 和 I2C 模式。做这个方案的时候我一般把 MSSP1 配成 SPI 主机模式专门挂 MRAMMSSP2 留着做别的用途比如挂个 SPI 显示屏或者跟另一颗从机通信。引脚分配上PIC18F85J50 的 SPI 引脚是复用的MSSP1SCK1 在 RC3SDI1 在 RC4SDO1 在 RC5SS1 在 RA5MSSP2SCK2 在 RB1SDI2 在 RB2SDO2 在 RB3SS2 在 RB4注意 SS 引脚。虽然硬件 SPI 模块有 SS 功能但在实际项目里我强烈建议用普通 GPIO 做软件片选原因后面会讲。MR25H40CDF 只需要四根线SCK、SI数据输入、SO数据输出、CS片选加上 VDD 和 VSS正好 6 根SOIC-8 封装还剩两个脚是 NC。2.3 硬件连接与去耦设计接线本身不复杂但有几个细节容易翻车。MR25H40CDF 的 VDD 和 VSS 之间必须放一颗 0.1μF 的陶瓷去耦电容位置尽量靠近芯片引脚走线越短越好。我见过有项目为了省事把电容放在板子另一头结果 SPI 高速读写时偶发数据错误查了半天才发现是电源纹波问题。CS 引脚上建议加一个 10kΩ 的上拉电阻到 VDD。为什么因为单片机复位期间 GPIO 是高阻态CS 如果悬空可能被干扰拉低导致 MRAM 误触发。虽然 MR25H40CDF 内部有上拉但外部再加一个更保险成本也就几分钱。SPI 走线方面如果 MRAM 离主控超过 5cm建议在 SCK 线上串一个 22Ω 到 33Ω 的电阻做阻抗匹配减少反射。这个在高速时钟下特别重要我试过 10MHz 时钟、走线 8cm 不加匹配电阻误码率明显上升加了 33Ω 之后稳如老狗。3. MR25H40CDF 核心细节与 SPI 时序实操要点3.1 芯片内部结构与地址空间MR25H40CDF 的 4Mbit 容量换算成字节是 512KB地址范围 0x00000 到 0x7FFFF需要 19 位地址。SPI 指令里地址是按 3 个字节发送的高字节在前最高位那几位是无效的实际只用低 19 位。内部存储阵列是真正的随机访问没有页、扇区、块的概念。你写地址 0x00000 和写地址 0x7FFFF耗时完全一样都是纳秒级。这一点和 Flash 完全不同用起来非常省心。芯片支持的 SPI 模式是 Mode 0CPOL0CPHA0和 Mode 3CPOL1CPHA1数据在时钟上升沿采样。我一般用 Mode 0因为 PIC18F85J50 的 MSSP 默认配置就是 Mode 0省得改。3.2 关键指令集解析MR25H40CDF 的指令集很精简常用的就这几条指令名称指令码功能说明WREN0x06写使能写操作前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据重点说 WREN。MR25H40CDF 上电后默认是写禁止状态任何写操作之前必须先发 WREN 指令否则写不进去。而且 WREN 是一次性的每次写操作前都要重新发。我见过有新手写完一次数据后第二次写忘了发 WREN结果数据没写进去还查了半天。状态寄存器里主要关注 WEL 位写使能锁存和 BP 位块保护。WEL 位在发完 WREN 后置 1写操作完成后自动清零。BP 位可以用来保护部分地址空间不被写入做参数区保护的时候很有用。3.3 SPI 时序参数与时钟选择MR25H40CDF 支持的最高 SPI 时钟是 40MHz但实际用的时候要考虑主控能力和走线质量。PIC18F85J50 在 40MHz 晶振下MSSP 的 SPI 时钟最高可以到 10MHzFosc/4。我一般配到 5MHz 到 10MHz 之间兼顾速度和稳定性。时序上要注意几个关键参数CS 建立时间CS 拉低到第一个 SCK 上升沿最小 5nsCS 保持时间最后一个 SCK 下降沿到 CS 拉高最小 5ns数据输出有效时间最大 8ns时钟高电平时间和低电平时间最小 10ns这些参数在 10MHz 以下基本都能满足不用特别操心。但如果你的主控跑得比较快或者走线比较长就要拿示波器看一下波形确认建立保持时间够不够。提示MR25H40CDF 的 CS 必须在整个指令序列期间保持低电平包括地址和数据阶段。如果中间 CS 被拉高指令会被中止数据可能写一半。这是软件片选比硬件片选更可控的原因之一。3.4 写操作的特殊注意事项MRAM 的写操作和 EEPROM、Flash 都不一样有几个坑要提前知道。第一MRAM 写入是直接覆盖不需要擦除。你往一个地址写新数据旧数据直接被替换没有中间状态。这听起来很美好但意味着如果你写了一半掉电那个地址的数据就是不确定的。所以关键数据要么加校验要么用双备份加标志位的方式。第二MRAM 的写入虽然快但 SPI 传输本身需要时间。写一个字节要发 1 字节指令 3 字节地址 1 字节数据 5 字节在 10MHz 时钟下大约 4μs。如果你要写 1KB 数据就是 4ms 左右。这个速度比 EEPROM 快很多但也不是瞬间完成掉电保护窗口还是要考虑的。第三连续写的时候MR25H40CDF 支持地址自动递增你发一次指令和起始地址然后连续发数据芯片会自动往后续地址写。这个模式效率最高建议批量写入时用。4. PIC18F85J50 端 SPI 驱动实现与实操过程4.1 MSSP 模块初始化配置先上代码这是 MSSP1 配成 SPI 主机的初始化函数基于 XC8 编译器void SPI1_Init(void) { // 配置引脚方向 TRISC3 0; // SCK1 输出 TRISC5 0; // SDO1 输出 TRISC4 1; // SDI1 输入 TRISA5 0; // SS1 配置为普通 IO 输出软件片选 // 关闭 MSSP 模块再配置 SSP1CON1 0x00; // SSP1STAT: 数据在上升沿采样时钟极性 Mode 0 SSP1STAT 0x40; // bit6 CKE 1数据在时钟上升沿采样 // SSP1CON1: SPI 主机模式时钟 Fosc/16 // bit5 SSPEN 1 使能bit4 CKP 0 空闲低电平 // bit3-0 SSP1M 0001 主机模式 Fosc/16 SSP1CON1 0x21; // 片选默认拉高 MRAM_CS 1; }这里解释几个关键点。SSP1STAT 的 CKE 位决定数据在时钟的哪个边沿采样CKE1 表示上升沿采样配合 CKP0空闲低电平就是标准的 SPI Mode 0。SSP1CON1 的低四位是时钟选择0x21 对应 Fosc/16在 40MHz 晶振下就是 2.5MHz。如果你想要更快的时钟可以改成 0x20Fosc/410MHz但要注意走线和从机能力。4.2 字节收发底层函数SPI 的字节收发是基础所有上层操作都靠它uint8_t SPI1_ExchangeByte(uint8_t data) { SSP1BUF data; // 写入发送缓冲 while(!PIR1bits.SSP1IF); // 等待传输完成 PIR1bits.SSP1IF 0; // 清除中断标志 return SSP1BUF; // 读取接收数据 }这个函数是阻塞式的发一个字节同时收一个字节。SPI 是全双工的发和收同时进行所以读操作的时候我们发 dummy 字节通常是 0xFF来产生时钟把从机的数据顶出来。注意PIC18F85J50 的 SSP1BUF 是发送和接收共用的寄存器写的时候是发送读的时候是接收。如果忘了清 SSP1IF 标志下一次等待会直接跳过导致数据错乱。这个坑我踩过查了一下午。4.3 MRAM 读写函数完整实现有了底层字节收发上层读写就简单了。先看写使能和读状态void MRAM_WriteEnable(void) { MRAM_CS 0; SPI1_ExchangeByte(0x06); // WREN 指令 MRAM_CS 1; } uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS 0; SPI1_ExchangeByte(0x05); // RDSR 指令 status SPI1_ExchangeByte(0xFF); MRAM_CS 1; return status; }写数据函数支持连续写入void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t i; MRAM_WriteEnable(); // 必须先发 WREN MRAM_CS 0; SPI1_ExchangeByte(0x02); // WRITE 指令 SPI1_ExchangeByte((addr 16) 0x07); // 地址高字节只用低3位 SPI1_ExchangeByte((addr 8) 0xFF); // 地址中字节 SPI1_ExchangeByte(addr 0xFF); // 地址低字节 for(i 0; i len; i) { SPI1_ExchangeByte(buf[i]); } MRAM_CS 1; }读数据函数void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t i; MRAM_CS 0; SPI1_ExchangeByte(0x03); // READ 指令 SPI1_ExchangeByte((addr 16) 0x07); SPI1_ExchangeByte((addr 8) 0xFF); SPI1_ExchangeByte(addr 0xFF); for(i 0; i len; i) { buf[i] SPI1_ExchangeByte(0xFF); // 发 dummy 字节产生时钟 } MRAM_CS 1; }地址高字节那里为什么要 0x07因为 512KB 地址空间只需要 19 位3 个字节共 24 位高 5 位是无效的。虽然芯片可能忽略这些位但显式清零更规范避免以后换更大容量芯片时出问题。4.4 软件片选 vs 硬件片选的选择前面提到我建议用软件片选这里展开说一下原因。PIC18F85J50 的硬件 SS 功能在主机模式下有个特点如果 SS 引脚被拉低SPI 模块会自动切换到从机模式并复位发送逻辑。这在多从机场景下很麻烦因为你要么把 SS 固定拉高那还不如用普通 IO要么就得小心管理。用普通 GPIO 做片选控制权完全在软件手里。你可以精确控制 CS 的拉低和拉高时机可以在 CS 拉低期间做任意操作不受硬件模块干扰。代价就是多写两行代码但换来的灵活性完全值得。具体做法就是把 SS1 引脚RA5配成普通输出用宏定义操作#define MRAM_CS LATA5然后在读写函数里手动控制。这样即使 SPI 模块内部状态机有什么异常CS 的控制也不会受影响。5. 数据完整性保障与掉电场景处理5.1 为什么 MRAM 也需要数据保护有人可能会想MRAM 写入那么快掉电应该不会丢数据吧理论上单字节写入是原子的但 SPI 传输是多字节的如果你正在写一个多字节的数据结构写到一半掉电那这个结构就是半新半旧的状态逻辑上还是坏的。所以数据保护的重点不是防丢而是防不一致。我一般用两种策略校验和加双备份或者日志式写入。5.2 双备份加标志位方案这个方案适合参数存储比如设备配置、校准系数这类不常改但很重要的数据。思路是在 MRAM 里划出两个区域 A 和 B每个区域存一份完整数据外加一个序号和 CRC 校验。写入时先写序号小的那个区域写完校验通过后再更新序号。读取时比较两个区域的序号取序号大的那个有效数据。具体结构可以这样设计typedef struct { uint16_t seq; // 序号每次写入递增 uint16_t crc; // 数据 CRC16 校验 uint8_t data[60]; // 实际数据 } ParamBlock; // 共 64 字节两个块分别放在地址 0x00000 和 0x00040。写入流程读取两个块的序号确定哪个是当前有效块把新数据写入另一个块序号设为当前序号1计算 CRC 并写入读回校验确认写入成功读取流程读两个块分别校验 CRC取 CRC 正确且序号大的那个块如果两个都错用默认值这个方案的好处是任何时刻掉电至少有一个块是完整的。最坏情况是丢失最后一次写入但不会读到坏数据。5.3 日志式写入适合高频数据记录如果是数据记录仪这种高频写入场景双备份就不合适了因为每次写都要写两份浪费空间也浪费时间。这时候用日志式写入更合适。思路是把存储空间当成一个环形缓冲区每条记录带一个递增的序号和 CRC。写入时直接往当前写指针位置写写完指针后移。读取时从最新记录往前找找到第一条 CRC 正确的记录。MRAM 的无限擦写特性在这里发挥得淋漓尽致你不用担心环形缓冲区反复覆盖同一个地址会写坏。我做过一个每秒写 10 条记录的项目跑了一年多MRAM 状态完全正常。5.4 掉电检测与写入时机虽然 MRAM 写入快但如果你要写的数据量比较大比如几 KB还是需要掉电检测来争取时间。PIC18F85J50 有内置的欠压复位BOR和低压检测LVD模块。我一般把 LVD 配到 2.8V 左右假设系统 3.3V 供电当电压降到 2.8V 时触发中断在中断里把关键数据赶紧写进 MRAM。从 2.8V 降到 MRAM 最低工作电压 2.7V中间有 0.1V 的窗口。如果电源端有 100μF 的电容负载电流 20mA那么放电时间大约是t C × ΔV / I 100μF × 0.1V / 20mA 0.5ms0.5ms 在 10MHz SPI 下能写大约 125 字节。如果关键数据不超过这个量就来得及。如果数据量大要么加大电容要么提前在正常运行时就把数据准备好掉电时只写标志位。提示LVD 中断里不要做复杂计算只做最必要的写入操作。中断服务程序越短越好避免在电压下降过程中执行时间过长导致写入失败。6. 常见问题排查与实战避坑指南6.1 读写失败问题速查表现象可能原因排查方法解决方案读出来全是 0xFFCS 没拉低或接线错误示波器看 CS 和 SCK 波形检查片选引脚配置和接线读出来全是 0x00MISO 线没接或从机没供电万用表测 MRAM 的 VDD检查供电和 MISO 连接写入后读出来是旧数据忘了发 WREN读状态寄存器看 WEL 位每次写前加 WREN数据偶尔错位SPI 时钟太快或走线太长降低时钟测试降速或加匹配电阻部分数据正确部分错误地址计算错误打印地址值核对检查地址移位和掩码上电后数据随机上电时序问题看 CS 和 VDD 上电顺序加 CS 上拉确保 VDD 先稳定6.2 那些文档里不会写的坑第一个坑MR25H40CDF 的 CS 拉高之后需要等待至少 5ns 才能进行下一次操作。在低速时钟下这不是问题但如果你用 10MHz 以上时钟连续操作中间不加延时可能出问题。我的做法是在 CS 拉高后加一个NOP()或者几个时钟周期的延时保险起见。第二个坑PIC18F85J50 的 MSSP 模块在 SPI 主机模式下如果 SSP1BUF 写入太快前一次传输还没完成就写第二次会导致数据丢失。所以SPI1_ExchangeByte里的等待循环是必须的不能省。我见过有人为了提速把等待去掉结果数据时对时错查了很久。第三个坑MRAM 虽然写入不需要擦除但如果你往同一个地址连续快速写入中间没有足够的间隔可能会触发内部写保护机制。虽然这种情况很少见但在极端高频写入场景下要注意。我的经验是两次写同一地址之间至少间隔 100ns正常应用都不会碰到这个限制。第四个坑MR25H40CDF 的状态寄存器 BP 位默认是 0所有地址都可写。但如果你不小心写了状态寄存器把 BP 位置 1就会保护部分地址空间导致写不进去。如果你发现写操作莫名其妙失败先读一下状态寄存器看看 BP 位。6.3 调试工具与手段调试 SPI 存储最有用的工具是逻辑分析仪。一个几十块钱的 8 通道逻辑分析仪配合开源软件就能把 CS、SCK、MOSI、MISO 四根线的波形全抓下来。看波形的时候重点看几个东西CS 是否在整个指令期间保持低电平、SCK 的极性和相位是否和从机匹配、数据在时钟边沿是否稳定。如果没有逻辑分析仪用示波器看 SCK 和 CS 也能排查大部分问题。再不行就用最笨的办法在代码里加打印把读到的状态寄存器值、写入前后的数据都打出来对比分析。我个人的习惯是在驱动层加一个自测函数上电时先往 MRAM 的某个测试地址写一个已知模式比如 0xAA55读回来对比确认通信正常后再进行后续操作。这个自测能挡住 90% 的硬件问题。7. 性能实测与优化经验7.1 实测读写速度我在 10MHz SPI 时钟下实测了一组数据操作数据量耗时等效速率单字节写1 字节约 4.2μs-连续写1KB约 4.3ms约 238KB/s连续读1KB约 4.1ms约 250KB/s单字节读1 字节约 4.0μs-这个速度在 8 位单片机方案里已经相当不错了。对比 EEPROM 的页写 5ms 一页通常 64 字节MRAM 快了将近两个数量级。7.2 优化建议如果你觉得速度还不够有几个方向可以优化。提高 SPI 时钟是最直接的。PIC18F85J50 在 40MHz 晶振下最高 10MHz如果你用 64MHz 的外部时钟通过 PLLSPI 可以到 16MHz。但要注意 MRAM 的 40MHz 上限和走线质量。减少函数调用开销。SPI1_ExchangeByte每次调用都有函数进出栈的开销批量读写时可以把循环展开或者用宏替代函数。我试过把读函数里的字节交换改成宏1KB 读取时间从 4.1ms 降到 3.8ms提升约 7%。用 DMA 或者中断方式。PIC18F85J50 没有 SPI DMA但可以用中断方式在后台传输主程序同时做别的事。不过对于大多数应用阻塞式已经够用了中断方式反而增加复杂度。7.3 功耗考量MRAM 的写入功耗比 EEPROM 低因为不需要擦除的高压和长时间。MR25H40CDF 的写入电流典型值约 6mA待机电流约 1mA。对于电池供电的设备可以在不读写的时候把 CS 拉高芯片进入待机模式。如果做低功耗设计还可以考虑给 MRAM 单独供电控制不用的时候直接断电。但要注意断电前确保没有正在进行的写操作否则可能写坏数据。8. 方案扩展与个人体会这套 MRAM PIC18F85J50 的方案我后来在好几个项目里复用包括一个电力监测终端和一个医疗输液泵的数据记录模块。每次稍微调整一下数据结构和保护策略就能直接用省了很多重复开发的时间。扩展方向上有几个思路。如果容量不够可以换 MR25H40CDF 的更大容量型号或者用多颗级联通过不同的 CS 引脚选择。如果主控升级到 16 位或 32 位SPI 时钟可以跑更高MRAM 的 40MHz 上限能充分发挥。如果要做文件系统MRAM 的随机访问特性非常适合做 FAT 或者 LittleFS 的底层存储不需要考虑擦除块对齐的问题。我个人在实际操作中的体会是MRAM 这类新型存储最大的价值不在于性能参数而在于它简化了系统设计。用 EEPROM 或 Flash 的时候你脑子里始终要绷着一根弦擦除了吗写完了吗掉电会不会丢换成 MRAM 之后这些顾虑基本消失了你可以把精力放在业务逻辑上。多花的那点器件成本换来的是更短的开发周期和更低的现场故障率这笔账怎么算都划算。最后分享一个小技巧如果你手头没有 MR25H40CDF想先验证代码逻辑可以用一颗普通的 SPI Flash比如 W25Q64临时替代但要注意 Flash 需要擦除才能写入而且写入后要等待忙状态结束。把驱动层抽象好换芯片的时候只改底层几个函数就行。这样即使项目后期因为成本原因换存储方案上层代码也不用大改。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门