尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于STM32F030RC与MR25H40CDF的SPI接口MRAM掉电存储方案

基于STM32F030RC与MR25H40CDF的SPI接口MRAM掉电存储方案 1. 项目概述为什么工业现场需要一个掉电不丢的存储方案最近在调试一个工业数据采集模块控制器选了 STM32F030RC采集端需要暂存一批校准参数、掉电时保存现场状态、记录诊断日志。看了一圈存储方案最后上了 Everspin 的 MR25H40CDF。这是颗 4Mbit 的串行 MRAM走 SPI 接口和 STM32F030RC 的 SPI1 对接。整套方案跑下来实测效果很稳这里把选型思路、硬件连接、驱动实现和踩坑过程都整理出来。先说结论如果你正在做工控板、仪表、PLC 外围模块或者任何需要频繁写数据、掉电不能丢、又担心 Flash 寿命不够用的项目MRAM 值得认真考虑。它最大的特点就是按字节读写、几乎无限次擦写、数据掉电保持不丢失。这三点刚好补上了 EEPROM 容量小、Flash 擦写慢且块结构限制多、FRAM 容量偏低这几块短板。需要说明的是MR25H40CDF 本质是磁阻存储器用磁隧道结存储数据读写机制和电容存储的 DRAM、浮栅存储的 Flash 完全不同。它的写操作不需要先擦除没有写坏的寿命焦虑写一个字节和写一整页的耗时基本一致。加上工业级温度范围这个后缀封装是 8 引脚 PDFN温度等级覆盖 -40℃ 到 85℃非常适合放在环境不太友好的设备里。这篇分享适合三类人看准备在嵌入式项目里引入 MRAM 的开发者、正在纠结存储方案选型的硬件工程师、以及手上刚好有 STM32F0 系列但没用过串行 MRAM 的同学。看完你应该能自己画电路、写驱动、跑通读写并且知道现场调试中最容易翻车的几个细节。2. 核心方案拆解MRAM、EEPROM、Flash、FRAM 到底怎么选2.1 不同存储介质的本质差异存储选型这件事本质上是在四个维度上做权衡容量、写入寿命、写入粒度和掉电保持。传统的 SPI Flash比如 W25Q64容量大、成本低但写入前必须按 sector 擦除最小擦除块动辄 4KB写一个字节也得先读出整个扇区、改掉其中一位、再整块写回。对需要频繁小数据更新的场景这种机制既慢又难受。EEPROM比如 AT24C02可以按字节写寿命标称通常 100 万次看起来够用。但实际做产品时你会发现如果一个字段每次上电都更新100 万次也就是一年多点的量级而且 EEPROM 写一个字节的时间在毫秒级写多字节要等内部时序实时性差。更不用说大容量 EEPROM 型号少、价格贵想存几十 KB 数据基本没得选。FRAM 也不错铁电存储按字节写、寿命高但容量普遍偏小最大的串行 FRAM 也就 8Mbit 左右价格比 MRAM 还贵而且供应商选择少。MRAM 的出现正好填补了这个空间容量做到 4Mbit512KB接口走标准 SPI寿命理论上 100 亿次写入写时序和读时序一样快不需要擦写调度驱动写起来跟读 Flash 的 0x03 指令几乎一样简单。2.2 针对本项目为什么锁定了 MR25H40CDFSTM32F030RC 是 Cortex-M0 内核48MHz 主频通信接口有一个 SPI、一个 I2C默认没有 FSMC 并口总线。这意味着我没办法像接 NOR Flash 那样用并行总线高速读写只能走串行接口。MR25H40CDF 就是 SPI 从设备标准 4 线制CS、SCLK、MOSI、MISO跟 MCU 的 SPI1 直接相连一点额外逻辑都不用加。容量上 4Mbit 对当前项目刚好够用我分配了 64KB 给参数区含三份冗余备份、128KB 给事件日志、剩余空间做 ADC 校准数据暂存和 bootloader 升级缓存。如果只是存几个参数512KB 确实偏大但考虑到日志记录和现场固件升级的需求这个容量反而成了大缓冲区省掉了外挂 Flash 的物料成本。还有一点是功耗。MR25H40CDF 的待机电流在微安级别工作电流大概 15mA 左右比并行 NOR Flash 低得多。我这块板子是 24V 供电的工业模块M0 芯片本身功耗就低整板功耗控制很轻松。当然如果你需要更低功耗版本Everspin 有深睡眠模式指令也简单后面代码里我会带上。3. 硬件连接与电路设计细节3.1 MCU 管脚分配与 SPI 接线STM32F030RC 的 SPI1 可以映射到多个管脚组合我用的是默认映射PA5 做 SCK、PA6 做 MISO、PA7 做 MOSICS 用软件控制的普通 GPIO选 PA4。为什么不用硬件 NSS因为项目里这个 SPI 总线上还可能挂其他从设备比如外部 ADC 或者传感器硬件 NSS 在多从机场景下管理起来别扭还不如软件控制来得干脆。具体连接关系如下MR25H40CDF 引脚功能连接到 STM32F030RC1 CS#片选低有效PA4GPIO 推挽输出2 SCLK时钟PA5SPI1_SCK3 MOSI主机输出/从机输入PA7SPI1_MOSI4 MISO主机输入/从机输出PA6SPI1_MISO5 WP#写保护低有效直接拉高到 VCC6 VSS地GND7 VCC电源 3.3V3.3V8 HOLD#暂停通信低有效直接拉高到 VCCWP# 和 HOLD# 这两个引脚的功能需要特别注意。WP# 拉低时会禁止写状态寄存器和写存储区很多人调试时遇到写操作没反应排查到最后发现是 WP# 悬空或者被拉低了。HOLD# 拉低时芯片暂停 SPI 通信相当于时钟无效总线上其他设备可以占用这两个引脚在单从机场景下直接接上拉电阻到 VCC 最省心。3.2 电源、去耦与上拉电阻MR25H40CDF 支持 2.7V 到 3.6V 供电我直接接在 3.3V 主电源上。VCC 引脚旁边放 0.1uF 和 1uF 两个陶瓷电容并联去耦位置尽量靠近芯片。VCC 和 VSS 之间不要省这个电容MRAM 内部读写时电流变化比较快特别是高频翻转时退耦不好容易在电源轨上出现毛刺。SPI 的 MISO 线是芯片输出它在读操作时由从机驱动不需要额外上拉。但 MOSI、SCLK、CS 这几个输入端如果 MCU 复位期间管脚处于浮空状态建议加 10K 上拉电阻防止芯片在 MCU 未初始化时误动作。尤其注意 CS 的上拉如果 CS 悬空被干扰拉低芯片会进入等待指令状态虽然不会误写数据但会增加莫名的调试困难。我这个板子空间紧张只在 CS 上加了上拉实测没有问题但正规布局建议四条线都处理一下。3.3 工业现场的电气鲁棒性补充工业环境和实验室不一样强电干扰、电机启停、长线缆传输都很常见。SPI 线如果引出到板外比如通过排线连接显示面板建议在 MCU 侧串联 33Ω 电阻配合寄生电容可以起到简单的低通滤波作用。信号线上再加 TVS 管注意 TVS 的结电容不能太大否则 SPI 速率上不去。另一个容易忽略的点是地线。MR25H40CDF 是纯数字器件抗干扰能力本身不错但不要让它和电机驱动电路共享回流路径。数字地和功率地单点相连存储芯片尽量靠近 MCU 摆放走线短而粗。实测下来把 SPI 走线走在底层打过孔、周围铺地铜EMC 表现会比直接顶层走线好很多。这些细节看似不起眼但做产品认证时能省大量整改时间。4. 驱动代码实现与调试实录4.1 SPI 初始化配置STM32F030RC 的 SPI1 配置不算复杂但有几个点要仔细。MR25H40CDF 支持 SPI Mode 0 和 Mode 3我选了 Mode 0CPOL0CPHA0这是最通用的配置。主频方面芯片手册标称最大 40MHz实际上 STM32F0 的 SP I尽可以跑到 48MHzPCLK/2我这边为了兼容长走线和工业干扰只用了 9MHz留足裕量。void SPI1_Init_MRAM(void) { GPIO_InitTypeDef gpioInit; SPI_InitTypeDef spiInit; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); gpioInit.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; // SCK, MOSI gpioInit.GPIO_Mode GPIO_Mode_AF; gpioInit.GPIO_OType GPIO_OType_PP; gpioInit.GPIO_PuPd GPIO_PuPd_NOPULL; gpioInit.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpioInit); gpioInit.GPIO_Pin GPIO_Pin_6; // MISO gpioInit.GPIO_Mode GPIO_Mode_AF; gpioInit.GPIO_OType GPIO_OType_PP; // 注意MISO 设为浮空输入更标准 gpioInit.GPIO_PuPd GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, gpioInit); gpioInit.GPIO_Pin GPIO_Pin_4; // CS 软件控制 gpioInit.GPIO_Mode GPIO_Mode_OUT; gpioInit.GPIO_OType GPIO_OType_PP; gpioInit.GPIO_PuPd GPIO_PuPd_UP; GPIO_Init(GPIOA, gpioInit); GPIO_SetBits(GPIOA, GPIO_Pin_4); // CS 默认高 spiInit.SPI_Direction SPI_Direction_2Lines_FullDuplex; spiInit.SPI_Mode SPI_Mode_Master; spiInit.SPI_DataSize SPI_DataSize_8b; spiInit.SPI_CPOL SPI_CPOL_Low; spiInit.SPI_CPHA SPI_CPHA_1Edge; spiInit.SPI_NSS SPI_NSS_Soft; spiInit.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; // 72MHz/8 9MHz spiInit.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, spiInit); SPI_Cmd(SPI1, ENABLE); }这里有个小坑SPI1 挂的是 APB2 时钟F030RC 的 APB2 最大 48MHz如果按 SDK 里的默认时钟树PCLK 可能是 48MHz9MHz 就是 48/86MHz 或者 48/68MHz用 8 分频得到 6MHz 也完全够用。调试时不必死磕最高速率稳定第一。4.2 基础读写函数MRAM 的 SPI 指令集和普通 SPI NOR Flash 高度相似读数据是 0x03写数据是 0x02不过是按字节写没有页缓冲这种概念。这意味着你不需要拆页、不需要对齐想写哪就写哪。uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t rx; MRAM_CS_LOW(); SPI_SendByte(0x03); // READ SPI_SendByte((addr 16) 0xFF); SPI_SendByte((addr 8) 0xFF); SPI_SendByte(addr 0xFF); rx SPI_ReceiveByte(); // 读任意字节 MRAM_CS_HIGH(); return rx; } void MRAM_WriteByte(uint32_t addr, uint8_t data) { MRAM_CS_LOW(); SPI_SendByte(0x06); // WREN 写使能必须先发 MRAM_CS_HIGH(); MRAM_CS_LOW(); SPI_SendByte(0x02); // WRITE SPI_SendByte((addr 16) 0xFF); SPI_SendByte((addr 8) 0xFF); SPI_SendByte(addr 0xFF); SPI_SendByte(data); // 直接写入无需页擦除 MRAM_CS_HIGH(); }连续读取可以拉低 CS 后一直发 0x03、地址然后连续读字节地址自动递增直到你拉高 CS。这对我做日志读取和固件上传非常方便。实测直接读 512KB 数据9MHz 时速率约 1.1MB/s比用 I/O 模拟 SPI 快太多了。连续写一个缓冲区时也不必像 Flash 那样每写一页重新发一次指令只要 CS 保持低电平写完一个字节后地址自动加一可以继续写下一个字节。官方手册说可以持续写直到地址到顶我一般写个 64 字节就拉高 CS 重新发指令既保持简洁又不至于在异常情况下把整个芯片写穿。4.3 状态寄存器与写保护大多数情况下MRAM 不需要管写保护的事因为写操作本身就是即时完成的没有忙状态这也是 MRAM 区别于 Flash 的关键。但我还是建议软件上读一下状态寄存器主要是确认 WP# 引脚和状态寄存器里的 WPEN 位没把写保护打开。读状态寄存器指令是 0x05注意它只返回一个字节其中最高位就是 WPEN。uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_LOW(); SPI_SendByte(0x05); status SPI_ReceiveByte(); MRAM_CS_HIGH(); return status; }如果状态寄存器返回 0x80说明 WPEN 和 WP# 引脚都处于写保护状态任何写操作都会被拒绝。这个位出厂默认是 0但我碰到过客户板子上的 WP# 引脚连到 MCU 某个 GPIO程序初始化时把这个 GPIO 拉低了结果写数据一直失败。排查思路很简单读状态寄存器如果值是 0x80查硬件。4.4 性能实测与使用技巧我特地在板子上跑了一组简单的性能测试记录在案场景数据量实测耗时备注连续写 4KB4096 字节约 4.5ms9MHz SPI无页对齐开销连续读 4KB4096 字节约 3.7ms读取略快于写入擦除 1 sector无0ms根本不需要擦除掉电保存 1 字节1 字节1ms配合掉电检测10μs 级可完成对比一下普通 SPI Flash 写 4KB 数据的时间假设一个扇区擦除要 60ms再算上写页时间至少也要 100ms。MRAM 的优势不仅是快更重要的是调试体验好——你不需要做磨损均衡、不需要维护坏块表、不需要等待内部擦写完成。写代码时完全可以把 MRAM 当作一块超级大的 RAM 来用。实际项目中我把日志系统设计成了环形缓冲一块 128KB 的区域头指针和尾指针都存在 MRAM 里每次事件写入一个 80 字节的结构体写完更新指针。之前用 Flash 时日志写满一块区域就要做擦除擦除期间事件会丢失几十毫秒。换成 MRAM 后日志系统几乎没有峰值延迟每次掉电重启指针完全恢复数据一条不丢。5. 调试中遇到过的疑难杂症与排查心得5.1 读出来全是 0xFF 或 0x00不死不活这类问题九成出在 SPI 配置或硬件连接上。先说那个比较隐蔽的坑STM32F0 的 SPI 在 Master 模式下MISO 引脚要配置为浮空输入不能配置成复用推挽。我最初把 MISO 配成了 GPIO_Mode_AF 复用而且默认是开漏结果读取全是 0。原因很简单把输出驱动和输入缓冲混用了。排查步骤建议按这个顺序来先示波器看 SCLK 有没有时钟再看 CS 波形是否正常拉低然后看 MOSI 上的指令字节对不对最后再看 MISO 是否有返回。四步下来基本能定位是接线、还是时钟、还是配置问题。如果手头有逻辑分析器直接抓 SPI 时序一眼就能看出芯片有没有回数据。5.2 偶发写入丢失找半天是掉电时序问题有段时间现场反馈设备偶尔上电后参数被重置我在实验室复现不了后来模拟电源跌落才定位到问题。当时 MCU 检测到掉电在 VCC 跌落到阈值以下后还想往 MRAM 里写关键的现场状态。VCC 已经降到 2.5V 左右MRAM 本身还能工作但 SPI 线路噪声增大写入的数据出现了错误。解决办法是加了一颗超级电容或者大电解电容给存储供电回路提供几百毫秒的维持时间同时把掉电检测阈值调高让 MCU 在 VCC 还有 3.0V 时就进入紧急保存流程。这里有个经验不要把保存现场留到电源已经崩溃的那一刻才启动要提前一点。我用的是 STM32F030RC 内置的 PVD可编程电压检测器设置 2.9V 阈值中断里立刻把 16 字节的关键数据写入 MRAM 的固定地址再把 WP# 拉低防止后续误写。实测电容撑住 200ms 足够完成操作。5.3 连续写之后某个字节突然错乱检查 CS 时序还有一次客户反馈日志里偶尔出现一个字节的乱码。追了半天发现是 CS 拉高的时机不对。标准 SPI 从设备CS 高电平表示命令结束芯片在这个上升沿锁存数据。在快速连续操作时如果 CS 刚拉高之后太短时间又拉低芯片内部可能会把上一次的指令残留识别成新指令造成地址错位。解决办法是软件上保证 CS 两次操作之间至少留一个时钟周期的间隔实际操作中 CS 拉高之后延时 1μs 再拉低。同时检查 MCU 的 SPI 状态寄存器确保发送完成标志置位后再拉高 CS避免后面一个字节的数据还没发完就切断了通信。5.4 与烧录器共用一个 SPI 导致冲突最后说一个产品化时很实际的坑。我的板子上把 SPI1 同时接到了 MRAM 和调试用的外部 Flash或者烧录器通过 CS 区分设备。MRAM 的 HOLD# 引脚默认拉高如果烧录器拉低 HOLD#MRAM 就进入暂停状态不会响应任何片选。这本身没问题但有些调试器会在这个总线上枚举设备把 MRAM 误识别成 Flash然后尝试往里面擦写。一旦擦写指令误发出去MRAM 会按字节写模式执行可能会破坏某个地址的数据。解决方案是分区隔离调试接口走独立 SPI或者直接把 MRAM 的 WP# 在出厂固件阶段拉死。如果必须共用总线记得在 Bootloader 里对 MRAM 的操作加上一个 0xA5 之类的魔数校验防止误操作。6. 用在真实项目中的总体评价与经验沉淀这块板子已经在实验室跑了三周反复上下电超过两千次日志写入超过五十万条没有出现一条数据丢失或者校验失败的情况。MR25H40CDF 加上 STM32F030RC 这套组合让我体会最深的不是指标有多高而是整个开发过程的省心。原来用 SPI Flash每次写数据前要想擦除策略日志区要留冗余掉电要防写一半的坏块。换到 MRAM 之后这些负担全部卸掉了。存储芯片回到了它本该有的样子就是一个通用的字节寻址存储器件你给它地址和数据它记住你给它地址它原样吐出来。即使写了一半突然断电再上电读那个地址大概率也还是旧的完整数据因为 MRAM 的写入是即时翻转不存在 Flash 那样的擦除中间态。从成本角度看单独看 MR25H40CDF 的单价确实比同容量的 SPI Flash 贵但把 Flash 需要的坏块管理、磨损均衡软件开销、擦写出错重试机制、掉电保护电路、增加校验冗余的 Flash 容量这些统统算进去系统级成本反而没有想象中那么夸张。如果你的产品对数据可靠性要求高、对写入频率不敏感、又不想引入文件系统来做 Flash 管理MRAM 是一种非常干净的答案。
返回列表