☰
MRAM与PIC18F4585的SPI存储方案:工业数据高速读写与高可靠设计
2026/10/5 10:03:50 网站建设 项目流程

1. 项目缘起与整体设计思路

工业现场的数据存储有个很尴尬的现状:用 EEPROM 吧,写入速度慢得让人抓狂,擦写次数也就百万次级别,高频采集场景下没几个月就写废了;用 SRAM 加后备电池吧,电池没电数据全丢,维护成本高;用 Flash 吧,写之前得擦除整个扇区,掉电保护逻辑复杂,而且擦写寿命同样有限。我前几年做一个工业数据采集终端的项目,就卡在这个存储介质的选择上,后来接触到 MRAM 才算是找到了一个比较舒服的解法。

这次要聊的这套方案,核心就是MR25H40CDF 这颗 4Mbit 的 MRAM 存储芯片搭配PIC18F4585 这颗 8 位单片机,通过 SPI 总线完成数据的存储和读取。MR25H40CDF 是 Everspin 家的磁阻随机存取存储器,容量 512KB(4Mbit),SPI 接口,最关键的特性是:写入不需要擦除、写入速度接近 SRAM、擦写寿命几乎无限(10^14 次以上)、掉电数据不丢失。PIC18F4585 则是 Microchip 经典的 8 位 MCU,自带 MSSP 模块可以硬件实现 SPI,主频最高 40MHz,工业温度范围,抗干扰能力不错,两者搭配在工业嵌入式场景里算是很务实的一个组合。

这套方案能做什么?简单说就是:在工业现场、车载、电力监控、医疗设备这类对数据可靠性和写入频率都有要求的场景下,实现高速、高可靠、长寿命的数据存储与读取。适合谁来参考?有一定单片机基础、懂 SPI 时序、正在为存储介质选型发愁的嵌入式工程师,尤其是做工业类产品的朋友。哪怕你之前只用过 24C02 这种 I2C EEPROM,看完这篇也能把 MRAM 用起来,因为 SPI 的操作逻辑比 I2C 还要直白一些。

我先把整体思路捋一下。为什么选 MRAM 而不是 FRAM 或者 NVSRAM?FRAM 写入次数也很高,但容量普遍偏小,而且价格不便宜;NVSRAM 本质还是 SRAM 加电池,体积和维护是硬伤。MRAM 在容量、速度、寿命、非易失性这几个维度上比较均衡,4Mbit 的容量对于存储采集数据、配置参数、日志记录来说够用了。为什么选 PIC18F4585 而不是 STM32?这个项目本身对算力要求不高,PIC18 系列在工业领域用了很多年,供货稳定,抗干扰好,开发工具成熟,而且 4585 自带硬件 SPI,驱动 MRAM 绰绰有余。如果你的项目用的是 STM32 或者其他带 SPI 的 MCU,逻辑完全一样,把寄存器操作换成 HAL 库调用就行。

整个系统的数据流是这样的:传感器或者上位机通过 UART/CAN 把数据给到 PIC18F4585,MCU 把数据整理成固定格式的帧,通过 SPI 写入 MR25H40CDF 的指定地址;需要读取时,MCU 发出读命令,把数据从 MRAM 读回,再通过通信接口发出去。MRAM 在这里扮演的是一个"高速、永不丢失的仓库"角色,MCU 是"仓库管理员",SPI 就是两者之间的"传送带"。

提示:MRAM 虽然写入寿命极长,但并不意味着可以无脑乱写。合理的地址管理和磨损均衡思路(哪怕 MRAM 不太需要)仍然值得考虑,因为这关系到数据结构的可维护性。

2. 核心器件解析与硬件设计要点

2.1 MR25H40CDF 的关键特性与引脚说明

MR25H40CDF 是一颗 4Mbit(512K x 8)的 SPI MRAM,工作电压 2.7V~3.6V,工业级温度范围 -40℃~+85℃,SPI 模式支持 0 和 3,最高时钟频率 40MHz。它的引脚定义很标准:VDD、VSS、SCK、SI(MOSI)、SO(MISO)、CS(片选),外加 WP 和 HOLD 两个控制脚。WP 是写保护,HOLD 用于暂停通信,实际项目里如果不需要这两个功能,WP 接 VDD、HOLD 接 VDD 即可,别悬空,悬空容易受干扰误触发。

