ARTICLE DETAIL

资讯详情

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

MRAM嵌入式存储实战:MR25H40CDF与STM32F723ZE SPI驱动详解

MRAM嵌入式存储实战:MR25H40CDF与STM32F723ZE SPI驱动详解 1. 为什么 MRAM 在嵌入式存储里越来越受关注搞嵌入式存储的兄弟应该都有体会选存储芯片这件事早就不只是“容量够不够、价格便不便宜”那么简单了。EEPROM 写入慢、寿命有限NOR Flash 擦除块大、写入前得先擦、擦写次数也就十万次量级FRAM 虽然快但容量小、价格高。这几年 MRAM磁性随机存储器慢慢进入大家的视野尤其是 Everspin 的 MR25H40 系列在工业控制、电力监测、轨道交通、医疗设备这些对数据可靠性要求极高的场景里用得越来越多。我这次拿到的组合是MR25H40CDF STM32F723ZE。前者是一颗 4Mbit512KB的 SPI 接口 MRAM后者是 ST 家带 Cortex-M7 内核、主频能跑到 216MHz 的高性能 MCU。这个搭配很有意思MCU 性能足够强能跑复杂的控制算法和通信协议栈MRAM 则负责把关键数据以“非易失、近乎无限次写入、写入即生效”的方式存下来。两者通过 SPI 总线连接硬件上简单软件上也不复杂但里面有不少细节值得掰开讲。这篇文章我打算从选型逻辑、硬件连接、SPI 驱动配置、读写时序、数据可靠性设计、常见问题排查几个维度把整个方案完整地过一遍。不管你是刚接触 MRAM 的新手还是已经在用 SPI Flash 想换方案的老手应该都能从中找到可以直接抄作业的部分。文章里涉及的具体参数和代码都是基于我实际调试过的工程整理出来的不是纸上谈兵。提示MRAM 和 Flash 最大的区别在于“写入不需要擦除、写入次数近乎无限、掉电数据不丢”。理解这一点后面很多设计思路就顺了。2. 方案整体设计与选型思路拆解2.1 为什么是 MR25H40CDF 而不是 SPI Flash先说说这颗 MRAM 的基本盘。MR25H40CDF 是 4Mbit 容量也就是 512K 字节组织方式是 512K × 8 位。SPI 接口支持最高 40MHz 时钟工作电压 2.7V 到 3.6V工业级温度范围 -40℃ 到 85℃。这些参数看起来平平无奇但真正让它区别于 Flash 的是下面几点。第一写入前不需要擦除。SPI Flash 写一个字节之前必须先擦除整个扇区通常 4KB擦除操作耗时动辄几十毫秒而且擦除期间整个扇区都不能访问。MRAM 没有这个概念直接写就行写一个字节和写一页在操作流程上没有本质区别。第二写入寿命近乎无限。Flash 的擦写寿命一般是 10 万次EEPROM 好一点能到 100 万次而 MRAM 的写入次数可以到 10^14 甚至更高量级。这意味着你可以把它当成“掉电不丢的 RAM”来用频繁记录运行日志、状态变量、计数器完全不用担心寿命问题。第三写入速度极快。MRAM 的写入是纳秒级的通过 SPI 接口传输时瓶颈在 SPI 时钟而不是存储介质本身。40MHz 时钟下理论传输速率 5MB/s实际有效速率也能到 3MB/s 以上。相比之下Flash 的页写入时间虽然也不长但加上擦除时间整体写入吞吐就差了一个数量级。第四数据保持时间长。MRAM 的数据保持能力在 20 年以上而且不像 Flash 那样存在电荷泄漏导致的长期数据丢失风险。对于需要长期保存校准参数、设备序列号、累计运行时间的场景这一点很关键。当然MRAM 也不是没有缺点。价格比同容量 Flash 贵不少容量密度也低512KB 基本就是常见型号的上限了。所以我的选型逻辑很明确只把最关键的、需要频繁写入的、掉电绝对不能丢的数据放在 MRAM 里大块的非频繁写入数据还是交给 Flash 或者外部存储。这样既发挥了 MRAM 的优势又控制了成本。2.2 STM32F723ZE 的 SPI 外设能力评估STM32F723ZE 属于 STM32F7 系列Cortex-M7 内核216MHz 主频带 FPU 和 DSP 指令集性能上跑 SPI 驱动绰绰有余。它有多达 6 个 SPI/I2S 接口其中 SPI1 到 SPI6 分布在不同的总线上挂载的 DMA 通道也不一样。我选的是SPI2原因有几个。SPI2 挂在 APB1 总线上APB1 的时钟最高 54MHz经过分频后 SPI2 的时钟可以配置到 27MHz 或者 13.5MHz对于 MR25H40CDF 的 40MHz 上限来说27MHz 是一个稳妥的选择。SPI1 挂在 APB2 上时钟更高但 SPI1 的引脚经常和别的外设复用冲突布线的时候要多考虑一层。SPI2 的引脚是 PB13SCK、PB14MISO、PB15MOSI加上一个普通的 GPIO 做片选布局上比较清爽。另外STM32F723ZE 的 SPI 支持硬件 NSS 管理也支持软件片选。我实际用的是软件片选原因后面会详细说。DMA 方面SPI2 的 RX 和 TX 分别对应 DMA1 的 Channel 3 和 Channel 4配置起来很直接。2.3 整体数据流与存储分区设计在动手写代码之前我先规划了一下 MRAM 的地址空间怎么用。512KB 看着不大但如果不做分区后期维护会很痛苦。我的分区方案是这样的地址范围大小用途写入频率0x00000 - 0x00FFF4KB设备信息区序列号、型号、硬件版本出厂一次0x01000 - 0x01FFF4KB校准参数区传感器零点、增益系数偶尔0x02000 - 0x02FFF4KB配置参数区通信地址、波特率、阈值偶尔0x03000 - 0x7FFFF500KB运行日志与状态记录区频繁设备信息区和校准参数区都做了双备份每个区存两份读取的时候做校验如果主份损坏就自动切换到备份。运行日志区采用环形缓冲区的方式写满之后从头覆盖配合一个写入指针记录当前位置。这个指针本身也存在 MRAM 里每次写入日志后更新保证掉电后能接着上次的位置继续写。注意MRAM 虽然写入次数近乎无限但并不意味着可以无限制地高频写入。SPI 总线的带宽是有限的如果日志写入频率太高会占用大量 CPU 时间和总线资源。我的做法是日志先写到 RAM 缓冲区攒够一定数量或者定时触发再批量写入 MRAM。3. 硬件连接与 SPI 时序关键细节3.1 引脚连接与 PCB 布局要点MR25H40CDF 是 8 引脚封装引脚定义很标准CS、SO、WP、VSS、SI、SCK、HOLD、VDD。和 STM32F723ZE 的连接方式如下CS接 STM32 的 PB12普通 GPIO推挽输出初始高电平SCK接 PB13SPI2_SCK复用推挽MISO接 PB14SPI2_MISO复用推挽或者上拉输入MOSI接 PB15SPI2_MOSI复用推挽WP和HOLD都接 VDD禁用写保护和保持功能简化控制VDD接 3.3VVSS接地这里有几个硬件上的坑我踩过值得说一下。第一CS 引脚一定要加上拉电阻10K 就行。STM32 复位期间 GPIO 是浮空输入状态如果没有上拉CS 可能被拉低导致 MRAM 误以为被选中在总线上的其他设备通信时产生冲突。第二SCK 和 MOSI 上可以串 22Ω 到 33Ω 的电阻抑制过冲和振铃尤其是在走线比较长或者频率比较高的时候。第三去耦电容不能省VDD 和 VSS 之间放一个 0.1μF 的陶瓷电容尽量靠近芯片引脚。PCB 布局方面SPI 的四根信号线尽量等长、短、直远离高频或者大电流的走线。如果 MRAM 和 MCU 不在同一块板子上排线不要太长超过 10cm 就要考虑信号完整性问题了。3.2 SPI 模式选择与时钟配置计算MR25H40CDF 支持 SPI 模式 0CPOL0CPHA0和模式 3CPOL1CPHA1。我选的是模式 0这也是最常用的配置。在 STM32CubeMX 里配置 SPI2 的时候参数这样设Clock Polarity (CPOL)LowClock Phase (CPHA)1 EdgeData Size8 BitsFirst BitMSB FirstNSSSoftwareBaud Rate Prescaler4CRC CalculationDisabled时钟计算是这样的SPI2 挂在 APB1 总线上STM32F723ZE 的 APB1 时钟我配置的是 54MHz。分频系数选 4得到 SPI 时钟 54MHz / 4 13.5MHz。这个频率下传输一个字节需要 8 个时钟周期也就是 8 / 13.5MHz ≈ 0.59μs。传输 512KB 全部数据理论上需要 512 × 1024 × 0.59μs ≈ 309ms。实际使用中不会一次性读写这么多通常是几十字节到几KB的操作耗时在微秒到毫秒级别。如果你想跑更快可以把分频系数改成 2得到 27MHz。但这时候要注意 PCB 走线和信号质量如果发现误码率上升就要降回 13.5MHz 或者优化硬件。我实测下来13.5MHz 在普通双面板上非常稳27MHz 在四层板上也没问题。3.3 软件片选与硬件片选的取舍STM32 的 SPI 外设支持硬件 NSS 管理也就是由硬件自动控制片选信号。听起来很方便但实际用起来有几个限制。第一硬件 NSS 模式下片选信号的时序是固定的不能灵活控制片选和第一个时钟沿之间的间隔。第二如果总线上挂多个 SPI 从设备硬件 NSS 需要额外的逻辑电路来译码反而更麻烦。第三MR25H40CDF 对片选建立时间和保持时间有要求软件片选可以精确控制这些时间。所以我用的是软件片选把 PB12 配置成普通推挽输出在每次 SPI 传输前后手动拉低和拉高。代码大概长这样#define MRAM_CS_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET) #define MRAM_CS_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET) void MRAM_Select(void) { MRAM_CS_LOW(); // 片选建立时间MRAM 要求最小 5ns这里加一点余量 __NOP(); __NOP(); __NOP(); __NOP(); } void MRAM_Deselect(void) { // 片选保持时间确保最后一个时钟沿被正确采样 __NOP(); __NOP(); __NOP(); __NOP(); MRAM_CS_HIGH(); }提示片选拉低之后不要立刻发时钟留几个 NOP 的建立时间传输结束后也不要立刻拉高片选留几个 NOP 的保持时间。虽然 MRAM 手册上写的最小时间只有几纳秒但在高频下留余量总是没错的。4. MRAM 驱动实现与核心操作流程4.1 基本读写命令与状态寄存器操作MR25H40CDF 的命令集很简洁常用的就几条命令名称命令码功能说明WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据和 Flash 不同的是MRAM 的 WRITE 命令不需要先擦除但仍然需要先发 WREN。这是很多新手容易忽略的地方以为 MRAM 可以像 RAM 一样直接写结果发现写不进去。WREN 的作用是设置内部写使能锁存器每次写操作之前都要发一次。状态寄存器里主要关注 WEL写使能锁存和 WIP写进行中两个位。WEL 在 WREN 之后置 1在写操作完成后自动清零。WIP 在写操作期间为 1完成后为 0。实际使用中由于 MRAM 写入速度极快WIP 几乎瞬间就变回 0所以通常不需要轮询等待。读数据的流程是拉低 CS → 发 READ 命令0x03→ 发 3 字节地址24 位地址因为 512KB 需要 19 位地址高位补 0→ 连续读取数据 → 拉高 CS。写数据的流程是拉低 CS → 发 WREN0x06→ 拉高 CS → 拉低 CS → 发 WRITE0x02→ 发 3 字节地址 → 连续写入数据 → 拉高 CS。注意 WREN 和 WRITE 之间必须拉高一次 CS这是 MRAM 的协议要求不能把 WREN 和 WRITE 放在同一个片选周期里。4.2 单字节与页写入的代码实现先看单字节写入的实现void MRAM_WriteByte(uint32_t addr, uint8_t data) { uint8_t cmd; uint8_t addr_buf[3]; addr_buf[0] (addr 16) 0xFF; addr_buf[1] (addr 8) 0xFF; addr_buf[2] addr 0xFF; // 发送 WREN MRAM_Select(); cmd 0x06; HAL_SPI_Transmit(hspi2, cmd, 1, 100); MRAM_Deselect(); // 发送 WRITE 命令 地址 数据 MRAM_Select(); cmd 0x02; HAL_SPI_Transmit(hspi2, cmd, 1, 100); HAL_SPI_Transmit(hspi2, addr_buf, 3, 100); HAL_SPI_Transmit(hspi2, data, 1, 100); MRAM_Deselect(); }页写入其实就是把上面的单字节数据换成缓冲区连续发送多个字节。MRAM 没有页边界限制可以跨页连续写入地址会自动递增写满整个芯片后回绕到 0。这一点比 Flash 方便很多Flash 跨页写入需要分段处理。批量写入的代码void MRAM_WriteBuffer(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd; uint8_t addr_buf[3]; addr_buf[0] (addr 16) 0xFF; addr_buf[1] (addr 8) 0xFF; addr_buf[2] addr 0xFF; MRAM_Select(); cmd 0x06; HAL_SPI_Transmit(hspi2, cmd, 1, 100); MRAM_Deselect(); MRAM_Select(); cmd 0x02; HAL_SPI_Transmit(hspi2, cmd, 1, 100); HAL_SPI_Transmit(hspi2, addr_buf, 3, 100); HAL_SPI_Transmit(hspi2, buf, len, 100); MRAM_Deselect(); }读操作更简单不需要 WRENvoid MRAM_ReadBuffer(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd; uint8_t addr_buf[3]; addr_buf[0] (addr 16) 0xFF; addr_buf[1] (addr 8) 0xFF; addr_buf[2] addr 0xFF; MRAM_Select(); cmd 0x03; HAL_SPI_Transmit(hspi2, cmd, 1, 100); HAL_SPI_Transmit(hspi2, addr_buf, 3, 100); HAL_SPI_Receive(hspi2, buf, len, 100); MRAM_Deselect(); }4.3 DMA 加速大批量数据传输当需要读写的数据量比较大时用 HAL_SPI_Transmit 和 HAL_SPI_Receive 会占用大量 CPU 时间。这时候可以上 DMA。STM32F723ZE 的 SPI2 对应 DMA1 Channel 3RX和 Channel 4TX配置好之后调用 HAL_SPI_Transmit_DMA 和 HAL_SPI_Receive_DMA 就行。不过 DMA 和软件片选配合的时候要注意DMA 传输完成中断里才能拉高片选不能在调用 DMA 发送函数之后立刻拉高否则数据还没发完片选就没了。我的做法是用传输完成回调函数void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI2) { MRAM_Deselect(); mram_dma_busy 0; } }注意DMA 模式下WREN 命令最好还是用阻塞方式发送因为 WREN 只有一个字节用 DMA 反而增加复杂度。WRITE 命令和数据的发送可以用 DMA这样能省下不少 CPU 时间。5. 数据可靠性与掉电保护设计5.1 双备份与校验机制工业场景下数据可靠性是重中之重。我在设备信息区和校准参数区都做了双备份每个数据存两份地址相差 0x800。读取的时候先读主份计算 CRC16 校验值如果校验通过就用主份如果校验失败再读备份校验通过就用备份并且把备份数据回写到主份。如果两份都坏了那就只能报错让上层处理。CRC16 我用的是 CCITT 多项式0x1021初始值 0xFFFF。这个校验算法在嵌入式里很常用计算速度快检错能力也够用。每个数据块的结构是这样的typedef struct { uint32_t magic; // 魔数标识数据块有效 uint16_t version; // 版本号 uint16_t length; // 数据长度 uint8_t data[56]; // 实际数据 uint16_t crc; // 前面所有字段的 CRC16 } DataBlock_t;整个结构体 64 字节正好对齐。魔数我用的 0x5A5A1234每次读取先检查魔数不对就直接判定为无效数据。5.2 写入过程中的掉电应对策略MRAM 的写入是“即时生效”的不存在 Flash 那种“擦除到一半掉电导致整个扇区数据丢失”的问题。但掉电仍然可能发生在写入过程中导致数据只写了一半。比如你要写 64 字节的数据块写到第 32 字节的时候掉电了那这个数据块就是半新半旧的状态CRC 校验肯定过不了。我的应对策略是先写备份区再写主份区。具体流程是先把新数据写到备份区校验通过后再把新数据写到主份区。这样即使写主份区的时候掉电备份区仍然是完整的旧数据或者新数据不会两边都坏。读取的时候如果主份校验失败自动切到备份系统仍然能正常工作。另外对于运行日志区我采用追加写入的方式每次写完一条日志后更新写入指针。指针本身也带 CRC 校验如果指针损坏就从头开始扫描日志区找到最后一条有效日志的位置恢复指针。这个恢复过程在系统启动时执行一次耗时大概几十毫秒可以接受。5.3 温度与电压对 MRAM 的影响MRAM 的磁性存储单元对温度和磁场比较敏感。MR25H40CDF 标称的工业级温度范围是 -40℃ 到 85℃在这个范围内读写都是可靠的。但如果超出这个范围比如到了 100℃ 以上数据保持能力会下降写入也可能出错。电压方面2.7V 到 3.6V 是正常工作范围。低于 2.7V 时芯片可能进入未定义状态读写都不可靠。所以电源设计上要保证 3.3V 稳定纹波不要太大。如果系统里有大功率器件频繁开关建议给 MRAM 的电源加一个 LC 滤波。还有一个容易被忽略的点强磁场环境。MRAM 虽然抗磁场能力比传统的磁性存储强很多但在极强的磁场下仍然可能受到影响。如果设备要用在电机旁边或者有强磁铁的场合最好做一下屏蔽或者保持距离。6. 常见问题排查与实操避坑指南6.1 读写失败问题速查表现象可能原因排查方法解决方案读出来全是 0xFFCS 没拉低、SPI 没配置、MISO 接错用示波器看 CS 和 SCK 波形检查 GPIO 配置和硬件连接写进去读出来不对没发 WREN、WREN 和 WRITE 在同一片选周期抓 SPI 波形看命令序列确保 WREN 后拉高 CS 再发 WRITE偶尔读写错误SPI 时钟太快、走线太长、干扰降低时钟频率测试降频、加匹配电阻、优化布局数据放一段时间就丢了电源不稳、温度超标监测电源和温度加滤波电容、改善散热状态寄存器读出来异常命令码发错、时序不对对照手册检查命令码确认 CPOL/CPHA 配置正确6.2 SPI 通信异常的排查思路SPI 通信出问题我一般的排查顺序是先看硬件再看配置最后看软件逻辑。硬件方面用示波器或者逻辑分析仪抓 CS、SCK、MOSI、MISO 四根线的波形。重点看几个地方CS 拉低的时候 SCK 是不是空闲电平模式 0 下应该是低电平第一个时钟沿是不是在 CS 拉低之后足够时间才出现MISO 上的数据是不是在正确的时钟沿被采样。如果波形看起来没问题那大概率是软件配置的问题。软件配置方面最容易错的是 CPOL 和 CPHA。模式 0 和模式 3 是最常用的但有些芯片只支持其中一种。MR25H40CDF 两种都支持但如果配错了读出来的数据会整体移位或者完全不对。另外STM32 的 SPI 配置里有一个“First Bit”选项要选 MSB First选错了数据字节顺序就反了。软件逻辑方面最常见的问题是 WREN 和 WRITE 的片选处理。我见过有人把 WREN 和 WRITE 放在同一个片选周期里结果写不进去。还有人忘了在写操作之前发 WREN也是写不进去。这些细节在手册里都写了但实际写代码的时候容易漏。6.3 提升写入效率的实操技巧如果你需要频繁写入大量数据有几个技巧可以提升效率。第一合并写操作。不要每写一个字节就发一次 WREN 和 WRITE而是攒够一批数据一次性写入。MRAM 支持连续写入地址自动递增写一批数据和写一个字节的命令开销是一样的。第二用 DMA 传输。前面说过DMA 可以解放 CPU尤其是在写入大量数据的时候。配置好 DMA 之后CPU 只需要发起传输剩下的交给 DMA 控制器传输完成后再处理后续逻辑。第三合理设置 SPI 时钟。在保证可靠性的前提下尽量用高的时钟频率。13.5MHz 和 27MHz 的传输速率差一倍对于大数据量写入来说时间差距很明显。但也不要盲目追求高频要先测试稳定性。第四减少不必要的状态查询。MRAM 写入速度极快WIP 位几乎瞬间就清零不需要每次都轮询等待。我实测下来写完直接进行下一次操作完全没问题不需要加延时。提示如果你从 SPI Flash 迁移到 MRAM记得把代码里所有“擦除”相关的逻辑都删掉。MRAM 不需要擦除保留擦除代码不仅没用还可能因为误操作导致数据丢失。7. 实际项目中的经验体会这套 MR25H40CDF STM32F723ZE 的方案我在两个项目里实际用过。一个是电力监测终端需要每秒钟记录一次电压电流数据一天下来就是 86400 条记录每条 32 字节一天的数据量大概 2.7MB。MRAM 只有 512KB所以我是按环形缓冲区的方式只保留最近几天的数据更早的数据通过通信接口上传到上位机。MRAM 的无限写入寿命在这里发挥了关键作用如果换成 Flash每天几万次写入几个月就把扇区写坏了。另一个项目是医疗设备需要保存患者的校准参数和使用记录。这个场景对数据可靠性要求极高绝对不能丢数据。我用了双备份加 CRC 校验的方案运行了一年多没有出现过数据丢失的情况。有一次设备在写入过程中意外断电重新上电后系统自动从备份区恢复了数据主份区校验失败被标记为无效然后自动用备份数据修复了主份。整个过程对用户完全透明设备正常工作。踩过的坑也有几个。最开始用硬件 NSS 的时候发现片选时序不太对读出来的数据偶尔会错位。后来改成软件片选问题就消失了。还有一次是 PCB 布局的时候SPI 走线经过了一个 DC-DC 电源下面干扰很大读写误码率很高。重新布线让 SPI 走线远离电源之后问题解决。这些经验告诉我SPI 虽然简单但硬件设计上不能马虎尤其是高速或者长距离传输的时候。最后分享一个小技巧如果你不确定 MRAM 的读写是否正常可以先往地址 0x00000 写一个已知的数据比如 0xA5然后读出来看是不是 0xA5。再往地址 0x7FFFF 写一个 0x5A读出来验证。这两个地址一个在头部一个在尾部如果都能正确读写说明地址线和数据线都没问题。这个方法简单有效我在调试新板子的时候每次都用。
返回列表