恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
工业数据记录新方案:MRAM与PIC18单片机SPI驱动实战
首页
资讯中心
/
工业数据记录新方案:MRAM与PIC18单片机SPI驱动实战
工业数据记录新方案:MRAM与PIC18单片机SPI驱动实战
发布时间:2026/10/5 10:05:53
1. 项目缘起与方案选型1.1 为什么要在工业场景里折腾 MRAM 和 8 位单片机工业现场的数据记录有个很尴尬的处境用 EEPROM 吧写入速度慢得让人着急擦写寿命也就百万次级别遇到高频采集的场景没几个月就把某个扇区写废了用 SRAM 加后备电池吧电池在高温高湿环境下两三年就鼓包漏液维护成本高得离谱用 Flash 吧写之前得先擦一整块掉电时机不对还会丢数据。我前几年做过一个配电柜里的电能质量监测模块客户要求每 200ms 记录一次三相电压电流的有效值和谐波畸变率一天下来就是 43 万次写入用 EEPROM 方案根本扛不住。后来接触到 Everspin 的 MR25H40CDF 这颗磁阻随机存储器才算找到了一个比较舒服的解法。MRAM 的写入原理是靠磁隧道结的磁化方向翻转来存储数据不是靠电荷所以它天生具备几个特性写入不需要擦除、写入速度接近 SRAM、擦写寿命理论上无限官方标称 10^14 次以上、掉电后数据能保持二十年以上。这几个特性凑在一起正好把工业数据记录的几个痛点全给覆盖了。主控这边我选的是 PIC18LF46K22一颗 8 位单片机64KB Flash、3896 字节 SRAM、1KB EEPROM自带 MSSP 模块支持 SPI 和 I2C工作电压 1.8V 到 5.5V工业级温度范围 -40 到 85 摄氏度。有人可能会问都什么年代了还用 8 位机我的理由很实在这个项目逻辑不复杂就是定时采集、算几个有效值、往 MRAM 里写、偶尔通过串口往外吐数据8 位机完全够用而且 PIC18 系列的外设配置直观没有时钟树那些弯弯绕绕代码量小出问题好查最关键的是这颗片子供货稳定、价格便宜工业客户对 BOM 成本敏感能省一点是一点。MR25H40CDF 的容量是 4Mbit也就是 512KB按每帧 32 字节算能存 16384 条记录循环覆盖的话够现场跑好几个月。它支持 SPI 模式 0 和模式 3最高时钟 40MHz不过我用 PIC18 的 MSSP 跑在 10MHz 就足够了毕竟数据量不大没必要把时钟拉满增加信号完整性的风险。1.2 硬件连接与关键引脚处理先看接线。MR25H40CDF 是 8 引脚 SOIC 封装引脚定义很清爽1 脚是片选 CS2 脚是串行输出 SO3 脚是写保护 WP4 脚是地5 脚是串行输入 SI6 脚是时钟 SCK7 脚是写保护保持 HOLD8 脚是电源。PIC18LF46K22 这边我用 MSSP1 模块RC3 做 SCKRC4 做 SDIRC5 做 SDO片选单独用 RA5 控制这样软件片选比硬件片选灵活后面想挂多个 SPI 从设备也方便。这里有几个引脚处理上的细节值得说一下。WP 引脚我直接拉到 VDD因为我不打算用硬件写保护所有保护逻辑都在软件层做这样调试的时候不用改硬件。HOLD 引脚也拉到 VDD因为我的通信是单主单从不存在多主竞争不需要用 HOLD 来暂停传输。如果你要把 WP 和 HOLD 接到单片机的 IO 上做动态控制那初始化的时候一定要先把这两个脚置高再操作 MRAM否则会出现写不进去或者读出来全是 0xFF 的情况这个坑我踩过。电源部分MR25H40CDF 的工作电压是 2.7V 到 3.6VPIC18LF46K22 我跑在 3.3V所以两者可以直接对接不需要电平转换。去耦电容我在 MRAM 的 VDD 和 GND 之间放了 0.1uF 和 1uF 各一颗位置尽量靠近芯片引脚SPI 时钟线走线尽量短且远离模拟信号这些是常规操作但真的不能省。1.3 SPI 模式选择与时钟极性相位确认MR25H40CDF 支持 SPI 模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。我选的是模式 0原因是 PIC18 的 MSSP 在模式 0 下配置最直接SCK 空闲为低数据在上升沿采样下降沿变化跟大多数逻辑分析仪的默认解码设置也一致调试起来省事。配置 MSSP 的寄存器就几个关键位SSPCON1 的 SSPM 设为 0b0010 表示 SPI 主模式时钟由 Fosc/64 分频得到我用的 64MHz 系统时钟分频后是 1MHz后来改成 Fosc/16 得到 4MHz实测读写都稳。SSPSTAT 的 SMP 位设为 0 表示在数据输出末尾采样CKE 位设为 0 表示时钟空闲为低且数据在上升沿发送。这些位看着琐碎但配错一个就会导致读出来的数据整体移位或者全错建议第一次调试的时候用逻辑分析仪抓一下波形确认 SCK、SI、SO 的时序关系再往下写代码。2. MRAM 读写指令集与底层驱动实现2.1 指令集速查与操作码含义MR25H40CDF 的指令集不复杂常用的就六条WREN0x06写使能、WRDI0x04写禁止、RDSR0x05读状态寄存器、WRSR0x01写状态寄存器、READ0x03读数据、WRITE0x02写数据。每条指令的第一个字节是操作码后面跟地址和数据。地址是 24 位的因为 512KB 容量需要 19 位地址线但芯片要求传 3 个字节的地址高 5 位是无关位我一般填 0。读操作是发完操作码和地址后连续输出时钟数据就从 SO 脚一位一位出来地址会自动递增所以可以一次读一整页。写操作类似发完操作码和地址后把数据从 SI 脚送进去也是自动递增地址。状态寄存器的 bit0 是 WEL 位写使能锁存每次写操作之前必须先发 WREN 把这一位置 1写操作完成后芯片自动清零。bit1 是 BP0bit2 是 BP1这两个是块保护位可以保护部分地址区域不被写入。bit7 是 SRWD状态寄存器写保护位配合 WP 引脚使用。我一般上电后先读一次状态寄存器确认芯片在线然后根据需要配置块保护。2.2 底层读写函数的实现细节先看写使能函数很简单拉低片选发 0x06拉高片选void MRAM_WriteEnable(void) { CS_LOW(); SPI_WriteByte(0x06); CS_HIGH(); }读状态寄存器uint8_t MRAM_ReadStatus(void) { uint8_t status; CS_LOW(); SPI_WriteByte(0x05); status SPI_ReadByte(); CS_HIGH(); return status; }写数据函数参数是 24 位地址、数据指针、长度void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_WriteEnable(); CS_LOW(); SPI_WriteByte(0x02); SPI_WriteByte((addr 16) 0xFF); SPI_WriteByte((addr 8) 0xFF); SPI_WriteByte(addr 0xFF); for (uint16_t i 0; i len; i) { SPI_WriteByte(buf[i]); } CS_HIGH(); }读数据函数void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { CS_LOW(); SPI_WriteByte(0x03); SPI_WriteByte((addr 16) 0xFF); SPI_WriteByte((addr 8) 0xFF); SPI_WriteByte(addr 0xFF); for (uint16_t i 0; i len; i) { buf[i] SPI_ReadByte(); } CS_HIGH(); }SPI_WriteByte 和 SPI_ReadByte 是底层字节收发函数用 PIC18 的 SSPBUF 寄存器实现发一个字节等 BF 标志位置起读一个字节先写 0xFF 再读 SSPBUF。这里有个细节PIC18 的 MSSP 在接收的时候如果只读不写SSPBUF 不会产生时钟所以读操作必须写一个 dummy 字节来产生时钟这个 dummy 字节写 0xFF 还是 0x00 都行我习惯写 0xFF。2.3 页写与地址边界处理MR25H40CDF 的页大小是 256 字节写操作可以跨页芯片内部会自动处理地址递增不像 EEPROM 那样跨页会回卷。但为了代码的可预测性我还是建议按页对齐写入每次写不超过 256 字节且起始地址按 256 对齐。这样做的好处是如果将来换用其他存储器代码不用大改。地址边界处理上512KB 的地址范围是 0x00000 到 0x7FFFF超出这个范围芯片会回卷到 0。我在应用层做了一个环形缓冲区的管理写指针到了 0x7FFFF 就回到 0x00000读指针跟着写指针走保证数据是循环覆盖的。3. 数据存储结构设计与掉电保护3.1 记录帧格式与环形缓冲区管理每条记录我设计成 32 字节结构如下帧头 2 字节0xAA55、时间戳 4 字节秒计数、三相电压有效值各 2 字节共 6 字节、三相电流有效值各 2 字节共 6 字节、谐波畸变率 2 字节、温度 2 字节、校验和 2 字节、保留 6 字节。帧头用来做同步校验和用来判断数据是否完整。环形缓冲区的管理用一个写指针和一个读指针写指针存在 MRAM 的固定位置比如地址 0x00000 开始的 4 个字节读指针存在 0x00004 开始的 4 个字节。每次写入新记录前先读写指针判断是否到了缓冲区末尾到了就回卷然后写数据最后更新写指针。读指针由上位机查询时更新表示已经读走的数据位置。这里有个关键点写指针的更新必须在数据写入成功之后再做否则掉电会导致写指针指向一个还没写完的位置下次上电会读到脏数据。我的做法是先把数据写到目标地址然后读回来校验校验通过再更新写指针。校验不通过就重写一次连续三次失败就记录一个错误标志不再更新写指针。3.2 掉电检测与写入原子性保障工业现场掉电是常态所以掉电保护必须做。PIC18LF46K22 有一个 HLVD 模块可以设置高低压检测我把它配置成在电源电压低于 3.0V 时产生中断。中断里立刻停止采集把当前正在写的记录写完然后更新写指针最后进入死循环等电放完。这里的时间窗口很关键。MRAM 的写入速度很快32 字节在 4MHz SPI 下大概 64 微秒就能写完加上校验和更新写指针总共不超过 200 微秒。电源从 3.3V 掉到 3.0V 的时间取决于负载和电容我在电源输入端放了一颗 470uF 的电解电容实测能撑 5 毫秒左右足够完成收尾操作。写入原子性方面MRAM 本身没有原子写机制但因为它写入快配合掉电中断实际效果接近原子写。如果你对原子性要求极高可以用双缓冲区方案写 A 区的时候 B 区保留上一份数据写完 A 区更新一个标志位指向 A 区这样即使写 A 区过程中掉电B 区还有完整数据。3.3 数据校验与坏块管理校验和用简单的累加和把前 30 字节累加取低 16 位存在最后 2 字节。读的时候重新算一遍跟存储的校验和对比不一致就认为这条记录损坏跳过不处理。坏块管理方面MRAM 理论上没有坏块但为了保险我在初始化的时候会做一次全片扫描往每个扇区写一个测试模式再读回来确认读写正常。扫描结果存在 MRAM 的固定位置如果发现某个扇区读写异常就在坏块表里标记后续分配地址时跳过。这个扫描过程大概需要几秒钟放在上电初始化阶段做不影响正常采集。4. 实际调试中遇到的坑与排查方法4.1 SPI 通信失败的常见原因调试 SPI 最怕的就是读出来全是 0xFF 或者全是 0x00。全 0xFF 一般是 MISO 线没接好或者从设备没供电全 0x00 一般是时钟没出来或者片选没拉低。我遇到过一次读出来全是 0xFF查了半天发现是 MRAM 的 CS 引脚虚焊补焊之后就好了。还有一种情况是读出来的数据整体移位比如发 0x05 读状态寄存器读回来的是 0x0A左移了一位。这是 SPI 模式配错了CPHA 或者 CPOL 跟从设备不匹配。解决办法是用逻辑分析仪抓 SCK 和 SI 的波形对照 MRAM 数据手册的时序图确认数据在哪个边沿变化、哪个边沿采样。4.2 写入不成功的问题排查写入不成功最常见的原因是忘了发 WREN。MRAM 每次写操作之前都必须发 WREN写完之后 WEL 位自动清零下次写还得再发。我刚开始写驱动的时候写函数里忘了加 WREN结果数据死活写不进去读出来还是旧数据查了两个小时才反应过来。另一个原因是 WP 引脚被拉低了。WP 引脚低电平有效如果硬件上不小心把它接地了所有写操作都会被禁止。我一般建议在 PCB 上把 WP 通过 10K 电阻上拉到 VDD需要写保护的时候再用 IO 拉低。4.3 数据错乱的定位思路数据错乱一般是地址算错了或者缓冲区越界。我的排查方法是先读固定地址的已知数据比如在初始化的时候往地址 0x00000 写一个固定的模式然后读回来对比确认基本读写没问题。然后再逐步测试跨页写、跨扇区写、环形回卷写每步都加校验定位到具体是哪一步出错。还有一种数据错乱是电源不稳导致的。MRAM 虽然写入快但如果电源纹波太大写入过程中电压跌落超过阈值数据可能写一半就停了。这种情况用示波器看电源纹波就能发现解决办法是加去耦电容或者换低 ESR 的电容。4.4 常见问题速查表现象可能原因排查方法解决措施读出全 0xFFMISO 未接、从设备未供电万用表测 MISO 对地电压检查接线和供电读出全 0x00SCK 无输出、CS 未拉低逻辑分析仪抓 SCK 和 CS检查 MSSP 配置和片选代码数据整体移位SPI 模式不匹配对照时序图检查 CPOL/CPHA修改 SSPSTAT 寄存器配置写入无效未发 WREN、WP 被拉低读状态寄存器看 WEL 位写前加 WREN检查 WP 引脚数据错乱地址算错、缓冲区越界固定地址读写测试检查地址计算和缓冲区边界掉电丢数据掉电中断未配置、电容不够示波器看掉电波形配置 HLVD加大电容5. 性能实测与优化建议5.1 读写速度实测数据我用逻辑分析仪和示波器实测了一组数据。SPI 时钟 4MHz 下写 32 字节记录耗时约 72 微秒读 32 字节耗时约 68 微秒。SPI 时钟提到 10MHz 后写耗时降到约 30 微秒读耗时约 28 微秒。再往上提到 20MHz耗时降到约 16 微秒但信号完整性开始变差SCK 上升沿有过冲所以最终定在 10MHz。连续写 1000 条记录的测试中平均每条写入耗时 31 微秒加上校验和更新写指针的开销总共约 45 微秒。按 200ms 采集周期算写入占用的时间不到 0.03%对主循环几乎没有影响。5.2 功耗表现与低功耗优化MR25H40CDF 的读写电流典型值 5mA待机电流 100 微安左右。PIC18LF46K22 在 64MHz 下运行电流约 15mA休眠电流可以降到 1 微安以下。如果项目对功耗有要求可以在采集间隙让单片机进入休眠用定时器唤醒MRAM 在片选拉高后自动进入待机整体平均功耗可以做到 1mA 以下。低功耗优化上我一般把 SPI 时钟降到 1MHz因为低速下功耗更低而且数据量不大1MHz 也够用。另外把不用的外设全部关掉比如第二路 MSSP、ADC、比较器这些外设即使不工作也会漏电。5.3 长期运行稳定性观察我有一台设备在现场连续跑了六个月每 200ms 写一条记录总共写了约 780 万条。期间经历过几次掉电最长一次停电 4 小时来电后数据完整没有丢失。MRAM 的写入次数远未达到理论寿命读出来的数据校验全部通过。长期运行中唯一遇到的问题是温度。夏天柜内温度到了 70 摄氏度MRAM 的数据保持能力在高温下会下降官方标称 85 摄氏度下数据保持 10 年70 摄氏度下更久。我实测在 70 摄氏度下断电放置 48 小时数据没有丢失。如果应用环境温度更高建议选工业级或者车规级的型号。6. 代码组织与工程化建议6.1 驱动层与应用层分离我的代码分三层底层是 SPI 字节收发中间层是 MRAM 读写指令封装上层是数据记录管理。底层只负责跟 MSSP 寄存器打交道中间层提供 MRAM_Read、MRAM_Write、MRAM_WriteEnable 这些函数上层负责记录帧的组装、校验、环形缓冲区管理。这样分层的好处是换存储器的时候只需要改中间层上层逻辑不动。中间层的头文件里我把地址范围、页大小、指令码都定义成宏方便移植和修改。比如#define MRAM_PAGE_SIZE 256 #define MRAM_TOTAL_SIZE 0x80000 #define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRITE 0x02 #define MRAM_CMD_READ 0x036.2 调试接口与日志输出调试阶段我留了一个串口输出接口可以把 MRAM 的读写操作打印出来包括地址、长度、操作类型、耗时。正式发布的时候用宏开关把日志关掉不影响性能。串口波特率用 115200够用且稳定。另外我留了一个命令解析接口通过串口可以手动读写 MRAM 任意地址方便现场排查问题。命令格式很简单比如“R 000000 32”表示从地址 0 读 32 字节“W 000000 01020304”表示从地址 0 写 4 字节。这个接口在调试的时候帮了大忙不用重新烧程序就能验证硬件。6.3 版本兼容与升级考虑MR25H40CDF 是 4Mbit 的如果将来数据量增大可以换成 16Mbit 的 MR25H256指令集和引脚完全兼容只需要改一下地址范围宏定义。PIC18LF46K22 的 Flash 是 64KB我的代码编译出来大概 12KB还有很大余量后续加功能不用换片子。升级方面我预留了 Bootloader 的接口通过串口可以更新固件。Bootloader 放在 Flash 的前 4KB应用程序从 0x1000 开始。升级的时候先把新固件写到 MRAM 的保留区域校验通过后再搬到 Flash这样即使升级过程中掉电旧固件还在不会变砖。7. 一些个人体会这个项目做下来我最大的感受是选对存储器比选对单片机更重要。MRAM 的价格比 EEPROM 贵不少但省去了电池、省去了擦除等待、省去了寿命计算综合成本反而更低。PIC18 虽然老但胜在稳定和熟悉工业项目里稳定比先进重要。调试 SPI 的时候逻辑分析仪是必备工具没有它排查问题全靠猜。我用的是一款入门级的 8 通道逻辑分析仪采样率 24MHz抓 SPI 波形足够了。另外建议在 PCB 上把 SPI 的测试点留出来方便夹探头。最后说一个细节MRAM 的片选信号在两次操作之间一定要拉高哪怕时间很短。我有一次为了省事连续读两个地址的时候没有拉高片选结果第二次读出来的数据是错的。后来看手册才知道MRAM 在片选保持低电平的时候内部状态机不会复位连续操作会出问题。这个坑不大但很容易忽略。