这里有个细节值得说:MR25H40CDF 的 SPI 模式支持 Mode 0(CPOL=0, CPHA=0)和 Mode 3(CPOL=1, CPHA=1)。PIC18F4585 的 MSSP 模块配置成 SPI 主模式时,SSPSTAT 寄存器的 CKP 位和 SSPCON1 的 SSPM 位组合决定了时钟极性。我一般用 Mode 0,因为大多数 MCU 的 SPI 默认就是 Mode 0,配置起来最省事。如果你用 Mode 3,记得把 CKP 置 1,同时确保从机在时钟上升沿采样、下降沿输出,时序别搞反了。

MRAM 的读写命令集非常简洁,比 Flash 简单太多。核心命令就几个:

命令名称命令码功能说明
WREN0x06写使能,写操作前必须发
WRDI0x04写禁止
RDSR0x05读状态寄存器
WRSR0x01写状态寄存器
READ0x03读数据
WRITE0x02写数据

注意,MRAM 的写操作不需要擦除,这是它和 Flash 最大的区别。你发完 WREN,再发 WRITE 命令加地址加数据,数据就直接写进去了,整个过程几十纳秒到几微秒级别,非常干脆。状态寄存器的 bit0 是 WIP(Write In Progress),写的时候会置 1,写完自动清零,轮询这个位就能判断写完成。

2.2 PIC18F4585 的 SPI 硬件配置

PIC18F4585 的 MSSP 模块支持 SPI 和 I2C 两种模式,我们这里用 SPI 主模式。相关寄存器主要是四个:SSPCON1、SSPSTAT、SSPBUF、SSPSR。配置步骤我整理成下面这样:

  1. 设置 TRISC 寄存器,把 SCK(RC3)、SDO(RC5)配置为输出,SDI(RC4)配置为输入,CS 用普通 IO 口控制,比如 RC2。
  2. 配置 SSPSTAT:SMP 位决定采样时刻,Mode 0 下 SMP=0(在数据输出中间采样),CKE=0(时钟上升沿发送)。
  3. 配置 SSPCON1:SSPM 设为 0b0000(主模式,时钟 = Fosc/4),CKP 根据模式选择。
  4. 把 SSPEN 置 1,使能 MSSP 模块。
  5. 需要发送数据时,把数据写入 SSPBUF,然后轮询 SSPIF 标志位,等它置 1 说明发送完成,读走 SSPBUF 清除标志。

这里有个坑我踩过:PIC18F4585 的 SPI 主模式时钟是 Fosc/4,如果 MCU 跑 40MHz,SPI 时钟就是 10MHz,MRAM 完全能跟上。但如果你用 Fosc/16 或 Fosc/64,速度会降下来,对于高速采集场景可能不够用。所以 SSPM 的选择要根据你的数据吞吐量来定。我一般用 Fosc/4,10MHz 的 SPI 时钟,写 512 字节的页只需要几百微秒。

CS 片选我强烈建议用软件片选,也就是用普通 IO 口控制,而不是依赖 MSSP 模块的 SS 引脚。原因很简单:MRAM 的读写操作需要精确控制 CS 的拉低和拉高时机,软件片选灵活得多,而且不会因为 SS 引脚配置问题导致通信异常。硬件片选在单从机场景下意义不大,反而容易出问题。

2.3 硬件电路设计注意事项

PCB 布局这块,SPI 信号线尽量短,SCK 和 MOSI 走线远离模拟信号和电源开关节点。MRAM 的电源脚旁边必须放 0.1uF 的去耦电容,越近越好,我一般还会并一个 1uF 的钽电容做低频滤波。如果 SPI 走线超过 10cm,建议在 SCK 和 MOSI 上串 22Ω~33Ω 的电阻做阻抗匹配,减少过冲和振铃。

WP 和 HOLD 引脚的处理前面说了,接 VDD。但如果你需要动态写保护,可以把 WP 接到 MCU 的一个 IO 口上,需要写的时候拉高,不写的时候拉低,这样能防止误写。不过 MRAM 本身写入寿命极长,误写几次也无所谓,所以这个功能看需求。

还有一个容易被忽略的点:MRAM 是磁存储,虽然抗辐射和抗磁场干扰能力比普通存储强,但如果你板子上有大功率电感或者继电器,尽量让 MRAM 远离这些磁干扰源。我有个项目把 MRAM 放在继电器旁边,结果偶尔出现数据位翻转,后来挪了位置就好了。这不是 MRAM 的锅,是布局问题。

3. 软件实现与核心代码解析

3.1 SPI 底层驱动封装

