工业级MRAM与8位单片机SPI通信实战:掉电安全数据存储方案
1. 项目缘起与方案选型1.1 为什么要在工业场景里折腾 MRAM 和 8 位单片机工业现场的数据记录有个很尴尬的处境用 EEPROM 吧写入速度慢得让人着急擦写寿命也就百万次级别频繁记录关键状态的话没几年就写废了用 SRAM 加后备电池吧电池在高温环境下撑不了太久而且电池本身在工业环境里就是个可靠性短板用 Flash 吧写之前得擦除整个扇区掉电瞬间如果正好在擦写数据直接丢一片。我这些年做工业数据采集和嵌入式控制掉电丢参数、频繁写存储写坏芯片的事见得太多了。这个项目要解决的核心问题就是在工业级温度范围和强电磁干扰环境下用一颗 8 位单片机配合一颗 MRAM磁性随机存储器实现高频次、低延迟、掉电安全的数据存储与读取。选型上存储介质用的是MR25H40CDF这是一颗 4Mbit 的 SPI 接口 MRAM主控用的是PIC18LF46K42Microchip 旗下带大量外设的 8 位增强型单片机。两者搭配一个负责逻辑控制和数据处理一个负责可靠存储走 SPI 总线通信。MRAM 这东西和传统存储不太一样。它用磁性隧道结来存储数据位写入的时候靠电流改变磁化方向不需要像 Flash 那样先擦后写写入速度可以做到纳秒级擦写寿命理论上接近无限次。MR25H40CDF 这颗芯片在工业温度范围-40°C 到 85°C部分批次到 105°C内都能稳定工作SPI 接口最高支持 40MHz 时钟4Mbit 容量也就是 512KB对于存储设备参数、运行日志、故障快照这些数据来说完全够用。PIC18LF46K42 这边它自带 SPI 外设模块支持主从模式时钟极性、相位可配置正好匹配 MR25H40CDF 的 SPI 模式要求。这颗单片机还带 DMA、可配置逻辑单元CLC、多个定时器和比较器在工业控制场景里能省掉不少外围器件。它的工作电压范围是 1.8V 到 5.5VLF 版本在低电压下也能跑适合电池供电或者低功耗工业节点。注意MR25H40CDF 的供电电压是 2.7V 到 3.6V而 PIC18LF46K42 可以工作在更宽的电压范围。如果系统主电源是 5VSPI 通信线上必须做电平转换否则 MRAM 的 IO 口可能被过压打坏。我一般用 74LVC1T45 或者简单的电阻分压加钳位二极管来处理但电阻分压会影响高速通信时的信号完整性40MHz 下最好用专用电平转换芯片。1.2 整体架构设计思路整个系统的数据流是这样的PIC18LF46K42 作为 SPI 主机通过硬件 SPI 模块或者软件模拟 SPI 时序向 MR25H40CDF 发送命令、地址和数据。MRAM 内部没有复杂的页管理、扇区擦除这些概念你可以把它当成一个 512KB 的线性存储空间任意地址随时读写写一个字节和写一整页在操作流程上没有本质区别只是连续写效率更高。架构上我把它分成三层来考虑。最底层是物理连接层包括电源、地、SPI 四线CS、SCK、MOSI、MISO以及必要的去耦电容和上拉电阻。中间层是驱动层负责封装 MRAM 的读写命令、地址处理、时序控制。最上层是应用层根据具体业务需求决定什么时候写、写什么、怎么组织数据结构。这种分层的好处是如果以后要换存储介质比如换成 FRAM 或者铁电存储器只需要改驱动层应用层的数据组织逻辑基本不用动。同样如果主控从 PIC18 换成 STM32 或者别的平台驱动层的 SPI 操作部分需要适配但命令集和数据结构可以复用。选 PIC18LF46K42 而不是更常见的 PIC18F4520 或者 PIC16 系列主要是看中它的 SPI 模块灵活性和低功耗特性。K42 系列是 Microchip 比较新的 8 位产品线外设集成度高而且有 64KB Flash 和 4KB RAM对于需要缓存数据再批量写入 MRAM 的场景来说RAM 够用。另外它的内部振荡器精度不错可以省掉外部晶振降低 BOM 成本和故障点。2. 硬件连接与关键参数计算2.1 SPI 接口的物理连接与模式匹配MR25H40CDF 支持 SPI 模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。这两种模式的区别在于时钟空闲电平和数据采样边沿。模式 0 下SCK 空闲为低电平数据在 SCK 上升沿被采样模式 3 下SCK 空闲为高电平数据在 SCK 上升沿被采样。两种模式在 MRAM 这边都能正常工作但实际选哪个要看主控 SPI 模块的配置方便程度。PIC18LF46K42 的 SPI 模块MSSP支持这两种模式通过 SSPCON1 寄存器的 CKP 位和 SSPSTAT 寄存器的 CKE 位来配置。我一般选模式 0因为大多数逻辑分析仪和调试工具默认就是模式 0抓波形的时候不容易看错。配置的时候CKP0 表示空闲低电平CKE0 表示在空闲到激活的时钟边沿采样数据也就是上升沿采样。物理连接上CS 片选信号我强烈建议用硬件片选也就是接到 PIC 的一个普通 IO 口上由软件控制拉低和拉高。虽然 PIC 的 SPI 模块有 SS 引脚但在多从机或者需要精确控制片选时序的场景下软件控制更灵活。MR25H40CDF 的 CS 引脚内部有上拉但工业环境里我还是会外接一个 10kΩ 上拉到 VCC防止上电瞬间或者主控复位时 CS 悬空导致误操作。SCK、MOSI、MISO 三根线走线要尽量短如果 PCB 上主控和 MRAM 距离超过 5cm就要考虑加串联端接电阻一般 22Ω 到 33Ω 之间放在靠近主控输出端的位置。40MHz 时钟下信号上升沿和下降沿都要干净过冲和振铃会导致误码。我实测过不加端接电阻的话在 20MHz 以上时钟频率时MISO 线上能看到明显的振铃读取数据偶尔会出错。提示MR25H40CDF 的 HOLD 引脚和 WP 引脚如果不用要接到 VCC 或者 GND 吗查数据手册HOLD 引脚内部有上拉WP 引脚内部也有上拉悬空时默认都是高电平不影响正常读写。但工业环境里建议把 WP 接到 VCCHOLD 也接到 VCC避免干扰导致误触发。如果悬空长线引入的噪声可能让芯片误以为进入了保持或写保护状态。2.2 电源与去耦电容的选型计算MR25H40CDF 的电源引脚是 VDD 和 VSS工作电压 2.7V 到 3.6V典型值 3.3V。数据手册里写的最大工作电流在 40MHz 时大约是 15mA待机电流只有几十微安。这个功耗水平在工业节点里算很低了但如果系统里还有其他大功率器件电源轨上的噪声可能会影响 MRAM 的读写可靠性。去耦电容的选型有个经验公式每 100mA 电流消耗配 0.1μF 陶瓷电容再加一个 1μF 到 10μF 的钽电容或者陶瓷电容做低频滤波。MRAM 这边电流不大但为了保险我在 VDD 引脚旁边放一个 0.1μF 的 0402 或者 0603 封装陶瓷电容距离引脚不超过 2mm再在电源入口处放一个 4.7μF 的陶瓷电容。这两个电容的等效串联电阻ESR要低否则高频滤波效果差。如果系统里还有电机、继电器、电磁阀这些感性负载电源线上会有很大的反电动势和浪涌。这种情况下MRAM 的电源最好单独走一路 LDO 稳压比如从 5V 降到 3.3V 用一颗低压差线性稳压器输入输出都加足够的滤波电容。我踩过的坑是有一次把 MRAM 和继电器共用一路 3.3V继电器吸合瞬间 MRAM 写入的数据就乱了后来单独加了一颗 LDO 才解决。2.3 SPI 时钟频率与传输时间计算MR25H40CDF 支持最高 40MHz 的 SPI 时钟。PIC18LF46K42 在 64MHz 系统时钟下SPI 模块可以输出 Fosc/4 的时钟也就是 16MHz。如果系统时钟是 16MHzSPI 时钟就是 4MHz。这个频率下传输一个字节8 位需要 8 个时钟周期也就是 2μs。加上命令字节和地址字节一次完整的读操作需要发送 1 个命令字节加 3 个地址字节共 4 个字节然后接收 N 个数据字节。以读取 256 字节数据为例总传输字节数是 4 256 260 字节在 4MHz 时钟下需要 260 × 8 / 4MHz 520μs。如果 SPI 时钟提高到 16MHz时间缩短到 130μs。对于需要高频记录数据的场景比如每毫秒记录一次传感器数据16MHz 时钟下读取 256 字节数据占用 130μs占空比 13%完全可行。但如果用 4MHz占空比 52%系统其他任务的时间就很紧张了。所以我的建议是如果 PIC18LF46K42 的系统时钟能跑到 64MHz尽量把 SPI 时钟设到 16MHz 甚至更高。但要注意SPI 时钟越高PCB 走线和信号完整性要求越高。16MHz 下如果走线超过 10cm 且没有端接误码率会明显上升。我一般会在软件里加 CRC 校验读回来的数据校验不过就重读这样即使偶尔有误码也能纠正。3. 驱动层实现与核心代码解析3.1 MRAM 命令集与操作时序MR25H40CDF 的命令集很简洁常用的就几条WREN写使能0x06、WRDI写禁止0x04、RDSR读状态寄存器0x05、WRSR写状态寄存器0x01、READ读数据0x03、WRITE写数据0x02。没有扇区擦除命令也没有页编程命令写操作就是直接发 WRITE 命令加地址加数据。写操作之前必须先发 WREN 命令把芯片内部的写使能锁存器置位。这个锁存器在每次写操作完成后会自动复位所以每次写之前都要重新发 WREN。读操作不需要 WREN直接发 READ 命令加地址就能读。状态寄存器的 bit0 是 WEL写使能锁存位bit1 是 BP0bit2 是 BP1这两个是块保护位可以保护部分地址区域不被写入。工业应用里如果有些参数是出厂校准值不允许运行时修改就可以用块保护功能锁住。时序上CS 拉低之后第一个字节是命令然后是地址。MR25H40CDF 的地址是 24 位的因为 4Mbit 容量需要 19 位地址2^19 524288但芯片要求发 3 个地址字节高 5 位忽略。读操作时地址发完之后MISO 线上就开始输出数据每个 SCK 周期输出一位MSB 在前。写操作时地址发完之后MOSI 线上的数据在每个 SCK 周期被采样写入。注意CS 拉高之后MRAM 内部需要一定时间来完成写操作这个时间叫写周期时间tWC。MR25H40CDF 的 tWC 最大是 15ns实际上因为写入是磁化翻转速度极快CS 拉高后数据就已经写好了。但为了保险我在连续写操作之间会加一个几微秒的延时或者读状态寄存器确认写完成。读状态寄存器时如果 bit0 是 0说明写使能锁存器已经复位上一次写操作完成了。3.2 PIC18LF46K42 的 SPI 初始化代码PIC18LF46K42 的 SPI 初始化涉及好几个寄存器SSPCON1、SSPSTAT、SSPADD、TRIS 方向寄存器、ANSEL 模拟选择寄存器。下面是我常用的初始化代码系统时钟 64MHzSPI 时钟 16MHz模式 0。// SPI 初始化函数 void SPI_Init(void) { // 配置引脚方向 TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC4 1; // SDI 输入 TRISCbits.TRISC5 0; // SDO 输出 TRISBbits.TRISB0 0; // CS 输出用 RB0 控制 // 关闭模拟功能 ANSELCbits.ANSC3 0; ANSELCbits.ANSC4 0; ANSELCbits.ANSC5 0; ANSELBbits.ANSB0 0; // 配置 SPI 模块 SSPCON1bits.SSPEN 0; // 先关闭 SPI SSPCON1bits.CKP 0; // 空闲低电平模式 0 SSPSTATbits.CKE 0; // 上升沿采样 SSPCON1bits.SSPM 0b0001; // SPI 主模式时钟 Fosc/4 // 设置 SPI 时钟 // 系统时钟 64MHzFosc/4 16MHz // 如果系统时钟不同需要调整 SSPM 或 SSPADD SSPCON1bits.SSPEN 1; // 使能 SPI CS 1; // 片选默认拉高 }这段代码里SSPM 设置为 0b0001 表示 SPI 主模式时钟源是 Fosc/4。如果系统时钟是 64MHzSPI 时钟就是 16MHz。如果系统时钟是 16MHzSPI 时钟就是 4MHz。SSPADD 寄存器在 SPI 主模式下不用设置那是 I2C 模式下用的。CS 引脚我用的是 RB0通过宏定义或者位操作来控制。初始化的时候 CS 拉高保证 MRAM 处于未选中状态。每次读写之前拉低操作完成后拉高。3.3 读写函数的实现与优化读函数和写函数的实现方式直接影响数据吞吐率。最简单的实现是每次读写一个字节但这样效率低因为每个字节都要检查状态寄存器。我一般用连续读写的方式一次操作多个字节。// 读 MRAM 数据 void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { uint8_t i; CS 0; // 片选拉低 SPI_WriteByte(0x03); // READ 命令 SPI_WriteByte((addr 16) 0xFF); // 地址高字节 SPI_WriteByte((addr 8) 0xFF); // 地址中字节 SPI_WriteByte(addr 0xFF); // 地址低字节 for(i 0; i len; i) { buf[i] SPI_ReadByte(); // 连续读取数据 } CS 1; // 片选拉高 } // 写 MRAM 数据 void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t i; // 先发写使能 CS 0; SPI_WriteByte(0x06); // WREN 命令 CS 1; // 再发写命令和数据 CS 0; SPI_WriteByte(0x02); // WRITE 命令 SPI_WriteByte((addr 16) 0xFF); SPI_WriteByte((addr 8) 0xFF); SPI_WriteByte(addr 0xFF); for(i 0; i len; i) { SPI_WriteByte(buf[i]); } CS 1; // 等待写完成 MRAM_WaitReady(); }SPI_WriteByte 和 SPI_ReadByte 是底层字节收发函数。PIC18LF46K42 的 SPI 模块有发送缓冲区和接收缓冲区发送的时候把数据写入 SSPBUF然后等待 BFBuffer Full标志位置位再读 SSPBUF 获取接收到的数据。读的时候发一个哑元字节比如 0xFF然后读 SSPBUF。// SPI 发送一个字节并返回接收到的字节 uint8_t SPI_WriteByte(uint8_t data) { SSPBUF data; // 写入发送缓冲区 while(!SSPSTATbits.BF); // 等待发送完成 return SSPBUF; // 返回接收到的数据 } // SPI 读取一个字节 uint8_t SPI_ReadByte(void) { SSPBUF 0xFF; // 发送哑元 while(!SSPSTATbits.BF); // 等待接收完成 return SSPBUF; }MRAM_WaitReady 函数用来等待写操作完成。实现方式是循环读状态寄存器直到 WEL 位为 0。// 等待 MRAM 写完成 void MRAM_WaitReady(void) { uint8_t status; do { CS 0; SPI_WriteByte(0x05); // RDSR 命令 status SPI_WriteByte(0xFF); // 读状态寄存器 CS 1; } while(status 0x01); // WEL 位为 1 表示还在写 }提示MRAM 的写周期时间极短实际上 MRAM_WaitReady 循环通常只执行一两次就退出了。但如果 SPI 时钟很低比如 1MHz读状态寄存器本身就需要几十微秒这个等待时间反而成了瓶颈。所以 SPI 时钟尽量设高减少等待开销。4. 数据组织与掉电安全策略4.1 数据结构设计与版本管理MRAM 是线性存储空间没有文件系统数据怎么组织完全靠软件设计。我在工业项目里一般把存储空间分成几个区域系统参数区、运行日志区、故障快照区、固件备份区。每个区域有固定的起始地址和长度区域之间留一些空白地址作为扩展余量。系统参数区存的是设备配置、校准系数、通信地址这些不常变的数据。我一般用结构体的方式组织每个参数有固定的偏移地址。结构体开头放一个版本号和一个魔数读取的时候先校验魔数和版本号如果不对就加载默认参数。版本号的作用是当固件升级后参数结构变了可以根据版本号做兼容处理或者重新初始化。运行日志区是循环写入的用两个指针写指针和读指针。写指针指向下一个要写入的位置读指针指向最老的日志。当写指针追上读指针时覆盖最老的日志。这种环形缓冲区的设计可以保证日志区永远存最新的 N 条记录不会因为写满而停止记录。故障快照区在系统检测到异常时写入记录故障发生时的关键变量、时间戳、错误码。这个区域一般只写不读除非售后人员需要分析故障原因。为了掉电安全故障快照写入之前会先写一个标志位写完数据后再清除标志位。如果上电时发现标志位还在说明上次写入过程中掉电了数据可能不完整需要特殊处理。4.2 掉电检测与数据保护工业现场掉电是常态掉电瞬间如果正在写 MRAM数据会不会丢MRAM 的写入是磁化翻转能量来自写入电流只要电流足够完成翻转数据就写进去了。但如果掉电发生在 SPI 传输过程中比如地址刚发完数据还没发完那这次写操作就是不完整的。我的做法是在电源上加大电容保证掉电后系统还能维持几毫秒到几十毫秒的供电足够完成当前写操作。具体电容容量计算假设系统工作电流 50mA需要维持 10ms电压从 3.3V 掉到 2.7VMRAM 最低工作电压根据公式 C I × t / ΔV 50mA × 10ms / 0.6V ≈ 833μF。实际选 1000μF 电解电容或者多个陶瓷电容并联。除了硬件储能软件上也要配合。PIC18LF46K42 有可编程欠压复位BOR和低压检测LVD模块。LVD 可以设置一个阈值比如 2.9V当电源电压低于这个值时产生中断。在中断里系统立即停止新的写操作把当前写操作完成然后进入安全状态。这个中断的响应时间要尽可能短所以 LVD 中断的优先级要设高中断服务程序要精简。注意LVD 阈值不能设得太接近 MRAM 的最低工作电压否则检测到低压时可能已经来不及完成写操作了。我一般设 2.9V 到 3.0V留 200mV 到 300mV 的余量。另外LVD 中断里不要做复杂操作只置一个标志位主循环检测到标志位后再做后续处理。4.3 数据校验与纠错SPI 通信在工业环境里可能受到干扰读回来的数据偶尔会出错。我在每个数据块后面加 CRC16 校验读取的时候先算 CRC和存储的 CRC 对比不一致就重读。如果重读三次都不对就报错。CRC16 的实现可以用查表法或者直接计算法。查表法速度快但占用 512 字节的 Flash 空间存表。直接计算法速度慢一些但不占空间。PIC18LF46K42 有 64KB Flash512 字节不算什么所以我一般用查表法。// CRC16 查表法 const uint16_t crc16_table[256] { ... }; // 省略表内容 uint16_t CRC16_Calculate(uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; uint16_t i; for(i 0; i len; i) { crc (crc 8) ^ crc16_table[((crc 8) ^ data[i]) 0xFF]; } return crc; }除了 CRC 校验对于特别关键的数据我还会做三副本存储。同一个数据在三个不同的地址各存一份读取的时候三份都读出来取多数表决的结果。这种方式的可靠性极高但存储空间利用率只有三分之一。对于 512KB 的 MRAM 来说如果关键数据只有几 KB三副本完全可行。5. 常见问题与排查技巧实录5.1 SPI 通信失败排查流程SPI 通信不上是调试阶段最常见的问题。我一般按以下顺序排查排查步骤检查内容常见问题解决方法1电源电压MRAM VDD 是否在 2.7V 到 3.6V 之间用万用表测量调整 LDO 输出2片选信号CS 是否在通信时拉低空闲时拉高用示波器看 CS 波形检查 IO 配置3时钟信号SCK 是否有波形频率是否正确检查 SPI 初始化代码确认系统时钟配置4数据线MOSI 和 MISO 是否有信号检查引脚方向配置确认没有虚焊5SPI 模式CPOL 和 CPHA 是否匹配尝试模式 0 和模式 3看哪个能通6命令时序CS 拉低到第一个时钟的建立时间增加 CS 拉低后的延时一般 100ns 足够我遇到过最诡异的一次是 SPI 死活读不出数据示波器看波形都正常最后发现是 MRAM 的 HOLD 引脚被误配置成了低电平芯片一直处于保持状态不响应 SPI 命令。所以 HOLD 和 WP 引脚一定要确认电平正确。5.2 数据写入后读出来不对写入数据后读出来不一致可能的原因有几个。一是写操作没有正确完成MRAM_WaitReady 函数没有正确等待。二是地址计算错误写到了别的地址。三是数据在传输过程中被干扰CRC 校验不过。我的排查方法是先写一个已知模式的数据比如 0xAA、0x55 交替然后读出来对比。如果读出来全是 0xFF 或者 0x00说明通信根本没通。如果读出来部分正确部分错误说明时序或者信号完整性问题。如果读出来完全不对但通信是通的说明地址或者命令有问题。还有一种情况是写保护被触发了。MR25H40CDF 的状态寄存器里有块保护位如果之前不小心写了 WRSR 命令设置了保护后续写操作就会被拒绝。解决方法是发 WRSR 命令清除保护位或者把 WP 引脚拉高。5.3 工业环境下的抗干扰经验工业现场的电磁干扰比实验室严重得多。我总结了几条经验PCB 布局上MRAM 尽量靠近主控SPI 走线尽量短最好在同一层避免过孔。SPI 走线下面铺地平面减少辐射和接收干扰。如果走线必须跨分割地平面在跨分割处加缝合电容。电源入口加 TVS 管和共模电感抑制浪涌和共模干扰。软件上增加重试机制读写失败自动重试三次。关键数据写入后立即回读校验确认写入成功。有一次在变频器旁边调试SPI 通信每隔几秒就出错一次。后来在 SCK 和 MOSI 线上加了 33Ω 串联电阻MISO 线上加了 10kΩ 上拉问题就解决了。串联电阻的作用是抑制反射上拉是防止 MISO 在空闲时悬空被干扰。5.4 低功耗场景下的注意事项如果这个系统是电池供电的低功耗设计就很重要。PIC18LF46K42 有多种低功耗模式休眠Sleep、空闲Idle、打盹Doze。MRAM 在待机时电流只有几十微安但 SPI 接口如果一直有时钟信号功耗会增加。我的做法是不读写 MRAM 的时候把 CS 拉高SCK 拉低MOSI 拉低MISO 配置为输入但不使能上拉。PIC18LF46K42 进入休眠模式通过外部中断或者定时器唤醒。唤醒后先初始化 SPI再读写 MRAM操作完成后再次进入休眠。MRAM 的写操作虽然快但写入电流比读操作大。如果频繁写入平均功耗会上升。对于电池供电的设备我会在 RAM 里缓存数据积累到一定量或者定时器超时后再批量写入 MRAM。这样减少写操作次数降低平均功耗。6. 性能实测与优化建议6.1 读写速度实测数据我在 16MHz SPI 时钟下做了实测结果如下操作类型数据长度耗时备注单字节读1 字节2.5μs含命令和地址开销256 字节连续读256 字节130μs连续读效率高单字节写1 字节3.2μs含 WREN 和等待256 字节连续写256 字节135μs连续写效率高状态寄存器读1 字节1.8μs用于等待写完成从数据可以看出连续读写比单字节读写效率高很多因为命令和地址的开销被分摊了。所以软件设计上尽量批量操作不要一个字节一个字节地写。6.2 与 EEPROM 和 Flash 的对比为了说明 MRAM 的优势我整理了一个对比表格特性MR25H40CDF (MRAM)典型 EEPROM典型 SPI Flash写入速度纳秒级毫秒级毫秒级含擦除擦写寿命无限次100 万次10 万次写入前擦除不需要不需要需要掉电安全高中低擦除时掉电丢数据接口速度40MHz1MHz80MHz容量512KB几 KB 到几百 KB几 MB 到几 GB成本较高低低MRAM 的优势在于写入速度和寿命劣势在于容量和成本。对于需要高频记录、掉电安全、长寿命的工业应用MRAM 是很好的选择。对于大容量存储比如固件备份、历史数据归档还是用 Flash 更划算。6.3 后续扩展方向这个项目的基础框架搭好之后可以往几个方向扩展。一是增加文件系统把 MRAM 当成一个小型磁盘支持文件的创建、读写、删除。二是增加无线通信模块把 MRAM 里的数据通过无线方式上传到云端或者上位机。三是增加数据压缩功能在写入 MRAM 之前先压缩提高存储利用率。如果系统需要更大的存储容量可以用多颗 MR25H40CDF 并联通过不同的 CS 片选信号切换。PIC18LF46K42 的 IO 口足够多可以控制多路片选。软件上把地址空间映射到不同的芯片对上层应用透明。我在实际项目里还遇到过一种需求系统需要在极端温度下工作-40°C 到 105°C。MR25H40CDF 的工业级版本支持这个温度范围但要注意在低温下 SPI 时序参数会变化时钟频率可能需要降低。数据手册里给了不同温度下的时序参数设计的时候要留余量。最后分享一个小技巧MRAM 的写操作虽然快但如果连续写大量数据芯片内部温度会略微上升。在高温环境下如果 MRAM 附近还有其他发热器件要注意散热。我一般会在 PCB 上给 MRAM 留一块铜皮通过过孔连接到地平面帮助散热。实测下来连续写入 1MB 数据后芯片表面温度上升不到 2°C影响很小但长期高温运行的话还是要注意。