ARTICLE DETAIL

资讯详情

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

MRAM与PIC18F45K22工业数据存储方案:SPI驱动与掉电保护实战

MRAM与PIC18F45K22工业数据存储方案:SPI驱动与掉电保护实战 1. 项目缘起与方案选型为什么是 MRAM 加 PIC18F45K22工业现场的数据记录有个很尴尬的处境用 EEPROM 吧写入速度慢、擦写寿命也就百万次级别遇到高频采集场景比如电机振动监测、电表脉冲计数一天写个几万次几个月就把片子写废了用 SRAM 加后备电池吧电池在高温高湿的车间里撑不了几年漏液、掉电丢数据都是老问题用 Flash 吧写之前得擦一整块掉电瞬间正好在擦除整块数据全没了这在工业里是致命的。MR25H40CDF 这块 MRAM 就是冲着这些痛点来的。它是 Everspin 家的 4Mbit 磁性随机存储器SPI 接口最关键的一点写入不需要擦除、没有写延迟、掉电即存。你往里面写一个字节它当场就进去了不像 Flash 那样还得等擦除周期。官方标称的读写 endurance 是 10 的 14 次方量级按每天写 10 万次算够你写几千年。工作温度覆盖 -40 到 85 摄氏度工业级场景完全扛得住。主控这边选 PIC18F45K22理由也很实在。它是 Microchip 8 位 PIC18 家族里比较经典的一款44 引脚自带 MSSP 模块就是硬件 SPI/I2C 控制器3.3V 和 5V 都能跑工业现场那些 5V 的老传感器、老继电器接口对接起来不用额外电平转换。片上 64KB Flash、3896 字节 RAM跑一个数据记录仪的状态机绰绰有余。开发工具链成熟XC8 编译器、MPLAB X IDE、PICkit 系列烧录器成本低资料全小批量工业项目用它做原型验证非常合适。这个组合的核心价值在于用一颗不需要擦除、不怕掉电、寿命近乎无限的存储芯片配一颗皮实耐用、外设够用、开发门槛低的 8 位 MCU搭出一套能在工业现场长期稳定运行的数据存储方案。适合谁看做工业仪表、数据采集终端、设备黑匣子、智能电表、PLC 扩展模块的嵌入式工程师尤其是那些被 EEPROM 寿命和 Flash 掉电问题折磨过的朋友。下面我把整个方案从硬件连接到软件驱动再到实际踩过的坑完整拆一遍。2. 硬件连接与 SPI 时序别小看那几根线2.1 引脚对接与片选策略MR25H40CDF 是标准的 8 引脚 SOIC 封装引脚定义很清爽VDD、VSS、SCK、SI、SO、CS、WP、HOLD。其中 WP 是写保护HOLD 是暂停通信这两个脚在大多数应用里直接拉高就行除非你要做硬件级写保护。和 PIC18F45K22 对接我用的是 MSSP1 模块的 SPI 模式。具体引脚分配如下MR25H40CDF 引脚PIC18F45K22 引脚说明VDD3.3V供电注意去耦电容VSSGND共地SCKRC3/SCK硬件 SPI 时钟SIRC5/SDI主机输出从机输入SORC4/SDO主机输入从机输出CSRA5普通 GPIO 做片选WP3.3V拉高禁用写保护HOLD3.3V拉高保持通信这里有个关键选择片选用 GPIO 软件控制不用 MSSP 的硬件 SS 功能。原因很简单PIC18F45K22 的硬件 SS 在 SPI 从机模式下才有意义主机模式下它就是个普通 IO而且用硬件 SS 反而容易在初始化时序上出幺蛾子。用 RA5 手动拉低拉高时序完全可控调试的时候用逻辑分析仪一看就明白。注意CS 引脚一定要接一个 10K 上拉电阻到 3.3V。我遇到过一批板子MCU 复位期间 IO 是高阻态CS 悬空被干扰拉低MRAM 误以为主机在跟它通信结果上电后读出来的 ID 全是乱的。加上拉之后问题消失。2.2 SPI 模式与时钟配置MR25H40CDF 支持 SPI 模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。我选的是模式 0因为 PIC18F45K22 的 MSSP 在模式 0 下配置最直观空闲时 SCK 为低数据在 SCK 上升沿采样。时钟频率方面MR25H40CDF 最高支持 40MHz但 PIC18F45K22 在 64MHz 系统时钟下MSSP 的 SPI 最高只能到 Fosc/4也就是 16MHz。实际我用的是8MHz也就是 SSPADD 设置为 (Fosc/(4*SPI_CLK))-1。假设系统时钟 64MHz要得到 8MHz SPI 时钟SSPADD (64000000 / (4 * 8000000)) - 1 2 - 1 1等等这里算出来是 1但 SSPADD 最小值在实际使用中建议不低于 1否则时序余量太小。我实测 8MHz 下用杜邦线连接都能稳定读写但如果你板子走线比较长建议降到 4MHz 甚至 2MHz工业现场电磁干扰大稳比快重要。配置 MSSP 的寄存器设置如下// SPI 模式 0时钟 Fosc/4 * (SSPADD1) 的倒数 SSP1STATbits.CKE 1; // 时钟沿选择 SSP1STATbits.SMP 0; // 在数据输出末尾采样 SSP1CON1bits.CKP 0; // 空闲时 SCK 为低 SSP1CON1bits.SSPM 0b0000; // SPI 主机模式时钟 Fosc/4 SSP1CON1bits.SSPEN 1; // 使能 MSSP实操心得PIC18F45K22 的 MSSP1 和 MSSP2 是两个独立模块我习惯用 MSSP1 接 MRAMMSSP2 留给其他外设比如显示屏或者第二颗传感器。这样两个 SPI 总线互不干扰代码里也好管理。2.3 上电初始化时序MR25H40CDF 上电后需要一段稳定时间官方数据手册写的是 VDD 达到最低工作电压后等待 tPUPower-Up Time典型值 1ms 左右。我的做法是在 MCU 初始化完成后先延时 10ms再发一个 WREN 指令然后读状态寄存器确认 WEL 位被置起来说明芯片已经准备好通信了。这个步骤很多人会跳过直接上电就读 ID结果偶尔读出来全是 0xFF。原因就是 MRAM 内部还在上电复位SPI 接口没就绪。加上这 10ms 延时和状态检查批量生产了几百台没再出现过上电读不到的问题。3. 驱动层实现从字节收发到页读写3.1 底层字节收发函数SPI 通信的本质就是交换字节主机发一个字节出去从机同时回一个字节进来。PIC18F45K22 的 MSSP 模块有 SSPBUF 寄存器写进去一个字节就开始发送同时接收到的数据也会进 SSPBUF。所以底层函数要做的就是写 SSPBUF等 BF 位Buffer Full置起读 SSPBUF。uint8_t SPI_ExchangeByte(uint8_t data) { SSP1BUF data; // 写入要发送的字节 while (!PIR1bits.SSP1IF); // 等待发送完成 PIR1bits.SSP1IF 0; // 清除中断标志 return SSP1BUF; // 返回接收到的字节 }这个函数看起来简单但有个细节读 SSPBUF 之前必须先清 SSP1IF 标志否则下次等待会直接跳过。我见过有人忘了清标志结果 SPI 读写速度飞快但数据全错查了半天以为是硬件问题。3.2 MRAM 指令集与操作码MR25H40CDF 的指令集和标准 SPI Flash 很像但少了擦除相关的指令。核心指令如下指令名称操作码功能说明WREN0x06写使能写操作前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据RDID0x9F读设备 ID写操作之前必须发 WREN这是硬性要求。MRAM 不像 SRAM 那样随时可写它内部有个写使能锁存器只有 WREN 置位后才能执行 WRITE 指令。写完一个页或者一个字节序列后WEL 位会自动清零下次写之前还得再发 WREN。读设备 ID 是个很好的自检手段。发 0x9F 之后MR25H40CDF 会返回 3 个字节制造商 ID0x18、存储类型0x03、容量代码0x03 表示 4Mbit。上电自检的时候读一下这三个字节对不上就说明硬件连接有问题。3.3 页写与地址边界处理MR25H40CDF 的存储空间是 512K 字节按 256 字节一页划分总共 2048 页。写操作有个坑如果你从页中间开始写写到页尾会自动回卷到页首覆盖掉页首的数据。比如从地址 0x00FF 开始写 4 个字节实际会写到 0x00FF、0x0000、0x0001、0x0002而不是跨到下一页。这个行为和 EEPROM 的页写一模一样但很多人第一次用 MRAM 会以为它像 SRAM 一样线性写入。我当初就踩过这个坑在一个数据记录项目里从奇数地址开始连续写 300 个字节结果每页的前几个字节被莫名其妙覆盖查了一整天才定位到页回卷问题。解决办法很简单写之前先算好当前地址到页尾还剩多少字节如果剩余空间不够就分两次写。代码逻辑如下void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { while (len 0) { uint16_t page_remain 256 - (addr % 256); // 当前页剩余空间 uint16_t write_len (len page_remain) ? len : page_remain; MRAM_WritePage(addr, buf, write_len); // 实际写页函数 addr write_len; buf write_len; len - write_len; } }这样不管从哪个地址开始写都能保证不跨页回卷。代价是写大块数据时会分成多次页写但 MRAM 没有擦除时间每次页写也就几十微秒整体速度依然比 Flash 快得多。3.4 状态寄存器与写保护状态寄存器里有两个位值得关注WEL写使能锁存和 BP0/BP1块保护。WEL 在每次 WREN 后置 1写操作完成后自动清 0。BP 位可以硬件锁定部分存储区域防止误写。我在工业项目里的做法是把 bootloader 配置区、设备序列号区用 BP 位锁起来只留数据记录区可写。这样即使程序跑飞了乱写也不会把关键信息覆盖掉。配置方法就是发 WRSR 指令把对应的 BP 位写 1。注意WRSR 本身也需要先发 WREN。而且写状态寄存器之后BP 位的生效需要等到下一次上电或者发 WRDI 再 WREN 才刷新。这个细节数据手册里写得很隐蔽我是在调试时发现锁不住翻了好几遍手册才找到。4. 完整读写流程与实测数据4.1 单字节读写实测先看最简单的单字节读写。写一个字节到地址 0x000100然后读回来验证// 写单字节 void MRAM_WriteByte(uint32_t addr, uint8_t data) { CS_LOW(); SPI_ExchangeByte(0x06); // WREN CS_HIGH(); CS_LOW(); SPI_ExchangeByte(0x02); // WRITE SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); SPI_ExchangeByte(data); CS_HIGH(); // 等待写完成MRAM 写周期典型 35ns但保守等 1us __delay_us(1); } // 读单字节 uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; CS_LOW(); SPI_ExchangeByte(0x03); // READ SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); data SPI_ExchangeByte(0xFF); // 发 dummy 字节读回数据 CS_HIGH(); return data; }实测下来在 8MHz SPI 时钟下单字节写从拉 CS 到拉高 CS 大约 5 微秒读大约 4 微秒。连续写 256 字节一页总耗时约 300 微秒平均每个字节 1.2 微秒。这个速度在 8 位 MCU 上已经相当可观了比同频率下的 EEPROM 快两个数量级。4.2 批量数据记录场景我拿这个方案做过一个电机振动监测的记录仪PIC18F45K22 用 ADC 每 1ms 采一次三轴加速度每 100 次采样打包成一帧每帧 64 字节写入 MRAM。算下来每秒写 10 帧也就是 640 字节每秒。MRAM 的写入速度完全跟得上瓶颈反而在 ADC 采样和数据处理上。连续跑了 72 小时写入了大约 1.6 亿字节MRAM 没有任何异常。后来我算了一下按这个写入速率要写满 10 的 14 次方次 endurance得连续跑几百年。4.3 掉电数据保持测试这是 MRAM 最让我放心的地方。我做了个粗暴的测试在连续写入的过程中直接拔掉电源然后重新上电读取最后写入的地址。反复做了 50 次每次读出来的数据都是完整的没有出现半写或者乱码。对比之前用 Flash 的方案掉电测试十次里有兩三次会出现最后一块数据全 0xFF 或者部分字节错乱。MRAM 的写入是原子级的一个字节要么写进去要么没写进去不存在中间状态。对于工业黑匣子这种应用这个特性值千金。5. 常见问题与排查技巧实录5.1 读出来全是 0xFF 或 0x00这是最常遇到的问题原因通常有三个第一CS 引脚没有正确拉低。用示波器或者逻辑分析仪看 CS 波形确认每次通信前 CS 确实被拉低了。如果 CS 一直高MRAM 根本不理你。第二SPI 模式不匹配。MR25H40CDF 支持模式 0 和模式 3如果你 MCU 配的是模式 1 或模式 2数据采样沿不对读回来就是乱的。检查 SSP1CON1 和 SSP1STAT 的配置。第三上电初始化延时不够。前面说过上电后至少等 1ms 再通信我一般延时 10ms 保险。5.2 写入后读出来数据不对先检查 WREN 有没有发。MRAM 每次写之前都必须发 WREN如果漏了WRITE 指令会被忽略数据根本没写进去。再检查地址边界。如果你从非页对齐的地址开始写超过页尾的数据会发生页回卷。用前面给的 MRAM_Write 函数自动处理边界。还有一个隐蔽的问题写操作后没有等待足够时间就发下一条指令。虽然 MRAM 写周期只有 35ns但如果你紧接着发 READ而 CS 还没拉高MRAM 可能还在处理写操作。我的做法是每次写操作后拉高 CS延时 1 微秒再开始下一次通信。5.3 SPI 通信不稳定偶尔出错工业现场电磁干扰大SPI 时钟线容易耦合噪声。几个改善措施降低 SPI 时钟频率从 8MHz 降到 4MHz 甚至 2MHzSCK、SI、SO 线上串 22 到 100 欧姆的电阻抑制振铃CS 线加上拉电阻防止悬空误触发PCB 走线尽量短SPI 线远离电机驱动、继电器等干扰源我有个项目现场有变频器一开始 SPI 用 8MHz 偶尔读错数据降到 2MHz 并加上串阻后连续运行半年没再出问题。5.4 常见问题速查表现象可能原因排查方法解决措施读 ID 全 0xFFCS 未拉低、SPI 模式错逻辑分析仪看波形检查 CS 和 SPI 配置写入后读回不对漏发 WREN、页回卷检查写函数逻辑补 WREN处理页边界偶发数据错误时钟太快、干扰降低时钟测试降频、加串阻、加去耦上电读不到初始化延时不够增加延时测试延时 10ms 再通信状态寄存器写不进漏发 WREN读 WEL 位WRSR 前先 WREN独家避坑技巧我习惯在 MRAM 的第一个扇区写一个固定的测试模式比如 0xAA、0x55 交替。每次上电自检时先读这个区域对不上就报错。这个简单的自检能提前发现 90% 的硬件连接问题比读 ID 还管用。6. 工程化建议与扩展思路6.1 数据存储结构设计在实际项目里我不会把数据裸写进 MRAM而是设计一个简单的文件系统结构。通常分三个区系统区存设备序列号、固件版本、校准参数用 BP 位锁死索引区存数据帧的起始地址、长度、时间戳循环覆盖数据区存实际的采集数据按页对齐写入索引区用循环队列管理每写一帧数据就更新一次索引。这样即使掉电也能通过索引快速定位到有效数据。索引区本身也用双备份加校验和防止单点损坏。6.2 与上位机的数据导出MRAM 里的数据最终要导出来。我的做法是留一个 UART 接口上位机发特定指令MCU 从 MRAM 读数据通过串口发出去。协议很简单上位机发 0xAA 0x55 加起始地址和长度MCU 回数据加 CRC 校验。因为 MRAM 读取速度远高于串口波特率所以导出速度瓶颈在串口。115200 波特率下导出 512KB 数据大约需要 45 秒。如果数据量大可以考虑用 SD 卡做二级存储MRAM 只做掉电瞬间的缓存。6.3 低功耗场景的考量MRAM 的待机电流典型值 10 微安左右读写时大约 5 毫安。对于电池供电的工业传感器可以在不写数据的时候把 CS 拉高MRAM 自动进入低功耗待机。PIC18F45K22 本身也支持休眠模式需要记录数据时用定时器唤醒写完继续睡。我算过一笔账如果每 10 秒写一次数据每次写 64 字节耗时约 100 微秒平均电流可以做到 50 微安以下。用一节 2000mAh 的锂亚电池理论续航超过 4 年。当然实际还要算上传感器和 MCU 休眠的功耗但 MRAM 这部分几乎可以忽略。6.4 从 4Mbit 扩展到更大容量MR25H40CDF 是 4Mbit512KB如果项目需要更大容量有两个方向一是换用同系列的更大容量型号比如 MR25H40 系列往上还有 16Mbit 的版本引脚兼容驱动改一下地址字节数就行二是用多颗 MRAM 并联每颗独立 CS软件上做地址映射。我试过用两颗 MR25H40CDF 拼成 1MB 存储空间CS 分别接 RA5 和 RA4读写时根据地址高字节选择片选。代码改动很小就是在 CS 控制上加个判断。这个方案适合需要大容量但又不想换主控的项目。6.5 代码组织与移植性最后说一下代码组织。我把 MRAM 驱动分成三层最底层是 SPI 字节收发中间层是指令封装WREN、READ、WRITE 等最上层是面向应用的读写接口按地址读写、按帧存储。这样换 MCU 的时候只需要改最底层的 SPI 函数上面两层完全不用动。我在 STM32 和 PIC 之间移植过这套驱动底层 SPI 函数改了不到 20 行上层业务代码一行没动。对于需要跨平台的项目这种分层设计能省很多事。个人体会MRAM 这东西刚上手会觉得比 EEPROM 贵不少但算上它省掉的擦除管理代码、掉电保护电路、电池更换维护成本整体方案成本反而可能更低。尤其是工业现场一次现场维护的差旅费就够买几十颗 MRAM 了。我在实际项目中越来越倾向于在关键数据存储环节用 MRAM省心。
返回列表