ARTICLE DETAIL

资讯详情

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

MRAM与PIC18LF46K40工业数据存储方案:SPI驱动与掉电保护实战

MRAM与PIC18LF46K40工业数据存储方案:SPI驱动与掉电保护实战 1. 为什么偏偏是 MRAM 加 PIC18 这个组合第一次拿到 MR25H40CDF 和 PIC18LF46K40 这两颗料的时候我脑子里冒出来的第一个念头是这搭配有点意思。一颗是 Everspin 家的 4Mb 串行 MRAM另一颗是 Microchip 的 PIC18 系列低功耗 8 位单片机一个主打非易失、近乎无限次擦写、写入不用等一个主打皮实、便宜、外设够用。把它们凑在一起做工业现场的数据存储和读取不是随便抓两颗料拼一起而是有很明确的工程逻辑在背后。先说 MR25H40CDF 到底是个什么东西。很多人第一次听到 MRAM 会以为是某种新型 Flash其实它的存储机理完全不一样。MRAM 全称是 Magnetoresistive Random Access Memory磁阻式随机存储器靠的是磁性隧道结MTJ的磁化方向来存 0 和 1而不是靠电荷。这个区别带来的直接好处就是写入速度是纳秒级的不需要像 Flash 那样先擦后写也不存在擦写寿命只有十万次这种焦虑。MR25H40CDF 这颗是 4Mb 容量也就是 512KBSPI 接口40MHz 时钟工业级温度范围读写寿命标称 10^14 次以上数据保持 20 年以上。这几个参数放在工业场景里基本就是你随便写写坏了算我输的级别。再说 PIC18LF46K40。这是 Microchip 在 2016 年前后推出的 PIC18 K40 系列里的一员64 引脚64KB Flash3.5KB RAM1KB EEPROM自带 MSSP 模块可以跑 SPI 和 I2C工作电压 1.8V 到 5.5V低功耗版本 LF 后缀意味着它能在 3.3V 甚至更低电压下稳定跑。为什么选它因为在工业嵌入式里很多时候你不需要 Cortex-M4 那种算力你需要的是稳定、外设够、开发工具成熟、供货周期长、代码可维护。PIC18 这套架构虽然老但 Microchip 的 XC8 编译器、MCCMPLAB Code Configurator图形化配置工具、Curiosity 开发板生态都很完整一个中等水平的嵌入式工程师两天就能把 SPI 读写 MRAM 的框架搭起来。所以这个组合的核心价值就一句话用一颗几乎不会写坏的存储器配一颗皮实到可以跑十年的单片机解决工业现场数据必须可靠落盘、掉电不能丢、频繁写入不能坏的问题。典型场景包括电力监测终端的事件记录、工业仪表的校准参数和累计量存储、医疗设备的使用日志、汽车电子里的故障码保存等等。这些场景的共同点是写入频率可能很高比如每秒记录一次状态但每次写入的数据量很小几十字节而且绝对不能因为掉电或者写入次数过多导致数据丢失。如果你正在做类似的项目或者手头正好有这两颗料不知道怎么用起来下面我会把整个设计思路、SPI 通信细节、读写时序、代码框架、踩过的坑全部摊开讲。不管你是刚入行的嵌入式新人还是做了几年想换个存储方案的老人应该都能从里面找到能直接抄作业的东西。2. 整体方案设计与选型背后的取舍2.1 为什么不用 Flash 或者 EEPROM这是我在做方案评审时被问得最多的问题。每次我说要用 MRAM总有人问Flash 便宜那么多EEPROM 也够用为什么要花几倍的钱上 MRAM答案藏在写入模式里。Flash 的物理特性决定了它必须先擦除再写入而且擦除的最小单位是扇区通常 4KB写入的最小单位是页通常 256 字节。这意味着如果你要改一个字节实际上要把整个扇区读到 RAM 里改完再擦掉整个扇区再把 4KB 写回去。这个过程不仅慢毫秒级而且每次擦除都在消耗寿命。NOR Flash 的擦写寿命通常是 10 万次NAND 更低。工业场景里如果每秒写一次10 万次就是 27 小时一天多就写废了。EEPROM 好一些字节级可写寿命通常 100 万次但写入速度慢典型字节写入时间 5ms 左右而且容量小、价格也不便宜。PIC18LF46K40 自带的 1KB EEPROM 如果用来存校准参数是够的但要做高频事件记录就捉襟见肘了。MRAM 的写入是真正的随机访问写入不需要擦除字节级可写写入时间纳秒级寿命 10^14 次。你算一下就算每秒写 1000 次10^14 次也能写 3000 多年。这就是为什么在需要频繁写入、掉电不能丢、数据量又不大的场景里MRAM 是唯一合理的选择。贵是贵但省下来的系统复杂度和可靠性风险值这个钱。2.2 SPI 接口的选型考量MR25H40CDF 只提供 SPI 接口没有并口版本。这个选择其实很聪明SPI 只需要 4 根线CS、SCK、MOSI、MISO加上电源和地一共 6 根PCB 布线简单引脚占用少而且 40MHz 的时钟在短距离板级通信里完全够用。4Mb 的容量用 SPI 读理论最大吞吐 5MB/s实际因为协议开销大概 2-3MB/s对于工业数据记录来说绰绰有余。PIC18LF46K40 的 MSSP 模块支持 SPI 主模式和从模式我们当然用主模式。这里有个细节要注意PIC18 的 SPI 时钟源是系统时钟分频得到的所以 SPI 速率取决于你的系统时钟配置。如果你用 64MHz 的系统时钟PIC18 K40 系列可以通过 PLL 倍频到 64MHzSPI 分频系数最小是 4那 SPI 时钟就是 16MHz。MR25H40CDF 支持 40MHz所以 16MHz 完全在范围内不需要额外降速。2.3 硬件连接与电平匹配PIC18LF46K40 是低功耗版本工作电压范围 1.8V 到 5.5V。MR25H40CDF 的工作电压是 2.7V 到 3.6V。所以如果你把 PIC18 跑在 3.3V两者可以直接连不需要电平转换。如果你把 PIC18 跑在 5V那就需要电平转换芯片或者至少加限流电阻加分压但我不推荐这么做因为 5V 系统下 SPI 高速通信容易出问题而且 MRAM 本身也不支持 5V。实际连接就 6 根线PIC18LF46K40 引脚MR25H40CDF 引脚说明RC3 (SCK)SCKSPI 时钟RC5 (SDO)SI主出从入RC4 (SDI)SO主入从出RA5 (GPIO)CS片选软件控制3.3VVDD电源GNDVSS地这里有个坑PIC18 的 MSSP 模块默认的 SDO 是 RC5SDI 是 RC4SCK 是 RC3但具体引脚要看你的封装和配置。64 引脚的 PIC18LF46K40 有多个引脚可以映射到 MSSP用 MCC 配置的时候要确认清楚。另外 CS 我建议用普通 GPIO 而不是 MSSP 的硬件 CS因为 MRAM 的 CS 需要在每个命令前后精确控制硬件 CS 在某些模式下会自动拉高拉低反而不好控制。注意MR25H40CDF 的 CS 必须在每个 SPI 事务开始时拉低事务结束后拉高。如果你连续读多个字节CS 可以一直保持低电平但一旦拉高就必须等至少 10ns 才能再次拉低。这个时间在 16MHz SPI 下完全不是问题但如果你用更快的时钟就要注意时序余量。3. MR25H40CDF 的读写机制与命令集详解3.1 内部存储结构与地址空间MR25H40CDF 是 4Mb 容量也就是 512KB地址范围从 0x00000 到 0x7FFFF需要 19 位地址。SPI 协议里地址是按字节传输的所以每次读写命令后面要跟 3 个字节的地址24 位高 5 位忽略。这一点和 24 系列 EEPROM 类似但比 EEPROM 快得多。内部存储阵列是真正的随机访问没有页、扇区的概念你可以从任意地址开始读写任意长度的数据。写的时候不需要考虑对齐读的时候也不需要。这个特性在实现环形缓冲区或者日志结构存储的时候特别方便因为你可以把整个 512KB 当成一个大的线性空间来用不需要做块管理。3.2 核心命令集MR25H40CDF 的命令集很精简常用的就几条命令名称命令码说明WREN0x06写使能写操作前必须发WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据状态寄存器只有一位有用就是 WELWrite Enable Latchbit 1。每次写操作前必须发 WREN 命令把 WEL 置 1写操作完成后 WEL 自动清零。这个机制和 Flash、EEPROM 一样是为了防止误写。读操作很简单拉低 CS发 0x03发 3 字节地址然后就可以连续读数据了地址会自动递增。写操作类似拉低 CS发 0x06WREN拉高 CS再拉低 CS发 0x02发 3 字节地址然后发数据。注意 WREN 和 WRITE 之间必须拉高 CS这是 MRAM 的要求不能连续发。3.3 时序参数与速度计算MR25H40CDF 的关键时序参数SCK 最高频率40MHzCS 建立时间CS 拉低到第一个 SCK 上升沿至少 5nsCS 保持时间最后一个 SCK 下降沿到 CS 拉高至少 5ns数据输出有效时间从 SCK 下降沿起最大 8ns写周期时间0ns没错MRAM 写入不需要等待最后一条是 MRAM 最爽的地方。Flash 写完要等 5ms 才能发下一条命令EEPROM 写完要等 5msMRAM 写完立刻可以发下一条命令不需要轮询状态寄存器不需要延时。这意味着如果你要连续写 1000 个字节MRAM 可以在几微秒内完成而 EEPROM 需要 5 秒。在 PIC18 上如果你把 SPI 时钟配到 16MHz每个字节传输 8 位加上命令和地址开销写一个字节大概需要 (131)*8/16MHz 2.5us。读一个字节类似。这个速度对于工业数据记录来说完全够用甚至可以直接在中断里做存储不用担心阻塞太久。4. PIC18LF46K40 端的 SPI 驱动实现4.1 系统时钟与 SPI 模块初始化PIC18LF46K40 的时钟系统比较灵活可以用内部振荡器HFINTOSC或者外部晶振。工业场景我建议用外部晶振因为内部振荡器的精度虽然够用±2%但温漂比晶振大。如果你用 16MHz 晶振通过 PLL 倍频到 64MHz系统时钟就是 64MHzSPI 分频后是 16MHz。用 MCC 配置的话步骤大概是在 Clock Control 里选外部晶振设置 PLL 使能系统时钟 64MHz在 MSSP 里选 SPI 主模式时钟极性 CPOL0时钟相位 CPHA0模式 0设置 SPI 时钟分频为 4得到 16MHz配置 CS 引脚为普通 GPIO 输出初始高电平如果你不用 MCC直接写寄存器也行// 系统时钟配置假设外部16MHz晶振PLL倍频到64MHz OSCCON1 0x60; // 选择外部晶振 OSCCON2 0x00; OSCFRQ 0x00; // 不分频 // 等待时钟稳定 while(!OSCCON3bits.ORDY); // SPI 初始化 TRISCbits.TRISC3 0; // SCK 输出 TRISCbits.TRISC4 1; // SDI 输入 TRISCbits.TRISC5 0; // SDO 输出 TRISAbits.TRISA5 0; // CS 输出 LATAbits.LATA5 1; // CS 初始高 SSP1CON1 0x20; // SPI 主模式时钟 Fosc/4 SSP1STAT 0x00; // 模式 0数据在时钟上升沿采样 SSP1ADD 0x00; // 不用这里有个细节PIC18 的 SPI 发送和接收是同一个寄存器 SSP1BUF。你写数据进去硬件自动开始发送同时接收数据也进这个寄存器。所以发送和接收必须成对操作不能只发不收或者只收不发。4.2 底层字节收发函数uint8_t SPI_ExchangeByte(uint8_t data) { SSP1BUF data; while(!PIR3bits.SSP1IF); // 等待传输完成 PIR3bits.SSP1IF 0; // 清标志 return SSP1BUF; }这个函数是 SPI 驱动的核心所有上层操作都基于它。注意 PIC18 的 SPI 标志位在 PIR3 寄存器里不是 PIR1这个和很多老 PIC18 不一样查手册的时候要看清楚。4.3 MRAM 读写函数实现基于上面的字节收发函数封装 MRAM 的读写#define MRAM_CS_LOW() LATAbits.LATA5 0 #define MRAM_CS_HIGH() LATAbits.LATA5 1 void MRAM_WriteEnable(void) { MRAM_CS_LOW(); SPI_ExchangeByte(0x06); // WREN MRAM_CS_HIGH(); } void MRAM_Write(uint32_t addr, uint8_t *data, uint16_t len) { MRAM_WriteEnable(); MRAM_CS_LOW(); SPI_ExchangeByte(0x02); // WRITE SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); for(uint16_t i 0; i len; i) { SPI_ExchangeByte(data[i]); } MRAM_CS_HIGH(); } void MRAM_Read(uint32_t addr, uint8_t *data, uint16_t len) { MRAM_CS_LOW(); SPI_ExchangeByte(0x03); // READ SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); for(uint16_t i 0; i len; i) { data[i] SPI_ExchangeByte(0x00); } MRAM_CS_HIGH(); }这段代码可以直接用但有几个地方要注意。第一地址是 24 位的虽然 MR25H40CDF 只有 19 位地址但高 5 位必须发 0不能省略。第二写操作前必须发 WREN而且 WREN 和 WRITE 之间要拉高 CS不能连续发。第三读操作时发 0x00 是为了产生时钟MOSI 上的数据被忽略。4.4 状态寄存器读取与写保护虽然 MRAM 写入不需要等待但读一下状态寄存器确认 WEL 位是好的习惯uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_LOW(); SPI_ExchangeByte(0x05); // RDSR status SPI_ExchangeByte(0x00); MRAM_CS_HIGH(); return status; }状态寄存器的 bit 1 是 WELbit 0 是 WIPWrite In Progress。MRAM 的 WIP 永远是 0因为写入是即时的。如果你读到 WIP 是 1那说明芯片可能有问题。5. 工业场景下的数据存储架构设计5.1 环形缓冲区与日志结构工业现场最常见的数据存储需求是记录最近 N 条事件新的覆盖旧的。这个需求用环形缓冲区最合适。MRAM 的 512KB 空间可以分成两部分前 4KB 存元数据写指针、读指针、记录数、校验和后面 508KB 存实际数据。每条记录的结构可以这样设计typedef struct { uint32_t timestamp; // 时间戳秒 uint16_t event_id; // 事件类型 uint8_t data[26]; // 事件数据 uint16_t crc; // CRC16 校验 } LogRecord; // 共 34 字节508KB 除以 34 字节约等于 15000 条记录。写指针每次写完一条就前进 34 字节到了末尾就回到起始位置。读的时候从读指针开始读读到写指针为止。这个设计的核心是写指针和读指针的原子更新。因为 MRAM 写入是即时的你可以在写完数据后立刻更新写指针不需要担心掉电导致数据写了一半指针没更新。但为了保险我建议在每条记录里加 CRC读的时候校验如果 CRC 不对就跳过这条记录。5.2 掉电保护与数据完整性MRAM 本身掉电不丢数据但系统掉电的瞬间可能正在写数据。如果写到一半掉电这条记录就是坏的。解决办法有两个一是每条记录加 CRC读的时候校验二是用双缓冲区写 A 区的时候 B 区保留上一份完整数据写完 A 区再更新标志位。我一般用第一种方案因为简单可靠。CRC16 的计算在 PIC18 上大概需要几十微秒对于每秒写一次的场景完全不是负担。如果你对实时性要求极高可以用硬件 CRC 模块但 PIC18LF46K40 没有硬件 CRC只能用软件算。5.3 参数存储与磨损均衡除了日志工业设备还需要存校准参数、配置参数这些读多写少的数据。这些数据我建议单独划一个区域比如最后 4KB用固定的地址存。因为 MRAM 不存在磨损问题所以不需要做磨损均衡直接覆盖写就行。但要注意参数区最好加一个有效标志和 CRC。每次写参数的时候先写数据再写 CRC最后写有效标志。读的时候先检查有效标志再校验 CRC都通过了才用。这样即使写参数过程中掉电也不会用到半截参数。6. 实操中踩过的坑与排查技巧6.1 SPI 通信不稳定的常见原因我调试这套方案的时候遇到过几个典型问题列出来供参考现象可能原因解决方法读出来全是 0xFFCS 没拉低或者 MISO 没接检查 CS 引脚配置和接线读出来全是 0x00MOSI 没接或者时钟没输出用示波器看 SCK 和 MOSI数据偶尔错位SPI 模式不对CPOL/CPHA确认 MRAM 是模式 0写入后读出来不对WREN 没发或者 CS 时序不对检查 WREN 和 WRITE 之间 CS 是否拉高高速时出错线太长或者没有匹配电阻降低 SPI 时钟加 33 欧姆串联电阻6.2 电源与去耦MRAM 对电源噪声比较敏感尤其是写入的时候。我建议在 MR25H40CDF 的 VDD 和 VSS 之间放一个 0.1uF 陶瓷电容越近越好。如果板子上有电机或者继电器最好再加一个 10uF 钽电容。PIC18 这边也要做好去耦每个电源引脚都要有 0.1uF。6.3 初始化顺序与上电时序上电的时候MRAM 和 PIC18 的供电顺序没有严格要求但建议先给 MRAM 供电再给 PIC18 供电或者同时供电。如果 PIC18 先上电在 MRAM 还没准备好之前就发 SPI 命令可能会读到错误数据。解决办法是在初始化代码里加一个延时等 MRAM 上电稳定后再操作。6.4 长线传输与信号完整性如果 MRAM 和 PIC18 不在同一块板子上SPI 线走长了容易出问题。我试过用 20cm 的排线连接16MHz 时钟下误码率明显上升。解决办法是降低 SPI 时钟到 4MHz或者在每根线上串 33 欧姆电阻或者用双绞线加地线隔离。如果距离超过 30cm建议改用差分方案但那就不是 SPI 了。7. 性能实测与方案对比7.1 写入速度实测我用逻辑分析仪抓过实际波形在 16MHz SPI 时钟下写 1 字节含命令和地址约 2.5us写 34 字节记录约 10us读 34 字节记录约 10us连续写 1000 条记录约 10ms这个速度意味着你可以在 1 秒内写 10 万条记录对于工业场景来说完全过剩。实际上瓶颈不在 MRAM而在 PIC18 的处理能力和你的业务逻辑。7.2 与 Flash、EEPROM 的对比特性MR25H40CDFNOR FlashEEPROM写入速度纳秒级毫秒级毫秒级擦除需求不需要必须先擦不需要擦写寿命10^1410^510^6字节级写入支持不支持支持容量512KB512KB-16MB1KB-1MB价格高中中接口SPISPII2C/SPI从表里可以清楚看出MRAM 的优势在写入速度和寿命劣势在价格和容量。如果你的场景是频繁写小数据MRAM 是唯一选择如果是偶尔写大数据Flash 更划算。7.3 功耗考量MR25H40CDF 的写入电流典型值 15mA待机电流 100uA 左右。PIC18LF46K40 在 64MHz 下运行电流约 10mA休眠电流可以低到 1uA 以下。如果你的设备是电池供电可以在不写数据的时候把 MRAM 的 CS 拉高让它进入待机模式需要写的时候再唤醒。整个系统的平均功耗可以做到毫安级用一节 18650 电池能跑几个月。8. 代码框架与工程组织建议8.1 分层设计我建议把代码分成三层底层驱动层SPI 字节收发、MRAM 读写函数、CS 控制中间层环形缓冲区管理、记录读写、CRC 校验应用层业务逻辑调用中间层接口这样分层的好处是如果以后要换存储器比如换成 FRAM 或者 Flash只需要改底层驱动中间层和应用层不用动。8.2 关键数据结构typedef struct { uint32_t write_ptr; // 写指针 uint32_t read_ptr; // 读指针 uint32_t record_count; // 记录总数 uint16_t magic; // 魔数用于判断是否初始化过 uint16_t crc; // 元数据 CRC } MRAM_Meta;元数据存在 MRAM 的起始地址每次系统启动时先读元数据检查 magic 和 CRC如果不对就初始化。8.3 初始化流程void MRAM_Init(void) { // 1. 初始化 SPI 模块 SPI_Init(); // 2. 延时等待 MRAM 上电稳定 __delay_ms(10); // 3. 读元数据 MRAM_Meta meta; MRAM_Read(0, (uint8_t*)meta, sizeof(meta)); // 4. 检查 magic 和 CRC if(meta.magic ! 0x4D52 || !CheckCRC(meta)) { // 元数据无效初始化 meta.write_ptr sizeof(MRAM_Meta); meta.read_ptr sizeof(MRAM_Meta); meta.record_count 0; meta.magic 0x4D52; meta.crc CalcCRC(meta); MRAM_Write(0, (uint8_t*)meta, sizeof(meta)); } }这个初始化流程很关键它保证了即使第一次上电或者元数据损坏系统也能自动恢复到一个可用的状态。9. 扩展思路与后续优化方向这套方案跑通之后我还试过几个扩展方向有些效果不错有些踩了坑一并分享。第一个扩展是加文件系统。如果你需要在 MRAM 上存不同类型的文件可以移植一个轻量级文件系统比如 LittleFS 或者 SPIFFS。但这两个文件系统原本是为 Flash 设计的有擦除块的概念移植到 MRAM 上需要改底层驱动把擦除操作变成空操作。我试过移植 LittleFS能用但代码量增加了不少对于简单的数据记录场景有点杀鸡用牛刀。第二个扩展是双 MRAM 冗余。如果对可靠性要求极高可以用两颗 MRAM 做镜像写的时候同时写两颗读的时候对比两颗的数据不一致就报错。这个方案成本翻倍但可靠性也翻倍。我做过一个电力监测的项目客户要求数据绝对不能丢就用了双 MRAM 冗余跑了两年没出过问题。第三个扩展是加 RTC 时间戳。工业数据记录通常需要时间戳PIC18LF46K40 本身没有 RTC可以外挂一颗 DS3231 或者 PCF8563通过 I2C 读时间。或者用 PIC18 的 Timer1 加 32.768kHz 晶振做软件 RTC精度差一些但成本低。第四个扩展是低功耗优化。如果设备是电池供电可以在不写数据的时候让 PIC18 进入 Sleep 模式MRAM 进入待机模式用外部中断或者定时器唤醒。我实测过Sleep 模式下整个系统电流可以降到 5uA 以下用一节 CR2032 纽扣电池能跑一年以上。最后说一个我踩过的坑MRAM 的 CS 引脚在上电复位期间的状态不确定如果这时候 PIC18 也在复位可能会误触发一次 SPI 事务。解决办法是在 CS 引脚上加一个 10k 上拉电阻确保复位期间 CS 保持高电平。这个细节在数据手册里没有明确写是我用示波器抓波形才发现的。这套 MR25H40CDF 加 PIC18LF46K40 的方案我从第一次画板子到稳定运行大概花了三周时间其中大部分时间花在调试 SPI 时序和验证掉电保护上。现在回头看最难的不是写代码而是理解 MRAM 和 Flash 在写入机制上的本质区别以及这个区别如何影响你的系统设计。一旦想通了这一点剩下的就是按部就班地实现和测试。如果你也在做类似的项目希望这些经验能帮你少走几天弯路。
返回列表