拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

工业数据记录方案:MRAM与PIC18F47K42的SPI驱动与掉电保护实战

工业数据记录方案:MRAM与PIC18F47K42的SPI驱动与掉电保护实战

1. 项目缘起与方案选型

1.1 为什么要在工业场景里折腾 MRAM 和 PIC

工业现场的数据记录有个很尴尬的处境:用 EEPROM 吧,写入速度慢得让人着急,擦写寿命也就百万次级别,高频采集场景下没几个月就写废了;用 SRAM 加后备电池吧,电池在高温高湿环境下三五年就鼓包漏液,维护成本高得离谱;用 Flash 吧,写之前得擦整个扇区,掉电时机不对还会丢数据,而且擦写寿命同样有限。我在做一款工业数据采集终端的时候,就卡在这个存储介质的选择上,前后试过好几套方案,最后落到MR25H40CDF这颗 MRAM 上,配合PIC18F47K42做主控,才算把这个问题彻底解决。

MR25H40CDF 是一颗 4Mbit 的磁阻随机存取存储器,SPI 接口,关键特性是写入不需要擦除、没有写延迟、擦写寿命几乎无限。这三点对工业数据记录来说简直是量身定做。PIC18F47K42 是 Microchip 的一款 8 位单片机,自带 SPI 外设,主频 64MHz,有充足的 RAM 和丰富的外设,驱动 MRAM 绰绰有余。两者搭配,构成一个"采集—缓存—持久化"的完整数据链路。

这篇文章适合谁看?如果你正在做工业仪表、数据记录仪、黑匣子类的嵌入式项目,需要一颗靠谱的非易失存储,或者你手上有 PIC18 平台想接一颗 SPI 存储芯片但不知道怎么下手,那这篇内容可以直接抄作业。我会把硬件连接、SPI 时序配置、读写驱动、掉电保护、常见坑都讲透,代码给到能编译的程度。

1.2 方案对比:MRAM 到底赢在哪里

在动手之前,我先把几种常见非易失存储介质拉出来对比了一遍,这也是我做选型时的真实决策过程。

特性EEPROMNOR FlashFRAMMRAM (MR25H40CDF)
写入前是否需擦除否是(按扇区)否否
字节写入速度慢(ms级)慢(ms级)快快(无写延迟)
擦写寿命约100万次约10万次约10^14次近乎无限
掉电数据保持好好好好(20年以上)
接口复杂度I2C/SPISPII2C/SPISPI
单位容量成本低低中高中高

从表里能看出来,MRAM 的核心优势就是写入无延迟、寿命近乎无限。工业场景里数据是持续写入的,比如每秒记录一次温度、压力、振动,一天就是 86400 次写入。EEPROM 按 100 万次寿命算,十几天就写废一个字节地址。MRAM 没有这个问题,你可以把它当成一个"掉电不丢的 SRAM"来用,想怎么写就怎么写。

注意:MRAM 虽然写入寿命长,但它是按字节寻址的,不像 Flash 有扇区概念。这意味着你不需要做磨损均衡,代码逻辑会简单很多。但代价是单位容量价格比 Flash 高,所以适合存关键数据而不是海量日志。

1.3 整体架构设计思路

整个系统的数据流是这样的:传感器采集数据 → PIC18F47K42 内部 RAM 做缓冲 → 通过 SPI 写入 MR25H40CDF → 需要时读回。MRAM 在这里扮演的是"持久化层"的角色,MCU 的 RAM 是"缓存层"。

为什么要加一层 RAM 缓冲?因为 SPI 通信本身有开销,如果每采集一个数据就立刻写 MRAM,SPI 事务频繁,CPU 占用高。我的做法是在 RAM 里开一个环形缓冲区,攒够一批或者定时触发一次批量写入。这样既保证了数据不丢(掉电前有足够时间把缓冲刷进 MRAM),又降低了 SPI 的平均负载。

地址规划上,我把 4Mbit(512KB)的空间分成几个区域:前 16KB 存系统配置和校准参数,中间 480KB 存采集数据,最后 16KB 存日志和状态标记。这样分区管理,读写逻辑清晰,也方便后续做数据导出。

2. 硬件连接与 SPI 时序配置

2.1 引脚连接与硬件设计要点

