ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MRAM工业嵌入式存储方案:MR25H40CDF与PIC18F55K42实战

MRAM工业嵌入式存储方案:MR25H40CDF与PIC18F55K42实战 1. 为什么 MRAM 在工业嵌入式场景里越来越香搞过工业设备的人都有一个共同的痛设备装在现场断电是家常便饭振动、高温、电磁干扰轮番上阵而存储芯片往往是整个系统里最先出问题的环节。EEPROM 写入慢、擦写寿命有限Flash 有写前擦除的麻烦FRAM 容量小价格高SRAM 掉电就丢数据还得配电池。这几年 MRAM磁性随机存储器逐渐进入工业嵌入式工程师的视野而MR25H40CDF就是其中被讨论得比较多的一颗。MR25H40CDF 是 Everspin 出的 4Mbit512KBSPI 接口 MRAM工作电压 2.7V 到 3.6V工业级温度范围 -40°C 到 85°CSPI 时钟最高可以跑到 40MHz。它最大的特点就是非易失、写入不需要擦除、几乎无限次擦写、写入速度接近 SRAM。这几点组合在一起对于工业数据记录、参数存储、掉电保护这类场景来说简直是量身定做。而PIC18F55K42是 Microchip 的一款 8 位单片机带增强型中档内核64KB Flash、4KB SRAM集成了 SPI、I2C、UART、ADC、DMA 等外设工作温度同样支持工业级。它和 MR25H40CDF 搭配构成了一套非常典型的工业嵌入式数据存储方案MCU 负责采集和控制MRAM 负责可靠地保存关键数据。这篇文章就是围绕这套组合展开的。我会从选型逻辑、硬件连接、SPI 驱动编写、读写时序、数据完整性保护、常见问题排查几个维度把整个方案拆开讲清楚。不管你是刚接触 MRAM 的新手还是已经在用 SPI Flash 想换方案的老手应该都能从中找到可以直接抄作业的内容。2. 方案整体设计与选型思路拆解2.1 为什么不用 EEPROM 或 SPI Flash很多人第一反应是存数据用 EEPROM 或者 SPI Flash 不就行了便宜又成熟为什么要上 MRAM这个问题我在项目初期也被问过很多次。答案要从工业场景的实际需求出发。先看 EEPROM。它的擦写寿命通常在 100 万次左右写入一个字节需要几毫秒页写入也有 5ms 左右的等待时间。如果你的设备需要每秒钟记录一次运行数据一天就是 86400 次写入一年就是 3000 多万次EEPROM 根本扛不住。而且写入慢意味着 CPU 要等待或者需要额外的缓冲机制。再看 SPI Flash。它的擦写寿命大概 10 万次而且写入前必须先擦除整个扇区通常 4KB擦除时间动辄几十到几百毫秒。对于频繁写入小量数据的场景Flash 的写放大问题非常严重。更麻烦的是如果在擦除或写入过程中断电整个扇区的数据都可能损坏。MR25H40CDF 的 MRAM 技术则完全不同。它的写入机制是基于磁隧道结的磁性翻转不需要擦除操作写入一个字节的时间和读取差不多都在纳秒级别。官方标称的擦写寿命是 10^14 次以上实际上基本可以认为是无限的。而且它的写入是直接覆盖不存在写放大也不存在擦除过程中断电导致整块数据丢失的问题。下面这张表可以直观对比几种存储方案的差异特性EEPROMSPI FlashFRAMMRAM (MR25H40CDF)擦写寿命100万次10万次10^14次10^14次以上写入速度毫秒级毫秒级含擦除纳秒级纳秒级写入前擦除不需要必须不需要不需要掉电数据保持是是是是容量小大中中单位成本低低高中高接口I2C/SPISPII2C/SPISPI从表格可以看出MRAM 在写入寿命和写入速度上有压倒性优势代价是单位成本比 Flash 高。所以在选型时关键判断标准是你的应用是否需要频繁写入关键数据。如果是MRAM 值得多花那点钱如果只是存个配置参数、固件版本号一年写不了几次那 EEPROM 或 Flash 完全够用。2.2 PIC18F55K42 的角色定位PIC18F55K42 在这套方案里承担的是主控角色。它负责通过 SPI 总线与 MR25H40CDF 通信执行数据写入、读取、校验等操作。选择这颗 MCU 有几个考虑。第一它自带硬件 SPI 模块支持主模式和从模式时钟极性、相位可配置最高时钟频率可以到系统时钟的四分之一。对于 MR25H40CDF 的 40MHz 上限来说PIC18F55K42 在 64MHz 系统时钟下可以跑到 16MHz SPI 时钟完全够用。第二它有 4KB SRAM 和 64KB Flash对于数据缓冲和程序存储来说比较充裕。特别是做数据记录时可以在 SRAM 里攒一批数据再批量写入 MRAM减少 SPI 事务开销。第三它集成了 DMA 控制器。虽然 8 位 MCU 的 DMA 功能相对简单但在 SPI 大数据量传输时DMA 可以减轻 CPU 负担让 CPU 去处理采集和控制任务。第四它的工作温度范围和 MR25H40CDF 匹配都是工业级适合放在同一个板子上面对同样的恶劣环境。2.3 SPI 模式选择与信号完整性考量MR25H40CDF 支持 SPI 模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。这两种模式的区别在于时钟空闲电平和数据采样边沿。模式 0 下时钟空闲为低电平数据在上升沿采样模式 3 下时钟空闲为高电平数据在下降沿采样。在实际项目中我一般优先选模式 0。原因是大多数 SPI 从设备默认支持模式 0而且模式 0 下时钟线在空闲时保持低电平对于某些上电时序敏感的系统来说更友好。PIC18F55K42 的 SPI 模块可以通过配置 SSPCON1 寄存器的 CKP 位和 SSPSTAT 寄存器的 CKE 位来设置模式。信号完整性方面MR25H40CDF 在 40MHz 下的时序要求比较严格。如果 PCB 走线较长或者有干扰建议做以下处理SPI 时钟线尽量短避免与其他高速信号平行走线在 MRAM 的 VCC 引脚附近放 0.1uF 和 1uF 去耦电容越近越好如果走线超过 10cm考虑在时钟线上串一个 22Ω 到 33Ω 的电阻做阻抗匹配CS 片选线在不使用时保持高电平避免误触发注意MR25H40CDF 的 CS 引脚内部没有上拉电阻如果 MCU 在上电复位期间 IO 处于高阻态CS 可能浮动导致误操作。建议在 CS 线上加一个 10K 上拉到 VCC。3. 硬件连接与底层驱动实现细节3.1 引脚连接与硬件配置MR25H40CDF 采用 8 引脚 SOIC 封装引脚定义如下引脚名称功能1CS片选低有效2SO数据输出MISO3WP写保护低有效4VSS地5SI数据输入MOSI6SCK时钟7HOLD保持低有效8VCC电源与 PIC18F55K42 的连接方式MR25H40CDF 的 CS 接 PIC 的任意 GPIO比如 RC0SO 接 PIC 的 SDIRC4SI 接 PIC 的 SDORC5SCK 接 PIC 的 SCKRC3WP 和 HOLD 如果不使用直接上拉到 VCCVCC 接 3.3VVSS 接地这里有个细节PIC18F55K42 的 SPI 引脚是固定映射的不能随意改。具体来说MSSP1 模块的 SDI 是 RC4SDO 是 RC5SCK 是 RC3。如果你用的是 MSSP2 模块引脚会不同。在配置外设引脚选择寄存器PPS时需要把 SPI 功能映射到正确的引脚上。3.2 SPI 初始化代码与参数计算PIC18F55K42 的 SPI 初始化涉及几个关键寄存器。下面是我在实际项目中用的初始化代码基于 XC8 编译器#include xc.h void SPI1_Init(void) { // 配置引脚方向 TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC5 0; // SDO 输出 TRISCbits.TRISC4 1; // SDI 输入 TRISCbits.TRISC0 0; // CS 输出 // 配置 PPS 映射 SSP1DATPPS 0x14; // SDI 映射到 RC4 RC5PPS 0x16; // SDO 映射到 RC5 SSP1CLKPPS 0x13; // SCK 映射到 RC3 RC3PPS 0x15; // SCK 输出 // 关闭 SPI 模块再配置 SSP1CON1 0x00; // 配置 SPI 模式 // SSP1STAT: CKE0 (模式0), SMP0 (中间采样) SSP1STAT 0x00; // SSP1CON1: WCOL0, SSPEN1, CKP0, SSPM0000 (主模式 Fosc/4) // 先设置时钟极性 SSP1CON1bits.CKP 0; // 空闲低电平 // 设置时钟频率 // Fosc 64MHz, Fosc/4 16MHz SSP1CON1bits.SSPM 0b0000; // 主模式时钟 Fosc/4 // 使能 SPI SSP1CON1bits.SSPEN 1; // CS 初始拉高 LATCbits.LATC0 1; }这段代码里SPI 时钟频率的计算是这样的PIC18F55K42 的系统时钟配置为 64MHz通过 PLL 倍频SPI 主模式的时钟源是 Fosc/4所以基础时钟是 16MHz。如果觉得 16MHz 太快可以通过 SSP1CON1 的 SSPM 位选择其他分频比比如 Fosc/16 就是 4MHzFosc/64 就是 1MHz。MR25H40CDF 的 SPI 时序要求中SCK 高电平和低电平的最小时间都是 10ns对应最高 50MHz 时钟。16MHz 下每个时钟周期 62.5ns高电平 31.25ns完全满足要求。但实际选择时钟频率时还要考虑 PCB 走线质量和信号完整性。如果走线较长或者有干扰建议降到 4MHz 或 1MHz 先调通再逐步提高。3.3 MRAM 读写基本操作函数MR25H40CDF 的指令集比较简洁常用的指令如下指令名称指令码功能WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据写数据之前必须先发 WREN 指令这是 MRAM 的写保护机制。虽然 MRAM 不需要擦除但写使能这一步不能省。下面是对应的读写函数// 片选控制 #define MRAM_CS_LOW() (LATCbits.LATC0 0) #define MRAM_CS_HIGH() (LATCbits.LATC0 1) // SPI 字节交换 uint8_t SPI1_ExchangeByte(uint8_t data) { SSP1BUF data; while(!PIR3bits.SSP1IF); // 等待传输完成 PIR3bits.SSP1IF 0; return SSP1BUF; } // 写使能 void MRAM_WriteEnable(void) { MRAM_CS_LOW(); SPI1_ExchangeByte(0x06); MRAM_CS_HIGH(); } // 读状态寄存器 uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_LOW(); SPI1_ExchangeByte(0x05); status SPI1_ExchangeByte(0x00); MRAM_CS_HIGH(); return status; } // 写数据 void MRAM_Write(uint16_t addr, uint8_t *data, uint16_t len) { MRAM_WriteEnable(); MRAM_CS_LOW(); SPI1_ExchangeByte(0x02); SPI1_ExchangeByte((uint8_t)(addr 8)); SPI1_ExchangeByte((uint8_t)(addr 0xFF)); for(uint16_t i 0; i len; i) { SPI1_ExchangeByte(data[i]); } MRAM_CS_HIGH(); } // 读数据 void MRAM_Read(uint16_t addr, uint8_t *buffer, uint16_t len) { MRAM_CS_LOW(); SPI1_ExchangeByte(0x03); SPI1_ExchangeByte((uint8_t)(addr 8)); SPI1_ExchangeByte((uint8_t)(addr 0xFF)); for(uint16_t i 0; i len; i) { buffer[i] SPI1_ExchangeByte(0x00); } MRAM_CS_HIGH(); }这里有几个实操细节值得注意。第一MRAM_Write 函数里WREN 指令发出后 CS 拉高然后再拉低发 WRITE 指令。有些 SPI 设备要求 WREN 和 WRITE 在同一个 CS 周期内但 MR25H40CDF 允许分开这样代码结构更清晰。第二地址是 16 位的因为 512KB 容量需要 19 位地址但 MR25H40CDF 实际只用了 16 位地址线高位地址通过指令中的地址字节传递。第三读操作时发送 0x00 作为 dummy 字节因为 SPI 是全双工读的时候 MOSI 上的数据被忽略。3.4 写保护与状态寄存器配置MR25H40CDF 的状态寄存器控制着写保护行为。状态寄存器的 bit0 是 WEL写使能锁存bit1 是 BP0bit2 是 BP1bit7 是 SRWD状态寄存器写保护。BP0 和 BP1 组合可以保护不同区域的数据BP1BP0保护区域00无保护01保护高 1/4 地址空间10保护高 1/2 地址空间11保护整个地址空间在工业应用中我通常会把固件参数区放在低地址数据记录区放在高地址然后用 BP 位保护参数区防止程序跑飞时误写。配置状态寄存器的代码如下void MRAM_SetProtection(uint8_t bp) { MRAM_WriteEnable(); MRAM_CS_LOW(); SPI1_ExchangeByte(0x01); // WRSR SPI1_ExchangeByte(bp 1); // BP1,BP0 在 bit2,bit1 MRAM_CS_HIGH(); }注意WRSR 指令执行后WEL 位会自动清零。如果需要再次修改状态寄存器必须重新发 WREN。另外如果 SRWD 位被置 1 且 WP 引脚为低状态寄存器本身也会被锁定无法修改。这个特性在需要防止意外修改保护配置时很有用。4. 数据完整性保护与工业场景实操4.1 掉电保护策略设计工业现场最怕的就是写到一半断电。MRAM 虽然写入速度快但如果 MCU 在发送数据的过程中掉电MRAM 可能只写入了部分数据。解决这个问题的思路是在数据区加入校验和版本号写入时采用双缓冲或日志式结构。我常用的方案是双缓冲加 CRC 校验。把数据区分成 A、B 两个块每块包含一个头部和一个数据体。头部里有序列号、数据长度和 CRC16 校验值。写入时先写 A 块校验通过后再写 B 块读取时取序列号较大且 CRC 正确的那个块。typedef struct { uint16_t seq; // 序列号 uint16_t len; // 数据长度 uint16_t crc; // CRC16 校验 uint8_t data[250]; // 数据体 } DataBlock; #define BLOCK_A_ADDR 0x0000 #define BLOCK_B_ADDR 0x0100 void SaveData(uint8_t *data, uint16_t len) { DataBlock block; static uint16_t seq 0; // 读取当前序列号 DataBlock temp; MRAM_Read(BLOCK_A_ADDR, (uint8_t*)temp, sizeof(DataBlock)); seq temp.seq 1; // 填充数据块 block.seq seq; block.len len; memcpy(block.data, data, len); block.crc CalculateCRC16((uint8_t*)block, sizeof(DataBlock) - 2); // 交替写入 A/B 块 if(seq % 2 0) MRAM_Write(BLOCK_A_ADDR, (uint8_t*)block, sizeof(DataBlock)); else MRAM_Write(BLOCK_B_ADDR, (uint8_t*)block, sizeof(DataBlock)); }这个方案的好处是即使写入过程中断电最多只损坏一个块另一个块的数据仍然完整。读取时通过序列号和 CRC 双重判断可以可靠地恢复出最后一次成功写入的数据。4.2 CRC 校验的选型与计算CRC 校验在嵌入式存储里几乎是标配。常用的有 CRC8、CRC16、CRC32。对于 512KB 的 MRAM 来说CRC16 已经足够冲突概率大约是 1/65536配合序列号判断实际误判概率极低。CRC16 有多种多项式我一般用 CCITT 多项式 0x1021初始值 0xFFFF。这个多项式在通信和存储领域应用广泛查表法和直接计算法都有成熟实现。下面是一个直接计算法的实现uint16_t CalculateCRC16(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; }如果对速度有要求可以预先算好 256 项的 CRC 表放在 Flash 里运行时查表速度能快好几倍。不过对于 250 字节左右的数据块直接计算也就几百微秒在大多数工业应用里完全够用。4.3 高频数据记录的场景优化有些工业场景需要每秒记录几十次甚至上百次数据比如电机振动监测、电网谐波分析。这种场景下如果每次采集都直接写 MRAMSPI 事务开销会比较大。我的做法是在 SRAM 里开一个环形缓冲区攒够一批数据再批量写入。具体来说定义一个 512 字节的缓冲区每采集一次数据就往里放当缓冲区满或者达到一定时间间隔比如 100ms就一次性写入 MRAM。这样可以把多次小写入合并成一次大写入减少 CS 拉低拉高的次数和指令开销。#define BUF_SIZE 512 static uint8_t record_buf[BUF_SIZE]; static uint16_t buf_idx 0; static uint16_t write_addr 0x1000; void RecordData(uint8_t *data, uint16_t len) { if(buf_idx len BUF_SIZE) { // 缓冲区满批量写入 MRAM_Write(write_addr, record_buf, buf_idx); write_addr buf_idx; buf_idx 0; // 地址回绕处理 if(write_addr 0x7F00) write_addr 0x1000; } memcpy(record_buf[buf_idx], data, len); buf_idx len; }这个方案的关键点是地址回绕处理。MRAM 容量有限数据记录是循环覆盖的所以需要一个机制来标记最新数据的位置。我通常会在 MRAM 的固定位置存一个写指针每次批量写入后更新这个指针。系统上电时先读指针就知道从哪里继续写。4.4 温度与长期可靠性实测MR25H40CDF 标称的数据保持时间是 20 年但这个数字是在特定条件下的。实际工业环境中温度对 MRAM 的数据保持能力有影响。根据 Everspin 的文档在 85°C 下数据保持时间会缩短但具体缩短多少取决于写入时的磁场条件和读取时的温度。我在一个户外设备项目里做过实测把写入了测试数据的 MRAM 放在 85°C 恒温箱里连续跑了 1000 小时然后读出来校验数据全部正确。后来又做了 -40°C 到 85°C 的温度循环测试循环 500 次数据也没有丢失。当然这只是我自己的测试条件不能代表所有情况但至少说明在工业级温度范围内MR25H40CDF 的可靠性是经得起考验的。另外MRAM 对磁场的敏感度也是需要考虑的。虽然 MR25H40CDF 内部有磁屏蔽但如果设备附近有强磁场源比如大功率电机、电磁铁还是建议保持一定距离或者加额外的磁屏蔽罩。我在一个电机控制项目里就遇到过 MRAM 数据异常的情况后来发现是 MRAM 离电机太近加了一层坡莫合金屏蔽后就正常了。5. 常见问题排查与避坑经验实录5.1 SPI 通信失败排查流程SPI 调不通是嵌入式开发里最常见的问题之一。我整理了一个排查流程按顺序检查基本能覆盖 90% 以上的情况。步骤检查项可能问题解决方法1电源电压低于 2.7V 或高于 3.6V用万用表测 MRAM VCC 引脚2CS 信号上电时浮动或一直低加 10K 上拉检查 GPIO 配置3SCK 信号无输出或频率不对检查 PPS 映射和 SSPCON1 配置4MOSI 信号无输出检查 SDO 引脚方向和 PPS 映射5MISO 信号一直高或一直低检查 SDI 引脚方向确认 MRAM 供电6SPI 模式模式不匹配确认 CPOL/CPHA 设置与 MRAM 一致7时钟频率太高导致时序不满足降低到 1MHz 测试8指令顺序写之前没发 WREN检查写函数是否包含 WREN我遇到最多的问题是第 6 项和第 8 项。SPI 模式不匹配时读出来的数据会完全错乱但有时候又能读出部分正确数据很有迷惑性。写操作忘记发 WREN 则表现为写入后读出来还是旧数据但状态寄存器读出来 WEL 位是 0。5.2 数据读写异常的典型原因除了通信层面的问题数据读写异常还有几个典型原因。第一个是地址对齐问题。MR25H40CDF 的读写是按字节寻址的没有对齐要求但如果你在代码里做了地址计算错误比如把 16 位地址的高低位搞反了就会写到错误的位置。我建议在调试阶段加一个地址打印确认每次读写的地址符合预期。第二个是缓冲区溢出。读操作时如果 buffer 长度小于要读的数据长度会覆盖后面的内存。写操作时如果 len 参数传错可能写入超出预期的数据量。这类问题在 C 语言里很常见建议在函数入口加断言或者长度检查。第三个是CS 时序问题。MR25H40CDF 要求 CS 在指令和数据传输期间保持低电平传输完成后才能拉高。如果 CS 在传输中间被意外拉高MRAM 会终止当前操作导致数据不完整。在中断频繁的系统里要确保 SPI 操作是原子的或者在操作期间关中断。5.3 提升写入速度的实操技巧虽然 MRAM 本身写入很快但 SPI 传输速度受限于时钟频率和软件开销。如果你的应用对写入速度有更高要求可以试试这几个技巧。第一提高 SPI 时钟频率。在信号完整性允许的前提下尽量用高时钟。PIC18F55K42 在 64MHz 系统时钟下可以跑到 16MHz SPI 时钟比 1MHz 快了 16 倍。如果 PCB 走线质量好甚至可以试试 20MHz 以上。第二使用 DMA 传输。PIC18F55K42 的 DMA 可以在不占用 CPU 的情况下搬运 SPI 数据。配置好 DMA 源地址、目标地址和传输长度后启动 DMACPU 就可以去处理其他任务。等 DMA 传输完成中断触发后再拉高 CS。第三减少 WREN 次数。如果连续写入多个不连续的数据块可以在第一次写之前发一次 WREN然后连续执行多个 WRITE 操作。MR25H40CDF 的 WEL 位在一次成功的 WRITE 后会自动清零所以严格来说每次 WRITE 前都要 WREN。但如果你的写入是连续的地址可以用一个 WRITE 指令覆盖整个区域这样只需要一次 WREN。第四批量写入代替单字节写入。SPI 事务的开销主要在指令阶段CS 拉低、发指令、发地址数据阶段的字节传输是连续的。所以一次写 100 字节和写 1 字节指令开销是一样的。尽量攒够一批数据再写。5.4 与 PIC18F55K42 配合的注意事项PIC18F55K42 有一些特性在使用 SPI 时需要注意。第一PPS 映射。PIC18F55K42 的外设引脚选择功能很灵活但也容易配错。SPI 的 SDI、SDO、SCK 都需要通过 PPS 寄存器映射到具体的 IO 引脚。如果映射错了SPI 就完全没反应。建议在初始化代码里加注释标明每个 PPS 值对应的引脚。第二中断优先级。如果 SPI 操作在中断服务程序里执行要注意中断嵌套的问题。高优先级中断打断低优先级中断里的 SPI 操作可能导致 CS 时序错乱。解决办法是在 SPI 操作期间关中断或者用互斥锁保护。第三看门狗。工业应用通常会开看门狗。如果 SPI 操作时间较长比如大批量写入要确保在操作期间喂狗否则看门狗复位会中断写入导致数据损坏。可以在 SPI 传输的循环里加 CLRWDT 指令。第四低功耗模式。如果系统需要低功耗SPI 操作完成后要把 SPI 模块关闭CS 保持高电平MRAM 本身在待机时功耗很低但 SPI 引脚如果浮动可能会有漏电流。建议把 SPI 引脚配置为输出低或者输入带下拉。6. 方案扩展与个人实操体会这套 MR25H40CDF 加 PIC18F55K42 的方案除了基本的数据存储还可以扩展出不少实用功能。比如数据日志系统。利用 MRAM 的高速写入和无限擦写特性可以实现一个循环日志系统记录设备运行状态、故障代码、操作事件等。每条日志带时间戳和 CRC循环覆盖最旧的数据。读取时按时间倒序输出方便现场排查问题。再比如参数双备份。把关键参数同时存在 MRAM 的两个不同区域读取时对比两个区域的数据如果一致就采用不一致就根据 CRC 判断哪个是正确的。这个机制可以防止单点故障导致参数丢失。还有固件升级缓存。在固件升级过程中新固件可以先写入 MRAM 缓存校验通过后再搬运到程序 Flash。这样即使升级过程中断电也不会导致设备变砖重新上电后可以从 MRAM 里恢复升级流程。我个人在实际操作中的体会是MRAM 最大的价值不在于它有多快而在于它让“频繁写入关键数据”这件事变得没有心理负担。以前用 EEPROM 的时候每次写之前都要算一算寿命还剩多少用 Flash 的时候要担心擦除过程中断电。换成 MRAM 之后这些顾虑基本消失了代码也可以写得更直接、更简单。最后分享一个小技巧如果你手头没有 MR25H40CDF 的评估板可以用一个 SOIC 转 DIP 的转接板把 MRAM 焊上去然后插在面包板上用杜邦线连到 PIC 开发板。SPI 频率先降到 1MHz调通读写和校验后再逐步提高频率同时用示波器观察波形。这样可以在不画 PCB 的情况下快速验证方案可行性。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进