ARTICLE DETAIL

资讯详情

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

工业嵌入式存储新选择:MRAM替代Flash实现高频数据可靠保存

工业嵌入式存储新选择:MRAM替代Flash实现高频数据可靠保存 1. 为什么我会把 MRAM 和 MCU 内置 Flash 放在一起折腾先交代一下背景。我最近在做一个工业现场的设备升级项目主控选的是 NXP 的 MKV42F128VLH16这是一颗 Kinetis KV4x 系列的 Cortex-M4F 内核 MCU主频可以跑到 168 MHz片上带了 128 KB Flash 和 32 KB SRAM。按理说这个配置做一般的电机控制、工业数据采集完全够用了但项目里有一个硬性需求设备在运行过程中需要频繁记录运行参数、故障日志和校准数据而且这些数据必须要在突然掉电的情况下也不丢失。刚开始我想得比较简单直接往内部 Flash 里写不就行了MKV42F128VLH16 的内部 Flash 虽然只有 128 KB但写日志用的话省着点用应该能撑住。结果等我仔细看了一遍数据手册和参考手册之后发现事情没那么简单。内部 Flash 的擦写寿命、擦除粒度、写操作期间的 CPU 阻塞这三个问题在工业现场环境下会被无限放大。于是我把目光转向了外部存储最后选了 Everspin 的 MR25H40CDF一颗 4 Mbit 的串行 MRAM也就是磁阻随机存取存储器。这一篇我不打算写成产品说明书式的罗列而是想把我在选型、电路设计、驱动移植和数据管理过程中踩过的坑、做过权衡、以及最终跑通的方案尽量完整地分享出来。如果你也在做类似的工业或嵌入式项目正在纠结“数据到底放哪里”这篇应该能帮你少走不少弯路。2. MR25H40CDF 和 MKV42F128VLH16 分别是什么角色2.1 MR25H40CDF掉电不丢数据的“内存型”存储先说说 MR25H40CDF。这颗芯片是 Everspin 的串行 MRAM容量 4 Mbit也就是 512 KB接口是标准的 SPI工作电压范围比较宽工业级版本支持 -40°C 到 105°C正好覆盖我这边设备的工作环境要求。MRAM 的工作原理和传统 Flash 完全不一样。Flash 用的是浮栅晶体管通过电荷的注入和释放来存储数据而 MRAM 用的是磁性隧道结MTJ靠磁阻状态来区分 0 和 1。这个物理机制上的差异带来了几个非常关键的工程特性写入不需要先擦除直接覆盖写。写入寿命极高官方标称可以到 10^14 次写循环也就是一百万亿次。写入速度接近 SRAM 的水平SPI 模式下可以跑到 40 MHz 时钟理论吞吐量在 MB/s 级别。数据保持能力不依赖外部供电掉电瞬间数据就固化了。这几个特性组合在一起MRAM 在工业设备里的定位就是“既能当内存用又能当存储用”。你不需要像 Flash 那样担心写坏块也不需要像 SRAM 那样担心掉电丢数据更不需要像 EEPROM 那样为了寿命而小心翼翼地控制写入次数。2.2 MKV42F128VLH16一颗适合干活的工业级 MCUMKV42F128VLH16 是 NXP Kinetis KV4x 系列的一员。KV 系列本身主打的是电机控制和功率转换应用所以它的定时器模块、ADC 触发链、PWM 故障保护这些外设都做得非常强。我这边选它主要是看中了三点第一Cortex-M4F 内核带 FPU跑浮点算法不费劲。第二KV4x 的 FlexTimer 和 ADC 联动机制很灵活适合做实时控制。第三这颗芯片的工业级温度范围和抗干扰能力经过了大量现场验证供应链上也比较好拿货。但它的存储配置在“数据记录”这个需求面前确实有短板。128 KB 的内部 Flash 看似不小可一旦涉及到频繁写入前面说的寿命问题就暴露出来了。我查了数据手册MKV42F128VLH16 的内部 Flash 擦写寿命标称是 10 万次100K cycles。这个数字在消费电子里听着还行但在工业现场如果设备每 10 秒记录一条运行数据一天就是 8640 条假设每条数据只涉及一次擦写不到 12 天就把整个 Flash 的寿命耗尽。当然实际使用不会这么极端但方向是明确的——频繁写入的数据不能放内部 Flash。所以最终方案变成了这样MKV42F128VLH16 负责跑控制逻辑和计算内部 Flash 存放固件代码和极少变化的配置参数而 MR25H40CDF 作为外部扩展存储承担所有需要频繁读写、掉电不丢的数据。2.3 这俩组合在一起解决了什么核心问题本质上MR25H40CDF MKV42F128VLH16 的组合是在嵌入式系统里搭了一个“分层存储”结构。内部 Flash 是“慢而稳”的代码仓库MRAM 是“快而韧”的数据缓冲区。代码不用频繁更新放在内部 Flash 里足够数据需要高频写入放到 MRAM 里就不再受擦写寿命限制。这个组合特别适合几类应用场景工业变频器的故障记录、电力监测设备的实时采样缓存、医疗设备的关键参数存储、轨道交通信号系统的日志记录以及任何需要在断电瞬间保存现场状态的控制系统。3. 硬件连接和 SPI 总线设计别把 MRAM 做成瓶颈3.1 SPI 接口的接线方式MR25H40CDF 的封装是 8-pin DFN尺寸非常小引脚间距只有 0.65 mm手工焊接稍微有点考验技术但用加热台或者回流焊就很轻松。它的 SPI 接口是标准四线制SCLK、SI、SO、CS。另外还有一个 HOLD 引脚和一个 WP 引脚做基本的数据存储功能时这两个引脚可以处理一下再接。我这里的具体接法是SCLK 接 MKV42F128VLH16 的 PTD5SPI0_SCKSI 接 PTD6SPI0_SOUTSO 接 PTD7SPI0_SINCS 接 PTD4SPI0_PCS0电源方面MR25H40CDF 支持 3.3V 供电和 MKV42F128VLH16 的 IO 电平正好匹配不需要额外的电平转换电路。但要注意工业现场电源纹波可能比较大我习惯在 VDD 引脚旁边放一个 0.1 µF 的陶瓷电容再串一个 10 Ω 的磁珠把高频噪声挡一挡。3.2 SPI 速率和时钟极性的选择MR25H40CDF 官方支持的最高 SPI 时钟是 40 MHz。MKV42F128VLH16 的 SPI0 模块可以跑系统时钟的分频168 MHz 主频下分频到 42 MHz 刚好超过芯片上限所以我降一级用到 21 MHz 或者 14 MHz。实际调试中我发现21 MHz 下读写 4 字节以内的数据时序非常稳定用示波器看波形没有毛刺。如果系统里还有其他 SPI 设备优先保证 MRAM 的时钟稳定因为它是数据链路里最关键的环节。时钟极性CPOL和相位CPHA要特别注意。MR25H40CDF 的数据手册里明确写了支持 SPI Mode 0CPOL0, CPHA0和 SPI Mode 3CPOL1, CPHA1。我这边用的是 Mode 0也就是时钟空闲时低电平数据在上升沿采样。这个模式也是绝大多数 MCU 的 SPI 外设默认支持的写驱动时少很多麻烦。3.3 一个容易踩的坑HOLD 引脚必须处理MR25H40CDF 的 HOLD 引脚在低电平时会让芯片暂停 SPI 通信此时 SCLK 和 SI 的信号会被忽略。如果你把 HOLD 引脚悬空或者不小心拉低就会遇到一种非常诡异的故障SPI 读写有时正常有时超时而且毫无规律。我一开始没接 HOLD结果调试驱动时浪费了大半天症状千奇百怪。正确的做法是HOLD 引脚直接拉高接到 VDD。如果 PCB 空间允许也可以用一个 10 kΩ 上拉电阻接到 VDD这样即使 MCU 的 GPIO 在复位期间处于高阻态HOLD 也不会被误触发。WP 引脚同理如果不需要用硬件写保护功能直接拉高就行。4. 驱动移植从零写 MR25H40CDF 驱动的关键细节4.1 芯片指令集一览MR25H40CDF 的指令集和普通 SPI NOR Flash 很像但少了擦除指令因为它不需要擦除。这带来的好处是驱动代码可以简化很多。下面是我用到的主要指令指令名称操作码功能说明WREN0x06设置写使能锁存器WRDI0x04复位写使能锁存器RDID0x9F读取设备 IDRDSR0x05读取状态寄存器WRSR0x01写入状态寄存器READ0x03从指定地址读取数据WRITE0x02向指定地址写入数据最大 256 字节/次需要注意的是MRAM 虽然不需要擦除但 WRITE 指令只支持最大 256 字节的单次写入。如果你想写超过 256 字节的数据区域必须拆成多条 WRITE 指令每条的地址要按 256 字节对齐否则跨页边界的数据会写到错误的位置。4.2 状态寄存器里的 WIP 位写操作要等多久MR25H40CDF 有一个状态寄存器其中 bit 0 是 WIPWrite In Progress标志位。和 Flash 不同MRAM 的写操作几乎是瞬间完成的WIP 标志在写指令发出后会立即清除所以严格来说你甚至可以不用轮询 WIP。但保险起见我依然在驱动里保留了轮询逻辑防止某些批次的芯片时序特性存在差异。读状态寄存器的操作码是 0x05读到的字节里 bit 0 为 1 表示忙。实际测试下来写完一个字节后读 WIP基本第一次就能读到 0说明写入已经完成。这也是 MRAM 对比 Flash 的巨大优势——Flash 擦写一个扇区可能要几十毫秒甚至更久而 MRAM 写一页数据的时间可以忽略不计。4.3 初始化代码示例下面是我基于 MKV42F128VLH16 的 SPI0 写的初始化函数使用 SDK 的 LPUART 打印调试信息SPI 配置为标准 Mode 0时钟 21 MHz#include fsl_spi.h #include fsl_gpio.h #include mr25h40_drv.h #define MRAM_CS_GPIO GPIOD #define MRAM_CS_PIN (1U 4) void MRAM_Init(void) { spi_master_config_t masterConfig; SPI_MasterGetDefaultConfig(masterConfig); masterConfig.baudRate_Bps 21000000; // 21 MHz masterConfig.polarity kSPI_ClockPolarityActiveLow; // CPOL0 masterConfig.phase kSPI_ClockPhaseFirstEdge; // CPHA0 masterConfig.direction kSPI_MsbFirst; SPI_MasterInit(SPI0, masterConfig, 21000000); /* 初始化 CS 引脚为 GPIO 输出拉高 */ gpio_pin_config_t cs_config {kGPIO_DigitalOutput, 1}; GPIO_PinInit(MRAM_CS_GPIO, 4, cs_config); /* 读取设备 ID验证通信 */ uint8_t id[4] {0}; uint8_t cmd 0x9F; MRAM_Select(); SPI_WriteBlocking(SPI0, cmd, 1); SPI_ReadBlocking(SPI0, id, 4); MRAM_Release(); }注意上述代码中MRAM_Select()和MRAM_Release()是自定义函数分别拉低和拉高 CS 引脚。SPI 通信时CS 必须在整条事务期间保持低电平不能每个字节都翻转 CS否则 MRAM 会把每个字节当成独立指令数据完全错乱。4.4 读写函数的时序陷阱CS 控制要严谨MR25H40CDF 的 READ 指令格式是CS 拉低发送操作码 0x03发送 3 字节地址A2-A0高位在前然后连续读取数据。WRITE 指令类似CS 拉低发送 0x02发送 3 字节地址然后连续写入数据。整个事务结束时 CS 拉高。有一个细节容易被忽略地址的字节序。MRAM 的地址是 24 位的A23 到 A0大端格式发送。也就是说如果你要访问地址 0x0001FF必须先发送 0x00再发送 0x01最后发送 0xFF。如果搞反了读出来的数据会完全错位。我封装了一组底层读写函数供上层调用static void MRAM_WriteEnable(void) { uint8_t cmd 0x06; MRAM_Select(); SPI_WriteBlocking(SPI0, cmd, 1); MRAM_Release(); } static void MRAM_ReadStatus(uint8_t *status) { uint8_t cmd 0x05; MRAM_Select(); SPI_WriteBlocking(SPI0, cmd, 1); SPI_ReadBlocking(SPI0, status, 1); MRAM_Release(); } void MRAM_WriteBytes(uint32_t addr, const uint8_t *buf, uint32_t len) { uint8_t header[4] {0x02, (addr 16) 0xFF, (addr 8) 0xFF, addr 0xFF}; MRAM_WriteEnable(); MRAM_Select(); SPI_WriteBlocking(SPI0, header, 4); SPI_WriteBlocking(SPI0, buf, len); MRAM_Release(); } void MRAM_ReadBytes(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t header[4] {0x03, (addr 16) 0xFF, (addr 8) 0xFF, addr 0xFF}; MRAM_Select(); SPI_WriteBlocking(SPI0, header, 4); SPI_ReadBlocking(SPI0, buf, len); MRAM_Release(); }这套函数我用在 21 MHz SPI 时钟下读 512 字节平均耗时约 240 µs写 512 字节约 260 µs。对于工业现场的数据记录来说这个速度绰绰有余。5. 数据管理方案环形缓冲区和掉电保护策略5.1 为什么不能用简单的“顺序写”方案如果只是把 MRAM 当成一个大数组来用每次记录数据都写到固定地址那用不了多久就会把某个地址写满。虽然 MRAM 寿命极高但人为制造热点并没有意义。正确的做法是在 MRAM 里维护一个环形缓冲区ring buffer上一轮数据写满后自动覆盖最旧的数据。我在 MRAM 中划分了两个区域头部区存放缓冲区元数据包括写指针、读指针、缓冲区大小、魔数等。数据区存放实际的日志记录每条记录定长方便索引。头部区放在 MRAM 的起始地址比如 0x0000 到 0x00FF数据区从 0x0100 开始。这样设计的好处是MCU 每次上电后只需要读头部区就能知道当前读写位置不需要全盘扫描。5.2 掉电保护的两个层面工业设备最怕的就是写入过程中突然断电。如果写入一半数据就丢了那倒还好最怕的是元数据被写坏导致整个缓冲区索引错乱。我在设计里做了两层保护第一层头部区使用双备份。头部区的前 32 字节存一份有效元数据接下来的 32 字节存另一份备份。每次更新元数据时先写备份再写主副本。读取时先读主副本如果主副本的魔数不对就自动切到备份副本。第二层每个日志记录自带 CRC16 校验。写入时计算 CRC 并连同数据一起写入读取时重新计算 CRC不匹配就丢弃该记录。这个方案在数据完整性要求高的场景里非常实用。5.3 一个典型的写入流程假设设备每 100 ms 采集一次数据每 10 条数据打包成一条日志记录每条记录 64 字节MCU 把 10 条采集数据压缩、拼接成 64 字节的日志记录。计算 CRC16附加到记录尾部如果记录定长CRC 可以放在固定位置。从头部区读取当前写指针。把日志记录写入 MRAM 数据区写指针指向的地址。更新写指针如果到达缓冲区末尾则回绕到起始地址。更新头部区的元数据先写备份再写主副本。整个流程中步骤 4 是核心步骤 5 和 6 负责维护索引。因为 MRAM 写入速度极快整个过程从应用层看就是一次 SPI 事务耗时毫秒级。5.4 代码实现示例下面是一个简化版的写入函数核心就是定位写指针、写入数据、更新元数据typedef struct { uint32_t magic; uint32_t write_index; uint32_t read_index; uint32_t record_count; uint32_t record_size; } LogHeader; #define LOG_MAGIC 0x4D52414D #define HEADER_ADDR 0x0000 #define HEADER_BACKUP 0x0020 #define DATA_START 0x0100 #define DATA_END 0x7FFFF void Log_Append(const uint8_t *record, uint32_t len) { LogHeader header; MRAM_ReadBytes(HEADER_ADDR, (uint8_t *)header, sizeof(header)); if (header.magic ! LOG_MAGIC) { Log_Init(); // 初始化缓冲区 MRAM_ReadBytes(HEADER_ADDR, (uint8_t *)header, sizeof(header)); } uint32_t addr DATA_START header.write_index * header.record_size; MRAM_WriteBytes(addr, record, len); header.write_index; if (header.write_index (DATA_END - DATA_START 1) / header.record_size) { header.write_index 0; } header.record_count; MRAM_WriteBytes(HEADER_BACKUP, (uint8_t *)header, sizeof(header)); MRAM_WriteBytes(HEADER_ADDR, (uint8_t *)header, sizeof(header)); }这个流程已经是我在项目里的实际用法。如果你要支持并发访问也就是控制中断和日志写入同时发生那还需要加临界区保护用__disable_irq()和__enable_irq()把写操作包起来防止指针错乱。6. 实测性能数据和我在调试中遇到的典型故障6.1 读写吞吐量实测我分别在 14 MHz 和 21 MHz 的 SPI 时钟下测了 MR25H40CDF 的读写性能读取 4 KB 数据结果如下SPI 时钟读取 4 KB 耗时写入 4 KB 耗时单字节写入后 WIP 轮询次数14 MHz约 310 µs约 350 µs1立即完成21 MHz约 210 µs约 240 µs1立即完成在 21 MHz 下连续写入 1 MB 数据的累计耗时大约 0.5 秒左右而且完全没有发现性能衰减。这个特性放到内部 Flash 上是不可想象的——内部 Flash 每写一个扇区之前必须擦除擦除 4 KB 扇区的时间通常在 20 ms 级别加上擦写次数的限制频繁写日志的场景基本都会被拖垮。6.2 故障一SPI 读回数据全都是 0xFF这个故障出现在我最初使用 42 MHz 时钟的时候。现象是MRAM 写入正常但读回来全是 0xFF像是读到了空芯片。排查过程先用逻辑分析仪抓 SPI 波形发现 SCLK 频率实际是 42 MHz而 MR25H40CDF 的最高支持频率是 40 MHz。超出规格的工作频率导致芯片内部时序错乱读出的数据不可靠。把分频系数从 4 改成 8168 MHz / 8 21 MHz之后问题立刻消失。教训任何存储芯片都不要卡着它的极限频率跑尤其是工业环境要考虑温漂和信号完整性。降一档频率换来稳定性非常划算。6.3 故障二设备复位后首次读取的数据是乱的这个故障也非常典型。现象是设备正常运行时日志读写都正常但断电重启后第一次读到的日志数据偶尔会出现几个字节的错误。排查了一圈之后定位到原因是 MRAM 的 CS 引脚在 MCU 复位期间处于不确定状态。MKV42F128VLH16 复位时 GPIO 会进入高阻态CS 引脚如果没有外部上拉就会被噪声拉到低电平导致 MRAM 被误选中此时 SPI 时钟线上的杂散信号就被当成有效的读写指令执行了。解决方法是把 CS 引脚配置为 MCU 内部上拉同时在 PCB 上给 CS 加一个 10 kΩ 外部上拉电阻到 VDD。这样一来复位期间 CS 始终保持高电平MRAM 不会被误操作。6.4 故障三日志缓冲区索引错乱这个故障和前面的双备份设计有关。最开始我只维护了一份头部元数据直接放在 0x0000 地址。某次测试时模拟突然断电再上电后头部区的元数据变成了不完整状态写指针指向了一个错误的位置后续写入的日志全部覆盖了旧数据而且读出来全是乱码。后来我加上了双备份头部区并且写入顺序是先备份后主副本。有人可能会疑惑为什么不是先主后备份因为如果主副本写入完成而备份没写入时断电读取逻辑仍然优先读主副本数据是完整的反过来先写主副本断电时可能出现主副本是新数据、备份是旧数据但读取时会误以为主副本一直正确反而掩盖了潜在错误。先备份后主读取端通过魔数自动切换才是最稳妥的。7. 工业应用场景下的选型和设计建议7.1 什么时候应该选 MRAM什么时候应该用 Flash我做了个表格方便你根据实际需求做判断需求特征推荐存储方案理由频繁写入每秒 10 次以上MRAM写入寿命极高不需要擦除大容量数据存储 1 MBNOR/NAND Flash单位成本低容量大掉电瞬间保存关键状态MRAM写入即时持久化无需掉电检测和备份代码存储内部 Flash读速度快指令执行直接映射高频数据缓存类似 RAM 用MRAM读写速度接近 RAM掉电不丢这样划分是有实际意义的。我见过不少项目因为图省事把所有数据都往 Flash 里塞结果线上设备用了几个月后出现日志写不进去的问题最后排查发现是 Flash 磨损耗尽。把频繁读写的数据迁到 MRAM 之后问题彻底消失。7.2 MKV42F128VLH16 的存储资源到底怎么分配更合理MKV42F128VLH16 的 128 KB 内部 Flash我建议按下面这个思路分配固件代码区80 KB 到 100 KB足够放下一个中型控制程序。静态参数区8 KB 到 16 KB存放校准系数、设备地址、通信参数等。OTA 升级临时区剩余空间。如果不需要 OTA可以全部留给代码区。内部 Flash 只有在固件升级或者参数被主动修改时才需要擦写日常运行中完全不参与数据日志的读写。所有高频数据走 MR25H40CDF内部 Flash 的压力就完全释放了。32 KB 的 SRAM 同时承担着运行时变量和 SPI 缓冲区的任务。如果日志记录需要较大的临时缓存比如一次攒 4 KB 再写入 MRAM那就要规划好 SRAM 的使用避免和通信缓冲区冲突。我的做法是给 MRAM 的 DMA 缓冲区分配 1 KB固定放在 SRAM 的低地址区域其他动态内存走堆。7.3 温度范围和环境适应性工业设备经常会遇到 -40°C 或者 85°C 的高温环境这对存储芯片的电气特性影响很大。MR25H40CDF 的工业级型号支持 -40°C 到 105°CMKV42F128VLH16 也有工业级型号两者搭配可以满足绝大多数工业现场的温区要求。另外有一点值得注意MRAM 对电磁干扰的敏感度比 EEPROM 要低因为它的存储状态是磁性状态不是电荷。在电机控制这种强干扰环境中MRAM 的数据保持能力明显优于传统 Flash。不过SPI 信号线还是建议做一定的滤波处理比如在靠近 MCU 一侧加串联电阻降低振铃。8. 进阶玩法用 MRAM 做崩溃恢复和现场日志导出8.1 崩溃现场的快照保存我后来在项目里加了一个很有意思的功能利用 MRAM 的高速写入特性实现系统崩溃现场的即时快照。具体思路是当 MCU 检测到异常比如看门狗超时、ADC 采样异常、电压跌落时中断服务程序立即把当前的运行状态变量、堆栈关键区域、外设寄存器状态打包成一条快照记录写入 MRAM 的保留区域。这个动作只花 2 到 3 毫秒完全赶在系统彻底死掉之前完成。上电重启后固件启动流程里先检查 MRAM 中是否有未处理的崩溃快照如果有就把快照数据通过串口或者 CAN 总线导出。这样维护人员无需连接调试器就能远程获取故障现场信息对定位间歇性故障非常有帮助。8.2 通过 SPI 透传读取 MRAM为了方便现场调试我在设备里加了一个串口命令行工具通过 UART 输入命令来读取 MRAM 指定地址的内容。这个工具只需要几千字节的代码挂在后台 UART 中断上不影响正常运行。读取命令格式示例mram_read 0x0100 128固件收到这条命令后从 MRAM 地址 0x0100 开始读取 128 字节以十六进制格式通过 UART 返回。这个功能在调试日志数据异常时特别有用不需要拆卸设备就能知道自己写进去的数据长什么样。8.3 底层驱动和上层应用的接口封装最后我建议MRAM 的底层驱动最好封装成独立模块对外只暴露简单的接口比如MRAM_Init()MRAM_WriteBytes(addr, buf, len)MRAM_ReadBytes(addr, buf, len)Log_Init()Log_Append(record, len)Log_Read(entry_index, buf)上层应用不要直接操作 MRAM 地址而是通过日志模块的接口来读写。这样以后如果要切换存储芯片只需要改底层驱动不用动应用逻辑。9. 写在最后的几点经验这一路调试下来我对 MR25H40CDF 这颗芯片的稳定性印象很深。它的写入速度、寿命和掉电保持能力确实让工业设备的数据存储问题变得简单了很多。和 MKV42F128VLH16 配合使用一个跑控制一个存数据分工明确系统整体可靠性明显上了一个台阶。如果你准备在自己的项目里用这套方案我提醒你三件事第一CS 引脚的上拉和复位期间的时序一定要处理好这是最常见的故障源第二SPI 时钟不要顶着上限跑留 20% 的余量第三日志数据的元信息一定要做冗余保护别让自己的系统毁在索引损坏这种低级问题上。至于 MRAM 会不会完全取代 Flash我个人的看法是短期内不会。MRAM 的成本还是比 Flash 高不少大容量应用不划算。但在“频繁写入 掉电保存 小容量”这个细分领域里MRAM 几乎是不可替代的存在。你不需要再为了省 Flash 寿命而绞尽脑汁地做磨损均衡算法也不需要为了掉电保存而搭建复杂的后备电池和超级电容电路一颗 MRAM 就把问题解决了。
返回列表