前阵子做的一套工业现场设备需要高频记录运行数据,主控选了 Microchip PIC18F97J94,存储介质则换成了 Everspin 的 MR25H40CDF,一颗 4Mb 的 SPI 接口 MRAM。之前这块板子用 SPI NOR Flash 存日志,几个月就跑出各种诡异问题:偶尔丢记录、写入失败、整片数据读出来异常。换到 MRAM 之后,读写逻辑简单了一个量级,故障也消失了。这篇就把整个项目从选型、硬件连接、驱动实现到可靠性设计的过程写一写,给正在做嵌入式数据存储的朋友一个参考。
1. 为什么用 MR25H40CDF 而不是 Flash 或 EEPROM:工业存储选型的那点事
1.1 从一块被“写穿”的 SPI NOR Flash 说起
最开始选存储芯片时并没有太上心,觉得日志量不大,容量用 1MB 或 4MB 的 SPI NOR Flash,买个常见型号就行。结果设备在现场连续跑了两三个月,陆续有用户反馈设备重启后部分历史记录丢失,甚至开机自检报存储器错误。当时排查了很久,最终定位到几个根因。
第一个根因比较直接:NOR Flash 写之前必须先擦除,而擦除的最小单位通常是 4KB 或 64KB 的扇区。日志是追加写的,如果某个扇区反复写、反复擦,寿命消耗非常快。常见 SPI NOR Flash 标称擦写寿命是十万次左右,看着不少,但工业现场如果每 10 秒写一条日志,一天就是 8640 次,一个扇区撑不到半个月。就算做磨损均衡,也要在固件里维护块映射表,麻烦不说,掉电期间映射表一旦写坏,整个存储分区就废了。
第二个根因来自掉电。NOR Flash 写数据时如果突然断电,当前扇区可能处于“擦除一半”或“写一半”的中间状态。重新上电后芯片状态机是否还能恢复,完全看运气。我们有一台返修机就是拆开看,Flash 里某些地址读出来既不是旧数据也不是新数据,而是 0x5A5A5A5A 这种乱码,典型的半写。
第三个根因是写延迟。NOR Flash 的字节写周期通常要几十微秒到几百微秒,擦写一个扇区至少几十毫秒。数据记录本身不复杂,但为了避开忙等待,往往要引入任务调度或延迟,代码越写越绕。
后来重新选型,把目光放到 MRAM 上。MRAM 的写机制和 Flash 完全不同,不需要擦除,改哪个字节就直接写哪个字节,供电掉了数据也不会丢,没有“半个扇区”的概念。所以到了第二轮设计,直接用 MR25H40CDF 替换掉了原来的 Flash。
1.2 MRAM 与 NOR Flash、EEPROM 的横向对比
MR25H40CDF 属于 Everspin 的串行 MRAM 系列,容量 4Mb,也就是 512KB,SPI 接口,8 脚封装。很多同学一听到“磁随机存储器”就觉得是黑科技,其实工程上不需要理解太多物理原理,只需要把它理解为“像失效 RAM 一样按字节随机读写,但是掉电不丢数据”的芯片就行。
为了说明这个问题,我把几类常用非易失存储放在一起做了个对比,不一定覆盖所有型号,但整体趋势是这样的:
| 特性 | MR25H40CDF(MRAM) | SPI NOR Flash | SPI EEPROM |
|---|---|---|---|
| 容量范围 | 常见 256Kb~4Mb,也有更大 | 1Mb~512Mb 甚至更大 | 通常 1Kb~1Mb |
| 写前是否要擦除 | 不需要 | 必须擦除扇区 | 不需要 |
| 随机字节写 | 支持,直接改写 | 有限制,只能把 1 改 0,要擦除才能写 1 | 支持 |
| 擦写寿命 | 理论 10^14 次量级 | 10^4~10^6 次 | 10^6 次左右 |
| 写周期 | SPI 时钟级,无忙状态 | 页编程几毫秒,擦除更久 | 5ms 级别 |
| 掉电数据保持 | 20 年量级 | 10~20 年 | 100 年以上(容量小) |
| 工作温度 | 工业级选项,宽温 | 工业级常见 | 工业级常见 |
| 单片价格 | 中等 | 低 | 低 |
对于日志记录、故障数据保存、校准参数频繁更新这种场景,MRAM 的“无擦除、高耐久、随机写”几乎是量身定做。代价是容量不如 Flash 大、单价高,所以在方案里它适合存“频繁写且可靠性要求高”的数据,大批量历史文件仍然可以交给大容量 Flash 或 SD 卡。
1.3 MR25H40CDF 工作方式和指令概览
MR25H40CDF 对外就是一颗标准 SPI Slave,一般都支持 SPI Mode 0 和 Mode 3,内部地址空间是 0x000000 到 0x07FFFF。要注意这里的单位:4Mb 是 4,194,304 位,除以 8 是 524,288 字节,也就是 512KB,所以地址要用 19 位,命令后面跟三个字节地址。
指令集和串行 Flash 很像,这也是工程上容易上手的原因。基本指令就那么几条:
| 指令 | 操作码 | 说明 |
|---|---|---|
| WREN | 0x06 | 写使能锁存 |
| WRDI | 0x04 | 写禁止锁存 |
| RDSR | 0x05 | 读状态寄存器 |
| WRSR | 0x01 | 写状态寄存器 |
| READ | 0x03 | 读数据 |
| WRITE | 0x02 | 写数据 |
| RDID | 0x9F | 读 JEDEC ID |
需要注意的是,MRAM 虽然不需要擦除,但写操作前依然要先发 WREN,把芯片内部的写使能锁存器置 1。这是很多第一次用的人容易忽略的细节。WREN 命令必须是一个完整的 CS 周期:CS 拉低、发 0x06、CS 拉高,然后才能发 WRITE 命令。如果偷懒把 WREN 和 WRITE 放在同一个 CS 低电平周期里,写操作不会生效。
另外,MR25H40CDF 写完后没有 Busy 状态,不需要轮询状态寄存器等待,CS 拉高,命令就完成了。这和 Flash 差别很大,驱动代码可以写得非常简洁。后面代码部分会具体说明。
2. 把 PIC18F97J94 和 MR25H40CDF 连起来:硬件设计
2.1 PIC18F97J94 的选型与外设分配
PIC18F97J94 是 Microchip 的 8 位单片机,100 脚封装,程序 Flash 空间大,外设也比较丰富,适合做中小规模工业控制板。选它主要是三方面考虑:一是工业级工作范围,二是 3.3V 供电和大多数传感器、存储芯片直接电平兼容,三是开发工具链成熟,用 MPLAB X IDE 加 XC8 编译器,嵌入式 C 语言开发效率不低。
在这块板子上,MR25H40CDF 接在 MSSP1 模块上,也就是 SPI1。PIC18F97J94 带两个 MSSP 模块,用哪个完全看引脚规划。我的习惯是:SPI 时钟和两根数据线尽量选在连续的 IO 上,方便布线,片选则随便挑一个 GPIO 来做。因为片选用 GPIO 控制比硬件片选更灵活,读写时想拉高就拉高,想拉低就拉低,不用受 MSSP 模块的片选逻辑限制。
这里额外说一句选型时的坑:8 位 MCU 的 Flash 写擦寿命也不高,不要用内部数据区去频繁记录日志。PIC18F97J94 虽然程序空间有 128KB,但它的 Flash 也不是为每秒钟写一次设计的,拿它当数据记录介质的话,很快消耗到寿命极限。外部挂 MRAM 是更稳妥的做法。
2.2 引脚连接与上下拉处理
MR25H40CDF 是 8 引脚封装,除了 VDD 和 GND,关键信号是 CS、SCK、SI、SO、WP、HOLD。与 PIC18F97J94 的接法如下:
| MR25H40CDF 引脚 | PIC18F97J94 引脚 | 说明 |
|---|---|---|
| CS# | GPIO 输出 | 片选,低电平有效 |
| SCK | SPI1 SCK | 时钟,主模式输出 |
| SI/DI | SPI1 SDO | 主出从进,MCU 发指令和数据 |
| SO/DO | SPI1 SDI | 主进从出,MCU 读数据 |
| WP# | VDD | 直接接高,允许写入 |
| HOLD# | VDD | 直接接高,不使用暂停功能 |
| VDD | 3.3V | 按手册加去耦电容 |
| GND | GND | 公共地 |
有两个地方特别提醒。
第一,SI 和 SO 不要接反。有些工程师习惯了把主机的 SDO 接到从机的 DI,结果另一边接成 DO,逻辑上没错,但 PCB 上稍微绕一下就很容易把两个信号弄混。实测中,接反以后读 JEDEC ID 会得到 0x00 或 0xFF,很难判断是时序问题还是接线问题,排查成本很高。建议打样之前用万用表按网络名一一测过再动烙铁。
第二,WP# 和 HOLD# 不要悬空。WP# 悬空在某些批次里有内部下拉或弱上拉,但波动可能导致误写保护;HOLD# 悬空时,芯片现场可能突然进入暂停状态,SCK 上的脉冲不再被解释成数据,会出现偶发读错。这两根脚最简单的做法是直接接到 VDD。如果想让整板在任何异常情况下都不会被软件改掉数据,也可以把 WP# 用一个 10k 电阻接地,同时在软件里用状态寄存器配合,稳妥但灵活性下降。
2.3 PCB 布局和现场抗干扰的经验
MR25H40CDF 的 SPI 时钟可以跑几十兆赫兹,我们实际只跑到几兆赫兹,所以 PCB 要求不算苛刻。工业环境里,还是建议遵守几个基本原则:SPI 的四根线尽量短,不要跨太长距离;去耦电容放在芯片电源脚附近,一般 0.1uF 和 1uF 并联;如果信号要经过连接器到另一块板,可以在 MOSI、SCK、CS 上各串一个 22 欧姆电阻,降低振铃。
这块板子最初是手工飞线搭的,SCK 线和 SI 线挨得很近,跑 8MHz 时钟时读数据偶尔错一个 bit。后来发现是飞线之间耦合噪声导致的。改成 PCB 走线后,问题消失。如果你只是做实验验证,飞线尽量短,优先降低时钟频率,写驱动调试时用 1MHz 就够了,不需要一上来就跑高频。
还有一个很多工程师容易忽略的地方:MRAM 的 CS# 在 MCU 复位期间必须保持高电平。PIC18F97J94 复位后 IO 默认是高阻输入,如果 CS# 没有外部上拉,芯片可能在上电瞬间被拉低,收到几根杂散 SCK 脉冲,误入某条指令。解决办法就是在 CS# 上加一个 10k 上拉到 VDD,或者把 CS 脚配置为带内部上拉的输出并尽早拉高。这个细节看起来小,但在工业现场复位瞬间很容易造成不可预期的行为。
3. 驱动代码:嵌入式 C 语言读写 MR25H40CDF 的完整实现
3.1 SPI 初始化:用 MCC 还是手写寄存器
PIC18F97J94 的 SPI1 初始化,我建议优先用 Microchip 的 MPLAB Code Configurator 图形化生成。MCC 里能直接选主模式、SPI 时钟速度、SPI Mode,生成代码后就不需要去翻寄存器位定义了。MR25H40CDF 支持 Mode 0 和 Mode 3,两者都行,我习惯选 Mode 0。
MCC 生成的初始化函数大致是这样的:
void SPI1_Initialize(void) { SPI1_Disable(); SPI1_WriteReg_SSP1STAT = 0x40; SPI1_WriteReg_SSP1CON1 = 0x20; SPI1_WriteReg_SSP1CON1bits.SSPEN = 1; }如果不想用 MCC,直接手写寄存器也可以。核心是把 MSSP 配成 SPI Master 模式,设置空闲时钟极性,然后使能模块。具体寄存器位因型号差异略有不同,最好对着 PIC18F97J94 数据手册的 MSSP 章节确认一下。实在不放心寄存器配置,可以先用 GPIO 模拟 SPI,把读写流程跑通,再切到硬件 SPI,这样问题定位容易很多。
SPI 时钟速度不是越高越好。对于这个项目,PCB 走线良好时跑 4MHz 完全没压力,8MHz 也可以。实验板建议先用 1MHz,排除高频干扰因素后再往上提。
3.2 WREN / READ / WRITE 基础封装
MR25H40CDF 的驱动实现其实很短,核心就是几条命令。我习惯先封装一个spi_exchange函数,然后在此基础上封装 MRAM 的读、写、状态查询。
// 伪代码,SPI 收发函数自身是 MCU 硬件 SPI 的封装 uint8_t spi_exchange(uint8_t byte) { SPI1_Exchange8bit(byte); }然后是写使能、读状态寄存器、写一字节、读一字节。
#define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRDI 0x04 #define MRAM_CMD_RDSR 0x05 #define MRAM_CMD_READ 0x03 #define MRAM_CMD_WRITE 0x02 #define MRAM_CMD_RDID 0x9F #define MRAM_CS_LOW() MRAM_CS_SetLow() #define MRAM_CS_HIGH() MRAM_CS_SetHigh() void mram_write_enable(void) { MRAM_CS_LOW(); spi_exchange(MRAM_CMD_WREN); MRAM_CS_HIGH(); } uint8_t mram_read_status(void) { uint8_t status; MRAM_CS_LOW(); spi_exchange(MRAM_CMD_RDSR); status = spi_exchange(0x00); MRAM_CS_HIGH(); return status; } void mram_write_byte(uint32_t addr, uint8_t data) { mram_write_enable(); MRAM_CS_LOW(); spi_exchange(MRAM_CMD_WRITE); spi_exchange((addr >> 16) & 0xFF); spi_exchange((addr >> 8) & 0xFF); spi_exchange(addr & 0xFF); spi_exchange(data); MRAM_CS_HIGH(); } uint8_t mram_read_byte(uint32_t addr) { uint8_t data; MRAM_CS_LOW(); spi_exchange(MRAM_CMD_READ); spi_exchange((addr >> 16) & 0xFF); spi_exchange((addr >> 8) & 0xFF); spi_exchange(addr & 0xFF); data = spi_exchange(0x00); MRAM_CS_HIGH(); return data; }这里有一个看起来很啰嗦但非常有用的习惯:每次写之前都无条件调用mram_write_enable()。因为 WREN 锁存会在一次成功的写操作之后自动被清除,重复调用既不会影响性能,又可以避免“上次忘了发 WREN 导致这次写入失败”这类问题。
MR25H40CDF 写完后不需要等待,这一点和 Flash 完全不同。所以上面的写函数里面没有轮询状态寄存器,CS 拉高就完了。如果后续读到错误数据,大概率是 SPI 配置或硬件连接问题,不是芯片状态问题。
3.3 批量读写与 512KB 地址回卷
实际项目里不可能一个字节一个字节地读和写,日志和参数通常都是一块一块地存取。批量写函数可以这样写:
void mram_write_buf(uint32_t addr, const uint8_t *buf, uint32_t len) { mram_write_enable(); MRAM_CS_LOW(); spi_exchange(MRAM_CMD_WRITE); spi_exchange((addr >> 16) & 0xFF); spi_exchange((addr >> 8) & 0xFF); spi_exchange(addr & 0xFF); for (uint32_t i = 0; i < len; i++) { spi_exchange(buf[i]); } MRAM_CS_HIGH(); }这个函数唯一的坑在于地址回卷。MR25H40CDF 容量是 512KB,内部地址从 0x000000 到 0x07FFFF。如果连续发数据,地址递增到 0x080000 后会回卷到 0x000000,并不是擦除到更大空间。所以写入时,如果末尾接近 0x07FFFF,必须把一段数据拆成两半,前半段写到 0x07FFFF,后半段从头开始继续写。
更严谨的做法是在写之前做边界判断:
uint32_t mram_remaining_to_top(uint32_t addr) { return 0x080000 - addr; }然后在调用mram_write_buf之前,根据剩余长度决定拆包还是分两次写。这个注意点对数据记录特别重要,因为日志区往往就是从高地址往低地址或环形管理,一不留神就会覆盖掉开头的参数区。
3.4 上电自检:读 JEDEC ID
MR25H40CDF 支持 RDID 命令,也就是 0x9F,正常能读回厂商 ID 和器件 ID。上电自检时读一下,既能确认 SPI 时序正确,也能确认芯片焊接、电源、接线都正常。
uint32_t mram_read_jedec_id(void) { uint32_t id = 0; MRAM_CS_LOW(); spi_exchange(MRAM_CMD_RDID); id = ((uint32_t)spi_exchange(0x00)) << 16; id |= ((uint32_t)spi_exchange(0x00)) << 8; id |= (uint32_t)spi_exchange(0x00); MRAM_CS_HIGH(); return id; }读回的值如果全是 0xFF 或者全是 0x00,基本上可以断定硬件连接或 SPI 配置有问题。前两方面的经验在调试部分会详细展开。自检通过后,再把 MRAM 里的关键参数读出来做 CRC 校验,通过后才认为存储系统初始化成功。
4. 工业场景的可靠性设计:数据存进去不等于万无一失
4.1 上电时序、复位与 CS 上拉
MR25H40CDF 本身在上电后即可工作,不需要漫长的初始化等待。但对整个系统来说,MCU 上电到引脚稳定之间,必须保证 CS 是高电平。我之前给 CS 脚配了外部 10k 上拉,同时把 HOLD 和 WP 都拉到 VDD,这样即使 MCU 在复位状态下,MRAM 也不会被误触发。
还有一个容易被忽视的时序点是 MCU 的片选 GPIO 默认状态。有些引脚在 MCU 复位期间是输出高,有些是高阻,有些还带弱上拉。上电瞬间如果 MRAM 的 CS 出现低电平脉冲,可能被判定为一次命令的开始,后续真正的命令时序就乱了。所以即便代码里初始化顺序完全正确,也不代表硬件上没有问题。我建议所有 MRAM 相关控制引脚在进入主程序的第一步,立即设置为输出并拉高 CS。
4.2 日志区格式与 CRC 校验
单纯把字节写进 MRAM 只能算是“能存取”,离“可靠存取”还差一步。工业日志记录最怕的事情是:写了一半掉电,重新上电后不知道哪些记录有效,哪些记录是残缺的。所以数据格式设计比驱动本身更影响现场可靠性。
我采用的做法是把整片 MRAM 分为参数区和日志区。参数区放设备校准参数、序列号、配置字,启动时读取并做 CRC 校验;日志区放运行记录,每条记录固定长度,结构大致如下:
typedef struct { uint16_t magic; // 固定值,比如 0xA55A,用来快速判断记录是否有效 uint16_t seq; // 自增序号,用来识别连续性 uint32_t timestamp; // 时间戳,来自 MCU 的 RTC 或外部时间源 uint8_t type; // 记录类型 uint8_t length; // 有效数据长度 uint8_t data[256]; // 实际数据 uint16_t crc; // CRC16,覆盖 magic 到 data 的所有字节 } LogRecord;每次写入一条记录时,先把整条记录填好,再一起写入 MRAM。读的时候,先检查 magic,再算 CRC,CRC 对不上就认为这条记录不完整。根据 seq 是否连续,还能判断中间是否丢了记录。这是一个非常简单但很实用的方案。
4.3 掉电保护:MRAM 不丢数据,但你的索引会丢
很多人以为芯片掉电不丢数据,就万事大吉了。实际上芯片确实不丢数据,但固件里的“写指针”如果放在内部 RAM 里,掉电一样会丢。如果放在 MRAM 里,写指针更新到一半掉电,也会出现索引和实际数据不一致的问题。
我用的方法是“双份索引加提交标记”。日志区有一个固定位置存放写指针,具体包含当前写位置、当前序号、最后一条已提交记录的 CRC。每写完一条日志,先更新日志数据,最后更新索引区。索引区存两份,交替使用,每份带 CRC。上电后先读索引 A,如果 CRC 不对,再读索引 B;如果两份都不对,就从头扫描日志区,找到最后一个 magic 和 CRC 都正确的记录,重建索引。
MRAM 的随机写特性在这里帮了大忙。如果用 Flash,改一个索引字节前要先擦除整个扇区,掉电在擦除窗口期几乎必然发生。MRAM 不存在这个问题,直接写目标地址即可,提交点设计简单得多。
4.4 温区、ESD 与长期运行观察
工业设备经常要在宽温环境下运行,特别是户外机柜,白天高温、晚上低温。MR25H40CDF 的工业级型号支持 -40°C 到 105°C 左右,PIC18F97J94 也是工业级器件,整板注意选择工业级电容和电阻,这套组合应付绝大多数场景没有压力。
ESD 方面,如果 MRAM 和调试接口或者外部排线直接连着,建议在 SPI 信号线上加 TVS 管,或者在主控和存储之间串电阻。机箱良好接地往往是最有效的防护措施。我们这版板子做了 8kV 接触放电测试,没有死机或丢数据,稳定性还可以。
现场长期运行下来,我建议定期做一个存储巡检:把关键参数区读出来,重新算一遍 CRC,和上次保存的校验值比对;日志区随机抽样几条记录做完整校验。MRAM 本身几乎不会坏,但接口连接器、焊点、电源纹波都可能导致偶发错误,巡检能尽早发现而不是等到数据全乱才处理。
4.5 实测写性能与耐久性评估
在 4MHz SPI 时钟下,写 1KB 数据大约需要 2ms,这中间还没有擦除等待。如果换上 40MHz 时钟,512KB 全部写满也就几十毫秒级别的理论时间。对记录应用来说,瓶颈基本只在固件自身的数据组装和串口输出上,MRAM 不是瓶颈。
耐久性方面,MR25H40CDF 号称 10^14 次写,实际工程里没机会测到那个数字。我做过一个压力测试:用一个 GPIO 翻转触发写日志,连续写了几十万条记录,每一条都带 CRC,读回来全部正确。对于大多数设备的生命周期来说,这个耐久度已经可以当“无限”用了。相比之下,之前的 NOR Flash 在这种写入频率下早就该换片了。
5. 调试阶段踩过的几个坑
5.1 读出来全是 0xFF 的排查过程
第一次把板子焊好,烧了固件,满心欢喜地读 MRAM,结果读回来的数据全是 0xFF。当时第一反应是 SPI 没通,于是把 SCK、MOSI、MISO 都用示波器抓了一遍,发现波形都正常,MISO 上也有响应。再仔细一看,MISO 读回来的每个字节都是 0xFF,也就是说芯片根本没把有效数据放到数据总线上。
反复查了很久,最后发现是 HOLD# 脚悬空导致的。HOLD# 是暂停功能引脚,低电平有效,悬空时电平不确定,芯片偶尔会认为自己被暂停了。SCK 上的脉冲继续跑,但内部移位寄存器不工作,MISO 就一直保持高电平,读出来自然全是 0xFF。把 HOLD# 焊到 VDD 之后,问题立刻消失。
这个坑提醒我,调试 SPI 从设备时,除了 SCK、MOSI、MISO、CS 四根线,其余控制引脚必须明确接高或接低,不能靠内部上拉“赌”电平。
5.2 WREN 必须是一个独立的 CS 周期
第二个坑出现在写操作上。刚开始写代码时,我图省事,把 WREN 和 WRITE 放在同一个 CS 低电平周期里:
MRAM_CS_LOW(); spi_exchange(0x06); // WREN spi_exchange(0x02); // WRITE ... MRAM_CS_HIGH();逻辑上看起来没问题,但 MRAM 死活不写入。读了状态寄存器,发现 WEL 位始终是 0,说明写使能根本没生效。后来查手册才发现,WREN 命令本身被芯片解释成“把锁存器置 1”,而锁存器状态的更新发生在 CS 上升沿。如果 CS 不拉高,锁存器就一直是 0,后续 WRITE 命令会被当作非法操作忽略掉。
改正后就变成了独立的两个 CS 周期:先 WREN + CS 拉高,再 WRITE + CS 拉高。这也是很多串行存储器件共通的时序要求。以后写其他 SPI 芯片的驱动时,遇到类似的“写使能不生效”问题,第一个该查的就是 CS 时序。
5.3 越过 0x07FFFF 后地址回卷把数据写穿
第三个坑和硬件无关,纯粹是固件逻辑问题。日志区我规划在前 64KB,参数区放在最后 32KB,按理说互不干扰。结果某次现场测试时,设备调试参数突然变成了乱码,最开始以为 EEPROM 参数区掉了,反复查代码没发现异常。
后来用上位机把整片 MRAM 读出来,才发现日志区某次批量写入时,缓冲区跨越了 0x07FFFF,写到末尾后地址自动回卷到 0x000000,然后一路继续写,把低地址的参数区覆盖了。因为日志区和参数区都在同一颗芯片上,这种错误比硬件故障更难查。
从此之后,我所有批量读写函数都加了边界判断,凡是要跨回卷点的写操作,一律强制按照“剩余空间”分两次执行。这个经验也分享给用 MRAM、FRAM 这类“无页大小概念”芯片的朋友:没有页边界不代表没有地址回卷,容量的尽头就是边界。
5.4 关于 MRAM 的一些实话
MR25H40CDF 并不是在所有场景都比 Flash 好。容量超过 4MB 的数据存储需求,MRAM 的性价比就会很难看;需要保存海量历史曲线、音频、截图这类大数据,还是得用大容量 Flash 加 TF/SD 卡。MRAM 真正擅长的是“频繁写、小容量、高可靠、掉电不丢”的窄而深的领域。
用了一段时间后,我再也不愿意在工业日志场景里用普通 NOR Flash 做直接存储了,至少也会加一层磨损均衡与掉电恢复。而 MR25H40CDF 这种无擦除、高耐久、随机写的特性,让固件设计简单太多。个人体会是,如果项目里对数据可靠性有硬指标,与其花大量精力在 Flash 上做坏块管理、磨损均衡、掉电恢复,不如直接换一颗 MRAM,省下来的时间足够覆盖芯片差价。