ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MR25H40CDF与PIC18F97J60:工业级MRAM存储设计实践

MR25H40CDF与PIC18F97J60:工业级MRAM存储设计实践 做嵌入式这几年存储选型是个绕不开的活儿。早期很多项目无非就是 24C02 之类的 EEPROM容量不够就换 25 系列 SPI Flash但一旦涉及到工业现场“频繁写入”“快速保存”“断电不能丢”这三个条件凑到一起传统方案的短板就会暴露出来。我之前在一个工业数据记录终端项目里用过 MR25H40CDF 和 PIC18F97J60 的组合一个是很典型的 8 位以太网工业单片机一个是串行 MRAM 存储芯片配合下来效果出乎意料地稳。这篇文章把整体思路、硬件接线、读写实现和调试踩坑都整理出来给正在做同类选型的嵌入式工程师一个参考。全程用的是实际工程里跑过的代码和流程可以直接照着改。1. 为什么偏偏是 MR25H40CDF 配 PIC18F97J601.1 工业存储的三大硬指标先说我个人的选型标准。工业现场的内存存储需求跟消费电子产品不太一样总结下来就是三个字快、稳、长。第一是快。这里的“快”不是指大带宽吞吐而是“任意时刻写一个字节都不会让系统等太久”。你用 EEPROM写一个字节要等 5ms 到 10ms 的写周期时间写多字节还要一页一页地等项目里如果有中断或实时任务在跑这 10ms 会变得非常尴尬。第二是稳稳定性不只是数据保持十年而是写入过程中不怕掉电。Flash 写一半掉电可能丢数据EEPROM 虽然写原子性好一些但页写时也有半写状态。第三是长指擦写寿命。运行日志、计费记录、校准参数这些数据频繁更新一天写几百次很正常普通 EEPROM 标称 100 万次擦写寿命看着够用实际上如果每次都写整个配置块很容易三五年就到极限。MR25H40CDF 这类串行 MRAM 正好把这三个指标全占了。它是磁阻式随机存储器本质上是 RAM 的特性但数据掉电不丢。写一个字节不用擦除、不用等周期SPI 传完即写完擦写寿命的标称值在 10^14 次级别工程上可以当作无限寿命工作温度范围普遍做到 -40°C 到 85°C在工业现场非常能打。我做过的那个项目里MR25H40CDF 承担的是三百多条运行参数的频繁保存和近万条事件日志的循环记录任务。如果用 EEPROM光磨损均衡就得写不少代码用 SPI NOR Flash还得考虑扇区擦除的耗时和坏块管理。换 MRAM 之后驱动复杂度直接降了一个量级逻辑上就当一块“掉电不丢失的 SRAM”来用。1.2 PIC18F97J60 在组合里的角色PIC18F97J60 是 Microchip 一款集成以太网 MAC 和 PHY 的单片机不用外挂以太网控制器芯片这对于小尺寸工控板很友好。它本身是增强型 8 位内核带 64KB 程序 Flash 和 3.8KB 左右的 RAM内置多个串行通信模块其中 MSSP 模块可配成 SPI 主模式直接驱动串行 MRAM。选择它的另一个理由是生态成熟。工业控制领域用了很多年的器件资料文档、参考设计、TCP/IP 协议栈例程非常齐全。配合 MR25H40CDF 时最舒服的一点是两者都吃 3.3V 供电IO 电平直接兼容不用做任何电平转换硬件上少了一堆麻烦。在项目里的角色划分很明确PIC18F97J60 负责采集传感器数据、跑网络协议栈、跟触摸屏或上位机通信MR25H40CDF 负责把采集到的关键数据、设备配置、故障事件落盘。两者各干各的一个管“脑子”一个管“记性”配合起来不需要引入额外的存储管理芯片。2. 硬件连接就这么几根线但每一根都不能马虎2.1 MR25H40CDF 引脚认知MR25H40CDF 是 4Mbit512K×8的串行 MRAM容量说大不大说小不小存配置和日志完全够用。它的封装是 SOP-8 之类的 8 脚小封装引脚定义跟常见的 SPI 存储器风格一致CS# 片选、SCK 时钟、SI 数据输入、SO 数据输出、WP# 写保护、HOLD# 保持、VDD 和 VSS。其中有两个引脚需要特别留意就是 WP# 和 HOLD#。很多工程师第一次用串行 MRAM 时容易把这两个脚悬空结果调试时出现各种诡异问题。WP# 拉低会禁止写操作HOLD# 拉低会暂停通信且让输出脚变为高阻。比较好的做法是正常应用下把这两个引脚直接接到 VDD或者软件里不做特殊控制就都上拉到 3.3V。还需要注意它的地址是 3 字节格式也就是 24 位地址对应 512K 字节空间。调用读写函数时地址参数一定要用 32 位无符号整数否则高字节被截掉只能访问到低 64K而且越界写也不会报错但数据会回卷覆盖前面的内容。2.2 PIC18F97J60 接线要点硬件连线方面PIC18F97J60 的 SPI 模块派出 SCK、SDO、SDI 三根线再随便拿一个 GPIO 做片选 CS#。典型的连接关系是下面这样MR25H40CDF 引脚PIC18F97J60 引脚说明CS#GPIO任意数字输出片选低电平有效平时拉高SCKSCK1 或 SCK2SPI 时钟SISDO1/SDO2主机输出、从机输入SOSDI1/SDI2从机输出、主机输入WP#3.3V禁用写保护HOLD#3.3V禁用保持功能VDD3.3V电源VSSGND地需要补充的是 PIC18F97J60 有多组 SPI 引脚可以映射不同封装和不同引脚功能复用表有差异。接线前必须查一下具体数据手册的引脚说明确认你用的那组引脚没有被模拟输入或者 JTAG 功能占用。我自己的习惯是优先选以太网和串口之外的空闲引脚免得后续调试网络时来回折腾。2.3 电路上补的那几手硬件上除了几根信号线还有几个容易忽略的细节值得提前做。SPI 信号线串接一个 22Ω 到 33Ω 的电阻尤其是 SCK 和 SI 线在工业现场能明显抑制振铃特别是当 PCB 走线超过 5 厘米的时候。VDD 引脚旁边放一个 0.1μF 陶瓷电容紧贴引脚再加一个 10μF 电解电容做电源平滑这对防止 SPI 通信瞬间掉电压翻车很有用。CS# 线要加上拉电阻。这个我多说一句因为真的在批量产品上吃过亏。单片机上电复位或程序下载期间GPIO 方向处于未确定状态如果外部电路又恰好在此时把 CS# 拉低MRAM 芯片会莫名其妙地被选中此时 SDO 引脚也可能输出数据导致总线冲突。解决办法很简单用 10kΩ 电阻把 CS# 上拉到 VDD保证无主控控制时它维持在高电平。HOLD# 和 WP# 也是一样就算接 VDD 也要通过上拉电阻或直接硬连不要用代码里去 spice 模拟“好像接了”的方式。实际操作中我见到过因为 HOLD# 悬空、手摸一下板子就导致通信中断的案例原因就是悬空引脚受人体静电感应电平乱跳。3. 读写实现从命令集到工程代码3.1 初始化 SPIMR25H40CDF 支持标准 SPI Mode 0 和 Mode 3也就是 CPOL/CPHA 两种组合均可用。对于 PIC18F97J60在 MSSP 模块里做相应配置即可。我个人习惯用 Mode 0全行业的大多数 SPI 从机也默认兼容 Mode 0后续如果想换别的传感器、别的存储芯片代码改动最小。初始化的关键是先把 SPI 时钟压到最慢档。我开始调试时通常选择 FOSC/64 甚至更低跑通流程后再逐步提速保证第一步排除时序问题。下面这段是基于 MPLAB XC8 的寄存器级初始化参考void SPI_MRAM_Init(void) { // 将用到的引脚设为数字 IO关闭模拟输入功能 // 具体寄存器要看实际选用的引脚和单片机的 ADCON/ANCON 配置 // 配置 MSSP1 为主模式起始时钟 FOSC/64 SSP1CON1 0b00100010; // SSPEN1, CKP0, SSPM0010 SSP1STATbits.CKE 1; // 配合 CKP0 得到 SPI Mode 0 // 片选引脚设为数字输出初始拉高 MRAM_CS_HIGH(); }注意不同批次或不同型号的 PIC18F97J60 引脚命名有细微差异比如 SDO/SDI 也可能是 RB、RC 口上的复用功能一定要以实际数据手册的 MSSP 引脚映射为准。3.2 单字节读写命令MR25H40CDF 的 SPI 指令集很简洁核心是四个命令WREN06h写使能、WRITE02h写数据、READ03h读数据、RDSR05h读状态寄存器。逻辑上跟串行 EEPROM 差不多但少了很多“页写”之类的限制。先封装一个最底层 SPI 字节交换函数unsigned char SPI_Exchange(unsigned char dat) { SSP1BUF dat; // 写入要发送的数据 while (!SSP1STATbits.BF); // 等待接收缓冲满 return SSP1BUF; // 返回收到的数据 }然后是写使能和单字节写入。写使能是 MRAM 写操作的前置条件虽然 MRAM 本身不需要像 Flash 那样擦除但命令序列里 WREN 还是必须的而且要遵守“CS 拉低、发 WREN、CS 拉高”这个完整时序#define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRITE 0x02 #define MRAM_CMD_READ 0x03 #define MRAM_CMD_RDSR 0x05 void MRAM_WriteByte(unsigned long addr, unsigned char dat) { // 写使能 MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_WREN); MRAM_CS_HIGH(); // 写数据 MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_WRITE); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); SPI_Exchange(dat); MRAM_CS_HIGH(); }注意地址发送顺序是高位在前、低位在后也就是先发 A16~A23再发 A8~A15最后发 A0~A7。这个顺序跟多数 SPI NOR Flash 一样。我之前一开始习惯性先发低位字节结果读出地址全乱了卡了好半天。单字节读取更简单不需要写使能unsigned char MRAM_ReadByte(unsigned long addr) { unsigned char dat; MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_READ); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); dat SPI_Exchange(0x00); // 这个字节是读的随便发一个 0 MRAM_CS_HIGH(); return dat; }这里有个细节SPI 是同步全双工协议读的时候主机也必须产生时钟所以要发一个“假字节”把 SO 线上的数据带回来。发 0x00 就行这是常规操作。3.3 多字节读写与环形日志MRAM 最让我舒服的一点是没有“页缓冲”的概念。SPI NOR Flash 写一页通常限制在 256 字节超过一页就要人工处理边界换页EEPROM 也类似。MR25H40CDF 只要 CS# 拉低期间可以连续不断地送地址后面的数据字节地址自动递增直到把整个存储空间写完。这意味着多字节写入的驱动代码非常简单void MRAM_WriteBuffer(unsigned long addr, unsigned char *buf, unsigned int len) { MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_WREN); MRAM_CS_HIGH(); MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_WRITE); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); while (len--) SPI_Exchange(*buf); MRAM_CS_HIGH(); } void MRAM_ReadBuffer(unsigned long addr, unsigned char *buf, unsigned int len) { MRAM_CS_LOW(); SPI_Exchange(MRAM_CMD_READ); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); while (len--) *buf SPI_Exchange(0x00); MRAM_CS_HIGH(); }页边界问题完全消失这对做日志系统是巨大的简化。我以前用 SPI NOR Flash 做日志每存满一页就要算下一页地址同时为了防止“跨页写”还要拆分成多次操作驱动又长又容易出 bug。换成 MRAM 之后我只需要维护一个“写指针”和“读指针”循环写就行。做环形日志时我建议每条记录固定长度比如 64 字节头部放 4 字节序号、2 字节长度、2 字节 CRC后面跟数据。写入顺序要注意先把记录数据本身写完最后再写序号和 CRC。这样即使写入过程中掉电最多丢一条未完成的记录已经写好的完整记录不会被半更新的 CRC 污染恢复时遇到 CRC 校验失败的记录跳过就行。3.4 存储布局建议MR25H40CDF 有 512K 字节怎么划分空间建议提前规划好。我常用的一种布局是区域地址范围用途配置区主区0x00000 ~ 0x000FF256 字节存设备编号、网络参数、校准系数配置区备份0x00100 ~ 0x001FF256 字节配置备份写入时交替更新日志区0x01000 ~ 0x7FFFF大约 500K 字节事件日志循环存储预留区0x00200 ~ 0x00FFF其他临时存储、自检标记配置区为什么用双备份因为配置参数往往是“关键资产”掉电写坏一个字节都可能让设备失联。我的做法是写配置时先写备份区再写主区校验位放在每条配置的末尾。启动时先读主区CRC 校验不过就回退读备份区再校验不过就恢复默认参数。MRAM 写入速度快做这种双备份几乎不增加耗时开销完全是白赚的可靠性。4. 调试记录我在现场踩过的坑4.1 写进去读不出来我第一次调 MR25H40CDF 时遇到的情况是写函数执行完回头读同一个地址读出来的全是 0xFF。排查了半个多小时最后才想起来把状态寄存器读出来看一眼。一读发现 WEL 位是 0说明 WREN 根本没生效或者被 MCU 复位清掉了。后来总结出三条检查顺序第一确认 CS# 在 WREN 命令后确实拉高又拉低了很多 SPI 存储器要求写使能之后 CS# 必须回到高电平再拉低进入下一个命令第二确认 WP# 引脚确实接在 VDD而不是悬空或拉低第三读一下状态寄存器看 WEL 位是否为 1。状态寄存器读取命令很简单用 RDSR05h就能把状态字节读回来。我建议在调试期写一个单独的测试函数专门读状态寄存器并打印出来这样就很容易区分“命令没发对”“写保护开启”和“芯片没焊好”三类问题。4.2 偶发乱码与抗干扰还有一个项目里出现的现象正常通信没问题但一到电机启动或者继电器吸合的时候从 MRAM 读出来的数据时不时错一位。用逻辑分析仪抓 SPI 波形发现 SCK 线在干扰瞬间有明显的振铃数据线上的波形也不是很干净。这个问题的元凶是长走线加没有匹配电阻。我当时为了布局方便把 MRAM 放得离单片机比较远SCK 走线大约 8 厘米还穿过了一个过孔。解决办法是在 SCK、SI、CS# 上串了 22Ω 电阻另外把 SPI 时钟从 FOSC/16 降到 FOSC/64通信速率降下来之后错误立刻消失。再往深处说工业现场本身就存在传导干扰SPI 信号地要做好走线避免跟电源、继电器控制线平行长距离走在一起。可靠性优先的场景下我建议在协议层加一道 CRC 校验不要只依赖硬件。无论是配置读取还是日志写入数据都带上 CRC 校验值接收方校验不过就重试一次。MRAM 本身出错率极低但通信链路会有干扰加一道协议校验的成本很低收益却很直观。读取时如果连续多次 CRC 失败立即把 CS# 拉高复位通信状态重新同步一次往往就恢复了。4.3 掉电瞬间的“灵异事件”还有一个让我印象深刻的问题是有一次做掉电测试时发现系统在掉电瞬间偶尔会往 MRAM 里写进去一条乱码记录。代码逻辑很明确只在收到掉电中断后才写数据但乱码记录依然出现了。排查到最后发现不是软件问题而是掉电瞬间 SPI 信号乱飞。单片机的电源电压跌落过程中IO 引脚的电平变得不可控如果此时 CS# 处于低电平MRAM 就会被误选中SCK 和 SI 上的毛刺会被当成命令和数据执行。解决办法就是前面提到的CS# 加上拉电阻确保 MCU 未正常初始化时 CS# 保持高电平。另外我在硬件设计里加了电压检测芯片只有当 VDD 稳定在 3.0V 以上时才允许主控访问 MRAM一旦检测到电压跌落到阈值以下立即触发掉电中断外设全部复位CS# 置高。这个机制在工业设备里很有必要。4.4 常见问题速查表现场现象常见原因排查、解决建议写之后读出来全 0xFFWREN 未生效或 WP# 被拉低检查 WREN 时序读状态寄存器确认 WEL 位读出来全 0x00SPI Mode 不匹配或 SI/SO 接反用逻辑分析仪看第一个字节核对引脚连接只有低 64K 能访问地址高字节被截断地址参数用 unsigned long确认 3 字节地址都发送偶发一位数据错误走线太长、无匹配电阻、干扰降 SPI 时钟、串 22Ω 电阻、缩短走线、加 CRC掉电瞬间写入脏数据CS# 悬空/低电平被误选中CS# 外部上拉掉电检测复位前强制拉高 CS#通过外部复位后通信失败HOLD# 电平不稳HOLD#、WP# 接 VDD不要悬空连续写几千字节后出错电源供电不足压跌增大电容检查 3.3V 电源裕量5. 把这套组合放到更大的场景里5.1 以太网远程配置和日志上传PIC18F97J60 既然带以太网把 MRAM 里的数据通过网口传出去就是顺理成章的事。我在项目里做了两层应用第一层是远程配置上位机通过 Modbus TCP 协议把设备参数写入到 MRAM 的配置区断电后不丢重启后自动加载第二层是日志上传本地存储的故障事件日志可以按时间段批量读取出来通过 TCP 连接发给上位机。这里要提醒一点PIC18F97J60 的 RAM 不够大不要把整个日志区一次性读进内存再发送那样 3.8KB 的 RAM 根本装不下。正确做法是边读边发每次从 MRAM 搬一小块缓冲到内存发完再读下一块。读取时保持 CS# 低电平连续读效率高也省内存。5.2 后续扩展的几种玩法MR25H40CDF 这块空间只用来存日志和配置有点可惜。我后来还扩展了几个用途一个是“运行状态掉电快照”把当前运行模式、累计运行时间、变频器频率等关键状态量在掉电前快速写入 MRAM 的一个固定区域下次上电后直接从快照恢复另一个是“校准参数版本管理”在配置区头部的两个字节里存一个软件版本号升级固件时自动做参数迁移。还有个小技巧是可以把 MRAM 当“隔离的共享信箱”用。如果系统里有多个任务需要交换非易失状态比如一个任务负责采集另一个任务负责上报它们之间通过 MRAM 特定区域传递数据省去复杂的互斥锁设计。因为 MRAM 写入速度快这个方式的实时性在大多数工控场景下都能接受。最后分享一个我个人的习惯新环境调试 MRAM 时先别急着上逻辑分析仪把 SPI 时钟降到最慢档发 WREN、写一个字节、读回来对比预期值先把基本链路跑通。链路通了再提速再考虑抗干扰和协议层的 CRC问题会少掉一半。这套 MR25H40CDF 加 PIC18F97J60 的组合我实际跑了快两年批量产品在工业现场没有出现过一例存储异常。如果你正在做类似的工业数据记录或者远程配置设备这套方案值得直接抄作业。
返回列表