ARTICLE DETAIL

资讯详情

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

工业级MRAM存储方案:MR25H40CDF与PIC18F46K20 SPI驱动实战

工业级MRAM存储方案:MR25H40CDF与PIC18F46K20 SPI驱动实战 1. 为什么在工业现场我会优先考虑 MRAM 而不是 EEPROM做嵌入式这行十几年存储方案选型这件事踩过的坑比写过的驱动还多。早些年做工业数据采集终端EEPROM 和 FRAM 轮着用EEPROM 写入慢、擦写寿命有限高频记录场景下不到半年就出现坏块FRAM 速度快、寿命长但容量小、价格高而且很多型号供货周期飘忽不定。后来接触到MR25H40CDF这颗 4Mbit 的 SPI MRAM才算找到一个在工业现场真正能扛的方案。MR25H40CDF 是 Everspin 家的磁性随机存储器SPI 接口512K 字节容量工作电压 2.7V 到 3.6V工业级温度范围 -40℃ 到 85℃。它最吸引我的地方有三个第一写入没有延迟不像 EEPROM 那样写完还要等内部擦写周期MRAM 是真正的写进去就完事第二擦写寿命近乎无限官方标称 10^14 次以上实际项目里你根本写不到那个量级第三掉电数据不丢这点和 Flash、EEPROM 一样但它的写入机制决定了它不会像 Flash 那样需要先擦后写。而PIC18F46K20这颗 8 位 MCU是 Microchip 经典的中端产品线64KB Flash、3936 字节 RAM、1KB EEPROM自带 MSSP 模块可以配置成 SPI 主模式。它的 SPI 时钟最高可以跑到 Fosc/4在 16MHz 晶振下就是 4MHz配合 MR25H40CDF 的 40MHz 最高 SPI 时钟绰绰有余。选它俩搭配核心逻辑就是MCU 负责采集和逻辑控制MRAM 负责高频、高可靠的数据落地两者通过 SPI 总线通信硬件上只需要四根线SCK、SDI、SDO、CS软件上就是标准的 SPI 读写时序。这套组合适合谁如果你在做工业仪表、电力监测、车载记录仪、医疗设备日志存储这类需要频繁写入、掉电不能丢数据、现场环境恶劣的项目那这套方案值得认真考虑。如果你只是做个消费类小玩具偶尔存个配置参数那 EEPROM 或者 Flash 就够了没必要上 MRAM 增加成本。下面我把这套方案从硬件设计到软件驱动再到实测踩坑完整地拆一遍。2. MR25H40CDF 的硬件连接与 PIC18F46K20 的 SPI 配置2.1 硬件连线四线 SPI 加上必要的去耦MR25H40CDF 的封装是 8 脚 SOIC 或者 DFN引脚定义很标准VDD、VSS、SCK、SI数据输入、SO数据输出、CS片选、WP写保护、HOLD保持。在 PIC18F46K20 这边MSSP 模块的引脚是固定的RC3 是 SCKRC4 是 SDI对应 MRAM 的 SORC5 是 SDO对应 MRAM 的 SI。CS 我一般用 RC2 或者任意一个普通 IO 来控制因为 PIC18F46K20 的硬件 SPI 不强制要求片选由硬件管理软件拉片选反而更灵活。连线的时候有几个细节必须注意。第一MRAM 的 VDD 和 VSS 之间一定要放一个 0.1uF 的陶瓷去耦电容位置尽量靠近芯片引脚这个电容不是可选项是必须项。我见过太多因为省这个电容导致 SPI 通信偶发失败的案例尤其是工业现场电源纹波大的时候。第二SCK、SI、SO 这三根线如果走线超过 10cm建议串联 22Ω 到 33Ω 的电阻做阻抗匹配抑制反射。第三CS 线不要和 SCK 走得太近避免耦合干扰导致误触发。WP 和 HOLD 引脚如果不用直接拉到 VDD 就行。WP 拉低会禁止写入HOLD 拉低会暂停通信这两个功能在调试阶段可以留着量产时直接拉高省事。我一般会在 WP 上留一个跳线或者测试点方便调试时临时锁死写入防止程序跑飞把数据写乱。2.2 PIC18F46K20 的 MSSP 初始化寄存器配置逐条说清楚PIC18F46K20 的 SPI 配置涉及 SSPCON1、SSPCON2、SSPSTAT、SSPADD 这几个寄存器。我直接给出一段实测可用的初始化代码然后逐条解释为什么这么配。void SPI_Init(void) { TRISC3 0; // SCK 输出 TRISC5 0; // SDO 输出 TRISC4 1; // SDI 输入 TRISC2 0; // CS 输出 CS 1; // 片选默认拉高 SSPCON1 0x20; // SSPEN1, CKP0, SSPM0000 (Fosc/4) SSPCON2 0x00; // 不使用 SS 模式 SSPSTAT 0x40; // CKE0, SMP0 (数据在时钟中间采样) SSPADD 0; // 主模式下此寄存器不用 }SSPCON1 0x20这一句bit5 是 SSPEN置 1 使能 MSSP 模块bit4 是 CKP置 0 表示空闲时 SCK 为低电平低四位 SSPM 设为 0000表示 SPI 主模式时钟为 Fosc/4。在 16MHz 晶振下SCK 频率就是 4MHzMR25H40CDF 完全能跟上。SSPSTAT 0x40这一句bit6 是 CKE置 0 表示数据在 SCK 从空闲到有效电平的跳变时发送bit7 是 SMP置 0 表示在数据输出时间的中间采样。这两个位的组合决定了 SPI 模式MR25H40CDF 支持模式 0 和模式 3我这里配的是模式 0CPOL0CPHA0实测最稳。注意PIC18F46K20 的 SPI 发送和接收是同一个寄存器 SSPBUF写进去就发送读出来就是接收。发送和接收是同时进行的所以读数据的时候要发一个 dummy 字节来产生时钟。2.3 片选控制与读写时序的配合MR25H40CDF 的 SPI 操作有个关键点CS 拉低之后必须先发命令字节然后才能发地址和数据。命令字节的高位决定了是读还是写。读命令是 0x03写命令是 0x02写状态寄存器是 0x01读状态寄存器是 0x05。地址是 24 位因为 512KB 容量需要 19 位地址但 MRAM 按 24 位地址传输高 5 位忽略。CS 的拉低和拉高时机很讲究。拉低之后不能立刻发时钟要等至少 5ns 的建立时间发完最后一个字节之后CS 拉高之前要确保最后一个时钟周期已经完成。我在实际项目里遇到过因为 CS 拉高太快导致最后一个字节没写进去的情况后来在 CS 拉高之前加了一个 NOP 延时就好了。void MRAM_WriteByte(unsigned long addr, unsigned char data) { CS 0; SPI_Write(0x02); // 写命令 SPI_Write((addr 16) 0xFF); SPI_Write((addr 8) 0xFF); SPI_Write(addr 0xFF); SPI_Write(data); CS 1; __delay_us(1); // 确保写入完成 }这段代码里__delay_us(1)不是必须的因为 MRAM 写入没有内部擦写周期但加上它可以让时序更从容尤其是在 SCK 频率较高的时候。实测在 4MHz SCK 下不加延时也能正常工作但工业现场我建议留着不差这一微秒。3. 从单字节到页写入MRAM 读写驱动的完整实现3.1 单字节读写最基础但最容易出错的环节单字节读写是驱动的基础但恰恰是这里最容易埋坑。MR25H40CDF 的读操作是CS 拉低发 0x03发 24 位地址然后连续读数据每读一个字节 SCK 产生 8 个时钟地址自动递增。写操作类似发 0x02发地址然后连续写数据地址同样自动递增。读操作的实现unsigned char MRAM_ReadByte(unsigned long addr) { unsigned char data; CS 0; SPI_Write(0x03); SPI_Write((addr 16) 0xFF); SPI_Write((addr 8) 0xFF); SPI_Write(addr 0xFF); data SPI_Write(0x00); // 发 dummy 字节同时读回数据 CS 1; return data; }这里SPI_Write(0x00)的返回值就是 MRAM 输出的数据。PIC18F46K20 的 SPI 是全双工的发送 dummy 字节的同时SSPBUF 里会被填入接收到的数据。这个机制和 STM32 的 HAL_SPI_TransmitReceive 是一个道理但 PIC 这边更底层需要自己处理。写操作前面已经给过了这里补充一个细节MRAM 的写入是即时生效的不需要等待。但如果你在写完之后立刻读同一个地址需要确保 CS 已经拉高并且至少经过一个时钟周期。我在调试时遇到过写完立刻读读到旧数据的情况后来在读写之间加了 10us 延时就好了。这不是 MRAM 的问题是 SPI 时序配合的问题。3.2 页写入提升吞吐量的关键优化单字节写入虽然简单但每次都要发命令和地址开销太大。MR25H40CDF 支持页写入一页 256 字节。页写入的机制是发写命令和起始地址然后连续写最多 256 字节地址在页内自动递增跨页时会回绕到页首。注意是回绕不是自动进到下一页。这个特性很容易踩坑。void MRAM_WritePage(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; unsigned int page_offset addr 0xFF; if (page_offset len 256) { len 256 - page_offset; // 截断到页边界 } CS 0; SPI_Write(0x02); SPI_Write((addr 16) 0xFF); SPI_Write((addr 8) 0xFF); SPI_Write(addr 0xFF); for (i 0; i len; i) { SPI_Write(buf[i]); } CS 1; __delay_us(1); }这段代码里我做了页边界检查如果起始地址在页中间写入长度会被截断到页尾。调用者需要自己处理跨页的情况循环调用这个函数。我一般会封装一个MRAM_WriteBuffer函数内部自动处理跨页。提示页写入的 256 字节限制是硬件决定的不是软件可以绕过的。如果你一次写超过 256 字节且不处理跨页数据会从页首开始覆盖这个坑我在早期项目里踩过丢了一整页的校准数据。3.3 状态寄存器与写保护什么时候需要动它MR25H40CDF 的状态寄存器只有两个有效位WEL写使能锁存和 BP0、BP1块保护。WEL 位在每次写操作前由硬件自动置位写完自动清零一般不需要手动干预。BP0 和 BP1 用来保护特定地址区域不被写入这个功能在工业现场很有用。比如你的系统里有一部分区域存的是出厂校准参数运行过程中绝对不允许被改写。你可以在初始化时把 BP 位设成保护上半区或者下半区这样即使程序跑飞误发写命令那片区域也不会被改写。解除保护需要先发写状态寄存器命令再发新的状态值。void MRAM_WriteStatus(unsigned char status) { CS 0; SPI_Write(0x01); // 写状态寄存器命令 SPI_Write(status); CS 1; __delay_us(1); } unsigned char MRAM_ReadStatus(void) { unsigned char status; CS 0; SPI_Write(0x05); // 读状态寄存器命令 status SPI_Write(0x00); CS 1; return status; }实测下来状态寄存器的写入需要的时间比数据写入稍长建议写完状态后延时 1ms 再读回来验证。我在一个电力监测项目里用这个功能保护了电表常数和校准系数运行两年多没有出现过数据被意外改写的情况。4. 工业现场实测那些文档里不会写的坑4.1 电源纹波导致的偶发通信失败这个问题折磨了我整整两周。现象是设备在实验室跑得好好的一到现场就偶尔出现 SPI 读回全 0xFF 或者全 0x00 的情况重启就好过几天又出现。一开始怀疑是 MRAM 坏了换了几颗新的还是一样。后来用示波器抓电源纹波发现现场 24V 转 3.3V 的 DC-DC 在负载突变时会有 200mV 左右的尖峰正好落在 MRAM 的供电引脚上。解决方案是在 MRAM 的 VDD 引脚旁边加了一个 10uF 的钽电容并联 0.1uF 陶瓷电容同时在 3.3V 电源入口加了一个 TVS 管。改完之后跑了三个月再没出现过通信失败。这个经验告诉我工业现场的电源质量远比实验室恶劣去耦电容不能省TVS 也不能省。4.2 SPI 时钟极性配置错误导致的读写异常MR25H40CDF 支持 SPI 模式 0 和模式 3。模式 0 是 CPOL0、CPHA0模式 3 是 CPOL1、CPHA1。如果你配成了模式 1 或模式 2读出来的数据会错位写进去的数据也会错位。我见过一个同事调了一整天最后发现是 SSPSTAT 的 CKE 位配反了。判断方法很简单写一个已知数据到某个地址然后读回来对比。如果读回来的数据是写入数据左移或右移了一位那基本就是时钟极性或者相位配错了。PIC18F46K20 的 SSPSTAT 寄存器里CKE 和 SMP 的组合决定了采样时刻具体对应关系我整理了一个表CKESMPSPI 模式采样时刻00模式 0时钟中间01模式 1时钟末尾10模式 2时钟中间11模式 3时钟末尾MR25H40CDF 用模式 0 或模式 3 都行我习惯用模式 0配置简单实测最稳。4.3 长走线下的信号完整性问题有一个项目MRAM 和 MCU 分别放在两块板子上通过排线连接排线长度大概 15cm。实验室测试没问题现场跑起来偶尔出现写进去的数据读出来不对。用示波器看 SCK 信号发现上升沿有明显的振铃幅度大概 1V 左右。解决方法是在 SCK、SI、SO 三根线上各串联一个 33Ω 电阻靠近 MCU 端放置。同时在 MRAM 端对地加 10pF 的小电容。改完之后振铃基本消失通信稳定。这个经验说明SPI 虽然简单但走线长了照样出问题尤其是 SCK 这种高频信号。4.4 写入速度的实测数据与优化空间我用逻辑分析仪抓过实际波形在 4MHz SCK 下单字节写入含命令和地址耗时约 4.5us页写入 256 字节耗时约 520us。换算下来单字节写入的吞吐率约 220KB/s页写入约 490KB/s。这个速度对于工业数据记录来说完全够用但如果你需要更高的吞吐率可以把 SCK 提到 10MHzMR25H40CDF 支持到 40MHzPIC18F46K20 在 40MHz 晶振下可以跑到 10MHz SCK。不过要注意SCK 提高之后走线阻抗匹配和去耦电容的要求更高。我在一个高速采集项目里把 SCK 提到 10MHz结果发现误码率上升后来把串联电阻从 33Ω 降到 22Ω同时把去耦电容从 0.1uF 换成 0.01uF 并联 0.1uF问题才解决。所以速度不是越高越好要配合硬件设计来调。5. 数据可靠性设计从写入校验到掉电保护5.1 写入后回读校验简单但有效的手段MRAM 的写入可靠性很高但在工业现场我仍然建议每次关键数据写入后做一次回读校验。做法很简单写完之后立刻读回同一个地址对比数据是否一致。如果不一致重写一次最多重试三次。这个机制在我做的电力监测项目里拦截过好几次因为电源波动导致的写入异常。unsigned char MRAM_WriteVerify(unsigned long addr, unsigned char data) { unsigned char retry; for (retry 0; retry 3; retry) { MRAM_WriteByte(addr, data); if (MRAM_ReadByte(addr) data) { return 0; // 成功 } } return 1; // 失败 }这段代码看起来简单但在现场跑起来很管用。注意重试之间最好加一点延时给电源和信号一个恢复的时间。5.2 掉电检测与数据落盘时机工业现场掉电是常态关键数据必须在掉电前落盘。PIC18F46K20 本身没有掉电检测模块但可以用 ADC 监测电源电压或者用外部电压监测芯片。我一般用 ADC 监测 3.3V 电源当电压低于 3.0V 时触发中断在中断里把关键数据写入 MRAM。MRAM 的写入速度很快单字节 4.5us写 100 字节也就 450us。只要电源电容够大完全来得及。我在电源入口放了 1000uF 的电解电容掉电后还能维持 10ms 左右的供电足够写完关键数据。注意掉电中断里不要做复杂运算只做最必要的数据保存。中断服务程序越短越好避免在电压下降过程中执行不确定的代码。5.3 数据分区与磨损均衡的取舍MRAM 的擦写寿命是 10^14 次这个量级下其实不需要做磨损均衡。但如果你有高频写入的区域比如每秒写一次计数器一年也就 3150 万次离 10^14 还差得远。所以我的做法是关键数据固定地址存储不做均衡日志数据用环形缓冲区写满一圈覆盖旧数据。这样既简单又可靠。环形缓冲区的实现要点是维护一个写指针每次写入后指针递增到末尾回绕到开头。同时维护一个读指针用于读取历史数据。两个指针都存储在 MRAM 的固定位置掉电后重新上电可以恢复。6. 这套方案还能怎么扩展如果你已经跑通了 MR25H40CDF 和 PIC18F46K20 的基本读写接下来可以考虑几个扩展方向。第一把 SPI 总线复用挂多个 MRAM 芯片用不同的 CS 引脚选择容量可以线性扩展。第二把数据加上 CRC 校验每次读取时验证数据完整性这个在工业现场很有必要。第三把 MRAM 作为数据缓存定期把数据批量转移到 SD 卡或者通过通信接口上传形成两级存储架构。我在一个车载记录仪项目里就是这么做的MRAM 负责高频写入每秒记录一次车辆状态MCU 每 10 秒把 MRAM 里的数据批量写入 SD 卡。这样既保证了高频写入的可靠性又利用 SD 卡的大容量做长期存储。整套系统跑了两年多没有出现过数据丢失。最后分享一个调试小技巧如果你怀疑 MRAM 通信有问题先别急着改代码用逻辑分析仪抓一下 SPI 波形看看命令字节、地址字节、数据字节的时序对不对。大部分问题都能从波形上直接看出来比盲猜代码快得多。
返回列表