先写最底层的 SPI 字节收发函数,这是所有操作的基础。PIC18F4585 用 C18 或者 XC8 编译器,代码大概长这样:

#include <xc.h> #define CS_LAT LATC2 #define CS_TRIS TRISC2 void SPI_Init(void) { CS_TRIS = 0; // CS 输出 CS_LAT = 1; // CS 默认拉高 TRISC3 = 0; // SCK 输出 TRISC5 = 0; // SDO 输出 TRISC4 = 1; // SDI 输入 SSPSTAT = 0x00; // Mode 0, SMP=0, CKE=0 SSPCON1 = 0x20; // SSPEN=1, SSPM=0000 (Fosc/4) } unsigned char SPI_Transfer(unsigned char data) { SSPBUF = data; while (!SSPIF); // 等待发送完成 SSPIF = 0; return SSPBUF; // 读回接收数据 }

这个SPI_Transfer函数是核心,发一个字节的同时收一个字节,全双工。MRAM 的读操作就是靠这个函数实现的:发命令和地址的时候,返回的数据忽略;发 dummy 字节的时候,返回的就是 MRAM 里的数据。

CS 的控制要单独封装:

void MRAM_CS_Low(void) { CS_LAT = 0; } void MRAM_CS_High(void) { CS_LAT = 1; }

注意:CS 拉低到第一个 SCK 上升沿之间要留至少 5ns 的建立时间,PIC18 的 IO 翻转速度足够快,一般不用额外加延时,但如果你的 MCU 主频很高,保险起见可以加一个 NOP。

3.2 MRAM 写操作完整流程

写数据到 MRAM 的流程分三步:发 WREN、发 WRITE 命令加地址加数据、等待写完成。代码如下:

