
1. 项目缘起与方案选型为什么是 MRAM 加 PIC241.1 一个真实的需求场景前阵子接了个工业数据采集终端的活儿设备装在配电柜里环境温度夏天能到 70 度冬天户外柜内能到零下 30 度。需求说起来简单每 100ms 采集一次三相电流电压把关键数据存下来断电不能丢上电要能立刻读出来继续用。但真做起来存储这块把我折腾得够呛。一开始想用 EEPROM比如 24C 系列便宜、简单、I2C 两根线。但算了一下写入频率就放弃了100ms 一次一天就是 86 万次EEPROM 的擦写寿命撑死 100 万次一天多就写废了。换 FRAM 吧寿命够但容量小、价格高而且供货周期不稳定。后来想到 SPI FlashW25Q 系列容量大、便宜但 Flash 有个致命问题——写入前必须擦除整个扇区而且擦除时间长掉电时如果正在擦除整个扇区数据都可能丢。工业现场掉电是家常便饭这个风险不能接受。最后选定了MR25H40CDF这颗 MRAM磁性随机存储器。它的特点很鲜明写入不需要擦除、字节级随机写、写入速度接近 SRAM、擦写寿命几乎无限10^14 次以上、断电数据保持 20 年以上。价格比 EEPROM 贵但比 FRAM 便宜容量 512KB对于存采集数据绰绰有余。主控选了PIC24EP512GU810理由后面细说。1.2 为什么主控选 PIC24EP512GU810PIC24EP 系列是 Microchip 家的 16 位 MCUGU 后缀带 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)容量512KB8MB64KB64KB接口SPISPII2CSPI写入前擦除不需要必须整扇区擦除不需要不需要写入寿命10^14 次10 万次100 万次10^14 次写入速度字节级无延迟页写入 0.7ms擦除 45ms页写入 5ms字节级无延迟掉电风险极低擦除时掉电丢整扇区低极低单价参考中高低低高供货稳定稳定稳定波动从表里能看出来MRAM 的核心优势就是写入不用等、不用擦、不怕掉电。对于工业采集这种高频写入场景这三点直接决定了方案能不能落地。Flash 虽然便宜量大但擦除窗口期的掉电风险是硬伤EEPROM 寿命不够FRAM 什么都好就是贵且供货不稳。MRAM 卡在中间性价比最合适。提示MR25H40CDF 是 Everspin 家的产品型号里的 40 代表 4Mbit也就是 512KB。注意别和 MR25H256256Kbit搞混容量差 16 倍。2. 硬件设计与 SPI 总线布局2.1 MR25H40CDF 的引脚与电气特性MR25H40CDF 是 8 引脚 SOIC 封装引脚定义很标准CS片选低有效SO数据输出MISOSI数据输入MOSISCK时钟WP写保护低有效HOLD保持低有效VDD电源2.7V 到 3.6VVSS地这里有两个容易踩坑的引脚WP 和 HOLD。WP 拉低时芯片进入写保护所有写操作被忽略HOLD 拉低时暂停当前通信释放后继续。很多新手直接把这两个脚悬空结果发现写不进去数据查半天查不出来。我的做法是WP 通过 10K 电阻上拉到 VDDHOLD 也上拉到 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。我选的是模式 2CKP1, CKE0实测下来在长走线时抗干扰更好。如果你用模式 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 的指令集很简洁常用的就几条指令编码功能WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据读写时序的核心逻辑写操作拉低 CS - 发送 WREN0x06- 拉高 CS - 拉低 CS - 发送 WRITE0x02- 发送 24 位地址 - 发送数据 - 拉高 CS。读操作拉低 CS - 发送 READ0x03- 发送 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 配置读全 0xFFMISO 断路量通断补焊或换芯片读全 0x00CS 常低量 CS 电平检查 CS 控制代码高温失败时序余量不足降温测试降低 SPI 时钟偶发错误电源纹波大示波器看 VDD加去耦电容DMA 数据错缓冲区被改检查代码用双缓冲4.5 几个独家避坑技巧技巧一写入后立即回读验证。关键数据写入后马上读回来对比不一致就重写。这个习惯帮我抓到了好几次偶发故障。技巧二CS 线上加小电容。在 CS 和地之间并一个 100pF 电容能滤掉高频毛刺防止误触发。但电容不能太大否则边沿变缓影响时序。技巧三MRAM 初始化时先读设备 ID。虽然 MR25H40CDF 没有标准 ID 寄存器但可以读已知地址的配置数据验证通信正常。如果读出来全是 0xFF说明硬件有问题早点发现比运行时出错好。技巧四循环队列加磨损均衡。虽然 MRAM 寿命几乎无限但养成好习惯没坏处。我的做法是写满一圈后整个区域擦除重写MRAM 不需要擦除直接覆盖避免长期只写同一区域。技巧五掉电检测配合写入。如果设备有掉电检测电路检测到掉电时立即把关键数据写入 MRAM然后进入低功耗模式。MRAM 写入快几毫秒就能完成比 Flash 靠谱得多。5. 性能实测与优化建议5.1 实测数据我在实验室和现场都做了测试数据如下测试项条件结果单字节写入20MHz SPI1.2us32 字节记录写入20MHz SPI38us连续写入 1000 条20MHz SPI38ms单字节读取20MHz SPI0.8us32 字节记录读取20MHz SPI26us高温 85 度写入20MHz SPI偶发失败高温 85 度写入10MHz SPI稳定低温 -40 度写入20MHz SPI稳定掉电数据保持断电 24 小时数据完整从数据看20MHz 下写入一条 32 字节记录只要 38us100ms 周期完全够用。高温下降频到 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 测试页写入 5ms100ms 周期内写一条够用但寿命只有 100 万次一天 86 万次一天多就废了。MRAM 寿命 10^14 次按 100ms 一次算能写 3 亿年。这些对比数据让我更确信选型没错。工业场景要的是可靠不是便宜。多花几块钱买 MRAM省的是现场故障的麻烦。6. 项目收尾与个人体会这个项目从选型到量产花了三个月中间踩了不少坑也积累了一些经验。MRAM 加 PIC24 的组合在工业采集场景里算是比较稳妥的方案。MRAM 的字节级写入和无限寿命解决了高频写入和掉电风险PIC24 的 SPI 和 DMA 资源保证了实时性和低 CPU 占用。如果你也在做类似的项目我的建议是存储选型别只看价格算算写入频率和寿命算算掉电风险综合下来 MRAM 往往是最优解。SPI 配置别怕麻烦示波器多看几眼时序比事后调试省时间。PCB 布局别省事去耦电容和端接电阻该加就加工业环境的干扰比你想象的严重。最后分享一个小技巧MRAM 的 WP 引脚可以接一个 GPIO正常运行时拉高需要保护配置数据时拉低。这样既能写数据又能防止误改配置一举两得。我在现场调试时这个设计帮我避免了好几次误操作导致的数据丢失。