ARTICLE DETAIL

资讯详情

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

STM32F767ZI与MR25H40CDF:SPI MRAM工业存储实战

STM32F767ZI与MR25H40CDF:SPI MRAM工业存储实战 做了很多年嵌入式论坛上聊存储的时候大家总是条件反射地想到Flash、EEPROM、SD卡很少有人会第一时间提到MRAM。直到我在一个批量采集与参数记录项目里把Everspin的MR25H40CDF和STM32F767ZI放到同一块板子上用SPI在MCU与MRAM之间来回写配置、校准值和运行日志才真正感受到这种“掉电不丢、随手就写”的器件有多顺。整套方案调完最突出的体会是工业嵌入式场景里MRAM和Flash的使用体验完全不在一个维度。这篇文章就围绕这个组合展开从选型、硬件接线、驱动设计、性能优化到现场排错把存储和读取数据的完整链路讲清楚给同样在做工控、仪表、边缘采集的朋友一个能直接复用的参考。我会尽量用工程视角说细节不绕弯子。你能看到为什么MRAM在频繁写入、高温、掉电苛刻的现场更稳也能看到STM32F767ZI在SPI传输时要注意的坑。懂了原理之后即使你手头的型号不是MR25H40CDF换成别的MRAM或FRAM这套思路依然能搬过去用。1. 为什么工业场合会选MRAM而不是Flash/SRAM/FRAM先想清楚一个很实际的问题现场设备存储数据到底在存什么在工业采集器、PLC网关、伺服驱动器这类设备里99%的需求是两类数据一类是很少改动的配置参数另一类是会一直追加的日志和运行状态。前者需要绝对可靠后者需要频繁写入。NOR Flash遇到频繁写入就难受了写之前要整块擦除擦写次数有上限掉电在擦除中间还能把整片数据搞没。SRAM虽然读写快但一掉电就全丢配电池不仅占地方电池本身在高温环境里还是短板。MRAM正好补上这个位置。它属于非易失存储掉电后数据还在又没有Flash的擦除操作和寿命焦虑写入速度接近SRAM使用寿命也不存在“几十万次”这种紧箍咒。FRAM也能做到类似效果但FRAM容量通常做不大价格高MRAM在4Mbit这个档位恰好更实用。MR25H40CDF这种4Mbit容量的SPI MRAM对大多数工控板而言存配置表、存报警记录、存校准系数都绰绰有余而且封装和Flash芯片类似替换方便。1.1 三种非易失存储横向对比把NOR Flash、FRAM、MRAM放在一张表里看会更直观类型写入前是否需要擦除写入寿命典型写入速度掉电保持适合场景NOR Flash需要按扇区擦除约10万次较慢擦除耗时毫秒级好固件、冷数据、OTA镜像FRAM不需要近乎无限快总线速度写入好小容量频繁改写MRAM不需要近乎无限快SPI总线速度写入好日志、参数、数据采集缓存这个差异在实际工程里意味着什么假设一个设备每秒写一条20字节的日志到Flash为了省擦除次数你得设计环形缓冲区、脏标记、垃圾回收。数据量一增加Flash的空闲页管理就变成了一个复杂的模块。但用MRAM之后驱动代码就是简单的“开CS、发命令、写数据、关CS”完全不需要考虑擦除和均衡磨损。这对嵌入式项目绝对算得上降维打击。1.2 MR25H40CDF 的关键规格与选型理由MR25H40CDF这颗芯片的具体规格我觉得几个点对选型最有用容量4Mbit也就是512KB按工业参数页来说足够用。接口SPI兼容标准SPI时序支持Mode 0和Mode 3。工作电压典型3.3V。温度范围工业级覆盖-40到105摄氏度。读写寿命理论无限次写不用做磨损均衡。写操作不需要擦除可直接覆盖写入。项目里选它还有个理由替换成本低。很多板子原来用的是SPI NOR Flash引脚就那么几个把Flash换成MR25H40CDF后供电、接地、时钟、数据引脚基本可以对得上。唯一要注意的是WP和HOLD引脚的处理后面我会专门说。如果你手头只有W25Q128这类Flash不用急着换先理解“直接写”和“先擦再写”的差异再评估项目里日志写入的频率心里就有数了。2. STM32F767ZI 侧的硬件准备与接线要点STM32F767ZI一直是性能很足的芯片Cortex-M7内核主频能到216MHz多个SPI、多个DMA拿它接MR25H40CDF绰绰有余。通常我不会选太复杂的接口用SPI1就行引脚PA5、PA6、PA7分别做SCK、MISO、MOSICS用普通GPIO引脚自己控制。选择GPIO来控制CS而不是用硬件NSS是因为软件CS更可控尤其在连续读写、DMA传输的时候能精确控制在什么时刻降低引脚、什么时刻抬高引脚。CubeMX配置里SPI1选择Full-Duplex MasterMotorola格式8bit数据位MSB First预分频按需要来。STM32F767ZI的SPI时钟源如果来自APB2主频可能到108MHz想要让SPI时钟不超过MR25H40CDF的最高频率分频系数一般选8或16。比如APB2时钟108MHz分频8后SCK实际13.5MHz这个速度在工业板上比较稳妥不容易被PCB寄生电容和长走线搞出信号质量问题。MR25H40CDF手册标称能跑更高但实际工程我会留余量。2.1 SPI引脚分配与CubeMX配置下面这组引脚分配最省事也是网上很多评估板默认的连接方式SCKPA5MISOPA6MOSIPA7CSPA4软件控制GPIO输出WP接VCC或通过10k电阻上拉HOLD接VCC或通过10k电阻上拉在STM32CubeMX里做完基本配置后记得把SPI的NSS设置为Disable或Software模式然后自己初始化PA4。代码层面可以这样做GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_SPI1_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_4; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_PULLUP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_VERY_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);初始化完成后CS默认高电平。MBRAM芯片在没有CS选中时不应该响应任何命令CS高电平也方便在上电阶段避免误操作。2.2 供电、去耦和WP/HOLD引脚处理MR25H40CDF供电引脚旁边要放一个100nF的陶瓷电容我会习惯性再并一个4.7uF电容这样应对周期性的SPI突发供电需求更稳。很多嵌入式系统出问题不是原理不对而是电源纹波和地弹把信号搞坏了。MRAM在写入瞬间电流变化不大但周围如果有电机驱动、继电器还是需要考虑隔离和电源走线。WP引脚和HOLD引脚是两个容易被忽略的地方。WP是写保护低电平有效如果它被拉低WREN即使发送成功写入命令也会无效。HOLD是SPI暂停引脚低电平有效如果它在通信过程中意外拉低芯片会暂停当前的SPI操作。这两个引脚在设计时我都建议直接通过一个4.7k到10k的电阻接到VCC让它们默认失效。有些开发板会把HOLD引脚引出来做其他复用这是很危险的最好咬死接死不要给现场留隐患。上电时序也要注意STM32上电之后GPIO默认为输入状态STM32F767ZI复位期间引脚电平不确定如果CS被拉低MRAM可能收到无意义的命令。所以电路上可以在CS引脚这里加一个10k上拉电阻确保在GPIO初始化之前CS保持高电平。STM32F767ZI的PVD可以配合掉电检测检测到电压跌落时立刻进入紧急存储流程这个我们放到可靠性设计部分展开。3. 驱动层设计与读写流程硬件连线搞定后真正决定数据能不能稳的是驱动层。SPI MRAM的命令集看起来简单但处理不好就会出现“写入不生效”“读出来FF”这种经典问题。我习惯把驱动分成三个层次最底层是SPI字节收发中间是命令封装层最上面是页读写和状态等待接口。这样把时序细节封装好应用层逻辑就会很干净。3.1 SPI模式、命令集和时钟极性MR25H40CDF支持SPI Mode 0和Mode 3具体区别是时钟极性CPOL和相位CPHA的组合。Mode 0是CPOL0、CPHA0Mode 3是CPOL1、CPHA1。我用的是Mode 0因为STM32的SPI在Mode 0下最容易配对示波器看波形也最直观。这里要记住设备只认一种ModeMCU和MRAM必须保持一致否则数据采样点会错位表现出来就是读写偶尔成功、偶尔失败。基础命令集不多最常用这几个0x06写使能0x04写禁用0x03读数据0x02写数据0x05读状态寄存器0x01写状态寄存器0xB9进入睡眠模式0xAB唤醒所有命令都由CS拉低开始。发送完命令和地址后如果是读操作MRAM会从SO引脚持续输出数据如果是写操作MCU要往SI引脚持续发送数据。地址是24位上位地址先发。我直接写了一个通用的CS控制流程CS拉低SPI发送命令处理数据CS拉高。这个顺序看着简单但很多初学者会在DMA回调里乱改CS导致时序错乱。3.2 状态寄存器与WIP位等待MRAM内部有一个状态寄存器其中最低位WIP是忙标志位和Flash的BUSY位类似。当芯片正在执行内部写操作时WIP为1此时不应该发送新的命令等它变回0再操作。驱动里我习惯在每次写操作之后调用一个函数等待WIP清零void MRAM_WaitBusy(void) { uint8_t cmd 0x05; uint8_t status 0x80; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); while (status 0x01) { HAL_SPI_Receive(hspi1, status, 1, HAL_MAX_DELAY); if (status 0x01) { // 继续读状态 HAL_SPI_Receive(hspi1, status, 1, HAL_MAX_DELAY); } } HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); }严格来说更好的写法是连续读取状态寄存器直到WIP变为0同时加一个超时计数防止芯片无响应时程序卡死。工业现场最忌讳死循环哪怕异常概率很低也要给while加上边界条件。比如可以定义一个timeout超过5毫秒还不能清零就返回错误让上层做恢复处理。3.3 写使能和写禁止一个绕不开的流程MRAM写操作之前必须先发送0x06写使能命令否则后续的0x02写数据命令会被忽略。写使能命令执行后芯片内部会拉高WEL锁存位紧接着的写操作执行完WEL会自动清除。也就是说每次写命令前都要重新写使能不能像Flash驱动那样把使能当一次全局配置。我在封装写页函数时是这样处理的先CS拉低发0x06再CS拉高然后马上CS拉低发0x02和24位地址接着发送要写的数据最后CS拉高再等待WIP清零。不要把0x06和0x02放在同一个CS周期里发虽然有些芯片允许但MR25H40CDF的datasheet明确要求写使能命令本身占用一个独立片选周期。这个细节不能猜一定要看手册。例如写一页256字节的代码框架uint8_t MRAM_WritePage(uint32_t address, uint8_t *data, uint16_t len) { uint8_t cmd; // 1. 写使能 cmd 0x06; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); // 2. 写命令 24位地址 数据 cmd 0x02; uint8_t addr_buf[3] {(address 16) 0xFF, (address 8) 0xFF, address 0xFF}; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); HAL_SPI_Transmit(hspi1, addr_buf, 3, HAL_MAX_DELAY); HAL_SPI_Transmit(hspi1, data, len, HAL_MAX_DELAY); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); // 3. 等待内部写入完成 MRAM_WaitBusy(); return 0; }这里还有个“页边界”问题。MR25H40CDF内部把存储空间按页组织不同型号页面大小可能不同常见是256字节。跨页写入时写入操作不能自动跨越页边界也就是说你写一个长度为300字节的数据如果起始地址离页边界只剩100字节后面的200字节会回卷到当前页头部而不是自动写到下一页。所以驱动里要自己处理分页按页拆成多次写操作。这是和NOR Flash很不同的地方设计上层接口时要格外小心。4. 实际项目中的性能优化与可靠性设计MRAM不需要擦除写性能已经比Flash好很多但SPI接口毕竟还是串行传输在大数据量场景下STM32F767ZI的算力不能浪费在阻塞式等待上。我这里实际用的是DMA传输加中断回调把CPU占用降下来。同时工业设备必须考虑掉电、异常和自恢复这些可靠性细节和驱动函数同样重要。4.1 用DMA中断搬运大块数据通用做法是读操作使用HAL_SPI_Receive_DMA把数据直接从SPI外设搬到内存写操作使用HAL_SPI_Transmit_DMA。但要注意CS的控制。如果直接在DMA发送完成中断里拉高CS看起来没问题实际却可能提前拉高。DMA完成中断发生在最后一个字节移出SPI数据寄存器时但SPI移位寄存器可能还在传输最后几个bit。稳妥的办法是在所有字节的SPI发送真正空闲之后再拉高CS。HAL库的做法是在SPI_TxCpltCallback里判断SPI状态和剩余数据量或者干脆用HAL_SPI_Transmit_DMA之后再延时一个bit。更稳的方案是改用双缓冲DMA把命令和地址预先放到一个缓冲区数据放到另一个缓冲区维持CS低电平跨整个事务。实际项目里我一般不会刻意追求极端吞吐更看重稳定。大块日志写入时可以先关中断拉低CS发送命令和地址然后开启DMA发送数据最后一颗字节发送完成后由中断回调拉高CS。为了保证时序也可以在回调里检查SPI实例的TxXferCount和RxXferCount等它们都为0再动CS。这个方法不复杂但能避免偶发的CS太早关闭导致最后一个字节丢失。4.2 掉电保护、日志写入和校验设计MRAM本身不怕掉电并不意味着你可以在任何时间掉电。如果掉电正好发生在写操作过程中数据可能写了一半状态寄存器的WIP还没归零。为了避免读到半个帧每条日志记录最好都带长度字段、CRC校验和完整标志。我常用的格式是帧头、数据长度、数据内容、CRC32、帧结束字。上电扫描时先按帧头定位再校验CRC发现校验失败就认定最后一条日志不完整从头一条完整记录之后继续写。这样即使掉电时机再刁钻也能保证之前的数据完好。MRAM虽然没有擦除寿命问题但我仍然会做双备份或环形覆盖策略。如果存的是关键校准参数用两个槽位交替写每次写完一个槽后更新有效标记这样现场万一在写中间掉电还有一个旧槽位可以回退。对日志这种连续写的数据直接从头写到尾然后回到起点覆盖简单粗暴但可靠因为不需要擦除覆盖永远快。STM32F767ZI的PVD也可以参与进来。PVD检测到VDD低于阈值时触发中断中断里立刻关掉无关外设把SPI剩余数据写完再拉高CS最后置一个掉电标记。虽然MRAM写入时间极短几百字节大概几百微秒这点时间电源电容通常撑得住。关键是流程要短不要在中断里做复杂运算。4.3 实测数据与经验值我实际测试的环境是STM32F767ZI主频216MHzSPI1挂在APB2上分频系数8SCK约13.5MHz。这个速度下写入512字节并等待WIP清零总体耗时大约400到500微秒。如果分频系数4SCK约27MHz整体耗时还能降到300微秒左右但这时要看PCB布局质量信号线太长就容易出现误码。做一个1KB日志记录加上CRC计算和格式封装写MRAM的时间不会超过1毫秒这个速度在工控设备端为毫秒级响应提供了充足余量。读取速度类似SPI读没有擦除损耗连续读512字节也就几十到几百微秒。正因为延迟这么低我甚至敢把它当成一个小型“掉电不丢的RAM缓冲区”来用运行数据周期性地往里面刷配合掉电信息记录整个系统在异常恢复后的状态连续性好了很多。5. 调试心得与常见问题排查最后这部分是这些年现场调试攒下来的经验按“症状找原因”的方式列出。MR25H40CDF和STM32F767ZI组合里出现的坑不见得是芯片本身的更多是SPI时序、引脚约束和初始化顺序的问题。5.1 读回0xFF或0x00先查时序与引脚读数据全部是0xFF首先怀疑芯片根本没被选上。CS引脚是不是没拉低MISO是不是没接对PA6的复用功能是否配置成了SPI这些基础问题最容易出现。全0x00则多是因为MRAM处于写保护或时钟相位不对也可能SO引脚被拉到了GND。最有效的手段是用逻辑分析仪抓CS、SCK、MOSI、MISO四根线看CS低电平期间SCK是否有正常边沿MOSI命令字节是否正确。没有逻辑分析仪的话可以用示波器看波形。5.2 写命令正常但数据不变写入命令发出去状态寄存器读取也正常但读回来数据还是原来的值。这可能是因为WP引脚被拉低导致芯片处于硬件写保护状态。先量一下WP引脚电压确认它确实为高。另一个原因是写使能没有执行成功可以在发送WREN之后马上读状态寄存器的WEL位确认它已经置1。很多芯片的WEL位在写入完成后会自动清0如果你在写命令前读它应该是1写命令后再读变成0这就说明流程正常。如果WEL一直是0多半是SPI时序有问题或者给芯片供电不稳。还有一个偏门情况芯片进入了睡眠模式。如果之前发了0xB9芯片会忽略大部分命令只响应唤醒命令0xAB。这时候读写都会表现为异常或超时。解决办法是先拉高CS等待tREC时间后发送0xAB唤醒再做操作。这个状态容易在低功耗设计里出现因为睡眠后状态不太直观容易误判成芯片故障。5.3 高速率下偶发数据错位在SPI速率接近MRAM上限时偶发数据错位多半来自信号完整性。SCK上升沿或下降沿太缓、MISO信号回到MCU时建立时间不够都会导致采样错误。解决办法是降分频或者调整SPI极性和相位保证数据在稳定后采样。PCB上尽量缩短MISO走线避免在MISO上串接高阻值电阻。MRAM和STM32之间的走线最好控制在3厘米以内。若必须使用杜邦线延长速度要降到20MHz以下才可靠。我记得有一次调试SPI速率提高后正常数据里偶尔夹一个错误字节排查了很久发现是MOSI上串了一个100欧姆电阻并且线束绕了电源电感一圈。后来把走线改短、电阻去掉问题再也没出现。所以前面提到的高速优化必须建立在良好的板级设计基础上。我自己的习惯是上电自检函数里先对MRAM做一次全地址的0xA5、0x5A回写测试再读出来比对。由于MRAM写次数无限制这种自检可以每次开机都跑不会磨损芯片。万一某一个地址异常也能直接定位到坏块不至于等到数据污染才被发现。这个测试代码只有几十行建议放到产品固件里默认开启成本几乎为零但现场排查故障时能省下大量时间。
返回列表