
1. 项目缘起与方案选型为什么是 MRAM 加 PIC181.1 一个真实的数据存储痛点做过工业现场设备的朋友大概都有过这种经历设备装在配电柜里夏天柜内温度能到七十多度冬天在北方户外又能跌到零下三四十度。这种环境下设备需要频繁记录运行参数、故障日志、累计运行时长而且经常是断电重启之后必须马上把上次的数据读回来。早期我用 EEPROM 做这类存储写次数是个绕不过去的坎一百万次看着多但如果是每秒写一次关键状态几个月就写废了。后来换 FRAM速度快、寿命也够但容量小、单价高而且很多型号对高温的耐受并不理想。再后来接触到 MRAM也就是磁性随机存储器才算找到一个相对平衡的方案。这次要聊的组合是MR25H40CDF这颗 4Mbit 的 SPI MRAM搭配PIC18F55K42这颗带丰富外设的 8 位单片机。标题说的是在工业和嵌入式应用中存储和读取数据听起来很泛但落到实际项目里它对应的就是一类非常具体的需求小体积、宽温、高可靠、掉电不丢、写入无延迟、寿命近乎无限的数据记录场景。适合谁看如果你正在做工业仪表、电力监测终端、医疗设备日志、车载黑匣子这类东西或者你只是单纯想搞清楚 MRAM 到底怎么用、和 Flash、EEPROM 比到底强在哪这篇内容应该能帮到你。1.2 MRAM 到底是个什么东西先把概念说清楚。MRAM 全称 Magnetoresistive Random Access Memory磁性随机存储器。它的存储单元不是靠电荷而是靠磁性隧道结的磁化方向来存 0 和 1。这个物理原理带来的直接好处有几个第一写入不需要先擦除这点和 EEPROM、Flash 完全不同第二写入速度是纳秒级的比 EEPROM 快几个数量级第三理论擦写寿命接近无限厂商一般标 10 的 14 次方以上第四掉电数据不丢因为它本来就是非易失的。我用一个生活化的类比来解释它和 Flash 的区别。Flash 像一块黑板你要改一个字得先把整块黑板擦干净再重写擦的过程慢而且黑板会磨损。EEPROM 像一本可以逐字涂改的笔记本改一个字只动一个字但涂改次数多了纸会破。MRAM 则像一块磁力写字板你写什么它记什么改的时候直接覆盖没有擦除这个动作也不存在磨损。这个差异在需要高频写入的场景里是决定性的。MR25H40CDF 是 Everspin 家的产品4Mbit 容量也就是 512K 字节SPI 接口工作电压 2.7V 到 3.6V工业级温度范围覆盖 -40 到 85 摄氏度部分批次能到 105 度。它支持最高 40MHz 的 SPI 时钟实际读写吞吐相当可观。封装是 8 脚的 DFN 或者 SOIC占板面积很小。1.3 为什么选 PIC18F55K42 来配它PIC18F55K42 是 Microchip 的 PIC18 系列里比较新的一颗55K42 这个型号属于 K42 家族。它有几个特性特别适合配 MRAM 用。首先是它自带硬件 SPI 模块不用软件模拟时序稳定能跑到比较高的时钟。其次是它的存储容量和外设配置128KB Flash、8KB RAM、1KB 的 EEPROM对于要缓存日志、做协议解析的应用来说够用。再就是 K42 系列有丰富的定时器、ADC、比较器、DMA还有 CRC 硬件模块做数据校验很方便。更关键的一点是它的工作电压范围和 MRAM 匹配都是 3.3V 系统不需要额外的电平转换。而且 PIC18F55K42 支持 XLP 超低功耗技术如果设备是电池供电或者需要低功耗待机这颗片子配合 MRAM 的非易失特性可以做到平时休眠、需要时唤醒写一笔、写完立刻睡回去整体功耗控制得很好。选型的时候我也对比过几个方案。用 STM32 配 MRAM 当然也行SPI 外设更强大但成本和开发复杂度对这个小项目来说有点过剩。用 8 位机配 FRAM 的方案容量和温度范围又不如 MRAM。最后定下来 PIC18F55K42 加 MR25H40CDF是一个在成本、可靠性、开发难度三者之间比较均衡的选择。2. 硬件连接与 SPI 时序细节2.1 引脚连接与硬件设计要点MR25H40CDF 是标准 SPI 从设备引脚定义很清晰CS 片选、SCK 时钟、SI 数据输入、SO 数据输出加上 VDD、VSS还有两个写保护相关的引脚。实际连线的时候我把它接到 PIC18F55K42 的硬件 SPI 引脚上。PIC18F55K42 的 SPI1 模块默认映射在 RC3 是 SCK、RC4 是 SDI、RC5 是 SDO片选我用的是普通 GPIO比如 RA5这样控制更灵活。这里有个细节值得说。MRAM 的 CS 引脚一定要接一个上拉电阻典型值 10K目的是保证单片机复位或者上电过程中 CS 处于高电平避免总线上出现误操作。我早期有一版板子忘了加上拉结果上电瞬间偶尔会丢一笔数据查了很久才发现是 CS 在上电时被拉低了一下MRAM 误以为收到了命令。这个坑很隐蔽因为不是每次都复现。电源部分MRAM 的 VDD 和 VSS 之间要放一个 0.1uF 的陶瓷去耦电容尽量靠近芯片引脚。如果板子上还有其他高速器件建议再并一个 1uF 的。SPI 的走线尽量短SCK 这根时钟线尤其要注意如果走线长、频率高容易产生过冲和振铃可以在源端串一个 22 到 33 欧姆的电阻做阻抗匹配。我实测在 20MHz 时钟下串 33 欧姆之后波形干净很多。2.2 SPI 模式选择与时钟配置MR25H40CDF 支持 SPI 模式 0 和模式 3也就是 CPOL 和 CPHA 的组合。模式 0 是时钟空闲低电平、数据在上升沿采样模式 3 是时钟空闲高电平、数据在上升沿采样。两种都能用我一般选模式 0因为大部分单片机的默认配置就是模式 0省得改。时钟频率方面MR25H40CDF 最高支持 40MHz但实际能跑多快取决于你的 PCB 布线和单片机的 SPI 分频。PIC18F55K42 的系统时钟如果是 64MHzSPI 分频后可以到 16MHz 或者 32MHz。我建议先用一个保守的频率比如 8MHz 或者 10MHz把功能调通再逐步往上提。因为 SPI 读的时候从设备要在时钟沿把数据推出来主机要在下一个沿采样中间留给信号传播的时间就是半个时钟周期。频率越高这个时间窗口越窄布线不好就容易读错。计算一下如果 SPI 时钟是 20MHz半个周期是 25ns。信号在 PCB 上传播速度大约是 15cm/ns25ns 能走 3.75 米看起来绰绰有余。但实际还要算上芯片的输入输出延迟、驱动能力、寄生电容导致的上升沿变缓。所以理论值只能参考实际还是以波形和误码率为准。2.3 命令集与读写时序拆解MR25H40CDF 的命令集不复杂常用的就几条。读数据是 0x03写数据是 0x02写状态寄存器是 0x01读状态寄存器是 0x05写使能是 0x06写禁止是 0x04。还有读识别码之类的命令调试的时候可以用来确认芯片是否正常。读操作的时序是这样的CS 拉低发送 0x03接着发送 24 位的地址因为 4Mbit 容量需要 19 位地址但芯片按 24 位也就是 3 字节来传高位补零。地址发完之后芯片就开始在 SO 线上输出数据每个时钟沿出一个 bit主机连续读就行。读操作可以一直读下去地址会自动递增直到 CS 拉高结束。写操作稍微多一步。因为 MRAM 有写保护机制写之前必须先发写使能命令 0x06把内部的写使能锁存器置位。然后 CS 拉低发 0x02发地址发数据。数据可以连续写地址自动递增。写完 CS 拉高芯片内部开始真正的写入。这里要注意MRAM 的写入虽然快但也不是瞬间完成写完最后一个字节到 CS 拉高之后需要等一小段时间具体看数据手册里的 tWR 参数一般是几十纳秒级别。如果写完立刻又发命令可能会被忽略。稳妥的做法是写完读一下状态寄存器确认写使能锁存器已经复位或者干脆延时一小会儿。3. 软件实现从底层驱动到数据管理3.1 底层 SPI 驱动封装软件这块我习惯分三层来做最底层是 SPI 收发字节的函数中间层是 MRAM 的命令封装最上层是业务数据管理。这样分层的好处是换芯片或者换单片机的时候改动量最小。底层 SPI 收发用 PIC18F55K42 的硬件 SPI 模块核心就是往 SSP1BUF 寄存器写数据然后等 BF 标志位置位再从 SSP1BUF 读回来。这里有个 PIC 的老规矩读 SSP1BUF 的时候实际上是在读 SSPSR 移位寄存器的内容写 SSP1BUF 是写到 SSP1SR。所以收发要分开操作。下面是一段典型的收发函数uint8_t spi_transfer(uint8_t data) { SSP1BUF data; while (!PIR3bits.SSP1IF); // 等待传输完成 PIR3bits.SSP1IF 0; return SSP1BUF; }这段代码看着简单但有个坑如果 SPI 时钟设得太高而中断优先级又没配好这个 while 循环可能会被其他中断打断导致时序错乱。我的做法是在 SPI 传输期间关掉全局中断或者把 SPI 相关操作放在高优先级中断里。实测下来关中断是最省事的因为一次字节传输在 10MHz 下也就 0.8 微秒对系统实时性影响可以忽略。3.2 MRAM 读写函数实现在底层之上封装 MRAM 的读写。读函数大概长这样void mram_read(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_CS_LOW(); 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); } MRAM_CS_HIGH(); }写函数要多一个写使能的步骤void mram_write(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_CS_LOW(); spi_transfer(0x06); // 写使能 MRAM_CS_HIGH(); MRAM_CS_LOW(); 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]); } MRAM_CS_HIGH(); }注意写使能命令是单独一次 CS 拉低拉高不能和写命令连在一起。我见过有人把 0x06 和 0x02 放在同一次 CS 有效期内发结果写不进去因为芯片在收到写命令的时候会检查写使能锁存器而锁存器是在 CS 上升沿才置位的。这个时序细节数据手册里写得很清楚但容易看漏。3.3 数据分区与磨损均衡的取舍MRAM 虽然寿命近乎无限但也不是说完全不用考虑写入管理。从工程角度我还是建议对存储空间做个简单分区。比如前 4KB 放设备参数和校准数据中间 256KB 放运行日志后面放故障记录和累计统计。分区的好处是逻辑清晰出问题的时候好定位。关于磨损均衡MRAM 其实不太需要因为它的擦写寿命是 10 的 14 次方级别就算每秒写一次也要三百万年才写坏。但如果你用的是 EEPROM 或者 Flash那就必须做。我在这个项目里没做磨损均衡但做了一个环形缓冲的日志管理目的是防止日志写满之后覆盖掉重要数据。环形缓冲的思路是维护一个写指针写到末尾就回到开头同时记录一个序号读的时候按序号排序。这样即使掉电也能根据序号恢复出最新的日志顺序。3.4 掉电保护与数据完整性工业设备最怕的就是写数据写到一半掉电。MRAM 的写入是原子性的吗严格说单次字节写入是原子的但如果你写一个多字节的结构体写到第三个字节掉电了那前两个字节是新数据后面还是旧数据结构就乱了。解决办法有两个一是用双缓冲写的时候先写备份区写完校验通过再更新主区二是加一个状态标志写之前把标志置为正在写写完之后清掉上电时检查这个标志如果发现是正在写就说明上次没写完用备份数据恢复。我一般用第二种因为省空间。具体做法是在数据区前面留一个字节做状态标志0xA5 表示数据有效0x5A 表示正在更新。上电初始化的时候先读这个标志如果是 0x5A就从备份区把数据拷回来。这个机制配合 MRAM 的快速写入实际用下来很稳从来没丢过数据。4. 实测数据与性能对比4.1 读写速度实测我在实验室里用逻辑分析仪抓过波形也写过测试程序跑循环。在 SPI 时钟 10MHz 的情况下连续读 512 字节大概耗时 420 微秒算下来平均每个字节 0.82 微秒和理论值基本吻合。连续写 512 字节大概 450 微秒多出来的时间主要是写使能命令和 CS 切换的开销。如果把时钟提到 20MHz读 512 字节降到 220 微秒左右写降到 240 微秒。再往上提到 32MHz提升就不太明显了因为 CS 切换和命令字节的固定开销占比越来越大。所以我的建议是如果对速度要求不是极致10MHz 到 20MHz 是个甜点区间稳定性和速度兼顾。对比一下 EEPROM同样是 512 字节EEPROM 因为要分页写每页写完还要等 5 毫秒左右的内部写周期总耗时轻松超过 50 毫秒。MRAM 比它快了两个数量级。这个差距在需要高频记录的场景里是质变。4.2 功耗表现功耗这块我做了个简单测试。MRAM 在待机时的电流是微安级别读写时的电流大概几毫安。PIC18F55K42 在 64MHz 全速运行时大概十几毫安休眠时不到一微安。整个系统如果做成平时休眠、定时唤醒记录的模式平均功耗可以压到几十微安用一节纽扣电池能撑很久。具体做法是利用 PIC 的定时器唤醒功能比如每 10 秒唤醒一次读几个传感器写一笔日志到 MRAM然后立刻睡回去。整个唤醒到休眠的过程大概几百微秒占空比极低所以平均电流很小。这里 MRAM 的非易失特性帮了大忙因为它不需要像 SRAM 那样靠备用电池保持数据断电就是断电数据还在。4.3 温度与可靠性测试温度测试我做了高低温循环从 -40 度到 85 度每个温度点保持一小时循环了 20 次。测试内容是反复读写特定地址然后校验数据。结果没有出现任何误码。MRAM 的磁性存储单元对温度的敏感度比电荷存储低这是它的物理优势。可靠性方面我还做了反复上下电测试模拟现场断电。每次上电后读取之前写入的数据校验 CRC。做了大概五千次上下电循环数据全部正确。这个测试主要是验证掉电保护机制和 MRAM 本身的非易失可靠性。实测下来只要硬件设计没问题软件逻辑正确数据完整性是有保障的。5. 常见问题与排查实录5.1 读出来全是 0xFF 或者 0x00这是最常见的现象。如果读出来全是 0xFF通常是 SPI 通信根本没建立起来主机发出去的时钟从设备没响应SO 线一直保持高电平。排查顺序是先确认 CS 有没有正常拉低拉高用示波器看 CS 引脚再确认 SCK 有没有波形频率对不对然后确认 SI 和 SO 有没有接反这个错误很低级但确实经常发生最后确认 MRAM 的供电是否正常VDD 有没有到 3.3V。如果读出来全是 0x00可能是 SO 线被拉低了或者从设备根本没上电。也有可能是 SPI 模式设错了主机在错误的时钟沿采样采到的都是无效数据。这时候把 CPOL 和 CPHA 换一下试试往往能解决。5.2 写入不生效写进去读出来还是旧数据这个问题我遇到过几次。第一次是因为忘了发写使能命令MRAM 默认是写保护的不发 0x06 就写不进去。第二次是因为写使能和写命令之间的 CS 时序不对前面说过写使能必须单独一次 CS 周期。第三次比较隐蔽是因为写完之后立刻读MRAM 内部还在写读回来的是旧数据。解决办法是写完加一个小延时或者读状态寄存器确认写完成。还有一种可能是地址算错了。MR25H40CDF 是 512K 字节地址范围是 0 到 0x7FFFF。如果你传了个超出范围的地址芯片会回绕到开头看起来就像写到了错误的地方。所以地址计算一定要用 32 位变量别用 16 位16 位最大才 65535根本不够用。5.3 高速时钟下数据出错低速没问题一提高 SPI 时钟就出错这种一般是信号完整性问题。先看波形SCK 和数据的上升沿有没有过冲、振铃、台阶。如果有在源端串电阻或者缩短走线。再看 CS 的时序CS 拉低到第一个时钟沿之间要有足够的建立时间CS 拉高到下一个操作之间要有保持时间。这些参数数据手册里都有照着查就行。还有一个容易忽略的点是电源噪声。SPI 高速切换的时候电源上会有尖峰如果去耦电容不够或者离得太远MRAM 可能会误动作。我的经验是每个电源引脚都要有独立的 0.1uF 电容而且尽量靠近引脚地线要短而粗。5.4 常见问题速查表现象可能原因排查方法解决措施读出全 0xFFSPI 未建立、CS 异常、供电缺失示波器看 CS、SCK、VDD检查连线、供电、CS 上拉读出全 0x00SO 被拉低、模式错误检查 SO 线、换 SPI 模式修正模式、检查焊接写入不生效未发写使能、时序错误抓写使能命令波形单独 CS 周期发 0x06高速出错信号完整性、电源噪声看波形过冲、加去耦串电阻、加电容、降频数据错乱地址越界、多字节写掉电检查地址范围、加状态标志用 32 位地址、双缓冲5.5 几个实操心得第一个心得是关于 CS 控制的。我强烈建议用 GPIO 控制 CS而不是用硬件 SPI 模块自动控制的 CS。硬件 CS 虽然省事但时序调整不灵活而且有些单片机的硬件 CS 在两次传输之间会短暂拉高对 MRAM 来说可能被识别成命令结束。用 GPIO 手动控制想什么时候拉低就什么时候拉低想保持多久就保持多久调试起来方便很多。第二个心得是关于调试手段的。我习惯在 MRAM 驱动里加一个自检函数上电的时候往一个固定地址写一个已知模式比如 0xAA55然后读回来比对。如果对不上就说明硬件或者驱动有问题直接报错。这个自检花不了几毫秒但能提前发现很多问题避免设备装到现场之后才发现存储坏了。第三个心得是关于数据校验的。MRAM 本身很可靠但 SPI 传输过程中可能受干扰。我在每个数据块后面加两个字节的 CRC16读的时候校验。如果 CRC 不对就重读一次重读还不对就报错。这个机制增加了一点开销但对工业应用来说数据可信比速度重要。6. 项目扩展与个人体会这套方案做完之后我又在几个衍生项目里复用了它。比如有一个电力监测终端需要记录每小时的电压电流数据一年下来数据量不小MRAM 的 512K 字节刚好够用而且不用担心写坏。还有一个医疗设备要求记录每次操作的日志并且断电后不能丢MRAM 的掉电非易失特性正好满足。如果后续要扩展我觉得有几个方向可以考虑。一是换更大容量的 MRAM比如 16Mbit 的型号这样能存更多日志。二是加一个文件系统把 MRAM 当成一个小磁盘来管理这样上层应用写起来更方便。三是结合 PIC18F55K42 的 DMA让 SPI 传输不占 CPU进一步降低功耗。最后分享一个我在实际调试中的小技巧。如果你怀疑是 SPI 时序问题但又没有逻辑分析仪可以用单片机自己做一个简单的时序抓取。具体做法是把 SPI 的 SCK 和 SO 接到另外两个 GPIO 上用定时器中断去采样虽然精度不高但看个大概的时序关系还是够用的。当然有条件的话还是买个便宜的逻辑分析仪几十块钱的东西能省下大量调试时间。这个项目让我对 MRAM 有了比较深的认识。它的优势不在于容量也不在于价格而在于可靠性和写入特性。在那些对数据完整性要求极高、写入频繁、环境恶劣的场景里MRAM 加一颗合适的单片机是一个很值得考虑的方案。PIC18F55K42 虽然不是性能最强的但它的外设配置和低功耗特性配合 MR25H40CDF 用起来很顺手开发难度也不高适合快速落地。