☰
MRAM与PIC18F86J10工业数据记录方案:SPI驱动与掉电保护实战
2026/10/5 1:17:22 网站建设 项目流程

1. 项目缘起与方案选型:为什么是 MRAM 加 PIC18F86J10

工业现场的数据记录有个很尴尬的处境:用 EEPROM 吧,写入速度慢、擦写寿命也就百万次级别,高频采集场景下没几个月就写废了;用 SRAM 加后备电池吧,电池在高温高湿环境下两三年就鼓包漏液,维护成本高得离谱;用 Flash 吧,写之前得擦整个扇区,掉电瞬间正好在擦写就会丢数据,而且擦写寿命同样有限。我在一个电力监测项目上就吃过这个亏,现场设备每天记录上千条事件日志,用了两年多陆续有板子回来返修,拆开一看全是存储芯片写坏的。

后来接触到 Everspin 的 MR25H40CDF 这颗磁阻随机存储器,才算找到比较理想的解法。MRAM 的存储原理跟传统电荷存储完全不是一回事,它靠磁性隧道结的磁化方向来记录 0 和 1,所以天生具备几个特性:写入不需要擦除、没有擦写寿命上限(官方标称 10^14 次以上)、掉电数据不丢、写入速度和读取速度基本对称。MR25H40CDF 是 4Mbit 容量,也就是 512KB,SPI 接口,工业级温度范围,供电 2.7V 到 3.6V,封装是 8 脚 SOIC,跟常见的 SPI Flash 引脚兼容度很高。

主控这边选 PIC18F86J10,是 Microchip 的 8 位单片机,64 脚 TQFP 封装,自带硬件 MSSP 模块可以跑 SPI,主频最高 40MHz,程序存储 64KB,RAM 接近 4KB。选它不是因为性能多强,而是它在工业控制领域用量大、供货稳、开发工具链成熟,而且 3.3V 单电源供电跟 MR25H40CDF 天然匹配,不需要额外做电平转换。很多新手一上来就想用 STM32 或者 ESP32,性能确实强,但如果你做的是低功耗、长期在线、环境恶劣的工业记录仪,PIC18 这种皮实耐用的老将反而更合适。

这个组合解决的核心问题就一个:在掉电不可预测、写入频繁、维护困难的工业现场,把关键数据可靠地存下来并且随时能读出来。适合谁看?做工业数据采集、电力监控、医疗设备日志、车载黑匣子这类产品的嵌入式工程师,尤其是被 EEPROM 寿命和 Flash 掉电问题折磨过的朋友。下面我按实际项目落地的顺序,把硬件连接、SPI 时序、驱动代码、数据组织、异常处理这些环节全部拆开讲一遍。

2. 硬件层设计:SPI 连接、片选与电源处理

2.1 引脚连接与 SPI 模式确认

MR25H40CDF 是标准 SPI 从设备,8 个引脚分别是 CS、SO、SI、SCK、VDD、VSS、WP、HOLD。注意它跟普通 SPI Flash 有个区别:WP 和 HOLD 这两个引脚在 MRAM 上功能是保留的,实际使用中直接拉高就行,不需要像 Flash 那样做写保护控制。我见过有人照着 W25Q64 的电路去接 MRAM,把 WP 接到 GPIO 上做写保护,结果发现根本没作用,白白浪费一个 IO。

跟 PIC18F86J10 的连接方式,我用的是 MSSP 模块的硬件 SPI,映射到 RC3(SCK)、RC4(SDI)、RC5(SDO),片选用 RA5 普通 GPIO 控制。这里有个细节:PIC18F86J10 的 MSSP 在 SPI 主模式下,SCK 空闲电平和采样边沿由 SSPCON1 寄存器的 CKP 和 CKE 位决定。MR25H40CDF 支持 SPI 模式 0(CPOL=0,CPHA=0)和模式 3(CPOL=1,CPHA=1),我选的是模式 0,因为大部分调试工具默认就是模式 0,抓波形的时候不容易搞混。

