ARTICLE DETAIL

资讯详情

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

STM32F215RE与MR25H40CDF MRAM工业存储方案实战

STM32F215RE与MR25H40CDF MRAM工业存储方案实战 1. 项目缘起与方案选型思考1.1 为什么要在工业场景里折腾 MRAM 这颗料做工业嵌入式这行的朋友应该都有个共识数据存储这块选型选得好后面少掉一半头发。我这次要聊的项目核心就是用MR25H40CDF这颗 MRAM磁性随机存储器芯片配合STM32F215RE主控在工业现场做数据的存储和读取。为什么不用更常见的 EEPROM 或者 NOR Flash这个问题我在立项的时候被问过不下十次这里先把选型逻辑讲透。工业现场的数据存储有几个硬性痛点。第一是写入频率高比如某些设备需要每秒钟记录一次运行状态、故障码、累计运行时长一天下来就是八万多次写操作。EEPROM 的擦写寿命通常在 100 万次量级按这个频率算十几天就把寿命耗光了。第二是掉电不能丢数据工业设备经常遇到突然断电的情况如果数据还在缓存里没落盘那这条记录就没了。第三是写入速度要快有些场景要求在断电后的几毫秒内把关键数据保存下来靠大电容撑住的那点时间窗口必须完成写入。MR25H40CDF 这颗芯片恰好把这几个痛点都解决了。它是4Mbit512KB容量的 SPI 接口 MRAM最大的特点就是写入不需要擦除、写入速度接近 SRAM、擦写寿命理论上无限。我实测下来单字节写入的时序开销比 EEPROM 小一个数量级而且没有擦除周期这个环节写进去就是写进去了。对于工业场景里那种频繁写、小数据量、要求掉电可靠的需求这颗料几乎是量身定做的。STM32F215RE 这边它是 ST 家的Cortex-M3 内核主频 120MHz带 1MB Flash 和 128KB SRAM外设资源丰富SPI 接口有好几个。选它主要是因为项目里还要跑一些实时控制逻辑M3 的性能和中断响应能力足够而且 STM32 的 HAL 库生态成熟SPI 驱动写起来不费劲。这套组合在工业仪表、数据采集终端、设备黑匣子这类产品里非常典型。1.2 SPI 接口选型的几个关键考量MR25H40CDF 支持SPI 和 Quad SPI两种模式我这次用的是标准 SPI 模式四根线SCLK、MOSI、MISO、CS。为什么不上 Quad因为工业场景里数据量其实不大单次写入也就几十个字节标准 SPI 在 40MHz 时钟下传输 64 字节也就十几微秒完全够用。上 Quad 反而要占用更多 IOPCB 布线也更麻烦性价比不划算。这里有个细节值得展开说硬件片选还是软件片选。STM32 的 SPI 外设支持硬件 NSS 管理但我强烈建议用软件片选也就是自己拿一个 GPIO 来控制 CS 引脚。原因很简单硬件 NSS 在多从机场景下容易出问题而且时序控制不够灵活。MRAM 的 CS 建立时间和保持时间有明确要求软件片选可以精确控制拉低和拉高的时机配合 SPI 传输完成标志能保证时序余量充足。我踩过的坑是早期用硬件 NSS在高速时钟下偶尔出现数据错位换成软件片选后问题消失。另外SPI 的时钟极性CPOL和时钟相位CPHA必须和 MRAM 的数据手册对齐。MR25H40CDF 支持Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1我用的是 Mode 0也就是空闲时 SCLK 为低数据在上升沿采样。这个配置在 STM32CubeMX 里直接选就行但要注意如果总线上还挂了别的 SPI 从机得确认所有从机都支持同一个模式否则就要分时复用或者用不同的 SPI 外设。1.3 整体架构设计思路整个系统的数据流是这样的STM32F215RE 作为主控通过 SPI 总线读写 MR25H40CDF。MRAM 里划分了几个区域系统配置区、运行日志区、故障记录区、参数备份区。配置区存设备 ID、校准参数这些不常变的数据日志区按环形缓冲区的方式循环写入写满就覆盖最旧的数据故障记录区一旦写入就不覆盖直到人工清除参数备份区存的是关键运行参数的快照掉电前更新。这个分区设计的核心思路是把不同写入频率和不同可靠性要求的数据分开管理。日志区写入最频繁用环形缓冲避免频繁擦除虽然 MRAM 不需要擦除但环形缓冲能控制写入地址的均匀分布故障记录区要求最高写入前先校验写入后回读确认配置区基本不动只在出厂标定时写一次。软件层面我封装了一个MRAM 驱动层向上提供MRAM_Read()、MRAM_Write()、MRAM_Erase()虽然 MRAM 不需要擦除但为了接口统一保留了这个函数内部实现为空操作或者写 0xFF这几个接口。再往上是一个存储管理层负责地址映射、数据校验CRC16、掉电保护逻辑。最上层是应用层直接调用存储管理层的接口不用关心底层是 MRAM 还是别的什么存储介质。这种分层设计的好处是以后如果换存储芯片只需要改驱动层上层代码基本不动。2. 硬件设计与核心细节解析2.1 电路连接与 PCB 布局要点先看硬件连接。MR25H40CDF 是 8 引脚封装关键引脚就那几个VCC、GND、SCLK、MOSI、MISO、CS。STM32F215RE 这边我用的 SPI1对应引脚是 PA5SCLK、PA6MISO、PA7MOSICS 用 PA4 做软件片选。接线本身不复杂但有几个细节不注意就会翻车。电源去耦这块MRAM 的 VCC 引脚旁边必须放一个0.1uF 的陶瓷电容越近越好最好在 2mm 以内。我试过把电容放在离芯片 1cm 远的地方高速读写时偶尔出现数据错误挪近之后问题消失。如果 PCB 空间允许再并一个 1uF 的电容对低频噪声抑制更好。工业现场电磁环境复杂电源上的毛刺很容易导致 MRAM 内部状态机异常去耦电容是最后一道防线。SPI 信号线的走线要注意等长和阻抗。SCLK 是时钟线最容易受干扰尽量走短线避免跨分割平面。MOSI 和 MISO 如果走线较长可以考虑串联 22 欧姆的电阻做阻抗匹配减少反射。CS 线虽然速率不高但也不能忽视因为它控制着 MRAM 的片选状态如果 CS 上有毛刺可能导致 MRAM 误响应。我在 CS 线上加了一个 10K 的上拉电阻确保在 STM32 复位期间 CS 保持高电平MRAM 不会误动作。接地方面MRAM 的 GND 引脚要就近打过孔连接到地平面不要走长线。如果板子上有多个地平面确保 MRAM 所在的地平面和 STM32 的地平面是同一个或者通过 0 欧姆电阻单点连接。工业设备里地环路问题很常见处理不好会导致通信不稳定。2.2 SPI 时序参数计算与配置MR25H40CDF 的最高 SPI 时钟频率是40MHz但实际能用多快取决于 STM32 的 SPI 外设配置和 PCB 信号质量。STM32F215RE 的 SPI1 挂在 APB2 总线上最高时钟 60MHz如果系统时钟 120MHzAPB2 分频系数为 2。SPI 的波特率预分频器可以设置成 2、4、8、16、32、64、128、256对应时钟就是 30MHz、15MHz、7.5MHz 等等。我最终选的是15MHz预分频系数 4。为什么不选 30MHz因为实测下来30MHz 时信号过冲比较明显眼图裕量不足长时间运行偶发误码。15MHz 虽然慢一半但稳定性大幅提升而且对于工业数据存储来说15MHz 下传输 64 字节只需要约 34 微秒完全满足实时性要求。这里的原则是在满足速度要求的前提下时钟越低越稳。时序参数方面MR25H40CDF 的数据手册给出了几个关键时间CS 建立时间tCSS最小 5nsCS 保持时间tCSH最小 5ns数据输出有效时间tDV最大 8ns。STM32 的 SPI 在 15MHz 下时钟周期约 66.7ns这些时序要求都能轻松满足。但要注意CS 拉低到第一个 SCLK 上升沿之间要有足够的延迟我一般留 100ns 以上确保 MRAM 内部状态机准备好。同样最后一个 SCLK 下降沿到 CS 拉高之间也要留延迟我留 50ns 以上。代码层面SPI 初始化的时候要设置好这些参数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; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE;注意NSS设成SPI_NSS_SOFT这样硬件 NSS 引脚就释放出来做普通 GPIO 用了。FirstBit设成MSB因为 MRAM 的指令和地址都是高位在前。2.3 MRAM 内部地址映射与指令集MR25H40CDF 的 512KB 地址空间是线性映射的地址从 0x00000 到 0x7FFFF。读写指令很简单读指令 0x03后面跟 3 字节地址然后就是数据输出写指令 0x02后面跟 3 字节地址然后就是数据输入。另外还有写使能指令 0x06和读状态寄存器指令 0x05。这里有个关键点MRAM 的写操作不需要先擦除但每次写之前要发写使能指令。写使能是一个独立的 SPI 事务拉低 CS发 0x06拉高 CS。然后再拉低 CS发 0x02 地址 数据拉高 CS。写使能标志在 CS 拉高后会自动清除所以每次写操作都要重新发。这个流程和 EEPROM 类似但 EEPROM 多了擦除步骤MRAM 省掉了。状态寄存器里有一个WELWrite Enable Latch位用来指示写使能是否生效。我一般会在写操作后读一下状态寄存器确认 WEL 被正确清除间接验证写操作被 MRAM 接受了。另外还有一个BPBlock Protect位可以硬件保护部分地址区域不被写入。工业场景里如果有些数据是出厂标定后就不允许改的可以用 BP 位做硬件写保护比软件保护更可靠。地址映射方面我的分区是这样的区域名称起始地址结束地址大小用途系统配置区0x000000x00FFF4KB设备ID、校准参数运行日志区0x010000x3FFFF252KB环形缓冲区故障记录区0x400000x4FFFF64KB故障码、时间戳参数备份区0x500000x7FFFF192KB关键参数快照这个分区不是随便定的。配置区放最前面因为出厂标定时只需要写这一小块日志区最大因为写入最频繁故障记录区独立出来方便快速定位参数备份区留足空间因为有些参数需要保存多个历史版本。3. 软件实现与核心环节实操3.1 底层驱动封装从 GPIO 到 SPI 事务底层驱动是整个项目的地基写得好后面省事写得烂后面天天救火。我的做法是把所有和 MRAM 相关的操作封装成几个原子函数上层只调这些函数不直接碰 SPI 外设。首先是CS 控制函数static inline void MRAM_CS_Low(void) { HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_RESET); } static inline void MRAM_CS_High(void) { HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_SET); }这两个函数看起来简单但有个细节CS 拉低和拉高之间要有足够的延时。我在MRAM_CS_Low()后面加了一个__NOP()循环大概几十纳秒确保 MRAM 检测到片选有效。同样MRAM_CS_High()之前也要确保最后一个时钟沿已经完成。然后是SPI 收发函数。STM32 的 HAL 库提供了HAL_SPI_Transmit()和HAL_SPI_Receive()但在 MRAM 这种需要连续收发发指令的同时收数据的场景下用HAL_SPI_TransmitReceive()更合适。不过 HAL 库的函数有超时机制在工业现场如果 SPI 总线被干扰可能会卡在超时等待里。我的做法是自己写一个轻量级的 SPI 收发函数直接操作寄存器不依赖 HAL 的超时逻辑static uint8_t SPI_TransferByte(uint8_t txData) { while (!(SPI1-SR SPI_SR_TXE)); *(__IO uint8_t *)SPI1-DR txData; while (!(SPI1-SR SPI_SR_RXNE)); return *(__IO uint8_t *)SPI1-DR; }这个函数的好处是没有超时不会卡死但前提是 SPI 外设已经正确初始化。如果 SPI 时钟没使能或者引脚配置错了这个函数会死循环。所以我在初始化阶段会先做一次自检确认 SPI 能正常收发。基于这个字节收发函数就可以封装 MRAM 的读写操作了。读操作的流程是拉低 CS - 发 0x03 - 发 3 字节地址 - 连续读 N 字节 - 拉高 CS。写操作的流程是拉低 CS - 发 0x06 - 拉高 CS - 拉低 CS - 发 0x02 - 发 3 字节地址 - 连续写 N 字节 - 拉高 CS。这里有个性能优化点如果连续写多个字节不需要每个字节都发一次写使能。写使能在一次 CS 有效期内只需要发一次然后可以连续写多个字节。我实测下来连续写 64 字节比逐字节写快将近一倍。3.2 数据校验与掉电保护机制工业场景里数据写入后必须能确认写对了否则存进去的是垃圾还不如不存。我的做法是每条记录后面跟一个 CRC16 校验值。写入的时候计算 CRC读出的时候重新计算并比对不一致就认为数据损坏。CRC16 的实现我用的是查表法速度快占用空间小。多项式选的是CRC-16/CCITT-FALSE0x1021初始值 0xFFFF。这个多项式在工业通信里很常见兼容性好。查表法需要一张 256 项的 uint16_t 表占 512 字节 Flash对于 STM32F215RE 的 1MB Flash 来说九牛一毛。static const uint16_t crc16_table[256] { ... }; uint16_t CRC16_Calculate(const uint8_t *data, uint32_t len) { uint16_t crc 0xFFFF; while (len--) { crc (crc 8) ^ crc16_table[((crc 8) ^ *data) 0xFF]; } return crc; }掉电保护是另一个核心机制。工业设备断电是常态不能假设每次写入都能完整完成。我的策略是双备份 写入标志。每条关键数据存两份分别放在两个不同的地址区域。写入的时候先写第一份再写第二份最后写一个写入完成标志。读取的时候先检查标志如果标志有效读第一份并校验如果第一份校验失败读第二份如果两份都失败返回错误。这个机制的关键在于写入顺序。必须先写数据再写标志。如果先写标志再写数据断电后标志有效但数据不完整就会读到脏数据。另外标志本身也要有校验我一般用取反双写的方式标志地址写 0xAA紧接着写 0x55读取时两个都对了才认为标志有效。还有一个细节MRAM 的写入是原子性的吗严格来说MRAM 的单字节写入是原子的但多字节写入不是。如果写到一半断电已经写进去的字节会保留没写的字节保持原值。所以对于多字节数据必须用上面的双备份机制来保证一致性。3.3 环形日志缓冲区的实现细节运行日志区我用的是环形缓冲区这是工业设备里最常用的日志存储方式。核心思路是写指针一直往前跑写到末尾就回到开头覆盖最旧的数据。读的时候从最旧的数据开始读一直读到写指针的位置。环形缓冲区的关键是读写指针的管理。我用两个 32 位变量分别记录写指针和读指针存在 MRAM 的固定位置。每次写入新日志前先更新写指针再写数据。这里有个顺序问题先更新指针还是先写数据我的做法是先写数据再更新指针。因为如果先更新指针再写数据断电后指针指向的位置可能是无效数据而先写数据再更新指针断电后指针还指向旧位置最多丢一条新日志不会读到脏数据。日志记录的结构是这样的typedef struct { uint32_t timestamp; // 时间戳秒 uint16_t event_code; // 事件码 uint8_t data[8]; // 附加数据 uint16_t crc; // CRC16校验 } LogEntry_t;每条日志 16 字节252KB 的日志区可以存约 16000 条。按每秒一条算可以存四个多小时。如果设备运行时间长可以适当降低记录频率或者扩大日志区。读取日志的时候从读指针开始逐条读取并校验 CRC。如果遇到 CRC 错误说明这条日志损坏可能是写入时断电跳过继续读下一条。如果连续多条 CRC 错误说明读指针可能失效了需要做一次全区域扫描找到最后一条有效日志的位置重建读指针。这里有个实操心得环形缓冲区的读写指针最好双备份存储并且每次更新指针时都做一次 CRC 校验。我早期版本没做指针备份有一次设备断电后指针区域数据损坏导致整个日志区无法读取只能格式化丢掉了所有历史日志。后来加了指针备份和校验再没出过这个问题。4. 常见问题排查与实战避坑指南4.1 SPI 通信失败排查流程SPI 通信失败是调试阶段最常见的问题表现是读出来的数据全是 0xFF 或者 0x00或者数据随机跳变。我总结了一套排查流程按顺序走一遍基本能定位到问题。第一步检查硬件连接。用万用表量一下 MRAM 的 VCC 是不是 3.3VGND 是不是通的。然后量 SCLK、MOSI、MISO、CS 四根线确认没有虚焊、短路。我遇到过好几次是 CS 线虚焊时通时不通折腾了半天。第二步用示波器看波形。重点看 SCLK 有没有输出频率对不对CS 的拉低和拉高时机对不对MOSI 上有没有数据。如果 SCLK 没有波形说明 SPI 外设没初始化好或者引脚配置错了。如果 CS 一直是低电平说明 GPIO 初始化有问题。第三步读 MRAM 的 ID。MR25H40CDF 有一个RDID 指令0x9F可以读出制造商 ID 和设备 ID。如果读 ID 都失败说明通信链路有问题如果读 ID 成功但读写数据失败说明指令或地址有问题。读 ID 是最基础的通信测试一定要先过这一关。第四步降低 SPI 时钟。如果高速下通信失败把时钟降到 1MHz 试试。如果低速能通高速不通说明是信号完整性问题需要检查 PCB 走线、去耦电容、阻抗匹配。第五步检查时序参数。用示波器测量 CS 拉低到第一个 SCLK 上升沿的时间确认大于 5ns测量最后一个 SCLK 下降沿到 CS 拉高的时间确认大于 5ns。如果不够在代码里加延时。下面这张表是我整理的问题速查表现象可能原因排查方法解决方案读数据全 0xFFMISO 未连接或 MRAM 未响应检查 MISO 连线读 ID修复连线确认 CS 有效读数据全 0x00MISO 被拉低或 MRAM 未上电检查 MISO 电平量 VCC修复短路确认供电数据随机跳变SPI 时钟太快或信号干扰降低时钟看波形降频加去耦电容写入后读回不一致写使能未生效或时序不足读状态寄存器 WEL 位确保写使能加延时偶发通信失败电源毛刺或地环路看电源纹波检查接地加滤波电容单点接地4.2 写入寿命与数据保持的实测数据MRAM 标称擦写寿命是无限次但实际能用多久我做了个长期测试。测试方法是让 STM32 以 1kHz 的频率连续写入同一个地址连续跑了一个月累计写入约 26 亿次。测试结束后读写功能正常数据保持正常。这个结果说明 MRAM 的寿命确实远超 EEPROM 和 Flash对于工业场景里那种高频写入的需求完全可以放心用。数据保持方面MRAM 是非易失性的断电后数据可以保持20 年以上室温。我做了个加速测试把写入了数据的 MRAM 放在 85 度恒温箱里烤了 1000 小时相当于室温下 10 年以上的老化读出来的数据依然正确。这个结果让我对 MRAM 在工业宽温环境下的可靠性有了信心。不过有个注意事项MRAM 对强磁场敏感。虽然 MR25H40CDF 内部有磁屏蔽但如果放在强磁场附近比如大功率电机、电磁铁旁边数据可能会受影响。我在一个电机控制项目里遇到过这个问题MRAM 离电机太近偶尔出现数据位翻转。后来把 MRAM 挪远了一点并加了磁屏蔽罩问题解决。所以 PCB 布局时MRAM 要远离强磁源。4.3 工业现场 EMC 问题的实战处理工业现场的电磁兼容性问题比实验室里复杂得多。我在一个变频器旁边跑这套存储方案时遇到了几个典型问题这里分享一下处理经验。第一个问题是 SPI 通信偶发失败。现象是设备运行几个小时后突然读不到 MRAM 数据重启后恢复正常。用示波器抓波形发现 SPI 时钟上有高频噪声叠加。原因是变频器的开关噪声通过空间耦合到了 SPI 线上。解决方案是在 SCLK 和 MOSI 上串联33 欧姆的磁珠并在 MRAM 的 VCC 上增加10uF 的钽电容。处理后连续运行一周没再出现通信失败。第二个问题是数据位翻转。现象是存储的参数偶尔变成错误值但 CRC 校验能发现。排查后发现是 MRAM 受到了静电放电ESD的影响。工业现场操作人员走动、设备启停都会产生 ESD。解决方案是在 MRAM 的 SPI 线上加TVS 二极管并在 PCB 边缘增加接地铜箔。TVS 选的是SM712专门用于差分信号线的 ESD 保护。第三个问题是地环路干扰。现象是设备接入不同的电源插座时通信稳定性不一样。原因是不同插座的地电位有差异形成了地环路电流。解决方案是单点接地把 MRAM 的 GND 和 STM32 的 GND 通过一个 0 欧姆电阻连接而不是直接铺铜连接。这样地环路电流不会流过信号参考地通信稳定性大幅提升。这些 EMC 处理经验教科书上不会写都是现场踩坑踩出来的。工业嵌入式开发和消费电子最大的区别就在这里实验室里跑通不算完现场跑稳才算数。4.4 代码移植与不同 STM32 系列的适配这套驱动代码最初是在 STM32F215RE 上写的后来项目需要移植到了 STM32F103 和 STM32H743 上。移植过程中发现几个需要注意的点。SPI 外设的差异。F2 和 F1 的 SPI 寄存器基本兼容但 H7 的 SPI 外设变化较大增加了 FIFO 和更复杂的时钟配置。移植到 H7 时SPI_TransferByte()函数需要修改因为 H7 的 SPI 有 16 字节 FIFO不能简单用TXE和RXNE标志来判断。我的做法是在 H7 上用 HAL 库的HAL_SPI_TransmitReceive()虽然效率略低但兼容性好。GPIO 速度配置。F2 的 GPIO 速度等级和 H7 不同H7 的 GPIO 可以配置到 Very High Speed。在 H7 上SPI 时钟可以跑到 50MHz 以上但 MRAM 最高只支持 40MHz所以还是要限制在 40MHz 以内。另外H7 的 GPIO 输出驱动能力更强如果走线较长可能需要串联电阻来抑制过冲。中断优先级。如果 SPI 传输放在中断里不同系列的 NVIC 优先级分组可能不同。F2 默认是 4 位优先级H7 支持更灵活的分组。移植时要确认优先级配置不会导致 SPI 传输被高优先级中断打断否则可能出现时序问题。HAL 库版本。不同系列的 HAL 库版本不同函数名和参数可能有细微差异。比如HAL_SPI_Init()在 F2 和 H7 上的参数结构体就不完全一样。移植时最好用 CubeMX 重新生成初始化代码然后把应用层代码移植过去不要直接复制初始化代码。这套代码后来还移植到了 GD32 和 APM32 等国产 MCU 上基本逻辑一样只是寄存器地址和 HAL 库不同。国产 MCU 的 SPI 外设和 STM32 高度兼容移植工作量不大。这也说明把驱动层和硬件层解耦的设计思路是对的换平台只需要改底层上层业务逻辑不用动。5. 性能优化与进阶玩法5.1 批量写入与 DMA 传输标准 SPI 传输是 CPU 轮询的方式每个字节都要 CPU 参与效率不高。如果数据量大比如要一次性写入几 KB 的参数备份轮询方式会占用大量 CPU 时间。这时候可以用DMA来加速。STM32F215RE 的 SPI1 支持 DMA 请求可以配置 DMA 通道来自动搬运数据。配置好之后CPU 只需要启动 DMA然后就可以去处理其他任务等 DMA 传输完成中断再回来处理。我实测下来用 DMA 传输 1KB 数据CPU 占用时间从约 500 微秒降到几乎为零效果非常明显。不过 DMA 也有坑。DMA 传输期间不能修改 SPI 配置否则会导致传输错误。另外DMA 传输完成后要及时关闭 DMA 通道否则下次传输可能出错。还有DMA 的传输完成中断优先级要设置好不能太高也不能太低太高会影响其他中断太低会导致响应延迟。对于 MRAM 的写入DMA 特别适合参数备份区的大块数据写入。日志区因为每次只写十几字节用 DMA 反而增加开销还是轮询更合适。所以我的做法是小数据用轮询大数据用 DMA根据数据量动态选择。5.2 双缓冲机制提升读写效率在某些高频读写场景下比如每毫秒要记录一次数据如果每次都直接写 MRAMSPI 传输的开销会累积。我的优化方案是双缓冲在 STM32 的 SRAM 里开辟两个缓冲区数据先写到缓冲区 A写满后切换到缓冲区 B同时把缓冲区 A 的数据批量写入 MRAM。这样 SPI 传输的次数大幅减少效率提升明显。双缓冲的关键是缓冲区大小的选择。太小了切换频繁太大了占用 SRAM 多。我一般选256 字节因为 MRAM 的页大小是 256 字节虽然 MRAM 没有页的概念但 256 字节是一个合理的批量单位。256 字节的缓冲区两个就是 512 字节对于 STM32F215RE 的 128KB SRAM 来说微不足道。切换逻辑用状态机实现缓冲区 A 填充中 - 缓冲区 A 写 MRAM - 缓冲区 B 填充中 - 缓冲区 B 写 MRAM - 回到缓冲区 A。写 MRAM 的操作放在低优先级任务里执行不阻塞高优先级的采集任务。这样既保证了数据不丢又不影响实时性。5.3 数据压缩与存储空间优化512KB 的 MRAM 看起来不小但如果日志记录频繁很快也会写满。我的优化方案是数据压缩。日志记录里的时间戳、事件码、附加数据很多都有规律可以用简单的压缩算法减少存储空间。比如时间戳如果日志是等间隔记录的可以只存起始时间戳 间隔而不是每条都存完整时间戳。事件码如果种类不多可以用位图表示一个字节存 8 个事件的状态。附加数据如果变化不大可以只存变化量而不是完整值。我实现了一个简单的RLE游程编码压缩对于连续相同的字节用计数值 字节值表示。实测下来对于典型的工业日志数据压缩率能达到50% 到 70%相当于存储空间翻倍。解压的时候按相反逻辑展开速度很快对 CPU 负担很小。当然压缩也有代价读取时需要解压增加了 CPU 开销。所以我的策略是日志区用压缩存储配置区和参数备份区不压缩。配置区和参数备份区数据量小压缩意义不大而且这些数据要求快速读取解压反而添乱。5.4 基于 MRAM 的掉电数据保护完整方案最后聊一个进阶玩法完整的掉电数据保护方案。工业设备里掉电保护不只是存数据那么简单还要保证数据的一致性和可恢复性。我的方案是三级保护。第一级是SRAM 缓存数据先写到 SRAM这是最快的第二级是MRAM 持久化定期或在掉电信号触发时把 SRAM 数据写入 MRAM第三级是备份电池如果掉电时间较长用备份电池维持 SRAM 供电等电源恢复后再写入 MRAM。掉电检测用PVD可编程电压检测器STM32 内部有这个功能可以设置一个电压阈值当 VCC 降到阈值以下时触发中断。在中断里立即启动 MRAM 写入把关键数据保存下来。PVD 的阈值要设置得比 MRAM 的最低工作电压高确保在 MRAM 失效之前完成写入。这里有个时间计算STM32F215RE 在 120MHz 下写入 256 字节到 MRAM 大约需要 200 微秒15MHz SPI每字节约 0.67 微秒加上指令和地址开销。电源从正常电压降到 MRAM 最低工作电压2.7V的时间取决于电源的滤波电容大小。如果电容是 100uF负载电流 50mA那么放电时间约2 毫秒从 3.3V 降到 2.7V。2 毫秒足够完成 256 字节的写入还有余量。但如果电容只有 10uF放电时间只有 200 微秒就非常紧张了。所以掉电保护电容要选足够大我一般用 220uF 以上。这个方案我在多个工业项目里用过实测掉电后数据零丢失恢复后系统能正确读取掉电前的状态。对于工业设备来说这个可靠性是必须的也是 MRAM 相比其他存储介质的核心优势所在。
返回列表