MR25H40CDF 是标准的 8 引脚 SOIC 封装,SPI 接口。和 PIC18F47K42 连接时,我用的是硬件 SPI 外设(MSSP 模块),引脚分配如下:

MR25H40CDF 引脚功能PIC18F47K42 引脚说明
CS片选RC0(GPIO)软件控制片选
SCK时钟RC3(SCK)硬件 SPI 时钟
SI数据输入RC5(SDO)MCU 输出
SO数据输出RC4(SDI)MCU 输入
VCC电源3.3V需加 0.1uF 去耦
VSS地GND—
WP写保护接 VCC 或 GPIO低电平写保护
HOLD保持接 VCC低电平暂停通信

这里有几个硬件上的坑我踩过。第一,CS 片选我坚持用软件控制,虽然 PIC18 的 SPI 外设支持硬件片选,但软件控制更灵活,尤其是在多从机场景下,你可以精确控制片选时序。第二,WP 和 HOLD 引脚千万别悬空,悬空会导致芯片行为不确定,我一开始没接 WP,结果写入偶尔失败,查了半天才发现是写保护引脚浮空被干扰了。第三,去耦电容必须靠近芯片电源引脚,MRAM 在写入瞬间电流会有波动,去耦不好会导致写入错误。

提示:PCB 布线时,SCK 时钟线尽量短,远离模拟信号线。SPI 速率高的时候,时钟线上的过冲和振铃会直接影响通信可靠性。我一般会在 SCK 上串一个 22 欧姆的电阻做阻抗匹配。

2.2 SPI 模式选择与时钟计算

MR25H40CDF 支持 SPI 模式 0(CPOL=0,CPHA=0)和模式 3(CPOL=1,CPHA=1)。我选的是模式 0,因为这是最常用的配置,PIC18 的 MSSP 模块默认就支持。

时钟频率方面,MR25H40CDF 最高支持 40MHz 的 SPI 时钟。但 PIC18F47K42 在 64MHz 系统时钟下,SPI 时钟是 Fosc 的分频。我实际用的是Fosc/4 = 16MHz,这个速率在工业环境下足够稳,而且留了余量。为什么不跑满 40MHz?因为工业现场电磁干扰大,高速 SPI 的误码率会上升,16MHz 是个可靠性和速度的平衡点。

SPI 时钟的计算过程是这样的:PIC18F47K42 的 SPI 时钟源是系统时钟 Fosc,通过 SSPxCON1 寄存器的 SSPxM<3:0> 位设置分频比。Fosc/4 对应 SSPxM = 0b0000。如果你系统时钟是 64MHz,那 SPI 时钟就是 16MHz。这个计算很简单,但要注意从机的最大时钟频率限制,MR25H40CDF 是 40MHz,所以 16MHz 完全在范围内。

2.3 初始化代码与寄存器配置

PIC18 的 SPI 初始化我封装成一个函数,方便复用。核心是配置 MSSP 模块的四个寄存器:SSPxCON1、SSPxCON2、SSPxSTAT、SSPxADD。