信号PIC18F86J10 引脚MR25H40CDF 引脚说明
SCKRC3SCK时钟,模式 0
SDIRC4SO主入从出
SDORC5SI主出从入
CSRA5CS低有效,软件控制
WP不接WP直接上拉到 VDD
HOLD不接HOLD直接上拉到 VDD

2.2 电源与去耦的实操经验

MRAM 的写入电流比读取电流大不少,峰值能到十几毫安,所以电源去耦不能省。我在 VDD 和 VSS 之间放了两个电容:一个 0.1uF 的陶瓷电容紧贴芯片引脚,负责高频瞬态;一个 10uF 的钽电容放在稍远的位置,负责低频储能。这两个电容的位置很关键,0.1uF 那个如果离芯片超过 5mm,效果就打对折了。

还有一点容易被忽略:MR25H40CDF 的 VDD 上升时间如果太慢,上电过程中可能出现误写。官方数据手册建议电源上升时间控制在 1ms 以内,如果用的是大电容滤波的电源方案,最好加一个电源监控芯片,在电压稳定之前把 CS 拉高。我在一个项目上就遇到过上电后第一条记录莫名其妙变成乱码的情况,查了半天发现是电源上升太慢,MRAM 在电压爬升过程中被误触发了写操作。

提示:MRAM 虽然掉电不丢数据,但上电瞬间如果 CS 处于低电平且 SCK 有毛刺,芯片可能把毛刺当成时钟信号执行误操作。建议 CS 引脚加一个 10K 上拉电阻,确保上电默认高电平。

2.3 硬件片选与软件片选的取舍

热搜词里有人问 SPI 硬件片选和软件片选的区别,这里正好说一下。PIC18F86J10 的 MSSP 模块本身不提供自动片选功能,所以只能用软件片选,也就是用普通 GPIO 手动拉低拉高。硬件片选一般出现在带专用 SPI 控制器的芯片上,比如某些 ARM 处理器,片选由硬件在传输开始时自动拉低、结束时自动拉高,省 CPU 干预。

软件片选的好处是灵活,你可以在一条 CS 有效期内连续发多个命令,也可以在不同从设备之间随意切换。坏处是如果代码写得不严谨,比如中断打断了 SPI 传输导致 CS 提前拉高,数据就废了。我的做法是在 SPI 读写函数里加临界区保护,传输期间关全局中断,传完再开。对于 512KB 的 MRAM 来说,单次页写入最多 256 字节,关中断的时间也就几十微秒,对系统实时性影响可以接受。

3. SPI 驱动层实现:从寄存器配置到读写函数

3.1 MSSP 初始化与时钟计算

PIC18F86J10 的 SPI 时钟由 SSPADD 寄存器决定,公式是 Fosc / (4 * (SSPADD + 1))。假设系统晶振用 8MHz,PLL 倍频到 40MHz,那么 Fosc 就是 40MHz。MR25H40CDF 最高支持 40MHz 的 SPI 时钟,但实际布线如果比较长或者有干扰,建议降到 10MHz 到 20MHz 更稳。我取 SSPADD = 0,得到 40MHz / 4 = 10MHz 的 SPI 时钟,这个速度下读写 512KB 全片大概需要 0.5 秒左右,对于日志记录场景完全够用。

初始化代码大概长这样:

