ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PIC32MZ 与 MR25H40CDF MRAM 的 SPI 驱动设计与掉电安全存储实战

PIC32MZ 与 MR25H40CDF MRAM 的 SPI 驱动设计与掉电安全存储实战 1. 项目缘起与方案选型思路工业现场的数据记录仪、PLC 扩展模块、电力监测终端这类设备有一个共同的痛点频繁掉电。产线急停、配电柜跳闸、电池供电设备电量耗尽这些场景下如果正在写入的关键参数比如校准系数、累计电量、故障快照没写完就断电轻则数据丢失重则设备上电后跑飞。我手上这个项目就是给一台工业环境监测终端做数据存储改造主控用的是PIC32MZ1024EFF144存储介质从原来的 SPI Flash 换成了MR25H40CDF这颗 MRAM。先说清楚这两颗料是什么定位。PIC32MZ1024EFF144 是 Microchip 的 32 位 MCUMIPS M-Class 内核主频能跑到 200MHz1MB Flash、512KB RAM144 脚封装外设资源相当全SPI 模块有好几路。MR25H40CDF 是 Everspin 的 4Mbit512KB串行 MRAMSPI 接口重点在于它是磁性存储不是电荷存储。这一点决定了它和 Flash、EEPROM 的本质区别。为什么不用 Flash我踩过的坑很直接SPI Flash 写之前要擦除擦除是按扇区来的通常 4KB写一个字节也得走读-改-擦-写整个流程一次写操作动辄几毫秒到几十毫秒。这期间断电整个扇区的数据可能全废。而且 Flash 擦写寿命一般 10 万次做秒级数据记录的话几个月就能把某个扇区写穿。EEPROM 好一点字节级可写但写入速度慢毫秒级寿命也就 100 万次量级而且容量小。MRAM 的机制是磁隧道结MTJ靠电子自旋方向存储数据写入是纳秒级的没有擦除概念字节直接覆盖写寿命标称 10^14 次以上基本可以认为无限。MR25H40CDF 的 SPI 时钟能跑到 40MHz理论连续写入吞吐很可观。这就是我选它的核心理由掉电安全 近乎无限的擦写寿命 字节级随机写。提示MRAM 不是更快的 Flash它的价值在于像 RAM 一样写像 Flash 一样不丢。理解这一点后面的驱动设计思路才顺。方案的整体架构是PIC32MZ 通过硬件 SPI 挂 MR25H40CDFMCU 内部 RAM 做写缓存MRAM 做掉电非易失存储区同时用 MCU 的片上 Flash 存固件和只读配置。数据分两类一类是高频采集数据秒级直接写 MRAM一类是低频配置参数也放 MRAM但加 CRC 校验。下面把设计、驱动、实操、排错完整拆一遍。2. 硬件连接与 SPI 模式的关键细节2.1 引脚分配与片选策略PIC32MZ1024EFF144 有多个 SPI 模块SPI1~SPI6我选的是 SPI2因为它对应的引脚在 144 脚封装上布线最顺。MR25H40CDF 是标准 8 脚 SOIC 封装引脚定义CS、SO、SI、SCK、VDD、VSS、WP写保护、HOLD保持。连接关系如下表MR25H40CDF 引脚PIC32MZ 引脚说明CSRB2GPIO 软件片选低有效必须软件控制SOMISOSDI2 / RB8SPI2 数据输入SIMOSISDO2 / RB9SPI2 数据输出SCKSCK2 / RB14SPI2 时钟WP上拉到 VDD不用硬件写保护HOLD上拉到 VDD不用暂停功能VDD3.3V供电VSSGND地这里有个关键选择硬件片选还是软件片选。PIC32MZ 的 SPI 模块支持硬件片选SS 引脚自动拉低拉高但我在这个项目里坚持用软件片选。原因有两个第一MRAM 的时序要求 CS 在 SCK 稳定后再拉低硬件片选在某些配置下时序不好控第二软件片选方便我在一次事务里连续发多个命令字节不用担心中间 CS 被自动抬起。实测下来软件片选配合手动控制 CS时序最干净。注意CS 拉低到第一个 SCK 上升沿之间要留至少 5ns 的建立时间MR25H40CDF 手册里叫 tSLCH。PIC32MZ 跑 200MHz 时一个指令周期才 5ns所以拉低 CS 后最好插一两个 NOP 再启动 SPI 传输别图省事。2.2 SPI 模式与时钟配置MR25H40CDF 支持 SPI Mode 0 和 Mode 3。Mode 0 是 CPOL0、CPHA0时钟空闲低电平数据在上升沿采样。我选 Mode 0因为 PIC32MZ 的 SPI 在 Mode 0 下配置最直观而且大部分逻辑分析仪默认就是按 Mode 0 抓的调试方便。时钟频率方面MR25H40CDF 最高支持 40MHz。PIC32MZ 的 SPI 时钟来自外设总线时钟PBCLK假设 PBCLK 是 100MHz那分频系数要算一下目标 40MHz分频 100 / 40 2.5取整为 2实际 50MHz 超标或 4实际 25MHz。我最终选分频 4跑 25MHz。为什么不冲 40MHz因为工业现场 PCB 走线长、干扰大25MHz 下眼图很干净40MHz 时偶尔出现位错误。稳定性优先于极限速度这是工业项目的铁律。配置代码基于 Harmony 或直接寄存器操作这里给寄存器级思路// SPI2 配置Mode 025MHz SPI2CON 0; SPI2CONbits.MSTEN 1; // 主机模式 SPI2CONbits.CKP 0; // 空闲低电平 (CPOL0) SPI2CONbits.CKE 1; // 上升沿采样 (CPHA0) SPI2CONbits.SMP 0; // 采样相位主模式下建议 0 SPI2CONbits.ON 1; // 使能 SPI SPI2BRG 3; // 分频 2*(BRG1) 8? 需按实际 PBCLK 核算这里 SPI2BRG 的计算要特别小心。PIC32MZ 的 SPI 波特率公式是F_SCK F_PBCLK / (2 * (SPI2BRG 1))。如果 PBCLK 是 100MHz要得到 25MHz则2*(BRG1) 4BRG 1。我上面写 3 是错的实际要按你的 PBCLK 重新算。这个公式一定要对着数据手册确认不同系列 MCU 的 SPI 分频公式不一样抄错就调不通。2.3 PCB 布线的经验SPI 在 25MHz 下虽然不算高速但工业板子上干扰源多继电器、电机驱动布线不能马虎。我的做法SCK 走线尽量短包地处理两侧铺地并打过孔。MISO/MOSI 尽量等长避免 skew 过大。CS 线远离 SCK防止串扰导致误触发。MRAM 的 VDD 就近放 0.1uF 陶瓷电容再并一个 1uF。如果板子空间允许SCK 串一个 22Ω 电阻做阻抗匹配抑制过冲。这些细节在实验室可能看不出差别但到了现场电磁环境恶劣的地方就是稳定与偶发故障的分界线。3. MRAM 驱动实现与读写时序拆解3.1 命令集与基本操作MR25H40CDF 的命令集很精简核心就几条命令编码作用WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据注意MRAM 的写操作必须先发 WREN否则 WRITE 命令会被忽略。这一点和 EEPROM 类似但和 Flash 不同的是MRAM 不需要擦除命令WRITE 直接覆盖。状态寄存器的 bit0 是 WEL写使能锁存bit1 是 BP0、bit2 是 BP1块保护写之前可以读一下确认。读时序拉低 CS → 发 0x03 → 发 3 字节地址24 位因为 512KB 2^19但 MR25H40 用 3 字节地址格式→ 连续读数据 → 拉高 CS。写时序拉低 CS → 发 0x06WREN→ 拉高 CS → 拉低 CS → 发 0x02 → 发 3 字节地址 → 发数据 → 拉高 CS。注意 WREN 和 WRITE 之间要拉高一次 CS这是很多新手容易漏的地方。3.2 底层读写函数实现先写字节级收发函数这是所有操作的基础static uint8_t spi_transfer(uint8_t data) { SPI2BUF data; while (!SPI2STATbits.SPIRBF); // 等待接收完成 return SPI2BUF; } static void mram_cs_low(void) { LATBbits.LATB2 0; __asm__ volatile (nop; nop;); // 建立时间 } static void mram_cs_high(void) { LATBbits.LATB2 1; }然后是写使能和状态检查static void mram_wren(void) { mram_cs_low(); spi_transfer(0x06); mram_cs_high(); } static uint8_t mram_read_status(void) { uint8_t st; mram_cs_low(); spi_transfer(0x05); st spi_transfer(0xFF); mram_cs_high(); return st; }写数据函数注意地址是 24 位void mram_write(uint32_t addr, uint8_t *buf, uint32_t len) { mram_wren(); // 可选检查 WEL 位 mram_cs_low(); spi_transfer(0x02); 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]); } mram_cs_high(); }读数据函数void mram_read(uint32_t addr, uint8_t *buf, uint32_t len) { mram_cs_low(); spi_transfer(0x03); spi_transfer((addr 16) 0xFF); spi_transfer((addr 8) 0xFF); spi_transfer(addr 0xFF); for (uint32_t i 0; i len; i) { buf[i] spi_transfer(0xFF); } mram_cs_high(); }3.3 写入速度实测与计算25MHz SPI 下每个字节 8 位加上命令和地址开销。写一个字节的实际时间命令 1 字节 地址 3 字节 数据 1 字节 5 字节 40 位。40 位 / 25MHz 1.6us。加上 WREN 的 8 位0.32us和两次 CS 切换开销实际约 2us 一个字节。如果连续写 256 字节一页命令地址 4 字节 数据 256 字节 260 字节 2080 位 / 25MHz 83.2us。这个速度比 Flash 快了一个数量级而且没有擦除等待。实测用逻辑分析仪抓连续写 512 字节耗时约 170us和理论值吻合。实操心得MRAM 写入虽然快但如果你在中断里频繁调用写函数每次都要走 WREN CS 切换开销会累积。我的做法是批量写把要写的数据先在 RAM 里攒够一页比如 64 字节再一次性写下去减少 CS 切换次数。4. 掉电安全设计与数据完整性保障4.1 为什么 MRAM 天然适合掉电场景前面说了 MRAM 写入是纳秒级完成但这里要澄清一个概念MRAM 的写入完成是指数据已经进入存储单元不需要额外的编程时间。Flash 写完数据后还要等内部电荷泵把电荷注入浮栅这期间断电数据就没了。MRAM 没有这个过程SPI 事务结束的那一刻数据就已经稳定存储了。这意味着什么意味着你可以在检测到掉电的瞬间比如电源监测中断触发立刻把关键数据写进 MRAM只要 SPI 事务能跑完数据就安全。我实测过用一个大电容维持 MCU 供电约 5ms足够写完 1KB 数据。4.2 双备份 CRC 的数据结构虽然 MRAM 本身可靠但工业项目不能赌单点。我的数据结构是双备份 CRC 校验typedef struct { uint32_t magic; // 0x4D52414D MRAM uint32_t seq; // 序列号每次写入递增 uint8_t data[56]; // 实际数据 uint16_t crc; // CRC16 校验 } record_t; // 共 64 字节存储布局A 区从 0x00000 开始B 区从 0x10000 开始各占 64KB交替写入。每次写新记录先写 A 区再写 B 区seq 递增。上电时读两个区选 seq 更大且 CRC 正确的那个。如果两个都坏用默认值并报警。CRC16 用查表法实现速度快static const uint16_t crc16_table[256] { ... }; uint16_t crc16(const uint8_t *data, uint32_t len) { uint16_t crc 0xFFFF; while (len--) { crc (crc 8) ^ crc16_table[((crc 8) ^ *data) 0xFF]; } return crc; }4.3 掉电检测与写入时机PIC32MZ 有内置的掉电检测BOR和低压检测LVD。我配置 LVD 在 2.9V 触发中断正常 3.3V 供电中断里立刻执行紧急写入void __ISR(_LVD_VECTOR, IPL7AUTO) lvd_handler(void) { IFS1bits.LVDIF 0; emergency_save(); // 把关键数据写入 MRAM // 不再返回等待断电或复位 while (1); }emergency_save()里只写最关键的几十字节确保在电容放电完毕前完成。这里要注意中断优先级要设到最高IPL7否则可能被其他中断打断导致写不完。注意LVD 中断里不要调用任何可能阻塞的函数比如带 while 等待的 SPI 操作要确保不会死等。如果 SPI 因为某种原因卡住整个紧急保存就失败了。我的做法是给 SPI 等待循环加超时计数超时就放弃至少保证不卡死。5. 常见问题排查与实战避坑记录5.1 读出来全是 0xFF 或 0x00这是最常见的现象。排查顺序先量 CS 电平。如果 CS 一直是高说明片选没拉低检查 GPIO 配置和 LATB 寄存器。量 SCK 有没有波形。没有波形说明 SPI 没使能检查 SPI2CON 的 ON 位。量 MISO 有没有数据。如果 MISO 一直高可能是 MRAM 没供电或者 HOLD 脚没上拉。HOLD 脚悬空会导致芯片进入保持状态不响应。检查 SPI 模式。Mode 0 和 Mode 3 搞反了读出来就是乱的。用逻辑分析仪抓一下看数据在哪个沿变化。我遇到过一次折腾了半天发现是 HOLD 脚忘了上拉芯片一直处于保持状态。MR25H40CDF 的 WP 和 HOLD 脚不用的话必须上拉到 VDD不能悬空。5.2 写入后读出来数据不对大概率是 WREN 没生效。MRAM 的 WRITE 命令要求 WEL 位为 1如果 WREN 和 WRITE 之间 CS 没有正确拉高再拉低WEL 可能被清除。我的建议是每次写之前都发一次 WREN并且用mram_read_status()确认 WEL 位是 1 再写。另一个可能是地址算错了。MR25H40CDF 是 512KB地址范围 0x00000~0x7FFFF。如果你传了超过 0x7FFFF 的地址高位会被忽略数据写到别的地方去了。5.3 高速下偶发位错误25MHz 以上出现偶发错误通常是信号完整性问题。排查用示波器看 SCK 上升沿有没有过冲或振铃。有的话串电阻。看 MISO 的建立时间是否足够。如果 MCU 采样点太靠前可以调整 SPI 的 SMP 位。缩短走线或者降低时钟频率。工业项目里25MHz 够用了没必要冲 40MHz。5.4 常见问题速查表现象可能原因解决方法读出全 0xFFCS 未拉低 / HOLD 悬空检查 GPIOHOLD 上拉读出全 0x00MISO 未连接 / 芯片未供电检查连线与供电写入无效WREN 未生效每次写前发 WREN 并检查 WEL数据错位SPI 模式错误确认 Mode 0CPOL0/CPHA0偶发位错误信号完整性差降频、串阻、缩短走线地址越界地址超过 0x7FFFF检查地址计算掉电丢数据紧急写入未完成提高中断优先级加超时保护5.5 几个独家避坑技巧技巧一上电先做一次全片自检。写一个已知模式比如 0xAA55到几个关键地址读回来比对确认 SPI 通路正常再进入主逻辑。这能避免因为硬件问题导致的莫名其妙故障。技巧二状态寄存器定期读。虽然 MRAM 不需要像 Flash 那样关心磨损但状态寄存器的块保护位如果被误写会导致部分区域写不进去。上电时读一次确认 BP0/BP1 都是 0。技巧三CS 切换加微小延时。在 CS 拉高后、下一次拉低前插一个几微秒的延时或者至少几个 NOP给芯片内部状态机复位的时间。我在 40MHz 下不加延时偶尔会出错加了就稳了。技巧四数据写入用影子区策略。不要直接覆盖当前有效数据而是写到影子区写完校验通过后再更新指针。这样即使写入过程中断电旧数据依然完整。这个策略配合 MRAM 的快速写入几乎可以做到零数据丢失。6. 性能对比与场景扩展思考6.1 MRAM vs Flash vs EEPROM 实测对比指标MR25H40CDF (MRAM)典型 SPI Flash典型 SPI EEPROM写入方式字节直接覆盖扇区擦除后写字节写写入时间~2us/字节擦除 50ms 写 1ms~5ms/字节擦写寿命10^14 次10^5 次10^6 次掉电安全极高低擦除中掉电全丢中容量512KB1MB~16MB64KB~512KB成本较高低中适用场景高频记录、掉电关键固件、日志配置参数从表里能看出来MRAM 的定位很明确不是替代大容量 Flash而是替代那些写入频繁 掉电不能丢的场景。我这个项目里固件还是放 MCU 片上 FlashMRAM 专门存运行数据。6.2 还能怎么扩展这套方案可以往几个方向延加文件系统如果数据量大可以在 MRAM 上跑一个轻量级日志文件系统比如 LittleFS管理多记录。多芯片级联MR25H40CDF 只有 512KB如果不够可以挂多片用不同 CS 区分或者用 SPI 菊花链不过 MRAM 不支持菊花链得用独立 CS。配合 DMAPIC32MZ 的 SPI 支持 DMA大批量读写时用 DMA 可以解放 CPU进一步降低功耗。数据加密工业数据有时需要防篡改可以在写入前做 AES 加密MRAM 存密文。6.3 关于选型的个人体会我在实际使用中发现MRAM 最大的价值不是速度而是设计简化。用 Flash 的时候我得设计磨损均衡、掉电保护、擦除调度代码复杂且容易出 bug。换成 MRAM 后这些逻辑基本可以砍掉直接写就行代码量少了三分之一可靠性反而更高。当然MRAM 贵。512KB 的 MR25H40CDF 单价比同容量 Flash 贵不少。所以我的建议是关键数据用 MRAM大容量日志用 Flash两者配合。别一股脑全上 MRAM成本扛不住也别为了省钱全用 Flash掉电丢数据的代价更大。最后分享一个小技巧调试阶段我会在 MRAM 里划一小块区域专门存调试日志记录每次上电、掉电、异常事件的时间戳和状态。这块区域用最简单的格式不做 CRC就图个快。出问题时读出来一看基本能还原现场。这个习惯帮我定位过好几次偶发故障比单纯看串口打印有用得多。
返回列表