ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MRAM与PIC18LF4680实现工业级掉电安全数据存储方案

MRAM与PIC18LF4680实现工业级掉电安全数据存储方案 1. 项目缘起与方案选型思考工业现场的数据存储有个很尴尬的处境用 EEPROM 吧写入速度慢得让人着急擦写次数也就百万次级别高频采集场景下没几个月就写废了用 SRAM 加后备电池吧电池没电数据就全丢维护成本高得离谱用 Flash 吧写之前得先擦一个扇区掉电时机不对还会丢数据。我前两年做一个工业数据采集终端的项目就卡在这个存储环节上后来选定了 MRAM 方案主控用的是 PIC18LF4680存储芯片是 MR25H40CDF跑下来效果确实稳。先说说这两颗芯片各自是什么定位。MR25H40CDF是 Everspin 出的 4Mbit 串行 MRAM磁阻随机存储器SPI 接口40MHz 时钟最关键是它具备 SRAM 级别的写入速度同时又是非易失的掉电数据不丢读写寿命号称无限次。PIC18LF4680是 Microchip 的一款 8 位单片机自带 MSSP 模块可以硬件跑 SPI工作电压范围宽工业温度等级也有非常适合做这种中小规模的数据采集和存储控制。为什么选 MRAM 而不是继续用 Flash 或者 FRAM这里面的取舍值得展开说。Flash 的问题在于写入粒度你哪怕只改一个字节也得把整个扇区读到 RAM 里、改完再擦除整个扇区写回去这个过程耗时且掉电风险大。FRAM 虽然写入快、寿命也高但容量普遍偏小而且价格按容量算下来比 MRAM 还贵供货也不如 MRAM 稳定。MRAM 的写入是直接覆盖不需要擦除字节级写入40MHz SPI 下理论带宽能到 5MB/s对于工业采集这种每秒几百到几千个采样点的场景完全够用。从系统架构上看这个组合解决的核心问题是在无电池、无超级电容的条件下实现高频次、掉电安全的数据记录。适合的场景包括工业设备运行日志、电力监测终端、车载黑匣子、医疗设备参数记录等。适合阅读这篇文章的读者应该是有一定单片机基础、了解 SPI 通信、正在做嵌入式数据存储方案选型的工程师或者正在学习嵌入式外设驱动的学生。注意MRAM 虽然写入寿命近乎无限但并不意味着可以无限制地高频写入而不考虑功耗和总线占用实际设计中仍需做写入缓冲和批量提交策略。2. 硬件连接与 SPI 总线设计要点2.1 引脚连接与硬件片选MR25H40CDF 是 8 引脚封装SPI 四线制加片选。和 PIC18LF4680 连接时我用的引脚分配是这样的MR25H40CDF 引脚功能PIC18LF4680 引脚说明CS片选RC2普通 GPIO 控制SO数据输出RC4SDIMSSP 输入SI数据输入RC5SDOMSSP 输出SCK时钟RC3SCKMSSP 时钟VDD电源3.3V需加 0.1uF 去耦VSS地GND就近接地WP写保护接 VDD 或 GPIO建议 GPIO 可控HOLD保持接 VDD不用时拉高这里有个细节很多人会忽略WP 和 HOLD 引脚不能悬空。WP 悬空时芯片内部状态不确定可能导致写入被意外禁止或者意外使能。我的做法是 WP 接到一个空闲 GPIO 上软件里可以主动控制写保护调试阶段拉高允许写入量产固件里可以根据需要拉低保护关键数据区。HOLD 引脚如果不用就老老实实拉到 VDD悬空会引入噪声在工业电磁环境下尤其容易出问题。2.2 硬件片选还是软件片选热搜词里有人问 SPI 硬件片选和软件片选的区别这里结合项目实际说一下。PIC18LF4680 的 MSSP 模块在 SPI 主模式下片选信号是需要软件控制的它不像有些 ARM 芯片那样有硬件 NSS 自动管理。所以在这个项目里片选就是普通 GPIO拉低选中拉高释放。那硬件片选和软件片选到底差在哪硬件片选是 SPI 外设自动在传输前后拉低拉高 CS时序精准适合高速连续传输软件片选是手动控制 GPIO灵活但需要程序员保证时序正确。在这个项目里因为只有一颗 SPI 从设备软件片选完全够用而且更灵活——比如我可以在两次传输之间插入延时或者在不传输时把 CS 拉高让芯片进入低功耗状态。实操心得软件片选时CS 拉低到第一个 SCK 上升沿之间要留至少 5ns 的建立时间MR25H40CDF 手册里写的是 CS 建立时间最小 5nsPIC18 在 40MHz 下指令周期 100ns实际上只要在拉低 CS 后执行一条 NOP 再开始传输就足够了。2.3 电源与去耦设计MRAM 的写入电流比读取电流大不少写入瞬间的电流尖峰如果电源去耦没做好会导致电压跌落轻则写入失败重则芯片复位。我在 PCB 上的做法是VDD 引脚旁边放一个 0.1uF 的陶瓷电容距离引脚不超过 2mm再在电源入口处放一个 10uF 的钽电容做储能。如果是工业环境电源线上再串一个磁珠抑制高频噪声。地线处理上MRAM 的 VSS 要和单片机的 GND 单点连接避免数字地和模拟地混在一起形成地环路。SPI 信号线如果走线超过 10cm建议在 SCK 和 SI 上串 22 欧姆的电阻做阻抗匹配减少反射。3. SPI 通信协议与寄存器操作详解3.1 MR25H40CDF 的指令集MR25H40CDF 的指令集不复杂常用的就几条指令名称指令码功能说明WREN0x06写使能写入前必须先发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据状态寄存器里主要关注 WEL 位写使能锁存和 WIP 位写进行中。WEL 位在每次 WREN 后置 1写入完成后自动清零。WIP 位在写入过程中为 1写入完成后为 0。这里有个容易踩的坑每次写入操作前都必须发 WREN 指令哪怕你连续写多个地址只要中间没有 CS 拉高再拉低WREN 只发一次就行。但如果 CS 拉高了WEL 位就会自动清零下次写入前必须重新发 WREN。我一开始没注意这个细节调试时发现偶尔写入失败后来用逻辑分析仪抓波形才发现是 CS 操作后忘了重新使能写。3.2 PIC18LF4680 的 MSSP 初始化PIC18LF4680 的 MSSP 模块配置成 SPI 主模式关键寄存器是 SSPCON1、SSPSTAT、SSPADD。初始化代码如下void SPI_Init(void) { TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC4 1; // SDI 输入 TRISCbits.TRISC5 0; // SDO 输出 TRISCbits.TRISC2 0; // CS 输出 SSPCON1 0x20; // SSPEN1, CKP0, SPI Master Fosc/4 SSPSTAT 0x40; // CKE1, 数据在时钟上升沿采样 SSPADD 0; // 主模式下不用 CS 1; // 初始释放片选 }SSPCON1 的 bit4 是 CKP决定空闲时时钟极性SSPSTAT 的 bit6 是 CKE决定数据在哪个时钟边沿采样。MR25H40CDF 支持 SPI 模式 0 和模式 3我用的是模式 0CPOL0CPHA0也就是空闲时 SCK 为低数据在上升沿采样。这个配置下 CKP0CKE1。时钟频率方面SSPCON1 的低四位是 SSPM设成 0b0000 就是 Fosc/4。PIC18LF4680 我用的是 10MHz 晶振Fosc/4 就是 2.5MHzMR25H40CDF 最高支持 40MHz所以还有很大余量。如果想跑更快可以把 SSPM 设成 0b0001Fosc/16或者用 PLL 倍频但工业环境下我不建议跑太高2.5MHz 到 5MHz 足够稳定。3.3 字节读写函数实现先看写一个字节的底层函数unsigned char SPI_ExchangeByte(unsigned char data) { SSPBUF data; while(!PIR1bits.SSPIF); // 等待传输完成 PIR1bits.SSPIF 0; return SSPBUF; }这个函数同时完成发送和接收SPI 是全双工的发一个字节的同时也会收一个字节。读 MRAM 时先发指令和地址然后继续发 dummy 字节来产生时钟接收到的就是数据。写数据的完整流程void MRAM_WriteByte(unsigned long addr, unsigned char data) { CS 0; SPI_ExchangeByte(0x06); // WREN CS 1; CS 0; SPI_ExchangeByte(0x02); // WRITE SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); SPI_ExchangeByte(data); CS 1; // 等待写入完成 while(MRAM_IsBusy()); }读数据的流程unsigned char MRAM_ReadByte(unsigned long addr) { unsigned char data; CS 0; SPI_ExchangeByte(0x03); // READ SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); data SPI_ExchangeByte(0x00); // dummy 字节产生时钟 CS 1; return data; }注意MR25H40CDF 是 4Mbit也就是 512KB地址线需要 19 位所以地址要发 3 个字节。很多人用 24 位地址习惯了这里 3 字节地址的高 5 位是无效的但发 0 就行不影响。4. 数据存储策略与掉电保护设计4.1 环形缓冲区设计工业采集场景下数据是持续产生的但存储空间有限不可能无限写入。我的做法是在 MRAM 里划出一块区域做环形缓冲区用两个指针管理写指针和读指针。写指针指向下一个要写入的位置读指针指向下一个要读取的位置。当写指针追上读指针时说明缓冲区满了这时候要么覆盖最老的数据要么停止写入等待读取。环形缓冲区的元数据读写指针、缓冲区大小、魔数存在 MRAM 的固定地址每次上电时先读取这些元数据来恢复状态。因为 MRAM 是非易失的掉电后指针不会丢重新上电后可以接着上次的位置继续写。typedef struct { unsigned long write_ptr; unsigned long read_ptr; unsigned long buf_start; unsigned long buf_size; unsigned long magic; } RingBufferMeta; RingBufferMeta meta; void RingBuffer_Init(void) { MRAM_ReadBuffer(META_ADDR, (unsigned char*)meta, sizeof(meta)); if(meta.magic ! 0xA5A5A5A5) { // 首次使用初始化元数据 meta.write_ptr BUF_START; meta.read_ptr BUF_START; meta.buf_start BUF_START; meta.buf_size BUF_SIZE; meta.magic 0xA5A5A5A5; MRAM_WriteBuffer(META_ADDR, (unsigned char*)meta, sizeof(meta)); } }4.2 掉电检测与紧急写入虽然 MRAM 写入速度快掉电时数据不会丢但有个前提是写入操作已经完成。如果掉电发生在 SPI 传输过程中数据可能只写了一半。所以掉电检测电路还是必要的。我的做法是用一个分压电阻网络监测电源电压接到 PIC18LF4680 的 ADC 引脚上。正常工作时 ADC 每 10ms 采样一次当检测到电压低于阈值比如 3.0V时立即触发中断在中断里把当前缓冲区里未提交的数据紧急写入 MRAM。因为 MRAM 写入不需要擦除一个字节写入在 2.5MHz SPI 下大概 10us 左右写 100 个字节也就 1ms完全来得及。实操心得掉电检测的阈值要留足余量。我一开始设的 2.8V结果发现电源从 3.3V 掉到 2.8V 的时间只有几百微秒根本来不及写完。后来改成 3.0V 触发并且把去耦电容加大到 100uF争取到了 5ms 左右的缓冲时间这才稳了。4.3 数据校验与恢复工业现场电磁干扰大SPI 通信偶尔会出现位翻转。我在每个数据块后面加了一个 CRC16 校验读取时先校验再使用。如果校验失败尝试重新读取连续三次失败就标记该块数据损坏跳过继续读下一块。CRC16 用查表法实现速度快占用空间小。PIC18LF4680 的 Flash 有 64KB放一张 512 字节的 CRC 表绰绰有余。unsigned int CRC16_Calculate(unsigned char *data, unsigned int len) { unsigned int crc 0xFFFF; while(len--) { crc (crc 8) ^ crc16_table[(crc ^ *data) 0xFF]; } return crc; }5. 性能实测与优化经验5.1 读写速度实测数据我在实验室用逻辑分析仪抓了实际波形测出来的数据如下操作类型数据量耗时等效速率单字节写入1 字节约 12us83KB/s连续写入256 字节约 1.2ms213KB/s单字节读取1 字节约 8us125KB/s连续读取256 字节约 0.9ms284KB/s连续读写比单字节快很多因为指令和地址只发一次后面就是连续的数据流。所以实际使用中我尽量用批量读写减少指令开销。5.2 优化技巧第一个优化是减少 CS 翻转次数。每次 CS 拉高再拉低MRAM 内部需要重新同步而且 WREN 也要重发。连续写入时我在一次 CS 有效期内写完所有数据中间不释放片选。第二个优化是用 DMA 或者中断驱动 SPI。PIC18LF4680 没有 DMA但可以用中断驱动。不过对于 2.5MHz 的 SPI 来说中断开销反而可能拖慢速度我实测下来还是轮询方式最快。如果换成带 DMA 的 ARM 芯片那又是另一回事了。第三个优化是写入缓冲。采集到的数据先存在 RAM 缓冲区里攒够 64 字节或者每隔 100ms 批量写入一次 MRAM。这样既减少了 SPI 传输次数又降低了功耗。注意批量写入时如果掉电缓冲区里未写入的数据会丢。所以掉电检测中断里必须把缓冲区数据紧急刷入 MRAM这个逻辑不能省。5.3 功耗考量MR25H40CDF 的写入电流典型值 15mA读取电流 8mA待机电流 100uA。对于电池供电的设备功耗是个大问题。我的做法是不写入时把 CS 拉高MRAM 自动进入待机模式采集间隔较长时甚至可以把 PIC18LF4680 也进入睡眠模式用定时器唤醒。实测下来每秒写入一次、每次写 64 字节的场景下平均功耗大概 2mA 左右用 2000mAh 的锂电池可以跑 40 天以上。6. 常见问题排查与避坑指南6.1 写入失败排查流程写入失败是最常见的问题排查思路如下现象可能原因排查方法完全写不进去WP 引脚悬空或拉低测量 WP 引脚电压偶尔写入失败CS 时序不对逻辑分析仪抓 CS 和 SCK 波形写入后读出来是 0xFFWREN 没发或 WEL 未置位读状态寄存器确认 WEL 位写入后数据错位地址字节顺序错误确认地址是 24 位还是 16 位高速写入失败SCK 频率超过芯片上限降低 SPI 时钟6.2 我踩过的几个坑第一个坑是WP 引脚悬空。一开始我觉得 WP 不用就不接结果芯片内部状态不确定有时候能写有时候不能写折腾了半天才发现是 WP 的问题。后来老老实实接到 GPIO 上问题解决。第二个坑是CS 拉高后立即拉低。MRAM 在 CS 拉高后需要一定时间完成内部操作如果立即拉低开始下一次传输芯片可能还没准备好。我在两次传输之间加了 1us 的延时问题消失。第三个坑是电源去耦不足。写入时电流尖峰导致电压跌落PIC18 复位整个系统重启。后来在 MRAM 的 VDD 引脚旁边加了 0.1uF 和 10uF 并联去耦问题解决。第四个坑是SPI 模式配置错误。MR25H40CDF 支持模式 0 和模式 3我一开始配成了模式 1结果读出来的数据全是乱的。后来查手册确认是模式 0改过来就正常了。6.3 调试工具推荐逻辑分析仪是必备的我用的是 Saleae 的 8 通道版本抓 SPI 波形非常方便。没有逻辑分析仪的话至少要用示波器看 SCK 和 CS 的时序。软件层面可以在代码里加调试打印把关键寄存器的值通过 UART 输出到串口助手帮助定位问题。实操心得调试 SPI 时先把 SCK 频率降到最低比如 100kHz确认通信正常后再逐步提高。这样可以把时序问题和逻辑问题分开排查效率高很多。7. 方案扩展与个人体会这套 MRAM 加 PIC18LF4680 的方案我已经在三个项目里用过了最长的已经连续运行两年多没有出现过数据丢失。后来有项目需要更大容量我换成了 MR25H40CDF 的升级型号 MR25H256容量翻倍引脚兼容代码几乎不用改。如果要做更高速的采集PIC18LF4680 的 SPI 可能成为瓶颈可以考虑换成带 DMA 的 32 位 MCU比如 STM32 系列SPI 可以跑到 20MHz 以上配合 MRAM 的 40MHz 上限带宽能到 2.5MB/s应付音频采集都够了。最后分享一个小技巧MRAM 的存储单元在出厂时可能不是全 0 或全 1首次使用前建议先做一次全片擦除其实就是全写 0这样后续的读写测试才有基准。另外MRAM 对磁场敏感虽然芯片内部有屏蔽层但尽量不要把芯片放在大电流走线或者变压器旁边否则可能影响数据保持特性。
返回列表