void MRAM_SPI_Init(void) { // 配置引脚方向 TRISCbits.TRISC3 = 0; // SCK 输出 TRISCbits.TRISC5 = 0; // SDO 输出 TRISCbits.TRISC4 = 1; // SDI 输入 TRISCbits.TRISC0 = 0; // CS 输出 // 片选默认拉高 LATCbits.LATC0 = 1; // 关闭 MSSP 模块再配置 SSP1CON1 = 0x00; // SPI 模式0:CKP=0, CKE=0 // 时钟 Fosc/4:SSP1M = 0b0000 SSP1CON1 = 0b00100000; // 关闭 SS 硬件控制,用软件片选 SSP1CON2 = 0x00; // 清除状态 SSP1STAT = 0x00; // 使能 SPI SSP1CON1bits.SSPEN = 1; }

这段代码里有个细节:SSP1CON1 的 SSPEN 位要先清零再配置,否则配置可能不生效。我一开始没注意,直接写寄存器,结果 SPI 死活不出波形,后来翻数据手册才发现要先关模块。另外,SSP1STAT 的 SMP 位控制采样时机,模式 0 下 SMP=0 表示在数据输出中间采样,这个对高速通信的稳定性有影响,建议保持默认。

3. 读写驱动实现与地址管理

3.1 MRAM 指令集与操作码

MR25H40CDF 的指令集很简洁,常用的就几条:

指令操作码功能
WREN0x06写使能
WRDI0x04写禁止
RDSR0x05读状态寄存器
WRSR0x01写状态寄存器
READ0x03读数据
WRITE0x02写数据

注意 MRAM 和 EEPROM 不同,它没有擦除指令,写入就是直接覆盖。每次写之前需要发 WREN 使能写操作,写完自动禁止。这个流程和 Flash 类似,但少了擦除步骤,简单不少。

状态寄存器的 WEL 位(Write Enable Latch)表示写使能状态,WIP 位(Write In Progress)表示写入进行中。MRAM 的写入是即时的,WIP 位几乎瞬间就清零,所以一般不需要轮询等待。但为了保险,我在写操作后还是会读一次状态寄存器确认。

3.2 单字节读写函数实现

先看最基础的单字节读写。读操作相对简单,发指令加地址,然后读数据。写操作要先发 WREN,再发 WRITE 指令和地址,最后写数据。

// 读一个字节 uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; MRAM_CS_LOW(); MRAM_SPI_Transfer(0x03); // READ 指令 MRAM_SPI_Transfer((addr >> 16) & 0xFF); // 地址高字节 MRAM_SPI_Transfer((addr >> 8) & 0xFF); // 地址中字节 MRAM_SPI_Transfer(addr & 0xFF); // 地址低字节 data = MRAM_SPI_Transfer(0xFF); // 读数据 MRAM_CS_HIGH(); return data; } // 写一个字节 void MRAM_WriteByte(uint32_t addr, uint8_t data) { MRAM_CS_LOW(); MRAM_SPI_Transfer(0x06); // WREN MRAM_CS_HIGH(); MRAM_CS_LOW(); MRAM_SPI_Transfer(0x02); // WRITE 指令 MRAM_SPI_Transfer((addr >> 16) & 0xFF); MRAM_SPI_Transfer((addr >> 8) & 0xFF); MRAM_SPI_Transfer(addr & 0xFF); MRAM_SPI_Transfer(data); MRAM_CS_HIGH(); }

这里的关键点是WREN 和 WRITE 之间必须拉高 CS,这是 SPI 协议的时序要求。我见过有人把 WREN 和 WRITE 放在同一个 CS 低电平周期里,结果写入失败。MRAM 要求 WREN 作为一个独立的事务完成,CS 拉高后才算生效。

MRAM_SPI_Transfer是一个底层字节交换函数,发一个字节同时收一个字节:

uint8_t MRAM_SPI_Transfer(uint8_t txData) { SSP1BUF = txData; while(!PIR3bits.SSP1IF); // 等待传输完成 PIR3bits.SSP1IF = 0; // 清除标志 return SSP1BUF; }

注意:PIC18F47K42 的 SPI 中断标志在 PIR3 寄存器里,不是 PIR1。这个和老的 PIC18 型号不一样,移植代码的时候要特别小心。我第一次移植就栽在这里,标志位等错了寄存器,程序直接卡死。

3.3 页写入与批量传输优化

单字节写入虽然能用,但效率太低。每次写都要发 WREN、发指令、发地址,SPI 事务开销大。MR25H40CDF 支持页写入,一页 256 字节,你可以在一次 CS 低电平周期内连续写入多个字节,地址自动递增。

void MRAM_WritePage(uint32_t addr, uint8_t *buf, uint16_t len) { uint16_t i; MRAM_CS_LOW(); MRAM_SPI_Transfer(0x06); // WREN MRAM_CS_HIGH(); MRAM_CS_LOW(); MRAM_SPI_Transfer(0x02); // WRITE MRAM_SPI_Transfer((addr >> 16) & 0xFF); MRAM_SPI_Transfer((addr >> 8) & 0xFF); MRAM_SPI_Transfer(addr & 0xFF); for(i = 0; i < len; i++) { MRAM_SPI_Transfer(buf[i]); } MRAM_CS_HIGH(); }

页写入的边界要注意:如果写入跨越页边界(地址低 8 位从 0xFF 翻转到 0x00),地址会回卷到当前页的开头,而不是自动进入下一页。这是很多 SPI 存储芯片的共性,写驱动时必须处理。我的做法是在上层做地址对齐,确保每次写入不跨页。如果数据长度超过一页,就拆成多次页写入。

批量读取就更简单了,发完 READ 指令和起始地址后,可以连续读任意长度,地址自动递增,没有页边界限制。这个特性很适合做数据导出,一次性把整块数据读出来。

3.4 地址空间规划与数据结构

512KB 的空间怎么分,直接决定了后续代码的复杂度。我的分区方案是这样的:

区域起始地址大小用途
配置区0x0000016KB系统参数、校准值
数据区0x04000480KB采集数据记录
日志区0x7C00016KB运行日志、状态标记

配置区存的是设备序列号、传感器校准系数、采样周期这些不常变的数据。数据区用环形缓冲的方式管理,写满后从头覆盖。日志区记录设备启动次数、异常事件、最后写入位置等。

数据记录我定义了一个结构体,每条记录固定 32 字节,包含时间戳、通道号、数值、校验和。这样 480KB 能存 15360 条记录,按每分钟一条算,能存 10 天左右。如果采样频率更高,可以压缩记录格式或者扩大数据区。

typedef struct { uint32_t timestamp; // 时间戳 uint16_t channel; // 通道号 float value; // 测量值 uint16_t crc; // 校验 uint8_t reserved[20]; // 预留 } DataRecord_t;

提示:结构体在内存里会有对齐填充,实际占用可能比字段之和多。用sizeof(DataRecord_t)确认实际大小,别自己手算。我一开始按字段算 32 字节,结果编译器对齐后变成 36 字节,地址计算全乱了。

4. 掉电保护与数据完整性

4.1 掉电检测机制设计

工业现场最怕的就是掉电丢数据。MRAM 虽然写入快,但如果掉电发生在 SPI 事务中间,数据照样会损坏。我的做法是加一个掉电检测电路,用比较器监测电源电压,当电压降到阈值以下时触发中断,MCU 在电容储能耗尽前把 RAM 缓冲区的数据刷进 MRAM。

具体实现上,我用 PIC18F47K42 的HLVD 模块(高/低压检测),设置阈值为 3.0V。正常工作时电源是 3.3V,当电压降到 3.0V 时,HLVD 产生中断。中断服务程序里立刻停止采集,把缓冲区数据写入 MRAM,然后进入休眠。

void __interrupt() HLVD_ISR(void) { if(PIR2bits.HLVDIF) { PIR2bits.HLVDIF = 0; // 紧急保存缓冲区数据 MRAM_FlushBuffer(); // 标记掉电事件 MRAM_LogEvent(EVENT_POWER_FAIL); // 进入休眠 SLEEP(); } }

这个机制的关键是储能电容的容量计算。假设系统工作电流 20mA,掉电后需要 50ms 完成数据刷写,电压从 3.0V 降到 2.5V(MRAM 最低工作电压),那电容容量 C = I × t / ΔV = 20mA × 50ms / 0.5V = 2000uF。实际选型要留余量,我用的是 4700uF,确保有足够时间完成写入。

4.2 数据校验与恢复策略

光有掉电保护还不够,数据写入过程中也可能因为干扰出错。我在每条记录里加了 CRC16 校验,读取时验证,校验失败就标记该条记录无效。

CRC 的计算我用的是查表法,速度快,占用空间小。多项式选的是 CRC-16/CCITT(0x1021),这个在工业领域用得比较多。

uint16_t CRC16_Calculate(uint8_t *data, uint16_t len) { uint16_t crc = 0xFFFF; uint16_t i; for(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; }

除了 CRC,我还在数据区头部维护一个写入指针,记录当前写到哪个位置。每次写入后更新指针,掉电重启后从指针位置继续写。指针本身也带 CRC 保护,防止指针损坏导致数据覆盖。

注意:写入指针的更新和数据的写入要保证原子性。我的做法是先写数据,再写指针,中间加一个状态标记。如果重启后发现状态标记是"写入中",说明上次写入可能不完整,就把这条记录标记为无效,指针回退一位。

4.3 缓冲区管理与刷写策略

RAM 缓冲区的管理直接影响掉电保护的效果。我用的是环形缓冲区,大小 2KB,能存 64 条记录。刷写策略有两种触发条件:一是缓冲区快满(达到 80%),二是定时触发(每 5 秒一次)。

为什么设两个条件?因为如果只等缓冲区满,掉电时缓冲区里可能有大量未写入数据,刷写时间不够。定时刷写能把未写入数据量控制在较小范围,降低掉电风险。5 秒的间隔是根据储能电容的支撑时间反推的,确保任何时刻掉电,缓冲区里的数据都能在电容耗尽前写完。

刷写函数要处理缓冲区回卷的情况,我把它封装成一个函数,内部处理地址计算和页对齐:

void MRAM_FlushBuffer(void) { uint16_t count = Buffer_GetCount(); uint16_t i; for(i = 0; i < count; i++) { DataRecord_t rec; Buffer_Pop(&rec); uint32_t addr = DATA_AREA_START + (write_ptr * sizeof(DataRecord_t)); MRAM_WritePage(addr, (uint8_t*)&rec, sizeof(DataRecord_t)); write_ptr = (write_ptr + 1) % MAX_RECORDS; } MRAM_UpdateWritePtr(write_ptr); }

这段代码里,write_ptr是环形缓冲的写指针,MAX_RECORDS是数据区能存的最大记录数。每次刷写后更新指针到 MRAM,确保掉电重启后能接着写。

5. 常见问题与排查实录

5.1 SPI 通信失败排查表

调试 SPI 通信时遇到的问题最多,我整理了一个排查表,按现象查原因,基本能覆盖 90% 的情况。

现象可能原因排查方法
读回全 0xFFCS 未拉低、SPI 未使能示波器看 CS 和 SCK 波形
读回全 0x00MISO 未接、从机未供电检查 SO 引脚和电源
数据错位SPI 模式不匹配确认 CPOL/CPHA 设置
偶发错误时钟太快、干扰降低 SPI 时钟、加去耦
写入无效WREN 未生效、WP 拉低读状态寄存器确认 WEL 位
地址错乱页边界回卷检查写入是否跨页

我最常遇到的是数据错位,表现是读回的数据整体偏移一位。这通常是 SPI 模式设置错误,CPHA 搞反了。MRAM 用模式 0,如果配成模式 1,采样时机就错了。用逻辑分析仪抓一次波形,对比数据手册的时序图,一眼就能看出来。

5.2 写入寿命与磨损的误区

很多人一听 MRAM 寿命长,就以为可以随便写。实际上虽然 MRAM 的擦写寿命是 10^14 次级别,但SPI 接口本身有事务开销,频繁写入会占用 CPU 时间,影响系统实时性。而且每次写入都要发 WREN,这个额外的 SPI 事务也是开销。

我的建议是尽量批量写入,把多个数据攒在一起写,减少 SPI 事务次数。另外,避免对同一地址的高频写入,虽然 MRAM 不怕磨损,但高频写入同一地址在电磁干扰下出错概率会增加。用环形缓冲分散写入地址,是个好习惯。

提示:MRAM 的数据保持时间在 20 年以上,但这个指标是在常温下测的。高温环境(比如 85°C 以上)会加速磁隧道结的老化,数据保持时间会缩短。工业级应用如果环境温度高,建议定期做数据巡检和备份。

5.3 电源与信号完整性问题

工业现场的电源质量参差不齐,MRAM 对电源波动比较敏感。我遇到过一批设备,在实验室好好的,到了现场就偶发写入失败。查了半天,发现是现场有大功率设备启停,电源上有尖峰干扰。

解决办法是在 MRAM 的电源引脚加TVS 管和磁珠,TVS 钳位尖峰电压,磁珠滤高频噪声。另外,SPI 信号线上加小电容(10pF 左右)对地,能滤掉高频干扰,但电容不能太大,否则会拖慢信号边沿,影响高速通信。

信号完整性方面,SCK 和 MOSI 的走线要等长,虽然 SPI 速率不高,但等长走线能减少时序偏差。如果走线长,考虑加串联电阻做阻抗匹配。我一般用 22 到 33 欧姆的电阻,具体值根据走线长度和板材调整。

5.4 调试工具与实用技巧

调试 SPI 存储,逻辑分析仪是必备工具。我用的是 8 通道的逻辑分析仪,抓 CS、SCK、MOSI、MISO 四根线,配合协议解码,能直接看到 SPI 事务的内容。比示波器好用,因为示波器只能看波形,逻辑分析仪能直接解出指令和数据。

另一个技巧是写一个自检函数,上电时对 MRAM 做一次全区域读写测试。写入已知模式(比如 0xAA、0x55 交替),读回比对,确认存储介质正常。这个自检能提前发现硬件问题,避免设备到了现场才发现存储坏了。

uint8_t MRAM_SelfTest(void) { uint32_t test_addr = 0x7F000; // 用日志区做测试 uint8_t pattern[16] = {0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55, 0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55}; uint8_t readback[16]; MRAM_WritePage(test_addr, pattern, 16); MRAM_ReadPage(test_addr, readback, 16); return (memcmp(pattern, readback, 16) == 0) ? 1 : 0; }

自检通过返回 1,失败返回 0。上电时调用一次,失败就点亮故障灯,提示硬件问题。这个函数帮我省了很多现场排查的时间。

6. 性能实测与优化经验

6.1 读写速度实测数据

我在 16MHz SPI 时钟下做了实测,数据如下:

操作数据量耗时等效速率
单字节读1 字节约 3.2us—
单字节写1 字节约 5.6us—
页读256 字节约 132us1.94 MB/s
页写256 字节约 140us1.83 MB/s

单字节操作的开销主要在指令和地址的传输上,实际数据只占一小部分。页操作效率高得多,因为指令和地址只发一次。所以批量操作是提升吞吐量的关键。

按页写 1.83MB/s 算,写满 480KB 数据区需要约 262ms。这个速度对工业数据记录来说完全够用,即使每秒写 100 条记录(每条 32 字节),也只需要 1.75ms,CPU 占用率很低。

6.2 降低 CPU 占用的优化手段

虽然 SPI 传输本身是硬件完成的,但等待传输标志的循环会占用 CPU。我的优化手段是用中断方式做 SPI 传输,传输完成触发中断,CPU 在等待期间可以处理其他任务。

具体做法是配置 SPI 接收中断,在中断服务程序里处理数据。发送时把数据写入 SSP1BUF,然后返回,中断触发后再发下一个字节。这样 CPU 不用空转等待,效率高很多。

不过中断方式会增加代码复杂度,如果系统对实时性要求不高,轮询方式也够用。我的经验是:数据量小用轮询,数据量大用中断。页写入 256 字节,用中断方式能把 CPU 占用从 30% 降到 5% 以下。

6.3 长期运行稳定性验证

工业设备要长期运行,稳定性验证不能少。我做了两组测试:一是连续写入测试,让设备连续写 72 小时,检查是否有写入失败或数据错误;二是掉电测试,随机断电 100 次,检查数据完整性。

连续写入测试中,我每写 100 万次做一次全区域校验,72 小时写了约 1.8 亿次,没有出现一次写入失败。掉电测试中,100 次随机断电,数据丢失率为 0,只有 2 次因为掉电时机恰好在指针更新过程中,导致最后一条记录被标记为无效,但之前的数据都完好。

这两组测试让我对这套方案有了信心。后来这批设备在现场跑了两年多,没有出现过存储相关的故障。

提示:长期运行测试一定要在高温环境下也做一遍。我在 70°C 恒温箱里跑了 500 小时,发现 MRAM 的写入电流会随温度升高略微增加,电源设计要留够余量。常温下没问题的电源,高温下可能就不够了。

6.4 后续扩展方向

这套方案目前满足了我的需求,但还有扩展空间。一是增加文件系统层,把 MRAM 模拟成 FAT 文件系统,方便数据导出到 U 盘;二是加入数据压缩,用简单的 RLE 或差分编码,提高存储利用率;三是双 MRAM 冗余,关键数据同时写两颗 MRAM,进一步提高可靠性。

文件系统层我试过 FatFs,但 PIC18 的 RAM 有限,跑 FatFs 有点吃力。后来改用简化的自定义格式,用固定的记录结构加索引表,导出时通过串口上传到上位机解析。这个方案更轻量,适合 8 位单片机。

双 MRAM 冗余成本较高,一般场景用不上。但如果做安全相关的设备,比如医疗或交通领域,冗余存储是值得的。两颗 MRAM 用同一个 SPI 总线,用不同的 CS 片选,写入时同时写两颗,读取时比对,不一致就报错。

这套 MRAM 加 PIC18 的存储方案,我从选型到量产花了大概三个月,中间踩了不少坑,但最终的效果让我很满意。工业现场的数据记录,稳定可靠是第一位的,MRAM 的写入无延迟和长寿命特性,正好切中了这个需求。如果你也在做类似的项目,希望这些经验能帮你少走弯路。

返回列表