拓冰建站拓冰建站
首页 / 资讯中心 / 正文

工业电机控制器数据存储:从SPI NOR Flash到MRAM的实践

做过好几年工业电机控制器我对数据怎么在强电磁干扰、频繁掉电、连续高频写入的环境里保存下来这事算是花过不少冤枉钱才整明白。以前的项目里单片机用的是NXP的MKV58F1M0VLQ24Cortex-M7内核跑电机FOC和伺服算法都挺能打但一说到运行日志和故障录像的存储就有点尴尬。最初我用的是SPI NOR FlashW25Q64之类的刚开始觉得便宜又熟悉结果故障录波功能一开Flash的擦写寿命和擦除等待时间就成了两座大山。后来我把存储方案换成Everspin的MR25H40CDF一颗4Mbit的SPI接口MRAM算是把这口堵上了。这篇就把这个方案的选型思路、硬件接线、驱动代码、掉电保护设计以及我实测中踩过的几个坑完整过一遍。如果你是做嵌入式、工业控制、伺服驱动器或者任何需要对数据掉电保存又不想折腾Flash磨损均衡的场合这篇应该能给你省不少事。1. 存储芯片选型为什么把SPI NOR Flash换成了MRAM1.1 故障录波一上来Flash的寿命短板就暴露了先说我们当时最头疼的场景。电机控制器需要做故障黑匣子也就是每次过流、过压、编码器报错的时候把故障发生前和发生后各几百个采样点的三相电流、母线电压、转速等数据记录下来。这些数据一次大概2KB左右采样周期1kHz一天反复触发几十次故障测试再加上平时要写运行日志写入频率比普通设备高得多。SPI NOR Flash的物理规则相信大家都清楚要写数据得先擦成0xFF一个扇区擦除通常要几十毫秒而且擦写寿命普遍在10万次左右。按故障录波和日志一天写入几千次来算三个月到半年就逼近寿命上限了。更麻烦的是故障录像要求掉电瞬间的那一批数据也必须落盘这时候NOR Flash还在慢吞吞地擦扇区黄花菜都凉了。于是我开始研究替代方案。1.2 MR25H40CDF的核心优势不需要擦除而且怎么写都不怕MR25H40CDF是Everspin的SPI接口MRAM容量4Mbit也就是512KB。名字拆开看MR代表磁阻存储25是SPI系列H40代表4Mbit容量等级C是3.3V供电DF对应特定的封装和温度等级。它最核心的存储单元是磁隧道结用磁化方向来记录数据断电之后磁化方向保持不动所以天然非易失。对我来说MRAM最香的地方有三个不需要擦除。写入之前不用先把旧数据擦成0xFF想改哪个字节直接改这一点和SRAM一样顺滑。无限次写入。数据手册上写的是几乎无限的读写耐久性不用再像伺候NOR Flash一样做磨损均衡。写入速度快。SPI时钟从几十K到几十M都能工作字节寻址没有擦除等待时间非常适合高频小数据落盘。这些特性放在工业应用里杀手锏效应非常明显。尤其故障录波这种平时不怎么写一旦出问题必须马上把一批数据写进去的场景MRAM几乎就是量身定做。1.3 和EEPROM、FRAM放在一起比一比很多人会问EEPROM不也是字节可写非易失吗FRAM不也是快吗这里我把三者拉出来对比一下方便你选型时心里有数。特性EEPROMFRAMMRAMMR25H40CDF写前擦除不需要不需要不需要写入寿命约100万次约1e12次写入后读会额外耗损几乎无限单次写入速度毫秒级需等待总线速度级别总线速度级别容量通常很小Kbit级常见几十至几百KB4Mbit / 512KB原理浮栅电荷铁电极化磁隧道结EEPROM容量小、写速度慢当日志存储根本不够用。FRAM速度快但容量和货源是个问题而且部分FRAM的读操作也会增加疲劳。MRAM在容量、寿命、速度这三者之间最均衡。当然MRAM单价确实比普通Flash贵后面我会聊这个成本账到底怎么算。2. MKV58F1M0VLQ24这边怎么把SPI接口接出来2.1 引脚分配与上下拉处理MKV58F1M0VLQ24是Kinetis KV58系列的一员Cortex-M7内核最高能跑到240MHz内置1MB Flash和256KB SRAM外设资源很丰富工业温度等级也够。它内部叫DSPI的串行外设接口有好几路我这里用的是DSPI0。实际项目里我这颗MRAM是这么接的KV58引脚复用功能连接对象PTD0SPI0_PCS0MR25H40CDF的CSPTD1SPI0_SCKMR25H40CDF的SCKPTD2SPI0_SOUTMR25H40CDF的SI数据输入PTD3SPI0_SINMR25H40CDF的SO数据输出注意具体引脚分配要以MKV58F1M0VLQ24数据手册里的引脚复用表为准不同封装和外设实例对应关系略有差别。PCB布线前最好把引脚号、ALT功能一行行核对一遍我见过不止一次把SOUT和SIN画反的板子画反了基本就是读不到数据。MR25H40CDF的SPI输入输出和常见的Flash一样主机SOUT接从机的SI主机的SIN接从机的SO。两个方向千万不能搞错否则写进去的数据读出来全乱。另外MRAM的/WP和/HOLD这两个控制引脚不允许悬空。我直接把这两个引脚各用一个10kΩ电阻上拉到3.3V这样默认不写保护、不进入HOLD状态程序里可以少操很多心。如果后续不想让程序误改关键配置区可以把/WP引脚下拉到GND配合状态寄存器的保护位一起用。2.2 电源、去耦与抗干扰布局MRAM和KV58都是3.3V供电电平匹配没有额外问题。我比较注意的几点是在MRAM的VDD引脚旁边放0.1uF陶瓷电容再靠近一点放1uF电容形成两级去耦。不要只放一个1uF就完事高频噪声需要小电容先吃掉。SPI四根线尽量短远离IGBT驱动、母线功率环路这些大电流走线。电机控制板上最不缺的就是强干扰源。如果SPI走线必须穿过干扰区可以在SCK和SOUT上串33Ω电阻和分布电容组成低通滤波能压掉不少尖峰。实测对10MHz时钟信号的边沿影响可控。KV58是Cortex-M7跑起来功耗不低MRAM静态功耗很小但两者供电共用时还是要注意MCU大电流瞬态对MRAM电源的拉扯。有条件的话给MRAM的VDD加一个几欧姆的磁珠隔离能明显提升稳定性。我最初版本没串磁珠电机堵转时电源纹波大MRAM偶发读回CRC错误后来加了磁珠就稳了很多。这个问题后面还会展开说。3. MR25H40CDF的指令集和读写驱动实现3.1 指令集概览和NOR Flash神似关键差异点要记牢MR25H40CDF的命令集对用过SPI NOR Flash的人非常友好基本就是那几条命令操作码说明WREN0x06设置写使能锁存WELWRDI0x04清除写使能锁存RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据READ和WRITE命令都是先发操作码再发3个字节地址然后按顺序传输数据字节。MR25H40容量是512KB实际只需要19位地址3字节地址字段的高5位必须清零否则芯片行为不受控这是最容易翻车的一个细节。状态寄存器里最重要的是WEL位写操作前必须先发WREN把WEL置1否则WRITE命令会被忽略。这个行为和NOR Flash一模一样写使能本身需要单独一次片选事务不能把WREN和WRITE合并到同一次CS低电平里去。然后是最关键的差异NOR Flash写数据之前必须先擦除MRAM不需要。你在地址0x1000写一个字节不管原来里面是什么写完就是新值。NOR Flash有页编程概念一个页一般是256字节写一页要等几毫秒MRAM的WRITE命令传多少字节都可以CS拉高的那一刻数据就已经写进去了不需要等待擦除或编程完成。因此驱动里基本看不到等待状态寄存器忙位这种代码结构简单一大截。3.2 驱动代码读写函数完整实现下面是一套简化但可用的驱动代码我基于寄存器方式写方便你移植到自己的SPI抽象层。片选拉低、拉高这两个函数需要你自己映射到底层GPIO或者DSPI的自动CS功能。先定义命令和基本传输原语#define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRDI 0x04 #define MRAM_CMD_RDSR 0x05 #define MRAM_CMD_WRSR 0x01 #define MRAM_CMD_READ 0x03 #define MRAM_CMD_WRITE 0x02 /* 片选操作需要接入实际硬件 */ static void mram_cs_low(void) { /* GPIO或者DSPI CS 拉低 */ } static void mram_cs_high(void) { /* GPIO或者DSPI CS 拉高 */ } /* 底层发送并接收一个字节需要接入实际SPI驱动 */ static uint8_t mram_spi_xfer(uint8_t tx) { return 0; }写使能和读状态寄存器static void mram_write_enable(void) { uint8_t st; mram_cs_low(); mram_spi_xfer(MRAM_CMD_WREN); mram_cs_high(); /* 可选校验防止偶发写使能失败 */ mram_cs_low(); mram_spi_xfer(MRAM_CMD_RDSR); st mram_spi_xfer(0x00); mram_cs_high(); if (!(st 0x01)) { /* WEL没有被置位说明异常可以在这里打印或重试 */ } } static uint8_t mram_read_status(void) { uint8_t st; mram_cs_low(); mram_spi_xfer(MRAM_CMD_RDSR); st mram_spi_xfer(0x00); mram_cs_high(); return st; }读数据重点是先发命令和3字节地址然后连续读void mram_read(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t i; mram_cs_low(); mram_spi_xfer(MRAM_CMD_READ); mram_spi_xfer((uint8_t)((addr 16) 0xFF)); mram_spi_xfer((uint8_t)((addr 8) 0xFF)); mram_spi_xfer((uint8_t)(addr 0xFF)); for (i 0; i len; i) { buf[i] mram_spi_xfer(0x00); } mram_cs_high(); }写数据先写使能然后发WRITE命令和地址后续直接灌数据void mram_write(uint32_t addr, const uint8_t *buf, uint32_t len) { uint32_t i; mram_write_enable(); mram_cs_low(); mram_spi_xfer(MRAM_CMD_WRITE); mram_spi_xfer((uint8_t)((addr 16) 0xFF)); mram_spi_xfer((uint8_t)((addr 8) 0xFF)); mram_spi_xfer((uint8_t)(addr 0xFF)); for (i 0; i len; i) { mram_spi_xfer(buf[i]); } mram_cs_high(); }这里的mram_spi_xfer只是占位实际工程里你要把它替换成KV58 DSPI的收发函数。如果用Kinetis SDK可以换成DSPI_MasterTransfer函数配合DMA效率更高。3.3 用DMA和FIFO把SPI占用CPU降到最低KV58的DSPI自带发送FIFO和接收FIFO深度都不错还支持DMA请求。我在做故障录波时需要一次性把几KB数据从SRAM搬到MRAM如果靠CPU逐字节搬运10MHz下拷2KB数据大概要2毫秒这期间CPU被大量占用触发其他中断任务容易抖动。我的建议是连续大块读写直接上DMA。KV58的DMADMAMUXeDMA配置起来不算复杂把DSPI的TX请求和RX请求分别映射到DMA通道主机只管提供时钟输出。写MRAM时如果需要边发边收可以只用TX DMA接收数据直接丢弃读MRAM时则要用双缓冲把从SI送进去的0x00和从SO收出来的数据都搬走。打开DMA之后CPU占用可以降到接近零这对寸土寸金的实时控制应用来说价值很大。我实际项目里故障触发后的2000字节写入CPU只花了一个DMA配置的几十个周期就结束了剩下的交给DMA移位计数。4. 把数据落盘做成一个有掉电保护的服务4.1 存储空间规划参数区、日志区、黑匣子区512KB空间不算大但合理分区后完全够用。我一般这么规划区域地址范围用途写入频率参数区0x000000 - 0x00FFFF设备配置、标定参数、校准系数很低日志区0x010000 - 0x03FFFF运行事件日志、状态记录高黑匣子区0x040000 - 0x07FFFF故障前后采样数据触发时一次性写入参数区保存PID参数、电流环标定、通信地址这类配置写入频率低但要求绝对可靠。我会单独把每份参数做成头部带标识符和CRC的结构每份固定长度更新时整份覆盖写。日志区用循环覆盖的方式。每条日志固定16字节第一字段是递增序号第二字段是时间戳最后是CRC16。启动时扫描全区的序号找到序号最大的那条之后从下一条开始写。写满512KB后回到区头覆盖最老的日志。因为MRAM不需要擦除这个循环队列实现起来极简单连擦写计数都不用维护。黑匣子区我按槽位设计每个槽4KB本轮故障写槽0下一轮写槽1交替覆盖。这样即使当前槽因为掉电只写了一半上一轮完整槽位的数据还在分析历史故障时不会全军覆没。4.2 掉电保存LVD中断 余电窗口 无擦除写入工业设备掉电从来不会跟你打招呼缺相、接触器跳闸、母线下坠随时可能发生。我要求在掉电后的最后一个供电周期里把当前电机状态和最近一次故障数据保存下来。MKV58内置低电压检测LVD模块可以配置电压下降沿触发中断。我把LVD阈值设在3.0V左右主供电正常是3.3V一旦低于阈值立刻进掉电中断。这时候靠板上几个大电容和母线余电MCU还能再撑几毫秒到几十毫秒这个时间窗口就是我们救数据的时间。以前用NOR Flash时这个窗口完全不够用要先找到日志槽位置再擦一个扇区再写数据整个过程可能去到了几十毫秒还不稳定。换MRAM后简单太多掉电中断里直接禁止其他中断把SRAM里暂存的最近一批日志和关键参数调用mram_write往黑匣子槽里一灌写256字节在10MHz SPI下只需要零点几个毫秒几十毫秒窗口绰绰有余。我建议掉电处理任务越短越好不要在里面做复杂运算。提前把待写数据放在固定SRAM缓冲区掉电中断只做一件事搬数据。4.3 数据一致性校验与恢复策略MRAM写坏的概率极低但外界强干扰、SPI时序被打乱仍然可能造成某条记录不完整。因此我的所有数据块尾部都带CRC读取时先校验。启动恢复逻辑是扫描日志区比较相邻条目的序号。正常情况序号严格加1如果发现序号跳变说明中间有丢失。校验每条日志的CRC。CRC错误的那条视为未完成写入停止往前恢复把该条空间作废。参数区用双份镜像存储一份失效时读取另一份两份都失效时回厂家默认。黑匣子区每个槽有槽头魔法数和长度读取时只认槽头正确的槽。这套逻辑原本是给Flash设计的老套路但MRAM下不需要管擦写寿命和磨损均衡实现反而轻松。重点是MRAM的字节覆写能力让部分更新变得非常安全我可以先写数据主体最后再写CRC从而天然形成事务边界。如果是NOR Flash想更新一个字节还得擦整个扇区事务性处理会费劲得多。5. 实测数据和调试中踩过的几个坑5.1 性能实测10MHz SPI下的读写速度我在KV58跑10MHz SPI时钟MR25H40CDF本身支持更高保守起见我选10MHz走线更稳实测数据如下操作耗时单字节读约0.5us单字节写含WREN寻址数据约1us256字节连续读约0.3ms256字节连续写含WREN约0.35ms4KB块连续写DMA约4.2ms和NOR Flash比同样写256字节Flash需要先擦扇区视扇区大小和工艺20ms到上百ms不等再页编程约1msMRAM在时间上是碾压级的。和EEPROM比速度优势也明显而且容量大了几百倍。对我来说这个性能已经完全够用。5.2 坑一CPOL/CPHA配置翻车读出来全是0xFF第一次调MRAM驱动上电后发现读取全是0xFF写了也没反应。查了半天发现是KV58 DSPI的SPI模式配置问题。MR25H40CDF支持SPI模式0和模式3即CPOL/CPHA都是0或者都是1。这个本身没问题。但KV58的DSPI初始化里需要配置时钟极性和相位还要注意Kinetis手册里关于采样沿的描述和一般MCU不太一样很容易把采样沿理解反。我一开始配成了模式1结果时钟边沿对不上从机完全收不到有效数据。解决方式用逻辑分析仪抓SCK、SI、SO的实际波形确认空闲电平、有效采样边沿是否符合预期。别再凭感觉猜。我通常是先把DSPI配成模式0用示波器看SCK空闲是否在低电平再看数据在哪个边沿变化测试完成后固定下来。5.3 坑二地址高5位没清掉数据越界错乱我的日志区地址从0x0001000开始一开始写小地址测试一切正常。后来想测试高地址区域传了0x40000进去结果数据高字节带了非零的高5位MRAM实际解析出来的地址完全错乱读到的是别的区域。原因是MR25H40只有19位有效地址而我用的uint32_t地址变量在高5位上存在残余数据。解决办法很直接在读写函数入口强制掩码只取低19位addr 0x7FFFF; /* 512KB范围取19位地址 */这个掩码必须加在每一个读写命令地址字段之前防止有人传入错误地址。我建议写成一个小函数统一处理三段地址字节的拆分也方便后续换容量更大的MRAM时统一调整。5.4 坑三强干扰导致读CRC错误得从布线和校验上找补这是最折磨人的一个坑。板子在实验室一切正常一进客户产线接上大功率伺服电机跑一会儿就冒出几条CRC错误日志。首先确认不是MRAM数据本身被改写——因为出错的是随机读操作而不是历史数据篡改。原因锁定为SPI数据线受到电磁干扰导致主控读到的内容翻转。排查过程我做了三件事把MRAM供电加磁珠隔离避免母线噪声往VDD里串。在SCK、SOUT、SIN、CS四条线上串33Ω电阻压低沿速率。读操作增加软件重试机制像日志这类高频读的内容连续读两次比较不一致则再读第三次。这套组合下去CRC错误基本消失。我的体会是工业板上跑SPI时软件重试比单纯提高硬件抗干扰能力更便宜、见效更快但两者缺一不可。MRAM本身写入抗干扰能力是强的但读数据这个动作对线上噪声敏感所以别把干扰防护只盯着供电信号线上的去耦也很关键。写在最后的话如果单看元器件单价MRAM比普通SPI NOR Flash贵一些这是事实。但我算了一笔账省掉Flash的磨损均衡算法、省掉扇区擦除等待、省掉掉电保护时要多预留的缓冲时间这些省下来的开发成本和故障率完全覆盖了那点购价差。尤其我们做的是故障录波这种对数据可靠性近乎苛刻的应用MRAM的无限写入和不需擦除让我睡得踏实很多。如果你也在纠结嵌入式产品里该怎么存数据我的建议是把MRAM放进备选池里实际跑一轮测试尤其注意读写速度、掉电窗口、CRC校验这几个点。只有真正在台架上测过之后你才知道它到底值不值。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门