ARTICLE DETAIL

资讯详情

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

MRAM 工业嵌入式存储实战:MR25H40CDF 与 PIC18LF47K42 驱动详解

MRAM 工业嵌入式存储实战:MR25H40CDF 与 PIC18LF47K42 驱动详解 MRAM 这东西第一次用的人多半会犯一个错把它当 EEPROM 使。我最早接触 MR25H40 的时候也是这个思路觉得反正都是非易失写就完了。结果在高温老化测试里连续写了三天数据一点没丢但功耗曲线和时序余量跟我预估的完全对不上。后来把数据手册翻了三遍才明白MRAM 的写入机制跟 EEPROM、Flash 根本不是一个路子它的写入时间跟读取时间是一个量级的没有擦除动作也没有写等待周期。这个特性用好了是优势用不好就是坑。这篇内容围绕MR25H40CDF这颗 4Mb 的 SPI MRAM 和PIC18LF47K42这颗 8 位单片机讲清楚在工业和嵌入式场景下怎么做数据存储和读取。适合正在选型非易失存储的硬件工程师、写底层驱动的嵌入式软件工程师以及需要做数据记录、参数保存、掉电保护这类功能的开发者。我会把 SPI 时序配置、命令集操作、页写入边界、跨页处理、数据完整性校验这些实际会踩到的点都拆开讲代码基于 MCC 生成的 Harmony 风格底层做示例但逻辑对任何平台都通用。1. 为什么在这个场景里 MRAM 比 EEPROM 和 FRAM 更值得考虑1.1 工业场景对非易失存储的真实需求是什么工业设备里的数据存储需求跟消费电子完全不是一个量级。消费电子可能只需要存个配网信息、用户偏好写个几百次就到头了。但工业场景不一样举几个我实际遇到过的例子电力监测终端每秒钟要记录一次三相电压电流的有效值一天就是 86400 条记录PLC 的工艺参数在调试阶段可能被反复修改上千次某类传感器节点要求掉电瞬间把最后 100 条采样数据完整保存下来下次上电接着用。这些场景对存储介质的要求可以归纳成几条写入寿命要够、写入速度要快、掉电不能丢数据、宽温范围要稳定、接口要简单。EEPROM 写入寿命通常在 100 万次左右单字节写入时间在毫秒级而且很多 EEPROM 在 105 摄氏度以上就开始出现数据保持力下降的问题。FRAM 写入寿命和速度都好但容量普遍偏小4Mb 这个量级的 FRAM 价格不友好。Flash 容量大价格低但擦除块大、写入前必须擦除、擦写寿命只有 10 万次左右做高频数据记录很快就到寿命上限。MRAM 的定位刚好卡在中间。MR25H40CDF 提供 4Mb512K x 8的容量SPI 接口40MHz 时钟写入没有擦除动作字节写入和页写入都是直接覆盖写入寿命标称 10 的 14 次方以上数据保持力 20 年工作温度覆盖工业级和汽车级。这几个参数放在一起对于需要高频写入、掉电保护、宽温工作的工业场景来说是一个非常务实的选择。1.2 MR25H40CDF 的存储机制跟 EEPROM 的本质区别要理解 MRAM 为什么写入这么快得从它的存储单元说起。EEPROM 和 Flash 都是基于浮栅晶体管写入靠的是把电子注入浮栅或者从浮栅里抽走这个过程需要较高的电压和较长的时间而且反复注入抽出会损伤氧化层这就是写入寿命有限的根本原因。MRAM 用的是磁性隧道结MTJ存储单元由两层磁性材料和中间一层极薄的绝缘层构成其中一层磁性方向固定另一层可以通过电流产生的磁场或者自旋力矩来翻转。数据就存在这两层磁性方向的相对关系里平行表示 0反平行表示 1。这个机制带来的直接好处是写入不需要高电压不需要擦除写入时间跟读取时间在同一量级通常在几十纳秒。而且因为是磁性翻转而不是电荷注入理论上写入次数可以做到无限实际标称的 10 的 14 次方更多是测试设备的限制而不是物理极限。另外MRAM 对辐射不敏感这个特性在航天和核工业场景里很有价值虽然本篇不展开但选型时值得知道。对于写驱动的开发者来说最直观的感受是你不需要再关心擦除块边界不需要维护磨损均衡算法不需要在写入前判断目标区域是否为空。写一个字节就是写一个字节写一页就是写一页地址连续递增就行。这大大简化了上层数据管理的逻辑。1.3 PIC18LF47K42 在这个方案里扮演什么角色PIC18LF47K42 是 Microchip 的 PIC18 系列里比较新的一颗带 128KB Flash、8KB RAM、1KB EEPROM外设资源在这个价位段算丰富。它在这个方案里的角色是数据采集和存储控制的主控。选择它有几个实际考虑一是它支持 3.3V 供电跟 MR25H40CDF 的供电范围匹配不需要额外的电平转换二是它有两个 MSSP 模块可以一个接 MRAM一个接其他 SPI 从设备不会互相抢总线三是它的工作温度范围有工业级版本跟 MRAM 的工业级温度范围能对上。另外PIC18LF47K42 的 SPI 模块支持时钟极性、时钟相位、位序、时钟频率的灵活配置这对驱动 MR25H40CDF 很关键因为 MRAM 对 SPI 模式有明确要求配错了就是读出来全是 0xFF 或者 0x00。后面会详细讲配置细节。2. SPI 物理层和模式配置最容易翻车的地方2.1 MR25H40CDF 支持的 SPI 模式与 PIC 端配置对应关系MR25H40CDF 支持 SPI 模式 0 和模式 3。模式 0 是 CPOL0、CPHA0模式 3 是 CPOL1、CPHA1。这两种模式的共同点是数据在时钟的上升沿采样区别在于空闲时时钟电平不同。数据手册里明确写了这两种模式都支持但实际用的时候建议固定用一种不要动态切换。在 PIC18LF47K42 这边MSSP 模块的 SSPCON1 寄存器里有 CKP 位控制时钟极性SSPSTAT 寄存器里有 CKE 位控制时钟相位。对应关系是SPI 模式CPOLCPHAPIC CKPPIC CKE采样边沿模式 00001上升沿模式 31110上升沿这里有个容易搞混的点PIC 的 CKE 位定义跟通用的 CPHA 定义是反的。CKE1 对应 CPHA0CKE0 对应 CPHA1。我第一次配的时候就是按 CPHA 直接填的结果读出来全是 0xFF查了半天才发现是 CKE 位搞反了。配置代码大概长这样// SPI 模式 0 配置 SSP1CON1bits.CKP 0; // 空闲时时钟低电平 SSP1STATbits.CKE 1; // 上升沿采样对应 CPHA0 SSP1CON1bits.SSPM 0b0000; // SPI Master, 时钟 Fosc/4 SSP1CON1bits.SSPEN 1; // 使能 MSSP时钟频率方面MR25H40CDF 最高支持 40MHz但 PIC18LF47K42 在 3.3V 供电下SPI 时钟最高只能到 Fosc/4。如果系统时钟是 64MHzSPI 时钟就是 16MHz这个速度对 MRAM 来说完全够用因为 MRAM 本身没有写入等待16MHz 下连续写入的吞吐率能到 2MB/s 左右。2.2 片选信号的处理硬件片选还是软件片选MR25H40CDF 的片选是低有效每次操作前拉低操作完拉高。这里有两种做法用 MSSP 模块的硬件 SS 引脚或者用普通 GPIO 做软件片选。硬件片选的问题是PIC 的 MSSP 在主机模式下对 SS 引脚的控制有时候不太直观特别是在多从机场景下SS 引脚的行为可能跟预期不一致。而且硬件片选在发送完最后一个字节后片选拉高的时机是由硬件控制的如果你在字节之间需要插入延时或者做其他操作硬件片选就不够灵活。我的建议是用普通 GPIO 做软件片选。选一个空闲的 IO 口配置成推挽输出初始状态拉高。每次操作前拉低操作完拉高。这样你对片选的时序有完全的控制权调试的时候也方便用逻辑分析仪抓波形。#define MRAM_CS_LAT LATBbits.LATB0 #define MRAM_CS_TRIS TRISBbits.TRISB0 void MRAM_CS_Init(void) { MRAM_CS_TRIS 0; // 输出 MRAM_CS_LAT 1; // 初始拉高 } void MRAM_CS_Select(void) { MRAM_CS_LAT 0; } void MRAM_CS_Deselect(void) { MRAM_CS_LAT 1; }有一个细节要注意MRAM 的片选拉低到第一个时钟沿之间需要满足最小建立时间查数据手册是 5ns 左右这个在 16MHz 时钟下周期 62.5ns完全不是问题不需要额外加延时。但片选拉高之前要确保最后一个时钟沿已经完成这个由 SPI 硬件保证只要你在发送完最后一个字节后再拉高片选就行。2.3 PCB 布局对 SPI 信号完整性的影响SPI 在低速下几 MHz随便走线都能跑但到了 16MHz 以上PCB 布局就开始有影响了。我遇到过的一个实际案例MRAM 放在板子边缘SPI 走线大概 8cm没有做阻抗控制也没有包地结果在 20MHz 时钟下读写偶尔出错降到 10MHz 就稳定。后来在 SCK 和 MOSI 上串了 22 欧姆的电阻问题解决。几个实用的布局建议SPI 走线尽量短最好控制在 5cm 以内SCK 和 MOSI 如果比较长可以在源端串 22 到 33 欧姆的电阻做阻抗匹配MISO 是被动驱动的如果走线长可以在 MRAM 端串一个小电阻片选信号如果跟其他信号并行走线注意串扰必要时在中间加地线隔离。另外MR25H40CDF 的电源引脚旁边一定要放 0.1uF 的陶瓷电容越近越好。MRAM 在写入瞬间的电流会有小幅波动去耦不好可能导致写入失败。我在一个项目里因为去耦电容放远了 5mm高温下就出现了偶发的写入错误后来把电容挪到引脚旁边就再没出现过。3. 命令集操作从读 ID 到页写入的完整流程3.1 读制造商 ID 和设备 ID上电后的第一件事每次上电初始化完 SPI 之后第一件事应该是读 MRAM 的制造商 ID 和设备 ID确认通信正常。MR25H40CDF 的命令集里读 ID 的命令是 0x9FRDID发送这个命令后MRAM 会返回 1 字节的制造商 ID0xE0Everspin 的代码和 2 字节的设备 ID。uint8_t MRAM_ReadID(uint8_t *manuf_id, uint16_t *dev_id) { uint8_t rx[3]; MRAM_CS_Select(); SPI_Transfer(0x9F); // RDID 命令 rx[0] SPI_Transfer(0x00); // 制造商 ID rx[1] SPI_Transfer(0x00); // 设备 ID 高字节 rx[2] SPI_Transfer(0x00); // 设备 ID 低字节 MRAM_CS_Deselect(); *manuf_id rx[0]; *dev_id ((uint16_t)rx[1] 8) | rx[2]; return (rx[0] 0xE0) ? 0 : 1; // 0 表示成功 }MR25H40CDF 的设备 ID 是 0x0382制造商 ID 是 0xE0。如果读出来不是这两个值先检查 SPI 模式配置再检查片选和接线。我遇到过读出来是 0xFF 的情况最后发现是 MISO 线虚焊也遇到过读出来是 0x00 的情况是 SPI 时钟极性配反了。3.2 读命令和写命令的时序差异MR25H40CDF 的读命令是 0x03READ写命令是 0x02WRITE。这两个命令的时序结构类似先发命令字节再发 3 字节地址24 位地址因为 4Mb 容量需要 19 位地址但 MRAM 用 3 字节地址格式然后跟数据。读操作的时序是片选拉低 - 发 0x03 - 发地址高字节 - 发地址中字节 - 发地址低字节 - 连续读数据 - 片选拉高。数据在时钟下降沿输出主机在上升沿采样。写操作的时序是片选拉低 - 发 0x02 - 发地址高字节 - 发地址中字节 - 发地址低字节 - 连续写数据 - 片选拉高。数据在时钟上升沿被 MRAM 采样。这里有一个关键点MRAM 的写操作没有等待周期。你发完最后一个数据字节拉高片选写入就完成了。不需要发 WREN 命令不需要等待内部写完成不需要查询状态寄存器。这一点跟 EEPROM 和 Flash 完全不同也是 MRAM 用起来最舒服的地方。void MRAM_Write(uint32_t addr, uint8_t *data, uint16_t len) { MRAM_CS_Select(); SPI_Transfer(0x02); // WRITE 命令 SPI_Transfer((addr 16) 0xFF); // 地址高字节 SPI_Transfer((addr 8) 0xFF); // 地址中字节 SPI_Transfer(addr 0xFF); // 地址低字节 for (uint16_t i 0; i len; i) { SPI_Transfer(data[i]); } MRAM_CS_Deselect(); } void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_CS_Select(); SPI_Transfer(0x03); // READ 命令 SPI_Transfer((addr 16) 0xFF); SPI_Transfer((addr 8) 0xFF); SPI_Transfer(addr 0xFF); for (uint16_t i 0; i len; i) { buf[i] SPI_Transfer(0x00); } MRAM_CS_Deselect(); }3.3 页写入的边界问题与跨页处理MR25H40CDF 支持页写入页大小是 256 字节。但这里有一个容易踩的坑页写入不会自动跨页。如果你从地址 0x00F0 开始写 32 字节写到地址 0x010F实际上只有前 16 字节写进了 0x00F0 到 0x00FF后面 16 字节会回卷到 0x0000 到 0x000F而不是继续写到 0x0100 到 0x010F。这个行为跟 EEPROM 的页写入是一样的但很多人第一次用 MRAM 会以为它像内存一样可以连续写。我就在这个坑里待过做数据记录的时候记录长度是 64 字节记录缓冲区地址没有做页对齐结果跨页的那条记录把前面的数据覆盖了查了两天才定位到。正确的做法是在写入之前先计算本次写入是否会跨页。如果会跨页就拆成两次写入第一次写到页边界第二次从下一页起始地址开始写。void MRAM_WriteSafe(uint32_t addr, uint8_t *data, uint16_t len) { while (len 0) { uint16_t page_remain 256 - (addr 0xFF); // 当前页剩余空间 uint16_t write_len (len page_remain) ? len : page_remain; MRAM_Write(addr, data, write_len); addr write_len; data write_len; len - write_len; } }这个函数看起来简单但实际项目里一定要用。我现在的习惯是所有对 MRAM 的写入都走这个安全写入函数不管写入长度是多少统一处理避免以后改需求的时候忘记跨页问题。4. 数据完整性校验、掉电保护和磨损管理4.1 为什么 MRAM 也需要数据校验有人可能会想MRAM 写入这么可靠还需要校验吗答案是需要的。MRAM 的存储单元本身很可靠但数据在传输过程中可能出错。SPI 总线在工业环境里可能受到电机、继电器、变频器的干扰导致某一位翻转。另外MRAM 虽然写入寿命长但也不是无限在极端条件下比如超温、超压也可能出现写入失败。我在一个电机控制项目里就遇到过MRAM 里存的参数偶尔会变成乱码后来查出来是变频器工作时产生的电磁干扰通过 SPI 线耦合进来了。加了校验之后至少能发现数据出错然后从备份区恢复。常用的校验方式有两种CRC 和校验和。CRC 的检错能力更强但计算量稍大校验和简单但对某些错误模式不敏感。对于参数存储这种数据量不大的场景我一般用 CRC-16计算速度快检错能力够用。uint16_t CRC16_Calculate(uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; for (uint16_t i 0; i len; i) { crc ^ (uint16_t)data[i] 8; for (uint8_t j 0; j 8; j) { if (crc 0x8000) { crc (crc 1) ^ 0x1021; } else { crc 1; } } } return crc; }存储的时候把数据和 CRC 一起写入。读取的时候先读数据再读 CRC重新计算 CRC 跟存储的对比。如果不一致说明数据出错了可以从备份区恢复或者使用默认值。4.2 掉电保护的数据结构设计掉电保护是工业场景里的刚需。设备正在写数据的时候突然断电如果数据结构设计得不好下次上电可能读到半截数据导致解析错误。MRAM 的写入速度很快掉电窗口很小但也不是零所以数据结构还是要设计好。我常用的方案是双区加标志位的结构。把存储区分成 A、B 两个区每个区都有一个头部头部包含一个魔数、一个序列号和一个 CRC。写入的时候先写非活动区写完更新序列号然后再切换活动区。读取的时候比较两个区的序列号取序列号大的那个如果序列号相同就比较 CRC取 CRC 正确的那个。typedef struct { uint32_t magic; // 魔数标识数据有效 uint32_t seq; // 序列号每次写入递增 uint16_t crc; // 数据 CRC uint16_t len; // 数据长度 uint8_t data[250]; // 实际数据 } MRAM_Record_t;这个结构占 256 字节刚好一个页。写入的时候先写数据区再写头部最后写魔数。读取的时候先检查魔数再检查 CRC都通过才认为数据有效。如果魔数不对或者 CRC 不对就尝试读另一个区。这个方案的好处是即使写入过程中掉电最多丢失最后一次写入的数据之前的数据不会受影响。因为每次都是写到非活动区活动区的数据始终是完整的。4.3 磨损管理在 MRAM 上还需要吗严格来说MRAM 的写入寿命足够长对于绝大多数应用来说不需要做磨损均衡。10 的 14 次方次写入就算每秒写一次也要写 300 多万年。所以从寿命角度磨损管理不是必须的。但是从数据管理的角度做一个简单的循环写入还是有价值的。比如你要记录 1000 条日志如果每次都写同一个地址那只能保留最后一条。如果做成循环缓冲区就可以保留最近 1000 条。这个不是磨损管理是数据管理。#define LOG_MAX_RECORDS 1000 #define LOG_RECORD_SIZE 64 #define LOG_BASE_ADDR 0x1000 uint32_t log_write_index 0; void LOG_Write(uint8_t *record) { uint32_t addr LOG_BASE_ADDR (log_write_index % LOG_MAX_RECORDS) * LOG_RECORD_SIZE; MRAM_WriteSafe(addr, record, LOG_RECORD_SIZE); log_write_index; // 把写索引也存到 MRAM 里掉电后能恢复 MRAM_WriteSafe(LOG_INDEX_ADDR, (uint8_t *)log_write_index, sizeof(log_write_index)); }这里有一个细节写索引本身也要存到 MRAM 里而且要在数据写入之后存。这样即使掉电最多丢失最后一条记录索引不会错乱。如果先存索引再写数据掉电后索引指向的位置可能是空数据读取的时候需要判断。5. 实际项目中的性能调优与常见问题排查5.1 如何把连续写入吞吐率提到最高MR25H40CDF 在 40MHz 时钟下的理论吞吐率是 5MB/s但实际能达到多少取决于你的 SPI 驱动效率。我实测过几种不同的写法结果差异挺大。第一种写法是每次写入都重新拉片选、发命令、发地址。这种写法在写入小数据量的时候开销很大因为命令和地址占了 4 个字节如果只写 4 个字节数据有效吞吐率只有 50%。第二种写法是利用 MRAM 的连续写入特性。MRAM 在片选保持低电平的情况下地址会自动递增所以你可以一次发命令和起始地址然后连续写很多数据不需要每次都重新发命令。这种写法在写入大数据块的时候效率很高。// 高效连续写入一次命令连续写多页 void MRAM_WriteBurst(uint32_t addr, uint8_t *data, uint32_t len) { MRAM_CS_Select(); SPI_Transfer(0x02); SPI_Transfer((addr 16) 0xFF); SPI_Transfer((addr 8) 0xFF); SPI_Transfer(addr 0xFF); for (uint32_t i 0; i len; i) { SPI_Transfer(data[i]); } MRAM_CS_Deselect(); }但要注意连续写入虽然地址会自动递增但页边界回卷的特性还在。如果你一次写的长度超过当前页剩余空间还是会回卷。所以连续写入只适合两种情况一是写入长度不超过当前页剩余空间二是你确认数据可以接受回卷一般不会。我的做法是对于大数据块按页拆分每页一次片选操作页内连续写。这样既避免了回卷又减少了命令开销。5.2 SPI 时钟频率与信号质量的权衡SPI 时钟频率越高吞吐率越高但信号质量越难保证。在 16MHz 以下一般的 PCB 布局都能稳定工作。到了 20MHz 以上就需要关注走线长度、阻抗匹配、串扰这些问题。我的一般原则是如果 PCB 走线在 3cm 以内可以跑到 20MHz3 到 8cm建议降到 10MHz 到 16MHz超过 8cm建议降到 10MHz 以下或者加缓冲器。当然这只是一个经验值具体还要看板子的实际情况。调试的时候用逻辑分析仪抓 SCK、MOSI、MISO、CS 四根线的波形看有没有过冲、振铃、边沿变缓这些问题。如果 SCK 的上升沿有明显的振铃可以在源端串一个电阻。如果 MISO 的建立时间不够可以降低时钟频率或者调整采样边沿。还有一个容易被忽略的点PIC18LF47K42 的 SPI 时钟是系统时钟分频得到的分频系数是 4、16、64 这几档。如果你想要一个特定的时钟频率需要先确定系统时钟再选分频系数。比如系统时钟 64MHz分频 4 就是 16MHz分频 16 就是 4MHz。没有中间档位所以选系统时钟的时候要考虑到 SPI 的需求。5.3 常见问题排查表现象可能原因排查方法读出来全是 0xFFMISO 未连接或虚焊SPI 模式错误检查 MISO 接线用示波器看 MISO 是否有波形确认 SPI 模式读出来全是 0x00MOSI 未连接片选未拉低SPI 时钟未输出检查 MOSI 和 CS 接线用示波器看 SCK 是否有波形读 ID 正确但读写数据出错地址计算错误跨页问题检查地址计算确认写入长度是否跨页偶尔读写失败信号完整性问题电源去耦不足降低 SPI 时钟检查去耦电容用逻辑分析仪抓波形高温下写入失败电源电压不足时序余量不够检查电源电压降低 SPI 时钟确认温度范围掉电后数据丢失写入未完成就掉电数据结构问题检查写入流程确认掉电保护结构是否正确这个表里的问题我在不同项目里都遇到过。最常出现的是前两个基本都是接线或者配置问题。第三个跨页问题也很常见特别是从 EEPROM 转过来的开发者容易忽略页边界。5.4 一个完整的参数存储示例最后给一个完整的参数存储示例把前面讲的东西串起来。这个示例实现了一个参数管理模块支持参数的读取、写入、校验和掉电恢复。#define PARAM_MAGIC 0x50415241 // PARA #define PARAM_A_ADDR 0x0000 #define PARAM_B_ADDR 0x0100 #define PARAM_DATA_SIZE 200 typedef struct { uint32_t magic; uint32_t seq; uint16_t crc; uint16_t len; uint8_t data[PARAM_DATA_SIZE]; } ParamBlock_t; static ParamBlock_t param_ram; uint8_t Param_Load(void) { ParamBlock_t block_a, block_b; MRAM_Read(PARAM_A_ADDR, (uint8_t *)block_a, sizeof(block_a)); MRAM_Read(PARAM_B_ADDR, (uint8_t *)block_b, sizeof(block_b)); uint8_t valid_a (block_a.magic PARAM_MAGIC) (CRC16_Calculate(block_a.data, block_a.len) block_a.crc); uint8_t valid_b (block_b.magic PARAM_MAGIC) (CRC16_Calculate(block_b.data, block_b.len) block_b.crc); if (valid_a valid_b) { if (block_a.seq block_b.seq) { param_ram block_a; } else { param_ram block_b; } } else if (valid_a) { param_ram block_a; } else if (valid_b) { param_ram block_b; } else { return 1; // 两个区都无效 } return 0; } uint8_t Param_Save(void) { ParamBlock_t block; uint32_t target_addr; // 判断当前活动区写到非活动区 if (param_ram.seq % 2 0) { target_addr PARAM_B_ADDR; } else { target_addr PARAM_A_ADDR; } block.magic PARAM_MAGIC; block.seq param_ram.seq 1; block.len PARAM_DATA_SIZE; memcpy(block.data, param_ram.data, PARAM_DATA_SIZE); block.crc CRC16_Calculate(block.data, block.len); // 先写数据区再写头部最后写魔数 MRAM_WriteSafe(target_addr 12, block.data, PARAM_DATA_SIZE); MRAM_WriteSafe(target_addr 8, (uint8_t *)block.crc, 4); MRAM_WriteSafe(target_addr 4, (uint8_t *)block.seq, 4); MRAM_WriteSafe(target_addr 0, (uint8_t *)block.magic, 4); param_ram.seq block.seq; return 0; }这个示例里写入顺序是刻意设计的先写数据再写 CRC再写序列号最后写魔数。魔数是最后写的所以如果写入过程中掉电魔数没有写成功这个区就被认为是无效的读取的时候会使用另一个区的数据。这样就保证了掉电时数据的一致性。我在实际项目里用这个结构跑了两年多经历过多次意外断电参数从来没有丢失过。唯一需要注意的是每次保存参数都会写两个区中的一个所以参数保存的频率不能太高否则会频繁擦写。不过 MRAM 的寿命足够长就算每分钟保存一次也能用很多年。6. 从选型到落地的几个经验判断MR25H40CDF 这颗芯片我用过几个项目有工业数据记录仪、电机控制器、还有一个小型的电力监测终端。整体感受是它在写入速度和写入寿命上的优势很明显特别适合需要频繁写入或者对掉电保护要求高的场景。但也不是所有场景都适合如果你的数据写入频率很低比如一天写一次那用 EEPROM 就够了成本更低。如果你的数据量很大比如要存几 MB 的日志那 MRAM 的容量和价格就不太合适还是得用 Flash 加掉电保护电容的方案。PIC18LF47K42 跟 MR25H40CDF 的搭配在 8 位机方案里算是比较均衡的。PIC18 的 SPI 模块配置灵活MCC 工具也能生成初始化代码省了不少事。但要注意 PIC18 的 SPI 时钟分频只有几档选系统时钟的时候要提前算好 SPI 频率。另外PIC18LF47K42 的 RAM 只有 8KB做大数据缓冲的时候要小心别把栈给冲了。最后说一个实际调试中的小技巧如果你怀疑 MRAM 的读写有问题但又没有逻辑分析仪可以用一个简单的方法验证。先写一个已知的模式比如 0x55 和 0xAA 交替然后读回来对比。如果读回来跟写进去的不一样再写一个全 0x00 和全 0xFF 的模式看读回来是什么。通过对比不同模式的结果可以大致判断是数据线问题、时钟问题还是片选问题。这个方法虽然土但在没有专业仪器的时候很管用。
返回列表