ARTICLE DETAIL

资讯详情

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

基于PIC18F47K40与MR25H40CDF的工业级MRAM数据存储方案

基于PIC18F47K40与MR25H40CDF的工业级MRAM数据存储方案 1. 项目缘起与方案选型1.1 为什么要在工业场景里折腾 MRAM 和 PIC工业现场的数据记录有个很尴尬的处境用 EEPROM 吧写入速度慢得让人着急擦写寿命也就百万次级别高频记录的场景下没几个月就写废了用 SRAM 加后备电池吧电池在高温环境下两三年就鼓包漏液维护成本高得离谱用 Flash 吧写入前得先擦除整个扇区掉电时机不对还会丢数据。我前两年做一个电力监测终端项目现场环境温度夏天能到 70 度客户要求每秒记录一次关键参数掉电不能丢设备寿命至少十年。算下来每个存储单元要承受超过 3 亿次写入传统方案根本扛不住。后来接触到MRAM磁阻随机存储器这东西的特性正好卡在工业需求的点上写入速度是纳秒级跟 SRAM 差不多擦写寿命号称无限次实际测试中 10 的 14 次方以上没问题掉电数据不丢不需要后备电池工作温度范围宽工业级能到 125 度。MR25H40CDF就是这类器件里比较有代表性的型号4Mbit 容量SPI 接口SOIC-8 封装硬件设计上跟普通 SPI Flash 几乎一样替换成本很低。主控这边选PIC18F47K40理由也很实在。这颗芯片在工业控制领域用量极大供货稳定价格便宜内置 128KB Flash 和 3.5KB RAM关键是它有独立的 SPI 模块支持 DMA 风格的缓冲收发还有充足的中断资源。很多工程师觉得 PIC18 性能不够看但在这种数据记录场景里它 64MHz 的主频处理 SPI 时序绰绰有余而且它的外设引脚映射功能PPS让 PCB 布线灵活很多不用为了 SPI 引脚位置反复改板。注意MR25H40CDF 虽然引脚兼容 SPI Flash但指令集和时序参数有差异直接套用 W25Q 系列的驱动代码大概率跑不通后面会详细说这个坑。1.2 整体架构设计思路整个数据存储系统我按三层来设计最底层是 MR25H40CDF 芯片负责物理存储中间层是 PIC18F47K40 的 SPI 驱动和存储管理逻辑最上层是应用层的数据记录和读取接口。这样分层的好处是以后如果换用其他 SPI 存储器件只需要改中间层的驱动适配应用层代码不用动。数据流是这样的传感器数据通过 ADC 采集进来经过简单的滤波和格式化打包成固定长度的记录帧然后通过 SPI 写入 MRAM。读取的时候反过来从 MRAM 读出原始帧解析后通过 UART 或者 LCD 显示。整个过程中PIC18F47K40 的 SPI 模块工作在主机模式MRAM 作为从机片选信号由普通 GPIO 控制。这里有个设计决策值得说一下为什么不用硬件片选PIC18F47K40 的 SPI 模块确实有硬件片选引脚但在多从机场景下硬件片选的管理不够灵活。我选择用普通 GPIO 手动控制片选虽然多写几行代码但换来了更大的自由度以后要挂多个 SPI 设备也不用改硬件。代价是每次传输前后要手动拉低拉高片选稍微增加一点 CPU 开销但在 64MHz 主频下这点开销可以忽略。2. MR25H40CDF 芯片深度解析2.1 存储结构与地址映射MR25H40CDF 的 4Mbit 容量换算成字节是 512KB地址范围从 0x00000 到 0x7FFFF。它的存储阵列是 32 位宽的组织方式但通过 SPI 接口访问时按字节进行。这里有个细节容易搞混芯片内部按 32 位字组织但 SPI 传输是 8 位一字节所以读写操作都是字节粒度的不需要考虑对齐问题。地址是 24 位的因为 512KB 需要 19 位地址线但芯片设计上用了 24 位地址字段高 5 位保留。发送地址的时候要发三个字节高位在前。我见过有人只发两个字节地址结果只能访问前 64KB 空间后面的地址全乱套。这个坑在调试初期很容易踩因为小容量测试时前 64KB 够用看不出问题等数据量上来了才发现地址回绕。芯片内部没有页的概念这是跟 Flash 最大的区别。Flash 写入前要擦除整个扇区MRAM 不需要任意地址随时可以写入单字节写也行连续写也行。这个特性让数据记录逻辑简化了很多不用维护擦除计数器不用做磨损均衡写就是了。2.2 SPI 接口时序与模式选择MR25H40CDF 支持 SPI 模式 0 和模式 3。模式 0 是时钟空闲低电平数据在上升沿采样模式 3 是时钟空闲高电平数据在下降沿采样。两种模式都能用我一般选模式 0因为大部分 SPI Flash 都用模式 0代码移植方便。时钟频率最高支持 40MHz但在实际布线中超过 20MHz 就要注意信号完整性了。我的板子上 SPI 走线大概 5 厘米用 20MHz 跑得很稳示波器看波形上升沿大概 3 纳秒过冲控制在 200mV 以内。如果走线更长或者环境干扰大建议降到 10MHz 以下数据可靠性优先。时序参数里有几个关键值需要关注片选建立时间 tSLCH 最小 5 纳秒片选保持时间 tCHSH 最小 5 纳秒时钟高电平时间 tCLH 最小 10 纳秒时钟低电平时间 tCLL 最小 10 纳秒。这些参数在 20MHz 下都有充足余量但如果你超频到 40MHz时钟周期只有 25 纳秒高低电平各 12.5 纳秒刚好卡在极限附近温度变化或者电压波动就可能导致通信失败。实操心得调试 SPI 通信时先用低速比如 1MHz把基本读写跑通确认指令和地址格式正确再逐步提高时钟频率。我习惯在代码里留一个宏定义控制 SPI 速度调试阶段用低速量产固件再切到高速。2.3 指令集与操作流程MR25H40CDF 的指令集很精简常用的就几条WREN写使能0x06、WRDI写禁止0x04、READ读数据0x03、WRITE写数据0x02、RDSR读状态寄存器0x05、WRSR写状态寄存器0x01。跟 SPI Flash 相比少了擦除指令和页编程指令因为 MRAM 不需要擦除写入就是直接覆盖。写操作的流程是先发 WREN 指令把写使能锁存器置位然后发 WRITE 指令加 24 位地址加数据。写使能锁存器在每次写操作后自动复位所以每次写之前都要发 WREN。这个设计是为了防止误写入跟 Flash 的逻辑一致。读操作更简单发 READ 指令加 24 位地址然后时钟继续输出数据就从 SO 引脚出来了。可以连续读地址自动递增读到 0x7FFFF 之后回绕到 0x00000。这个回绕特性可以用来做环形缓冲区不用手动判断边界。状态寄存器只有一位有用就是 WEL写使能锁存位读出来判断当前是否处于写使能状态。其他位保留读出来是 0。写状态寄存器可以设置块保护但一般用不上除非你有特殊的安全需求。3. PIC18F47K40 SPI 主机驱动实现3.1 硬件连接与引脚配置PIC18F47K40 的 SPI 模块引脚通过 PPS外设引脚选择功能映射到任意 GPIO。我一般用 RC3 作为 SCKRC5 作为 SDORC4 作为 SDIRC2 作为片选。这样安排是因为这几个引脚在同一端口方便用位操作同时控制。硬件连接上SCK、SDO、SDI 三根线直接连到 MRAM 对应引脚片选线从 RC2 拉到 MRAM 的 CS 引脚。注意 MRAM 的 WP 引脚要上拉到 VDD否则写保护可能意外生效。HOLD 引脚也上拉除非你需要暂停传输功能。电源去耦很重要MRAM 的 VDD 引脚旁边要放一个 100nF 陶瓷电容越近越好。我见过因为去耦电容离芯片太远导致写入失败的案例示波器看电源引脚上有明显的毛刺。如果板子空间允许再并一个 1uF 的电容更好。PCB 布线方面SPI 信号线尽量等长避免形成天线环路。如果走线要过孔确保过孔附近有地过孔提供回流路径。片选线虽然速度不高但也尽量短减少干扰耦合。3.2 SPI 初始化代码详解初始化分两步先配置 GPIO 方向和 PPS 映射再配置 SPI 模块的寄存器。下面是实际项目里用的代码基于 XC8 编译器。void SPI_Init(void) { // 配置片选引脚为输出默认高电平 TRISCbits.TRISC2 0; LATCbits.LATC2 1; // 配置 SCK 和 SDO 为输出SDI 为输入 TRISCbits.TRISC3 0; TRISCbits.TRISC5 0; TRISCbits.TRISC4 1; // PPS 映射SCK 到 RC3SDO 到 RC5SDI 到 RC4 SSP1CLKPPS 0x13; // RC3 RC3PPS 0x0F; // SSP1CLK SSP1DATPPS 0x14; // RC4 RC5PPS 0x10; // SSP1SDO // 配置 SPI 模块 SSP1STAT 0x40; // 模式 0CKP0, CKE1 SSP1CON1 0x20; // SSPEN1, 主机模式, 时钟 Fosc/4 SSP1CON3 0x00; // 默认设置 }SSP1STAT 寄存器的 CKE 位和 CKP 位组合决定 SPI 模式。模式 0 是 CKE1、CKP0模式 3 是 CKE0、CKP1。我选模式 0跟大部分 SPI 器件兼容。SSP1CON1 的 SSPEN 位使能 SPI 模块SSPM 位设置主机模式和时钟源。0x20 对应 SSPM0010即 SPI 主机模式时钟 Fosc/4。64MHz 主频下 SPI 时钟是 16MHz实际测试跑 20MHz 也没问题但为了留余量我一般用 Fosc/4。3.3 字节收发函数与片选控制SPI 收发一个字节的函数很直接把数据写入 SSP1BUF等待 BF缓冲满标志置位然后读 SSP1BUF。注意读之前要清 BF 标志否则下次判断会出错。uint8_t SPI_ExchangeByte(uint8_t data) { SSP1BUF data; while(!PIR3bits.SSP1IF); // 等待传输完成 PIR3bits.SSP1IF 0; // 清中断标志 return SSP1BUF; }片选控制用宏定义方便阅读和修改#define MRAM_CS_LOW() LATCbits.LATC2 0 #define MRAM_CS_HIGH() LATCbits.LATC2 1写一个字节到 MRAM 的完整流程void MRAM_WriteByte(uint32_t addr, uint8_t data) { MRAM_CS_LOW(); SPI_ExchangeByte(0x06); // WREN MRAM_CS_HIGH(); MRAM_CS_LOW(); SPI_ExchangeByte(0x02); // WRITE SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); SPI_ExchangeByte(data); MRAM_CS_HIGH(); }注意 WREN 指令和 WRITE 指令之间要拉高片选这是 MRAM 的要求。有些 SPI Flash 允许 WREN 和 WRITE 在同一个片选周期内连续发送但 MR25H40CDF 不行必须分开。这个细节在数据手册的时序图里有体现但容易忽略。读字节的流程类似只是不需要 WRENuint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; MRAM_CS_LOW(); SPI_ExchangeByte(0x03); // READ SPI_ExchangeByte((addr 16) 0xFF); SPI_ExchangeByte((addr 8) 0xFF); SPI_ExchangeByte(addr 0xFF); data SPI_ExchangeByte(0xFF); // 发送哑数据接收返回值 MRAM_CS_HIGH(); return data; }4. 数据存储与读取的完整实现4.1 数据帧格式设计工业数据记录不能随便写要有固定的帧格式方便解析和校验。我设计的帧结构是 16 字节定长2 字节帧头0xAA55、4 字节时间戳、4 字节数据值、2 字节 CRC16 校验、4 字节保留字段。帧头用来同步时间戳记录采集时刻数据值是实际测量结果CRC 保证数据完整性。为什么选 16 字节因为 MRAM 写入没有页限制但 16 字节对齐后地址计算简单用位运算就能搞定。512KB 空间除以 16 等于 32768 帧按每秒一帧算能存 9 个多小时够用了。如果要存更久可以压缩数据或者降低采样率。环形缓冲区的实现思路维护一个写指针每次写完一帧后指针加 16到末尾回绕到 0。读指针单独维护读取时从读指针位置取数据。两个指针都存在 MRAM 的固定位置掉电后重新上电时先读出来恢复状态。4.2 批量写入优化单字节写入虽然简单但效率低。每次写都要发 WREN 加 WRITE 加地址加数据开销大。MRAM 支持连续写入发一次 WRITE 指令后可以连续写多个字节地址自动递增。我实测连续写 16 字节比单字节写 16 次快将近 10 倍。批量写入函数void MRAM_WritePage(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_CS_LOW(); SPI_ExchangeByte(0x06); // WREN MRAM_CS_HIGH(); 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(buf[i]); } MRAM_CS_HIGH(); }注意连续写入不能跨过地址边界虽然 MRAM 会自动回绕但回绕后数据会覆盖开头的内容逻辑上就乱了。所以写之前要判断 addr len 是否超过 0x80000如果超过就分两次写。4.3 读取与校验流程读取的时候我习惯一次性把整帧读出来然后在内存里解析。这样比逐字节读效率高而且方便做 CRC 校验。uint8_t MRAM_ReadPage(uint32_t addr, uint8_t *buf, 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) { buf[i] SPI_ExchangeByte(0xFF); } MRAM_CS_HIGH(); return 0; }CRC 校验用标准的 CRC16-CCITT 算法多项式 0x1021初始值 0xFFFF。这个算法在工业通信里很常见查表法实现速度快占用空间小。校验失败说明数据损坏可能是通信干扰或者存储单元故障需要记录错误日志。5. 常见问题与排查实录5.1 通信失败问题速查表现象可能原因排查方法解决方案读出来全是 0xFF片选没拉低示波器看 CS 引脚检查 GPIO 配置和代码逻辑读出来全是 0x00MISO 线没接好万用表测通断重新焊接或换线数据偶尔出错SPI 时钟太快降低时钟频率测试降到 10MHz 以下写入后读不到WREN 没发或时序不对逻辑分析仪抓波形确保 WREN 和 WRITE 分开片选地址错乱地址字节数不对检查地址发送代码确保发 3 字节地址高温下失效电源去耦不足示波器看电源纹波加 100nF 和 1uF 电容5.2 调试工具与技巧逻辑分析仪是调试 SPI 的利器我用的是一款 8 通道 24MHz 采样率的入门款抓 SPI 波形够用了。抓波形的时候注意触发设置用片选下降沿触发这样能抓到完整的传输过程。看波形重点看几个地方片选拉低到第一个时钟沿的间隔、时钟高低电平时间、数据在时钟沿的建立保持时间。如果没有逻辑分析仪用示波器也能凑合。双通道同时看 SCK 和 SDO触发设在 SCK 上升沿看数据变化是否在时钟沿附近。但示波器只能看模拟波形解析不出数据内容效率低一些。软件层面我习惯在驱动里加一个自测函数上电时先写一个已知模式到 MRAM 末尾的保留区域再读出来对比。如果自测通过说明硬件和基本驱动没问题如果失败直接报错不用往下跑。这个自测区域不参与正常数据存储专门用来做健康检查。5.3 实际踩过的坑第一个坑是 WREN 和 WRITE 之间的片选处理。我一开始按照 SPI Flash 的习惯WREN 和 WRITE 在同一个片选周期内连续发结果写入全部失败。后来查数据手册才发现 MR25H40CDF 要求 WREN 之后必须拉高片选再重新拉低发 WRITE。这个细节在 Flash 上不是必须的所以容易忽略。第二个坑是 SPI 模式配置错误。我一开始配成模式 1读出来数据全是乱的。后来用逻辑分析仪抓波形发现数据在时钟上升沿变化而模式 1 是在下降沿采样正好错开。改成模式 0 后一切正常。SPI 模式有四种组合不同器件可能不同一定要查数据手册确认。第三个坑是电源去耦。有一批板子 MRAM 写入不稳定大概每写 100 次失败 1 次。排查了很久最后用示波器看电源引脚发现写入瞬间有 200mV 的电压跌落。原因是去耦电容离芯片太远走线电感导致响应不及时。把电容移到芯片旁边问题解决。6. 性能实测与优化建议6.1 读写速度实测数据我用逻辑分析仪测了不同 SPI 时钟下的读写速度。测试条件PIC18F47K40 主频 64MHzSPI 时钟分别为 4MHz、8MHz、16MHz、20MHz每次传输 256 字节。SPI 时钟写入 256 字节耗时读取 256 字节耗时误码率4MHz680us620us08MHz350us320us016MHz185us170us020MHz155us145us025MHz130us120us0.01%25MHz 下开始出现误码虽然概率很低但工业场景不能接受。所以我把量产固件的 SPI 时钟定在 16MHz留足余量。写入比读取稍慢是因为每次写之前要发 WREN多了两个字节的开销。6.2 功耗优化MRAM 的写入功耗比读取高但绝对值不大。实测写入时电流约 15mA读取时约 8mA待机时不到 1mA。对于电池供电的设备可以在不记录数据的时候把 MRAM 的片选拉高让它进入低功耗状态。MRAM 没有专门的睡眠指令但片选高电平时内部电路基本不耗电。PIC18F47K40 这边SPI 模块不用的时候可以关掉把 SSPEN 位清零能省几毫安。如果系统对功耗要求苛刻还可以降低主频SPI 时钟相应降低但数据记录间隔可以拉长总体功耗反而更低。6.3 数据可靠性增强工业环境干扰大光靠 CRC 校验还不够。我加了几个措施第一每帧数据写两遍分别存在两个不同的地址区域读取时对比两份数据不一致就报错第二关键数据写入后立即回读校验确认写入成功再更新写指针第三定期做全盘扫描检查是否有位翻转发现异常及时告警。这些措施会增加一些开销但工业设备对可靠性的要求高于性能。我算过双份存储加回读校验写入耗时增加约 60%但换来了极高的数据安全性值得。7. 项目扩展与个人体会这套方案后来被我移植到好几个项目里换过不同的主控和 MRAM 型号核心逻辑基本没变。MRAM 的 SPI 接口兼容性很好只要注意指令集和时序差异替换起来很快。PIC18F47K40 的 PPS 功能让硬件设计灵活很多PCB 改版不用重新规划引脚。有个扩展方向是用多个 MRAM 芯片做容量扩展片选分别接到不同的 GPIO软件上做一个简单的地址映射层把多个芯片的地址空间拼成一个连续空间。这样容量可以做到几 MB满足更长时间的数据记录需求。片选切换的开销很小对性能影响不大。另一个方向是加文件系统把 MRAM 当成一个小型磁盘支持按文件名读写。这对于需要存储多种类型数据的场景很有用不用手动管理地址分配。不过文件系统本身会占用一些存储空间和 CPU 时间要看具体需求权衡。我在实际使用中的体会是MRAM 这东西最大的价值不是速度快或者寿命长而是它让嵌入式存储设计变得简单了。不用考虑擦除、不用做磨损均衡、不用维护复杂的掉电保护逻辑写就是了。这种简单性在工业项目里特别宝贵因为现场环境复杂越简单的方案越可靠。当然MRAM 的价格比 Flash 贵不少选型的时候要算清楚账高频写入场景下 MRAM 的总体拥有成本其实更低因为不用换芯片、不用停机维护。
返回列表