void MRAM_WriteEnable(void) { MRAM_CS_Low(); SPI_Transfer(0x06); // WREN MRAM_CS_High(); } void MRAM_WriteByte(unsigned long addr, unsigned char data) { MRAM_WriteEnable(); MRAM_CS_Low(); SPI_Transfer(0x02); // WRITE 命令 SPI_Transfer((addr >> 16) & 0xFF); // 地址高字节 SPI_Transfer((addr >> 8) & 0xFF); // 地址中字节 SPI_Transfer(addr & 0xFF); // 地址低字节 SPI_Transfer(data); // 数据 MRAM_CS_High(); // 等待写完成 while (MRAM_ReadStatus() & 0x01); }

地址是 19 位的(512KB = 2^19),所以需要三个字节来传输。MR25H40CDF 的地址范围是 0x00000 到 0x7FFFF。这里有个细节:地址的高 5 位其实是无效的,但芯片要求你发满 3 个字节,所以高位补 0 就行。

写多个字节的话,MRAM 支持连续写,地址会自动递增,不需要每写一个字节就发一次命令。连续写 256 字节的代码如下:

void MRAM_WritePage(unsigned long addr, unsigned char *buf, unsigned int len) { MRAM_WriteEnable(); MRAM_CS_Low(); SPI_Transfer(0x02); SPI_Transfer((addr >> 16) & 0xFF); SPI_Transfer((addr >> 8) & 0xFF); SPI_Transfer(addr & 0xFF); for (unsigned int i = 0; i < len; i++) { SPI_Transfer(buf[i]); } MRAM_CS_High(); while (MRAM_ReadStatus() & 0x01); }

连续写的时候要注意,地址不能跨页边界。MR25H40CDF 的页大小是 256 字节,如果你从地址 0x00FF 开始写 10 个字节,写到 0x0100 的时候地址会回绕到页首,覆盖掉之前的数据。所以写之前要算好地址,确保不跨页。我一般把数据按 256 字节对齐存放,避免这个问题。

3.3 MRAM 读操作与状态寄存器读取

读操作比写操作简单,不需要 WREN,直接发 READ 命令加地址,然后发 dummy 字节读数据:

void MRAM_ReadBytes(unsigned long addr, unsigned char *buf, unsigned int len) { MRAM_CS_Low(); SPI_Transfer(0x03); // READ 命令 SPI_Transfer((addr >> 16) & 0xFF); SPI_Transfer((addr >> 8) & 0xFF); SPI_Transfer(addr & 0xFF); for (unsigned int i = 0; i < len; i++) { buf[i] = SPI_Transfer(0x00); // 发 dummy 读数据 } MRAM_CS_High(); } unsigned char MRAM_ReadStatus(void) { unsigned char status; MRAM_CS_Low(); SPI_Transfer(0x05); // RDSR status = SPI_Transfer(0x00); MRAM_CS_High(); return status; }

读操作可以连续读整个芯片,地址会自动递增,没有页边界限制。这一点比写操作灵活。读 512KB 全片数据,按 10MHz SPI 时钟算,大概需要 0.4 秒左右,速度相当可以了。

3.4 数据帧格式设计与地址规划

存储数据不能乱存,得有规划。我一般把 512KB 的空间分成几个区域:

地址范围大小用途
0x00000 - 0x00FFF4KB系统配置参数
0x01000 - 0x01FFF4KB设备信息与校准数据
0x02000 - 0x3FFFF248KB采集数据环形缓冲区
0x40000 - 0x7FFFF256KB日志与事件记录

配置参数区存的是设备 ID、通信波特率、采集周期这些不常变的数据。采集数据区用环形缓冲区的方式管理,写指针到了区尾就回到区首,覆盖最老的数据。日志区存的是异常事件、报警记录,每条日志带时间戳和事件码。

数据帧我设计成固定 16 字节,格式如下:

typedef struct { unsigned long timestamp; // 4字节时间戳 unsigned short sensor_id; // 2字节传感器编号 float value; // 4字节浮点数值 unsigned char status; // 1字节状态 unsigned char reserved[5]; // 5字节保留 } DataRecord;

16 字节对齐的好处是读写方便,地址计算简单,addr = BASE + index * 16。而且 16 是 256 的因数,不会出现跨页写的问题。

4. 常见问题排查与实操避坑指南

4.1 SPI 通信失败排查思路

SPI 调不通是最常见的问题,我总结了一个排查顺序,按这个顺序走基本能定位到问题:

第一步,查硬件连接。用万用表量 CS、SCK、MOSI、MISO 四根线是否导通,有没有虚焊。我遇到过好几次是排线接触不良,换了根线就好了。

第二步,查 CS 时序。用示波器看 CS 拉低到 SCK 第一个上升沿之间有没有足够的建立时间,CS 拉高之前 SCK 是否已经停止。如果 CS 和 SCK 同时变化,MRAM 可能识别不到命令。

第三步,查 SPI 模式。MRAM 支持 Mode 0 和 Mode 3,如果你的 MCU 配的是 Mode 1 或 Mode 2,通信肯定失败。用逻辑分析仪抓一下 SCK 和 MOSI 的波形,看看数据是在上升沿还是下降沿采样。

第四步,查读回的数据。如果读出来全是 0xFF 或者 0x00,说明 MISO 线没接好或者 MRAM 没响应。如果读出来是乱码,可能是时钟太快或者模式不对。

第五步,降速测试。把 SPI 时钟降到 1MHz 试试,如果降速能通,说明是时序余量不够,检查走线和上拉电阻。

4.2 写操作不生效的几种原因

写操作不生效,最常见的原因是忘了发 WREN。MRAM 每次写之前都必须发 WREN,写完自动清除写使能,下次写还得再发。我刚开始用的时候,以为发一次 WREN 就能一直写,结果只有第一个字节写进去了,后面的全丢。

第二个原因是WP 引脚没拉高。如果 WP 接地或者悬空,写保护生效,写操作会被忽略。用万用表量一下 WP 脚的电压,正常应该是 VDD。

第三个原因是写完成判断逻辑有问题。有些朋友发完 WRITE 命令就直接拉高 CS,然后马上发下一条命令,结果 MRAM 还在写,下一条命令被忽略了。正确的做法是轮询状态寄存器的 WIP 位,等它清零再发下一条命令。

第四个原因是地址跨页。前面说过,连续写不能跨 256 字节页边界,跨了就会回绕覆盖。写之前算好地址,或者干脆每次只写一页。

4.3 数据可靠性保障技巧

工业现场干扰大,数据可靠性得重点保障。我一般会做这几件事:

加 CRC 校验。每帧数据后面跟 2 字节 CRC16,读出来先校验,校验不过就丢弃或者重读。CRC16 用查表法算,速度快,占用资源少。

关键数据双备份。配置参数这类关键数据,存两份,地址隔开。读的时候两份都读,比对一致才采用,不一致就用默认值并报警。

写后回读验证。写完数据后立刻读回来比对,不一致就重写。MRAM 写入速度快,回读验证的开销可以接受。

掉电保护。虽然 MRAM 掉电不丢数据,但写的过程中掉电可能导致数据写了一半。我一般会在数据帧里加一个"写入完成"标志位,写完整帧后最后写这个标志,读的时候先检查标志,标志不对就认为这帧数据无效。

4.4 常见问题速查表

现象可能原因解决方法
读出来全是 0xFFMISO 未连接或 MRAM 未供电检查 MISO 走线和 VDD
读出来全是 0x00CS 未拉低或 SPI 未使能检查 CS 时序和 SSPEN 位
写不进去未发 WREN 或 WP 未拉高补发 WREN,WP 接 VDD
写一半丢数据地址跨页或未等 WIP 清零按页对齐写,轮询 WIP
数据偶尔翻转电源干扰或磁干扰加去耦电容,远离干扰源
通信时好时坏走线过长或时钟太快降速,加串阻,缩短走线

提示:调试 SPI 设备,逻辑分析仪比示波器好用得多。几十块钱的 8 通道逻辑分析仪,抓 SPI 时序一目了然,能省很多时间。

5. 性能实测与场景扩展思考

5.1 实测性能数据

我在实验室用 PIC18F4585 跑 40MHz,SPI 时钟 10MHz,对 MR25H40CDF 做了几组测试:

测试项数据量耗时等效速率
单字节写1 字节约 15us-
页写(256字节)256 字节约 280us914KB/s
连续读512KB约 0.42s1.22MB/s
写后回读验证256 字节约 560us-

单字节写耗时 15us 主要是因为每次都要发 WREN 和轮询 WIP,开销在命令交互上。页写效率高很多,因为 WREN 只发一次,数据连续传输。所以实际项目里尽量用页写,别一个字节一个字节地写。

连续读的速率受限于 SPI 时钟和 MCU 的处理速度,10MHz SPI 理论速率是 1.25MB/s,实测 1.22MB/s,基本跑满了。如果你用 20MHz SPI,速率还能翻倍,但 PIC18F4585 的 SPI 最高就是 Fosc/4,40MHz 主频下就是 10MHz,想更快就得换 MCU 了。

5.2 与 EEPROM、Flash 的对比

为了让大家更清楚 MRAM 的定位,我做了个对比表:

特性MRAM (MR25H40CDF)EEPROM (24C02)NOR Flash (W25Q64)
容量512KB2KB8MB
接口SPII2CSPI
写入速度纳秒级毫秒级毫秒级(需擦除)
擦写寿命10^14 次10^6 次10^5 次
写入前擦除不需要不需要需要
掉电保持20 年以上10 年以上10 年以上
单价较高低低

MRAM 的优势在于写入速度和寿命,劣势在于容量和价格。如果你的场景是高频写入、小数据量、高可靠性,MRAM 很合适。如果是大容量存储、写入不频繁,Flash 更划算。EEPROM 现在基本只适合存几个配置字节了。

5.3 场景扩展与优化方向

这套方案可以扩展的方向不少。比如加一个RTC 芯片,给每条数据打上精确时间戳;加一个CAN 收发器,接入工业现场总线;加一个SD 卡,定期把 MRAM 里的数据转存到大容量存储,MRAM 当缓存用。

软件层面,可以做一个简单的文件系统,把 MRAM 空间按文件方式管理,支持创建、删除、追加操作。也可以做磨损均衡,虽然 MRAM 寿命极长,但均衡一下总没坏处,而且能让数据结构更规整。

如果数据量再大,可以考虑用双 MRAM 并联,一个存数据一个存备份,或者用DMA 传输减少 MCU 开销。PIC18F4585 没有 DMA,但如果你换成 PIC32 或者 STM32,SPI 加 DMA 能把 CPU 占用降到几乎为零。

我在实际项目里还遇到过一个需求:设备需要在 -40℃ 的低温下工作。MRAM 的工业级温度范围是 -40℃~+85℃,刚好覆盖。但要注意,低温下 SPI 时序余量会变小,建议把 SPI 时钟降一档,比如从 10MHz 降到 5MHz,稳定性会好很多。这个经验是踩过坑才总结出来的,低温测试的时候通信偶尔出错,降速后就再没出现过。

最后分享一个小技巧:MRAM 的写保护功能可以灵活用起来。把 WP 接到 MCU 的 IO 口,正常运行时拉高允许写,设备进入维护模式或者检测到异常时拉低禁止写,能防止程序跑飞导致的误写。这个成本几乎为零,但多了一层保护。

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

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

立即咨询