ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

PIC18F86J11与MR25H40CDF的SPI接口设计与工业数据存储实践

2026/10/5 6:18:45 拓冰建站 浏览量
PIC18F86J11与MR25H40CDF的SPI接口设计与工业数据存储实践 1. 项目缘起与方案选型思考工业现场的数据记录仪、PLC 扩展模块、电力监测终端这类设备对存储的要求其实挺苛刻的要频繁写、要掉电不丢、要能在高温高湿和强电磁干扰下稳定跑个十年八年。我手头这个项目就是给一台工业数据采集终端做存储子系统主控用的是 PIC18F86J11存储介质选的是 MR25H40CDF 这颗 4Mbit 的 MRAM。这套组合不是拍脑袋定的是踩过几轮坑之后才收敛下来的方案。先说主控。PIC18F86J11 是 Microchip 8 位 PIC18 家族里偏高端的一颗64 引脚自带 SPI 主控模块工作电压 2.0V 到 3.6V工业级温度范围 -40 到 85 摄氏度。它的 SPI 模块支持主从模式时钟极性、相位可配波特率分频也够用。选它主要是因为项目里已经有大量 5V 兼容的外围电路PIC18 的 IO 在 3.3V 供电下能直接驱动大部分工业传感器省掉一堆电平转换。而且这颗片子抗干扰能力在同类 8 位机里口碑不错之前几个项目跑下来没出过什么幺蛾子。再说存储。MR25H40CDF 是 Everspin 的 4Mbit SPI MRAM磁阻随机存取存储器。它和常见的 EEPROM、NOR Flash、FRAM 最大的区别在于写入不需要擦除、没有写寿命限制、写入速度接近 SRAM、掉电数据保持至少 20 年。这几个特性凑在一起正好命中工业数据记录的痛点。你想想如果用 EEPROM写一个字节要等 5ms 左右的擦写周期一天写一万次就快把寿命耗光了用 NOR Flash 更麻烦得整块擦除还得做磨损均衡。MRAM 就没这些破事直接写写多少次都行掉电也不丢。提示MR25H40CDF 的供电范围是 2.7V 到 3.6V和 PIC18F86J11 在 3.3V 下工作完全匹配不需要额外电平转换。但要注意 MRAM 的 VDD 和 VDDQ 要接在一起去耦电容尽量靠近芯片引脚。方案定下来之后核心工作就三块硬件连接与 PCB 布局、SPI 驱动层编写、上层数据读写逻辑。下面我按实际调试顺序把每一块拆开讲。2. 硬件连接与 PCB 布局的关键细节2.1 引脚连接与信号完整性MR25H40CDF 是 8 引脚 SOIC 封装引脚定义很标准CS 片选、SCK 时钟、SI 数据输入、SO 数据输出、VDD、VSS、WP 写保护、HOLD 保持。和 PIC18F86J11 连接的时候我用的硬件 SPI 模块映射到 RC3 作为 SCK、RC5 作为 SI、RC4 作为 SO片选单独用 RA5 控制。为什么片选不直接用硬件 SPI 的 SS 引脚因为 PIC18 的硬件 SS 在某些配置下会强制把 SPI 从机模式拉进来容易出问题所以干脆用普通 IO 手动拉片选逻辑更清晰。PCB 布局这块有几个坑我踩过。第一次打样的时候SPI 走线走了大概 8 厘米还从一颗 DC-DC 电感旁边穿过去结果读写误码率很高尤其是写操作偶尔会丢数据。后来重新布局把 MRAM 放到主控旁边走线控制在 3 厘米以内SCK 和 SI/SO 之间加了地线隔离问题就没了。SPI 虽然速率不算特别高但在工业环境里信号完整性不能马虎。信号PIC18F86J11 引脚MR25H40CDF 引脚注意事项CSRA5110k 上拉防止上电误触发SCKRC36走线尽量短远离模拟信号SIRC55与 SO 不要平行长距离走线SORC42可加 22 欧姆串联电阻WP接 VDD3不用写保护就拉高HOLD接 VDD7不用保持功能就拉高2.2 电源与去耦MRAM 的写入电流比读取大不少尤其是连续写的时候电源上会有瞬态波动。我在 VDD 引脚旁边放了 0.1uF 和 1uF 两个电容一个滤高频一个滤低频。实测下来如果只放 0.1uF连续写 256 字节的时候示波器能看到 VDD 上有大概 80mV 的纹波偶尔会导致写失败。加了 1uF 之后纹波降到 20mV 以内就稳了。另外MR25H40CDF 的 VDDQ 引脚是专门给 IO 供电的虽然它和 VDD 电压范围一样但最好单独走一根线到 3.3V中间串一个磁珠能进一步隔离数字噪声。这个细节在数据手册里提了一句但很容易被忽略。注意WP 和 HOLD 引脚内部有弱上拉但工业环境下建议外部再拉一个 10k 电阻到 VDD防止干扰导致误进入写保护或保持状态。3. SPI 驱动层编写与底层时序控制3.1 PIC18F86J11 的 SPI 模块初始化PIC18F86J11 的 SPI 模块配置寄存器主要有 SSPCON1、SSPSTAT、SSPADD。我用的配置是主控模式、时钟空闲高电平、数据在时钟上升沿采样也就是 CPOL1CPHA0。MR25H40CDF 支持 SPI 模式 0 和模式 3我选模式 3 是因为它在空闲时 SCK 为高抗干扰稍微好一点。初始化代码大概长这样void SPI_Init(void) { TRISC3 0; // SCK 输出 TRISC5 0; // SI 输出 TRISC4 1; // SO 输入 TRISA5 0; // CS 输出 CS 1; // 片选拉高空闲状态 SSPCON1 0x30; // SSPEN1, CKP1, SSPM0000 (主控模式 Fosc/4) SSPSTAT 0x40; // CKE0, SMP0 (数据在上升沿采样) SSPADD 0x03; // 时钟分频实际速率 Fosc / (4 * (SSPADD1)) }这里 SSPADD 的计算得说一下。假设系统时钟 Fosc 是 16MHzSPI 速率 16MHz / (4 * (31)) 1MHz。MR25H40CDF 最高支持 40MHz 的 SPI 时钟所以 1MHz 完全在范围内而且工业环境下速率低一点更稳。我试过把 SSPADD 设成 0速率跑到 4MHz短距离读写没问题但长线测试时误码率明显上升最后还是降到 1MHz。3.2 MRAM 读写命令与时序MR25H40CDF 的命令集很简洁常用的就几条WREN写使能0x06、WRDI写禁止0x04、READ读数据0x03、WRITE写数据0x02、RDSR读状态寄存器0x05、WRSR写状态寄存器0x01。写操作之前必须先发 WREN这是硬性要求忘了发就写不进去。读操作的时序是拉低 CS发 0x03然后发 24 位地址因为 4Mbit 512K 字节需要 19 位地址但命令格式是 24 位接着连续读出数据读完拉高 CS。写操作类似只是命令换成 0x02而且写完之后要等一个内部写周期虽然 MRAM 写得快但还是要查状态寄存器的 WIP 位确认写完了再发下一条命令。uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; CS 0; SPI_WriteByte(0x03); SPI_WriteByte((addr 16) 0xFF); SPI_WriteByte((addr 8) 0xFF); SPI_WriteByte(addr 0xFF); data SPI_ReadByte(); CS 1; return data; } void MRAM_WriteByte(uint32_t addr, uint8_t data) { CS 0; SPI_WriteByte(0x06); // WREN CS 1; CS 0; SPI_WriteByte(0x02); // WRITE SPI_WriteByte((addr 16) 0xFF); SPI_WriteByte((addr 8) 0xFF); SPI_WriteByte(addr 0xFF); SPI_WriteByte(data); CS 1; while (MRAM_ReadStatus() 0x01); // 等待 WIP 清零 }SPI_WriteByte 和 SPI_ReadByte 就是最基础的收发函数注意收发的时候要等 SSPBUF 满标志位别急着覆盖缓冲区。3.3 批量读写优化单字节读写虽然简单但效率低。MR25H40CDF 支持连续读写地址会自动递增所以批量操作的时候发完起始地址之后可以一直读或写不用每次都发命令。我实测过连续读 256 字节比逐字节读快了将近 3 倍。void MRAM_ReadBuffer(uint32_t addr, uint8_t *buf, uint16_t len) { CS 0; SPI_WriteByte(0x03); SPI_WriteByte((addr 16) 0xFF); SPI_WriteByte((addr 8) 0xFF); SPI_WriteByte(addr 0xFF); for (uint16_t i 0; i len; i) { buf[i] SPI_ReadByte(); } CS 1; }写批量的时候要注意WREN 只需要发一次然后连续写多个字节最后等 WIP 清零。但如果你写跨页了MRAM 不像 Flash 那样有页边界限制它整个地址空间是线性的随便跨这点很省心。实操心得SPI 收发函数里等标志位的时候最好加一个超时计数防止硬件故障导致死循环。我在工业现场遇到过 SPI 时钟被干扰拉死的情况加了超时之后至少能报错恢复。4. 上层数据存储逻辑与掉电保护4.1 数据分区与地址规划4Mbit 的 MRAM 实际可用 512K 字节我把它分成几个区前 4K 字节存系统配置和校准参数中间 500K 字节做循环数据记录最后 8K 字节存日志和故障快照。分区的时候留了一些余量方便以后扩展。循环记录区用的是环形缓冲区结构写指针和读指针都存在配置区里。每次写数据之前先更新写指针写完数据再更新指针这样即使写数据过程中掉电指针也不会乱。MRAM 的写操作是原子的单字节写不会写一半所以只要保证指针更新在数据写完之后就行。分区起始地址大小用途配置区0x000004KB系统参数、校准系数记录区0x01000500KB循环数据记录日志区0x7E0008KB故障日志、快照4.2 掉电检测与数据完整性工业设备最怕掉电丢数据。PIC18F86J11 本身有掉电检测模块可以配置成在电压低于阈值时触发中断。我在中断里做两件事一是把当前写指针和关键状态存到配置区二是把 MRAM 的 CS 拉高防止正在进行的 SPI 操作被中断打断。MRAM 的写入速度很快单字节写大概几十纳秒所以只要掉电检测及时基本不会丢数据。但有个细节要注意掉电中断里不要再做复杂的 SPI 操作因为电压已经在下降了SPI 时钟可能不稳定。我的做法是在正常运行时每写一批数据就更新一次指针掉电中断里只做一个简单的标志位写入这样最稳妥。void __interrupt() ISR(void) { if (PIR1bits.LVDIF) { PIR1bits.LVDIF 0; CS 1; // 立即拉高片选终止 SPI 操作 // 只写一个掉电标志不做复杂操作 MRAM_WriteByte(0x00010, 0xAA); } }4.3 数据校验与恢复虽然 MRAM 很可靠但工业环境里电磁干扰还是可能造成位翻转。我在每条记录后面加了一个 CRC16 校验读的时候先校验再使用。如果校验失败就跳过这条记录继续读下一条。实测下来加了 CRC 之后误码率从万分之一降到了百万分之一以下。CRC 的计算我用的是查表法速度快占用空间也不大。256 字节的表放在程序存储器里PIC18 的 ROM 够用。uint16_t CRC16_Calc(uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; while (len--) { crc (crc 8) ^ crc_table[((crc 8) ^ *data) 0xFF]; } return crc; }注意CRC 表要放在 ROM 里用 const 修饰否则会占用宝贵的 RAM。PIC18F86J11 只有 3936 字节 RAM省着点用。5. 常见问题与排查技巧实录5.1 读写失败排查流程调试过程中遇到读写失败我一般按这个顺序排查先看电源纹波再看 SPI 波形然后查片选时序最后查命令序列。下面这个表是我整理的问题速查表覆盖了大部分常见情况。现象可能原因排查方法解决方法读出来全是 0xFFCS 没拉低或 SPI 没使能示波器看 CS 和 SCK检查 SSPCON1 的 SSPEN 位写不进去没发 WREN 或 WP 拉低读状态寄存器写前发 0x06WP 接 VDD数据偶尔出错电源纹波或走线干扰示波器看 VDD 和 SCK加去耦电容缩短走线连续写失败没等 WIP 清零读状态寄存器 bit0加 while 等待循环上电后第一次读失败上电时序问题看 VDD 上升时间加延时或复位 MRAM5.2 几个容易忽略的细节第一个是上电时序。MR25H40CDF 要求 VDD 稳定后至少等 100us 才能发命令我一开始没加延时上电后第一次读总是失败后来在初始化里加了 1ms 延时就好了。第二个是 SPI 模式匹配。MRAM 支持模式 0 和模式 3但主控的 CPOL 和 CPHA 必须和它一致。我试过用模式 1 去读数据完全乱套后来查手册才发现模式不对。第三个是片选时序。CS 拉低到第一个 SCK 上升沿之间要有至少 5ns 的建立时间CS 拉高到下一个命令之间要有至少 10ns 的保持时间。虽然 PIC18 的 SPI 模块自动处理了这些但如果你用普通 IO 模拟 SPI就得自己保证。5.3 实测性能数据最后分享一下实测的性能数据供参考。测试条件Fosc 16MHzSPI 1MHz室温 25 摄氏度。操作耗时备注单字节读8us含命令和地址单字节写12us含 WREN 和等待256 字节连续读260us平均 1us/字节256 字节连续写320us平均 1.25us/字节1K 字节连续写1.2ms含 CRC 计算这个速度对于工业数据记录来说绰绰有余。我试过每秒写 1000 条记录每条 32 字节CPU 占用率不到 5%完全不影响主循环的其他任务。实操心得如果项目对写入速度要求更高可以把 SPI 时钟提到 10MHzMRAM 完全支持。但工业环境下建议留余量1MHz 到 4MHz 是比较稳妥的区间。6. 扩展思路与个人体会这套方案跑了大半年现场部署了大概两百多台设备目前没有出现过数据丢失的情况。MRAM 的可靠性确实比 EEPROM 和 Flash 高一个档次虽然单价贵一些但省掉了磨损均衡和擦除等待的麻烦整体算下来反而更划算。如果以后要扩展我有几个想法。一是把 SPI 改成 DMA 方式进一步降低 CPU 占用二是加一个双备份的配置区主备切换防止配置区被意外改写三是把 CRC 换成更强的 ECC 校验进一步提高数据完整性。这些等下一版硬件的时候再考虑。最后再分享一个小技巧MRAM 的写保护引脚 WP 如果不用一定要拉高但如果你在调试阶段想临时禁止写入可以把 WP 拉低这样就不用改代码了。这个在产线测试的时候特别方便能防止误写配置区。