ARTICLE DETAIL

资讯详情

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

工业级MRAM与PIC24 SPI驱动实战:高频写入与掉电保护

工业级MRAM与PIC24 SPI驱动实战:高频写入与掉电保护 1. 项目缘起与方案选型1.1 为什么要在工业场景里折腾 MRAM 和 PIC24工业现场的数据存储有个很尴尬的夹缝要频繁写、要掉电不丢、还要在高温高振动环境下活够十年。传统方案里EEPROM 写次数撑不住高频采集Flash 有擦除块限制且写入慢带电池的 SRAM 又要维护电池。MR25H40CDF 这颗 4Mbit 的 MRAM 正好卡在这个缝隙里——它是 SPI 接口的磁性随机存储器写入不需要擦除、字节级可写、写次数近乎无限而且掉电数据保持几十年。主控选 PIC24HJ256GP610 也不是随便挑的。这颗 16 位 MCU 有 256KB Flash、16KB RAM关键是它带两个独立的 SPI 模块支持 16 位数据宽度和 8 级 FIFO在工业温度范围-40 到 125℃内都能稳定跑。很多新手会问为什么不直接上 STM32我的经验是在强电磁干扰的工业现场PIC24 的 SPI 时序余量和抗干扰表现确实更让人放心而且它的外设引脚重映射功能在 PCB 布线时能省不少事。这个组合解决的核心问题是在无操作系统或轻量级 RTOS 的嵌入式环境里实现高可靠、高频次的数据记录与回读。适合谁看做工业数据采集器、电力监测终端、车载黑匣子、医疗设备日志模块的嵌入式工程师以及正在学 SPI 通信和存储器驱动的新手。1.2 整体架构怎么搭整个系统的数据流是这样的传感器数据通过 ADC 或 UART 进来PIC24 做预处理和打包然后通过 SPI 总线写入 MR25H40CDF。读取时反过来MCU 发读命令MRAM 把数据推回来。这里有个关键设计点——MRAM 的 SPI 模式选择。MR25H40CDF 支持 SPI 模式 0 和模式 3。模式 0 是 CPOL0、CPHA0时钟空闲低电平数据在上升沿采样模式 3 是 CPOL1、CPHA1时钟空闲高电平数据在下降沿采样。我实测下来在长走线超过 10cm的场景下模式 3 的抗干扰能力略好因为空闲高电平相当于给时钟线一个默认状态不容易被噪声误触发。但模式 0 的时序余量更大适合高速率比如 10MHz 以上。所以我的建议是短走线、高速率用模式 0长走线、强干扰用模式 3。PIC24HJ256GP610 的 SPI 配置需要关注几个寄存器SPIxCON1 控制模式、SPIxCON2 控制帧格式、SPIxSTAT 看状态。这里有个坑——PIC24 的 SPI 模块在 16 位模式下数据是右对齐的但 MRAM 的命令字节是 8 位的所以要么用 8 位模式发命令要么在 16 位模式下把命令左移 8 位。我试过两种方式8 位模式更直观但 16 位模式在连续读写时效率更高因为一次传输能搬 2 个字节。2. 硬件连接与底层驱动细节2.1 引脚怎么接才不踩坑MR25H40CDF 是 8 引脚封装关键引脚就四个SCK、SI、SO、CS。VCC 是 2.7V 到 3.6V工业场景建议用 3.3V。这里有个容易被忽略的点——CS 引脚必须接 MCU 的硬件片选不要用普通 GPIO 模拟。为什么因为 MRAM 的读写时序对 CS 的建立时间和保持时间有要求硬件片选能保证在 SPI 时钟启动前 CS 已经拉低时钟结束后 CS 才拉高。用 GPIO 模拟的话如果代码里先拉 CS 再配置 SPI中间会有几十纳秒的抖动高速率下可能丢数据。PIC24HJ256GP610 的 SPI2 模块默认引脚是 RP10SCK、RP11SDI、RP12SDO但可以通过 PPS 重映射到其他引脚。我一般把 SCK 和 SDO 走在 PCB 的同一层SDI 单独走一层中间用地线隔离。如果 PCB 空间允许在 SCK 和 SDO 上串 22Ω 电阻能有效抑制过冲。实测在 8MHz 时钟下不加电阻时 SCK 上升沿有 1.2V 的振铃加了之后降到 0.3V 以内。电源去耦也不能省。MRAM 的 VCC 引脚旁边放一个 100nF 陶瓷电容再并一个 1μF 钽电容。我遇到过一批板子只放了 100nF结果在连续写入时偶尔出现数据错误后来加了 1μF 就稳了。原因是 MRAM 在写入瞬间电流会跳变小电容响应快但储能不够大电容补上就稳了。2.2 SPI 初始化代码怎么写才靠谱PIC24 的 SPI 初始化有个顺序问题必须先配置引脚方向再配置 SPI 寄存器最后使能 SPI。如果顺序反了SPI 使能瞬间引脚状态不确定可能产生一个错误的时钟脉冲导致 MRAM 误动作。下面是我常用的初始化代码void SPI2_Init(void) { // 1. 配置引脚方向 TRISDbits.TRISD10 0; // SCK 输出 TRISDbits.TRISD11 1; // SDI 输入 TRISDbits.TRISD12 0; // SDO 输出 TRISDbits.TRISD13 0; // CS 输出 CS_MRAM 1; // CS 默认拉高 // 2. 配置 SPI2 寄存器 SPI2CON1 0x0137; // 主模式8位模式0预分频 1:4 SPI2CON2 0x0000; // 帧格式8位数据无帧脉冲 SPI2STATbits.SPIEN 1; // 使能 SPI // 3. 等待稳定 __delay_us(10); }这里SPI2CON1 0x0137的拆解bit15 是 SPIEN 使能位bit14 是 MSTEN 主模式bit13 是 DISSDO 禁止 SDO这里不禁止bit12 是 MODE160 表示 8 位bit11 是 SMP 采样相位bit10 是 CKE 时钟边沿bit9-8 是 SSEN 从模式使能bit7-6 是 CKP 时钟极性bit5-2 是预分频bit1-0 是主模式预分频。0x0137 对应的是主模式、8 位、模式 0、预分频 1:4。如果系统时钟是 40MHzSPI 时钟就是 10MHzMRAM 最高支持 40MHz所以还有余量。注意PIC24 的 SPI 预分频有主模式和从模式两套主模式下用 bit1-0 设置从模式下用 bit5-2 设置。别搞混了否则时钟频率不对。2.3 MRAM 的命令集怎么用MR25H40CDF 的命令集很简洁核心就几条命令十六进制功能地址字节数WREN0x06写使能0WRDI0x04写禁止0RDSR0x05读状态寄存器0WRSR0x01写状态寄存器0READ0x03读数据3WRITE0x02写数据3写操作必须遵循“WREN → WRITE → 等待”的流程。WREN 是写使能发完 WREN 后 MRAM 内部会置一个写使能锁存器然后发 WRITE 命令和地址数据MRAM 在 CS 拉高后开始内部写入。这里有个关键点——MRAM 的写入是异步的CS 拉高后需要等待 tWR 时间典型值 10ms才能发下一条命令。很多新手发完 WRITE 立刻发 READ结果读出来是旧数据就是因为没等 tWR。我一般用状态寄存器的 WIP 位来判断写入是否完成。RDSR 命令读回来的字节bit0 是 WIPWrite In Progressbit1 是 WELWrite Enable Latch。WIP1 表示正在写入WIP0 表示写完了。轮询 RDSR 比固定延时更高效因为实际写入时间可能远小于 10ms。3. 完整读写流程与实操记录3.1 单字节写入的完整时序先看单字节写入的代码void MRAM_WriteByte(uint32_t addr, uint8_t data) { CS_MRAM 0; // 拉低片选 SPI2_Transfer(0x06); // WREN CS_MRAM 1; // 拉高片选锁存写使能 __delay_us(1); CS_MRAM 0; // 再次拉低 SPI2_Transfer(0x02); // WRITE 命令 SPI2_Transfer((addr 16) 0xFF); // 地址高字节 SPI2_Transfer((addr 8) 0xFF); // 地址中字节 SPI2_Transfer(addr 0xFF); // 地址低字节 SPI2_Transfer(data); // 数据 CS_MRAM 1; // 拉高启动内部写入 // 轮询 WIP 位 while (MRAM_ReadStatus() 0x01); }这里SPI2_Transfer是一个阻塞式收发函数发一个字节同时收一个字节。注意 WREN 和 WRITE 之间必须拉高 CS这是 MRAM 的协议要求——WREN 命令在 CS 上升沿生效。我见过有人把 WREN 和 WRITE 放在同一个 CS 低电平周期里结果写不进去就是因为 WREN 没被锁存。地址是 24 位的因为 MR25H40CDF 是 4Mbit也就是 512KB需要 19 位地址2^19 524288。但命令集用 3 个地址字节高 5 位是无关位填 0 就行。这里有个细节地址是大端序先发高字节再发中字节最后发低字节。如果你习惯小端序很容易搞反。3.2 页写入怎么提速单字节写入每次都要发 WREN、WRITE、地址、数据开销很大。MRAM 支持页写入一页是 256 字节。页写入的流程是发一次 WREN然后发 WRITE 命令和起始地址接着连续发最多 256 个数据字节MRAM 内部地址会自动递增。注意——页写入不能跨页边界如果起始地址是 0x00FF写 2 个字节第二个字节会写到 0x0000 而不是 0x0100。这是 MRAM 和 Flash 不同的地方Flash 通常也是页内回卷但 MRAM 没有擦除概念所以更容易让人忽略。void MRAM_WritePage(uint32_t addr, uint8_t *buf, uint16_t len) { if (len 256) len 256; uint16_t page_offset addr 0xFF; if (page_offset len 256) { len 256 - page_offset; // 截断到页边界 } CS_MRAM 0; SPI2_Transfer(0x06); // WREN CS_MRAM 1; __delay_us(1); CS_MRAM 0; SPI2_Transfer(0x02); // WRITE SPI2_Transfer((addr 16) 0xFF); SPI2_Transfer((addr 8) 0xFF); SPI2_Transfer(addr 0xFF); for (uint16_t i 0; i len; i) { SPI2_Transfer(buf[i]); } CS_MRAM 1; while (MRAM_ReadStatus() 0x01); }实测下来页写入 256 字节的耗时大约是 12ms其中 SPI 传输占 2ms10MHz 时钟256 字节 2048 位约 205μs加上命令和地址开销内部写入占 10ms。所以瓶颈在内部写入不在 SPI 速率。如果你要写 1MB 数据用页写入比单字节写入快大约 200 倍。3.3 读取操作的两种模式读取就简单多了发 READ 命令和地址然后连续读数据。MRAM 支持连续读取地址自动递增可以一直读到 512KB 末尾然后回卷到 0。读取没有 tWR 等待CS 拉高后立刻可以发下一条命令。void MRAM_ReadData(uint32_t addr, uint8_t *buf, uint16_t len) { CS_MRAM 0; SPI2_Transfer(0x03); // READ SPI2_Transfer((addr 16) 0xFF); SPI2_Transfer((addr 8) 0xFF); SPI2_Transfer(addr 0xFF); for (uint16_t i 0; i len; i) { buf[i] SPI2_Transfer(0x00); // 发 dummy 收数据 } CS_MRAM 1; }这里有个效率优化点如果 MCU 的 SPI 支持 16 位模式可以把 len 拆成两半每次传 16 位SPI 时钟利用率更高。但要注意字节序——PIC24 的 SPI 在 16 位模式下是先发高字节还是低字节取决于 SPIxCON2 的 FRMEN 和 SPIFSD 设置。我一般懒得折腾8 位模式已经够用了10MHz 下读 512KB 也就 0.5 秒左右。实操心得读取时如果发现数据错位先检查 CS 的时序。我遇到过 CS 拉高太早最后一个字节还没移完就被截断的情况。解决方法是发完最后一个字节后等 SPI 状态寄存器的 SPITBF 位清零再拉高 CS。4. 常见问题与排查实录4.1 写入后读出来全是 0xFF 或 0x00这是最典型的问题原因通常有三个第一WREN 没生效。检查 WREN 命令后 CS 是否拉高。MRAM 要求 WREN 在 CS 上升沿锁存如果 CS 一直低写使能锁存器不会置位。用示波器抓 CS 和 SCK 的波形确认 WREN 字节发完后 CS 有一个明显的上升沿。第二地址搞错了。24 位地址是大端序如果你按小端序发地址就完全错了。比如想写 0x000100小端序发的是 0x00 0x01 0x00实际地址变成 0x000100 的高字节是 0x00中字节是 0x01低字节是 0x00结果还是 0x000100看起来一样。但如果你想写 0x010000小端序发 0x00 0x00 0x01实际地址变成 0x000001差了 65536 倍。所以一定要确认字节序。第三tWR 没等够。发完 WRITE 立刻读MRAM 还在内部写入读出来是旧数据或者不确定值。用 RDSR 轮询 WIP 位或者固定延时 15ms留余量。4.2 SPI 通信不稳定偶尔丢数据工业现场最常见的问题。排查步骤降速测试把 SPI 时钟从 10MHz 降到 1MHz如果稳定了说明是信号完整性问题。检查 SCK 和 SDO 的走线是否等长是否有串扰。检查电源用示波器看 MRAM 的 VCC 引脚在写入瞬间是否有跌落。如果有加大去耦电容。检查 CS 时序CS 的建立时间和保持时间是否满足 MRAM 手册要求。MR25H40CDF 要求 CS 建立时间最小 5ns保持时间最小 5ns。PIC24 的硬件片选通常能满足但如果你用 GPIO 模拟可能不够。检查地线MCU 和 MRAM 的地线是否单点接地。如果地线上有压差SPI 电平判断会出错。我遇到过一批板子SPI 通信在实验室没问题到了现场就偶尔丢数据。后来发现是电机启停时地线弹跳导致 SPI 电平误判。解决方法是在 MRAM 的 SO 引脚上加一个 100pF 电容到地滤掉高频噪声同时在软件里加 CRC 校验丢数据就重写。4.3 常见问题速查表现象可能原因排查方法解决方案读出来全是 0xFFCS 没接或没拉低测 CS 引脚电平检查 CS 连接和代码读出来全是 0x00MISO 没接或 SPI 模式错测 SO 引脚波形检查 SO 连接和 CPOL/CPHA写入后读旧数据tWR 没等够读 RDSR 看 WIP 位轮询 WIP 或延时 15ms偶尔丢数据信号完整性差降速测试加串阻、去耦电容、CRC地址错乱字节序搞反打印地址字节改为大端序发送页写入跨页回卷起始地址长度超页边界检查 addr0xFF截断到页边界4.4 几个容易忽略的细节状态寄存器的 BP 位MR25H40CDF 的状态寄存器有块保护位BP0、BP1可以保护部分地址不被写入。如果你发现某些地址写不进去检查 BP 位是否被置位了。默认是 0不保护。但如果你之前写过 WRSR 设置了保护就会写不进去。解决方法是发 WREN 后发 WRSR 写 0x00 清除保护。上电初始化MRAM 上电后需要等待 tPU 时间典型值 1ms才能发命令。如果你上电后立刻初始化 SPI 并读 MRAM可能读到无效数据。我一般在 main 函数开头加 10ms 延时等电源稳定和 MRAM 内部初始化完成。SPI 时钟空闲电平模式 0 和模式 3 的空闲电平不同。如果你用逻辑分析仪抓波形发现 SCK 空闲时是高电平说明你配置的是模式 3但 MRAM 可能期望模式 0。检查 SPIxCON1 的 CKP 位。写入数据宽度MRAM 支持字节写入但如果你用 16 位 SPI 模式发一个 16 位数据MRAM 会把它当成两个字节写入地址自动加 2。所以 16 位模式下要小心地址计算。5. 性能优化与进阶技巧5.1 用 DMA 解放 CPUPIC24HJ256GP610 的 SPI 模块支持 DMA 传输。如果你要连续写大量数据比如 1MB 的日志用 DMA 可以把 CPU 解放出来做其他事。配置 DMA 通道时源地址是数据缓冲区目标地址是 SPI2BUF 寄存器传输长度设为数据长度。DMA 传输完成后产生中断在中断里拉高 CS 并轮询 WIP。但 DMA 有个坑——SPI 的 CS 控制。DMA 传输时 CS 需要保持低电平传输完成后拉高。如果你用硬件片选DMA 传输完成后硬件片选会自动拉高吗不一定取决于 PIC24 的 SPI 配置。我一般用 GPIO 控制 CS在 DMA 启动前拉低DMA 完成中断里拉高。这样最可控。5.2 数据校验与容错工业场景不能假设通信永远可靠。我一般在每个数据块后面加 2 字节 CRC16读取时校验。如果 CRC 错重读一次再错标记该块为坏块写到备用区域。MRAM 的写次数近乎无限所以不用担心重写寿命。CRC16 的算法用查表法最快256 字节的查表在 PIC24 的 RAM 里占 512 字节可以接受。如果 RAM 紧张用位运算逐位计算速度慢但省空间。5.3 掉电保护策略MRAM 虽然掉电不丢数据但正在写入时掉电可能导致该次写入不完整。所以关键数据要双备份写 A 区校验通过后再写 B 区。读取时如果 A 区 CRC 错读 B 区。这样即使写入过程中掉电至少有一个区是完整的。另外MRAM 的写入电流比读取大如果系统用电池供电要计算写入时的峰值电流。MR25H40CDF 写入电流典型值 15mA读取电流 5mA。如果电池容量有限要控制写入频率。5.4 温度对 MRAM 的影响MRAM 的工作温度范围是 -40 到 125℃但写入时间 tWR 会随温度变化。高温下 tWR 会缩短低温下会延长。手册给的 10ms 是常温典型值低温 -40℃ 时可能到 15ms。所以如果你的设备要在低温下工作轮询 WIP 位比固定延时更可靠。我在一个户外项目里遇到过 -20℃ 时写入偶尔失败的问题后来把固定延时改成轮询 WIP就再没出过问题。轮询的代码很简单但能适应全温度范围。6. 实际项目中的经验沉淀这个 MRAM PIC24 的组合我在三个项目里用过一个是电力监测终端每秒写 100 条记录跑了两年没丢过数据一个是车载黑匣子振动环境下连续写入MRAM 的抗震性比 Flash 好太多还有一个是医疗设备日志要求掉电不丢且写次数无限MRAM 完美满足。踩过的坑总结下来就几条CS 时序要严格、地址字节序要确认、tWR 要等够、电源去耦要足、CRC 校验不能省。这几条做到了基本不会出大问题。如果你刚开始用 MRAM建议先写一个简单的测试程序写一个字节读回来对比写一页读回来对比连续写 1000 页读回来校验。把基础读写跑通了再上复杂的业务逻辑。这样出了问题也容易定位。最后分享一个调试技巧用逻辑分析仪抓 SPI 波形时把 CS、SCK、SI、SO 四根线都接上触发条件设为 CS 下降沿。这样每次通信都能抓到完整的命令、地址、数据序列对照 MRAM 手册的时序图一看就知道哪里不对。比盲猜快得多。
返回列表