void SPI_Init(void) { TRISC3 = 0; // SCK 输出 TRISC5 = 0; // SDO 输出 TRISC4 = 1; // SDI 输入 TRISA5 = 0; // CS 输出 CS = 1; // 默认拉高 SSPCON1 = 0x20; // SSPEN=1, CKP=0, SPI Master, Fosc/4 SSPSTAT = 0x40; // CKE=0, SMP=0, 模式0 SSPADD = 0; // 10MHz @ 40MHz Fosc }

这里 SSPSTAT 的 SMP 位控制采样时刻,模式 0 下 SMP 要设成 0,表示在数据输出中间采样。CKE 设成 0 表示 SCK 从空闲低电平开始,第一个边沿是上升沿。这两个位如果设错,读回来的数据会整体移位或者全是 0xFF,是新手最容易踩的坑。

3.2 单字节收发与页读写函数

SPI 的本质就是交换,发一个字节的同时收一个字节。PIC18F86J10 的 SSPBUF 寄存器写入后,硬件自动开始移位,等 BF 标志位置 1 表示收发完成。基础函数如下:

uint8_t SPI_Transfer(uint8_t data) { SSPBUF = data; while(!SSPSTATbits.BF); return SSPBUF; }

MR25H40CDF 的命令集跟普通 SPI Flash 类似但更简洁。写使能是 0x06,读状态是 0x05,读数据是 0x03,写数据是 0x02,读 ID 是 0x9F。注意 MRAM 没有擦除命令,也不需要擦除,这是它跟 Flash 最大的区别。写之前只需要发一次写使能,然后直接发写命令加地址加数据就行。

读函数实现:

void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { CS = 0; SPI_Transfer(0x03); SPI_Transfer((addr >> 16) & 0xFF); SPI_Transfer((addr >> 8) & 0xFF); SPI_Transfer(addr & 0xFF); for(uint16_t i = 0; i < len; i++) buf[i] = SPI_Transfer(0x00); CS = 1; }

写函数实现:

void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { CS = 0; SPI_Transfer(0x06); // 写使能 CS = 1; CS = 0; SPI_Transfer(0x02); SPI_Transfer((addr >> 16) & 0xFF); SPI_Transfer((addr >> 8) & 0xFF); SPI_Transfer(addr & 0xFF); for(uint16_t i = 0; i < len; i++) SPI_Transfer(buf[i]); CS = 1; }

3.3 写保护与状态轮询的取舍

普通 Flash 写完一个页之后需要轮询状态寄存器等内部擦写完成,MRAM 不需要,因为它的写入是即时的,没有内部擦除周期。但 MRAM 有一个状态寄存器可以读,主要看 WEL 位(写使能锁存)和 BP 位(块保护)。我一般写完不轮询,直接进行下一次操作,实测下来连续写入没有任何问题。

不过有一个场景需要注意:如果你在写使能和实际写数据之间插入太长的延时,WEL 位可能会自动清零。官方手册没有明确说超时时间,但我实测超过 100ms 左右就有概率失效。所以写使能和写数据之间不要做耗时操作,最好紧挨着执行。

注意:MRAM 的写使能是每次写操作前都要发一次,不是发一次就能一直写。这一点跟 Flash 一样,但比 Flash 好的地方是不需要擦除,所以写一个字节和写一页的流程是一样的,只是数据长度不同。

4. 数据组织与掉电保护策略

4.1 环形日志结构设计

512KB 的 MRAM 如果用来存日志,怎么组织很关键。我采用的是环形缓冲区加索引表的结构。把整个空间分成 512 个 1KB 的块,每块头部 16 字节是块头,包含块序号、写入时间戳、有效数据长度和 CRC 校验,后面 1008 字节是实际数据。另外在 MRAM 最开头留 256 字节做全局索引,记录当前写指针位置和总记录数。

这种结构的好处是写入时只需要顺序往后写,写满一圈自动回到开头覆盖最旧的数据,不需要做垃圾回收。MRAM 没有擦写寿命限制,所以覆盖写没有任何心理负担,这是它相比 Flash 最大的优势。如果用 Flash 做环形缓冲,每次覆盖前都要擦除整个扇区,不仅慢,而且擦除次数有限,用不了多久就坏了。

块头的 CRC 我用的是 CRC-16/CCITT 算法,查表法实现,算 1008 字节大概几十微秒。CRC 的作用是掉电时如果正好写到一半,块头或者数据的校验会失败,读取时就能识别出这个块是无效的,跳过它去读上一个有效块。

4.2 掉电检测与写入原子性

工业现场掉电是随机的,所以掉电检测电路必不可少。我在电源输入端加了一个分压电阻接到 PIC18F86J10 的 AN0 引脚,用 ADC 监测电压。当电压低于 3.0V 时触发中断,在中断里把当前正在写的块头标记为无效,然后停止写入。从检测到掉电到电源完全跌落到 MRAM 最低工作电压 2.7V,中间大概有几百微秒的窗口,足够完成一次块头写入。

这里有个细节:块头的写入必须是原子的。我的做法是先写数据区,再写块头。因为块头里有 CRC 和有效标志,只有块头写完了这个块才算有效。如果掉电发生在写数据区的时候,块头还是旧的无效状态,读取时会跳过这个块。如果掉电发生在写块头的时候,CRC 会校验失败,同样跳过。这样就保证了不会读到半截数据。

void Log_Write(uint8_t *data, uint16_t len) { uint32_t addr = current_ptr; BlockHeader_t hdr; hdr.seq = block_seq++; hdr.timestamp = Get_Timestamp(); hdr.length = len; hdr.valid = 0; // 先标记无效 hdr.crc = CRC16(data, len); MRAM_Write(addr, (uint8_t*)&hdr, sizeof(hdr)); MRAM_Write(addr + sizeof(hdr), data, len); hdr.valid = 0xA5; // 数据写完了再标记有效 MRAM_Write(addr, (uint8_t*)&hdr, sizeof(hdr)); current_ptr += BLOCK_SIZE; if(current_ptr >= MRAM_SIZE) current_ptr = INDEX_SIZE; }

4.3 读取时的容错处理

读取的时候不能假设所有块都是好的,必须逐个校验。我的读取流程是:从索引表拿到最新的写指针,往前回溯,逐个读块头,检查 valid 标志和 CRC。如果 valid 不对或者 CRC 校验失败,就跳过这个块继续往前找。找到有效块之后,再校验数据区的 CRC,双重保险。

实测下来,正常掉电情况下最多丢失最后一个正在写的块,之前的数据全部完好。这个表现比 EEPROM 和 Flash 都好,EEPROM 掉电可能整个芯片锁死,Flash 掉电可能整个扇区数据全丢。

存储介质掉电丢失范围擦写寿命写入速度是否需要擦除
EEPROM单字节可能丢失100万次慢否
NOR Flash整个扇区10万次中等是
MRAM最多一个块10^14次快否

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

5.1 读回数据全 0xFF 或全 0x00

这是最常见的现象,八成是 SPI 模式设错了。先确认 CPOL 和 CPHA 是否跟 MRAM 匹配,模式 0 和模式 3 都支持,但代码里设的模式必须跟实际波形一致。用逻辑分析仪抓一下 SCK 和 SI 的波形,看数据是在上升沿还是下降沿变化。如果 SCK 空闲是高电平,那就是模式 3,SSPSTAT 的 CKE 要设成 1。

另一个可能是 CS 时序问题。MRAM 要求 CS 拉低之后至少等 5ns 才能发第一个时钟,CS 拉高之前最后一个时钟要完全结束。如果 CS 拉高太早,最后一个字节可能没写进去。我在代码里 CS 操作前后各加了一个 NOP 延时,虽然理论上不需要,但实测能提高稳定性。

5.2 写入后读出来数据错位

数据错位通常是地址字节发错了。MR25H40CDF 是 4Mbit 容量,需要 19 位地址,也就是 3 个字节。注意高字节的前 5 位是无关位,但最好发 0。我见过有人把 24 位地址直接拆成 3 个字节发,结果高字节的无关位带了随机值,导致地址偏移。正确的做法是地址与 0x07FFFF 做与运算,确保高 5 位是 0。

还有一种错位是 SPI 传输函数里 SSPBUF 读写顺序搞反了。PIC18F86J10 的 SSPBUF 是双缓冲的,写进去之后要等 BF 标志置位才能读。如果先读后写,读到的就是上一次的残留数据。这个坑我在早期项目里踩过,调了一整天才发现是函数写反了。

5.3 长时间运行后数据异常

如果设备跑几天之后数据开始出错,优先检查电源。MRAM 的写入电流峰值比较大,如果电源纹波太大,写入过程中电压跌落可能导致写入失败。用示波器看 VDD 引脚上的纹波,正常应该在 50mV 以内。如果超过 100mV,就要加电容或者换 LDO。

另一个可能是 SPI 时钟太快导致误码。10MHz 在短距离板内通信没问题,但如果 MRAM 离主控比较远,比如超过 10cm,建议降到 5MHz 甚至 2MHz。SPI 不是差分信号,长距离传输抗干扰能力差,降速是最简单的解决办法。

5.4 常见问题速查表

现象可能原因排查方法解决措施
读回全 0xFFSPI 模式错误逻辑分析仪抓波形确认 CPOL/CPHA
读回全 0x00CS 未拉低或 MISO 未接万用表测通断检查硬件连接
数据错位地址字节错误打印发送的地址地址与 0x07FFFF
写入无效写使能超时示波器看 CS 和 SCK写使能后立即写数据
偶发数据丢失电源纹波大示波器看 VDD加去耦电容或降速
上电数据乱电源上升慢示波器看上电波形加电源监控芯片

实操心得:调试 SPI 设备,逻辑分析仪比示波器好用得多。几十块钱的 8 通道逻辑分析仪就能把 CS、SCK、MOSI、MISO 四根线全抓下来,还能直接解码 SPI 协议,省去手动数时钟的麻烦。我现在的习惯是焊好板子第一件事就是抓 SPI 波形,确认时序没问题再写上层代码。

6. 性能实测与场景扩展

6.1 实测数据与对比

我在实验室用 10MHz SPI 时钟做了几组测试。单字节写入耗时约 2.5 微秒,256 字节页写入约 220 微秒,512KB 全片写入约 0.55 秒。读取速度跟写入差不多,全片读取约 0.5 秒。对比同容量的 EEPROM,页写入要 5 毫秒左右,慢了 20 多倍。对比 NOR Flash,虽然写入速度接近,但 Flash 每次写之前要擦除 4KB 扇区,实际有效写入速度只有 MRAM 的三分之一左右。

功耗方面,MRAM 读取电流约 5mA,写入电流约 12mA,待机电流只有几十微安。对于电池供电的设备,大部分时间处于待机状态,平均功耗可以做到很低。我在一个用锂亚电池供电的远程监测项目上,每秒记录一次数据,电池寿命估算超过 5 年。

6.2 这个方案还能怎么扩展

MR25H40CDF 是 4Mbit 容量,如果数据量更大,可以换 MR25H256 或者 MR25H10,容量分别是 256Kbit 和 1Mbit,接口和命令集完全兼容,代码几乎不用改。如果要多片级联,每片用一个独立 CS 就行,SPI 总线可以并联。

软件层面,可以在环形日志的基础上加一层文件系统,把 MRAM 模拟成一个小型磁盘,支持按文件名读写。不过对于大多数工业记录场景,环形日志已经够用了,加文件系统反而增加复杂度和出错概率。我的原则是能用简单方案解决的就不要上复杂方案,工业产品稳定第一。

另外 MRAM 的快速写入特性很适合做实时数据缓存。比如系统正常运行时数据写到 MRAM,等网络恢复后再批量上传到服务器。这样即使网络中断,数据也不会丢,恢复后自动补传。这个模式我在远程抄表项目里用过,效果很好,现场设备从来不需要人工去拷数据。

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

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

立即咨询