多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

STM32F217ZG接MRAM,打造永不写坏、掉电不丢的工业存储方案

STM32F217ZG接MRAM,打造永不写坏、掉电不丢的工业存储方案 做工业设备的老工程师应该都有过这种经历设备在客户现场跑了两年因为 EEPROM 写寿命到了配置数据整片丢掉售后机票比物料钱还贵。我自己在 STM32F217ZG 平台上做嵌入式数据采集终端时也踩过类似的坑。后来把存储颗粒换成 Everspin 的 MR25H40CDF 磁阻存储器参数保存、运行日志、掉电时刻的设备状态都放在这一颗芯片里读写数据不再提心吊胆。这篇文章就把我这个项目里的存储方案完整梳理一遍包括硬件连接、SPI 驱动、掉电保护策略和实测数据给正在选型、或者准备把项目迁移到 MRAM 的同行一个直接能参考的样本。1. 为什么工业设备需要一块“写不坏、掉电不丢”的存储1.1 传统存储方案在工业现场的痛点以前做嵌入式存储脑子里第一反应通常是 SPI EEPROM 或者 SPI NOR Flash。这两种器件在消费类产品里没什么大问题但放到工业现场各自的毛病都会被放大。EEPROM 比如 25LC1024最大的痛点是写次数。数据手册写 100 万次擦写寿命听起来不少可实际的工业设备写配置和日志远比想象中频繁。比如一个 Modbus 网关每 10 秒记录一次运行状态一天就是 8640 次写操作按平均每个扇区被覆盖的频率算几个月到一年就能摸到寿命上限。更麻烦的是 EEPROM 单字节写入要等 4 到 5 毫秒的 tW 时间如果在写入过程中断电这一字节可能处于“半写坏”状态下次上电读出来的数据不新不旧非常难排查。NOR Flash 的坑则是“必须先擦除再写”。W25Q64 这类芯片写一个字节前得先把整个扇区擦掉哪怕你想改 1 个字节也要先读出一整页、擦除、再写回。擦除操作不仅慢而且同样有写入寿命限制典型在 10 万次量级。还有一个隐蔽问题擦除/编程过程中如果掉电可能会把擦除了一半的数据留在那里上电后整页数据直接损坏。这也是很多工业设备“重启后配置全丢”的常见原因。这两种存储还有一个共同问题写入延迟的不可控。EEPROM 写一个字节要几毫秒Flash 做一个扇区擦除可能要上百毫秒这在需要快速断电保护的场景里完全不够用。1.2 MRAM 的工作原理与 MR25H40CDF 关键参数MRAM 的全称是 Magnetoresistive Random Access Memory磁阻随机存取存储器。它和 EEPROM、NOR Flash 在原理上是完全不同的路子。MRAM 存储单元的核心是一个磁隧道结结构底层是固定磁化方向的参考层上层是可以翻转的自由层中间隔着一层极薄的氧化镁绝缘层。当自由层和参考层磁化方向相同时隧道结呈现低电阻表示逻辑 0方向相反时呈现高电阻表示逻辑 1。写数据就是通过外加电流产生的磁场让自由层磁化方向瞬时翻转。这个原理带来的直接好处有两点第一存储的是磁化状态而不是电荷所以掉电后数据天然保持不需要刷新也不需要电池第二翻转磁化方向是一个物理过程不像浮栅电荷那样需要反复忍受高电压应力所以理论上写寿命是无限的也不会出现“擦除破坏旁边单元”的问题。MR25H40CDF 的具体参数是这样的容量 4Mbit也就是 512KB对于工业设备保存配置参数、运行日志、报警记录来说这个容量非常合适。接口是标准 SPI支持 Mode 0 和 Mode 3最高时钟 40MHz。供电 2.7V 到 3.6V可以直接挂在 3.3V 系统上。工业级版本温度范围覆盖 -40℃ 到 85℃部分型号支持到 105℃数据保持能力是 20 年以上写入寿命无限。对于需要长期在恶劣环境运行的嵌入式设备这几乎是为“掉电保存”和“频繁写日志”这两个场景量身定做的。1.3 和常见存储颗粒的横向对比我用一张表把 MR25H40CDF、25LC1024 EEPROM 和 W25Q64 NOR Flash 放在一起比较项目选型时扫一眼就很清楚对比项MR25H40CDF MRAM25LC1024 EEPROMW25Q64 NOR Flash写入方式直接写无需擦除直接写必须先擦除扇区再写单次写入延迟随 SPI 时钟连续写入无 tW 等待单字节典型 4-5ms页编程数百微秒扇区擦除上百毫秒写入寿命无限约 100 万次约 10 万次擦除寿命更低掉电安全性非易失写入过程中断电基本无破坏风险写入中掉电可能留下半写数据擦除/编程中掉电可能损坏整页容量密度512KB密度中等常见 128KB 以下8MB 以上常见数据保持20 年以上100 年标称20 年标称从这张表能看出来MRAM 不是用来替代大容量 Flash 的它的定位非常明确数据量不大、写入频率高、掉电不能丢、可靠性要被反复确认的场合。配置参数、校准系数、运行日志、断电现场记录这就是 MRAM 的主场。ST 的 STM32F217ZG 有 128KB RAM 和 1MB Flash跑系统、跑协议栈都够但真正需要断电保存的那部分关键数据我全部挪到了 MR25H40CDF 上两者配合起来非常顺手。2. MR25H40CDF 与 STM32F217ZG 的硬件连接设计2.1 引脚分配SPI1 与 GPIO 的选择STM32F217ZG 是一款基于 Cortex-M3 内核的高性能 MCU主频可以跑到 120MHz。它一共有三个 SPI 外设其中 SPI1 挂在 APB2 总线上外设时钟最高 60MHzSPI 时钟理论上可以到 30MHz。在这个项目里我把 MR25H40CDF 接在了 SPI1 上用软件控制片选没有用硬件 NSS。具体引脚分配是这样的STM32F217ZG 引脚功能连接到 MR25H40CDFPA4GPIO 输出软件片选CS#PA5SPI1_SCKSCKPA6SPI1_MISOSOPA7SPI1_MOSISI3V3电源VDDGND地VSS3V3经 10kΩ 电阻上拉WP#、HOLD#选择 PA4 做片选纯粹是因为顺手旁边就是 SPI1 的三根线走线短。如果你要接多个 SPI 从设备可以把片选分散到别的 GPIO 上只要注意这些引脚默认状态必须是高电平不能让从设备在 SPI 初始化之前被误选中。CS 线一定要用 GPIO 软件控制不要用 SPI 的硬件 NSS。硬件 NSS 在多主、半双工模式下的行为很绕工业设备上本来就讲究简单可靠软件片选看着土但调试起来最省心。另外MRAM 的 WP# 是写保护输入低电平时会禁止写状态寄存器HOLD# 是暂停输入低电平时 SPI 通信会被挂起。这两个脚必须接好不能悬空。2.2 电源、去耦和抗干扰处理MR25H40CDF 的工作电压是 2.7V 到 3.6V直接用 3.3V 供电。但工业设备上3.3V 往往是开关电源经过 DCDC 转换过来的纹波和毛刺不会小。我的做法是在 MRAM 的 VDD 引脚旁边放一个 100nF 的陶瓷电容再在这个电容靠近电源入口的位置并一个 1uF 到 10uF 的电容容值大小取决于板上的电源噪声情况。如果板上还有继电器、电机、变频器这类干扰源建议在 PCB 布局时让 MRAM 和这些干扰源尽量远同时保证 VDD 和 GND 的回路面积足够小。SPI 信号线上我串联了 22Ω 的电阻。这个电阻的作用是抑制信号过冲和振铃尤其在采样边沿不希望出现毛刺的时候很有用。串联电阻会稍微降低信号边沿速度但 7.5MHz 的 SPI 时钟完全不受影响。如果你做主从板分离的长线传输比如超过 10cm 的排线那就别省这个电阻同时还要考虑把速率降下来。后面实测部分我会详细说长线高速跑 SPI 在工业现场是什么表现。2.3 上电时序和复位注意事项MRAM 这块芯片没有 EEPROM 那种复杂的上电复位延迟VDD 稳定后就可以直接访问这一点很省心。但 STM32F217ZG 侧的 SPI 外设和 GPIO 必须先初始化成正确状态再开始对 MRAM 操作。否则一上电 GPIO 处于默认状态PA4 如果刚好是低电平MRAM 就会以为片选被拉低进而接收线上乱码虽然不会造成数据损坏但会让初次通信的状态变得不可预测。我的初始化顺序是先配置 PA4 为推挽输出并立刻拉高然后再配置 SPI1 的 SCK、MOSI、MISO 复用引脚最后初始化 SPI1 外设。这样保证 CS 从系统上电到外设就绪的整个过程中始终处于释放状态。顺带一提如果板上有硬件复位按钮或者外部看门狗也要保证复位后 CS 是高电平再让主程序去碰 MRAM。3. STM32F217ZG 的 SPI 驱动从寄存器配置到读写封装3.1 SPI1 初始化配置说明我用的是 STM32 HAL 库但下面的配置思路对标准库、LL 库完全通用。SPI1 挂在 APB2 上STM32F217ZG 主频 120MHz 时APB2 时钟是 60MHz。我把波特率预分频配成 8得到 7.5MHz 的 SCK远低于 MR25H40CDF 的 40MHz 上限也低于 SPI1 本身的 30MHz 上限留了充足的裕量。MR25H40CDF 支持 SPI Mode 0 和 Mode 3。我选择 Mode 0也就是 CPOL 为低、CPHA 为第一个边沿采样原因很简单Mode 0 是绝大多数 SPI 器件的默认约定以后如果要在同一组 SPI 总线上挂其他传感器、ADC 芯片兼容性最好。初始化代码大致如下void MRAM_SPI1_Init(void) { GPIO_InitTypeDef gpio {0}; __HAL_RCC_SPI1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // PA5 - SCK, PA7 - MOSI gpio.Pin GPIO_PIN_5 | GPIO_PIN_7; gpio.Mode GPIO_MODE_AF_PP; gpio.Pull GPIO_NOPULL; gpio.Speed GPIO_SPEED_FREQ_HIGH; gpio.Alternate GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, gpio); // PA6 - MISO gpio.Pin GPIO_PIN_6; gpio.Mode GPIO_MODE_AF_PP; gpio.Pull GPIO_PULLUP; gpio.Speed GPIO_SPEED_FREQ_HIGH; gpio.Alternate GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, gpio); // PA4 - 软件片选先拉高 gpio.Pin GPIO_PIN_4; gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Pull GPIO_PULLUP; gpio.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, gpio); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; // CPOL0 hspi1.Init.CLKPhase SPI_PHASE_1EDGE; // CPHA0 hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_8; // 60MHz / 8 7.5MHz hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial 7; HAL_SPI_Init(hspi1); }这里有一个容易被忽略的细节MISO 引脚 PA6 的 GPIO 配置。很多人会把 MISO 配成普通的输入模式但在 STM32F2 系列上复用外设输入功能时 GPIO 就应该工作在复用模式HAL 里用 GPIO_MODE_AF_PP 即可。我把内部上拉也打开了因为 MRAM 在 CS 释放时 SO 引脚是高阻态MISO 线上如果没有上拉STM32 读到的电平会不稳定从而造成误码。这个上拉对提升通信可靠性帮助很大。3.2 底层原语WREN、状态寄存器与读写函数MR25H40CDF 的命令集和常见 SPI EEPROM 高度相似核心就几个0x06 是写使能 WREN0x04 是写禁用 WRDI0x05 是读状态寄存器 RDSR0x02 是写数据 WRITE0x03 是读数据 READ。需要注意MRAM 虽然写入本身不需要等待但它的状态寄存器里仍然有 WIP 和 WEL 位行为逻辑和 EEPROM 是兼容的。写命令执行后WEL 位会自动清零所以每次独立写命令前都必须在片选拉低之前先发一次 WREN否则 WRITE 命令会被忽略。这是最容易踩的坑很多人照着 Flash 例程改漏了 WREN结果写进去的全是 0xFF。读写原语代码如下static void mram_cs_low(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); } static void mram_cs_high(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } static void mram_wren(void) { uint8_t cmd 0x06; mram_cs_low(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); mram_cs_high(); } uint8_t mram_read_status(void) { uint8_t cmd 0x05; uint8_t sr 0; mram_cs_low(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_SPI_Receive(hspi1, sr, 1, 100); mram_cs_high(); return sr; } int mram_write_bytes(uint32_t addr, const uint8_t *data, uint32_t len) { uint8_t hdr[4] {0x02, (addr 16) 0xFF, (addr 8) 0xFF, addr 0xFF}; if (addr len 0x80000) { // 512KB return -1; } mram_wren(); mram_cs_low(); HAL_SPI_Transmit(hspi1, hdr, 4, 1000); HAL_SPI_Transmit(hspi1, (uint8_t *)data, len, 1000); mram_cs_high(); return 0; } int mram_read_bytes(uint32_t addr, uint8_t *data, uint32_t len) { uint8_t hdr[4] {0x03, (addr 16) 0xFF, (addr 8) 0xFF, addr 0xFF}; if (addr len 0x80000) { return -1; } mram_cs_low(); HAL_SPI_Transmit(hspi1, hdr, 4, 1000); HAL_SPI_Receive(hspi1, data, len, 1000); mram_cs_high(); return 0; }读写函数的地址字段是 24 位MR25H40CDF 只有 512KB高位地址实际上用不到但我仍然按完整 24 位发送这样以后换同系列的更大容量 MRAM 时驱动不用改。有一点要说清楚MRAM 写入不需要像 EEPROM 那样轮询 WIP 等到 tW 结束因为磁隧道结翻转本身就是纳秒级的过程。所以我的写函数发完数据直接拉高 CS 就算完成。如果你要求更严谨可以在写完后读一次状态寄存器确认 WEL 已经清零正常情况都会是 0。这里不要照抄 EEPROM 驱动里那种“写完等 5 毫秒”的延时白白浪费优势。3.3 应用层接口与数据校验裸的读写函数只能算寄存器级操作真正要往产品上搬还得包一层可靠的数据接口。我的做法是在 MRAM 里维护“记录头 有效载荷”的结构每条记录都带魔数、版本号、长度和 CRC32 校验上电读取时先验证头部再验证数据保证加载的一定是一份完整数据。定义一个紧凑结构体注意字节对齐typedef struct { uint32_t magic; // 0x4D52414D MRAM uint32_t crc32; // payload 的 CRC32 uint16_t version; // 结构版本号 uint16_t length; // payload 长度 uint8_t payload[]; } __attribute__((packed)) mram_record_t;调用mram_write_bytes写入时把整个结构体一次性写进去读取时同样的地址读回来先校验 magic再校验 crc32全部通过才认为数据有效。这个设计在上电加载、掉电保存、日志追加三种场景里都能复用比裸地址读写要稳妥得多。CRC32 算法库网上很多我直接用了标准的查表法实现32 字节的表在 Flash 里占用很小数据长度一般不超过 512 字节跑一次也就几十微秒。4. 掉电保护和数据一致性工业现场最看重的一环4.1 存储区规划与双备份设计512KB 的容量随便用肯定够但工业设备的存储分区是需要认真规划的。我在这块 MRAM 上做了三个区域区域地址范围用途参数区 A0x00000 - 0x0FFFF当前运行参数参数区 B0x10000 - 0x1FFFF参数备份日志区0x20000 - 0x7FFFF运行日志、报警记录、掉电快照参数区为什么要分成 AB 两份因为任何写入过程都可能被外部断电打断。虽然 MRAM 不像 Flash 那样会擦坏整页但如果写到一半头部更新了、数据没写完上电后读到的就是一份不完整的参数。AB 双备份的更新顺序是先写完整的备份区 B写入并校验 CRC 通过后再写主区 A最后更新一个位于独立地址的“当前生效标志”。上电时优先读 ACRC 校验失败则回退读 B。这个机制保证任何一次断电至少有一份参数是完整的。实际上MRAM 写一个 128 字节的参数块在 7.5MHz 下不到 200 微秒就完成了比 EEPROM 的写一个字节还快所以双备份的额外开销完全可以接受。但“双备份”带来的价值是实打实的它把“写入过程中掉电导致参数丢失”这个概率问题变成了几乎不可能事件。4.2 PVD 掉电中断里的紧急保存STM32F217ZG 自带可编程电压检测器 PVD可以在 VDD 下降到设定阈值时触发中断。这个功能在工业设备上非常实用相当于给了主控一个“电源要挂了”的提前通知。我的板子上把 PVD 阈值设为 2.9V当 VDD 跌破这个值MCU 进入 EXTI16 中断然后把关键状态写入 MRAM。掉电中断里的处理逻辑要非常克制。我保存的是一个 64 字节的结构体里面只有设备当前状态机、最近一次指令序号、关键传感器的最后采样值。64 字节在 7.5MHz 的 SPI 下只需写约 40 微秒PVD 触发到 VDD 真正跌到 MCU 不能工作的这段时间是足够完成的。PVD 中断代码示意void EXTI16_IRQHandler(void) { if (EXTI-PR EXTI_PR_PR16) { // 先关中断避免被更高优先级打断 __disable_irq(); // 将 RAM 里的现场状态结构体写入 MRAM 固定地址 // 注意这里不要再做复杂的日志组织只写最关键的数据 mram_write_bytes(MRAM_POWEROFF_SNAPSHOT_ADDR, (uint8_t *)poweroff_snapshot, sizeof(poweroff_snapshot)); EXTI-PR EXTI_PR_PR16; __enable_irq(); } }要特别注意掉电中断里调用的mram_write_bytes内部用的是 HAL 自带超时轮询实际执行时间不一定非常短。我在测试时把 SPI 分频临时调低了确保在最坏电源条件下也能写完。还有一个经验掉电保存时不要试图写超过 128 字节的数据时间窗口不够而且电源电压下降会导致 SPI 信号幅值跟着下降长数据更容易出错。板级设计上我在 MCU 的 VDD 和 GND 之间放了若干容值不等的电容相当于一个小型储能池让 PVD 触发后的可工作时间从几十微秒延长到几百微秒。这招很土但在工业控制器上非常有效。4.3 环式日志的实现思路日志记录是另一个 MRAM 大放异彩的场景。用 NOR Flash 做日志时最痛苦的就是擦除日志区写满了要整块擦擦除时掉电还可能损坏已有记录。用 MRAM 做日志因为写入就是直接覆盖没有擦除环节环形缓冲区的实现变得简单很多。我的日志区采用最简单的顺序追加模型。在参数区里保存一个 LogHead 结构体里面记录日志区的写指针、读指针和总条数。每次追加日志时先检查剩余空间如果不够就把读指针往前挪覆盖最旧的一条。追加过程分两步先在日志尾部写入新条目并校验再更新 LogHead。由于 MRAM 可以按任意地址重写指针更新不需要搬动其他数据。日志条目跟上文的 record 结构保持一致每条都带 CRC32。上电时先读 LogHead如果发现 LogHead 的 CRC 不对说明上次掉电前指针更新没有完成就用“扫描日志区最后一条有效记录”的方式把写指针恢复回来。这个恢复逻辑在 MRAM 上跑得很快因为整个日志区也就 384KB按 64 字节一条算只有 6000 条全扫描一遍也就几十毫秒。这种环形日志方案的硬件成本就是一颗 MR25H40CDF软件工作量不大但换来的是“写坏日志”这种事情从此消失。我的数据采集终端每 10 秒写一条 48 字节的记录一天 8640 条一年下来对同一存储单元的覆盖次数也不算高完全在 MRAM 的承受范围内。换成 EEPROM这种日志频率我根本不敢开。5. 实测性能、抗干扰表现与那些容易踩的坑5.1 实测读写吞吐数据在 7.5MHz SPI 时钟下我实际测过一组数据。读取 1KB 数据按照 4 字节命令头加 1024 字节数据理论传输时间是 (1028 × 8) / 7.5MHz ≈ 1.1 毫秒加上 HAL 库函数调用开销实测 1.3 毫秒左右。写入 1KB 数据的时间基本相同因为没有擦除过程也没有 tW 等待这一点在传统 EEPROM 和 Flash 上想都不敢想。对比 W25Q64 写 1KB 数据需要先擦除一个扇区典型耗时 60 到 150 毫秒然后按 256 字节一页做页编程每页典型 0.8 到 3 毫秒1KB 就是 4 次页编程再加上写进程间的状态检查整体要十几毫秒到两百毫秒不等。MRAM 在这个对比里快了不止一个数量级而且写入次数对性能零影响。我做过一个持续写入测试以 1 毫秒间隔写一个 128 字节的块连续写了 100 万次读回去的数据始终正确MR25H40CDF 的表面温度也没有明显变化。这种频率下同容量的 EEPROM 大概率已经报废了。5.2 工业现场抗干扰测试理论参数再漂亮工业现场才是真考场。我在一台变频器旁边做过长时间测试SCK 速率 7.5MHzSPI 排线长约 10 厘米没有屏蔽层。结果是偶发误码大概每几百万字节会出现一位错位。后来把 SCK 降到 3.75MHz并在三根信号线上增加了 22Ω 串联电阻连续跑了 48 小时没有出现任何误码。这个测试给我的教训是MRAM 和 MCU 都是好器件但工业现场的 EMI 不会管你 datasheet 上写 40MHz。凡是涉及长线、强干扰环境的项目适当降低 SPI 速率比任何软件纠错都有效。我最终把这套设备的工作速率定在 3.75MHz读取一个 512 字节的配置块仍然只要 1.1 毫秒完全没有性能压力。MISO 线上加上拉这个设计也在抗干扰测试中体现了作用。MRAM 在 CS 拉高后 SO 引脚进入高阻态如果主机侧的 MISO 是浮空输入线上的耦合噪声就会让接收寄存器出现随机数据。这个随机数据本身不会破坏通信但会让 SPI 状态机的收数据过程变得不可控。PA6 内部上拉解决的就是这个问题。5.3 工程中容易忽略的细节写这篇总结的时候我把这几个月踩过的坑重新过了一遍有几条值得单独拎出来说。第一选型要看完整型号。MR25H40CDF 里的 CDF 后缀对应的是特定封装类型我最初画 PCB 时以为它和常见的 8 脚 SOIC EEPROM 一个封装差点把板子画错。下单之前一定要把数据手册末尾的封装图打开确认引脚数和封装尺寸再画原理图。第二WP 引脚状态要在调试前确认一次。WP# 拉低只会禁止写状态寄存器不会禁止普通写命令所以块保护功能设置错误时很难直接看出来。调试时先调用一次mram_read_status()如果状态寄存器读到非 0 值比如 BP 位被意外置 1说明你的写保护设定有问题普通写操作会被拒绝。第三HAL_SPI_Transmit 和 HAL_SPI_Receive 在同一个 CS 低电平周期内连续调用中间会有极短的延时实际不影响 MRAM 通信。但如果未来你把速率提到 20MHz 以上想压榨性能建议换成HAL_SPI_TransmitReceive()配合 DMA一个命令周期内把命令、地址和数据全部发完吞吐还能再上一个台阶。这个改造需要重新设计软片选的时机不建议一上来就这么干。第四MRAM 写入不需要擦除这让“读改写”逻辑变得非常直接。你可以只修改一个字节而不影响相邻数据也不用像 Flash 那样先把整页搬进 RAM。应用层设计时完全可以放开手脚把存储当作一块“非易失 RAM”来用。这一点也提醒了团队里的新同事写 MRAM 驱动千万不要套用 Flash 驱动里的扇区擦除逻辑那纯粹是画蛇添足。第五关于读取数据的读取一致性。我在应用层给每条配置记录都加了 CRC32但这里有个细节CRC 计算用的初始值和多项式要在整个产品生命周期内固定。中途换过一次 CRC 算法参数造成旧批次设备的配置记录全部校验失败还好当时产品没量产不然就得写一个“强制回写默认参数”的售后工具。经验就是凡是存储相关的校验算法定义之后不要轻易改。我在实际项目中体会到最深的一点是MR25H40CDF 这种 MRAM 芯片改变的不是存储容量而是你写嵌入式存储代码时的心态。以前写 EEPROM 驱动脑子里总绷着“寿命、延迟、掉电窗口”这几根弦换到 MRAM 之后存储读写变得像操作普通 RAM 一样随心所欲日志随便记参数随便改掉电随便切。如果你现在正面对频繁写日志掉配置的困境完全可以把存储颗粒换成 MRAM驱动改动量不大但可靠性提升是肉眼可见的。
返回列表