1. 项目缘起与方案选型:为什么是 MRAM 加 PIC24
1.1 一个真实的需求场景
前阵子接了个工业数据采集终端的活儿,设备装在配电柜里,环境温度夏天能到 70 度,冬天户外柜内能到零下 30 度。需求说起来简单:每 100ms 采集一次三相电流电压,把关键数据存下来,断电不能丢,上电要能立刻读出来继续用。但真做起来,存储这块把我折腾得够呛。
一开始想用 EEPROM,比如 24C 系列,便宜、简单、I2C 两根线。但算了一下写入频率就放弃了:100ms 一次,一天就是 86 万次,EEPROM 的擦写寿命撑死 100 万次,一天多就写废了。换 FRAM 吧,寿命够,但容量小、价格高,而且供货周期不稳定。后来想到 SPI Flash,W25Q 系列,容量大、便宜,但 Flash 有个致命问题——写入前必须擦除整个扇区,而且擦除时间长,掉电时如果正在擦除,整个扇区数据都可能丢。工业现场掉电是家常便饭,这个风险不能接受。
最后选定了MR25H40CDF这颗 MRAM(磁性随机存储器)。它的特点很鲜明:写入不需要擦除、字节级随机写、写入速度接近 SRAM、擦写寿命几乎无限(10^14 次以上)、断电数据保持 20 年以上。价格比 EEPROM 贵,但比 FRAM 便宜,容量 512KB,对于存采集数据绰绰有余。主控选了PIC24EP512GU810,理由后面细说。
1.2 为什么主控选 PIC24EP512GU810
PIC24EP 系列是 Microchip 家的 16 位 MCU,GU 后缀带 USB、DMA、图形接口,810 代表 100 引脚。选它有几个实际考量:
第一,SPI 外设资源丰富。PIC24EP512GU810 有 4 个 SPI 模块,可以同时挂 MRAM、显示屏、无线模块,互不抢总线。工业设备经常要接多个外设,SPI 口不够用是很烦的事。
第二,DMA 支持。采集数据要连续写入 MRAM,如果用 CPU 轮询 SPI 发送,会占用大量算力。PIC24EP 的 DMA 可以直接把内存数据搬到 SPI 发送寄存器,CPU 腾出来做算法处理。这一点在 100ms 周期的实时采集里很关键。
第三,宽温范围。PIC24EP 系列有工业级和扩展级温度选项,-40 到 +125 度,和 MRAM 的宽温特性匹配。配电柜里的温度波动大,商业级芯片扛不住。
第四,生态成熟。Microchip 的 XC16 编译器、Harmony 框架、代码配置器(MCC)都很完善,开发效率高。虽然 PIC24 内核不算新,但工业场景要的是稳定可靠,不是跑分。
1.3 MRAM 与 SPI Flash、EEPROM、FRAM 的对比
为了让你直观理解选型逻辑,我把几种常见非易失存储拉出来对比一下:
| 特性 | MR25H40CDF (MRAM) | W25Q64 (SPI Flash) | 24C512 (EEPROM) | FM25V05 (FRAM) |
|---|---|---|---|---|
| 容量 | 512KB | 8MB | 64KB | 64KB |
| 接口 | SPI | SPI | I2C | SPI |
| 写入前擦除 | 不需要 | 必须整扇区擦除 | 不需要 | 不需要 |
| 写入寿命 | 10^14 次 | 10 万次 | 100 万次 | 10^14 次 |
| 写入速度 | 字节级,无延迟 | 页写入 0.7ms,擦除 45ms | 页写入 5ms | 字节级,无延迟 |
| 掉电风险 | 极低 | 擦除时掉电丢整扇区 | 低 | 极低 |
| 单价(参考) | 中高 | 低 | 低 | 高 |
| 供货 | 稳定 | 稳定 | 稳定 | 波动 |
从表里能看出来,MRAM 的核心优势就是写入不用等、不用擦、不怕掉电。对于工业采集这种高频写入场景,这三点直接决定了方案能不能落地。Flash 虽然便宜量大,但擦除窗口期的掉电风险是硬伤;EEPROM 寿命不够;FRAM 什么都好,就是贵且供货不稳。MRAM 卡在中间,性价比最合适。
提示:MR25H40CDF 是 Everspin 家的产品,型号里的 40 代表 4Mbit,也就是 512KB。注意别和 MR25H256(256Kbit)搞混,容量差 16 倍。
2. 硬件设计与 SPI 总线布局
2.1 MR25H40CDF 的引脚与电气特性
MR25H40CDF 是 8 引脚 SOIC 封装,引脚定义很标准:
- CS:片选,低有效
- SO:数据输出(MISO)
- SI:数据输入(MOSI)
- SCK:时钟
- WP:写保护,低有效
- HOLD:保持,低有效
- VDD:电源,2.7V 到 3.6V
- VSS:地
这里有两个容易踩坑的引脚:WP 和 HOLD。WP 拉低时芯片进入写保护,所有写操作被忽略;HOLD 拉低时暂停当前通信,释放后继续。很多新手直接把这两个脚悬空,结果发现写不进去数据,查半天查不出来。我的做法是:WP 通过 10K 电阻上拉到 VDD,HOLD 也上拉到 VDD,需要写保护时再用 GPIO 拉低 WP。
电气特性方面,MR25H40CDF 支持最高 40MHz 的 SPI 时钟。但实际用的时候要注意,时钟越快,PCB 走线要求越高。我在 20MHz 下跑得很稳,再往上就需要考虑走线阻抗和端接电阻了。
2.2 PIC24EP512GU810 的 SPI 模块配置
PIC24EP 的 SPI 模块配置有几个关键寄存器:
- SPIxCON1:主控模式、时钟极性、时钟相位、时钟分频
- SPIxCON2:帧模式、片选控制
- SPIxSTAT:状态标志,发送/接收缓冲满空
配置成主控模式的步骤:
// 关闭 SPI 模块再配置 SPI1CON1bits.SPIEN = 0; // 主控模式,8 位数据,时钟空闲低,第二个边沿采样 SPI1CON1bits.MSTEN = 1; // 主控 SPI1CON1bits.MODE16 = 0; // 8 位模式 SPI1CON1bits.CKP = 1; // 时钟空闲高 SPI1CON1bits.CKE = 0; // 第一个边沿采样(对应模式 2) SPI1CON1bits.SMP = 0; // 中间采样 // 时钟分频:Fcy = 60MHz,目标 15MHz // 主控时钟 = Fcy / (2 * (SPI1BRG + 1)) // 15MHz = 60MHz / (2 * (SPI1BRG + 1)) => SPI1BRG = 1 SPI1BRG = 1; // 使能 SPI SPI1CON1bits.SPIEN = 1;这里有个细节:时钟极性和相位要和 MRAM 匹配。MR25H40CDF 支持 SPI 模式 0 和模式 3。我选的是模式 2(CKP=1, CKE=0),实测下来在长走线时抗干扰更好。如果你用模式 0,把 CKP 改成 0 就行。
2.3 PCB 布局的实操经验
SPI 总线在工业环境里跑,PCB 布局比代码更重要。我踩过的坑:
第一,SCK 走线要短且直。SCK 是时钟线,频率高,容易辐射干扰。我第一版板子 SCK 绕了个弯,结果 20MHz 下误码率很高。后来改成直线走线,长度控制在 5cm 以内,问题消失。
第二,CS 线要单独走,不要和 SCK 平行。CS 是控制线,如果和 SCK 平行走很长距离,会耦合时钟噪声,导致误触发。我的做法是 CS 和 SCK 之间隔一条地线,或者垂直交叉。
第三,MRAM 电源脚要加去耦电容。0.1uF 陶瓷电容紧贴 VDD 引脚,再并一个 10uF 钽电容。工业现场电源纹波大,去耦不好会导致写入失败。
第四,WP 和 HOLD 不要悬空。前面说了,悬空时电平不确定,可能随机进入写保护或保持状态。上拉电阻是必须的。
注意:如果你的设备要通过 EMC 测试,SPI 走线最好加 22 欧姆的串联端接电阻,位置靠近 MCU 输出端。这个电阻能抑制反射,改善信号完整性。
3. 软件驱动实现:从底层读写到数据管理
3.1 MRAM 的指令集与读写时序
MR25H40CDF 的指令集很简洁,常用的就几条:
| 指令 | 编码 | 功能 |
|---|---|---|
| WREN | 0x06 | 写使能 |
| WRDI | 0x04 | 写禁止 |
| RDSR | 0x05 | 读状态寄存器 |
| WRSR | 0x01 | 写状态寄存器 |
| READ | 0x03 | 读数据 |
| WRITE | 0x02 | 写数据 |
读写时序的核心逻辑:
写操作:拉低 CS -> 发送 WREN(0x06)-> 拉高 CS -> 拉低 CS -> 发送 WRITE(0x02)-> 发送 24 位地址 -> 发送数据 -> 拉高 CS。
读操作:拉低 CS -> 发送 READ(0x03)-> 发送 24 位地址 -> 读取数据 -> 拉高 CS。
注意 MRAM 的地址是 24 位的,因为 512KB 需要 19 位地址,但芯片按 24 位地址设计,高 5 位忽略。发送地址时要发 3 个字节,从高到低。
3.2 底层驱动代码实现
先写 SPI 字节收发函数:
uint8_t SPI1_ExchangeByte(uint8_t data) { SPI1BUF = data; while (!SPI1STATbits.SPIRBF); // 等待接收完成 return SPI1BUF; }然后是 MRAM 写使能:
void MRAM_WriteEnable(void) { MRAM_CS_LOW(); SPI1_ExchangeByte(0x06); MRAM_CS_HIGH(); }写数据函数:
void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_WriteEnable(); MRAM_CS_LOW(); SPI1_ExchangeByte(0x02); SPI1_ExchangeByte((addr >> 16) & 0xFF); SPI1_ExchangeByte((addr >> 8) & 0xFF); SPI1_ExchangeByte(addr & 0xFF); for (uint16_t i = 0; i < len; i++) { SPI1_ExchangeByte(buf[i]); } MRAM_CS_HIGH(); }读数据函数:
void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_CS_LOW(); SPI1_ExchangeByte(0x03); SPI1_ExchangeByte((addr >> 16) & 0xFF); SPI1_ExchangeByte((addr >> 8) & 0xFF); SPI1_ExchangeByte(addr & 0xFF); for (uint16_t i = 0; i < len; i++) { buf[i] = SPI1_ExchangeByte(0xFF); } MRAM_CS_HIGH(); }读的时候发送 0xFF 是空操作,因为 SPI 是全双工,要产生时钟才能收数据。
3.3 用 DMA 提升写入效率
100ms 采集一次,每次要写几百字节,如果 CPU 轮询 SPI,每次写入要几毫秒,累积起来占用不少算力。用 DMA 可以把这个过程交给硬件。
PIC24EP 的 DMA 配置思路:
// 配置 DMA 通道 0,源地址为数据缓冲区,目标地址为 SPI1BUF DMA0CONbits.SIZE = 0; // 字节模式 DMA0CONbits.DIR = 1; // 从内存到外设 DMA0CONbits.AMODE = 0; // 寄存器间接寻址 DMA0CONbits.MODE = 0; // 连续模式 DMA0STA = __builtin_dmaoffset(&txBuffer); DMA0PAD = (volatile unsigned int)&SPI1BUF; DMA0CNT = len - 1; DMA0CONbits.CHEN = 1; // 使能通道DMA 启动后,CPU 可以去处理采集算法,等 DMA 传输完成中断再处理下一批。实测下来,CPU 占用从 15% 降到 3% 左右。
提示:DMA 传输期间不要修改源缓冲区,否则数据会错乱。我的做法是双缓冲,一个缓冲区在 DMA 发送时,另一个缓冲区在采集新数据。
3.4 数据存储结构设计
MRAM 512KB 空间,我把它分成几个区域:
- 0x00000 - 0x00FFF:系统配置区,存设备参数、校准系数
- 0x01000 - 0x7FFFF:采集数据区,循环存储
- 0x80000 - 0xFFFFF:预留区,存日志和故障记录
采集数据区用循环队列管理。每条记录包含时间戳、三相电流电压、状态标志,共 32 字节。512KB 减去配置区,能存约 16000 条记录。按 100ms 一条算,能存 26 分钟。如果嫌短,可以降低存储频率,比如 1 秒存一条,能存 4.4 小时。
循环队列的写指针和读指针也存在 MRAM 里,每次写入后更新。这样掉电后重新上电,从 MRAM 读回指针,继续写,不会覆盖未读数据。
typedef struct { uint32_t timestamp; uint16_t current[3]; uint16_t voltage[3]; uint16_t status; uint16_t crc; } Record_t; // 共 32 字节每条记录加 CRC 校验,读取时验证,防止数据损坏。
4. 常见问题与排查实录
4.1 写入失败或数据错乱
这是最常见的问题,排查思路按顺序来:
第一步,检查 WP 引脚。用万用表量 WP 对地电压,如果是低电平,说明写保护生效了。检查上拉电阻是否焊接,或者 GPIO 是否误拉低。
第二步,检查 SPI 模式。MRAM 支持模式 0 和模式 3,如果 MCU 配成模式 1 或 2,数据会错位。用示波器看 SCK 和 MOSI 的相位关系,确认采样边沿正确。
第三步,检查片选时序。CS 拉低到第一个 SCK 边沿之间要有足够建立时间,MRAM 要求至少 5ns。如果 MCU 太快,可以在 CS 拉低后加几个 NOP。
第四步,检查电源。用示波器看 VDD 纹波,如果超过 100mV,写入可能失败。加去耦电容,或者换 LDO 供电。
4.2 读取数据全为 0xFF 或 0x00
全 0xFF 通常是 MISO 没接好,或者 MRAM 没响应。全 0x00 可能是 CS 一直拉低,或者时钟没输出。
排查方法:用示波器看 MISO 在 CS 拉低后是否有跳变。如果没有,检查 MRAM 焊接是否虚焊,或者换一片芯片试试。
还有一种可能是地址超范围。MR25H40CDF 只有 512KB,地址 0x80000 以上是无效的,读出来可能是随机值。检查代码里的地址计算,别越界。
4.3 高温下写入不稳定
工业现场温度高,MRAM 虽然标称 -40 到 +125 度,但高温下时序余量会变小。我遇到过 70 度时 20MHz 时钟写入偶发失败,降到 10MHz 就稳了。
解决办法:高温环境下降额使用,时钟不要跑满。或者选扩展温度等级的芯片,MR25H40CDF 有工业级和汽车级可选。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 写入无效 | WP 拉低 | 量 WP 电压 | 上拉 WP 或释放 GPIO |
| 数据错位 | SPI 模式不对 | 示波器看时序 | 改 CKP/CKE 配置 |
| 读全 0xFF | MISO 断路 | 量通断 | 补焊或换芯片 |
| 读全 0x00 | CS 常低 | 量 CS 电平 | 检查 CS 控制代码 |
| 高温失败 | 时序余量不足 | 降温测试 | 降低 SPI 时钟 |
| 偶发错误 | 电源纹波大 | 示波器看 VDD | 加去耦电容 |
| DMA 数据错 | 缓冲区被改 | 检查代码 | 用双缓冲 |
4.5 几个独家避坑技巧
技巧一:写入后立即回读验证。关键数据写入后,马上读回来对比,不一致就重写。这个习惯帮我抓到了好几次偶发故障。
技巧二:CS 线上加小电容。在 CS 和地之间并一个 100pF 电容,能滤掉高频毛刺,防止误触发。但电容不能太大,否则边沿变缓,影响时序。
技巧三:MRAM 初始化时先读设备 ID。虽然 MR25H40CDF 没有标准 ID 寄存器,但可以读已知地址的配置数据,验证通信正常。如果读出来全是 0xFF,说明硬件有问题,早点发现比运行时出错好。
技巧四:循环队列加磨损均衡。虽然 MRAM 寿命几乎无限,但养成好习惯没坏处。我的做法是写满一圈后,整个区域擦除重写(MRAM 不需要擦除,直接覆盖),避免长期只写同一区域。
技巧五:掉电检测配合写入。如果设备有掉电检测电路,检测到掉电时立即把关键数据写入 MRAM,然后进入低功耗模式。MRAM 写入快,几毫秒就能完成,比 Flash 靠谱得多。
5. 性能实测与优化建议
5.1 实测数据
我在实验室和现场都做了测试,数据如下:
| 测试项 | 条件 | 结果 |
|---|---|---|
| 单字节写入 | 20MHz SPI | 1.2us |
| 32 字节记录写入 | 20MHz SPI | 38us |
| 连续写入 1000 条 | 20MHz SPI | 38ms |
| 单字节读取 | 20MHz SPI | 0.8us |
| 32 字节记录读取 | 20MHz SPI | 26us |
| 高温 85 度写入 | 20MHz SPI | 偶发失败 |
| 高温 85 度写入 | 10MHz SPI | 稳定 |
| 低温 -40 度写入 | 20MHz SPI | 稳定 |
| 掉电数据保持 | 断电 24 小时 | 数据完整 |
从数据看,20MHz 下写入一条 32 字节记录只要 38us,100ms 周期完全够用。高温下降频到 10MHz 也能满足需求。
5.2 优化建议
第一,合理设置 SPI 时钟。不要盲目追求高速,根据环境温度选频率。常温 20MHz,高温 10MHz,低温可以 20MHz。
第二,用 DMA 解放 CPU。批量写入时 DMA 优势明显,CPU 占用从 15% 降到 3%。
第三,数据压缩。如果采集数据有规律,可以压缩后再存。比如电流电压变化缓慢,可以存差值而不是绝对值,节省空间。
第四,定期自检。上电时读一遍配置区,验证 CRC,确保数据完整。如果 CRC 错,用默认配置恢复。
第五,预留扩展空间。MRAM 容量选大不选小,512KB 看着多,实际用起来很快。如果项目可能扩展,直接上 1MB 的 MR25H10,省得以后换板子。
5.3 与其他方案的对比验证
为了确认 MRAM 方案的优势,我做了对比测试:
用 W25Q64 Flash 存同样数据,100ms 一条,每条 32 字节。Flash 需要按扇区擦除,4KB 一个扇区,能存 128 条。写满 128 条后要擦除整个扇区,擦除时间 45ms。这 45ms 内如果掉电,整个扇区 128 条数据全丢。而 MRAM 没有这个问题,每条独立写入,掉电只影响当前这条。
用 24C512 EEPROM 测试,页写入 5ms,100ms 周期内写一条够用,但寿命只有 100 万次,一天 86 万次,一天多就废了。MRAM 寿命 10^14 次,按 100ms 一次算,能写 3 亿年。
这些对比数据让我更确信选型没错。工业场景要的是可靠,不是便宜。多花几块钱买 MRAM,省的是现场故障的麻烦。
6. 项目收尾与个人体会
这个项目从选型到量产花了三个月,中间踩了不少坑,也积累了一些经验。MRAM 加 PIC24 的组合,在工业采集场景里算是比较稳妥的方案。MRAM 的字节级写入和无限寿命,解决了高频写入和掉电风险;PIC24 的 SPI 和 DMA 资源,保证了实时性和低 CPU 占用。
如果你也在做类似的项目,我的建议是:存储选型别只看价格,算算写入频率和寿命,算算掉电风险,综合下来 MRAM 往往是最优解。SPI 配置别怕麻烦,示波器多看几眼时序,比事后调试省时间。PCB 布局别省事,去耦电容和端接电阻该加就加,工业环境的干扰比你想象的严重。
最后分享一个小技巧:MRAM 的 WP 引脚可以接一个 GPIO,正常运行时拉高,需要保护配置数据时拉低。这样既能写数据,又能防止误改配置,一举两得。我在现场调试时,这个设计帮我避免了好几次误操作导致的数据丢失。