ARTICLE DETAIL

资讯详情

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

工业数据记录选型:SPI MRAM如何替代Flash并实现掉电保存

工业数据记录选型:SPI MRAM如何替代Flash并实现掉电保存 1. 选型背景为什么工业侧的数据记录选了SPI MRAM而不是Flash前阵子做一台工业设备的运行数据记录模块要求掉电保存、高频写入、环境温度经常逼近70℃还得在几毫秒内把当前状态写完。第一版图省事用了串行Flash跑了一个月就发现问题日志越写越慢擦除时间不稳定断电测试还偶尔出现整扇区花掉的情况。后来把存储芯片换成Everspin的MR25H40CDF挂在STM32F723IE的SPI1上这套方案才真正稳定下来。这篇文章就是记录我自己的选型、接线、驱动和调试过程希望能给同样做工业控制、嵌入式数据采集的人一个可复用的参考。1.1 工业设备对存储介质的三个硬性要求工业环境下的数据记录和消费电子产品不太一样。消费级产品里掉个文件、丢一份配置重启恢复出厂设置就能接受工业现场不行伺服参数、相机标定、产线配方、故障时刻的状态量丢了轻则停机重调重则要返工排查事故原因。我在这个项目里给自己列了三条硬指标。第一是温度范围要宽。户外机柜夏天能到60多度产线设备旁边就是发热源电池供电的SRAM在这个温度下泄漏电流会明显变大普通Flash在高温区的数据保持时间也会缩短。第二是写入频次要高。日志型应用往往每个控制周期都写数据按10Hz频率算一天就是86万次写入传统Flash标称10万次擦写寿命在这种场景下撑不过一周。第三是掉电时刻不能丢数据。设备可能在写一半的时候突然断电如果存储介质本身写入延迟高就很容易留下半截脏数据。这三个约束叠加普通NOR Flash和EEPROM很难同时满足。NOR Flash擦除慢、按扇区操作小记录反复写还要额外做磨损均衡EEPROM虽然按字节写但容量小、写耐受力也一般。MRAM恰好把这三个短板都补上了非易失、随机字节写入、极高的写寿命。这也是我最终看向MR25H40CDF的原因。1.2 MR25H40CDF与Flash、EEPROM、FRAM的本质差异MR25H40CDF是一颗4Mbit的SPI接口MRAM本质上是磁阻存储器不是靠电荷存储所以没有擦除概念。这意味着它和NOR Flash的底层操作逻辑完全不同Flash写一个字节前得先把所在扇区擦成0xFFMRAM不需要直接往目标地址写新值就行读改写照常进行。说得直白一点它用起来更像一颗非易失的SRAM只是接口是SPI。和EEPROM比MRAM的写寿命和写速度都高出几个数量级。串行EEPROM很多型号标称100万次擦写写一个字节还需要等待几毫秒内部计时MR25H40CDF在写命令发出后数据几乎是即时进入存储层的不需要专门轮询“写忙”标志。和FRAM比FRAM也有高写寿命和随机写能力但大容量串行FRAM的可选型号比较少MRAM在4Mbit这个容量上的选择更丰富且宽温型号覆盖更全。最后还要提一句带电池的SRAM那玩意儿虽然写寿命无限、速度快但电池在工业现场是维护隐患能不用我尽量不用。下表是我在选型时列的一个简化对比方便后面同事参考介质类型按字节随机写写寿命写入延迟是否需要擦除掉电保持NOR Flash否10万级毫秒级是是EEPROM是百万级毫秒级否是FRAM是极高纳秒级否是带电池SRAM是无限纳秒级否需要电池MR25H40CDF是极高SPI时钟级否是对很多嵌入式同事来说MRAM一开始最需要扭转的认知就是“这不是Flash”。只要把“擦除”两个字从脑海里删掉很多设计都会顺起来。1.3 容量、寿命与写入耗时的提前估算选型不能只看芯片指标还得算账。MR25H40CDF容量是4Mbit换算下来就是512KByte。我在设计时按一条记录64字节算最多可以放8192条日志。如果内部做双缓冲一半空间做数据区一半做索引也能放四千多条。对一个按分钟记录运行状态的小型设备来说这个容量够用如果嫌不够可以选更大容量的MRAM系列但需要把地址长度和页尺寸相关的底层驱动一起调整。写入耗时也要提前估算。假设SPI时钟跑13.5MHz写一条64字节记录需要先发4字节命令头命令字加3字节地址再加上64字节数据总共68字节。每个字节8个bit568个bit换算下来大概42微秒。再加上GPIO翻转片选和命令间隔的开销实测在45微秒左右。这个速度意味着设备在断电瞬间只要还有几十微秒的供电余量就能把一小条记录安全落地。寿命方面更不用纠结。MRAM的写耐受力极高工程上可以认为它比MCU本身寿命还长完全不需要像Flash那样设计磨损均衡层。当然具体数字还是建议以Everspin官方数据手册为准不同批次、不同温度范围可能有细微出入但不影响结论在工业日志记录场景里MRAM的寿命余量非常充足。2. MR25H40CDF的底层行为引脚、状态寄存器和命令流2.1 引脚功能与最小硬件连接MR25H40CDF是标准SPI从器件封装常见引脚包括CS片选、SCK时钟、SI数据输入、SO数据输出、WP写保护、HOLD保持、VCC和GND。第一次画原理图时很多人会把这颗芯片当成普通Flash来画结果把WP和HOLD引脚悬空了。这在实验室环境有时候能用但在工业现场很容易出现偶发写保护或者时钟被HOLD干扰的问题。我的建议是最小系统里把WP引脚直接接VCC让块保护功能不生效HOLD引脚也接VCC让SPI通信永远不被暂停。如果设计上确实想用WP做硬保护那就必须用GPIO控制它而不是随手上拉到VCC了事。CS片选一定要用MCU的GPIO控制不能直接接地否则没法区分每次命令的起始和结束。SCK、SI、SO对应STM32F723IE的SPI引脚接法按照“SI接MOSI、SO接MISO”来就行别搞反。供电上VCC旁边要放一个100nF去耦电容并尽量靠近芯片电源引脚。如果板子上MCU和MRAM共用一路电源还要留意MCU大电流瞬态对MRAM供电的影响必要时加磁珠或者RC隔离。这些细节在原理图阶段不注意到EMC测试阶段会很痛苦。2.2 状态寄存器与写使能锁存MR25H40CDF虽然是MRAM但它的SPI指令集基本沿用了串行Flash那套所以状态寄存器仍然存在只是含义有区别。最需要关注的是WEL位也就是写使能锁存位。每次发送写命令后芯片并不一定会执行写入必须先把WEL位置1。置位方式是通过WREN指令也就是0x06。发送WREN时片选拉低把0x06发过去然后片选拉高WEL位就变为1。写完数据后WEL位会自动清0。这意味着如果你打算分多次写每次写之前都得重新发WREN。我见过不少同事从Flash经验迁移过来直接把WREN省了结果数据怎么都写不进去最后读状态寄存器才发现WEL一直是0。状态寄存器里还有块保护位BP0、BP1和WPEN位。如果这些位被配置成了非零值即使WEL为1受保护区域的写入也会被拒绝。我实际遇到过芯片在调试过程中被误写BP位变成01结果从0x20000开始怎么都写不动。排查方法很简单发0x05读状态寄存器看低两位是不是00不是就发WRSR把它清掉。2.3 读、写和快速读的命令格式读数据用0x03命令格式是片选拉低、发送0x03、发送3字节地址、然后持续读MISO上的数据。读多少字节完全由你保持片选的时间决定想读1字节就拉高片选想读一整块就持续读下去。MRAM的读是非破坏性的读完不会改变存储内容不用像某些存储器那样担心读操作会翻转数据。写数据用0x02命令但前提是先发0x06把WEL置1。写命令格式和读几乎一样0x02加3字节地址加要写入的数据片选拉高的瞬间写入生效。因为MRAM不需要擦除也不存在Flash那种“页编程”概念你可以在一次片选低电平期间连续写任意长度的字节地址会自动递增。需要注意的是片选在整个命令期间必须保持稳定中间任何抖动都可能导致命令被芯片丢弃。如果SPI时钟跑得比较高手册上可能还提供快速读命令0x0B它和普通读的区别是在地址后面多了一个dummy字节方便总线上有足够时间翻转方向。我这次SPI时钟只跑了13.5MHz左右所以直接用0x03就够了不需要Fast Read。3. STM32F723IE硬件侧落地引脚分配与SPI外设配置3.1 F723上的SPI引脚选择和PCB布局要点STM32F723IE是Cortex-M7内核、主频最高216MHz外设资源非常丰富SPI、QSPI、CAN、以太网都带。这类芯片在工业主板上通常不只是做存储读写可能还兼着通信、采集和控制所以给MRAM分配引脚时要提前留好布局。我这次用的是SPI1引脚分配为SCK、MISO、MOSI分别接到PA5、PA6、PA7CS用PA4作为普通GPIO输出。选择PA4是因为它靠近SPI1的固定引脚走线短而且它可以不用硬件NSS功能纯粹当成软件片选口用。这里有个提醒STM32的SPI外设虽然有硬件NSS但软件控制片选更灵活尤其是做多字节连续传输时不会因为NSS状态变化导致通信半途中断。PCB走线方面三条SPI信号线尽量等长、短走避免跨越分割地平面。如果板上有电机驱动或者开关电源最好在SPI信号线上加22欧姆到33欧姆的串联电阻抑制振铃。工业设备还要考虑ESD防护SPI引脚如果可能被引出到外部接插件建议加TVS管。但MRAM一般就在板上不引出的话可以不加。3.2 SPI模式、时钟分频与CS控制方式MR25H40CDF的SPI协议支持模式0和模式3也就是CPOL和CPHA的组合都可以用。我统一用的模式0CPOL为0CPHA为0空闲时钟为低数据在第一个边沿采样。这和我板子上其他SPI从器件一致驱动代码也最通用。如果项目里其他器件强制要求模式3MRAM一般也能跑但我在工程上不混用避免排查问题时多一点干扰因素。初始化SPI时设置主模式、8位数据帧、MSB先行。时钟分频我选了APB2时钟的8分频。F723的APB2外设时钟是108MHz8分频后SPI时钟约13.5MHz。这个频率不高但换来了很强的抗干扰余量而且日志写入45微秒的时间完全够用。如果后续要提高吞吐把分频调到4就是27MHz再往上就要仔细量波形了。CS控制只需要记住一个原则一次完整的命令必须从CS拉低开始、到CS拉高结束。初始化阶段把PA4输出设置为高电平平时保持CS为高。每次通信前拉低通信完成后马上拉高。不要用SPI外设的硬件NSS自动控制因为它和软件时序配合起来容易出奇怪的竞争问题。3.3 用DMA减少数据记录对CPU的占用工业主板上MCU不可能只顾着写日志它还要跑控制算法、处理通信、响应中断。写一段几十字节的数据虽然只有几十微秒但如果每秒要写几十条累积起来对CPU的占用就不可忽略了。解决办法是让SPI配合DMA传输。最简单的做法是把命令头和数据拼成一个连续缓冲区然后用HAL_SPI_Transmit_DMA发给SPI。命令数据全部发送完毕后SPI的DMA传输完成中断里再把CS拉高结束本次写命令。注意CS必须等DMA完全发完才能拉高否则会截断最后一个字节。我见过有人把CS拉高的代码放在发起DMA之后结果每次都少写一个字节。用DMA的代价是需要管理缓冲区生命周期。工业代码里最好用静态数组或者带信号量的任务模型避免DMA还在写的时候缓冲区被其他地方修改。如果项目相对简单日志频率不高完全不用DMA直接在中断里阻塞发送几十微秒也能接受。这个选择取决于你对CPU实时性的要求不必为了“用DMA”而用DMA。4. 驱动与数据模块实现从单字节读写到固定槽位日志4.1 最小编码WREN、读、写、校验四件套下面这段是我实际在F723上跑的简化驱动基于STM32Cube HAL库。先定义MRAM的SPI句柄、片选引脚和命令字然后写最小读写函数。代码里的宏定义和引脚变量需要根据你的工程替换。#define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRITE 0x02 #define MRAM_CMD_READ 0x03 #define MRAM_CMD_RDSR 0x05 #define MRAM_CMD_WRSR 0x01 #define MRAM_CS_LOW() HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_RESET) #define MRAM_CS_HIGH() HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_SET) static void mram_write_enable(void) { uint8_t cmd MRAM_CMD_WREN; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 10); MRAM_CS_HIGH(); } static uint8_t mram_read_status(void) { uint8_t cmd MRAM_CMD_RDSR; uint8_t txdummy 0x00; uint8_t status 0x00; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 10); HAL_SPI_TransmitReceive(hspi1, txdummy, status, 1, 10); MRAM_CS_HIGH(); return status; } void mram_write_bytes(uint32_t addr, const uint8_t *data, uint32_t len) { uint8_t header[4]; header[0] MRAM_CMD_WRITE; header[1] (addr 16) 0xFF; header[2] (addr 8) 0xFF; header[3] addr 0xFF; mram_write_enable(); MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, header, 4, 10); HAL_SPI_Transmit(hspi1, (uint8_t *)data, len, 10); MRAM_CS_HIGH(); } void mram_read_bytes(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t header[4]; header[0] MRAM_CMD_READ; header[1] (addr 16) 0xFF; header[2] (addr 8) 0xFF; header[3] addr 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, header, 4, 10); HAL_SPI_Receive(hspi1, buf, len, 10); MRAM_CS_HIGH(); }读状态函数要注意SPI是双向总线发完0x05之后要再发一个字节的假数据才能把SO上的状态读回来。写函数里的mram_write_enable是必须的漏了这句就是写得进去。写完拉高CS之后如果还想验证就调用mram_read_bytes把数据读回逐字节比较。工业代码里我建议每一步关键写入后都做读回校验MRAM写坏的概率极低但总线上有干扰时读回对比能最快暴露问题。4.2 固定长度记录的槽位组织和回环寻址日志系统最常用的是固定长度记录加环形地址。分配一块数据区把空间分成N个槽位每个槽位大小相等比如64字节。每条新日志写入时根据当前记录序号计算出目标地址写完后更新一个“最新记录位置”的索引。这种设计最大好处是寻址简单重启之后也能很快定位到当前写入点。计算公式就是base_addr加槽位序号乘槽位大小。比如数据区基地址是0x00000槽位大小64第42条记录就写到0x00000 42 * 64 0x00A80。环形处理也很简单记录序号不断增加但地址只在0到N-1之间循环。索引区存储当前序号读取时先找到最新一条再向前追溯。由于MRAM不需要擦除槽位覆盖写非常干净。旧的64字节数据会被新数据直接替换不会出现Flash那种“必须先擦后写、中途断电导致旧数据和新数据混在一起”的恶心情况。这也是我后期为什么彻底放弃Flash方案的原因之一环形日志在Flash上最难处理的磨损均衡和扇区擦除在MRAM上几乎不存在。4.3 掉电记录场景下的双缓冲与版本保护虽然MRAM写入速度快但工业设备掉电可能发生在任何一条指令之间。为了做到“断电瞬间也能恢复上一条有效记录”我设计了双缓冲加提交标记的方式。原理很简单每条日志预先准备两个槽位先把完整数据写入A槽再写B槽两个槽都写完以后最后写一个提交标记到固定控制区。读取时先看提交标记再决定使用哪个槽位的数据。实际代码里不需要做得很复杂。控制区可以放两个字节的magic数比如0xA5和0x5A配合写入序号一起用。每次启动时先扫描控制区如果magic有效说明上一次掉电时整条记录已经完整提交如果magic无效说明数据可能写了一半就回退到上一个有效记录。由于MRAM写入速度极快数据区和提交标记之间的时间窗口非常短这套机制在实测中表现很好。需要特别说明的是MRAM解决的是“存储介质写入中断后的数据完整性”问题不等于解决了“掉电瞬间系统能不能来得及触发保存”的问题。想做到断电瞬间自动保存还需要MCU供电端加掉电检测和足够的保持电容让系统在电压跌落时还能运行几毫秒。这个属于电源设计范畴但和存储配合好了整套掉电记录方案才完整。5. 实测结果、性能数字和调试避坑5.1 实测读写速率和单条记录写入耗时我在这块F723板子上实际跑出来的数据是这样的。SPI时钟13.5MHz写一条64字节日志包括发送0x02命令、3字节地址、64字节数据和片选翻转时间整体耗时在45到50微秒之间。实际测量时我在写函数前后翻转一个GPIO用示波器看高电平宽度误差很小。读取64字节日志大概在42微秒左右读取速度对完整日志上传也够用。整片512KByte空间全量读取一次按13.5MHz算理想时间约310毫秒实际读完整片差不多340毫秒。这个数字对开机自检来说可以接受。如果将来要做整片备份可以把SPI时钟提高到27MHz全量读压缩到170毫秒左右但对PCB走线和波形质量要求更高。写入耐力的工程验证我做了个压力测试连续对同一地址写入100万次每写一次读回一次校验全程没有检测到一次校验失败。这个实验不能完全证明MRAM寿命但至少说明在这个板子和这个时序参数下通信链路是稳定可靠的。我后来又把测试地址固定到同一字节跑了一整夜第二天早上起来再读数据依然正确。5.2 踩过的坑WP/HOLD浮空、SPI模式错误、伪读回讲几个实际踩过的坑每一个都花了我不少时间排查。第一个坑是HOLD引脚悬空。第一版原理图把HOLD引脚漏接了板子拿回来基本功能都能跑但偶发写失败而且不是固定地址失败是东一榔头西一棒子。用逻辑分析仪抓波形命令和电压看起来都正常后来翻数据手册才想到HOLD引脚没处理。悬空的HOLD在磁场干扰较大的工业环境下可能被拉低芯片就进入暂停状态后续指令全部不认。解决办法是把HOLD和WP都直接接VCC再没出现过这类问题。第二个坑是忘发WREN。从Flash迁移过来时我一度以为SPI存储器的写命令都是直接发0x02就行结果写操作全部无效读回来的数据全是0xFF。当时我甚至怀疑芯片是坏的换了一颗还是一样。后来读状态寄存器发现WEL为0才反应过来。这个坑其实非常好避免只要在每次写函数的第一行调用mram_write_enable然后调试时先读一下状态寄存器确认WEL为1就能快速排除问题。第三个坑是SPI模式错配。某次我为了调试另一个SPI器件把SPI1的CPOL和CPHA顺手改成了模式3结果MRAM读写紊乱。原因不是MRAM完全不支持模式3而是我当时板子上其他器件只能跑模式0两者混在一起让排查变得非常混乱。这种事不在技术难度在于工程管理。一个工程里多个SPI从器件尽量统一SPI模式不要给调试埋雷。第四个坑是读回“伪成功”。有几次写完数据读回校验也通过了但掉电后重新上电数据又不对。后来发现是板子供电不稳写操作时VCC跌到芯片最低工作电压以下MRAM可能进入了欠压状态。这个问题和存储芯片本身关系不大但我当时第一反应是换芯片走了弯路。排查掉电保存类问题第一步永远是检查电源波形。5.3 调试顺序逻辑分析仪、状态检查、与Flash习惯的差异如果你也准备把MRAM用进自己的嵌入式项目我建议调试顺序按这个来先发RDSR读状态寄存器确认芯片通信基本正常再对某个地址写一个字节读回来对比然后写连续多字节验证地址递增最后才上日志系统。每一步都确认无误再进行下一步基本能避开90%的坑。逻辑分析仪是排查SPI问题的最好工具别省。直接把CS、SCK、MOSI、MISO四根线夹到板子的测试点上抓一次写操作看CS低电平时序是否符合预期命令字和地址是否发全最后有没有在CS拉高前把数据发完。波形正常而读回不对时优先怀疑硬件连接和数据位方向。最后还是要强调一下和Flash的习惯差异。有些同事拿到MRAM会不自觉地去找“扇区擦除”“页编程”“磨损均衡”的代码片段移植过来这些都是多余的。MRAM的思维模型就是“SPI接口的SRAM”地址随便写字节随便改不用擦除也不用等忙。把这些习惯扭过来之后MRAM用起来会顺手很多我也因此在后续好几个项目里凡是涉及工业数据记录第一选择基本就是它。
返回列表