ARTICLE DETAIL

资讯详情

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

MRAM与PIC18F96J65的SPI存储方案:工业高频写入与掉电安全设计

MRAM与PIC18F96J65的SPI存储方案:工业高频写入与掉电安全设计 1. 项目缘起与方案选型为什么是 MRAM 加 PIC181.1 一个真实的数据存储痛点做过工业数据采集的朋友大概都遇到过这种场景设备装在配电柜里或者野外机柜中主控每隔几毫秒就要把一组关键参数比如校准系数、累计运行时长、故障快照落盘保存。用 EEPROM 吧写入寿命十万次量级高频写很快就磨穿了用 SRAM 加后备电池吧电池在高温环境下两三年就鼓包维护成本高得离谱用 Flash 吧写之前得擦除整个扇区而且擦写期间 CPU 得等着掉电瞬间还可能把整块数据搞成半死不活的状态。这个项目要解决的就是这类问题在工业和嵌入式环境里稳定、高频、掉电安全地存储和读取数据。核心器件选的是MR25H40CDF这颗 4Mbit 的 MRAM磁性随机存储器主控用的是PIC18F96J65两者之间走SPI总线通信。MRAM 这个东西简单说就是既有 RAM 的写入速度又有 Flash 的非易失性。它的写入不需要擦除字节级可写理论擦写寿命在 10^14 次以上而且写入过程是纳秒级的。对于需要频繁记录数据的工业场景这几乎是量身定做的方案。而 PIC18F96J65 是 Microchip 的一款 8 位单片机自带 SPI 模块工作温度范围宽外设资源够用在工业控制领域属于久经考验的老将。1.2 为什么不用 EEPROM 或 FRAM有人会问FRAM铁电存储器不也是非易失、高速、高寿命吗确实FRAM 在很多指标上和 MRAM 接近但 FRAM 有个老问题读写次数虽然高但在高温环境下数据保持能力会下降而且部分 FRAM 器件的容量做不大、价格偏高。MRAM 基于磁隧道结MTJ原理数据保持能力在宽温范围内更稳抗辐射能力也更好适合工业现场的电磁环境。至于 EEPROM前面已经说了写入寿命是硬伤。假设你每秒写一次EEPROM 十万次寿命意味着不到 28 小时就写废了。而 MR25H40CDF 的 10^14 次寿命按每秒写一次算可以写三百多万年。这个对比不是夸张是实打实的数量级差异。1.3 整体架构长什么样整个方案的数据流是这样的PIC18F96J65 通过硬件 SPI 接口MSSP 模块连接 MR25H40CDFCS 片选由普通 GPIO 控制。上电后单片机先读取 MRAM 中预存的配置区和状态区判断是否需要初始化运行过程中采集到的数据先写入 MRAM 的环形缓冲区同时更新状态标志掉电时由于 MRAM 写入是即时完成的不需要额外的掉电保护电路来抢救数据。这个架构的关键点在于MRAM 的写入是真正的即时落盘不像 Flash 那样有内部写缓冲和擦除延迟。所以你不需要在电源掉电时用大电容撑住几百毫秒来等 Flash 写完这直接简化了硬件设计。提示MRAM 虽然写入快但 SPI 总线的速率仍然是瓶颈。MR25H40CDF 支持最高 40MHz 的 SPI 时钟但 PIC18F96J65 在 64MHz 系统时钟下SPI 最高只能到 16MHzFosc/4。实际项目中我一般跑 10MHz留足时序余量。2. 硬件连接与 SPI 时序细节2.1 引脚连接与片选策略MR25H40CDF 是 8 引脚 SOIC 封装引脚定义很标准VCC、GND、CS、SCK、SI、SO、WP写保护、HOLD保持。和 PIC18F96J65 连接时我用的映射是这样的MR25H40CDF 引脚PIC18F96J65 引脚说明CSRC2GPIO软件片选低有效SCKRC3SCKSPI 时钟SIRC5SDO主出从入SORC4SDI主入从出WP接 VCC禁用硬件写保护HOLD接 VCC禁用保持功能这里有个细节值得说CS 我用的是普通 GPIO 而不是 MSSP 模块的硬件片选SS。原因很简单PIC18F96J65 的硬件 SS 在多主或复杂时序下容易出幺蛾子而且软件片选可以让我精确控制 CS 拉低到第一个时钟沿之间的建立时间。MR25H40CDF 要求 CS 建立时间最小 5ns软件控制完全能满足还能在 CS 拉低后插入几个 NOP 做延时确保时序稳定。WP 和 HOLD 直接接 VCC 是最省事的做法。WP 接高电平意味着状态寄存器里的块保护位才生效如果你不需要块保护接 VCC 就行。HOLD 接高电平表示不暂停通信除非你有多个 SPI 从设备共享总线需要暂停否则也接 VCC。2.2 SPI 模式选择Mode 0 还是 Mode 3MR25H40CDF 支持 SPI Mode 0CPOL0CPHA0和 Mode 3CPOL1CPHA1。我选的是Mode 0也就是时钟空闲为低电平数据在时钟上升沿采样。为什么选 Mode 0因为 PIC18F96J65 的 MSSP 模块在 Mode 0 下配置最简单SSPCON1 寄存器的 CKP 位清零、CKE 位清零就行。而且 Mode 0 的时序在示波器上看起来最直观CS 拉低后SCK 保持低电平第一个上升沿采样第一位数据。配置代码大概长这样// SPI 初始化Mode 0时钟 Fosc/4 SSPCON1 0x00; // CKP0, SSPM0000 (Fosc/4) SSPSTAT 0x00; // CKE0, SMP0 (在数据输出末尾采样) SSPADD 0x00; // 不用 I2C 模式此寄存器无关 SSPCON1bits.SSPEN 1; // 使能 MSSP TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC5 0; // SDO 输出 TRISCbits.TRISC4 1; // SDI 输入 TRISCbits.TRISC2 0; // CS 输出这里有个坑SSPSTAT 的 SMP 位。在 SPI 主模式下SMP0 表示在数据输出时间的末尾采样输入数据SMP1 表示在数据输出时间的中间采样。对于 MR25H40CDF我建议用 SMP0因为这样采样点更靠后留给从设备的数据建立时间更充裕。实测在 10MHz 时钟下SMP0 比 SMP1 的误码率低很多。2.3 上拉电阻与 PCB 布局SPI 总线上CS、SCK、SI 这三根线我各加了一个 10kΩ 的上拉电阻。有人觉得 SPI 是推挽输出不需要上拉但在工业环境中上拉电阻的作用不是驱动而是在单片机复位期间或引脚高阻态时把总线拉到确定电平防止 MRAM 收到乱码指令。PCB 布局上MRAM 尽量靠近单片机放置SPI 走线控制在 5cm 以内。如果实在要拉长SCK 和 SI 之间要保持 3W 原则线间距大于三倍线宽减少串扰。地平面要完整MRAM 的 GND 引脚旁边放一个 0.1μF 的陶瓷去耦电容再并一个 1μF 的钽电容。注意MRAM 对电源纹波比较敏感VCC 上的纹波如果超过 50mV写入过程中可能出现位翻转。我在一个项目里因为用了开关电源直接供电纹波 120mV导致 MRAM 偶发写入错误后来加了一级 LC 滤波才解决。3. 指令集与读写操作实战3.1 MR25H40CDF 的指令集速览MR25H40CDF 的指令集和标准 SPI Flash 很像但少了擦除指令多了几个 MRAM 特有的。常用指令如下指令名称指令码功能WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据RDID0x9F读器件 ID注意MRAM 的 WRITE 指令不需要先擦除这是和 Flash 最大的区别。你直接发 WRITE 指令跟地址然后连续写数据就行。写入过程中MRAM 内部会自动完成磁隧道结的翻转不需要你等待擦除周期。RDID 指令返回的器件 ID 是 0x7F7F7F7F7F7F7F7F8 字节前几个字节是厂商代码和容量代码。我一般在上电初始化时读一次 RDID确认通信正常。3.2 写操作的完整流程写一个字节到 MRAM 的地址 0x001000流程是这样的拉低 CS发送 WREN0x06拉高 CSWREN 需要 CS 上升沿才能生效拉低 CS发送 WRITE0x02发送 24 位地址0x001000发送数据字节拉高 CS注意第 3 步WREN 指令必须用 CS 上升沿来锁存这是很多新手容易漏掉的。如果你发完 WREN 不拉高 CS 就直接发 WRITE写使能位不会生效写入会失败。代码实现void MRAM_WriteByte(uint32_t addr, uint8_t data) { CS_LOW(); SPI_Exchange(0x06); // WREN CS_HIGH(); // 锁存写使能 CS_LOW(); SPI_Exchange(0x02); // WRITE SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); SPI_Exchange(data); CS_HIGH(); // 等待写入完成 while (MRAM_IsBusy()); }MRAM_IsBusy() 函数读状态寄存器的 WIP 位bit 0如果为 1 表示正在写入。MRAM 的写入时间很短典型值 100ns 左右但 SPI 传输本身需要时间所以实际上你发完最后一个字节WIP 位可能已经清零了。3.3 读操作的时序要点读操作比写操作简单不需要 WRENuint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; CS_LOW(); SPI_Exchange(0x03); // READ SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); data SPI_Exchange(0x00); // 发送哑字节读取数据 CS_HIGH(); return data; }读操作可以连续读地址会自动递增。如果你要读一大块数据不需要每次都发 READ 指令和地址发一次然后连续读就行。这个特性在读取环形缓冲区时非常有用。3.4 页写入与边界处理MR25H40CDF 的写入是按页组织的每页 256 字节。如果你从地址 0x0000FF 开始写 10 个字节写到第 2 个字节时地址会回卷到 0x000000而不是继续到 0x000100。这个行为和 Flash 一样是硬件地址回卷。所以在写跨页数据时你必须自己处理边界。我的做法是把要写的数据按页对齐切分每段不超过页边界。比如要写 300 字节到地址 0x0000F0第一段写 16 字节到 0x0000FF第二段写 256 字节到 0x000100第三段写 28 字节到 0x000200。void MRAM_WriteBuffer(uint32_t addr, uint8_t *buf, uint16_t len) { while (len 0) { uint16_t page_remain 256 - (addr 0xFF); uint16_t chunk (len page_remain) ? len : page_remain; MRAM_WritePage(addr, buf, chunk); addr chunk; buf chunk; len - chunk; } }这个函数看起来简单但实际项目中我见过有人直接连续写 512 字节结果数据错位查了半天才发现是页回卷问题。4. 数据组织与掉电安全设计4.1 存储区划分4Mbit 的 MRAM 总共 512KB 地址空间我一般这样划分地址范围大小用途0x00000 - 0x00FFF4KB配置区校准系数、设备参数0x01000 - 0x01FFF4KB状态区运行标志、故障码0x02000 - 0x7FFFF504KB数据环形缓冲区配置区和状态区各留 4KB是因为 MRAM 写入不需要擦除但为了数据安全我用了双备份加 CRC 校验。每个区的前 2KB 是主数据后 2KB 是备份数据写入时先写备份再写主数据读取时对比两者如果主数据 CRC 错误就用备份恢复。4.2 环形缓冲区的实现数据环形缓冲区是项目的核心。我定义了两个指针写指针和读指针都存储在状态区。写指针指向下一个要写入的位置读指针指向下一个要读取的位置。当写指针追上读指针时表示缓冲区满需要覆盖最旧的数据。typedef struct { uint32_t write_ptr; uint32_t read_ptr; uint32_t count; uint32_t crc; } RingBufferState_t;每次写入数据前先更新状态区的 write_ptr 和 count再写数据。这里有个顺序问题先更新状态还是先写数据我的做法是先把数据写入缓冲区再更新状态指针。因为如果先更新指针再写数据掉电时指针指向了一个还没写入的位置读取时会读到旧数据。而先写数据再更新指针最坏情况是数据写入了但指针没更新下次写入会覆盖这个位置数据丢失但不会读到脏数据。4.3 掉电检测与数据完整性虽然 MRAM 写入是即时完成的但 SPI 传输需要时间。如果在 SPI 传输过程中掉电MRAM 可能只收到了部分字节。为了防止这种情况我在硬件上做了一个简单的掉电检测电路电源输入端分压后接到 PIC18F96J65 的 ADC 引脚当电压低于阈值时触发中断中断服务程序立即停止当前写入操作并设置一个写入中断标志。下次上电时系统检查这个标志如果发现上次写入被中断就丢弃最后一条不完整的数据记录。这个机制配合 CRC 校验可以保证数据的一致性。提示MRAM 的写入本身是原子的但 SPI 传输不是。一个 32 字节的数据记录在 10MHz SPI 下需要约 25.6μs 传输时间。如果电源在 20μs 时掉电MRAM 只收到了前 25 个字节。所以掉电检测的响应时间必须小于 SPI 传输一个字节的时间0.8μs这对中断响应速度要求很高。5. 常见问题与排查实录5.1 通信失败读到的全是 0xFF这是最常见的问题。上电后读 RDID返回 0xFF 或 0x00说明 SPI 通信根本没建立。排查顺序检查 CS 是否正常拉低。用示波器看 CS 引脚发指令时应该有一个明显的低电平脉冲。如果 CS 一直高检查 GPIO 配置是否正确。检查 SCK 是否有波形。如果 SCK 没波形说明 MSSP 模块没使能或者 SSPEN 位没置 1。检查 SPI 模式。MRAM 支持 Mode 0 和 Mode 3如果你配成了 Mode 1 或 Mode 2数据会错位。用示波器看 SCK 空闲电平Mode 0 空闲为低Mode 3 空闲为高。检查电源和地。MRAM 的 VCC 必须在 2.7V 到 3.6V 之间如果电压低于 2.7V器件可能不工作。我遇到过一次排查了半天发现是 PCB 上 MRAM 的 VCC 引脚虚焊万用表量有 3.3V但一通信就掉到 2.5V。后来补焊就好了。5.2 写入成功但读出来不对这种情况通常是时序问题。MRAM 的写入需要一定的时间虽然典型值只有 100ns但在某些条件下比如高温、低压可能变长。如果你写完立即读可能读到旧数据。解决方法每次写入后轮询状态寄存器的 WIP 位直到 WIP0 再继续。或者简单粗暴地加一个 1μs 的延时但轮询更可靠。另一个可能的原因是页回卷。如果你写入的地址跨页了数据会回卷到页首覆盖之前的数据。检查你的写入地址和长度确保没有跨页。5.3 数据偶发位翻转在工业环境中电磁干扰可能导致 SPI 通信误码。我遇到过在变频器旁边工作时MRAM 数据偶尔出现位翻转。解决方法SPI 时钟降低到 5MHz 以下在 SCK 和 SI 上串联 22Ω 电阻减少反射增加 CRC 校验每次读取数据时验证 CRC如果错误就重读在 MRAM 的 VCC 和 GND 之间加 0.1μF 和 10μF 电容5.4 常见问题速查表现象可能原因解决方法读 RDID 返回 0xFFCS 未拉低、SPI 未使能检查 GPIO 和 SSPEN 位写入后读出旧数据WIP 未等待、页回卷轮询 WIP 位、检查地址边界数据偶发错误电磁干扰、电源纹波降低 SPI 速率、加滤波电容通信不稳定上拉电阻缺失、走线过长加 10kΩ 上拉、缩短走线高温下写入失败电源电压不足检查 VCC 是否低于 2.7V6. 性能实测与优化建议6.1 实测数据我在实验室环境下做了一组测试PIC18F96J65 跑 64MHzSPI 时钟 10MHzMRAM 供电 3.3V室温 25°C操作耗时单字节写入2.4μs256 字节页写入210μs单字节读取1.6μs256 字节连续读取205μsRDID 读取8.8μs写入 256 字节耗时 210μs平均每个字节 0.82μs基本就是 SPI 传输时间。MRAM 本身的写入延迟可以忽略不计。6.2 优化建议如果你需要更高的写入速度可以考虑提高 SPI 时钟。PIC18F96J65 在 64MHz 下 SPI 最高 16MHz但 MRAM 支持 40MHz。如果你换用支持更高 SPI 时钟的单片机写入速度可以提升 4 倍。使用 DMA。PIC18F96J65 没有 DMA但如果你换用 PIC32 或 STM32 系列可以用 DMA 搬运数据CPU 只需要配置一次大大减少开销。批量写入。把多个数据记录攒在一起凑够一页再写减少 CS 拉低拉高的次数。6.3 一个实际项目的经验我之前做过一个工业数据记录仪每秒采集 8 通道模拟量每个通道 2 字节加上时间戳和 CRC每条记录 24 字节。用 MRAM 存储环形缓冲区 504KB可以存约 21000 条记录按每秒一条算可以存将近 6 小时。实际运行中我发现最大的问题不是 MRAM 本身而是电源质量。设备装在变频器柜里电源纹波很大MRAM 偶尔写入错误。后来我在电源入口加了一级 TVS 管和 LC 滤波问题就解决了。另外MRAM 的 WP 引脚我建议不要直接接 VCC而是接到单片机的 GPIO。这样你可以在固件升级或维护时通过软件拉低 WP 来保护数据防止误写。虽然多占一个引脚但安全性提升很多。最后说一个细节MR25H40CDF 的状态寄存器里有一个块保护位BP0、BP1可以保护部分地址区域不被写入。如果你有一些关键配置数据不想被意外覆盖可以把它们放在被保护的块里然后设置 BP 位。这个功能在调试阶段特别有用防止程序跑飞时把配置区写坏。
返回列表