1. 项目缘起与方案选型:为什么是 MRAM 加 PIC18
做嵌入式这行十几年,最头疼的往往不是算法多复杂,而是数据存不住。尤其是工业现场那些设备——PLC 扩展模块、智能仪表、电机驱动器——经常要在断电瞬间把关键参数、故障记录、累计运行时间保存下来。传统方案无非是 EEPROM 或者带备用电池的 SRAM,再或者 SPI Flash。EEPROM 写入慢、擦写寿命有限,频繁写一个字节要等好几毫秒;SRAM 加电池,电池迟早要换,工业环境里高温高湿,电池漏液是迟早的事;SPI Flash 虽然容量大,但写入前要擦除整个扇区,而且擦写次数也就十万次量级,对于每秒都在记录数据的场景根本扛不住。
所以当我第一次接触到MR25H40CDF这颗 MRAM(磁性随机存储器)的时候,眼前确实亮了一下。它的核心优势很直接:写入不需要擦除、纳秒级写入速度、近乎无限的擦写寿命、断电立即保存。这几点几乎把工业数据存储的痛点全踩中了。而PIC18F47Q10是 Microchip 新一代带 Q10 后缀的 8 位单片机,集成了 MSSP 模块支持 SPI 主机模式,主频 64MHz,外设丰富,价格也亲民,拿它来驱动 MR25H40CDF 做数据存储和读取,是一个非常典型的工业嵌入式组合。
这篇文章我就把整个项目的设计思路、硬件连接、SPI 驱动编写、读写时序细节、常见坑和排查方法完整梳理一遍。不管你是刚入行的嵌入式软件工程师,还是正在选型存储方案的硬件老手,这篇内容都能直接拿去参考复现。我尽量说人话,把每个参数为什么这么设、每个步骤为什么这么做都讲清楚。
1.1 MR25H40CDF 到底强在哪
先把这个器件的基本盘交代清楚。MR25H40CDF 是 Everspin 公司出的 4Mbit(512K x 8)并行接口 MRAM,等等,这里要纠正一个常见误解——MR25H40CDF 其实是SPI 接口的 MRAM,不是并行接口。它的容量是 4Mbit,也就是 512KB,组织为 512K 字节。工作电压 2.7V 到 3.6V,标准 SPI 模式 0 和模式 3 都支持,最高 SPI 时钟可以跑到 40MHz。这意味着什么?意味着你用 PIC18F47Q10 的 SPI 主机,在 64MHz 系统时钟下分频到 16MHz 或 20MHz 的 SPI 时钟,读写速度已经远超传统 EEPROM 和大部分 SPI Flash。
更关键的是它的写入机制。MRAM 的存储单元是磁性隧道结(MTJ),通过改变自由层的磁化方向来存储 0 和 1。这个过程是物理翻转,不需要像 Flash 那样先充电再放电擦除,所以单字节写入就是单字节写入,没有扇区擦除的概念。写入一个字节的时间和读取差不多,都是几十纳秒级别。擦写寿命官方标称10^14 次以上,实际上你就算每秒写一万次,也要写几百年。这个特性对于工业设备里那种“每次循环都要记录状态”的场景,简直是量身定做。
还有一个容易被忽略的点:MR25H40CDF 在断电后数据能保持20 年以上,而且不需要电池。工业现场温度范围宽,-40°C 到 +85°C 都能正常工作,数据保持能力也不受磁场影响(这点比某些铁电存储器强)。所以如果你在做工业数据记录仪、智能电表、电梯控制器、医疗设备参数存储,这颗芯片值得认真考虑。
1.2 PIC18F47Q10 的 SPI 外设能力
PIC18F47Q10 属于 Microchip 的 PIC18-Q10 系列,40 引脚 PDIP 封装,64MHz 内部振荡器,128KB Flash,3.5KB RAM,1KB EEPROM。它有两个 MSSP 模块(MSSP1 和 MSSP2),都可以配置成 SPI 主机模式。SPI 时钟源来自系统时钟 Fosc/4 再分频,分频比可选 4、16、64,或者用 Timer2 作为时钟源实现更灵活的分频。在 64MHz 系统时钟下,SPI 最高时钟是 16MHz(Fosc/4),这个速度驱动 MR25H40CDF 绰绰有余。
PIC18F47Q10 还有一个很实用的特性:外设引脚选择(PPS)。你可以把 MSSP 的 SDO、SDI、SCK 映射到几乎任意数字引脚上,这给 PCB 布线带来了极大的灵活性。片选 CS 一般用普通 GPIO 控制,因为 SPI 主机硬件片选有时候不够灵活,软件片选可以精确控制时序,尤其是在多从机场景下。我后面会详细讲硬件片选和软件片选的取舍。
另外,PIC18F47Q10 的 MSSP 模块支持SPI 模式 0(CPOL=0, CPHA=0)和模式 3(CPOL=1, CPHA=1),MR25H40CDF 两种模式都兼容。我一般选模式 0,因为示波器上看波形最直观,SCK 空闲低电平,数据在上升沿采样,下降沿变化。这个后面讲时序的时候会展开。
2. 硬件连接与电路设计要点
硬件这块看似简单,SPI 四根线接上就行,但实际做工业产品,细节决定稳定性。我见过太多项目因为 SPI 走线太长、片选信号振铃、电源去耦不足导致数据读写随机出错。这一章把原理图设计、PCB 布局、信号完整性几个关键点讲透。
2.1 引脚连接与最小系统
MR25H40CDF 是 8 引脚 SOIC 封装,引脚定义如下:
| 引脚 | 名称 | 功能 | 连接说明 |
|---|---|---|---|
| 1 | CS | 片选,低有效 | 接 PIC GPIO,软件控制 |
| 2 | SO | 数据输出(从机输出) | 接 PIC SDI |
| 3 | WP | 写保护,低有效 | 接 VDD 或 GPIO 控制 |
| 4 | VSS | 地 | 接 GND |
| 5 | SI | 数据输入(从机输入) | 接 PIC SDO |
| 6 | SCK | 时钟 | 接 PIC SCK |
| 7 | HOLD | 保持,低有效 | 接 VDD 或 GPIO 控制 |
| 8 | VDD | 电源 | 接 3.3V |
这里有两个引脚容易被忽略:WP 和 HOLD。WP 是写保护,低电平有效,如果你不需要写保护功能,直接拉到 VDD。HOLD 是保持功能,低电平时暂停通信,一般也拉到 VDD。但如果你做的是需要防止误写的工业设备,可以把 WP 接到一个 GPIO,上电默认拉低保护,需要写入时再拉高。这个设计在参数存储场景下很有用,防止程序跑飞时乱写数据。
PIC18F47Q10 这边,我用 MSSP1 模块,引脚映射如下:
- SCK1 -> RC3(通过 PPS 映射)
- SDI1 -> RC4
- SDO1 -> RC5
- CS -> RA5(普通 GPIO 输出)
PPS 配置代码后面会给出。这里先记住一个原则:SPI 时钟线 SCK 尽量短,远离模拟信号和高压开关节点。如果 PCB 上 MRAM 离单片机超过 10cm,就要考虑串联端接电阻,一般 22Ω 到 33Ω 串在 SCK 和 SI 线上,抑制反射。
2.2 电源去耦与 PCB 布局
MR25H40CDF 的电源去耦我一般放两个电容:0.1uF 陶瓷电容紧贴 VDD 引脚,再加一个 1uF 或 2.2uF 的陶瓷电容在附近。别小看这个,MRAM 在写入瞬间会有电流脉冲,虽然平均功耗低,但瞬态电流如果供电跟不上,会导致写入失败或者数据错误。我实测过,只放一个 0.1uF 的时候,连续快速写入偶尔会出现某几个字节读回来不对,加了 2.2uF 之后问题消失。
PCB 布局上,MRAM 尽量靠近单片机放置,SPI 走线等长不是必须的,但 SCK 和 SI 尽量短且不要跨分割地平面。如果板子上有电机驱动或者继电器,MRAM 要远离这些干扰源,必要时加屏蔽罩或者用地线包围。片选 CS 线如果走线长,建议加一个 10kΩ 上拉电阻到 VDD,防止上电过程中 CS 浮动导致误触发。
注意:MR25H40CDF 的 CS 引脚内部没有上拉,如果单片机 GPIO 在上电复位期间是高阻态,CS 可能浮动,导致 MRAM 被意外选中。加外部上拉是低成本高可靠的做法。
3. SPI 驱动编写与读写时序详解
硬件搭好了,接下来是软件。SPI 驱动看起来简单,但 MR25H40CDF 的指令集和时序有一些细节,如果不注意,读出来的数据就是乱的。这一章从 SPI 初始化、指令格式、读写流程、时序参数计算几个方面展开。
3.1 SPI 主机初始化与 PPS 配置
PIC18F47Q10 的 SPI 初始化分几步:配置 PPS 映射引脚、设置 MSSP1 为 SPI 主机模式、配置时钟极性相位、设置时钟分频。下面是我常用的初始化代码,基于 XC8 编译器:
#include <xc.h> void SPI1_Init(void) { // 1. 配置 PPS 映射 // SCK1 -> RC3, SDI1 -> RC4, SDO1 -> RC5 RC3PPS = 0x0F; // SCK1 输出到 RC3 SDI1PPS = 0x14; // RC4 作为 SDI1 输入 RC5PPS = 0x10; // SDO1 输出到 RC5 // 2. 配置 CS 引脚为输出 TRISAbits.TRISA5 = 0; // RA5 输出 LATAbits.LATA5 = 1; // CS 默认高电平 // 3. 配置 MSSP1 SSP1STAT = 0x00; // 模式 0: CKE=0, SMP=0 SSP1CON1 = 0x20; // SSPEN=1, CKP=0, SSPM=0000 (Fosc/4) // 实际 SPI 时钟 = 64MHz / 4 = 16MHz // 4. 清除标志 PIR1bits.SSP1IF = 0; } uint8_t SPI1_Transfer(uint8_t data) { SSP1BUF = data; while(!PIR1bits.SSP1IF); // 等待传输完成 PIR1bits.SSP1IF = 0; return SSP1BUF; }这里有几个关键点解释一下。SSP1STAT的 CKE 位控制时钟边沿,SMP 位控制采样时刻。模式 0 下,CKE=0 表示数据在 SCK 上升沿采样,SMP=0 表示在数据输出末尾采样。SSP1CON1的 SSPM 设为 0000 表示 Fosc/4,也就是 16MHz。如果你觉得 16MHz 太快,可以设成 0001(Fosc/16,4MHz)或 0010(Fosc/64,1MHz)。我一般先用低速调试,确认读写正常后再提速。
实操心得:PPS 配置一定要在 MSSP 使能之前完成,否则引脚映射不生效。我踩过这个坑,调了半天发现 SCK 没波形,最后发现是 PPS 写晚了。
3.2 MR25H40CDF 指令集与读写流程
MR25H40CDF 的指令集很精简,常用的就几条:
| 指令 | 十六进制 | 功能 |
|---|---|---|
| WREN | 0x06 | 写使能 |
| WRDI | 0x04 | 写禁止 |
| RDSR | 0x05 | 读状态寄存器 |
| WRSR | 0x01 | 写状态寄存器 |
| READ | 0x03 | 读数据 |
| WRITE | 0x02 | 写数据 |
读数据流程:拉低 CS -> 发送 0x03 -> 发送 24 位地址(高字节在前)-> 连续读取 N 个字节 -> 拉高 CS。写数据流程:拉低 CS -> 发送 0x06(WREN)-> 拉高 CS -> 拉低 CS -> 发送 0x02 -> 发送 24 位地址 -> 发送 N 个字节数据 -> 拉高 CS。注意 WREN 是一个独立的事务,必须先发 WREN 再发 WRITE,中间 CS 要拉高一次。
地址是 24 位的,因为 512KB 需要 19 位地址,但 MRAM 用 24 位地址格式,高 5 位忽略。发送顺序是 A23 到 A0,高字节在前。
void MRAM_WriteEnable(void) { CS_LOW(); SPI1_Transfer(0x06); CS_HIGH(); } void MRAM_Write(uint32_t addr, uint8_t *data, uint16_t len) { MRAM_WriteEnable(); CS_LOW(); SPI1_Transfer(0x02); SPI1_Transfer((addr >> 16) & 0xFF); SPI1_Transfer((addr >> 8) & 0xFF); SPI1_Transfer(addr & 0xFF); for(uint16_t i = 0; i < len; i++) { SPI1_Transfer(data[i]); } CS_HIGH(); } void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { CS_LOW(); SPI1_Transfer(0x03); SPI1_Transfer((addr >> 16) & 0xFF); SPI1_Transfer((addr >> 8) & 0xFF); SPI1_Transfer(addr & 0xFF); for(uint16_t i = 0; i < len; i++) { buf[i] = SPI1_Transfer(0x00); } CS_HIGH(); }3.3 时序参数计算与速度优化
MR25H40CDF 的 SPI 时序参数在数据手册里有明确要求。最高 SCK 频率 40MHz,但那是理想条件下的极限值。实际用的时候要考虑 PCB 走线延迟、单片机 SPI 输出建立保持时间。我一般把 SCK 设在 10MHz 到 20MHz 之间,兼顾速度和稳定性。
以 16MHz SCK 为例,一个时钟周期 62.5ns。MR25H40CDF 要求 CS 建立时间(CS 下降到第一个 SCK 上升沿)最小 5ns,CS 保持时间(最后一个 SCK 下降到 CS 上升)最小 5ns。PIC 的 GPIO 翻转速度在 64MHz 下大概几十纳秒,完全满足。数据输出有效时间(SO 有效到 SCK 采样沿)典型值 8ns,PIC 的 SDI 采样窗口也够。
如果你要跑 40MHz,那就要仔细算 PCB 走线延迟了。信号在 FR4 上的传播速度大约是 6 英寸/纳秒,10cm 走线约 0.65ns 延迟,影响不大。但 40MHz 下时钟周期 25ns,上升沿可能就占 2-3ns,这时候串联端接电阻和阻抗匹配就很重要了。我建议工业产品不要追求极限速度,16MHz 已经能实现 2MB/s 的吞吐率,对于参数存储和日志记录绰绰有余。
注意:提高 SPI 速度后,一定要用示波器看 SCK 和 SO 的波形,确认没有过冲和振铃。如果波形不好,先降速,再检查端接和布局。
4. 数据存储策略与可靠性设计
有了底层读写驱动,接下来要考虑的是怎么组织数据。工业设备存储的数据一般分几类:设备参数(校准系数、配置项)、运行日志(故障记录、操作记录)、累计数据(运行小时数、产量计数)。不同类型的数据,存储策略不一样。
4.1 参数存储与双备份机制
设备参数最怕的是写入过程中断电,导致参数损坏。MRAM 虽然写入快,但也不是原子操作,一个多字节结构体写到一半断电,数据就乱了。我的做法是双备份加校验:在 MRAM 里分配两个区域 A 和 B,每个区域存一份参数结构体,结构体末尾加 CRC16 校验。写入时先写 A 再写 B,读取时先读 A 校验,如果 A 坏了读 B,如果 B 也坏了用默认参数。
typedef struct { uint16_t magic; // 0x5A5A 标识 float kp, ki, kd; // PID 参数 uint16_t calib_offset; // 校准偏移 uint32_t run_hours; // 运行小时数 uint16_t crc; // CRC16 校验 } DeviceParams_t; #define PARAM_A_ADDR 0x000000 #define PARAM_B_ADDR 0x000100 bool Params_Save(DeviceParams_t *p) { p->magic = 0x5A5A; p->crc = CRC16((uint8_t*)p, sizeof(DeviceParams_t)-2); MRAM_Write(PARAM_A_ADDR, (uint8_t*)p, sizeof(DeviceParams_t)); MRAM_Write(PARAM_B_ADDR, (uint8_t*)p, sizeof(DeviceParams_t)); // 回读校验 DeviceParams_t tmp; MRAM_Read(PARAM_A_ADDR, (uint8_t*)&tmp, sizeof(DeviceParams_t)); return (tmp.crc == p->crc); }这个双备份机制看起来浪费空间,但 512KB 的 MRAM 存这点参数连零头都用不到。可靠性提升是值得的。CRC16 我用的是 CCITT 多项式 0x1021,计算速度快,检错能力强。
4.2 环形日志与磨损均衡
运行日志是另一个大头。设备可能每天产生几十条记录,一年下来几万条。MRAM 虽然擦写寿命近乎无限,但也没必要可着一个地址写。我用环形缓冲区的方式管理日志区域:分配一块 64KB 的空间,每条日志固定 32 字节,可以存 2048 条。写指针循环递增,写满后覆盖最旧的记录。读的时候从写指针往前读。
#define LOG_BASE_ADDR 0x010000 #define LOG_ENTRY_SIZE 32 #define LOG_MAX_ENTRIES 2048 static uint16_t log_write_idx = 0; void Log_Append(uint8_t *entry) { uint32_t addr = LOG_BASE_ADDR + (log_write_idx * LOG_ENTRY_SIZE); MRAM_Write(addr, entry, LOG_ENTRY_SIZE); log_write_idx++; if(log_write_idx >= LOG_MAX_ENTRIES) { log_write_idx = 0; } // 写指针本身也要保存,防止断电丢失 MRAM_Write(LOG_IDX_ADDR, (uint8_t*)&log_write_idx, 2); }这里有个细节:写指针也要存到 MRAM 里,否则断电后不知道写到哪了。但写指针每次追加日志都要更新,如果频繁写同一个地址,虽然 MRAM 不怕,但为了减少写入次数,可以每 10 条日志更新一次指针,断电最多丢 10 条记录,对于工业日志来说可以接受。
实操心得:日志条目里一定要带时间戳和序号。时间戳可以用 RTC 或者系统运行秒数,序号用于检测是否有记录丢失。我一般用 4 字节 Unix 时间戳加 2 字节序号,剩下 26 字节放具体数据。
5. 常见问题排查与避坑指南
做了这么多项目,SPI MRAM 遇到的问题基本就那么几类。这一章我把典型故障现象、排查思路和解决方法整理成速查表,再补充几个数据手册上不会写的坑。
5.1 读写失败问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方法 |
|---|---|---|---|
| 读回全 0xFF | CS 没拉低或接线错误 | 示波器看 CS 和 SCK 波形 | 检查 CS 引脚配置和焊接 |
| 读回全 0x00 | SO 线没接或 MISO 配置错 | 检查 SDI 引脚 PPS 映射 | 确认 SDI1PPS 设置正确 |
| 数据偶尔错误 | SPI 速度太快或干扰 | 降低 SCK 频率测试 | 降速、加端接电阻、改善去耦 |
| 写入后读回不变 | WREN 没发或 WP 拉低 | 读状态寄存器确认写使能 | 检查 WREN 时序和 WP 引脚 |
| 地址错位 | 地址字节顺序错误 | 用已知数据写入特定地址验证 | 确认 24 位地址高字节在前 |
| 上电后数据丢失 | 电源上升太慢或 CS 浮动 | 示波器看上电时序 | 加 CS 上拉、增加电源斜率 |
这个表基本覆盖了 90% 的现场问题。我重点说几个容易误判的。
读回全 0xFF最常见的原因是 CS 根本没拉低,MRAM 没被选中,SO 线处于高阻态,被上拉电阻拉高了。这时候你看 SCK 有波形,SI 也有数据,但 SO 就是不动。用示波器双通道同时看 CS 和 SO,一目了然。
数据偶尔错误这个最头疼,因为不是必现。我遇到过几次,最后发现都是 SPI 速度太快加上 PCB 走线不好。有一次是 SCK 走线经过了一个 DC-DC 电源下方,开关噪声耦合进来,导致某些位翻转。把走线改到远离电源的位置,问题消失。所以 PCB 布局阶段就要考虑 SPI 走线的隔离。
5.2 数据手册之外的实战经验
数据手册会告诉你电气参数和时序要求,但不会告诉你这些:
第一,MRAM 的写入电流脉冲。连续快速写入时,VDD 上的电流是脉冲式的,平均电流可能只有几毫安,但峰值能到几十毫安。如果你的 LDO 响应慢,电压会瞬间跌落,导致写入失败。我在 VDD 引脚旁边放 2.2uF 陶瓷电容就是解决这个。有条件的话,用示波器看 VDD 纹波,写入时纹波超过 100mV 就要加电容。
第二,CS 的软件片选比硬件片选可靠。PIC 的 MSSP 模块有硬件 CS 功能,但我从来不用。硬件 CS 的时序是固定的,有时候在连续读写切换时会有毛刺。软件 CS 用 GPIO 控制,想什么时候拉低就什么时候拉低,想保持多久就保持多久,灵活得多。尤其是在多从机场景下,软件 CS 可以精确控制每个从机的选中时间。
第三,上电初始化要加延时。MR25H40CDF 上电后需要一段时间稳定,数据手册写的是 400us 典型值。我一般在初始化 SPI 之后加 1ms 延时,再发第一条指令。不加延时的话,上电第一次读写可能失败。这个坑我在批量生产时遇到过,个别板子上电后参数读不出来,加了延时就好了。
第四,写保护引脚不要悬空。WP 和 HOLD 如果悬空,可能被外部噪声触发,导致写入被保护或者通信被暂停。我都是直接拉到 VDD,如果需要写保护功能,就用 GPIO 控制,但默认状态一定是保护。
注意:如果你用 MRAM 存储安全关键数据,建议在写入后立即回读校验,并且用 CRC 或者校验和验证数据完整性。不要假设 SPI 写入一定成功,工业现场什么干扰都有。
6. 性能实测与方案对比
说了这么多理论,实际性能到底怎么样?我在实验室用 PIC18F47Q10 加 MR25H40CDF 做了一组测试,SPI 时钟 16MHz,连续写入 64KB 数据,再读回来校验。结果如下:
| 操作 | 数据量 | 耗时 | 等效速率 |
|---|---|---|---|
| 连续写入 | 64KB | 42ms | 1.52MB/s |
| 连续读取 | 64KB | 38ms | 1.68MB/s |
| 单字节写入 | 1B | 3.2us | - |
| 单字节读取 | 1B | 2.8us | - |
单字节写入 3.2us 包括了 WREN 指令、WRITE 指令、地址和数据的总线时间。对比 EEPROM 的 5ms 写入周期,快了 1500 倍。对比 SPI Flash 的扇区擦除加写入,更是快了几个数量级。
和几种常见存储方案对比:
| 方案 | 写入速度 | 擦写寿命 | 断电保持 | 功耗 | 成本 |
|---|---|---|---|---|---|
| MR25H40CDF | 纳秒级 | 10^14 | 20年 | 低 | 中高 |
| EEPROM | 毫秒级 | 10^6 | 100年 | 低 | 低 |
| SPI Flash | 毫秒级(含擦除) | 10^5 | 20年 | 低 | 低 |
| SRAM+电池 | 纳秒级 | 无限 | 依赖电池 | 中 | 中 |
MRAM 的成本确实比 EEPROM 和 Flash 高,但考虑到它省掉了擦除管理、磨损均衡、电池维护这些麻烦,综合成本未必高。尤其是工业设备,现场维护一次的成本可能比芯片差价高几十倍。
6.1 功耗表现与低功耗设计
MR25H40CDF 的功耗表现也值得一说。待机电流典型值 100uA,写入电流 15mA 左右,读取电流 8mA。对于电池供电的工业传感器,这个功耗可以接受。如果做低功耗设计,可以在不访问 MRAM 时把 CS 拉高,MRAM 进入待机模式。PIC18F47Q10 本身也支持休眠模式,需要记录数据时唤醒,写完继续睡。
我做过一个低功耗数据记录仪,每秒唤醒一次,写 32 字节日志,平均电流 200uA,用 2000mAh 电池能跑一年多。这个方案如果换成 EEPROM,写入时间长,唤醒时间也长,平均电流会高不少。
7. 项目扩展与进阶玩法
这套 MRAM 加 PIC18 的方案,基础功能就是存储和读取数据。但稍微扩展一下,能玩出更多花样。
7.1 掉电检测与紧急保存
工业设备最怕掉电丢数据。PIC18F47Q10 有内置的 ADC 和比较器,可以用 ADC 监测电源电压。当电压低于阈值时,触发中断,在电源电容放电完毕之前,把关键数据紧急写入 MRAM。MRAM 的纳秒级写入速度在这里优势尽显——EEPROM 可能还没写完一个字节,电就没了,而 MRAM 能写完几百个字节。
void __interrupt() ISR(void) { if(PIR1bits.ADIF) { uint16_t v = ADC_Read(); if(v < POWER_FAIL_THRESHOLD) { // 紧急保存关键数据 Emergency_Save(); // 进入低功耗或复位 } } }这个功能我实测过,用 1000uF 电容储能,从检测到掉电到写完 256 字节数据,耗时不到 1ms,电容电压还有 3V 以上,完全来得及。
7.2 多从机 SPI 总线扩展
如果你的系统里除了 MRAM 还有别的 SPI 设备,比如显示屏、传感器、Flash,可以用同一个 SPI 总线,不同的 CS 引脚。PIC18F47Q10 的 PPS 可以把 SCK、SDO、SDI 映射到一组引脚,然后每个从机单独用一个 GPIO 做 CS。软件上封装一个 SPI 事务函数,传入 CS 引脚号,自动处理片选。
void SPI_Transaction(uint8_t cs_pin, uint8_t *tx, uint8_t *rx, uint16_t len) { CS_LOW(cs_pin); for(uint16_t i = 0; i < len; i++) { rx[i] = SPI1_Transfer(tx[i]); } CS_HIGH(cs_pin); }这种架构下,MRAM 作为高速存储设备,和其他外设共享总线,互不干扰。注意每个从机的 SPI 模式要一致,如果某个从机只支持模式 3,那整个总线都要用模式 3。MR25H40CDF 两种模式都支持,所以兼容性没问题。
7.3 数据加密与安全存储
工业设备的数据安全越来越受重视。MRAM 里存的参数和日志,如果被非法读取,可能泄露设备运行信息。可以在写入前做简单的加密,比如 XOR 混淆或者 AES 加密。PIC18F47Q10 没有硬件 AES 加速,但软件实现 AES-128 也够用,毕竟数据量不大。
我一般用轻量级的 XOR 加 CRC 做基本保护,密钥存在单片机 Flash 的受保护区域。如果安全要求高,就上 AES,但要注意加密解密的时间开销。MRAM 写入快,加密慢一点也能接受。
实操心得:加密密钥不要存在 MRAM 里,否则等于把钥匙和锁放一起。存在 PIC 的 Flash 配置区,开启代码保护,读取需要特殊序列。
8. 写在最后的一些个人体会
这套方案我从 2021 年开始在几个工业项目上量产使用,累计出货几万台,现场故障率极低。MRAM 的可靠性确实对得起它的价格。我印象最深的是一个电梯控制系统项目,原来用 EEPROM 存楼层参数和故障记录,现场偶尔出现参数丢失,排查了很久发现是 EEPROM 写入时正好赶上电梯主回路接触器动作,电源波动导致写入失败。换成 MR25H40CDF 之后,同样工况下再没出现过数据丢失。因为 MRAM 写入太快了,电源波动的时间窗口根本来不及影响它。
如果你正在选型工业数据存储方案,我的建议是:先算清楚写入频率和数据重要性。如果每天写入不超过 100 次,EEPROM 够用;如果每秒都在写,或者断电必须保存,直接上 MRAM,省心。PIC18F47Q10 加 MR25H40CDF 这个组合,硬件成本增加有限,但软件上省掉了磨损均衡、擦除管理、电池维护这些麻烦事,总体开发效率和现场可靠性都提升明显。
最后分享一个小技巧:MRAM 的 512KB 空间,你可以划出一块区域做自检。上电时写入一个已知模式(比如 0xAA、0x55 交替),读回校验,确认存储介质正常。这个自检耗时不到 10ms,但能提前发现焊接不良或者芯片损坏的问题,避免现场运行时才发现存储故障。