1. 项目缘起与方案选型
1.1 为什么要在工业场景里折腾 MRAM 和 Kinetis
做工业嵌入式这行的朋友大多有个共识:选存储方案比选主控还让人头疼。主控跑得慢一点顶多是性能问题,存储选错了,轻则现场数据丢包,重则设备在客户产线上趴窝。我手头这个项目就是典型的工业数据采集终端,要求每 100ms 记录一次传感器状态,断电不能丢,还要能扛住车间里变频器、接触器频繁动作带来的电磁干扰。一开始团队想用常见的 SPI NOR Flash 加铁电存储器的组合,但评估下来发现两个问题:一是 NOR Flash 写入速度太慢,按页擦写根本跟不上采集节奏;二是铁电容量小、价格高,做历史数据缓存不够用。
后来接触到 Everspin 的 MR25H40CDF,这是一颗 4Mbit 的 SPI 磁阻随机存储器,也就是常说的 MRAM。它最大的特点是非易失、写入几乎无延迟、擦写寿命近乎无限,而且没有 NOR Flash 那种先擦后写的麻烦。主控这边选的是 NXP 的 MK20DX128VFM5,属于 Kinetis K20 系列,Cortex-M4 内核,128KB Flash、32KB SRAM,5V 供电,工业温度范围,外设资源刚好够用。两者通过 SPI 总线连接,构成一套“主控+非易失存储”的经典嵌入式数据存储方案。
这套组合适合谁参考?如果你正在做工业仪表、数据记录仪、PLC 扩展模块、医疗设备日志存储,或者任何需要频繁写入且断电不能丢数据的嵌入式项目,这篇文章里的思路和踩坑记录应该能帮你省下不少调试时间。下面我会从硬件连接、SPI 配置、读写时序、数据管理策略到问题排查,把整个实现过程拆开讲清楚。
1.2 核心器件特性对比与选型逻辑
在动手画板子之前,先把两颗芯片的关键参数摆出来对比,这样后面配置寄存器的时候心里有数。
| 特性 | MR25H40CDF | MK20DX128VFM5 |
|---|---|---|
| 类型 | SPI MRAM | Cortex-M4 MCU |
| 容量 | 512KB(4Mbit) | 128KB Flash / 32KB SRAM |
| 接口 | SPI(最高 40MHz) | SPI0/SPI1 模块 |
| 供电 | 2.7V ~ 3.6V | 1.71V ~ 3.6V(内核),IO 可 5V 容忍 |
| 写入方式 | 直接写入,无需擦除 | 通过 SPI 发送命令 |
| 擦写寿命 | 近乎无限 | Flash 约 10 万次 |
| 数据保持 | 20 年以上 | Flash 约 10 年 |
| 温度范围 | -40°C ~ +85°C | -40°C ~ +105°C |
选 MR25H40CDF 而不是 FRAM 或 NVSRAM,主要看中三点:第一,容量够大,512KB 可以存好几天的历史数据;第二,SPI 接口简单,占用引脚少,布线方便;第三,写入不需要等待,MCU 发完数据就可以干别的事,不用轮询忙状态。MK20DX128VFM5 这边,SPI 模块支持 DMA,可以进一步降低 CPU 占用,而且 Kinetis 系列的 SDK 和寄存器手册比较完善,调试起来有据可查。
注意:MR25H40CDF 的供电范围是 2.7V 到 3.6V,如果主控 IO 是 5V,必须加电平转换或者确认 IO 口设置为开漏加上拉。我见过有人直接把 5V 的 SPI 接到 MRAM 上,结果芯片发热严重,数据读写全乱。
2. 硬件连接与 SPI 总线设计
2.1 引脚连接与 PCB 布局要点
MR25H40CDF 采用 8 引脚 SOIC 封装,引脚定义很标准:CS、SO、SI、SCK、VDD、VSS、WP、HOLD。其中 WP 和 HOLD 在普通读写场景下可以直接上拉到 VDD,不需要额外控制。和 MK20DX128VFM5 连接时,我选用的是 SPI0 模块,对应引脚如下:
- PTC4 作为 SPI0_PCS0,接 MRAM 的 CS
- PTC5 作为 SPI0_SCK,接 MRAM 的 SCK
- PTC6 作为 SPI0_SOUT,接 MRAM 的 SI
- PTC7 作为 SPI0_SIN,接 MRAM 的 SO
这里有个细节:Kinetis 的 SPI 片选可以硬件自动控制,也可以软件手动拉低。我建议用硬件片选,因为 MRAM 对时序比较敏感,软件片选如果被中断打断,可能导致 CS 提前拉高,写入不完整。PCB 布局上,SPI 走线尽量等长,SCK 和 SO 之间保持足够间距,避免串扰。如果板子上有变频器或继电器,MRAM 的电源引脚旁边一定要放 0.1uF 和 10uF 电容,越近越好。
实操心得:我第一版板子把 MRAM 放在离主控 8cm 远的地方,SPI 时钟跑到 20MHz 就开始丢数据。后来把走线缩短到 3cm 以内,并在 SCK 上串了 22 欧姆电阻,问题解决。工业环境里,SPI 走线超过 5cm 就要考虑信号完整性问题。
2.2 SPI 模式与时钟配置
MR25H40CDF 支持 SPI 模式 0 和模式 3,也就是 CPOL=0/CPHA=0 或 CPOL=1/CPHA=1。我选的是模式 0,因为 Kinetis 的 SPI 模块默认配置就是模式 0,省得改。时钟频率方面,MRAM 手册标称最高 40MHz,但实际跑下来,在 3.3V 供电、室温环境下,30MHz 比较稳。工业温度范围下,我建议降到 20MHz,留足余量。
配置 SPI 时,几个关键寄存器要设置对:
SPIx_MCR:设置主模式、时钟极性、时钟相位SPIx_CTAR0:设置波特率分频、帧大小(8 位)、MSB 优先SPIx_SR:查询发送和接收标志
波特率计算公式是:SPI 时钟 = 总线时钟 / (Prescaler × (1 + SCKBR))。假设总线时钟 60MHz,Prescaler 设为 2,SCKBR 设为 1,则 SPI 时钟 = 60 / (2 × 2) = 15MHz。这个频率下,传输 512KB 数据大约需要 512×1024×8 / 15M ≈ 0.28 秒,对于 100ms 采集一次的应用来说完全够用。
3. MRAM 读写命令与操作流程
3.1 命令集解析与状态寄存器
MR25H40CDF 的命令集和普通 SPI Flash 类似,但少了擦除命令,多了直接写入的便利。常用命令如下:
| 命令名称 | 命令码 | 说明 |
|---|---|---|
| WREN | 0x06 | 写使能 |
| WRDI | 0x04 | 写禁止 |
| RDSR | 0x05 | 读状态寄存器 |
| WRSR | 0x01 | 写状态寄存器 |
| READ | 0x03 | 读数据 |
| WRITE | 0x02 | 写数据 |
状态寄存器只有最低位 WEL 和 WIP 有意义。WEL 是写使能锁存,执行 WREN 后置 1,写入完成后自动清零。WIP 是写进行中标志,MRAM 写入速度极快,通常发完数据 WIP 就已经是 0 了,不需要像 Flash 那样轮询等待。
读数据流程:拉低 CS → 发送 0x03 → 发送 3 字节地址(24 位)→ 连续读取数据 → 拉高 CS。写数据流程:拉低 CS → 发送 0x06(WREN)→ 拉高 CS → 拉低 CS → 发送 0x02 → 发送 3 字节地址 → 发送数据 → 拉高 CS。注意 WREN 和 WRITE 之间必须拉高 CS,否则 WREN 不生效。
3.2 地址空间与页边界处理
MR25H40CDF 的 512KB 地址空间从 0x000000 到 0x07FFFF。写入时没有页边界限制,可以跨页连续写,这是它比 Flash 方便的地方。但读的时候要注意,如果地址超出范围,会回卷到 0x000000。我在代码里加了一个地址检查函数,防止数组越界导致数据覆盖。
#define MRAM_SIZE 0x80000 #define MRAM_PAGE 256 void MRAM_Write(uint32_t addr, uint8_t *buf, uint32_t len) { if (addr + len > MRAM_SIZE) { // 地址越界处理 return; } SPI_CS_Low(); SPI_Transfer(0x06); // WREN SPI_CS_High(); SPI_CS_Low(); SPI_Transfer(0x02); // WRITE SPI_Transfer((addr >> 16) & 0xFF); SPI_Transfer((addr >> 8) & 0xFF); SPI_Transfer(addr & 0xFF); for (uint32_t i = 0; i < len; i++) { SPI_Transfer(buf[i]); } SPI_CS_High(); }这段代码里,SPI_Transfer 是一个阻塞式收发函数,发一个字节同时收一个字节。实际项目中可以改成 DMA 方式,减少 CPU 占用。
4. 数据存储策略与掉电保护
4.1 环形缓冲区与磨损均衡
虽然 MRAM 擦写寿命近乎无限,但为了数据管理方便,我还是设计了一个环形缓冲区。把 512KB 分成 2048 个 256 字节的块,每块存一条记录,记录包含时间戳、传感器数据和 CRC 校验。写指针和读指针存在 MRAM 的固定位置,每次写入后更新写指针。
环形缓冲区的好处是写入位置均匀分布,不会反复写同一个地址。虽然 MRAM 不怕磨损,但均匀分布可以降低局部发热,提高长期可靠性。另外,读指针和写指针分开存储,掉电后重新上电可以继续从上次位置读写。
4.2 掉电检测与数据完整性
工业现场掉电是常态,所以掉电检测电路必不可少。我在电源输入端加了一个分压电阻到 ADC 引脚,当电压低于 3.0V 时触发中断,在中断里把当前缓存的数据紧急写入 MRAM。MRAM 写入速度快,从检测到掉电到完成写入,通常只需要几百微秒,足够保存最后一条记录。
CRC 校验用的是 CRC-16-CCITT,多项式 0x1021,初始值 0xFFFF。每条记录写入时计算 CRC,读取时校验。如果 CRC 不匹配,说明数据损坏,跳过这条记录继续读下一条。实测下来,在变频器干扰环境下,CRC 校验能有效识别出约 0.1% 的坏数据。
注意:掉电中断里不要做复杂运算,只做必要的写入操作。我一开始在中断里调用了一个浮点运算函数,结果中断执行时间太长,数据没写完电就没了。后来改成只写原始字节,CRC 在正常运行时算好,问题解决。
5. 常见问题与排查实录
5.1 SPI 通信失败排查表
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 读回全 0xFF | CS 未拉低或时序错误 | 示波器看 CS 和 SCK 波形 |
| 读回全 0x00 | MISO 未连接或上拉 | 检查 SO 引脚焊接 |
| 数据偶尔错位 | SCK 频率过高 | 降低 SPI 时钟到 10MHz 测试 |
| 写入后读回不变 | WREN 未生效 | 确认 WREN 和 WRITE 之间 CS 拉高 |
| 地址越界回卷 | 地址计算错误 | 检查地址是否超过 0x07FFFF |
5.2 工业环境下的抗干扰经验
车间里的电磁干扰主要来自变频器和接触器。我试过几种措施:第一,SPI 走线包地,两侧加接地过孔;第二,MRAM 电源加磁珠和 TVS 管;第三,软件上增加重试机制,连续读三次,取多数结果。实测下来,包地加磁珠的效果最明显,误码率从 1% 降到 0.01% 以下。
另外,MRAM 的 WP 引脚在干扰环境下可能被误触发,导致写入保护。我建议把 WP 直接上拉到 VDD,不要悬空。如果必须用 WP 功能,加一个 100nF 电容到地,滤除高频干扰。
5.3 调试工具与技巧
调试 SPI 通信,逻辑分析仪比示波器好用。我用的是 Saleae Logic 8,采样率设 100MHz,可以同时抓 CS、SCK、SI、SO 四路信号。抓到的数据可以直接解析成 SPI 字节,对照命令集看哪一步出错。如果没有逻辑分析仪,也可以用 MCU 的 GPIO 翻转来粗略判断时序,但精度不够,只能看个大概。
JLink 烧录的时候,如果 SPI 引脚被占用,可能会影响调试。我一般先把 MRAM 的 CS 拉高,让 SPI 总线空闲,再连接 JLink。如果还是连不上,就在复位后延时 500ms 再初始化 SPI,给调试器留出连接时间。
6. 性能实测与优化方向
6.1 读写速度实测数据
在 15MHz SPI 时钟下,我实测了不同长度数据的读写时间:
| 数据长度 | 写入时间 | 读取时间 |
|---|---|---|
| 256 字节 | 0.18ms | 0.15ms |
| 1KB | 0.62ms | 0.58ms |
| 4KB | 2.4ms | 2.3ms |
| 64KB | 38ms | 37ms |
写入和读取时间基本对称,因为 MRAM 没有擦除等待。这个速度对于 100ms 采集周期来说绰绰有余,甚至可以在两次采集之间做数据压缩和校验。
6.2 降低功耗的几种手段
如果项目是电池供电,功耗就很关键。MR25H40CDF 的待机电流典型值 2mA,写入电流 15mA。降低功耗可以从几方面入手:第一,不读写的时候把 CS 拉高,MRAM 进入待机模式;第二,降低 SPI 时钟,功耗和频率正相关;第三,用 DMA 传输,MCU 可以在传输期间进入低功耗模式。我实测下来,把 SPI 降到 5MHz,加上 CS 管理,整体功耗从 25mA 降到 8mA。
6.3 后续扩展思路
这套方案目前只用了单颗 MRAM,如果容量不够,可以并联多颗,用不同的 CS 片选。MR25H40CDF 支持 HOLD 引脚,多颗并联时可以用 HOLD 做总线仲裁。另外,MK20DX128VFM5 的 SPI1 还可以接其他外设,比如显示屏或无线模块,把数据实时上传到上位机。
我在实际使用中发现,MRAM 的写入寿命虽然标称无限,但长期高温环境下,数据保持能力会下降。如果设备工作在 85°C 以上,建议每五年做一次数据迁移,把旧数据搬到新的 MRAM 里。这个经验在手册里不会写,但做工业产品必须考虑。
最后分享一个小技巧:MRAM 的读写地址可以按位操作,如果你只需要存几个标志位,可以直接用位带别名区,把 MRAM 地址映射到 MCU 的位带区,读写单个位就像操作寄存器一样方便。这个技巧在存配置参数时特别有用,省去了读改写整个字节的麻烦。