尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MRAM工业数据记录实战:PIC18F4685驱动MR25H40CDF的SPI存储方案

MRAM工业数据记录实战:PIC18F4685驱动MR25H40CDF的SPI存储方案 MRAM 这类存储介质在工业现场其实一直有点叫好不叫座的味道——参数漂亮价格劝退很多人评估完就换回 FRAM 或者带电池的 SRAM 了。但真到了那些要求掉电瞬间不能丢数据、写入次数按亿次算、还要扛住高低温循环的场景MR25H40CDF 这种 SPI 接口的磁存储器就会重新回到选型清单里。我最近用 PIC18F4685 搭配 MR25H40CDF 做了一套数据记录方案从硬件连线到驱动时序踩了不少坑这篇就把整个存储与读取的完整链路拆开讲清楚包括为什么这么选、SPI 模式怎么配、页写和连续读怎么处理、以及工业现场最容易翻车的几个细节。1. 为什么工业嵌入式场景会盯上 MR25H40CDF 这颗料1.1 先搞清楚 MRAM 和常见存储介质的本质差异很多人第一次看到 MR25H40CDF 的参数会有点懵4Mbit 容量、SPI 接口、没有写延迟、擦写次数标称 10^14 次以上。这几个指标放在一起和 EEPROM、Flash、FRAM 都不太一样。要理解它的价值得先明白 MRAM 的存储原理——它用磁性隧道结MTJ的磁化方向来记录 0 和 1而不是靠电荷或者浮栅。这个物理机制决定了它写数据不需要先擦除也不存在电荷泄漏导致的保持时间衰减问题。对比一下就更清楚了。SPI Flash比如常见的 W25Q 系列写入前必须擦除整个扇区擦除一次动辄几毫秒到几十毫秒而且擦写寿命通常只有 10 万次量级。EEPROM 虽然能字节级写入但写入速度慢寿命也就 100 万次左右。FRAM 倒是快、寿命也高但容量普遍偏小而且对某些极端温度环境的适应性不如 MRAM。MR25H40CDF 的定位正好卡在需要频繁写入 掉电不能丢 工业级温度范围这个交叉点上。提示MRAM 的非易失是真的非易失不需要像 FRAM 那样担心某些工艺下的保持特性也不像带电池的 SRAM 那样有电池寿命这个定时炸弹。1.2 4Mbit 容量在数据记录场景里到底够不够用MR25H40CDF 是 512K 字节的容量听起来不大但在工业数据记录里其实相当能打。关键在于你怎么规划存储结构。我这次做的方案是记录设备运行状态每条记录包含时间戳4 字节、温度值2 字节、压力值2 字节、状态标志1 字节和校验字节1 字节一共 10 字节。512K 字节可以存大约 5.2 万条记录按每分钟记录一次算能连续记录 36 天左右。如果改成事件触发式记录只在实际发生异常时写入那这个容量撑几个月甚至一年都没问题。真正需要提前想清楚的是地址空间怎么划分。我习惯把存储区分成三块头部信息区存记录总数、写指针、版本号等元数据、数据区循环写入记录、以及一个小的配置区存设备参数。头部和配置区放在低地址数据区从某个对齐边界开始这样后续做数据导出和解析的时候逻辑很清晰。MR25H40CDF 的地址是 19 位512K 字节用 3 字节地址传输最高几位是保留位实际有效地址范围是 0x00000 到 0x7FFFF。1.3 PIC18F4685 搭配这颗料的合理性分析PIC18F4685 是 Microchip 的一款 8 位单片机自带 MSSP 模块可以配置成 SPI 主模式最高 SPI 时钟能到 10MHz系统时钟 40MHz 时。它本身有 96KB Flash 和 3936 字节 RAM跑一个数据记录和存储管理的固件绰绰有余。选它搭配 MR25H40CDF 的核心原因是MSSP 硬件 SPI 稳定可靠不需要软件模拟时序而且 PIC18F4685 的引脚资源丰富工业现场常用的 UART、I2C、ADC 都能同时用上不会因为接了存储器就牺牲其他外设。另一个实际考量是供电。MR25H40CDF 工作电压 2.7V 到 3.6VPIC18F4685 可以工作在 2.0V 到 5.5V两者在 3.3V 系统里可以直接对接不需要额外的电平转换。这一点在紧凑的工业板卡上很关键少一颗电平转换芯片就少一个故障点。2. 硬件连接与 SPI 模式配置里那些容易翻车的点2.1 引脚连接不是照着数据手册连上就行MR25H40CDF 是 8 引脚封装SPI 接口涉及 CS、SCK、SI、SO 四根线加上 VDD、VSS 和两个 NC 引脚。看起来简单但实际布线有几个坑。首先是 CS 信号这颗芯片的片选是低有效而且对 CS 的建立时间和保持时间有要求。数据手册里写的 tCSSCS 建立到 SCK 第一个边沿和 tCSHSCK 最后一个边沿到 CS 拉高都是纳秒级但如果你用的是软件控制 CSGPIO 翻转速度跟不上高速 SPI 时钟就会出现数据错位。我的做法是如果 SPI 时钟超过 5MHzCS 尽量用硬件片选或者确保 GPIO 翻转足够快如果时钟在 1MHz 以下软件片选完全没问题。另外 SO 引脚是推挽输出不需要外部上拉但 SI 和 SCK 在空闲时的电平状态要注意避免在 CS 有效期间产生毛刺。PCB 布线时这四根线尽量等长、远离高频干扰源尤其是 SCK 走线不要和电机的 PWM 线平行走。引脚名称方向连接要点1CS输入低有效建议 10K 上拉到 VDD2SO输出直接接 MCU 的 SDI无需上拉3NC-悬空或接地不要接信号4VSS电源就近放置 0.1uF 去耦电容5SI输入接 MCU 的 SDO空闲时保持低电平6SCK输入接 MCU 的 SCK走线尽量短7NC-悬空或接地8VDD电源2.7V-3.6V加 0.1uF 1uF 去耦2.2 SPI 模式选择Mode 0 还是 Mode 3MR25H40CDF 支持 SPI Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1。这两种模式的区别在于时钟空闲电平和数据采样边沿。Mode 0 下 SCK 空闲为低数据在上升沿采样Mode 3 下 SCK 空闲为高数据在上升沿采样。两种模式都能用但实际项目里我强烈建议统一用 Mode 0原因是大多数 MCU 的硬件 SPI 默认配置就是 Mode 0而且逻辑分析仪抓波形时 Mode 0 的时序看起来更直观。配置 PIC18F4685 的 MSSP 模块时关键寄存器是 SSPCON1 和 SSPSTAT。SSPCON1 的 SSPM 位设为 0b0000 表示 SPI 主模式CKP 位控制时钟极性SSPSTAT 的 CKE 位控制时钟边沿。要配成 Mode 0CKP0、CKE0要配成 Mode 3CKP1、CKE1。这里有个容易搞混的地方CKE 位在发送和接收时的含义略有不同数据手册里的表格要看仔细。我一般直接写一个初始化函数把这两个寄存器一次性配好避免中途改来改去。// PIC18F4685 MSSP SPI 主模式初始化Mode 0 void SPI_Init(void) { TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC5 0; // SDO 输出 TRISCbits.TRISC4 1; // SDI 输入 SSPSTAT 0x00; // CKE0, SMP0 SSPCON1 0x20; // SSPM0000, CKP0, SPI Master Mode 0 PIR1bits.SSPIF 0; }2.3 时钟频率到底能跑多快MR25H40CDF 的数据手册标称最高 SPI 时钟是 40MHz但这个数字是在理想条件下的。实际用 PIC18F4685 驱动时SPI 时钟由系统时钟分频得到40MHz 系统时钟下最高能到 10MHz。我实测下来10MHz 在短走线、干净电源的条件下可以稳定工作但如果板子上有电机或者继电器建议降到 5MHz 甚至更低。这里有个经验值工业现场如果电缆长度超过 10cmSPI 时钟最好不超过 2MHz。因为 SPI 是单端信号没有差分抗干扰能力长线传输时 SCK 和数据的边沿容易畸变。我遇到过一块板子实验室里 10MHz 跑得好好的装到现场后偶尔读出全 0xFF后来把时钟降到 1MHz 就再没出过问题。所以别迷信数据手册的最高频率实际能跑多快取决于你的板子和环境。3. 从写使能到页写MR25H40CDF 的完整操作流程3.1 写操作前必须做的 WREN 和状态轮询MR25H40CDF 的写操作有一个强制前置步骤先发 WRENWrite Enable命令码 0x06指令把芯片内部的写使能锁存器置位。这个锁存器在每次写操作完成后会自动复位所以每次写之前都要重新发 WREN。很多人第一次用的时候会忘掉这一步结果写指令发出去芯片毫无反应读回来还是旧数据。发完 WREN 之后紧接着发写指令WRITE命令码 0x02后面跟 3 字节地址和要写入的数据。写指令执行期间芯片内部在忙这时候如果你发新的指令芯片会忽略。所以要么在两次写之间加足够的延时要么轮询状态寄存器RDSR命令码 0x05的 WIP 位Write In Progress。WIP 位为 1 表示芯片正在写为 0 表示空闲。我一般用轮询方式比固定延时更可靠因为写入时间会随温度和电压变化。// 等待写操作完成 void MRAM_WaitReady(void) { uint8_t status; do { CS_LOW(); SPI_Transfer(0x05); // RDSR status SPI_Transfer(0x00); // 读状态 CS_HIGH(); } while (status 0x01); // WIP 位为 1 则继续等 }3.2 页写机制256 字节边界是硬约束MR25H40CDF 支持页写Page Write一页是 256 字节。页写的规则是你可以在一次 CS 有效期间连续写入最多 256 字节地址在页内自动递增。但如果写入跨越了 256 字节的页边界地址会回绕到当前页的开头覆盖之前写的数据。这个行为是硬件决定的不会报错所以如果你不注意数据就会被悄悄覆盖。举个例子假设你从地址 0x00F0 开始写 32 字节地址会依次到 0x00FF然后下一个字节会回绕到 0x0000而不是 0x0100。这就是典型的跨页回绕问题。解决办法很简单写之前先算一下当前地址到页边界的剩余空间如果剩余空间不够就分两次写。我写了一个封装函数自动处理跨页拆分调用的时候只需要传起始地址、数据指针和长度。// 自动处理页边界的写入函数 void MRAM_Write(uint32_t addr, uint8_t *data, uint16_t len) { while (len 0) { uint16_t page_remain 256 - (addr 0xFF); uint16_t chunk (len page_remain) ? len : page_remain; CS_LOW(); SPI_Transfer(0x06); // WREN CS_HIGH(); CS_LOW(); SPI_Transfer(0x02); // WRITE SPI_Transfer((addr 16) 0x07); SPI_Transfer((addr 8) 0xFF); SPI_Transfer(addr 0xFF); for (uint16_t i 0; i chunk; i) { SPI_Transfer(data[i]); } CS_HIGH(); MRAM_WaitReady(); addr chunk; data chunk; len - chunk; } }3.3 连续读和快速读的区别与选用读操作有两种指令READ0x03和 FAST_READ0x0B。READ 指令在发完 3 字节地址后直接输出数据适合低速时钟。FAST_READ 在地址后面多一个 dummy 字节8 个时钟然后才输出数据这个 dummy 字节是为了让芯片内部有足够时间准备数据适合高速时钟。在 10MHz 以下READ 指令完全够用如果时钟超过 20MHz建议用 FAST_READ。连续读的时候地址会自动递增可以一直读到地址空间末尾。CS 保持低电平期间数据会持续输出。如果读到 0x7FFFF 之后继续读地址会回绕到 0x00000。这个特性在做整片数据导出的时候很方便但要注意别读过头。我一般会在读之前先算好要读的字节数读够就拉高 CS。4. 工业现场数据可靠性校验、磨损与掉电保护4.1 每条记录加校验字节的必要性工业现场的电磁环境复杂SPI 通信虽然比 I2C 抗干扰强但也不是绝对可靠。我在实际项目里遇到过读出来的数据某一位翻转的情况概率不高但确实存在。所以每条记录加一个校验字节是很有必要的。最简单的方案是加一个 XOR 校验或者 CRC-8 校验。XOR 校验计算快但检错能力有限CRC-8 稍微复杂一点但对突发错误的检测能力强很多。我这次用的是 CRC-8多项式 0x07初始值 0x00。每条记录写入前计算好 CRC 放在末尾读取时重新计算并比对。如果校验失败就把这条记录标记为无效跳过或者尝试重新读取。实测下来加了 CRC 之后数据可靠性明显提升尤其是在电机启停频繁的场景下。4.2 MRAM 的磨损到底需不需要担心MR25H40CDF 的擦写寿命标称 10^14 次以上这个数字大到基本可以忽略磨损问题。按每分钟写一次算一年也就 52 万次要写到 10^14 次需要将近 2 亿年。所以和 Flash 不同你不需要做磨损均衡Wear Leveling。但这并不意味着可以随便乱写因为频繁写入会增加功耗而且在某些低功耗场景下写入电流的峰值可能影响电源稳定性。我的建议是如果数据变化不频繁可以用变化才写的策略即先读当前值和要写的新值比较不同才执行写入。这样既能减少写入次数又能降低功耗。另外MRAM 的写入电流比读取电流大不少如果系统是电池供电这一点要纳入功耗预算。4.3 掉电保护写指针和数据的原子性数据记录系统最怕的就是写到一半掉电导致写指针和数据不一致。MR25H40CDF 的写入是原子性的吗严格来说单次页写256 字节以内在掉电时可能只写入了一部分芯片不保证整页的原子性。所以如果你的记录长度超过一个字节就需要额外的保护机制。我的做法是在数据区之外单独维护一个写指针变量每次写完一条记录后再更新写指针。写指针的更新放在记录写入完成并校验通过之后。这样即使掉电发生在记录写入过程中写指针还指向旧位置下次上电后从旧位置重新写最多丢失一条记录不会破坏整个数据结构。另外写指针本身也可以存两份互为备份读取时取一致的那个值。5. 调试实录那些让我熬夜的 SPI 通信问题5.1 读出来全是 0xFF 的排查过程第一次调试的时候上电后读 MR25H40CDF 的 Device IDRDID 指令 0x9F读回来全是 0xFF。0xFF 通常意味着 MISO 线一直是高电平要么是芯片没响应要么是 MISO 线没接好。我按以下顺序排查先用万用表量 VDD 和 VSS确认供电正常3.3V 稳定。用示波器看 CS 信号确认拉低时有明显下降沿且低电平期间没有毛刺。看 SCK 信号确认有 8 个时钟脉冲频率和预期一致。看 SI 信号确认命令码 0x9F 正确发出。最后看 SO 信号发现 SO 一直高电平没有任何翻转。问题定位在 SO 线。检查 PCB 发现 SO 走线经过了一个过孔而过孔的焊接不良导致虚焊。重新补焊后RDID 正常读回 0x00 0x2C 0x20 等标识字节。这个坑告诉我SPI 通信出问题先查硬件连接再查时序配置最后才怀疑芯片本身。5.2 高速时钟下的数据错位解决了硬件问题后我把 SPI 时钟提到 10MHz结果读出来的数据偶尔错位比如本该是 0x12 0x34 的序列变成了 0x34 0x12 或者 0x00 0x12。这种错位通常是采样边沿不对或者建立保持时间不够。我用逻辑分析仪抓了波形发现 SCK 的上升沿和数据的跳变几乎同时发生导致采样不稳定。解决办法有两个一是降低时钟频率到 5MHz错位消失二是调整 SPI 模式的采样边沿从 Mode 0 改成 Mode 3让数据在下降沿变化、上升沿采样留出更长的建立时间。我最终选择了降频因为 5MHz 对数据记录来说完全够用没必要为了速度牺牲稳定性。5.3 写操作偶尔失败的根因还有一个偶发问题写操作偶尔失败读回来还是旧数据。排查后发现是 WREN 指令和 WRITE 指令之间的 CS 拉高时间太短。MR25H40CDF 要求 WREN 之后 CS 拉高然后再拉低发 WRITE这两个操作之间需要一定的间隔。我的代码里 CS 拉高后立刻又拉低间隔只有几个时钟周期芯片来不及锁存写使能状态。在 CS 拉高后加了一个微秒级的延时问题解决。这个延时不需要很长1 到 2 微秒就够了但必须有。数据手册里的时序图标注了 tSHSLCS 高电平最短时间实际值在纳秒级但考虑到 GPIO 翻转速度和芯片内部逻辑留一点余量更稳妥。6. 把存储层封装成可复用的驱动模块6.1 分层设计底层 SPI 与上层记录管理分离项目做到后期我把代码分成了两层底层是 MRAM 驱动只负责字节级的读写和状态轮询上层是记录管理负责记录结构定义、写指针维护、校验计算和循环覆盖策略。这样分层的好处是如果以后换用其他 SPI 存储芯片只需要改底层驱动上层逻辑不用动。底层驱动的接口我定义了这几个函数MRAM_Init()、MRAM_Read()、MRAM_Write()、MRAM_WaitReady()、MRAM_ReadID()。上层记录管理的接口有Record_Write()、Record_Read()、Record_GetCount()、Record_EraseAll()。接口清晰之后调试和测试都方便很多。6.2 循环覆盖策略与写指针管理数据区是循环使用的写满之后从开头覆盖。写指针是一个 32 位变量指向下一条记录要写入的地址。每次写入后写指针增加一条记录的长度如果超出数据区末尾就回绕到数据区起始地址。同时维护一个记录总数计数器用于上层查询。这里有个细节写指针和记录总数需要定期同步到 MRAM 的头部信息区否则掉电后会丢失。我的策略是每写入 10 条记录同步一次或者检测到电源电压下降时立即同步。PIC18F4685 有低电压检测LVD模块可以配置成在电压低于阈值时产生中断在中断里把关键数据写入 MRAM。这个机制在实际现场救过好几次数据。6.3 上电自检与数据恢复流程每次上电后固件会执行一套自检流程先读 Device ID 确认芯片在线然后读头部信息区的魔数和版本号确认数据结构有效。如果头部信息损坏就尝试从备份区恢复如果备份也损坏就重新初始化存储区并记录一个存储区已重置的事件。自检通过后系统会根据写指针和记录总数重建内存中的索引。这个过程不需要读取所有记录只需要读头部信息即可。如果头部信息的 CRC 校验失败就扫描数据区找到最后一条有效记录重建写指针。这个恢复流程虽然不常触发但一旦触发就是救命的。7. 几个只有实际做过才会知道的细节7.1 上电时序MRAM 就绪之前不要发指令MR25H40CDF 的上电时间很短数据手册标称 tPU 是 300 微秒左右。但如果你在上电后立刻发 SPI 指令芯片可能还没准备好导致第一条指令被忽略。我的做法是在初始化函数里加一个 1 毫秒的延时等电源稳定、芯片内部就绪后再开始通信。这个延时在大多数场景下感觉不到但能避免很多莫名其妙的初始化失败。7.2 CS 引脚的上拉电阻不是可有可无CS 是低有效信号如果在上电过程中 MCU 的 GPIO 还处于高阻态CS 可能被外部干扰拉低导致芯片误触发。加一个 10K 的上拉电阻到 VDD可以确保 CS 在上电期间保持高电平芯片不响应任何指令。这个电阻成本几乎为零但能避免很多上电时的随机故障。7.3 电源去耦电容要就近放置MR25H40CDF 在写入时的电流峰值比读取时大如果去耦电容离芯片太远电源电压会有瞬间跌落可能导致写入失败。我的做法是在 VDD 和 VSS 引脚旁边放一个 0.1uF 的陶瓷电容再在板级电源入口放一个 1uF 的电容。两个电容配合既能滤高频噪声又能提供瞬态电流。7.4 温度对 SPI 时序的影响工业现场的温度范围可能是 -40°C 到 85°C。在极端温度下SPI 信号的传播延迟会变化芯片内部的时序余量也会缩小。我在高低温箱里测试过10MHz 时钟在 85°C 时偶尔出现误码降到 5MHz 后全温度范围稳定。所以如果你的产品要过工业级温度认证SPI 时钟频率一定要留足余量别按常温下的极限值来配。7.5 数据导出的格式设计最后说一个容易被忽略的点数据导出格式。现场调试的时候经常需要把 MRAM 里的数据读出来分析。如果记录格式设计得不好导出后还要写脚本解析很麻烦。我的做法是记录格式尽量简单固定长度字段对齐并且在头部信息区存一个记录格式的版本号。导出时直接按固定长度切片用 Excel 或者 Python 都能快速解析。另外可以在数据区末尾留一块调试信息区存一些运行日志方便现场排查问题。这套方案跑了大半年现场反馈一直很稳定。MR25H40CDF 的可靠性确实对得起它的价格PIC18F4685 的 MSSP 模块也足够省心。如果你也在做类似的数据记录项目建议先把 SPI 时序和页写边界这两个点吃透剩下的就是按部就班的工程实现了。
返回列表