ARTICLE DETAIL

资讯详情

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

工业设备存储选型:MRAM替代EEPROM与SPI Flash的实践

工业设备存储选型:MRAM替代EEPROM与SPI Flash的实践 如果你是做工业设备的嵌入式工程师一定遇到过这种局面设备要保存运行参数、事件记录、故障码数据不能丢还可能要反复写几千上万次。找个存储芯片不难难的是选一个各方面都顺手的方案。这阵子我在做一个仪表控制板选型时把 EEPROM、SPI Flash、MRAM 比了一圈最后定了 MR25H40CDF 这颗 Everspin 的 4Mbit MRAM主控用 Microchip 的 PIC18F97J94。整套读写链路从硬件到驱动我都走了一遍今天把这套方案背后怎么想的、电路怎么接、代码怎么写、坑在哪一次说清楚。1. 项目背景与需求拆解1.1 为什么最终选了 MRAM而不是 EEPROM 或 SPI Flash先说应用场景。工业仪表上电要读回上一次的设备参数运行中要存报警记录关机时要保存本次运行累计量。这类数据有两个硬性要求第一掉电不能丢第二写入次数不能有限制得太死板。很多设备一天写几十条记录一年就是上万次十年就是十万次。普通 SPI EEPROM 写寿命一般在 10 万到 100 万次看着不少但如果按秒级连续写日志很快就会到上限。SPI Flash 更麻烦虽然容量大但按页擦除、按页写入写寿命只有 10 万次左右而且掉电时如果正好在做块擦除数据是整片丢。MR25H40CDF 是 Everspin 的 MRAM也就是磁性随机存储器。它的写寿命标称在 10 的 14 次方这个量级常规产品生命周期内基本可以不考虑写坏的问题而且写入不需要先擦除直接把数据覆盖到目标地址就行。这一点和 SRAM 一样“想写就写”但数据掉电不丢刚好卡在工业日志、心跳计数值、参数频繁修改这一类痛点场景。MR25H40CDF 容量是 4Mbit折算下来 512KB 字节。对仪表参数、几百条报警记录、运行日志来说这个容量不算大但在“非易失”“高写次数”这两个维度上它比同容量 EEPROM 或 Flash 合适得多。我最后没有选超大容量 MRAM是因为工业现场需要存储的往往不是视频、图片这类大数据块而是高频次的、小块的、不能丢的记录。512KB 配合合理的分区管理已经能覆盖绝大多数控制板需求。1.2 PIC18F97J94 在这套方案里扮演什么角色主控选择也不是随手抓的。PIC18F97J94 是 Microchip 的 8 位高集成度 MCU最大优势是外围资源丰富带多个串行口有 MSSP 模块可以当 SPI 主机用还集成了 LCD 驱动、USB、RTC 等模块。对工业仪表这种“屏幕显示 按键输入 参数存储 通信上报”的典型结构来说一颗芯片能把很多功能包进去不用再外挂驱动芯片BOM 成本和生产维护都简单。我这次要做的控制板正好有段码液晶、有一个 RS485 通信口、需要存几十组参数和运行记录。PIC18F97J94 的 MPLAB X 开发环境成熟代码库齐全配合 Microchip Code Configurator 可以快速生成 SPI 外设初始化代码。虽然它是 8 位机很多人一听 8 位就觉得老但在工业控制领域稳定供货周期长、单个物料生命周期久、抗干扰能力经过验证这些都比“跑 Linux 上云”更实际。对数据处理量不高的存储场景8 位 MCU 完全够用。2. 整体方案设计与选型逻辑2.1 为什么走 SPI 接口不选并行总线的存储方案MR25H40CDF 本身是 SPI 接口这一点在选型时算是一个加分项。SPI 引脚少标准四线片选 CS、时钟 SCK、数据输入 SI、数据输出 SO加上电源和地板上占用面积很小。工业控制板经常有强电、通信、模拟采集叠在一起PCB 布局紧张少引脚就是少麻烦。并行存储虽然读写速度快但会占掉 MCU 十几个 IO而且高速并行总线更容易引入干扰调试也费劲。SPI 在工业应用里有一个额外好处可以方便地和多个 SPI 设备挂同一条总线。比如同一块板上还可以挂 SPI ADC、SPI DAC 或者 SPI 显示缓存。只要片选 CS 分开控制MRAM 和这些外设可以共用一个 SPI 主机。PIC18F97J94 的 MSSP 模块本身支持多片选管理通过普通 GPIO 拉低某个设备的 CS 即可逻辑不复杂。从性能上讲MR25H40CDF 最高支持 40MHz 左右的时钟频率。PIC18F97J94 的 SPI 主机时钟来自系统时钟分频实际能跑多少取决于系统主频和分频设置。我在方案里没有盲目拉满时钟而是先用 20MHz 做保守配置。对参数读取、日志存储这类操作20MHz 已经足够快而且低速信号走线余量大现场抗干扰能力更好。2.2 数据容量和读写吞吐量怎么核算很多人选存储芯片只看容量忽略了读写耗时。工业设备里最怕出现这种情况MCU 正在写 Flash突然掉电导致正在写的扇区损坏。MRAM 由于不需要先擦除写入是一个覆盖动作时间很短掉电窗口被压到很小。理论上只要电源电压还在芯片工作范围内写动作已经开始就能写完配合适当的电源监视和延时可靠性比 Flash 高一个量级。用 20MHz SPI 算个账。一个字节 8 个时钟传输一位的时间是 50ns一个字节就是 400ns。读或写 1KB 数据纯数据时间大约 0.41ms加上命令头、地址、CS 拉高拉低的开销实测大概 0.5ms 左右。如果一次写 64 字节的诊断记录开销几乎可以忽略。也就是说即使每秒写 10 条记录也只占用很小的 MCU 时间片。真正吃时间的不是 SPI 传输本身而是上层 CRC 校验、协议组包和任务调度。所以选型时我认为更重要的是“非易失”“写寿命”“无需擦除”而不是把 SPI 速率抬到极限。MR25H40CDF 在这种负载下基本是在“养生模式”下工作可靠性自然有富余。2.3 512KB 空间怎么划分才实用MR25H40CDF 总共 512KB 地址空间按 0x00000 到 0x7FFFF 寻址。我在项目里把它分成三块区域地址范围大小用途0x00000 - 0x000FF256 字节系统信息区版本号、出厂日期、设备编号0x00100 - 0x3FFFF约 255KB参数配置区生产参数、运行参数0x40000 - 0x7FFFF约 255KB事件日志区报警、故障、操作记录系统信息区放最前面因为这部分数据很少写上电初始化先读它。参数配置区允许在设备运行中修改每次修改都直接覆盖写不需要“先擦除再写”。事件日志区做成环形缓冲区写到末尾就回到头部配合序号字段可以判断哪条记录最新。由于 MRAM 没有擦写寿命问题环形日志不用做损耗均衡这也是比 Flash 舒服的地方。3. 核心电路设计细节3.1 最小接线表MR25H40CDF 是 8 脚 DFN 封装引脚不多接线相当直接。PIC18F97J94 的 MSSP 模块可以映射到具体引脚我用了片选独立 GPIO 的方式。连接关系如下MR25H40CDF 引脚方向连接对象说明CS输入PIC GPIO片选低电平有效SCK输入PIC SCK 输出SPI 时钟SI输入PIC SDO 输出主机数据输出SO输出PIC SDI 输入主机数据输入WP输入VCC 或 GPIO写保护HOLD输入VCC暂停通信正常接高VCC电源3.3V芯片供电GND电源地公共地WP 引脚我直接接到了高电平不启用硬件写保护。如果系统里需要防止运行中误改状态寄存器可以把这个引脚接到 MCU 的 GPIO 上用软件控制保护时机。HOLD 引脚必须拉高或由主机控制不能悬空否则在噪声环境下可能误触发暂停SPI 通信会卡住。3.2 电源和去耦需要注意什么MR25H40CDF 工作电压是 3.3V 级别PIC18F97J94 同样跑 3.3V所以这板子不需要额外的电平转换芯片。电源设计上我做了两个措施每个电源引脚旁边放一颗 100nF 陶瓷电容就近打孔到地同时在整个 3.3V 进入板子的地方放一颗 1uF 到 10uF 的胆电容或 MLCC 做蓄流。MRAM 自身功耗很低但工业板上往往有继电器、通信电路电源纹波大会导致 MOSI/MISO 电平判断出错所以电源去耦不要省。复位时序上MRAM 不要求像某些加密芯片那样严格的上电时序。MCU 完成 POR 后等 MSSP 外设和 GPIO 初始化完成再访问 MRAM 即可。我习惯在初始化代码里把 CS 先置为高电平避免上电瞬间 CS 不确定而把 MRAM 当成选中状态产生意外写操作。3.3 布局布线的三板斧这类低速 SPI 电路布线不需要太玄学但有三个经验值得记下来。第一SCK 和 SI/SO 尽量短不要绕大圈更不要在它们下面走高压或继电器驱动线。第二CS 信号对噪声敏感比数据线更容易受到干扰可以在 CS 引脚处加一个 10kΩ 上拉电阻到 3.3V确保 MCU 复位期间 MRAM 不会被意外选中。第三DFN 封装底部如果有裸露焊盘一定要接到地既散热又增强机械强度。如果板子上有变频器或接触器布板时最好在 SPI 四根线上串 33Ω 到 47Ω 的阻尼电阻终端加小电容滤波。实测下来低速 1MHz 时钟甚至不怎么受影响但 20MHz 高速时长线上振铃会导致采样错误。低速通信和稳定之间我倾向于稳。4. 读写驱动的核心实现4.1 先把 SPI 模式对上MR25H40CDF 支持的 SPI 模式是 Mode 0 和 Mode 3。我选 Mode 0空闲时 SCK 为低电平数据在上升沿采样下降沿发送。PIC18F97J94 的 MSSP 模块可以通过寄存器配置成这种模式。如果你是用 MPLAB Code Configurator 生成代码在 SPI 组件里选 Mode 0 即可如果手写寄存器注意核对 CKP 和 CKE 位避免 SCK 极性反了导致读回来全是错位数据。MSSP 初始化里主机时钟我用了系统时钟的 4 分频。系统主频 64MHz 时SPI 时钟就是 16MHz接近 20MHz 的目标。跑了一年多的现场板子16MHz 在这个距离内非常稳定。如果后续要更高吞吐可以再提高到 32MHz但要重新检查波形质量。4.2 命令集和状态寄存器MR25H40CDF 的 SPI 指令集和很多 SPI 存储芯片类似。我实际用到的几个命令如下命令操作码功能WREN0x06写使能RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读取数据WRITE0x02写入数据写数据之前必须先发 WREN 命令把状态寄存器里的写使能位打开。这一点和某些支持无条件写的器件不同。如果漏掉 WREN后面的 WRITE 命令会被芯片忽略。读数据不需要写使能拉起 CS发 READ 命令再发 24 位地址然后连续接收字节即可。MRAM 写入不像 Flash 那样需要等页擦除或比较久的编程时间但状态寄存器里仍有一个 WIP 位表示忙状态严谨的做法是在连续写完一批数据后读一次状态寄存器确认。4.3 底层读写代码框架下面是一个最简驱动框架用 PIC18 的 MSSP 交换字节函数作为基础。实际工程中可以把SPI_ExchangeByte替换成 MCC 生成的SPI1_ExchangeByte。#define MRAM_CS_LAT LATCbits.LATC2 // CS 引脚输出 #define MRAM_CS_TRIS TRISCbits.TRISC2 // CS 方向 static void MRAM_CS_Select(void) { MRAM_CS_LAT 0; } static void MRAM_CS_Release(void) { MRAM_CS_LAT 1; } void MRAM_WriteEnable(void) { MRAM_CS_Select(); SPI_ExchangeByte(0x06); // WREN MRAM_CS_Release(); } uint8_t MRAM_ReadStatus(void) { uint8_t sr; MRAM_CS_Select(); SPI_ExchangeByte(0x05); // RDSR sr SPI_ExchangeByte(0x00); MRAM_CS_Release(); return sr; } int MRAM_WriteData(uint32_t addr, const uint8_t *buf, uint16_t len) { uint16_t i; if (len 0) return 0; MRAM_WriteEnable(); MRAM_CS_Select(); SPI_ExchangeByte(0x02); // WRITE SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); for (i 0; i len; i) { SPI_ExchangeByte(buf[i]); } MRAM_CS_Release(); return len; } void MRAM_ReadData(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t i; MRAM_CS_Select(); SPI_ExchangeByte(0x03); // READ SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); for (i 0; i len; i) { buf[i] SPI_ExchangeByte(0x00); } MRAM_CS_Release(); }这套代码的关键点是每次写命令前都调用MRAM_WriteEnableCS 在两个命令之间必须拉高再拉低否则写使能不会锁存。地址是 24 位发送高位先发。对 4Mbit 容量来说地址不会超过 0x7FFFF所以最高字节通常为 0x00。读取没有页边界限制可以连续读任意长度直到 CS 拉高为止。4.4 工业数据的高层记录格式底层驱动只是搬运数据真正决定可靠性的还是上层封装。我在工业板上直接使用的不是裸字节而是一条格式化的记录。每条记录包含固定魔数、序号、长度、CRC16 校验和、有效数据。结构体大概是这样#define REC_MAGIC 0x5A5A #define REC_HEADER_LEN 10 typedef struct __attribute__((packed)) { uint16_t magic; // 魔数用于识别记录有效 uint32_t seq; // 记录序号用于判断新旧 uint16_t len; // data 的有效长度 uint16_t crc16; // 从 magic 到 data 末尾的 CRC uint8_t data[]; // 实际业务数据 } Record_t;写入前先从日志区读到当前最大序号然后加一生成新记录。把业务数据填到 data 区计算整条记录的 CRC16再调用MRAM_WriteData写入对应槽位。写入完成后马上读回整条记录重新计算 CRC如果和写入前不一致就重试。这套双重校验逻辑看起来多了几步但在工业现场很值得因为即使概率很低也不能把数据可靠性寄托在“应该没问题”上。对于参数配置区我更建议做 AB 双备份。A 区写配置 AB 区写配置 B每份配置都带版本号。上电时比较两份版本号选版本较新的作为有效配置。这种方法在掉电中途写入时至少有一份是完整的。MRAM 虽然写入窗口短但工业环境里总有极端情况做双备份比单纯依赖芯片特性更让人放心。4.5 实测读写耗时会用到哪些数据为了验证性能我做了几组简单测试。读取 1KB 数据纯 SPI 传输加命令开销实测约 0.4ms 到 0.5ms写入 1KB 数据因为多了 WREN 指令和回读校验整体约 0.6ms 到 0.8ms。如果关闭回读校验只做写入时间会更短。对 256 字节的报警记录单条写读回耗时不超过 0.2ms完全不影响主程序的运行节奏。所以在实际项目里SPI 存储模块基本不是性能瓶颈。如果感觉读写卡顿先检查是不是主循环频繁做了全量回读或者 CRC 计算太复杂再检查是不是 SPI 波特率设置过低。别一上来就把 SRAM 的锅甩给 MRAM它跑数据很快问题多半在代码结构。5. 工业场景中的可靠性问题与实战排查5.1 读回来的数据全是 0xFF是什么原因这片芯片读出来全是 0xFF最常见的原因是 CS 没有被真正拉低或者 SPI 模式不对。CS 是低电平有效如果 GPIO 配置成了高电平输出或者上拉把 CS 拉住了芯片根本不会响应。解决方法是先用万用表量 CS 引脚在上电后应当看到高电平进入读函数后用示波器抓 CS 应该能抓到低电平脉冲。还有一个原因是 SPI Mode 配成了 Mode 0但 MSSP 的时钟极性和相位没配对。MR25H40CDF 虽然支持 Mode 0 和 Mode 3但很多初始化代码在模式切换时搞反了。我建议先写一个循环读状态寄存器的函数如果 RDSR 能正确返回状态位就说明 SPI 通信链路是通的再继续调试读写主流程。5.2 数据偶尔写不进去或者写一次就丢数据写不进去优先检查有没有发 WREN。MR25H40CDF 的写命令在 WREN 之后才能生效。一个容易踩的坑是WREN 命令后 CS 没有先拉高再拉低就直接发 WRITE 命令。正确时序是 CS 拉低、发 0x06、CS 拉高然后再 CS 拉低、发 0x02 和地址数据。两个命令之间的 CS 高电平是必须的不能偷懒。如果回读校验发现数据丢了先在写入完成后再读状态寄存器看 WIP 位是否已经清零。正常停机时 WIP 会很快结束但如果在写入过程中 MCU 复位而 MRAM 的 CS 又被复位信号拉低可能产生半截写序列。解决办法是在 MCU 复位期间把 CS 用上拉电阻固定在无效电平让 MR25H40CDF 不收到不完整的 SPI 数据。5.3 中断里操作 SPI 导致时序错乱我在调试时遇到过一个很隐蔽的问题数据写入偶尔错位后来发现是定时器中断打断了一次 MRAM 写操作。中断服务程序里也调用 SPI 发送数据和主循环的 MRAM 写时序交叉导致总线上出现多出来的字节。这就像两个人同时用一根电话线说话肯定乱套。解决方案有两个一个是在写 MRAM 的完整命令期间关闭全局中断写完再开另一个是给 SPI 总线准备一个互斥标志任何模块使用 SPI 前先申请标志用完释放。工业控制里实时性要求高关中断不宜太久所以我最终选了互斥标志的方式。MRAM 单条命令只有几十个字节最坏等一个上位机通信周期不会造成任务超时。5.4 现场抗干扰怎么验证实验室里怎么测都好的板子到了配电柜里可能出问题。我调试这种存储电路时常会做两个干扰测试一个是电磁接触器随机吸合放电测试数据是否错乱另一个是可编程电源模拟瞬间掉电再上电测试数据能不能保持。MRAM 本身因为磁性存储机制抗辐射和抗干扰能力比 Flash 强但驱动电路和 MCU 侧仍然可能有问题。遇到干扰后数据错乱先看 CS 和电源线上的毛刺。如果 CS 上出现负向窄脉冲芯片可能被意外选中如果 3.3V 上跌落超过几百毫伏芯片可能处于欠压状态。处理办法是使 CS 上拉电阻更小一点比如 4.7kΩ电源入口加一个几百微法的储能电容保证掉电瞬间 MRAM 有足够能量完成最后一条写操作。6. 这套方案入手之后我藏了几个私货经验MR25H40CDF 和 PIC18F97J94 的组合最让我省心的地方是存储方案不再需要“擦除—写回—断电保护”这套繁琐流程。以前用 SPI Flash 做日志要照顾擦除块边界要预留写备份代码写了一堆换到 MRAM 之后直接把日志区当环形内存用写坏了旧记录也没事因为写寿命基本用不完。这个开发体验上的区别只有真实做过产品才能体会。如果让我给后来者一个建议我会说第一MRAM 的读写原理虽然不复杂但一定要把回读校验当成标配不要相信“它很快所以不会错”第二电源和 CS 的处理比 SPI 速率更值得花时间工业现场稳定优先第三如果产品有频繁写参数的需求可以考虑把 MRAM 同时当成参数区和日志区省掉外部 EEPROM减少一颗物料。后续我的板子还想把历史曲线数据按秒级存下来512KB 空间按 16 字节一条曲线点能存约三万次这个方案还能继续复用。
返回列表