ARTICLE DETAIL

资讯详情

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

基于SPI接口的MRAM数据存储:PIC18F57Q43读写MR25H40CDF全解析

基于SPI接口的MRAM数据存储:PIC18F57Q43读写MR25H40CDF全解析 在工业现场待久了你就会发现“存储”这个环节很容易被低估。很多仪表、电力终端、运动控制器要保存的不仅仅是配置参数还有事件记录、曲线数据、掉电现场这类数据会反复写入而且掉电之后还必须能原样拿出来分析。这次我在一个计量项目里把采集结果往 MR25H40CDF 里写主控用的是 PIC18F57Q43整个过程跑下来我对“SPI 接口 MRAM 8 位 MCU”的组合有了很直接的体会。这篇文章就围绕 MR25H40CDF 和 PIC18F57Q43 存储、读取数据的完整流程展开内容包括器件选型逻辑、硬件连接、SPI 初始化、读写实现、工业环境下的稳定性处理和故障排查适合做嵌入式开发、工业控制、数据记录相关项目的人参考。我按实际项目推进的顺序来写尽量把“为什么这么做”讲清楚。因为很多时候大家不是不知道操作码而是不知道在不同场景下该怎么选方案、怎么避坑。1. 方案视角为什么这次用 MRAM 而不是 EEPROM 或 SPI Flash1.1 三类存储器的特性差异工业应用里最常接触的存储芯片无非三类并口或 SPI 的 EEPROM、SPI NOR Flash、以及 MRAM。EEPROM 和 Flash 大家都很熟MRAM 相对小众但它在某些场景下的表现确实非常“顶”。我在选型表里把三个方向的核心参数拉了一下对比项普通 SPI EEPROMSPI NOR FlashMR25H40CDFMRAM容量级别常见 1Mbit 以下1Mbit~256Mbit4Mbit写入前是否需要擦除字节写无需擦除页写内部处理必须先擦除块再编程不需要擦除直接写擦写寿命常见 10万~100万次10万次左右已经很不错无限次写规格书里写的是无磨损写入时间字节写约 3~5ms页编程一般 1ms 级别写入周期极短实测可在 SPI 连续时钟内完成数据保持通常 20 年以上通常 20 年以上数据保持 20 年以上掉电数据保持保持保持保持这里面最让我在意的是“写入前要不要擦除”和“寿命能扛多少写入”这两件事。之前项目里用过 SPI NOR Flash 做数据记录最头疼的就是要先维护块擦除状态。记录数据时得先攒够一小块然后擦除再写中间一旦掉电缓冲区索引和 Flash 状态可能不一致恢复逻辑写起来很麻烦。做 EEPROM 版本时寿命和容量又都偏紧特别是要按秒记录事件、存几小时的历史波形时几 KB 到几百 KB 的 EEPROM 根本不够用。MR25H40CDF 这个片子的容量是 4Mbit也就是 512KB。对 8 位 MCU 来说这个空间不算小存几百条带时间戳的事件记录、几十帧波形数据完全够用。由于 MRAM 写操作不需要先擦除我可以在任意地址直接改写任意长度数据省掉了 Flash 的块管理逻辑整个固件的复杂度降了一档。1.2 工业应用真正在意的两个指标工业现场对存储器件的要求跟在开发板上跑通完全不是一回事。第一是“连续写入不掉链子”。比如伺服驱动器每 10ms 记录一次电流包络一天下来就是 860 万次写入。如果用的是寿命 10 万次的 Flash几个月就报废。MRAM 因为结构上没有“浮置栅极反复隧穿”的损耗机制理论上写多少遍都不会磨坏这对高频日志类应用几乎就是量身定做。第二是“掉电瞬间数据不能丢”。设备遇到突发的电源跌落时MCU 可能只来得及做一件事。如果存储时序足够快可以在电压降到阈值的几毫秒窗口里把关键状态写进去。MRAM 的写时序接近 SRAM 级别不需要等待几百微秒甚至几毫秒的编程时间这个窗口能挤出来。实际项目中我就在掉电中断里直接写一个 32 字节的现场信息测试下来成功率很高。另外还有一个容易忽略的点MRAM 和 SRAM 一样支持按字节随机写入。Flash 和 EEPROM 虽然也支持字节写但一个受限于擦除块一个受限于速度。MRAM 的逻辑更像是“内存地址 非易失保持”这对上层软件来说非常友好不用设计复杂的状态机。2. 硬件连接与电路设计细节2.1 引脚功能与最小电路MR25H40CDF 是标准的 8 脚封装接口是 SPI。具体到引脚功能我按实际连接顺序说一下CS#片选低有效必须由 MCU GPIO 控制不能直接接地。因为 SPI 总线上可能还有其他器件而且每次操作前后要有明确的片选时序。SCK时钟接 MCU 的 SPI SCK 引脚。SI数据输入接 MCU 的 SDO 引脚因为对 MRAM 来说是输入对 MCU 来说是输出。SO数据输出接 MCU 的 SDI 引脚。WP#写保护低有效如果为低则禁止写状态寄存器。正常使用时建议通过 10kΩ 电阻上拉到 VCC。HOLD#暂停低有效。拉低后会暂停 SPI 通信保持输出状态。这个引脚非常关键正常工作时必须上拉否则线上干扰把它拉低就会出现“读到一半数据卡住”的诡异现象。VCC / VSS电源和地VCC 为 3.3V。最小电路设计时我在 VCC 和 GND 之间放了一个 0.1μF 陶瓷电容靠近芯片引脚放置同时并联一个 1μF 的钽电容做低频滤波。工业板子上电源噪声往往不干净尤其是旁边有继电器、电机驱动的时候。MR25H40CDF 工作电压范围标称支持 2.4V 到 3.6V如果电源波动超过这个范围状态寄存器就会被改写导致后续写入失败所以电源退耦不能省。WP# 和 HOLD# 这两个引脚我是用 10kΩ 电阻统一上拉到 3.3V。有朋友问我能不能直接用 GPIO 驱动可以但当 GPIO 配置成默认输入且内部上拉不够强时反而有风险。用固定上拉更省心因为正常项目里没必要动态切换 HOLD#。2.2 PCB 布局与干扰考虑PCB 布局上我习惯让 SPI 四根线尽量短CS# 和 SCK 不要平行走太远避免串扰。特别是在加长排线连接外部存储板时建议 SCK、SI、SO 三根线之间加地线隔离或者直接走 FFC 排线加屏蔽层。还有一点是 CS# 的上拉。如果 MCU 复位期间又外部信号把 CS# 拉低了MRAM 可能进入未知状态。我一般在 CS# 网络上也放一个 10kΩ 上拉这样 MCU 还没初始化时存储芯片保持未选中状态。多个 SPI 器件共用总线时这个上拉更必要能避免片选竞争。另外要注意电平匹配。PIC18F57Q43 是 1.8V 到 5.5V 供电的芯片如果它工作在 3.3V而 MRAM 也是 3.3V那就没有任何电平问题。但如果 MCU 工作在 5VMRAM 在 3.3VSI 和 SCK 引脚就需要串联电阻或做电平转换不然会让 MRAM 输入超过数据手册最大额定值。反向的 SO 信号是 MRAM 输出 3.3V5V MCU 读 3.3V 一般没问题因为 HIGH 阈值通常低于 3.3V但为了稳妥我仍然会在 SO 串联一个 100Ω 电阻减少振铃。3. PIC18F57Q43 的 SPI 配置与初始化3.1 PPS 引脚映射PIC18F57Q43 属于 PIC18-Q 系列外设引脚是可重映射的也就是我们常说的 PPS。SPI 功能要分配到哪些引脚不是固定死的需要通过寄存器把外设输出映射到具体端口。我用的连接方式是信号MCU 引脚方向PPS 映射目标SCKMCU 输出SCK1OUTSDOMCU 输出SDO1OUTSDIMCU 输入SDI1INCS#MCU GPIO 输出普通 GPIOPPS 的好处是布线时可以绕开特殊功能引脚坏处是初始化时如果漏配了 PPSSPI 大概率不工作。我排查过几次“SPI 不输出时钟”的问题最后发现都是 PPS 漏配。所以建议把 PPS 配置代码放在系统初始化最前面并加上注释避免后面维护的人误删。3.2 MSSP 外设配置要点PIC18F57Q43 的 SPI 外设基于 MSSP 模块。开发阶段我是用 MPLAB Code Configurator 先生成基础工程的它会自动配置引脚、时钟和外设再把生成代码嵌到主程序里。如果完全手动写寄存器核心配置就是这几项SPI 主机模式时钟极性 SPO 0相位 SPHA 0也就是模式 0。MR25H40CDF 手册默认推荐模式 0当然它也支持模式 3。我统一用模式 0减少混淆。主时钟分频设为 FOSC/4 或 FOSC/2。PIC18F57Q43 我当时跑 16MHz 系统时钟FOSC/2 就是 8MHz。MR25H40CDF 支持最高 40MHz 时钟所以 8MHz 完全在范围内线长十几厘米都能稳定跑。配置 MSSP1 的收发方向。标准 SPI 是同时移位发送一个字节的同时会接收到一个字节。所以读操作要先发一个任意字节通常发 0x00来产生 SCK 时钟同时从 SO 取数据。这一点跟很多 MCU 的 SPI 外设行为一致不明白的话会卡很久。MCC 生成代码后我习惯再手动检查一下SPI1CON0、SPI1CON1、SPI1CON2这几个寄存器的实际值。做嵌入式开发最怕的就是“图形界面点了、工具也编译了、到底配置成什么心里没底”。养成读生成代码的习惯后面定位问题会快很多。3.3 初始化代码参考下面这段代码是精简版的初始化逻辑重点展示思路直接照搬时请根据你的系统时钟调整分频#define MRAM_CS LATBbits.LATB4 #define MRAM_CS_TRIS TRISBbits.TRISB4 #define MRAM_CS_LOW() MRAM_CS_TRIS 0; MRAM_CS 0; #define MRAM_CS_HIGH() MRAM_CS_TRIS 0; MRAM_CS 1; void SPI1_Init(void) { // 这里假设 PPS 和时钟已经在引脚层配置好 SPI1CON0 0x10; // SPI 使能主机模式模式0相关位 SPI1CON1 0x20; // 时钟分频具体值以 MCC 生成为准 SPI1CON2 0x00; // 片选相关扩展控制不启用 MRAM_CS_HIGH(); } uint8_t SPI1_Transfer(uint8_t dat) { SPI1TXB dat; while (!SPI1RXIF); return SPI1RXB; }这段代码追求的是把最核心的时序跑通真正的工业级代码还需要加参数校验、超时处理和总线状态检查。比如 SPI 发送阻塞在标志位上一旦 SPI 外设异常程序就会卡死。实际工程里我一般会给发送循环加一个软件超时超时后复位 SPI 外设并上报错误。4. 从写使能到读写数据的核心流程4.1 操作码和地址格式MR25H40CDF 的指令集非常简洁我这里列出常用的几条指令名操作码功能WREN0x06写使能将状态寄存器 WEL 置 1WRDI0x04写禁止将 WEL 清 0RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03从指定地址开始读数据WRITE0x02从指定地址开始写数据地址是 24 位长度的 SPI 地址。MR25H40CDF 容量 4Mbit即 524288 字节地址范围 0x00000 ~ 0x7FFFF所以 24 位地址里高 7 位必须是 0。有个细节很多 SPI Flash 或 EEPROM 的地址是 16 位或 24 位操作码也差不多。写代码时最容易犯的错误就是把地址左移右移搞混。我固定按“地址先发高字节再发中字节最后发低字节”的顺序写且把地址变量明确声明成uint32_t避免编译器把移位结果截断。4.2 MRAM 写入不需要擦除的实际影响传统 Flash 写数据前要先擦除所以工程师养成了一种惯性写地址前先想象这个地址是不是 0xFF。MRAM 完全不需要。直接在任意地址写入任意数据也不存在“写 1 必须先擦除”这种限制。你写 0x00它就存 0x00下次写 0xFF它就直接变 0xFF。这一点对代码架构影响很大。我在项目里可以像操作 SRAM 一样维护一个环形缓冲区索引落在哪个扇区就写哪个扇区完全不关心擦除对齐。数据记录模块不再需要“存储层”只需要“指针 校验”。如果哪天从 MRAM 换回 SPI NOR Flash这套代码不能直接复用迁移时要有心理准备。另外MRAM 的写入时间非常短。传统 EEPROM 需要等内部写周期完成有些驱动里会轮询WIP位。MR25 系列则基本是传输完最后一个字节后数据立即变成新值。如果想更稳妥可以发一个 RDSR 确认状态寄存器值但在我的高速日志应用里实测不额外等待也没有问题。工业项目如果追求稳定建议在连续写多个字节之后主动检查状态寄存器并做一次回读比对。4.3 读写例程实现下面给出我用 XC8 编写的精简读写函数可以直接放入工程验证。void MRAM_WriteEnable(void) { MRAM_CS_LOW(); SPI1_Transfer(0x06); // WREN MRAM_CS_HIGH(); } void MRAM_WriteByte(uint32_t addr, uint8_t dat) { MRAM_WriteEnable(); MRAM_CS_LOW(); SPI1_Transfer(0x02); // WRITE SPI1_Transfer((addr 16) 0xFF); SPI1_Transfer((addr 8) 0xFF); SPI1_Transfer(addr 0xFF); SPI1_Transfer(dat); MRAM_CS_HIGH(); } uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t dat; MRAM_CS_LOW(); SPI1_Transfer(0x03); // READ SPI1_Transfer((addr 16) 0xFF); SPI1_Transfer((addr 8) 0xFF); SPI1_Transfer(addr 0xFF); dat SPI1_Transfer(0x00); MRAM_CS_HIGH(); return dat; } void MRAM_ReadBuffer(uint32_t addr, uint8_t *buf, uint32_t len) { uint32_t i; MRAM_CS_LOW(); SPI1_Transfer(0x03); SPI1_Transfer((addr 16) 0xFF); SPI1_Transfer((addr 8) 0xFF); SPI1_Transfer(addr 0xFF); for (i 0; i len; i) { buf[i] SPI1_Transfer(0x00); } MRAM_CS_HIGH(); }读操作可以一次连续读很多字节不需要每读一个字节就失能再使能 CS#这跟普通 SPI NOR Flash 类似。写操作也可以连续写CS# 拉低、发 WRITE 指令和起始地址然后连续发送多个数据字节MRAM 会按地址递增逐字节写入。MRAM 没有页缓冲限制连续写长度可以一直延续到地址结尾但实际应用中我一般限制单次写入不超过一个完整记录块比如 256 字节这样掉电恢复时数据边界更容易判断。注意写数据之前一定要发 WREN。MR25H40CDF 上电后默认写使能是关闭的如果忘记执行0x06发 WRITE 指令后数据根本不会写进去。在调试时这个现象非常坑因为写指令看起来是正常的CS# 时序也对SO 也没有报错但回读就是旧值。这是我第一次调 MRAM 时踩过的坑后来把所有写操作都封装成“内部先发 WREN 再发 WRITE”的公共函数才彻底避免。5. 工业环境中的稳定性与故障排查5.1 常见问题速查表把实际调试过程中遇到的问题整理成表格方便快速对照现象可能原因解决办法写之后回读数据是旧值没有发 WREN 指令每次写操作前调用MRAM_WriteEnable()读固定地址出现全 0xFF地址逻辑错误或 CS# 时序不对检查 24 位地址发送顺序用示波器看 CS# 下降和 SCK 首沿位置数据每隔一段区域就错位三字节地址被当成两字节发送严格按(addr 16)、(addr 8)、addr拆字节读数据时偶发卡死SPI 等待标志位未及时清除给收发改加超时检查 SDI 引脚是否被复用一上电就写失败运行一段时间又正常电源不稳定或 MCU 复位后 CS# 被外部拉低提高 CS# 上拉增强电源退耦写入速度慢性能不对劲SPI 分频配置成了最大分频比在 MRAM 允许范围内提高 SCK 频率部分区域数据随机翻转HOLD# 被干扰拉低必须把 HOLD# 上拉到 VCC不要浮空5.2 掉电保护、CRC 校验和看门狗工业设备很少处在“正常断电”的理想环境。继电器弹跳、电网晃电、电机反电动势随时都会让 MCU 电压跌落。为了在掉电时还能把最后的数据写进 MRAM我在软件上做了三件事。第一是电源监测。利用 PIC18F57Q43 的模拟比较器或外部电源监控芯片在电压跌到 3.0V 时触发中断。中断里只做两件事关闭中断源、把 32 字节现场信息写入 MRAM 固定地址。因为 MRAM 写速度足够快这 32 字节在这个窗口内能写进去。如果是普通 EEPROM每个字节 3ms32 字节需要 96ms电源早就没了。第二是 CRC 校验。每次写入数据块时在这个块的最后 2 字节附加一个 CRC16 校验值。读取时先计算数据区 CRC再和存储的 CRC 比较。MRAM 本身可靠性很高但系统总线、电源噪声仍可能在 MCU 和 MRAM 之间的传输途中引入了错误。CRC 能保证软件层面发现异常。第三是看门狗超时。SPI 在工业环境下偶尔会被干扰拉死。我加了独立的看门狗定时器如果 SPI 读写循环超过 10ms就强制复位 MCU。复位后首先重新初始化 SPI 和 PPS然后再继续数据记录。因为 MRAM 的内容在掉电或复位后保持不变恢复逻辑只需要根据起始标记和 CRC 判断最后一条有效记录在哪。5.3 实测中的几个坑第一个坑是 WP# 引脚没有上拉结果状态寄存器被意外修改。这导致后续 WRITE 不生效。因为 MRAM 的写保护机制分为 WP# 引脚硬保护和状态寄存器软保护两层两者都会阻止写操作。我用万用表量 WP# 发现一直是低电平赶紧把上拉电阻加上就好了。以后我拿到带 WP# 或 HOLD# 的存储芯片第一步就是确认两个引脚的电平状态再动手调 SPI。第二个坑是在同一个 SPI 总线上挂了另一个从设备。MCU 的一个 MSSP 外设同时接了 Flash 和 MRAM两个芯片的 CS# 由不同 GPIO 控制。有次我操作 MRAM 时忘记把 Flash 的 CS# 拉高Flash 输出端跟 MRAM 的 SO 信号在总线上打架读出来就是乱码。最后我把空闲设备的 CS# 强制拉高的逻辑统一放到了 SPI 操作开始前的初始化函数里才算解决了。第三个坑是示波器测量时发现 CS# 下降沿到 SCK 第一个上升沿的时间太短。很多 SPI 从机对 CS# 建立时间有要求虽然没有严格到哪微秒级别但为了保险我会在 CS# 拉低之后插入几个空指令等待提供大约 1μs 的余量。写驱动时也可以利用 MSSP 的SPI1TCON来调整 CS# 到时钟之间的延迟。这种细节在开发板上跑不出来但到批量产线就会暴露。6. 压力测试与数据记录应用建议6.1 循环读写 10 万次测试代码调通后我做了长时间的循环读写压力测试。测试方法是在 0x00000 到 0x0001FFFF 区域内按 256 字节为一个块依次写入递增序列然后回读校验。写完一遍后清空再用 0xA5 填充再做反转测试。总共连续运行了几天大概重复了十几万次块写读。结果没有出现一次数据不一致MRAM 也没有表现出性能退化迹象。当然十万次对 MRAM 来说只是热身因为它理论上没有寿命上限。不过压力测试依然有必要它能暴露的是 SPI 时序和电源问题。我遇到过一种情况连续写 128 字节时偶尔第 100 个字节以后数据错位排查后确认是 SPI 分频在高速下加上长排线导致信号质量差把时钟从 16MHz 降回 8MHz 后稳定运行。所以MRAM 本身强悍不代表整条链路就必然稳定。6.2 在数据记录架构上的应用思考对于事件记录、波形存储、在线参数表这类应用MRAM 的“按字节直写”特性让架构可以做得非常干净。我建议不要照搬 Flash 的块管理逻辑尽量把 MRAM 当 SRAM 用只在数据记录块头部维护一个 8 字节元信息块序号、写入时间、长度、CRC。每次写入新记录就将块序号递增写入对应地址。读取时扫描元信息就能快速定位最新记录和剩余空间。这样做的另一个好处是系统升级固件时不需要担心擦写对存储寿命的影响。MRAM 可以在固件运行中直接更新引导参数还能在工厂测试阶段反复写入校准数据不需要预留“恢复出厂设置”的磨损额度。这是很多工程师换用 MRAM 之后最明显的感觉很多代码能写得直白多了。我个人在实际操作中还有一个体会MR25H40CDF 和 PIC18F57Q43 这套组合非常适合做“低功耗、高可靠、中等容量”的工业数据记录节点。项目初期的选型犹豫主要集中在成本上MRAM 单价比普通 SPI Flash 高一些但对比整个系统因数据丢失造成的维护成本和停机损失这个差价完全可以接受。如果你手上正好有掉电保存需求又不想维护 Flash 坏块和擦除状态机MRAM 这个方向非常值得试一把。
返回列表