ARTICLE DETAIL

资讯详情

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

工业级MRAM数据存储实战:PIC18F45K22与MR25H40CDF SPI驱动详解

工业级MRAM数据存储实战:PIC18F45K22与MR25H40CDF SPI驱动详解 1. 项目缘起与方案选型1.1 为什么要在工业场景里折腾 MRAM 这颗怪胎做工业嵌入式的人都有一个共同的痛设备装在现场断电是家常便饭振动、高低温、电磁干扰轮番上阵而你还得保证关键数据一个字节都不能丢。传统的方案无非是 EEPROM 或者带后备电池的 SRAM再或者 SPI NOR Flash。EEPROM 写入慢、擦写寿命有限频繁记录日志很快就写废了带电池的 SRAM 又要考虑电池寿命和环保问题维护成本高NOR Flash 虽然容量大但写入前必须擦除整个扇区掉电瞬间如果正好在擦除整个扇区的数据可能全没了。MR25H40CDF 这颗芯片就是冲着这些痛点来的。它是一颗 4Mbit512KB的 SPI 接口 MRAM磁性随机存储器最大的特点是非易失、写入无需擦除、写入速度接近 SRAM、擦写寿命几乎无限官方标称 10^14 次以上。你可以把它理解成一个断电不丢数据的 SRAM写一个字节就是写一个字节不用管什么扇区、页、擦除周期。对于工业数据记录、参数存储、掉电保护这类场景它几乎是理想选择。我这次的项目背景是一台工业现场的数据采集终端主控用的是 PIC18F45K228 位 MCU44 引脚64KB Flash3.8KB RAM自带 MSSP 模块可以跑 SPI。需求很明确每 100ms 采集一次传感器数据需要把最近 24 小时的关键数据循环存储掉电后重新上电要能读出来而且设备可能每天开关机好几次一年下来写入次数轻松上百万次。这种工况下EEPROM 和 Flash 都撑不住MRAM 是唯一合理的选择。1.2 PIC18F45K22 的 SPI 资源盘点PIC18F45K22 有两个 MSSP 模块MSSP1 和 MSSP2都可以配置成 SPI 主模式。我这次用的是 MSSP1映射在 RC3SCK、RC4SDI、RC5SDO上片选 CS 我单独用 RA5 控制。为什么不用硬件片选因为 PIC18 的 MSSP 在 SPI 主模式下并不直接提供自动片选信号CS 需要软件控制这一点和 STM32 的硬件 NSS 不太一样后面会详细说。时钟方面PIC18F45K22 在 64MHz 内部振荡器PLL 倍频后下SPI 时钟可以做到 Fosc/4 16MHz。但 MR25H40CDF 的 SPI 时钟上限是 40MHz所以 16MHz 完全在范围内。不过实际布线时我没有跑满后面会讲为什么降到 8MHz 更稳。1.3 整体方案框架整个方案分三层底层是 SPI 驱动负责字节收发中间是 MR25H40CDF 的读写驱动封装了写使能、写状态寄存器、页写、连续读等操作上层是数据管理逻辑负责循环缓冲区的地址管理和掉电恢复。这个分层的好处是SPI 驱动可以复用到其他 SPI 设备MRAM 驱动可以换到别的 MCU 平台数据管理逻辑跟硬件无关。选这个架构还有一个考虑工业现场调试困难分层之后每一层都可以单独用逻辑分析仪或者串口打印来验证出问题容易定位。我见过太多人把 SPI 收发和数据逻辑揉在一起结果通信偶尔出错时根本不知道是时序问题还是逻辑问题。2. MR25H40CDF 核心细节与 SPI 时序要点2.1 芯片引脚与硬件连接MR25H40CDF 是 8 引脚 SOIC 封装引脚定义很标准CS片选低有效、SCK时钟、SI数据输入、SO数据输出、WP写保护低有效、HOLD保持低有效、VCC、GND。实际使用中WP 和 HOLD 如果不用直接拉高到 VCC 即可。我这边 WP 拉高HOLD 也拉高因为不需要暂停传输功能。硬件连接上有一个细节值得说CS 线上我加了一个 10K 上拉电阻。为什么因为 PIC18 在上电复位期间IO 口是高阻态如果此时 CS 浮空被干扰拉低MRAM 可能会误进入某种状态。加上拉之后上电期间 CS 稳定为高芯片处于待机安全。这个电阻在实验室可能看不出差别但在工业现场电磁环境复杂加上它心里踏实。电源去耦方面VCC 引脚旁边放了一个 100nF 陶瓷电容加一个 1uF 钽电容越靠近芯片越好。MRAM 在写入瞬间电流会有波动去耦不好可能导致写入失败这种问题很隐蔽示波器看电源纹波才能发现。2.2 SPI 模式选择Mode 0 还是 Mode 3MR25H40CDF 支持 SPI Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1。这两种模式的区别在于时钟空闲电平和采样边沿。Mode 0 是时钟空闲低电平上升沿采样Mode 3 是时钟空闲高电平下降沿采样。我选的是 Mode 0原因是 PIC18 的 MSSP 在 Mode 0 下配置最直接CKP0、CKE0 即可。而且 Mode 0 在大多数逻辑分析仪上解码最直观调试方便。实际测试下来16MHz 时钟下 Mode 0 读写都稳定。这里有个坑要提醒如果你用软件模拟 SPIMode 0 和 Mode 3 的代码写法不同Mode 0 是先拉低时钟再放数据Mode 3 是先放数据再拉高时钟。搞混了会出现读出来的数据整体移位或者全 0xFF很多人第一次调 SPI 都栽在这里。2.3 指令集与操作码MR25H40CDF 的指令集很简洁常用的就几条指令名称操作码功能说明WREN0x06写使能写操作前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据状态寄存器只有 bit0WEL写使能锁存和 bit1BP0块保护等几位需要关注。WEL 位在每次 WREN 后置 1写操作完成后自动清零。所以每次写之前都要发 WREN不能偷懒。注意WREN 之后如果 CS 拉高WEL 位会被清除。所以 WREN 和后续的 WRITE 之间不能拉高 CS必须连续操作。2.4 地址空间与页写机制4Mbit 对应 512KB 地址空间地址线需要 19 位2^19 524288。SPI 传输时地址是 3 个字节高 5 位无效实际用低 19 位。比如地址 0x00000 到 0x7FFFF。MR25H40CDF 支持页写每页 256 字节。什么意思如果你从地址 0x0000F0 开始连续写 32 字节写到 0x00010F这没问题因为没跨页。但如果你从 0x0000F0 开始写 32 字节写到 0x000110这就跨页了地址会回卷到页首 0x000100覆盖掉之前的数据。这是 MRAM 和 EEPROM 类似的地方页写不能跨页。我的做法是数据管理逻辑里保证每次写入不超过页边界如果一条记录跨页就拆成两次写。虽然 MRAM 单字节写入没有额外开销但拆开写逻辑更清晰也避免了回卷风险。3. PIC18F45K22 SPI 驱动实现3.1 MSSP 初始化配置PIC18F45K22 的 MSSP1 初始化代码如下我用的是 XC8 编译器void SPI1_Init(void) { TRISC3 0; // SCK 输出 TRISC5 0; // SDO 输出 TRISC4 1; // SDI 输入 TRISAbits.TRISA5 0; // CS 输出 LATA5 1; // CS 默认高 SSP1STAT 0x40; // CKE0, SMP0 (Mode 0) SSP1CON1 0x20; // SSPEN1, CKP0, SSPM0000 (主模式 Fosc/4) SSP1ADD 0; // 不用 }SSP1STAT 的 bit6 是 CKEbit7 是 SMP。Mode 0 要求 CKE0、SMP0所以 SSP1STAT 0x40 其实是 CKE1等等这里要仔细看数据手册。SSP1STAT 寄存器 bit6 是 CKEbit7 是 SMP。0x40 是 bit6 置 1即 CKE1。但 Mode 0 要求 CKE0。所以正确写法应该是 SSP1STAT 0x00 或者只置 SMP0。我实际用的是 SSP1STAT 0x00SSP1CON1 0x20。这样 CKP0、CKE0对应 Mode 0。写代码时一定要对着数据手册的时序图确认不要凭记忆。3.2 字节收发函数SPI 字节收发是驱动的基础代码如下uint8_t SPI1_ExchangeByte(uint8_t data) { SSP1BUF data; while(!PIR1bits.SSP1IF); PIR1bits.SSP1IF 0; return SSP1BUF; }这个函数看起来简单但有两个细节。第一写入 SSP1BUF 后要等 SSP1IF 置位表示一个字节收发完成。第二读 SSP1BUF 之前必须清 SSP1IF否则下次判断会出错。我见过有人忘了清标志结果程序卡死在 while 循环里。还有一个隐藏问题如果 SPI 时钟太快而 MRAM 还没准备好读回来的数据可能是错的。所以我在每次 CS 拉低之后加了一个很小的延时几个 NOP确保 MRAM 检测到片选有效。这个延时在 16MHz 下可能不需要但在 8MHz 或者更长走线时有用。3.3 片选控制与操作序列片选控制我封装了两个宏#define MRAM_CS_LOW() (LATA5 0) #define MRAM_CS_HIGH() (LATA5 1)读操作序列CS 拉低 - 发 READ 指令 - 发 3 字节地址 - 读 N 字节 - CS 拉高。写操作序列CS 拉低 - 发 WREN - CS 拉高 - CS 拉低 - 发 WRITE - 发 3 字节地址 - 写 N 字节 - CS 拉高。注意 WREN 之后必须拉高 CS 再拉低这是 MRAM 的要求和某些 Flash 不同。我一开始以为 WREN 和 WRITE 可以连续发结果写不进去查了半天手册才发现这个问题。3.4 读状态寄存器与写保护读状态寄存器用来确认 WEL 位是否置位uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_LOW(); SPI1_ExchangeByte(0x05); status SPI1_ExchangeByte(0xFF); MRAM_CS_HIGH(); return status; }写操作后我会读一次状态确认 WEL 已经清零表示写入完成。虽然 MRAM 写入很快但保险起见还是确认一下。这个习惯在调试阶段特别有用能快速判断写使能有没有生效。4. 数据存储与读取的完整实操4.1 存储结构设计我的数据记录格式是每条记录 16 字节4 字节时间戳、4 字节传感器 1 数据、4 字节传感器 2 数据、2 字节 CRC、2 字节保留。512KB 空间可以存 32768 条记录。我把它组织成循环缓冲区用一个 4 字节的写指针存在 MRAM 的固定位置比如地址 0x00000上电时先读指针然后从指针位置继续写。为什么用循环缓冲区因为工业现场数据是持续产生的不可能无限存储循环覆盖最旧的数据是最合理的策略。写指针每次加 16到了末尾回卷到数据区起始地址。数据区从 0x00010 开始留出前 16 字节存指针和配置参数。4.2 写入流程与掉电保护写入一条记录的流程读当前写指针从 MRAM 固定地址读 4 字节计算目标地址 数据区起始 指针偏移如果目标地址 16 超过数据区末尾回卷到数据区起始写 16 字节数据到目标地址更新写指针到 MRAM 固定地址这里的关键是第 4 步和第 5 步的顺序。如果先写数据再写指针掉电发生在写数据之后、写指针之前那么上电后指针还是旧的会覆盖刚才写的数据但数据本身是完整的不会丢。如果先写指针再写数据掉电后指针指向新位置但数据没写会读到垃圾数据。所以必须先写数据再写指针。注意MRAM 写入本身是原子的吗不是。16 字节写入过程中掉电可能只写了一半。所以我在每条记录里加了 CRC上电读取时校验 CRC如果失败就认为这条记录无效跳过。这样即使掉电导致半写也不会把错误数据当成有效数据。4.3 读取流程与数据校验读取流程读写指针从数据区起始开始逐条读取记录对每条记录计算 CRC与存储的 CRC 比较CRC 正确的记录存入内存数组供上层使用遇到 CRC 错误的记录标记为无效继续读下一条实际测试中我模拟了写入过程中断电的情况连续测试 100 次每次上电后都能正确识别出最后一条不完整的记录并跳过之前的数据完好。这个测试很关键工业现场掉电是常态必须保证数据一致性。4.4 实测性能数据在 16MHz SPI 时钟下实测写入一条 16 字节记录含 WREN、地址、数据、状态确认耗时约 25 微秒。读取一条记录约 15 微秒。100ms 采集周期下写入占用时间不到 0.03%完全不影响主循环。如果降到 8MHz写入耗时约 45 微秒仍然可以接受。我最终产品用的是 8MHz原因是 PCB 走线较长约 10cm16MHz 下偶尔出现误码降到 8MHz 后连续运行 72 小时无错误。这个取舍很典型速度够用就行稳定性优先。5. 常见问题与排查技巧实录5.1 读出来全是 0xFF 或 0x00这是最常见的 SPI 问题。原因通常有三个CS 没拉低、时钟模式不对、MOSI/MISO 接反。排查步骤先用逻辑分析仪抓 CS、SCK、MOSI、MISO 四根线看 CS 是否在传输期间保持低电平SCK 是否有 8 个脉冲MOSI 上是否有正确的指令码。如果 CS 正常但数据不对检查 CPOL/CPHA 配置。如果 MOSI 上有数据但 MISO 没反应检查 MISO 线是否接对、MRAM 是否供电正常。我遇到过一次MISO 一直高电平查了半天发现是 MRAM 的 HOLD 引脚悬空被干扰拉低导致芯片一直处于保持状态不输出。把 HOLD 拉高就好了。这个坑很隐蔽因为 HOLD 引脚在大多数应用里不用很容易忽略。5.2 写入不生效写入不生效的典型表现是读回来还是旧数据。原因通常是 WREN 没发、WREN 和 WRITE 之间拉了 CS、或者 WP 引脚被拉低。排查时先读状态寄存器确认 WEL 位如果 WEL0说明 WREN 没生效检查 WREN 指令和 CS 时序。如果 WEL1 但写不进去检查 WP 引脚是否拉高。还有一个可能是地址算错了写到了别的地址。这种情况读回来的是别的位置的数据看起来像没写进去。建议在调试阶段把地址打印出来和预期对比。5.3 跨页写入导致数据回卷前面提到过页写不能跨页。如果你从地址 0x0000F0 写 32 字节实际会写到 0x0000F0 到 0x0000FF然后回卷到 0x000100 到 0x00010F。如果你期望的是 0x0000F0 到 0x00010F那就错了。排查方法是检查写入起始地址的低 8 位加上写入长度是否超过 256。如果超过要么拆成两次写要么调整起始地址。我的做法是在驱动层加一个断言如果检测到跨页就拆包。这样上层逻辑不用关心页边界驱动层自动处理。5.4 上电后数据丢失如果上电后读不到之前写的数据先确认 MRAM 是否真的非易失。MR25H40CDF 是 MRAM断电数据应该保留。如果丢了检查电源是否在掉电时快速下降导致写入未完成或者检查写指针是否被意外复位。我遇到过一次上电后指针总是 0查了半天发现是初始化代码里有一句把指针区域清零的操作本意是初始化结果每次上电都把指针清了。这种逻辑错误很常见建议初始化只在新芯片第一次使用时执行或者用魔术字判断是否已初始化。5.5 常见问题速查表现象可能原因排查方法解决方法读出全 0xFFCS 未拉低、MISO 断开逻辑分析仪抓波形检查 CS 和 MISO 连接读出全 0x00时钟模式错误检查 CPOL/CPHA改为 Mode 0写入不生效WREN 未发或 CS 时序错读状态寄存器 WEL 位确保 WREN 后 CS 拉高再拉低数据回卷跨页写入检查地址低 8 位长度拆包或调整地址上电数据丢失初始化误清零检查初始化代码加魔术字判断偶发误码时钟太快、走线长降低时钟测试降到 8MHz 或加匹配电阻6. 实操心得与扩展思路6.1 调试工具的选择调 SPI 最有用的工具是逻辑分析仪没有之一。我用的是 8 通道 24MHz 采样率的入门款抓 SPI 足够。关键是它能直接解码 SPI 协议把 MOSI 和 MISO 上的字节显示出来比示波器看波形直观得多。如果预算有限至少也要有一个能看时序的示波器否则出了问题只能盲猜。串口打印也很重要。我在驱动层加了调试宏可以开关打印每次读写的地址和数据。调试阶段打开量产关闭。这样既不占用最终产品的资源又能在需要时快速定位。6.2 电源设计的重要性MRAM 虽然写入快但写入瞬间电流比读操作大。如果电源去耦不好写入时电压跌落可能导致写入失败。我在 VCC 引脚旁边放了 100nF 和 1uF 电容并且电源走线尽量粗。实测发现去耦电容离芯片超过 5mm 效果就明显下降。所以布局时一定要把电容紧贴芯片引脚。6.3 数据安全与冗余工业现场对数据安全要求高我在设计里加了双重保护一是每条记录带 CRC二是关键配置参数存两份地址分开读取时对比不一致就用备份恢复。这样即使某个地址的存储单元出问题虽然 MRAM 概率极低也能恢复。另外写指针我也存了两份一份在 0x00000一份在 0x00008上电时读两份对比一致才使用不一致就用较大的那个因为较大的指针意味着更新的数据。这个策略在实际测试中成功处理了一次模拟的指针区域干扰。6.4 后续扩展方向这个方案目前是单机存储后续可以考虑加一个无线模块把数据定期上传到网关。MRAM 的快速写入特性使得高频采集成为可能比如把采集周期从 100ms 降到 10msMRAM 完全撑得住。另外PIC18F45K22 的另一个 MSSP 模块可以接一个 SPI 显示屏实时显示存储状态和数据曲线现场调试会更方便。如果数据量再大可以换更大容量的 MRAM比如 MR25H40 是 4Mbit同系列还有 16Mbit 的型号引脚兼容驱动只需改地址长度。这种可扩展性也是当初选 MRAM 的原因之一。6.5 最后分享一个小技巧MRAM 的写入寿命几乎无限但读操作其实也有寿命限制虽然也是 10^14 次级别。不过在实际应用中读次数远小于写次数不用太担心。真正需要注意的是不要在循环里无意义地频繁读状态寄存器虽然读不损耗寿命但占用 SPI 带宽。我的做法是写操作后读一次状态确认读操作不读状态直接读数据。这样 SPI 利用率最高。另外如果你发现 SPI 通信偶尔出错但逻辑分析仪抓不到问题可以试试在 SCK 线上串一个 22 欧姆电阻有时候能抑制反射。这个技巧在长走线时特别有用我试过好几次效果立竿见影。
返回列表