ARTICLE DETAIL

资讯详情

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

MR25H40CDF与STM32F469II的SPI接口MRAM驱动设计与掉电保护实现

MR25H40CDF与STM32F469II的SPI接口MRAM驱动设计与掉电保护实现 如果你搞工业控制或者嵌入式数据存储大概率遇到过这种场景程序里小心翼翼维护着EEPROM的磨损均衡或者眼巴巴等着Flash页擦除的那几百毫秒结果设备一断电关键参数还是丢了。我从去年开始把一批产品里的数据存储芯片换成MR25H40CDF搭配STM32F469II做主控既解决了掉电不丢失的存储要求又满足了工业级温度范围和高速读写的场景。这篇文章把这一套组合从选型、硬件到驱动的完整思路整理出来希望能给正在嵌入式系统里做数据存储的朋友提供一点参考。1. 项目背景与方案选型思考1.1 为什么工业嵌入式场景最终选了MRAM以前做参数存储第一反应都是25系列SPI Flash或者I2C EEPROM毕竟价格便宜、资料多、大家都会用。但真正放到工业设备里这两个东西的毛病其实不少。EEPROM的容量普遍偏小写一次还要等5毫秒左右如果日志记录频繁擦写寿命很容易被耗掉。SPI Flash容量倒是大可是它有页写、扇区擦除这些限制写一个字节往往要先把整个扇区读出来、改完再写回去一旦写了一半掉电数据一致性就很难保证。更麻烦的是Flash擦除本身就有寿命上限工业设备动不动要跑十年八年日志和参数写得勤一点后期坏块和磨损问题会让人非常头疼。MRAM的原理跟前面两个完全不是一个路子。它用磁隧道结存储数据写数据是改变磁性状态而不是存电荷所以不需要擦除、没有页限制、写入速度极快而且理论上可以无限次写入。断电后磁化方向保持不变数据也就自然保住了。实际体验下来它就是一颗像SRAM一样随便写、同时掉电不丢数据、还带SPI接口的芯片这对嵌入式工程师来说实在是太友好了。1.2 MR25H40CDF这颗料好在哪MR25H40CDF是Everspin的4Mb串行MRAM。刚开始选型的时候我其实也犹豫过FRAM毕竟铁电存储也是无磨损方案。但对比下来MR25H40CDF在几个维度上更适合我的项目。首先看容量4Mb换算下来是512KB做配置参数、运行日志、故障记录都够用。如果只是存几个关键变量512KB甚至显得有点奢侈但正因为容量够大我可以直接把日志做成环形缓冲省掉很多存储管理上的麻烦。其次是接口它走标准SPI协议指令集和常见的25系列Flash大部分兼容像WREN、READ、WRITE这些指令都可以直接用。这意味着代码迁移成本很低硬件上随便找一组SPI就能接。加上它支持40MHz左右的SPI时钟数据吞吐远高于EEPROM工业级温度范围对于现场环境来说也足够了。数据手册里标称的数据保持时间很长在常温下可以做到二十年以上这个参数在工业设备招标和项目验收的时候是有说服力的。1.3 STM32F469II的角色与优势主控选STM32F469II说实话不是因为它是市面上最强的而是它的资源对这类存储应用来说非常顺手。STM32F469II的CPU是Cortex-M4F主频180MHz带有FPU和DSP指令跑数据校验、CRC计算、日志解析这些任务绰绰有余。它内部有2MB Flash和384KB SRAM应用程序和运行缓冲区都宽裕不会因为内存不足去压缩存储逻辑。更重要的是外设配置STM32F469II同时提供了多路SPI、DMA控制器、以及带Cache的Cortex-M4内核。SPI1挂在APB2总线上时钟能跑到90MHz分频后的实际SPI频率可以稳定覆盖MRAM的需求而DMA能够把SPI收发完全交给硬件CPU只需要在事务开始和结束的时候介入这在大批量读写日志时非常香。我选的这块芯片本身还带了TFT-LCD控制器和SDRAM接口后续产品如果要加显示界面或者更大的数据缓冲就不用换主控了扩展性比较好。这也是当初没有选F103或者F407的原因不想为了存储方案把未来的人机交互升级空间堵死。2. 硬件连接与初始化细节2.1 最小系统连接方案MR25H40CDF接STM32F469II我用的SPI1因为PA4到PA7这组引脚比较好布线而且SPI1挂在APB2上时钟更高。下面是我的接线参考信号STM32F469II引脚MR25H40CDF信号说明CS#PA4GPIO输出CS#软件片选低电平有效SCKPA5SPI1_SCKSCKSPI时钟MISOPA6SPI1_MISODO芯片数据输出MOSIPA7SPI1_MOSISI芯片数据输入WP#接3.3VWP#硬件写保护拉高禁止保护HOLD#接3.3VHOLD#拉高禁用暂停功能VCC3.3VVDD电源GNDGNDGND地这里要重点提醒一句CS片选一定用普通GPIO软件控制不要用STM32的硬件NSS。原因很简单硬件NSS在多字节连续事务里容易在时序上出幺蛾子尤其是后面要跟DMA配合做大块读写软件GPIO控制CS更可控、更稳。2.2 硬件设计里的三个常见坑第一个坑是HOLD#和WP#悬空。这两个引脚千万不要悬空MR25H40CDF的HOLD#一旦被噪声拉低芯片会暂停当前SPI通信表现就是数据卡死或者读写的字节错位。手一抖直接悬空调试的时候能查到人崩溃。正规做法是把两个引脚都通过10kΩ电阻上拉到3.3V。第二个坑是去耦电容。MRAM写操作本质是改变磁矩虽然芯片内部处理得很好但外部电源突变依然可能影响信号完整性。我在每颗MRAM的VCC脚旁边放了0.1µF陶瓷电容并且在电源入口处并联4.7µF钽电容实测对SPI信号质量和稳定性有明显改善。第三个坑是MCU和MRAM供电不是同一个源。如果STM32F469II的IO电平是3.3V而MRAM供电掉了SPI引脚可能会通过芯片内部二极管倒灌电流轻则逻辑混乱重则损伤器件。所以我的设计里用了同一个3.3V电源域确保上电掉电时序一致。2.3 驱动初始化与状态寄存器确认初始化主要分成三步先初始化SPI外设再初始化CS引脚为GPIO输出最后读取MRAM的状态寄存器确认芯片在线。SPI1的初始化参数如下我使用的是HAL库// APB2 90MHzPrescaler 4实际SPI时钟 22.5MHz hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_1EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_4; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1);注意SPI1挂在APB2上最大可以分频到45MHz但MR25H40CDF的数据手册最高支持40MHz所以不要图快直接上分频2稳妥起见用分频4跑22.5MHz余量更足。上电后读取状态寄存器的方式是发送0x05指令然后接收一个字节uint8_t read_status(void) { uint8_t cmd 0x05; // RDSR uint8_t stat 0; SPI_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 10); HAL_SPI_Receive(hspi1, stat, 1, 10); SPI_CS_HIGH(); return stat; }上电时若读到的数据是随机值不要慌。MRAM与Flash不一样Flash上电默认全0xFF而MRAM的内容是未知的必须由软件做格式化处理这个细节在后面的数据可靠性设计中会详细说。3. 核心驱动实现与读写流程剖析3.1 指令集速览与SPI模式选择MR25H40CDF的指令集跟25系列Flash非常接近基础操作表如下指令命令码功能说明WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据FAST_READ0x0B快速读WRITE0x02写数据SLEEP0xB9进入睡眠模式WAKE0xAB唤醒MR25H40CDF支持SPI Mode 0和Mode 3也就是CPOL low/CPHA 1Edge或者CPOL high/CPHA 2Edge。驱动初始化我选了Mode 0也就是CPOL0、CPHA0跟大多数SPI器件的默认习惯一致示波器抓起来也直观。需要特别强调MRAM写数据时无需先擦除没有任何块擦除、扇区擦除指令。地址边界上连续写会回绕到起始地址所以驱动里要注意不要把数据写到跨越容量末尾的位置。3.2 读操作实现与解释读操作其实很简单发送READ指令跟3字节地址然后持续接收数据。MR25H40CDF虽然是4Mb容量需要19位地址但SPI协议里仍然用完整的24位地址格式高5位直接置0。核心代码如下int mram_read(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; if (addr len MRAM_SIZE) { return -1; } cmd[0] 0x03; // READ cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; SPI_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 4, 100); HAL_SPI_Receive(hspi1, buf, len, 1000); SPI_CS_HIGH(); return 0; }这里有个细节读操作期间CS必须全程拉低直到最后一个数据字节接收完成。如果CS中途拉高芯片会立刻终止这次读操作剩下的数据全是错的。HAL库的Transmit和Receive虽然是两次调用但只要CS在两次调用之间保持低电平底层的SPI引擎是连续工作的完全没有问题。3.3 写操作实现与解释写操作比读操作多一个写使能步骤。标准流程是先发WREN置位WEL位然后发WRITE指令、地址和数据。数据手册里要求每次写操作前都要发WREN驱动里最保险的做法就是每次写事务前发一次别偷懒。完整代码如下void write_enable(void) { uint8_t cmd 0x06; // WREN SPI_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 10); SPI_CS_HIGH(); } int mram_write(uint32_t addr, const uint8_t *buf, uint32_t len) { uint8_t cmd[4]; uint8_t stat; if (addr len MRAM_SIZE) { return -1; } write_enable(); stat read_status(); if (!(stat 0x02)) { // WEL 位未置位 return -2; } cmd[0] 0x02; // WRITE cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; SPI_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 4, 100); HAL_SPI_Transmit(hspi1, (uint8_t *)buf, len, 1000); SPI_CS_HIGH(); // 可选回读状态确认写入完成 stat read_status(); if (stat 0x01) { return -3; // WIP 还在忙按MRAM特性一般不会发生 } return 0; }跟Flash最大的区别是Flash写完一页要等几十毫秒MRAM写完发完CS拉起就算完成了写命令本身没有页编程时间。这也是为什么MRAM适合做高频小数据写入比如实时状态记录、批次计数这样的场景。3.4 用DMA做大块数据搬运如果只是读写几十个字节CPU等SPI完全没问题。但日志读取或者固件参数备份经常一次操作几KB如果还用CPU一个字节一个字节地喂SPI会浪费大量CPU时间。这时候要上DMA。HAL库直接支持SPI的DMA收发配置起来也不复杂。以DMA接收为例先把SPI接收DMA和发送DMA初始化好然后调用uint8_t tx_cmd[4]; uint8_t rx_buf[1024]; SCB_InvalidateDCache_by_Addr((uint32_t *)rx_buf, sizeof(rx_buf)); SPI_CS_LOW(); HAL_SPI_Transmit(hspi1, tx_cmd, 4, 1000); HAL_SPI_Receive_DMA(hspi1, rx_buf, sizeof(rx_buf)); HAL_SPI_DMAPause(hspi1); // 等待接收结束 SPI_CS_HIGH();注意STM32F469II带D-CacheDMA把数据写进内存后CPU读到的可能是Cache里的旧数据。所以在DMA接收之前必须先调用SCB_InvalidateDCache_by_Addr把对应缓冲区的Cache行作废否则会出现“SPI明明收对了程序读到的却是乱码”的诡异问题。发送方向类似DMA发送前要确保Cache里的数据已经写回内存也就是执行SCB_CleanDCache_by_Addr否则DMA从内存搬运时可能搬的是旧数据。DMA的事务结束可以用中断回调函数处理比如在HAL_SPI_TxCpltCallback里释放信号量。我建议把CS的拉高放在DMA完成回调里因为DMA结束之后SPI总线可能还有最后几个周期的尾巴提前拉高CS会导致最后一个字节丢失。4. 数据可靠性设计与工程化落地4.1 工业环境下的数据帧结构设计MRAM本身存储可靠性很高但工业现场的电磁干扰、MCU跑飞、总线毛刺这些因素仍然可能造成数据异常。所以不能光把裸数据往芯片里写一定要在应用层做数据帧封装。我目前用的日志数据帧结构是这样的字段长度说明帧头1字节固定0xA5用于同步版本号1字节数据帧格式版本记录序号4字节单调递增用于新旧判断数据长度2字节有效负载长度数据区N字节实际业务数据CRC324字节对前面所有字段做校验这个结构看起来简单但实际用起来非常顺手。帧头用于快速定位序号用于分辨新旧记录CRC32用于检测数据损坏。读取数据的时候先扫描找到帧头然后校验CRC如果校验失败就直接跳过这一帧不会因为一条坏记录把整个日志解析搞挂。生产现场经常有强电设备启停电源毛刺躲都躲不掉有了这个设计即便偶尔写进去坏数据解析端也不会崩溃下一次正常写入覆盖掉就行。4.2 掉电保护与原子写策略很多人一开始用MRAM以为芯片本身不会掉数据就万事大吉了。实际上应用层还会面临一个更现实的问题如果系统正在写数据写到一半掉电这块数据是不是完整的MRAM的物理写过程极快和Flash那种边擦边写完全不是一回事所以单次写操作本身不需要担心掉电损坏。但如果你的数据帧分两次写比如先写数据体再写帧头和序号第二次没写完就掉电了那读出来的就是一条“半成品”记录。我的做法是双槽备份加原子标志。具体实现是维护两个固定槽位交替写入最新的完整数据帧。写入顺序也很关键先写槽位里的数据体和CRC最后一步才更新槽位头部的有效标志。这样无论在任何一步掉电至少有一个槽位的数据是完整可用的。上电启动时两个槽位都读一遍哪个有效标志正确、序号更新就选哪个作为当前数据。这种方案在工业设备里非常成熟逻辑简单又稳。4.3 实测性能数据我把这个方案跑在真实板卡上用逻辑分析仪抓过SPI总线做了几组基本性能测试整理成表格供参考操作数据量实测参考耗时说明READDMA1KB约0.45ms22.5MHz SPI时钟WRITEDMA1KB约0.5ms含WREN和地址开销单字节写1字节约2µs不含应用层CRC批量日志写入每次256字节约0.13ms含状态寄存器检查这个速度对工业数据记录来说非常富余即使每秒写10条256字节的日志也只占用大约百分之十几的SPI时间主控还能干别的活。要特别说明22.5MHz的SPI时钟下吞吐已经够用没必要冒险超频到40MHz。如果真的要更快可以考虑用MRAM的FAST READ指令但实际收益不大因为瓶颈主要在指令开销和数据长度不差那几个dummy周期。5. 常见问题与排查实录5.1 写入后读回全是0xFF新手最容易遇到的问题就是用WRITE指令写完回读发现全是0xFF跟没写一样。排查思路先看状态寄存器。如果WEL位没置位写操作根本没生效。原因多半是WREN指令没发成功或者CS时序不对。WREN指令必须在CS低电平期间发送完整一个字节然后CS拉高芯片才会锁存写使能状态。如果CS拉高的时序太短或者GPIO配置有问题写使能就没有真正完成。另外检查WP#引脚。如果WP#被拉低MRAM会启用硬件写保护即使发了WREN也白搭。我见过一块板子就是因为WP#标号对错一路焊到了GND上折腾了大半天才发现。5.2 D-Cache导致DMA数据不对这个坑在STM32F469II这种带Cache的芯片上特别典型。症状是SPI读回来的数据在调试器里看内存是正确的但程序跑到下一行变量值突然变成了一堆旧数据。其实不是变量值变了而是CPU读的是Cache里的旧内容。解决办法就是前面提到的DMA接收前主动invalidate CacheSCB_InvalidateDCache_by_Addr((uint32_t *)rxbuf, sizeof(rxbuf));再补充一个细节STM32F469的Cache Line通常是32字节invalidate操作会按整条Cache Line对齐执行。如果你的DMA缓冲区长度不是32的整数倍干脆把缓冲区定义成32的倍数大小比如用256字节对齐数组省得末尾边界处出现奇怪问题。5.3 SPI速率过高导致偶发数据错位如果SPI分频配置错了比如在SPI1上直接用分频2得到45MHz时钟已经超过MR25H40CDF的40MHz上限。芯片在边缘条件下可能偶发采样错误症状不是每次读写都错而是偶尔某个字节多一位少一位排查起来极其隐蔽。这种问题逻辑分析仪不太容易抓因为错误频率低。我的排查方式是把SPI时钟降一档如果连续读写几千次不再出错基本可以锁定是时钟余量不足。工业产品要有足够的降额意识不要卡着芯片极限跑。5.4 HOLD#引脚干扰导致通信卡死还遇到过一种诡异现象设备正常运行几分钟后MRAM读写突然就卡住了读状态寄存器也毫无响应必须整机断电重启才恢复。查了几天终于发现HOLD#引脚被一根长走线带着受到旁边电机驱动脉冲的耦合干扰瞬时被拉低芯片进入暂停状态。之后把HOLD#加上了10kΩ上拉电阻并尽量远离高频线路重新布线问题彻底消失。这里必须强调HOLD#和WP#不是没用到就能随便处理的功能脚最好当成有源信号对待。6. 项目落地后的几点补充体会整块芯片方案的落地过程比我想象中顺利主要归功于MR25H40CDF本身兼容SPI Flash指令集代码层面没有伤筋动骨的改动。如果你现在正准备从EEPROM或者Flash迁过来建议先把状态寄存器读写跑通再过渡到正式业务数据这样排查问题有个清晰的分界线。另外我强烈建议在生产阶段增加一项回读校验测试不是简单的写后读一两个字节而是让设备烧写一段固定模式数据后完整回读比对。MRAM虽然可靠性高但任何芯片都可能在焊接、运输或者装机过程中受损产线测试能帮你把这些残次品提前挡在出厂之前。根据我个人经验用MRAM的板子开机自检里一定要加上“数据完整性检查”检查无效标志、CRC字段是否合法。因为MRAM上电内容可以是随机的第一次贴片回来的新板子不初始化就上电读读到的数据不一定是0xFF还可能是各种乱七八糟的数。只有靠应用层主动格式化并写入可靠标志才能确保整个系统一直运行在可控的数据状态里。这一点是MRAM和Flash最大的使用习惯差异谁忽略了谁吃亏。
返回列表