ARTICLE DETAIL

资讯详情

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

工业嵌入式存储选型:MRAM与dsPIC33FJ的SPI驱动实践

工业嵌入式存储选型:MRAM与dsPIC33FJ的SPI驱动实践 MRAM 这类存储介质在嵌入式圈子里讨论度一直不算高很多人做数据存储第一反应还是 EEPROM 或者 SPI Flash。但如果你做过工业现场的设备就会知道频繁写 Flash 有多难受——擦写寿命、写入延迟、掉电丢数据每一个都是坑。我最近在一个电机控制项目里用MR25H40CDF配合dsPIC33FJ256GP710A做参数存储和运行日志记录整套方案跑下来稳定性超出预期所以把选型思路、SPI 驱动实现、读写策略这些内容整理一下。这篇内容适合正在做工业控制、数据采集、嵌入式存储方案选型的工程师也适合刚接触 dsPIC 系列或者第一次用 MRAM 的朋友。我会从为什么选 MRAM 而不是 EEPROM/Flash 讲起然后一步步拆解硬件连接、SPI 初始化、读写函数实现、掉电保护策略最后分享几个实际调试中踩过的坑。代码基于 dsPIC33FJ256GP710A 的 SPI 模块用 C30 编译器整体思路对 STM32 或其他平台同样有参考价值。1. 为什么在这个项目里选了 MRAM 而不是 EEPROM 或 Flash1.1 工业场景对存储的真实需求先说说项目背景。这是一个工业电机控制器需要存储几类数据电机参数PID 系数、额定电流、极对数等、运行统计累计运行时间、启停次数、故障记录、以及实时采集的运行日志每 100ms 记录一次电流、温度、转速。参数数据量不大几百字节但运行日志是持续写入的一天下来写入次数轻松上万次。一开始我考虑的是外置 SPI Flash比如常见的 W25Q 系列。但算了一笔账就发现问题了Flash 的擦写寿命典型值是 10 万次按每天 1 万次写入算十天就把一个扇区写废了。虽然可以用磨损均衡算法分散写入但实现复杂度上去了而且 Flash 写入前必须先擦除整个扇区写入延迟动辄几毫秒到几十毫秒对于需要快速记录故障现场数据的场景来说太慢了。EEPROM 的寿命好一些典型 100 万次擦写字节级写入不需要擦除。但 EEPROM 的写入速度也不快而且容量普遍偏小做日志存储很快就满了。更关键的是EEPROM 写入过程中如果掉电数据可能处于不确定状态。MRAM磁阻随机存取存储器恰好解决了这几个痛点。MR25H40CDF 是 Everspin 出的 4Mbit SPI MRAM它的核心优势在于写入寿命几乎无限官方标称 10^14 次以上、写入速度接近 SRAM不需要擦除字节级随机写入、掉电数据不丢失。这三点对于工业数据存储来说简直是量身定做。1.2 MR25H40CDF 的关键参数解读选型的时候我把 MR25H40CDF 的 datasheet 翻了好几遍几个关键参数值得单独拎出来说参数数值实际意义容量512KB (4Mbit)存参数和日志绰绰有余接口SPI (Mode 0/3)和 dsPIC 的 SPI 模块直接对接最高时钟40MHz读取速度远超 EEPROM写入寿命10^14 次基本等于无限数据保持20 年工业设备生命周期内无忧工作电压2.7V ~ 3.6V3.3V 系统直接供电写入时间无延迟字节级不需要等待写完成这里要特别说一下写入时间无延迟这件事。MRAM 的写入是真正的随机访问你发一个写命令数据就写进去了不需要像 Flash 那样等待内部编程完成。这意味着你可以在 SPI 总线上连续写入中间不需要插入延时或者轮询状态寄存器。对于高频日志记录场景这个特性带来的效率提升非常明显。另外 MR25H40CDF 支持 SPI Mode 0 和 Mode 3这一点在对接 dsPIC 的时候要注意后面讲 SPI 初始化的时候会详细说。1.3 dsPIC33FJ256GP710A 的 SPI 资源匹配dsPIC33FJ256GP710A 是 Microchip 的一款 16 位数字信号控制器主频最高 40MIPS自带两个 SPI 模块SPI1 和 SPI2。我选它是因为这个项目需要做电机 FOC 控制对运算能力有要求同时它的外设资源足够丰富。SPI 模块这边dsPIC33FJ 的 SPI 支持 8 位和 16 位数据传输主从模式可配时钟极性CKP和时钟边沿CKE可以独立设置正好能匹配 MR25H40CDF 的 Mode 0 或 Mode 3 要求。最高 SPI 时钟可以做到 10MHz 以上取决于主频分频虽然跑不到 MR25H40CDF 的 40MHz 上限但对于存储读写来说完全够用。有一点需要注意dsPIC33FJ256GP710A 的 SPI 模块在 8 位模式下数据寄存器是 16 位的实际只用低 8 位。发送和接收是同时进行的读数据的时候需要先发一个 dummy 字节把数据顶出来。这个特性和 STM32 的 SPI 类似但寄存器操作方式不同后面代码部分会具体展开。2. 硬件连接与 SPI 时序匹配的细节2.1 引脚连接方案MR25H40CDF 是 8 引脚封装引脚定义很清晰VCC、GND、SCK、SI数据输入、SO数据输出、CS片选、WP写保护、HOLD保持。实际使用中 WP 和 HOLD 如果不用可以上拉到 VCC但我的建议是至少把 WP 引出来接到 GPIO这样可以在固件层面做写保护防止误写。和 dsPIC33FJ256GP710A 的连接我用了 SPI2 模块具体引脚分配如下SCK 接 dsPIC 的 SCK2具体引脚号根据封装查 datasheetSI 接 dsPIC 的 SDO2SO 接 dsPIC 的 SDI2CS 用一个普通 GPIO 控制我用的 RF5WP 接另一个 GPIORF4做软件写保护HOLD 直接上拉到 VCC这里有个细节CS 我特意没有用 SPI 模块的硬件片选SS而是用普通 GPIO 手动控制。原因后面会讲简单说就是硬件片选在多设备共享总线时容易出问题手动控制更灵活。2.2 SPI 模式选择Mode 0 还是 Mode 3MR25H40CDF 支持 SPI Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1。这两种模式的区别在于时钟空闲电平和数据采样边沿。Mode 0 下SCK 空闲为低电平数据在 SCK 上升沿采样Mode 3 下SCK 空闲为高电平数据在 SCK 上升沿采样。两种模式都能正常工作但我在实际调试中发现Mode 0 在长线传输时抗干扰能力稍好一些因为空闲低电平在总线浮空时不容易被误判为时钟信号。所以最终选了 Mode 0。在 dsPIC33FJ 上配置 Mode 0需要设置 SPI2CON1 寄存器的 CKP 位为 0空闲低电平CKE 位为 1数据在时钟边沿变化在下一个边沿采样。这里要特别注意 dsPIC 的 CKE 定义和有些平台是反的我第一次配的时候就是这里搞错了导致读出来的数据全是 0xFF。2.3 上拉电阻与信号完整性SPI 总线在高速运行时信号完整性是个容易被忽视的问题。我在 PCB 上做了以下处理SCK、SI、SO 三条线各串了一个 22Ω 的电阻靠近 dsPIC 端放置用来抑制过冲和振铃CS 线上加了一个 10kΩ 上拉电阻确保在 dsPIC 复位期间 CS 保持高电平防止 MRAM 被误触发MRAM 的 VCC 引脚旁边放了 0.1μF 和 1μF 的退耦电容尽量靠近芯片实测下来在 10MHz SPI 时钟下信号质量很好示波器上看上升沿干净没有明显的振铃。如果 PCB 走线比较长超过 10cm建议把时钟降到 5MHz 以下或者加缓冲器。提示MRAM 的 WP 引脚是低电平有效也就是说拉低 WP 会禁止写入。如果你不需要硬件写保护直接上拉到 VCC 即可。但如果你的应用有误写风险强烈建议用 GPIO 控制在固件里做双重保护。3. dsPIC33FJ 的 SPI 初始化与底层驱动实现3.1 SPI2 模块的寄存器配置dsPIC33FJ256GP710A 的 SPI 配置主要涉及三个寄存器SPI2CON1、SPI2CON2、SPI2STAT。我先把配置代码贴出来然后逐行解释。void SPI2_Init(void) { // 关闭 SPI2 模块配置前先禁用 SPI2STATbits.SPIEN 0; // SPI2CON1 配置 // bit15: SPIEN 0 (先关闭) // bit14: 未使用 // bit13: DISSDO 0 (使能 SDO 输出) // bit12: MODE16 0 (8 位模式) // bit11: SMP 0 (在数据输出中间采样) // bit10: CKE 1 (数据在时钟变化沿传输) // bit9: SSEN 0 (不使用硬件片选) // bit8: CKP 0 (空闲低电平Mode 0) // bit7-6: MSTEN 1 (主模式) // bit5-2: SPRE 0b0001 (主模式预分频 4:1) // bit1-0: PPRE 0b00 (主模式后分频 1:1) SPI2CON1 0x0120; // 主模式8 位Mode 0预分频 4:1 // SPI2CON2 配置 // bit15-13: FRMEN 0 (关闭帧模式) // bit12: SPIFSD 0 // bit11: FRMDLY 0 // bit10-9: SPIBEN 0 (关闭增强缓冲) // bit8: 未使用 // bit7-0: 未使用 SPI2CON2 0x0000; // 清除接收溢出标志 SPI2STATbits.SPIROV 0; // 使能 SPI2 模块 SPI2STATbits.SPIEN 1; }这里重点解释几个关键位SPRE 预分频设置dsPIC33FJ256GP710A 在 40MIPS 时系统时钟Fcy是 40MHz。SPI 时钟 Fcy / (2 * (SPRE 1))。我设 SPRE 1所以 SPI 时钟 40MHz / (2 * 2) 10MHz。这个速度在 MR25H40CDF 的 40MHz 上限之内同时信号质量也有保障。SMP 采样位SMP 0 表示在数据输出时间的中间采样SMP 1 表示在末尾采样。对于 10MHz 时钟中间采样更稳妥留出的建立保持时间更充裕。CKE 位这是最容易搞错的地方。dsPIC 的 CKE 1 表示数据在时钟变化沿从空闲态到有效态传输在下一个边沿采样。配合 CKP 0就构成了 SPI Mode 0。3.2 字节收发函数的实现dsPIC 的 SPI 收发是同步的写一个字节到 SPI2BUF 的同时接收寄存器里会收到一个字节。所以读和写用的是同一个函数uint8_t SPI2_Transfer(uint8_t data) { // 等待发送缓冲空 while (SPI2STATbits.SPITBF); // 写入数据启动传输 SPI2BUF data; // 等待接收完成 while (!SPI2STATbits.SPIRBF); // 返回接收到的数据 return (uint8_t)(SPI2BUF 0xFF); }这个函数看起来简单但有两个坑要注意第一SPI2STATbits.SPITBF是发送缓冲满标志不是发送完成标志。等待它清零只能保证发送缓冲有空位不能保证数据已经移出去了。对于连续传输这样写没问题但如果传输完要立刻拉高 CS需要额外等待一个时钟周期或者检查 SPIRBF。第二SPI2BUF是 16 位寄存器8 位模式下高 8 位是无效数据读的时候要屏蔽掉。我见过有人直接读整个 16 位然后强转 uint8_t在某些编译器优化下会出问题显式 0xFF更保险。3.3 MRAM 读写命令的封装MR25H40CDF 的命令集很简洁常用的就几条命令编码功能WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据基于这些命令我封装了几个基础函数#define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRDI 0x04 #define MRAM_CMD_RDSR 0x05 #define MRAM_CMD_WRITE 0x02 #define MRAM_CMD_READ 0x03 #define MRAM_CS_LOW() (LATFbits.LATF5 0) #define MRAM_CS_HIGH() (LATFbits.LATF5 1) void MRAM_WriteEnable(void) { MRAM_CS_LOW(); SPI2_Transfer(MRAM_CMD_WREN); MRAM_CS_HIGH(); } void MRAM_WriteDisable(void) { MRAM_CS_LOW(); SPI2_Transfer(MRAM_CMD_WRDI); MRAM_CS_HIGH(); } uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_LOW(); SPI2_Transfer(MRAM_CMD_RDSR); status SPI2_Transfer(0xFF); MRAM_CS_HIGH(); return status; }写数据函数需要先发 WREN 命令然后发 WRITE 命令加 24 位地址再连续发送数据void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t i; MRAM_WriteEnable(); MRAM_CS_LOW(); SPI2_Transfer(MRAM_CMD_WRITE); SPI2_Transfer((uint8_t)(addr 16)); SPI2_Transfer((uint8_t)(addr 8)); SPI2_Transfer((uint8_t)(addr)); for (i 0; i len; i) { SPI2_Transfer(buf[i]); } MRAM_CS_HIGH(); }读数据函数类似只是不需要 WRENvoid MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t i; MRAM_CS_LOW(); SPI2_Transfer(MRAM_CMD_READ); SPI2_Transfer((uint8_t)(addr 16)); SPI2_Transfer((uint8_t)(addr 8)); SPI2_Transfer((uint8_t)(addr)); for (i 0; i len; i) { buf[i] SPI2_Transfer(0xFF); } MRAM_CS_HIGH(); }注意MRAM 的地址是 24 位的因为 512KB 容量需要 19 位地址但 MR25H40CDF 用 24 位地址格式3 个字节。高 5 位是无关位填 0 即可。我一开始只发了 2 个字节地址结果读写全乱套了查了半天 datasheet 才发现这个问题。4. 数据存储策略参数区与日志区的分区管理4.1 存储空间规划512KB 的空间如果不做规划很容易写乱。我把整个 MRAM 分成三个区域区域起始地址大小用途参数区0x0000004KB电机参数、配置数据统计区0x0010004KB运行时间、启停次数、故障计数日志区0x002000504KB循环写入的运行日志参数区和统计区采用双备份 校验的方式存储每个数据块存两份读取时对比校验。如果一份损坏自动切换到另一份。日志区采用环形缓冲区结构写满后从头覆盖。4.2 参数区的双备份与校验机制参数数据虽然不大但一旦丢失或损坏设备可能无法正常启动。所以我用了双备份加 CRC 校验的方案。每个参数块的结构如下typedef struct { uint16_t magic; // 魔数 0x5A5A标识有效数据 uint16_t version; // 版本号 uint16_t length; // 数据长度 uint16_t crc; // CRC16 校验值 uint8_t data[504]; // 实际参数数据 } ParamBlock_t;存储的时候先在地址 0x000000 写一份再在 0x000200 写一份。读取的时候先读第一份检查 magic 和 CRC如果失败读第二份如果两份都失败加载默认参数并记录故障。CRC16 我用的是 Modbus 多项式0xA001计算速度快检错能力足够。这里有个经验CRC 计算一定要覆盖 magic、version、length 和 data 字段不要只算 data否则头部损坏了 CRC 还是对的就失去了校验意义。4.3 日志区的环形缓冲实现日志区是写入最频繁的区域设计目标是写入高效、掉电不丢最近数据、读取方便。我用了环形缓冲结构每个日志记录固定 32 字节typedef struct { uint32_t timestamp; // 时间戳ms int16_t current; // 电流 int16_t temperature; // 温度 int16_t speed; // 转速 uint16_t fault_code; // 故障码 uint8_t reserved[20]; // 预留 } LogEntry_t;环形缓冲需要维护一个写指针每次写入后指针递增。写指针本身也需要存储我把它存在统计区里每次写入日志后更新。为了防止写指针本身掉电丢失我用了写指针 计数器的方式每次更新写指针时同时更新一个递增的计数器读取时取计数器最大的那个作为有效值。日志区的写入不需要每次都发 WREN 命令吗实际上 MRAM 每次写入前都需要 WREN这是 SPI 协议的要求。但 MRAM 的 WREN 和写入之间没有延迟要求所以连续写入时可以在每次 WRITE 命令前都发一次 WREN开销很小。4.4 掉电保护的实际处理工业现场掉电是常态所以掉电保护必须做扎实。我的方案是在电源输入端加了一个大电容1000μF配合一个电压检测电路。当检测到输入电压低于阈值时触发 dsPIC 的外部中断在中断里把当前的关键数据写指针、统计计数紧急写入 MRAM。因为 MRAM 写入不需要等待这个紧急写入过程非常快几十微秒就能完成。相比之下如果用 EEPROM同样的数据量可能需要几毫秒大电容要加大很多才能撑住。这里有个细节电压检测的阈值要留足余量。我设的是 4.5V系统是 5V 供电LDO 降到 3.3V从 4.5V 掉到 LDO 失效大概有 10ms 的窗口足够完成紧急写入。阈值设太高会频繁误触发设太低来不及写需要根据实际电源特性调试。5. 调试过程中踩过的坑与排查思路5.1 读出来全是 0xFF 的问题第一次跑通 SPI 后读 MRAM 的状态寄存器返回的全是 0xFF。0xFF 通常意味着 MISO 线一直是高电平要么是 MRAM 没响应要么是 SPI 模式不对。排查步骤是这样的先用示波器看 CS、SCK、MOSI 三条线。CS 拉低正常SCK 有 10MHz 时钟MOSI 上有数据波形说明 dsPIC 这边发送没问题。然后看 MISO发现一直是高电平没有任何变化。这就指向两个可能MRAM 没被正确选中或者 SPI 模式不匹配导致 MRAM 没识别到命令。我先检查了 CS 线确认拉低到了 0V排除片选问题。然后回头查 SPI 模式配置发现 CKE 位设反了。dsPIC 的 CKE1 对应的是 Mode 0我一开始设成了 CKE0实际配成了 Mode 1MRAM 不认。改成 CKE1 后状态寄存器读出来是 0x00正常了。这个坑的教训是不同平台对 SPI 模式的寄存器定义可能不同一定要对着 datasheet 的时序图确认不要凭经验想当然。5.2 连续写入时的数据错位解决了读的问题后开始测试连续写入。写 256 字节的数据读回来发现从第 128 字节开始数据错位了后面的数据整体偏移了一个字节。这个问题比较隐蔽。我一开始怀疑是缓冲区溢出检查了代码没发现问题。后来用逻辑分析仪抓 SPI 波形发现第 128 字节处 CS 有一个短暂的毛刺。原来是我的 CS 控制宏在循环中被意外调用了导致 CS 短暂拉高又拉低MRAM 把这次毛刺当成了命令结束后续数据就被当成了新命令。修复方法是在 MRAM_Write 函数里CS 拉低后直到所有数据发完才拉高中间不允许任何其他操作打断。另外我把 CS 控制改成了直接操作寄存器LATFbits.LATF5而不是调用函数减少意外调用的可能。5.3 写保护引脚的误触发MRAM 的 WP 引脚是低电平有效我把它接到了 dsPIC 的 RF4初始化为输出高电平。但在调试过程中发现有时候写入不成功读状态寄存器发现 WEL 位写使能锁存是 0。查了半天发现是 RF4 在 dsPIC 复位期间处于高阻态WP 引脚被外部电路拉低了导致 MRAM 进入写保护状态。虽然复位后 RF4 被配置为高电平输出但 MRAM 可能已经锁存了写保护状态。解决办法是在 WP 引脚上加一个 10kΩ 上拉电阻确保复位期间 WP 保持高电平。同时在固件初始化时先拉高 WP再发 WRDI 命令清除可能存在的写保护状态。5.4 SPI 时钟过高的稳定性问题一开始我把 SPI 时钟设到了 20MHzSPRE0想看看 MRAM 能不能跑满。结果发现大部分时候正常但偶尔会出现读写错误概率大概千分之一。用示波器看 20MHz 下的 SCK 信号发现上升沿有明显的过冲幅度大概到 4V 左右3.3V 系统。虽然 MR25H40CDF 的 IO 耐压是 3.6V但过冲长期来看会影响可靠性。而且 20MHz 下dsPIC 的 SPI 建立保持时间余量很小稍微有点干扰就出错。最终我把时钟降到了 10MHz过冲消失了连续跑 72 小时压力测试零错误。对于存储应用来说10MHz 的吞吐率约 1.25MB/s完全够用没必要追求极限速度。6. 性能实测与方案对比6.1 读写速度实测数据在 10MHz SPI 时钟下我实测了不同操作的耗时操作数据量耗时等效速率单字节写1B约 3.2μs-连续写256B约 220μs1.16MB/s连续读256B约 210μs1.22MB/s参数区完整读取4KB约 3.4ms1.20MB/s日志写入32B约 30μs1.07MB/s这个速度对于工业数据存储来说绰绰有余。作为对比我之前用过的某款 SPI EEPROM 在 5MHz 下连续写 256 字节需要约 5ms因为要等待内部写周期速度差了 20 多倍。6.2 与 EEPROM、Flash 方案的横向对比对比项MR25H40CDFSPI EEPROMSPI Flash写入寿命10^14 次10^6 次10^5 次写入延迟无5ms/页3ms/扇区擦除需求不需要不需要必须擦除字节级写入支持支持不支持容量512KB通常 ≤128KB1MB~16MB单价较高中等低掉电保持20 年40 年20 年MRAM 唯一的劣势就是价格。MR25H40CDF 的单价大概是同容量 SPI Flash 的 10 倍左右。但如果算上磨损均衡算法的开发成本、EEPROM 的更换维护成本以及数据丢失带来的风险在工业场景下这个溢价是值得的。6.3 长期运行稳定性验证为了验证可靠性我做了一个 72 小时连续写入测试每 100ms 写入一条 32 字节的日志总共写入约 260 万次。测试结束后读取全部日志校验数据完整性零错误。同时做了 1000 次掉电测试在随机时刻切断电源重新上电后检查参数区和统计区的数据完整性。1000 次测试中参数区零损坏统计区有 3 次出现了写指针不一致但因为采用了双备份机制都成功恢复了。这个结果让我对 MRAM 在工业场景下的可靠性有了信心。当然实际部署时还是要做好电源设计和掉电保护不能完全依赖存储介质本身的特性。7. 几个容易被忽视的实操经验7.1 上电初始化的顺序很重要MRAM 上电后需要一定的时间才能进入正常工作状态datasheet 里写的是 400μs从 VCC 稳定到可以接受命令。但实际测试发现如果 VCC 上升较慢比如 LDO 软启动这个时间可能要更长。我的做法是在固件初始化时先延时 1ms然后读一次状态寄存器。如果读到的值不是预期值再延时 1ms 重试最多重试 5 次。这样既保证了可靠性又不会浪费太多启动时间。另外上电后建议先发一次 WRDI 命令清除可能存在的写使能状态。虽然理论上上电后 WEL 应该是 0但实际调试中遇到过异常情况多发一次 WRDI 不费事能避免一些奇怪的问题。7.2 地址对齐与页边界MRAM 没有页的概念理论上可以任意地址写入任意长度。但实际使用中我建议还是做地址对齐比如 4 字节对齐。原因有两个一是 dsPIC 是 16 位架构非对齐访问在某些情况下会降低效率二是对齐后数据结构更清晰调试时看内存 dump 更直观。另外虽然 MRAM 支持跨页写入但我的习惯是单次写入不超过 256 字节。这不是 MRAM 的限制而是为了代码的可维护性——单次写入太长出问题时不好定位。7.3 状态寄存器的定期检查MR25H40CDF 的状态寄存器里有几个有用的位WEL写使能锁存、BP0/BP1块保护、SRWD状态寄存器写保护。我建议在每次写入操作前读一次状态寄存器确认 WEL 为 1写入完成后再读一次确认 WEL 归零。这样做会增加一点开销每次读状态大概 2μs但能及时发现写入异常。我在实际项目中就遇到过因为 WP 引脚干扰导致 WEL 没置位写入静默失败的情况。如果没检查状态寄存器这种问题很难发现。7.4 日志数据的压缩存储如果日志数据量很大可以考虑做简单的压缩。我的做法是对变化缓慢的数据比如温度做差分存储只存变化量。这样每条日志可以从 32 字节压缩到 16 字节左右存储密度翻倍。不过压缩会增加代码复杂度而且解压需要额外的 CPU 时间。是否值得做取决于你的日志量和 CPU 负载。如果 CPU 负载已经很高建议直接存原始数据MRAM 的容量和速度都撑得住。7.5 固件升级时的数据迁移产品固件升级时参数区的数据结构可能会变化。我的做法是在参数区头部加一个 version 字段升级后读取时检查 version如果不匹配就执行数据迁移函数把旧格式的数据转换成新格式。这个迁移函数要写得非常小心因为一旦出错用户的参数就丢了。我的建议是迁移前先把原始数据完整读出来备份到 RAM迁移成功后再写入写入前先擦除旧数据MRAM 不需要擦除直接覆盖即可。如果迁移过程中掉电下次上电时 version 还是不匹配会重新执行迁移所以迁移函数必须是幂等的。8. 这套方案适合什么场景不适合什么场景MRAM 方案最适合的场景是写入频繁、数据量不大、对可靠性要求高、掉电不能丢数据。典型的应用包括工业控制器参数存储、医疗设备运行日志、汽车电子故障记录、电力监控数据采集等。不太适合的场景是需要大容量存储比如音视频数据、对成本极度敏感比如消费类电子产品、写入频率很低比如一年写几次的配置数据用 EEPROM 就够了。我在另一个项目里做过对比一个智能家居的传感器节点每天只写几次配置数据用 2KB 的 EEPROM 成本不到一块钱完全没必要上 MRAM。所以选型还是要看具体需求不要为了用新技术而用新技术。最后分享一个我在调试中总结的小技巧如果你不确定 MRAM 是否正常工作可以先写一个已知的模式比如 0xAA、0x55 交替然后读回来对比。这个测试能快速判断 SPI 通信是否正常、地址是否对齐、数据线是否接反。比读状态寄存器更直观因为状态寄存器的值有时候不好判断对错。另外MR25H40CDF 的 datasheet 里有一个细节容易被忽略HOLD 引脚在 SPI 通信过程中拉低会暂停传输但不会复位内部状态机。如果你不用 HOLD 功能一定要上拉到 VCC不要悬空。我见过有人把 HOLD 悬空结果在电磁干扰下偶尔触发导致通信异常查了很久才找到原因。
返回列表