首页 / 资讯中心 / 文章详情

MRAM与PIC18LF47K40的SPI存储方案:工业嵌入式非易失性数据存储实践

MRAM与PIC18LF47K40的SPI存储方案:工业嵌入式非易失性数据存储实践 ★ FEATURED ARTICLE
1. 为什么偏偏是 MRAM 加 PIC18 这个组合1.1 从两个器件的定位说起MR25H40CDF 是一颗 4Mbit 的磁阻随机存取存储器也就是 MRAM接口是标准 SPI。PIC18LF47K40 是 Microchip 自家的一款 8 位单片机带 SPI 外设工作电压范围宽低功耗版本在 1.8V 到 3.6V 之间都能跑。把这两颗芯片放在一起本质上是在解决一个很具体的问题工业现场和嵌入式设备里需要一种既能像 RAM 一样快速随机写入、又能在断电后保住数据的存储介质同时主控又不想上太复杂的文件系统或者外部存储控制器。我最早接触这个组合是在一个数据采集终端项目上。那个设备每分钟要记录一次传感器读数包括温度、压力、流量还要保存设备运行状态和故障日志。一开始用的是 EEPROM写次数很快就到瓶颈了一天写一千多次一年就是三十多万次EEPROM 的百万次擦写寿命看着够但实际工业环境里温度一高寿命衰减比手册上写的快得多。后来换成 FRAM价格又上去了。再后来看到 MRAM读写速度接近 SRAM写寿命几乎无限功耗还低就决定试一试。MR25H40CDF 的 4Mbit 容量换算过来是 512KB对于存日志、配置参数、校准数据、断点续传的缓存来说这个容量刚刚好。它不需要像 Flash 那样先擦后写也不需要像 EEPROM 那样等写入周期完成SPI 接口直接发命令就能读写对 PIC18 这种资源有限的 8 位机来说驱动写起来非常轻。1.2 SPI 接口在这套方案里的角色SPI 是这套方案里唯一的通信桥梁。PIC18LF47K40 的 MSSP 模块可以配置成 SPI 主模式MR25H40CDF 作为从设备四根线SCK、SDI、SDO、CS。这里有个细节MR25H40CDF 的 SPI 模式支持 Mode 0 和 Mode 3也就是 CPOL 和 CPHA 的组合。实际用下来Mode 0 最稳因为大多数 8 位机的 SPI 外设默认就是 Mode 0时钟空闲低电平数据在上升沿采样。SPI 时钟频率方面MR25H40CDF 最高支持 40MHz但 PIC18LF47K40 在 3.3V 下 SPI 时钟最快也就 10MHz 左右具体取决于系统时钟和分频设置。我一般会把 SPI 时钟设在 4MHz 到 8MHz 之间原因有两个一是 8 位机的 SPI 移位寄存器在高速下容易受 PCB 走线影响二是 MRAM 本身读写没有等待周期4MHz 已经足够快没必要为了那点速度去冒信号完整性的风险。注意MR25H40CDF 的 CS 建立时间和保持时间要留够手册上写的是 CS 下降沿到第一个 SCK 上升沿至少 5ns最后一个 SCK 下降沿到 CS 上升沿至少 5ns。实际布线时如果 CS 走线太长建议在软件里加几个 NOP 再拉 CS别小看这几个纳秒我遇到过因为 CS 时序不对导致读出来全是 0xFF 的情况。1.3 这套方案适合谁如果你在做工业数据记录仪、智能仪表、医疗设备、车载终端或者任何需要频繁写入小量数据、又不想上文件系统的嵌入式项目这套组合值得认真考虑。它不适合大容量存储场景512KB 存不了音频和图像但存参数、日志、状态机快照、掉电保护数据绰绰有余。对嵌入式软件工程师来说驱动写起来不复杂对硬件工程师来说外围电路简单不需要额外的电荷泵或者升压电路。2. MR25H40CDF 的底层操作逻辑2.1 命令集与地址空间MR25H40CDF 的命令集很精简核心就几条WREN、WRDI、RDSR、WRSR、READ、WRITE。地址是 19 位因为 4Mbit 等于 512K 字节需要 19 根地址线才能覆盖。SPI 传输时地址分三个字节发第一个字节的最高三位是无效的实际有效的是低 5 位加上后面两个完整字节凑成 19 位。读操作最简单拉低 CS发 READ 命令 0x03接着发三个字节地址然后就可以连续读数据了地址会自动递增读到 512KB 末尾会回卷到 0。写操作稍微多一步先发 WREN 命令 0x06拉高 CS再拉低 CS发 WRITE 命令 0x02发地址发数据。写完之后 CS 拉高数据才真正写入 MRAM 单元。这里有个和 Flash 完全不同的地方MRAM 不需要擦除。Flash 写之前必须先擦整个扇区MRAM 直接覆盖写就行。这意味着你的驱动里不需要维护擦除状态也不需要做磨损均衡。我一开始还习惯性地想加擦除逻辑后来发现完全是多余的。2.2 状态寄存器与写保护状态寄存器里有两个位值得关注WEL 和 BP0、BP1。WEL 是写使能锁存每次写操作前必须通过 WREN 命令置位写完成后自动清零。BP0 和 BP1 是块保护位可以保护部分地址空间不被写入。工业场景里我一般会把配置参数区设成写保护防止程序跑飞时误改校准数据。状态寄存器的读法拉低 CS发 RDSR 命令 0x05然后读一个字节。这个字节的 bit0 是 WELbit1 是 WEL 的镜像bit2 和 bit3 是 BP0 和 BP1bit7 是状态寄存器的写保护位。实际调试时如果写不进去第一件事就是读状态寄存器看 WEL 有没有置起来。2.3 时序参数与实操计算MR25H40CDF 的读写周期时间很短手册上写的是 35ns 左右但这是芯片内部的操作时间SPI 接口的传输时间才是瓶颈。假设 SPI 时钟 4MHz一个字节 8 位传输时间是 2 微秒。写一个 32 字节的数据包加上命令和地址开销总共 36 字节传输时间约 72 微秒。如果每分钟写一次平均功耗几乎可以忽略。但如果你要做高频采集比如每毫秒写一次那就要算一下总线占用率。每毫秒 72 微秒占用率 7.2%对 PIC18 来说完全可以接受。我实测过在 8MHz SPI 时钟下连续写 512 字节的缓冲区耗时大约 520 微秒包括 CS 翻转和命令开销。提示MRAM 的写操作是即时完成的不像 Flash 需要等待内部编程周期。所以 WRITE 命令发完CS 拉高之后数据就已经在 MRAM 里了不需要轮询状态寄存器等待写完成。这一点在掉电保护场景里特别重要断电瞬间只要 CS 已经拉高数据就安全了。3. PIC18LF47K40 的 SPI 外设配置3.1 MSSP 模块初始化步骤PIC18LF47K40 的 SPI 用 MSSP1 模块配置步骤我习惯按这个顺序来设置 TRIS 寄存器把 SCK、SDO 设成输出SDI 设成输入CS 用普通 GPIO 控制。关闭 MSSP 模块清零 SSP1CON1 的 SSPEN 位。设置 SSP1STAT 的 CKE 位和 SSP1CON1 的 CKP 位确定 SPI 模式。Mode 0 对应 CKE0、CKP0。设置 SSP1CON1 的 SSPM 位选主模式时钟分频。比如 SSPM0b0000 是 Fosc/4。使能 MSSP置位 SSPEN。配置 CS 引脚为输出默认拉高。这里有个坑PIC18 的 SPI 发送和接收是同一个寄存器 SSP1BUF。写 SSP1BUF 启动发送读 SSP1BUF 获取接收数据。发送完成标志是 SSP1IF但实际用的时候我一般直接轮询 SSP1STAT 的 BF 位BF 置位表示接收缓冲区满这时候读 SSP1BUF 就能拿到数据。3.2 字节收发函数的写法底层字节收发函数是整个驱动的基础我一般写成这样uint8_t spi_transfer(uint8_t data) { SSP1BUF data; while (!SSP1STATbits.BF); return SSP1BUF; }这个函数看起来简单但有几个细节要注意。第一SSP1BUF 是双缓冲的写进去之后硬件自动开始移位读的时候如果 BF 没置位读出来的是上次的旧数据。第二在高速 SPI 下while 循环等待 BF 的时间很短但如果编译器优化等级太高可能会把循环优化掉建议加 volatile 或者用 NOP 填充。CS 的控制我单独写函数void mram_cs_low(void) { LATCbits.LATC0 0; __delay_us(1); } void mram_cs_high(void) { __delay_us(1); LATCbits.LATC0 1; }那个 1 微秒的延时是为了满足 CS 建立和保持时间虽然手册上只要求 5ns但实际 PCB 上走线有电容多等一点更稳。3.3 读写函数的完整实现读函数void mram_read(uint32_t addr, uint8_t *buf, uint16_t len) { mram_cs_low(); spi_transfer(0x03); spi_transfer((addr 16) 0x07); spi_transfer((addr 8) 0xFF); spi_transfer(addr 0xFF); for (uint16_t i 0; i len; i) { buf[i] spi_transfer(0x00); } mram_cs_high(); }写函数void mram_write(uint32_t addr, uint8_t *buf, uint16_t len) { mram_cs_low(); spi_transfer(0x06); mram_cs_high(); mram_cs_low(); spi_transfer(0x02); spi_transfer((addr 16) 0x07); spi_transfer((addr 8) 0xFF); spi_transfer(addr 0xFF); for (uint16_t i 0; i len; i) { spi_transfer(buf[i]); } mram_cs_high(); }注意地址的高 3 位被屏蔽掉了因为 19 位地址最大就是 0x7FFFF。如果你传的地址超过这个范围实际会回卷所以上层调用时要自己保证地址合法。注意WREN 命令和 WRITE 命令之间必须拉高 CS这是 MRAM 的协议要求。我见过有人把 WREN 和 WRITE 放在同一个 CS 低电平周期里结果写不进去。手册上写得很清楚WREN 之后 CS 必须有一个上升沿写使能锁存才会生效。4. 工业场景下的数据存储策略4.1 日志存储的环形缓冲区设计工业设备日志的特点是写入频繁、读取少、需要保留最近一段时间的数据。我在 MRAM 里划出 256KB 做日志区用环形缓冲区管理。每条日志固定 32 字节包含时间戳、传感器数据、状态码、校验和。写指针和读指针存在 MRAM 的固定地址里每次写入后更新写指针。环形缓冲区的容量是 256KB 除以 32 字节等于 8192 条日志。按每分钟一条算可以存 5 天多的数据。如果设备每天上传一次完全够用。写指针和读指针的更新要原子操作我一般先写数据再更新写指针最后更新校验。如果断电发生在写数据之后、更新指针之前那条日志会被视为无效下次启动时跳过。4.2 配置参数的双备份机制配置参数区我放在 MRAM 的前 4KB分成两份每份 2KB互为备份。每份包含一个头部头部里有版本号、长度、CRC32 校验。写入时先写备份区再写主区。读取时先读主区校验失败再读备份区。如果两个都失败加载默认参数。这个双备份机制在工业现场救过我好几次。有一次设备在写入参数时突然断电主区的 CRC 校验失败但备份区是完整的设备启动后自动恢复了参数没有停机。4.3 掉电保护与数据完整性MRAM 的最大优势就是掉电不丢数据但前提是数据已经写进去了。在掉电检测电路触发中断后PIC18 还有几十毫秒的供电时间足够把关键数据写进 MRAM。我一般会在掉电中断里做三件事保存当前状态机快照、更新日志写指针、写一个掉电标记。下次上电时看到掉电标记就知道上次是非正常关机可以做相应的恢复处理。提示掉电中断里不要做复杂计算只做最必要的写入。MRAM 写入速度快但 SPI 传输需要时间32 字节的数据在 4MHz SPI 下需要约 72 微秒加上命令开销不到 100 微秒。只要供电电容够大完全来得及。5. 调试过程中踩过的坑与排查方法5.1 SPI 读出来全是 0xFF 或 0x00这是最常见的问题。0xFF 通常表示 MISO 线一直高电平0x00 表示一直低电平。排查顺序先确认 CS 有没有正常拉低。用示波器看 CS 引脚如果一直是高说明 GPIO 配置错了。确认 SCK 有没有波形。如果没有检查 MSSP 模块有没有使能SSPM 位有没有设对。确认 MISO 线有没有接对。MR25H40CDF 的 SDO 接 PIC 的 SDI很多人会接反。确认 MRAM 的供电和地有没有接好。我遇到过因为地线虚焊导致读出来全是 0xFF 的情况。5.2 写入后读出来数据不对写入不成功的原因通常有几个WREN 命令没有正确发送或者 WREN 和 WRITE 之间没有 CS 上升沿。地址超出了 19 位范围导致回卷到错误位置。状态寄存器的块保护位被设置了目标地址被保护。SPI 模式不对MRAM 支持 Mode 0 和 Mode 3如果 PIC 配成 Mode 1 或 Mode 2数据会错位。我一般会写一个测试函数先往地址 0x00000 写 0x55再读出来对比。如果不对再往 0x7FFFF 写 0xAA读出来对比。这样可以快速定位是地址问题还是数据问题。5.3 高速 SPI 下的数据错误当 SPI 时钟超过 8MHz 时如果 PCB 走线太长或者没有阻抗匹配容易出现数据错误。解决方法降低 SPI 时钟到 4MHz 测试如果正常说明是信号完整性问题。在 SCK 和 MOSI 上串 22 欧姆到 33 欧姆的电阻减少反射。缩短走线尽量让 PIC 和 MRAM 靠近。在 MRAM 的 VCC 引脚旁边加 0.1uF 和 1uF 的退耦电容。5.4 常见问题速查表现象可能原因排查方法读出来全是 0xFFMISO 未连接或 CS 未拉低示波器看 CS 和 MISO 波形读出来全是 0x00MISO 对地短路或 MRAM 未供电万用表测 MISO 对地电阻写入后数据不变WREN 未生效或块保护读状态寄存器看 WEL 和 BP 位数据错位SPI 模式不对确认 CPOL0, CPHA0高速下偶发错误信号完整性降速测试加串阻地址回卷地址超过 0x7FFFF检查地址计算逻辑6. 性能实测与优化建议6.1 读写速度实测数据我在 PIC18LF47K40 上跑了几个测试系统时钟 64MHzSPI 时钟 8MHz结果如下操作数据量耗时单字节写1 字节约 8 微秒32 字节写32 字节约 45 微秒512 字节写512 字节约 520 微秒单字节读1 字节约 6 微秒512 字节读512 字节约 515 微秒写操作比读操作多了一个 WREN 命令和 CS 翻转所以单字节写比单字节读慢一点。但批量写的时候WREN 的开销被摊薄了512 字节写的耗时和读差不多。6.2 功耗优化MR25H40CDF 的待机电流很低典型值 10 微安左右。写操作时电流会升到几毫安但持续时间很短。对于电池供电的设备我一般会在不访问 MRAM 时把 CS 拉高让 MRAM 进入待机模式。PIC18 的 SPI 模块在不使用时也可以关闭进一步降低功耗。实测下来一个每分钟写一次日志的设备MRAM 的平均功耗不到 1 微安对整体功耗几乎没有影响。6.3 代码优化技巧把常用的读写函数放在 RAM 里执行减少 Flash 访问时间。用 DMA 或者中断驱动的 SPI 传输减少 CPU 等待时间。PIC18LF47K40 没有 DMA但可以用 SPI 中断。批量读写时尽量一次传输多个字节减少 CS 翻转次数。地址计算用位操作代替乘除法PIC18 的乘除法比较慢。提示如果你用的是 XC8 编译器记得把优化等级开到 -O2 或者 -O3否则 SPI 传输函数里的循环会生成很冗余的代码。我对比过优化前后 512 字节写的耗时差了将近 30%。7. 这套方案还能怎么扩展MR25H40CDF 加 PIC18LF47K40 的组合本质上是一个小型的非易失性数据存储系统。如果你需要更大的容量可以并联多颗 MRAM用不同的 CS 控制。如果需要更高的可靠性可以在 MRAM 里做三副本存储每次写入写三份读取时投票。如果需要和上位机通信可以在 PIC18 上跑一个简单的 Modbus 协议把 MRAM 里的数据映射成寄存器。我在一个项目里把 MRAM 分成了四个区参数区、日志区、缓存区、固件备份区。固件备份区存的是 PIC18 的固件镜像配合 Bootloader 可以实现远程升级。升级失败时Bootloader 从 MRAM 里读回旧固件恢复运行。这个方案在工业现场很实用因为现场设备拆机烧录的成本太高。最后分享一个小技巧MRAM 的写寿命几乎是无限的但 SPI 接口的 CS 引脚有机械寿命限制如果你用继电器控制 CS那寿命就受限了。所以 CS 一定要用 GPIO 直接控制不要经过任何机械开关。另外MRAM 的数据保持时间在 85 度下是 10 年如果设备工作在更高温度建议定期刷新数据比如每年重写一次关键参数区。
阅读完成 · 觉得有帮助?
咨询建站