ARTICLE DETAIL

资讯详情

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

STM32F469II 搭配 SPI MRAM 实现工业级掉电安全存储

STM32F469II 搭配 SPI MRAM 实现工业级掉电安全存储 前阵子做工业网关的存储模块升级主控用的是 STM32F469II老板给的指标里有一条很扎眼设备可能在任意时刻掉电但运行中的事件记录、告警日志和最近一次配置快照必须完整保存。之前板子上用的 W25Q128 虽然便宜但每次写数据前要擦除、掉电正好撞上擦写就可能丢整页售后那边被 RMA 数据分析搞到快崩溃。我在这块板上直接换上了 Everspin 的 MR25H40CDF4Mbit SPI MRAM配合 STM32F469II 的 SPI 外设做数据落盘。这篇文章就把从选型、硬件连接到驱动实现、掉电保护、实测性能的完整过程写清楚重点包括那些数据手册没写、只有烧板子才会碰到的事情。如果你正在做需要高频掉电保存、频繁写入的应用比如工业数据采集器、电力设备故障记录、医疗设备参数存储或者只是受够了 Flash 的擦除和寿命问题这篇内容应该有参考价值。1. 为什么工业存储场景我会优先考虑 MRAM1.1 掉电保存的几种常见方案各有各的坑工业嵌入式里做数据落盘传统上绕不开这几种器件。第一种是 SPI NOR Flash。便宜、容量大、资料多问题在于它需要先擦除再写一个扇区擦除动作长达几十毫秒如果设备恰好在这几十毫秒内掉电扇区就可能处于半擦写状态数据直接完蛋。更麻烦的是 Flash 有 P/E 寿命限制常规 NOR Flash 标称十万次左右擦写对高频日志写入来说磨到寿命后期就会出现坏块、写入超时。第二种是 EEPROM。它比 Flash 灵活字节级擦写寿命通常能到一百万次但容量小、速度慢I2C 接口在高速写入场景很吃亏。而且 EEPROM 写入也有内部擦写时间掉电窗口同样是存在的。第三种是 SRAM 加电池或超级电容。速度确实快但电池在高温工业环境里会漏液、鼓包超级电容自放电快维护成本高。更关键的是数据是在意外掉电之后靠后端供电维持的如果掉电前连备份时机都没有照样丢。这些方案纠结下来核心矛盾在于介质要么有擦除步骤要么有寿命上限要么需要外部供电维持。MRAM 在这三个问题上正好都是干净利落的答案。1.2 MRAM 的物理原理一句话讲清MRAM 的全称是 Magnetoresistive Random Access Memory磁性随机存储器。它内部的核心单元是磁隧道结MTJ可以理解成两层磁性材料夹着一层极薄的绝缘层。两层磁化方向平行时电阻低反平行时电阻高用这个电阻状态表示 1 和 0。关键点在于数据是磁化方向决定的不是电荷。所以断电之后磁化方向不会变数据自然就保住了。写入时改变自由层的磁化方向也不需要像 Flash 那样靠隧道效应注入电荷到浮栅更不需要电荷泵产生高电压去擦除。这意味着什么意味着写入动作本身没有先擦后写的中间态写物理学上就是一次快速翻转时间由外部 SPI 时钟决定而不是由介质内部的擦写时间决定。我做日志型存储时MRAM 有一个特性比速度还要值钱写入过程没有 Flash 那种先变成 0xFF 再写数据的阶段掉电瞬间数据要么是旧的完整数据要么是新的完整数据。这极大降低了崩溃恢复的设计难度。1.3 MRAM 的成本能不能接受MRAM 价格确实比 Flash 贵同样 bit 数大概贵出几倍到十几倍。但你要算整体成本省掉了擦除均衡算法、掉电检测的复杂设计、留出 Flash 磨损余量的容量溢价再算上售后现场恢复数据的费用用在关键数据路径上其实很划算。工业产品的可靠性设计从来不是看器件单价的。2. MR25H40CDF 器件细节与指令集和串行 Flash 的本质差异2.1 封装和引脚MR25H40CDF 是 Everspin 的 4Mbit SPI MRAM工作电压 2.7V 到 3.6V直接对接 STM32F469II 的 3.3V 电平不用做电平转换。工业级温度范围 -40℃ 到 85℃。封装是常见的 8 脚 DFN 小封装引脚定义和很多 SPI Flash 是兼容排列的这倒是个好处布板时甚至可以和 Flash 的封装互换。引脚号名称方向作用1CS#输入片选低有效2SCK输入SPI 时钟3SI输入主出从入4SO输出主入从出5WP#输入写保护低电平禁止写6VSS电源地7HOLD#输入暂停通信低电平冻结8VDD电源3.3V注意 WP# 和 HOLD# 这两个脚引脚排列上靠近地脚很容易让人误以为它们不用管。后面我会专门讲这两脚悬空会出什么问题。2.2 指令集和串行 Flash 的对比MR25H40CDF 的指令集比 W25Q 系列简单很多最小闭环就四个指令。指令操作码说明WREN0x06写使能之后才能写WRDI0x04写禁止READ0x03读数据指令24位地址连续数据流WRITE0x02写数据指令24位地址连续数据流RDSR0x05读状态寄存器写操作格式是CS# 拉低 - 发 0x02 - 发 24 位地址 - 连续发数据 - CS# 拉高。读操作格式同理只是不需要 WREN。就这么简单。对比之下W25Q 写一个扇区要经历 WREN - 擦除扇区(0x20) - 轮询 BUSY - WREN - 页编程(0x02) - 轮询 BUSY这一套流程下来光擦除就几十毫秒。而 MR25H40CDF 直接把擦除这个概念从驱动里删掉了写就是写。2.3 状态寄存器没有 BUSY 位这对驱动逻辑影响非常大这是嵌入式工程师最容易踩着的一个点。W25Q 系列的 RDSR 返回的状态寄存器里bit0 是 BUSY忙标志写完后要轮询它直到清零否则不能发下一条指令。包括我一开始也犯了想当然的错误以为 MRAM 也会有 WIP 位。MR25H40CDF 的状态寄存器不一样它的 bit0 是 WEL写使能锁存bit1 到 bit7 保留。关键是它没有 BUSY/WIP 位因为写入不需要介质内部时间CS# 拉高那一瞬间数据就落定了。所以驱动里完全没有轮询忙这一步写完拉高 CS#下一笔直接可以继续。这个差异后面我会专门放到踩坑部分讲。2.4 64 字节逻辑页边界无擦除但有回绕MRAM 虽然不需要擦除但内部逻辑上还是按 64 字节组织成页。跨页写入时如果超出当前 64 字节边界地址会回绕到本页起始地址而不是自动进位到下一页。举个例子页大小为 64 字节页基地址的低 6 位全是 0。假设从地址 0x123F 开始写 4 个字节第一个字节写到 0x123F第二个字节开始地址回绕到 0x1200再写 0x1201、0x1202。也就是说单次 WRITE 指令最多连续写到一个 64 字节页的边界跨页必须拆成多条指令。这和 Flash 的页编程限制有点像但本质不同Flash 是因为存储阵列分扇区、需要擦除所以 256 字节一页限制写入MRAM 是因为内部行缓冲的编址设计超过 64 字节逻辑页就会回绕。两边的共性结论是一样的驱动层必须自己做跨页切分。3. STM32F469II 硬件连接SPI 规划与工业级细节3.1 SPI 外设选型与时钟分配STM32F469II 上 SPI 外设很多一共 6 个。需要注意它们挂在不同的总线时钟上SPI1 和 SPI6 挂在 APB2在 180MHz 主频时 APB2 最高 90MHzSPI2 到 SPI5 挂在 APB1APB1 最高 45MHz。SPI 主机波特率最大是 PCLK 的一半所以 SPI1 理论上最大跑到 45MHzSPI2 最大 22.5MHz。MR25H40CDF 的数据手册标准最高 SPI 时钟是 40MHz。这里就有个尴尬点SPI1 在 90MHz APB2 下分频系数只能是 2 的幂最低分频是 2 分频得 45MHz超过器件上限分频 4 得 22.5MHz又比较保守。实际项目里我直接选择了 SPI1分频 422.5MHz。为什么不硬上 45MHz第一超过手册标称值工业环境下温漂、噪声一叠加时序裕量不够第二MRAM 最大的优势是省掉了 Flash 的擦除时间22.5MHz 下写 4KB 也才 1.6ms 左右完全够用。真正的速度瓶颈从来不在 SPI 频率而在你自己的业务逻辑。3.2 引脚复用和冲突排查F469II 是 BGA176 封装引脚密度高几乎每个引脚都有多个可选复用功能。我在这种高密度板卡上踩过不少复用冲突的坑尤其是当你同时用了 LTDCLCD 控制器、FMC外部存储控制器、以太网 MAC 的时候GPIO 资源相当紧张。我这块板用的分配是SPI1_SCK 在 PA5SPI1_MISO 在 PA6SPI1_MOSI 在 PA7片选 CS# 单独占用一个普通 GPIO比如 PG6软件控制。PA5/PA6/PA7 这一组是 SPI1 的标准引脚也是比较顺手的一组但要注意它们和 DAC、ADC、TIM2 的部分通道复用不能和这些外设业务冲突。在 CubeMX 里配置的时候先把所有既定功能LTDC、FMC、UART、以太网等都选完再打开 SPI1看 CubeMX 会不会弹出冲突报告。如果冲突优先考虑把 SPI 移到 SPI2 的 PB13/PB14/PB15 上代价是 APB1 时钟下最高只有 22.5MHz。对于 MRAM 这种本身就 40MHz 上限的器件损失不大。3.3 外围电路CS/WP/HOLD 上拉、去耦和布局外围看起来简单但工业环境里这些细节决定可靠性。CS# 必须接上拉电阻10kΩ 左右接到 VDD。STM32 上电过程中 GPIO 是三态如果 CS# 浮空MRAM 可能误判片选有效把 SCK 上的随机电平当成指令执行。虽然概率不高但工业设备不能在概率上赌。WP# 和 HOLD# 虽然芯片内部有上拉但内部上拉非常弱工业现场的电磁干扰很容易把它拽到低电平。WP# 一旦拉低所有的写指令都会被忽略表现就是写入时而成功时而失败玄学问题。HOLD# 拉低则会冻结通信SCK 继续走但 SO 三态通信直接卡死。这两脚必须外部上拉 10kΩ。电源去耦VDD 引脚旁边放一颗 0.1uF 陶瓷电容尽量贴近引脚如果板子电源纹波偏大再加一颗 1uF。SCK 和 SI 走线控制短一点避免和继电器驱动线、电机驱动线并行长距离走线。SO 是弱驱动输出走线太长容易耦合噪声PCB 上把它当敏感信号处理。3.4 掉电监测配合写保护PVD 时序MRAM 不怕写一半掉电但工业设备想在掉电瞬间把最后的运行参数可靠落盘还是需要掉电检测的。STM32F469II 内置 PVD可编程电压检测器可以设定电压阈值当 VDD 跌落到阈值以下时触发中断。我把 PVD 阈值配在 2.9V。3.3V 电源掉电到 2.9V 之间大概还有 0.4V 的余量按常规电源放电曲线可以撑几百微秒到几毫秒足够完成一个受控停机动作。PVD 中断里要做三件事停止新的 DMA 写请求把 CS# 拉高释放 MRAM同时确保 WP# 处于高电平如果是 Flash 方案这一时刻你还要赶着保存上下文而 MRAM 方案里数据本来就在介质里只需要停止写入即可。PVD 初始化代码很简单void PVD_Init(void) { PWR_PVDTypeDef PVD_Config; PVD_Config.PVDLevel PWR_PVDLEVEL_0; // 约2.9V PVD_Config.Mode PWR_PVD_MODE_IT_RISING_FALLING; HAL_PWR_ConfigPVD(PVD_Config); HAL_PWR_EnablePVD(); HAL_NVIC_SetPriority(PVD_IRQn, 3, 0); HAL_NVIC_EnableIRQ(PVD_IRQn); } void PVD_IRQHandler(void) { HAL_PWR_PVD_IRQHandler(); }这里我建议把掉电处理程序放在 PVD 回调里不要在这时候再去读传感器或者操作 Flash行为越短越好。MRAM 的好处是你的核心数据已经存在介质里了掉电要做的只是别再写了不是赶紧备份。4. 软件驱动从 HAL 到可复用的存储组件4.1 SPI 初始化与基础收发SPI 配置我用的标准参数主机模式、8 位数据、MSB 先行、SPI Mode 0CPOL0CPHA0。MR25H40CDF 也支持 Mode 3但固定用 Mode 0 能少一个变量。CS# 用普通 GPIO 软件控制不使用 SPI 的硬件 NSS。硬件 NSS 在半主机模式下有时会自动拉低拉高典型的看起来没问题但偶尔多发一帧问题软件控制最可控。初始化框架void MRAM_SPI_Init(void) { 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; // 22.5MHz hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1); }4.2 完整写流程与页边界切分MR25H40CDF 每次写操作之前都必须发 WREN写指令执行完CS# 拉高之后 WEL 位会自动清零所以每一笔独立的写帧都要重新 WREN。这是个好习惯不要试图省掉。我封装了一个自包含的写接口内部自动处理 64 字节页边界切分#define MRAM_PAGE_SIZE 64 static void MRAM_WriteEnable(void) { uint8_t cmd 0x06; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); MRAM_CS_HIGH(); } static void MRAM_WritePage(uint32_t addr, const uint8_t *data, uint32_t len) { uint8_t cmd[4]; cmd[0] 0x02; // WRITE cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 4, 100); HAL_SPI_Transmit(hspi1, (uint8_t *)data, len, 1000); MRAM_CS_HIGH(); } void MRAM_Write(uint32_t addr, const uint8_t *data, uint32_t len) { while (len 0) { uint32_t page_remain MRAM_PAGE_SIZE - (addr (MRAM_PAGE_SIZE - 1)); uint32_t chunk (len page_remain) ? len : page_remain; MRAM_WriteEnable(); MRAM_WritePage(addr, data, chunk); addr chunk; data chunk; len - chunk; } }读操作简单得多不需要页切分连续读可以直接跨页只要 CS# 不拉高地址会自动递增void MRAM_Read(uint32_t addr, uint8_t *data, uint32_t len) { uint8_t cmd[4]; cmd[0] 0x03; // READ cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 4, 100); HAL_SPI_Receive(hspi1, data, len, 1000); MRAM_CS_HIGH(); }4.3 DMA 批量读写但读方向要注意 MOSI 数据如果是批量日志读写用轮询方式会占用 CPU。F469II 的 SPI1 可以接 DMA写 MRAM 用 DMA Tx 就行。但读方向有个隐藏细节SPI 是全双工协议主机要从 MRAM 读数据必须持续提供时钟。时钟从哪里来来自主机的 MOSI 发送动作。F4 系列如果只开接收 DMA发送端虽然也发数据但发的内容是 SPI 数据寄存器里的旧值不一定是确定值严格来说不应该依赖这个行为。所以我的读数据 DMA 是这样做的同时启动 TransmitReceiveTX 缓冲全部填 0xFF实际只关心 RX 数据。这样时钟完全由确定的发送动作产生彻底消除模糊行为void MRAM_Read_DMA(uint32_t addr, uint8_t *data, uint32_t len) { uint8_t cmd[4] {0x03, (addr16)0xFF, (addr8)0xFF, addr0xFF}; uint8_t *txbuf tx_buf; // 调用前填充0xFF, 长度len MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 4, 100); HAL_SPI_TransmitReceive_DMA(hspi1, txbuf, data, len); while (HAL_SPI_GetState(hspi1) ! HAL_SPI_STATE_READY) ; MRAM_CS_HIGH(); }实际项目里我不会在 while 里死等而是用信号量或事件标志DMA 完成中断里释放。上面的写法只用于说明帧结构。4.4 小型存储组件带 CRC 的记录头MRAM 本身再可靠掉电瞬间如果刚好写到记录的一半数据还是会出现半条记录状态。MRAM 解决的是一般不会的整块变 0xFF但解决不了我只写了一半。所以记录格式里还是要有完整的完整性标志。我的日志区格式是这样设计的typedef struct __attribute__((packed)) { uint32_t magic; // 固定魔数如0xA55A5AA5 uint16_t version; uint16_t length; // 数据长度 uint32_t crc32; // 数据区CRC32 uint32_t seq; // 序号用于乱序检测 } LogHeader;写日志项时先把数据和 header除 magic 外写到目标区最后再写 magic。读取时先检查 magic 是否为有效值若无效则说明这是一条没写完的记录直接跳过。CRC32 用于检测数据区是否完整。seq 用于日志顺序号排查是不是因为逻辑错误导致旧新数据混排。这套设计的思路是从 Flash 日志系统借鉴来的。虽然 MRAM 不需要磨损均衡但记录完整性的概念不能丢。5. 实测数据写入延迟、功耗和高温表现5.1 写性能对比MR25H40CDF 对 W25Q128我在同一块 F469II 板子上跑了对比实验W25Q128 用 48MHz SPIMR25H40CDF 用 22.5MHz SPI。所有操作都通过逻辑分析仪计时。操作W25Q12848MHzMR25H40CDF22.5MHz说明写1字节含擦除约10ms小于10usFlash需要先擦整个扇区写4KB连续数据约70ms约1.6msMRAM省掉擦除时间读4KB连续数据约0.9ms约1.9msMRAM读速受限于22.5MHz擦除后写可靠性掉电可能半擦无擦除概念关键差异写 4KB 的差距接近 44 倍这个数字还不算夸张因为 Flash 的 70ms 里大头是 4KB 扇区的擦除时间。如果你的业务是高频记录比如每秒写一条 64 字节日志Flash 方案每分钟就要擦一次扇区写放大就是个问题MRAM 方案完全不需要考虑这个。5.2 掉电测试连续 2000 次断电写入我在实验室做了断电压力测试用一个继电器控制板级电源随机时间点断开同时在断开前沿持续向 MRAM 日志区写数据。测试做了 8 小时大约 2000 次随机断电。上电后检查日志区配合上面的 magic CRC 机制结果记录损坏数量为 0。有几条记录停在 header 写入前表现为旧数据但没有任何一条出现新数据半写、或者整片浮空的情况。对比之前同一块板子使用 W25Q128 时做的类似测试断电经常撞上擦除窗口magic 直接丢失的情况大概每几十次就发生一次可靠性和 MRAM 方案差了一个量级。这还是在没有做复杂掉电备份设计的前提下。5.3 高温与寿命验证我把板子放进 70℃ 高温箱跑了 48 小时随机地址反复读写。MRAM 没有出现任何写入失败。Everspin 的 MRAM 标称读写耐久性达到 10^16 次量级数据保持能力在 20 年以上。十万次的 Flash 相比MRAM 基本上不需要磨损均衡。当然这种标称寿命没法在工程时间尺度内完整验证但至少有一点可以确定高频写入不再是寿命瓶颈系统里少了一个需要定期监控的耗材。6. 实际工程中踩过的坑6.1 直接复用 Flash 驱动状态寄存器语义错位我最早想偷懒把 W25Q 的驱动改个命令码就拿来用。写操作后习惯性地发了 RDSR 指令轮询 bit0 等待 BUSY 清零。结果程序卡死在等待 BUSY 循环里。原因上面已经说过了MR25H40CDF 的状态寄存器 bit0 是 WEL不是 BUSY。写入完成后 WEL 是自动清除了但我轮询的时间点在实际写入后此时 WEL 可能已经回到 0而我在等待的bit0 变成 0条件满足后又发一轮死等实际上问题出在第一次轮询时 WEL 还可能是 1——具体表现就是读回来一直是 1因为在 WREN 之后、没有新动作之前 WEL 保持置位。无论如何这个轮询逻辑本身就是多余的。正确做法是根本没有 BUSY 位写完 CS# 拉高就完成。这也是 MRAM 驱动和 Flash 驱动在架构上最重要的差别。6.2 跨页回绕导致日志顺序错乱我的日志写入接口一开始没有做页切分只在应用层按固定长度写。当一条日志恰好从 0x123E 之类的页尾地址开始时地址回绕后半段跑到了 0x1200日志读出来顺序错乱看起来像是数据被改写了。这个问题排查了一段时间因为单独读地址 0x123E 和 0x1200 都正常完全符合预期但组合起来不对。后来翻了数据手册关于页边界的说明才意识到MRAM 单次 WRITE 跨 64 字节边界时地址回绕到页首不是进位到下一页。解决方式就是前面 4.2 节的 MRAM_Write 循环切分。页边界逻辑必须在底层驱动模块里解决不要指望所有调用方都会记得这个约束因为迟早有人会忘。6.3 DMA 与 LTDC 总线仲裁导致的偶发卡死板子接了一块 LCD跑着 LTDC 和 DMA2D。最开始 DMA 写 MRAM 在显示跑起来之前一切正常显示一开就偶发卡死。逻辑分析仪看波形SPI 时钟短暂停顿后又恢复但帧已经乱了。F469II 内部总线仲裁机制中DMA2D/LTDC 这类高带宽外设突发访问时SPI DMA 的请求可能被延迟我的代码在 DMA 完成中断里立刻启动下一笔传输没有等待 SPI 完成当前传输结果偶发状态竞争。修复方案是给每一笔 DMA 传输加完成状态检查在启动下一笔之前必须确认上一次传输真正结束同时增加超时保护。稳定后再没复现过。这个坑不存在于 MRAM 本身而是 F469II 整体系统设计问题。但嵌入式工程师最容易犯的错就是默认DMA 中断里发起下一笔 DMA 一定安全实际上总线仲裁和 DMA 状态机之间还有空隙。6.4 WP# 悬空带来的玄学问题手焊的样板上WP# 脚没接任何东西只是让焊盘空着。结果出现无规律的写入失败有时用手靠近芯片也能复现。排查了很久最终用示波器量到 WP# 上有几十毫伏的噪声尖峰在特定电磁干扰下会被拉到低电平芯片直接拒绝写入。数据手册说 WP# 内部有上拉但内部上拉通常只有几十千欧到几百千欧对抗工业现场的耦合噪声不够。解决办法是外部加 10kΩ 上拉问题消失。这个坑给我们的教训是MRAM 本身可靠性再高外围引脚一旦悬空照样会被环境干扰打败。可靠性是整个链路的事情不是芯片单点的事情。就我个人而言这套组合现在基本是我做工业数据落盘的首选方案STM32F469II 负责应用和显示MR25H40CDF 负责关键数据驱动代码量不大不管理擦除、不考虑磨损均衡、不用担心掉电窗口。如果你后续项目需要更大容量Everspin 还有 8Mbit 和 16Mbit 的 SPI MRAM驱动只需要改一改地址位宽。唯一要记住的还是那句话先理解存储介质的本质再动手写驱动数据手册里每一行都有它存在的理由。
返回列表