ARTICLE DETAIL

资讯详情

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

MRAM替代EEPROM与Flash:工业掉电数据保存与嵌入式存储选型实战

MRAM替代EEPROM与Flash:工业掉电数据保存与嵌入式存储选型实战 做了这么多年工业控制我一直对“掉数据”这件事特别敏感。现场设备频繁上下电、电机启停干扰、环境温度高保存参数和运行日志的存储芯片往往第一个出问题。以前用外部 EEPROM 和 NOR Flash写几次、几十万次就磨损轻则数据出错重则设备直接停机。直到后来在项目里换上 Everspin 的 MR25H40CDF4Mbit SPI MRAM配合恩智浦 Kinetis KV42 系列 MKV42F128VLH16很多老毛病才算彻底根治。这篇文章就当一次完整复盘从选型、硬件连接、驱动代码到调试现场踩过的坑一次讲清楚写给正在纠结“嵌入式存储怎么选”的朋友。我做嵌入式开发十多年深知存储选型从来不是“能存就行”。很多设备故障不是主控算力不够而是存储先扛不住了。MR25H40CDF 和 MKV42F128VLH16 这对组合之所以值得写是因为它同时解决了三个长期痛点存储芯片不耐高频写入、掉电瞬间数据丢失、SPI 通信受现场干扰导致内容损坏。如果你也在做工业控制器、电机驱动、仪器仪表或者需要掉电保存状态的应用这篇文章应该能帮你省下不少弯路。1. 方案选型为什么是 MRAM 为什么选 KV421.1 哪些工业数据最容易“伤”存储芯片先说场景。工业设备里需要掉电保存的数据大致分三类第一类是状态量比如设备当前处于哪个工序、累计加工了多少件这类数据每次停机都要更新一天几十次、上百次很常见第二类是运行日志控制器每隔几秒或几百毫秒记录温度、电流、报警码一天下来可能上千条第三类是工艺参数和标定值比如伺服零点、PID 系数、传感器修正值虽然写入频率不高但一旦丢失整台设备就得重新调试。这三个场景对存储的要求完全不同。工艺参数用 EEPROM 就能应付写入不频繁容量也不需要太大但状态量和运行日志一直在写普通闪存标称十万次擦写寿命听着不少实际上如果每秒写一条日志几天就能磨掉一整块扇区的寿命。我见过一个项目用外部 SPI Flash 存计数器因为写得太频繁三个月后那块区域就读写出错整个控制板返修。所以说存储选型如果不把“写入频率”算进去后期基本都会出问题。MR25H40CDF 是 Everspin 的串行 MRAM存储单元用的是磁隧穿结数据靠磁阻状态保存而不是像 Flash 那样靠浮栅电荷保存。这意味着写入前不需要擦除随时可以直接覆盖而且理论擦写寿命极高数据手册上给出的耐久性达到 10 的 15 次方量级。这是什么概念就算每毫秒写一次也能写几万年。对于设备寿命最多十几年的工业产品来说MRAM 在写入寿命上几乎等于“无限次”。1.2 对比传统存储MRAM 赢在哪里我用一张表把常见非易失存储的差异列出来方便你直接对照存储类型写入是否需要先擦除典型擦写寿命写入速度容量选择SPI NOR Flash需要按扇区擦除10 万次左右中页编程有等待时间1Mbit~128Mbit 常见I2C/SPI EEPROM不需要按字节写100 万次左右慢有写周期时间1Kbit~4MbitFRAM不需要直接覆盖100 亿次左右快近 SRAM 速度常见几十 Kbit 到 8MbitMRAM不需要直接覆盖极高可视为无限快写周期即时完成4Mbit 起步常见到 16Mbit我之前有段时间也试过 FRAM写入寿命和速度确实不错但容量普遍偏小项目里如果既要做日志缓冲又要存参数空间就很紧张。MR25H40CDF 的逻辑存储容量是 4Mbit也就是 512KB对于绝大多数工业记录需求绰绰有余。而且 MRAM 的读写时序和 SPI NOR Flash 高度兼容软件适配成本很低这也是我最终选它的重要原因。MR25H40CDF 在 JEDEC 标准 SPI 命令体系下工作控制器可以像操作普通 SPI 存储一样操作它。写入是“即时完成”的不存在 Flash 那样的页编程时间也不需要软件等待内部的 charge pump。掉电那一瞬间只要 CS 和电源还稳定住数据就已经进到磁隧穿结里了不存在“写到一半”这种说法。1.3 MKV42F128VLH16 这颗 MCU 的匹配点MKV42F128VLH16 属于恩智浦 Kinetis KV4x 系列面向电机控制和工业应用内核是 ARM Cortex-M4F带硬件浮点单元主频最高可以跑到 168MHz。后缀里“F128”表示内置 128KB 程序 Flash“VLH”表示 LQFP64 封装温度范围是工业级 -40℃ 到 105℃。KV42 系列本身的设计定位就很适合这个项目它不像通用 MCU 那样把资源堆在显示和外设上而是把重点放在定时器、PWM、ADC 和通信接口上适合做实时控制。片上有多个可配置的 SPI 模块MR25H40CDF 挂一路 SPI 就行不占用主控太多资源。而且 Cortex-M4F 跑 168MHz处理电机 FOC 算法之外还有余量去管理存储驱动。还有人会问KV42 内部有 FlexMemory 可以当 EEPROM 用为什么还要外挂 MRAM我的实践经验是芯片内部 Flash 的擦写寿命和容量都有限掉电保存和日志记录这种高频写入如果放进内部 Flash不仅占用代码空间还会拖累整个系统的可靠性和寿命。外扩一片 MRAM把内部 Flash 留给程序两件事互不干扰写驱动的时候也不用小心翼翼考虑擦写均衡。2. 硬件连接与 PCB 设计要点2.1 SPI 引脚怎么接、上下拉怎么加MR25H40CDF 是标准 8 引脚 SPI 从设备和 KV42 之间只需要 4 根信号线加电源和地。MRAM 引脚MCU 端连接说明CSSPI0_PCS0如 PTA15片选必须接上拉电阻到 3.3VSCKSPI0_SCK如 PTA14SPI 时钟MOSISPI0_SIN如 PTA16MCU 输出接 MRAM 数据输入MISOSPI0_SOUT如 PTA17MCU 输入读回数据WP3.3V通过 10kΩ 上拉关闭写保护区段HOLD3.3V通过 10kΩ 上拉禁用保持模式VCC3.3V注意去耦GNDGND地CS 引脚的上拉电阻非常重要。MCU 上电复位到 GPIO 初始化完成之前引脚电平是不确定的。如果 CS 在低电平状态下悬空MRAM 会被置为选中状态此时只要 SCK 和 MOSI 上有噪声芯片就可能把噪声当成命令造成伪写入。10kΩ 上拉能在 MCU 未接管引脚时把 CS 稳定在高电平这是我在现场吃过亏后才养成的习惯。WP 和 HOLD 这两个引脚如果悬空影响很大。HOLD 引脚低电平会让器件进入保持模式SPI 通信完全不响应而 WP 低电平会启用块写保护导致写使能命令无效。所以即使 MCU 不控制它们也一定要上拉到高电平。我习惯在电路图里直接给这两个引脚各加一个 10kΩ 上拉省得后续软件排查时怀疑芯片“失灵”。2.2 电源去耦和板级布局MR25H40CDF 的供电范围是 2.7V 到 3.6V典型工作电压 3.3VMKV42F128VLH16 的 IO 和内核也是 3.3V 系统可以直接共用一个电源轨不需要电平转换。给 MRAM 供电时我的惯例是在 VCC 引脚旁边放一个 0.1µF 的陶瓷电容再配合一个 4.7µF 的钽电容或大容量陶瓷电容做储能。尤其是在电机启停、继电器吸合的瞬间系统电源会有明显跌落如果 MRAM 的 VCC 低于 2.7V写操作就可能失败。PCB 布局上SPI 信号线尽量短最好控制在 5cm 以内。工业设备的功率线、PWM 输出线一定要远离 SPI 走线不要平行太长。MRAM 和 MCU 之间如果走线过长信号完整性问题会被放大SPI 时钟频率被迫降下来还是小事更麻烦的是现场干扰导致读回数据有随机位错误。DFN8 封装的手工焊接要格外小心。MR25H40CDF 的 DFN8 底部有大面积裸露焊盘这个焊盘通常和地连通。焊接时如果只焊了四周引脚底下没焊好芯片可能连通信都不正常而且这种情况用万用表按引脚顺序量还很难发现。推荐用钢网刷锡膏或者用热风枪配合助焊剂焊完后用放大镜检查底部焊料是否填满。2.3 复位期间和上下电时序还有一个容易忽略的细节MRAM 的 CS 和电源之间的上下电时序。MR25H40CDF 在 VCC 上升到有效工作电压之后需要一小段稳定时间才能接受命令。MCU 这边上电启动也需要时间初始化 SPI 外设。所以在上电初始化代码里先等电源稳定再配置 SPI再操作 MRAM。我采用的顺序是系统上电 → 延时 10ms → 初始化 SPI GPIO → 配置 SPI 模式 → 读一次 MRAM 状态寄存器和 ID确认通信正常 → 再加载业务数据。这个小步骤看着不起眼却能避免 MCU 已开始发命令而 MRAM 还没就绪导致的莫名其妙失败。3. 驱动开发与读写实现3.1 SPI 模式和时钟参数MR25H40CDF 支持 SPI Mode 0CPOL0CPHA0和 Mode 3CPOL1CPHA1。我一般选 Mode 0原因很简单兼容性最广稍后代码移植到其他平台也方便。时钟频率方面这颗芯片数据手册给出最高 40MHz 级别但实际项目里我会把 SPI 时钟设置为 20MHz留出一倍余量。如果 PCB 走线比较长或者现场干扰较强降到 10MHz 甚至 5MHz 会更稳妥。工业应用不是跑分稳定性永远高于速度。存储读写再快也不会成为系统瓶颈但一次丢数据就可能导致设备停工。用 KV42 的 SPI 外设时要注意把 CPHA 配置成和 MRAM 期望的采样边沿一致。我开发时习惯配完寄存器后先读一次 ID确认波形对了再继续。下面是 KV42 上初始化 SPI0 的代码风格接近 Kinetis SDK方便你移植typedef struct { uint32_t baudRateBps; uint8_t polarity; uint8_t phase; uint8_t dataWidth; } mram_spi_cfg_t; mram_spi_cfg_t mramSpiCfg { .baudRateBps 20000000, /* 20MHz */ .polarity 0, /* CPOL0 */ .phase 0, /* CPHA0 */ .dataWidth 8 }; void mram_spi_init(void) { /* 这里以 Kinetis SDK 的 DSPI 接口为例 */ /* 实际工程替换为对应平台的 SPI 初始化即可 */ SPI_InitTypeDef init {0}; init.baudRateBps mramSpiCfg.baudRateBps; init.polarity mramSpiCfg.polarity; init.phase mramSpiCfg.phase; init.dataWidth mramSpiCfg.dataWidth; SPI_Init(SPI0, init); }3.2 基本读写函数读、写、等待MR25H40CDF 的命令集和普通 SPI Flash 很像核心命令只有几个命令操作码说明WRITE ENABLE0x06让芯片允许写入WRITE DISABLE0x04禁止写入READ STATUS0x05读状态寄存器WRITE STATUS0x01写状态寄存器READ DATA0x03从指定地址连续读FAST READ0x0B带 dummy 周期的高速读WRITE DATA0x02从指定地址连续写READ ID0x9F读 JEDEC 厂商 IDMRAM 写数据有一个关键点必须先发送写使能命令再发送写数据命令而且两段命令之间 CS 必须有一个从低到高的跳变。很多新手把 0x06 和 0x02 连在一起不拉高 CS结果芯片根本不理会写数据命令。这个细节我在代码里反复强调因为移植到别的 SPI 设备时特别容易忘。读数据函数static void mram_cs_low(void) { GPIO_SetPin(GPIOA, 15, 0); } /* 以实际引脚为准 */ static void mram_cs_high(void) { GPIO_SetPin(GPIOA, 15, 1); } static uint8_t spi_byte(uint8_t data) { /* 通用 SPI 收发函数一次交换一个字节 */ return SPI_TransferByte(SPI0, data); } void mram_read(uint32_t addr, uint8_t *buf, uint32_t len) { uint32_t i; if (len 0) return; mram_cs_low(); spi_byte(0x03); /* READ DATA */ spi_byte((addr 16) 0xFF); /* 地址高字节 */ spi_byte((addr 8) 0xFF); /* 地址中字节 */ spi_byte(addr 0xFF); /* 地址低字节 */ for (i 0; i len; i) { buf[i] spi_byte(0x00); /* 之后每个 SCK 时钟读回一字节 */ } mram_cs_high(); }写数据函数uint8_t mram_wait_idle(void) { uint8_t status; uint16_t timeout 1000; do { mram_cs_low(); spi_byte(0x05); /* READ STATUS REGISTER */ status spi_byte(0x00); mram_cs_high(); if (!(status 0x01)) /* WIP 位为 0空闲 */ return 0; } while (--timeout); return 1; /* 超时 */ } void mram_write(uint32_t addr, const uint8_t *buf, uint32_t len) { uint32_t i; /* 第一步写使能 */ mram_cs_low(); spi_byte(0x06); mram_cs_high(); /* 第二步写数据命令 */ mram_cs_low(); spi_byte(0x02); /* WRITE DATA */ spi_byte((addr 16) 0xFF); spi_byte((addr 8) 0xFF); spi_byte(addr 0xFF); for (i 0; i len; i) { spi_byte(buf[i]); } mram_cs_high(); /* 第三步等待写入完成 */ mram_wait_idle(); }MRAM 理论上写入是即时完成的mram_wait_idle 绝大多数情况下一次循环就退出。保留这个等待函数有两个好处代码可以兼容普通 Flash且能检测极端情况下 SPI 通信是否异常。3.3 状态寄存器与写保护的坑状态寄存器每一位的含义很简单bit0 是 WIPBusybit1 是 WELWrite Enable Latchbit2 到 bit6 在标准 SPI 模式下一般不用bit7 是 WP 引脚的写保护使能状态。写使能命令 0x06 会把 WEL 置 1写完数据后自动清 0。如果在调试中发现写入不生效先读状态寄存器看 WEL 是不是置位。如果 WEL 一直是 0说明 MCU 没正确发送 0x06或者 WP 引脚被拉低芯片处于保护状态。我做过一个现场排查就遇到这问题客户把 WP 引脚空着没接噪声偶尔把 WP 拉下去结果固件写入时有时无重新上拉后就恢复了。还有一点MRAM 虽然不需要擦除但支持兼容的擦除命令。擦除操作实际是把大片地址写成 0xFF。我基本不用擦除命令因为直接写覆盖就能改变任何区域的数据没必要多一道流程。3.4 数据一致性方案双缓冲 序列号MRAM 寿命再长也不能保证掉电瞬间所有字节都同时写入完成。虽然 MRAM 的单字节写是原子操作但如果你写的是几十字节的记录掉电有可能打断在中间。在工业现场掉电往往毫无预兆所以我在设计存储协议时都会加一层“事务保护”。最简单的方案是 A/B 双缓冲。假设每条记录 64 字节A 区放在地址 0x00000B 区放在地址 0x00100。每条记录开头放一个 4 字节递增序列号末尾放 CRC32。写入时写当前不活跃的区域全部写完后再更新该区序列号。上电读取时比较两个区的序列号谁大说明谁是最近一次完整写入的。#define REC_SIZE 64 #define REC_CRC_OFF (REC_SIZE - 4) #define REC_SEQ_OFF (REC_SIZE - 8) int32_t mram_load_recent(uint32_t *seq_out, uint8_t *data_out) { uint8_t bufA[REC_SIZE], bufB[REC_SIZE]; uint32_t seqA, seqB, crcA, crcB; mram_read(0x00000, bufA, REC_SIZE); mram_read(0x00100, bufB, REC_SIZE); seqA bufA[REC_SEQ_OFF] 24 | bufA[REC_SEQ_OFF1] 16 | bufA[REC_SEQ_OFF2] 8 | bufA[REC_SEQ_OFF3]; seqB bufB[REC_SEQ_OFF] 24 | bufB[REC_SEQ_OFF1] 16 | bufB[REC_SEQ_OFF2] 8 | bufB[REC_SEQ_OFF3]; crcA calc_crc32(bufA, REC_SIZE - 4); crcB calc_crc32(bufB, REC_SIZE - 4); if ((seqA 0 crcA ! 0) (seqB ! 0 crcB calc_crc32(bufB, REC_SIZE - 4))) { memcpy(data_out, bufB, REC_DATA_LEN); *seq_out seqB; } else if ((seqA seqB || seqB 0) crcA calc_crc32(bufA, REC_SIZE - 4)) { memcpy(data_out, bufA, REC_DATA_LEN); *seq_out seqA; } else if (crcB calc_crc32(bufB, REC_SIZE - 4)) { memcpy(data_out, bufB, REC_DATA_LEN); *seq_out seqB; } else { return -1; /* 两个区都损坏 */ } return 0; }双缓冲配合 MRAM 的即时写特性基本能应对掉电问题。CRC 校验放在每条记录尾部保证任何一位错误都能被发现。对于日志类数据我习惯再加一个环形指针区当前写指针位置存在固定地址每次循环写重启后从指针处继续。4. 实际调试问题与排查实录4.1 上电后读不到数据全是 0xFF第一次焊完测试板发现 MRAM 读出来全是 0xFF。这种问题九成出在硬件连接上排查顺序很重要。先用示波器或万用表量 MRAM 的 VCC 引脚是否有稳定的 3.3V再看 GND 是否真正连通然后检查 CS 上拉电阻是否焊好。如果这些都没问题就测 SPI 的四根线是否一一对应。特别注意 DFN8 封装下面的大焊盘。我遇到过一次“引脚电压全正常但芯片不响应”的情况最后发现是底部地焊盘没焊实芯片地回路靠引脚的一点接触线撑着工作极不稳定。重新补焊后一切正常。所以遇到读 0xFF先别急着怀疑代码用一根杜邦线把芯片地引脚和 PCB 地短接测试几乎立刻就能定位。4.2 写入后读回数据不对总是偏移如果写入的数据和读回的数据不一致尤其是指定地址的起始几个字节不对重点检查片选时序。CS 必须在命令和数据之间保持正确逻辑。调示波器抓 CS、SCK、MOSI 三条线对照数据手册里的时序图看首字节是在 CS 拉低后立刻出现还是晚了几个时钟。我在调试中发现过这样一个问题MCU 的 GPIO 驱动库在设置片选时引入较大延时导致第一个字节发送时 CS 已经拉低但 MRAM 要求 CS 拉低后 SCK 到来前有一个地址建立时间。这种微妙时序问题在开发板上未必暴露但在真实硬件和布局影响下就会出现。解法是片选拉低后增加几条空指令的延时或者用 SPI 外设的硬件片选功能让外设自己管理 CS 时序。4.3 偶发性数据损坏写入后过一阵子内容变了偶发数据损坏排查是最棘手的。先区分是硬件干扰还是软件逻辑问题。软件方面确认所有 MRAM 操作之间没有其他中断或者任务并发地访问 SPI 总线。如果两个任务同时抢 SPI 外设CS 时序被撕碎就会出现诡异数据。解决办法是给 SPI 访问加互斥量或者把 MRAM 操作禁止中断。硬件方面重点怀疑电源和地。用示波器长时间观察 MRAM 的 VCC看电机启动或者继电器吸合瞬间有没有毛刺跌落。我在现场遇到过一种情况大功率负载瞬间拉低 3.3V 电源MRAM 在电压临界状态执行写操作状态不确定事后读回数据就错了。后来在 MRAM VCC 旁边增加储能电容并且把 SPI 访问放在电源稳定的时间窗口内故障就消失了。还有一个容易忽略的点CS 线上的干扰。如果 CS 走线长且旁边有高频 PWM 线CS 电平可能被耦合拉低MRAM 会误以为 MCU 发起了一次命令从而执行伪写入。解决办法是 CS 上拉电阻改小到 4.7kΩ或者串联磁珠条件允许时在 CS 到地加一个低容值电容滤高频。4.4 读 ID 命令能快速验证通路调试时我最爱用的一个命令是 0x9F 读 JEDEC ID。只要通信链路是通的读 ID 一定会返回芯片的厂商码。MR25H40CDF 的厂商字节对应 Everspin 的 JEDEC 分配代码后面几字节是密度和版本信息。代码里设计一个 probe 函数初始化后先读 ID如果返回值不在预期范围内直接报错并阻止后续业务逻辑。int mram_probe(void) { uint8_t id[4]; mram_cs_low(); spi_byte(0x9F); for (int i 0; i 4; i) { id[i] spi_byte(0x00); } mram_cs_high(); /* 厂商字节固定为 Everspin 的 JEDEC 码具体值以数据手册为准 */ if (id[0] 0x29) return 0; /* 找到芯片 */ return -1; }这个函数在产线自检和现场维修时特别有用。设备上电后如果 probe 失败可以直接在调试串口打印“MRAM not found”维修人员就不用拆机量信号了。5. 这个方案还能做什么工业应用扩展5.1 保存电机换向角度和零点标定KV42 本身面向电机控制电机刹车断电后转子位置丢不丢直接决定下次上电能不能无缝启动。用 MRAM 保存当前电角度和编码器零点每次换向前更新一次几万转的累计写入量对 MRAM 来说毫无压力。上电后 MCU 从 MRAM 读回电角度配合编码器校正设备重启后可以做到“无感恢复”。5.2 生产计数、配方和累计运行时间对执行机构动作次数、维护周期、累计运行时间这种需要频繁累加的字段用 MRAM 做存储是天然契合。每次计数都直接写不需要做磨损均衡。我做过的一台设备每 30 秒更新一次运行时长一年写入约 100 万次如果用普通 Flash 早就磨穿了换 MRAM 之后完全不用管。5.3 异常事件日志的环形缓冲设备出故障时现场工程师最需要的是故障前后一段时间的运行轨迹。用 MRAM 的 512KB 空间做一个环形缓冲区记录最近几千条带时间戳的事件数据全部带 CRC。掉电后不会消失上电后可以导出分析。环形缓冲的指针本身也放在 MRAM 里每次写完指针后更新指针区。这里要注意环形缓冲的指针如果和日志数据同时写在邻近地址掉电时可能有一项更新失败。我会把指针单独放在一个固定地址和日志区域隔开至少一个扇区距离并且用双字镜像保存读的时候取两次一致的值。5.4 作为 Bootloader 和固件升级的暂存区MRAM 写入速度快、寿命长也可以用来做固件升级暂存区。先把升级包从外部通信接口写入 MRAM校验完整后Bootloader 再从 MRAM 读取并编程内部 Flash。好处是升级过程中如果外部通信断掉暂存区保留上次数据可以断点续传不会影响当前运行固件。6. 最后的实际操作心得回头复盘这个项目最深的体会是存储方案没有万能解选型要对准写入频率、数据容量和掉电要求这三个维度。MR25H40CDF 和 MKV42F128VLH16 的组合让我省去了大量“存储坏了返修”的麻烦但同时也要求硬件上把上拉、去耦、布线细节处理好软件上把双缓冲、CRC、ID 探测这些基本功做到位。新项目拿到样片后我建议你先写一个最笨的测试程序往 MRAM 写固定长度的 0x55、0xAA再全读回来比对反复跑几万次模拟真实掉电环境做测试。通过之后再开始写业务层后面会顺手很多。把写使能、等待、ID 探测封装成独立模块以后换其他 MRAM 型号或者 FRAM 时只改一小层驱动接口就行。这套路我沿用到现在踩过的坑基本都堵上了。
返回列表