ARTICLE DETAIL

资讯详情

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

MRAM与PIC18F47J53工业数据采集终端设计:SPI驱动与环形缓冲区实现

MRAM与PIC18F47J53工业数据采集终端设计:SPI驱动与环形缓冲区实现 1. 项目缘起与方案选型为什么是 MRAM 加 PIC181.1 一个真实的需求场景前阵子接了个工业数据采集终端的活儿客户的要求很朴素设备装在配电柜里环境温度夏天能到 70 度冬天户外柜能到零下 30 度要求每 100 毫秒记录一次传感器数据断电不能丢而且这设备设计寿命是 10 年中间不允许换电池。听到不允许换电池这几个字我脑子里第一个就把 FRAM 和 MRAM 排到了候选名单最前面因为传统的 SRAM 加纽扣电池方案直接出局EEPROM 的擦写寿命又扛不住 100ms 一次的高频写入。算一笔账你就明白为什么 EEPROM 不行了。100ms 写一次一天就是 864000 次一年就是 3.15 亿次。就算用最好的 EEPROM标称 100 万次擦写寿命一天之内就写废了。有人会说那加个缓存攒够一批再写但断电瞬间缓存里的数据就没了工业现场最怕的就是这个。所以必须找一种写入寿命近乎无限、写入速度又快、还不用电池维持的存储器。MRAM 就是在这个背景下进入视野的。MR25H40CDF 是 Everspin 出的 4Mbit 串行 MRAMSPI 接口DFN 封装工作温度覆盖工业级甚至部分车规级范围。它的核心优势在于写入没有擦除周期概念可以按字节直接改写写入寿命标称 10 的 14 次方以上理论上你每秒写一万次也能写几百年。而且它是非易失的断电数据稳稳当当不需要任何后备电源。主控这边选 PIC18F47J53理由也很实在。这颗片子是 Microchip 的 8 位机带硬件 SPI 模块有 128KB Flash 和将近 4KB RAM对于这个数据采集终端来说资源够用。更重要的是它在工业现场用了很多年供货稳定开发工具链成熟烧录器便宜产线工人上手快。用 32 位 ARM 当然性能更好但这个项目对算力要求不高8 位机成本低、功耗低、抗干扰设计资料多反而是更务实的选择。1.2 为什么不用 SPI Flash 或 SD 卡很多人第一反应是存数据用 SPI Flash 啊W25Q 系列多便宜。这话没错但要看场景。SPI Flash 的擦除块是 4KB 起步你要改一个字节得把整个扇区读到 RAM改完再擦除整个扇区写回去。这个过程耗时几十毫秒而且擦除次数有限通常 10 万次。对于高频小数据量写入SPI Flash 的写放大问题非常严重寿命消耗极快。SD 卡就更不用说了消费级产品工业温度范围根本扛不住而且文件系统在突然断电时容易损坏现场维护成本极高。我见过太多项目为了省几块钱用 SD 卡结果现场天天出问题售后跑断腿。MRAM 的定位正好卡在中间比 SRAM 便宜且非易失比 EEPROM 快且寿命长比 SPI Flash 灵活且可靠。它贵但贵得有道理用在关键数据记录上这个成本值得花。1.3 整体架构设计思路这个项目的硬件架构很清晰PIC18F47J53 作为主控通过硬件 SPI 接口连接 MR25H40CDF另外挂几个传感器在 I2C 和 ADC 上。MRAM 负责存储配置参数、校准数据和历史记录。软件上分三层底层是 SPI 驱动和 MRAM 读写函数中间层是数据管理逻辑上层是应用任务调度。这里有个设计决策值得展开说MRAM 的 4Mbit 容量换算过来是 512KB对于存传感器数据来说不算大。所以我设计了一个环形缓冲区结构把 MRAM 分成若干扇区每个扇区存固定条数的记录写满后覆盖最老的扇区。这样既保证了写入的连续性又避免了频繁擦除整个芯片。环形缓冲区的元数据写指针、记录计数、校验信息单独放在 MRAM 的头部区域每次写入时更新。提示MRAM 虽然写入寿命极长但并不意味着可以无限制地乱写。合理的做法仍然是做写入合并和缓冲减少 SPI 总线占用同时降低整体功耗。2. 硬件连接与 SPI 时序要点2.1 引脚连接与硬件设计细节MR25H40CDF 是 8 引脚 DFN 封装引脚定义很标准VDD、VSS、SCK、SI、SO、CS、WP、HOLD。和普通 SPI Flash 引脚兼容但要注意 WP 和 HOLD 这两个引脚在 MRAM 上的行为略有不同。MR25H40CDF 的 WP 引脚用于硬件写保护拉低时状态寄存器被锁定HOLD 引脚用于暂停通信拉低时 SCK 和 SI 被忽略SO 进入高阻态。实际连接时我把 CS 接到 PIC18F47J53 的 RA5 引脚配置为普通 GPIO 输出用软件控制片选。为什么不用硬件 SPI 的 SS 引脚因为 PIC18F47J53 的硬件 SS 在某些模式下会自动控制反而不好管理多从机场景。用普通 GPIO 手动拉低拉高时序完全可控调试也方便。SCK、SI、SO 分别接到 RC3、RC5、RC4这是 PIC18F47J53 的 MSSP 模块默认 SPI 引脚。WP 和 HOLD 我直接上拉到 VDD因为项目里不需要硬件写保护和暂停功能保持高电平让它们失效即可。如果你要做数据保护可以把 WP 接到另一个 GPIO在关键写入前拉低锁定。电源部分要注意MRAM 对电源纹波比较敏感我在 VDD 引脚旁边放了 0.1uF 和 1uF 两个电容一个滤高频一个滤低频。DFN 封装焊接时容易虚焊产线一定要做 AOI 检查我吃过这个亏有一批板子有几块 MRAM 时好时坏最后发现是底部散热焊盘没焊好。2.2 SPI 模式选择与时序计算MR25H40CDF 支持 SPI 模式 0 和模式 3也就是 CPOL0/CPHA0 或 CPOL1/CPHA1。我选的是模式 0因为 PIC18F47J53 的 MSSP 模块在模式 0 下配置最简单SCK 空闲低电平数据在上升沿采样。时序参数方面MR25H40CDF 最高支持 40MHz 时钟但 PIC18F47J53 在 3.3V 供电下MSSP 模块最高只能跑到 Fosc/4。我的系统时钟是 48MHz所以 SPI 时钟最高 12MHz。实际配置时我用了 6MHz也就是 SSPADD 设为 1Fosc/(4*(11))6MHz。为什么不跑满 12MHz因为工业现场线缆可能较长高速下信号完整性容易出问题6MHz 留了足够余量实测波形很干净。算一下 6MHz 下写一个字节的时间8 个时钟周期每个周期 1/6us总共约 1.33us。加上片选建立时间和保持时间一个字节的完整写入周期大约 2us。写一条 32 字节的记录加上命令和地址开销大约 80us。100ms 写一次的话SPI 总线占用率不到千分之一非常宽裕。2.3 硬件片选与软件片选的取舍热搜词里有人问spi硬件片选与软件片选的区别这里正好展开说。硬件片选是 SPI 控制器自动管理 CS 信号发送数据前自动拉低发完自动拉高。软件片选是你自己用 GPIO 控制代码里手动拉低拉高。硬件片选的优点是省代码时序由硬件保证。缺点是灵活性差比如你要在两次传输之间插入延时或者要控制 CS 拉低到第一个时钟沿的时间硬件片选往往做不到。MRAM 的 CS 建立时间要求是 5ns 以上保持时间也是 5ns硬件片选通常能满足但如果你要读状态寄存器判断写入完成中间需要反复切换 CS软件片选更顺手。我最终用软件片选代码里封装了两个宏#define MRAM_CS_LOW() (LATA5 0) #define MRAM_CS_HIGH() (LATA5 1)每次读写前拉低读写后拉高。注意拉低和第一个 SCK 上升沿之间要留至少 5nsPIC 的 GPIO 翻转速度足够快实际用示波器看有几十纳秒完全满足。拉高之前要确保最后一个 SCK 下降沿已经完成否则数据可能没锁存进去。我在代码里加了一个 NOP 延时保险起见。3. 底层驱动实现与关键代码解析3.1 SPI 初始化与字节收发函数PIC18F47J53 的 MSSP 模块初始化代码如下我直接贴出来注释写清楚每个寄存器的含义void SPI_Init(void) { TRISC3 0; // SCK 输出 TRISC5 0; // SI 输出 TRISC4 1; // SO 输入 TRISA5 0; // CS 输出 LATA5 1; // CS 默认高电平 SSP1STAT 0x40; // CKE0数据在上升沿采样 SSP1CON1 0x21; // SSPEN1CKP0Fosc/4 模式 SSP1ADD 1; // 时钟 Fosc/(4*(11)) 6MHz }这里 SSP1STAT 的 CKE 位决定数据在时钟的哪个边沿变化SSP1CON1 的 CKP 位决定空闲电平。两个配合起来就是 SPI 模式。模式 0 对应 CKE0、CKP0模式 3 对应 CKE1、CKP1。我选模式 0所以 CKE0、CKP0。字节收发函数是驱动的基础写一个字节和读一个字节本质上都是交换uint8_t SPI_ExchangeByte(uint8_t data) { SSP1BUF data; while(!PIR1bits.SSP1IF); // 等待传输完成 PIR1bits.SSP1IF 0; // 清除标志 return SSP1BUF; // 读取接收到的数据 }这个函数同时完成发送和接收。SPI 是全双工的你发一个字节出去同时也会收一个字节进来。写 MRAM 时你发命令和地址收到的字节丢弃读 MRAM 时你发空字节0xFF来产生时钟收到的就是 MRAM 返回的数据。注意SSP1BUF 是双缓冲的读之前一定要确认 SSP1IF 标志置位否则可能读到旧数据。我在早期版本里忘了清标志导致偶尔读到错误数据排查了半天。3.2 MRAM 写使能与状态轮询MR25H40CDF 的写操作需要先发写使能命令WREN0x06然后才能发写命令。写使能是一个单字节命令没有地址和数据void MRAM_WriteEnable(void) { MRAM_CS_LOW(); SPI_ExchangeByte(0x06); // WREN 命令 MRAM_CS_HIGH(); }写完数据后MRAM 内部需要时间完成实际存储这个时间叫写周期时间tWR典型值 10ms 左右。虽然 MRAM 比 EEPROM 快很多但仍然不是瞬间完成。你可以通过读状态寄存器RDSR0x05的 WIP 位来判断写入是否完成void MRAM_WaitReady(void) { uint8_t status; do { MRAM_CS_LOW(); SPI_ExchangeByte(0x05); // RDSR 命令 status SPI_ExchangeByte(0xFF); MRAM_CS_HIGH(); } while(status 0x01); // WIP 位为 1 表示忙 }这里有个实操心得不要每次写完都死等 WIP 清零那样会浪费 CPU 时间。我的做法是写完数据后继续执行其他任务等到下一次要写 MRAM 之前再检查 WIP。因为两次写入间隔 100ms远大于 10ms 的写周期实际上永远不会遇到忙的情况。但代码里保留检查逻辑防止极端情况。3.3 页写与地址自动递增MR25H40CDF 支持页写模式一页是 256 字节。你发一个写命令WRITE0x02然后发 3 字节地址接着连续发数据地址会自动递增。如果跨页了地址会回绕到页首覆盖之前的数据。这个特性要特别注意写跨页数据时必须分多次写每次不超过页边界。我的记录长度是 32 字节MRAM 页大小 256 字节所以一条记录永远不会跨页。但为了通用性我还是写了一个带页边界检查的写函数void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t page_remain 256 - (addr % 256); if(len page_remain) { MRAM_WritePage(addr, buf, len); } else { MRAM_WritePage(addr, buf, page_remain); MRAM_Write(addr page_remain, buf page_remain, len - page_remain); } }这个递归写法简洁但要注意栈深度实际项目中我改成了循环避免递归层数不可控。地址是 24 位的因为 4Mbit 等于 512KB需要 19 位地址但 MR25H40CDF 用 3 字节地址格式高 5 位忽略。4. 数据管理与环形缓冲区实现4.1 存储结构设计512KB 的 MRAM 我这样划分前 4KB 存系统参数和元数据包括设备 ID、校准系数、环形缓冲区写指针、记录总数、CRC 校验表。剩下的 508KB 全部用于数据记录每条记录 32 字节能存约 16256 条。按 100ms 一条算能存大约 27 分钟的数据。如果客户要求更长的历史可以降低采样率或者压缩数据。元数据区我做了双备份A 区和 B 区各 2KB交替写入。每次更新元数据时先写 A 区校验通过后再写 B 区。读取时如果 A 区校验失败就用 B 区恢复。这个做法借鉴了数据库的 WAL 日志思路虽然简单但非常有效我用了好几年没出过数据丢失。每条记录的结构如下字段长度说明时间戳4 字节秒级 Unix 时间戳传感器14 字节浮点温度值传感器24 字节浮点湿度值传感器34 字节浮点压力值状态字2 字节设备状态标志保留2 字节对齐用CRC162 字节记录校验记录尾4 字节固定标识 0xAA55AA55合计32 字节记录尾的作用是快速判断该位置是否有有效数据。设备第一次上电时 MRAM 里可能是随机值通过检查记录尾和 CRC 就能确定哪些位置是有效记录。4.2 环形缓冲区读写逻辑环形缓冲区的核心是两个指针写指针和读指针。写指针指向下一个要写入的位置读指针指向最老的记录。写满一圈后写指针追上读指针此时覆盖最老记录读指针也往前推。void RingBuffer_Write(Record *rec) { uint32_t addr DATA_START write_ptr * RECORD_SIZE; MRAM_WriteEnable(); MRAM_Write(addr, (uint8_t*)rec, RECORD_SIZE); write_ptr (write_ptr 1) % MAX_RECORDS; if(record_count MAX_RECORDS) { record_count; } else { read_ptr (read_ptr 1) % MAX_RECORDS; } MetaData_Save(); }MetaData_Save 函数负责把 write_ptr、read_ptr、record_count 写入元数据区。这个函数调用频率很高每次写记录都调用所以优化很关键。我的做法是先在 RAM 里缓存元数据每 100 次写入或者检测到掉电信号时才真正写 MRAM。掉电检测用了一个 ADC 通道监测电源电压低于阈值时触发中断在中断里紧急保存元数据。提示掉电保存的时间窗口很关键。PIC18F47J53 从检测到掉电到完全断电靠电容维持大约有 5ms 时间。写一次元数据需要约 1ms足够完成。但电容容量要算好我用了 470uF实测能撑 8ms 左右。4.3 数据读取与导出读取数据时从 read_ptr 开始依次读取 record_count 条记录。每条记录读出后校验 CRC 和记录尾校验失败就跳过并记录错误计数。导出数据通过 UART 发送格式是 CSV方便客户用 Excel 打开。void RingBuffer_Dump(void) { uint16_t i, idx read_ptr; for(i 0; i record_count; i) { uint32_t addr DATA_START idx * RECORD_SIZE; Record rec; MRAM_Read(addr, (uint8_t*)rec, RECORD_SIZE); if(rec.tail 0xAA55AA55 CRC16_Check(rec)) { printf(%lu,%.2f,%.2f,%.2f\r\n, rec.timestamp, rec.temp, rec.humi, rec.press); } idx (idx 1) % MAX_RECORDS; } }导出 16000 条记录每条 50 字节左右总共 800KB 数据UART 波特率 115200 下大约需要 70 秒。这个时间可以接受客户一般在设备维护时导出不要求实时。5. 常见问题与排查实录5.1 SPI 通信失败排查表现象可能原因排查方法解决方法读回全 0xFFCS 未拉低或接线错误示波器看 CS 和 SCK 波形检查 GPIO 配置和焊接读回全 0x00SO 线断路或 MRAM 未供电万用表测 VDD 和 SO 通断补焊或更换芯片数据偶尔错误SPI 时钟太快或干扰降低时钟到 1MHz 测试加屏蔽线或降低速率写入后读不到未发 WREN 或 WIP 未等待逻辑分析仪抓完整时序补发 WREN加等待地址错乱地址字节顺序错误对照数据手册检查改为 MSB 先发这个表是我实际踩坑总结的每一行都对应至少一次现场故障。最坑的是数据偶尔错误当时查了两天最后发现是 SPI 线走线太长和电机驱动线捆在一起电机一启动就干扰。后来把 SPI 线单独走加了个 100 欧姆的串联电阻问题解决。5.2 写入寿命的误区很多人以为 MRAM 写入寿命无限就拼命写。实际上 MR25H40CDF 的写入寿命是 10 的 14 次方次每字节虽然很大但如果你每秒写同一地址一万次一年就是 3.15 亿次十年 31.5 亿次仍然远小于 10 的 14 次方。所以正常使用完全不用担心寿命。但要注意的是频繁写入会增加功耗MRAM 写入电流比读取大不少电池供电场景要优化写入频率。5.3 温度对 MRAM 的影响MRAM 的工作温度范围是 -40 到 85 度工业级。但我在高温测试时发现超过 70 度后写入时间会变长tWR 从 10ms 增加到 15ms 左右。如果你的应用环境温度高等待 WIP 的超时时间要放宽。我在代码里设了 50ms 超时超过就报错实际在 85 度下也没触发过。低温下 MRAM 反而更快但要注意 PIC18F47J53 在 -40 度时内部振荡器起振可能变慢如果用了外部晶振要选工业级晶振负载电容也要按温度特性选 NP0 材质。5.4 电源掉电数据保护这是工业设备最关键的环节。我的方案是电源输入端加一个二极管和 470uF 电容二极管防止电容向电源倒灌。ADC 持续监测输入电压低于 4.5V 时触发中断。中断里立即停止数据采集把 RAM 里的元数据写入 MRAM然后进入休眠。实测从 4.5V 掉到 3.0VPIC 最低工作电压大约有 8ms写元数据需要 1ms时间充裕。但要注意 ADC 采样和中断响应本身有延迟实际可用时间可能只有 5ms所以元数据写入要尽量精简。我把元数据压缩到 64 字节一次页写完成耗时不到 500us。6. 实操建议与扩展思路6.1 给新手的上手路线如果你刚接触 MRAM 和 PIC建议按这个顺序来先点灯确认 GPIO 能控制再调 SPI用逻辑分析仪抓波形确认时钟和数据都对然后读 MRAM 的 ID 或者状态寄存器确认通信正常最后再写数据、读数据、做掉电测试。每一步都确认无误再往下走不要跳步。逻辑分析仪是必备工具几十块钱的 24MHz 采样率就够用。抓 SPI 波形时重点看 CS 拉低到第一个 SCK 沿的时间、数据在 SCK 哪个边沿变化、CS 拉高前最后一个 SCK 沿是否完成。这三个点对了SPI 基本就通了。6.2 代码优化与功耗控制PIC18F47J53 在 48MHz 下运行电流约 15mAMRAM 读写时额外增加 5mA 左右。如果做电池供电可以降低系统时钟到 16MHzSPI 降到 2MHz整体电流能降到 8mA 以下。MRAM 在待机时电流只有几微安可以忽略。另外SPI 传输完成后立即把 CS 拉高MRAM 会自动进入低功耗状态。不要一直把 CS 拉低那样 MRAM 会保持活跃功耗增加。6.3 这个方案还能怎么扩展MR25H40CDF 是 4Mbit如果数据量更大可以换 MR25H256CDF256Kbit或者 MR25H10CDF1Mbit引脚兼容代码只需改地址范围。如果要更高速可以换 QSPI 接口的 MRAM但 PIC18F47J53 不支持 QSPI得换主控。软件层面可以加一个文件系统把 MRAM 模拟成 FAT 格式这样设备插到电脑上就能直接读。不过 FAT 文件系统本身有写入开销会降低 MRAM 的写入效率适合数据导出不频繁的场景。最后分享一个我在实际项目中的体会MRAM 最大的价值不是速度快或者寿命长而是省心。用了 MRAM 之后我再也不用担心掉电丢数据、擦写寿命不够、电池没电这些问题可以把精力放在业务逻辑上。对于工业设备来说可靠性就是最大的成本节约。
返回列表