☰
工业数据记录新方案:MRAM与PIC18单片机SPI驱动实战
2026/10/5 10:05:51 网站建设 项目流程

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 脚是片选 CS,2 脚是串行输出 SO,3 脚是写保护 WP,4 脚是地,5 脚是串行输入 SI,6 脚是时钟 SCK,7 脚是写保护保持 HOLD,8 脚是电源。PIC18LF46K22 这边我用 MSSP1 模块,RC3 做 SCK,RC4 做 SDI,RC5 做 SDO,片选单独用 RA5 控制,这样软件片选比硬件片选灵活,后面想挂多个 SPI 从设备也方便。

这里有几个引脚处理上的细节值得说一下。WP 引脚我直接拉到 VDD,因为我不打算用硬件写保护,所有保护逻辑都在软件层做,这样调试的时候不用改硬件。HOLD 引脚也拉到 VDD,因为我的通信是单主单从,不存在多主竞争,不需要用 HOLD 来暂停传输。如果你要把 WP 和 HOLD 接到单片机的 IO 上做动态控制,那初始化的时候一定要先把这两个脚置高再操作 MRAM,否则会出现写不进去或者读出来全是 0xFF 的情况,这个坑我踩过。

电源部分,MR25H40CDF 的工作电压是 2.7V 到 3.6V,PIC18LF46K22 我跑在 3.3V,所以两者可以直接对接,不需要电平转换。去耦电容我在 MRAM 的 VDD 和 GND 之间放了 0.1uF 和 1uF 各一颗,位置尽量靠近芯片引脚,SPI 时钟线走线尽量短且远离模拟信号,这些是常规操作但真的不能省。

1.3 SPI 模式选择与时钟极性相位确认

MR25H40CDF 支持 SPI 模式 0(CPOL=0,CPHA=0)和模式 3(CPOL=1,CPHA=1)。我选的是模式 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 的指令集不复杂,常用的就六条:WREN(0x06)写使能、WRDI(0x04)写禁止、RDSR(0x05)读状态寄存器、WRSR(0x01)写状态寄存器、READ(0x03)读数据、WRITE(0x02)写数据。每条指令的第一个字节是操作码,后面跟地址和数据。

地址是 24 位的,因为 512KB 容量需要 19 位地址线,但芯片要求传 3 个字节的地址,高 5 位是无关位,我一般填 0。读操作是发完操作码和地址后,连续输出时钟,数据就从 SO 脚一位一位出来,地址会自动递增,所以可以一次读一整页。写操作类似,发完操作码和地址后,把数据从 SI 脚送进去,也是自动递增地址。

状态寄存器的 bit0 是 WEL 位,写使能锁存,每次写操作之前必须先发 WREN 把这一位置 1,写操作完成后芯片自动清零。bit1 是 BP0,bit2 是 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 0x03

6.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 在片选保持低电平的时候,内部状态机不会复位,连续操作会出问题。这个坑不大,但很容易忽略。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询