在工业现场摸爬滚打过的工程师都知道,设备里最怕出问题的往往不是MCU,而是存储器。最近做的一个项目里,我用 STM32F746ZG 搭配 Everspin 的 MR25H40CDF,在嵌入式设备里搭建了一套高速、抗掉电、不需要擦除的存储方案,用来保存参数、日志和关键运行数据。这篇文章就把选型、接线、驱动、数据管理和排查过程完整梳理一遍。项目用到的主控是 STM32F746ZG,Cortex-M7 内核,主频 216MHz,属于 ST 的高性能系列;存储芯片是 MR25H40CDF,一颗 4Mbit 的 SPI MRAM。整套方案最终跑在了一块工业数据采集板上,连续运行几个月,存储部分没有出现一次数据丢失或损坏。如果你也正在为"嵌入式设备里到底用什么芯片存数据"发愁,或者在调 MRAM 驱动时遇到读回 0xFF、写不进去这类问题,这篇内容应该能帮你省下不少时间。
1. 为什么工业场景下我选了 MRAM,而不是 EEPROM 或 NOR Flash
1.1 工业存储的三大核心矛盾
工业设备里的数据存储需求和消费电子产品不太一样,我总结下来主要有三个绕不开的矛盾:第一个是频繁写、掉电不丢。设备运行过程中需要持续记录温度、压力、电压、运行状态这些参数,写入频率可能每秒一次甚至更高,而且现场经常发生意外断电,数据不能因为掉电就丢。第二个是写入寿命。普通 NOR Flash 的擦写次数一般在 10 万次左右,如果设备每秒写一次,一块 Flash 大概不到三天就会写满寿命,这显然不可接受。第三个是速度。有些数据记录场景对写入时间有严格要求,比如故障录波、事件顺序记录,如果写一条数据要几十毫秒甚至更久,很多故障现场就抓不住了。
这三个矛盾放在一起,传统的 EEPROM 和 NOR Flash 其实都很吃力。EEPROM 虽然可以按字节擦写,但寿命同样是 10 万到 100 万次级别,写入速度在毫秒级,大容量产品也不多。NOR Flash 容量可以做上去,但写入前必须擦除,擦除一个扇区要几十到几百毫秒,而且有块管理、磨损均衡这一大堆事情要处理。如果只是存几十个参数,EEPROM 勉强够用;如果要存大量日志,还要高频写入,就必须换思路。MRAM 恰好能把这三个矛盾同时解开,所以我在这块项目板上最终选的就是 MR25H40CDF。
1.2 MR25H40CDF 与 FRAM、EEPROM、NOR Flash 的对比
MR25H40CDF 是一颗 4Mbit 的 SPI 接口 MRAM,也就是磁阻随机存取存储器。它的核心特点是利用磁阻效应来存储数据,写入速度快、非易失、寿命极长。选型的时候我把它和 FRAM、EEPROM、NOR Flash 都拉出来对比过,表里面是最关键的几项指标。
| 对比项 | MR25H40CDF (MRAM) | FRAM | EEPROM | NOR Flash |
|---|---|---|---|---|
| 写入速度 | 极快,接近 SRAM 水平 | 快,但高密度型号较少 | 毫秒级 | 先擦后写,秒级 |
| 写入寿命 | 可达 10^13 次以上 | 10^10 到 10^12 次 | 10^5 到 10^6 次 | 10^5 次左右 |
| 写入前擦除 | 不需要 | 不需要 | 不需要 | 必须擦除 |
| 按字节/页写 | 支持按字节写,非常灵活 | 支持 | 支持 | 只能按页编程 |
| 掉电保持 | 非易失,无需后备电池 | 非易失 | 非易失 | 非易失 |
| 典型容量 | 中低容量为主 | 中小容量 | 小容量 | 大容量为主 |
从这个表能看出来,MRAM 最大的优势是写入寿命和写入速度同时在线。对于工业数据记录这种高频写入场景,这是 EEPROM 和 NOR Flash 很难替代的。
FRAM 其实也是不错的方案,寿命和速度都很优秀,但密度通常做不到 MRAM 这么大,而且部分 FRAM 存在读干扰问题,读操作本身可能会造成存储单元的翻转趋势,需要额外的处理机制。MRAM 在读写两个方向上都比较"皮实",这也是我最终倾向前者的原因。实际使用中,MR25H40CDF 的写入操作基本不需要等待,发完数据就是写完了,这让我可以在中断服务程序里直接完成关键事件记录,极大简化了软件设计。
1.3 STM32F746ZG 在这里的角色
选 STM32F746ZG 主要是看中它的 IO 资源和运算能力。它基于 Cortex-M7 内核,最高主频 216MHz,片上有 1MB Flash 和 320KB RAM,非常适合需要同时处理显示、通信和控制的工业设备。存储子系统方面,STM32F746ZG 有多个 SPI 接口,可以很从容地把 MR25H40CDF 挂在独立的 SPI 总线上,避免和传感器、显示屏争抢总线。
另外 F746ZG 内部有丰富的外设,比如以太网 MAC、USB、FMC、多个 UART、CAN 等。在这个项目里,主控除了负责把数据写进 MRAM,还要通过 Modbus 把数据上传给上位机,通过工业以太网做远程监控。Cortex-M7 的性能余量很大,跑 FreeRTOS 之后还有充足算力做数据解析和协议转换。MRAM 在这里不是性能瓶颈,反而成了整个系统可靠性的底座。
2. 硬件连接与电路设计要点
2.1 认识 MR25H40CDF 的引脚
拿到 MR25H40CDF,第一件事是去 Everspin 官网拉数据手册,核对工作电压、SPI 速率和封装引脚,不要凭记忆接线。
MR25H40CDF 是 8 脚 DFN 封装,引脚定义和常见的 SPI NOR Flash 高度相似,我用的接线参考如下:
| 引脚编号 | 引脚名称 | 功能说明 | 接法 |
|---|---|---|---|
| 1 | CS# | 片选,低有效 | 连接 MCU 的 GPIO 或专用 CS |
| 2 | SO | 串行输出,MISO | 连接 MCU 的 SPI MISO |
| 3 | WP# | 写保护,低有效 | 上拉到 3.3V |
| 4 | VSS | 地 | 连接 GND |
| 5 | SI | 串行输入,MOSI | 连接 MCU 的 SPI MOSI |
| 6 | SCK | 串行时钟 | 连接 MCU 的 SPI SCK |
| 7 | HOLD# | 保持,低有效 | 上拉到 3.3V |
| 8 | VCC | 电源 | 连接 3.3V,并加去耦电容 |
这里特别提醒一下,WP# 和 HOLD# 千万不能浮空。WP# 浮空可能导致芯片误进入写保护状态,你写数据的时候怎么发 WREN 都没用;HOLD# 浮空更危险,一旦被干扰拉低,芯片会暂停通信,SPI 总线上所有数据都收不到,表现出来就是读数据读到一半卡死。我头一回画板的时候,就因为 HOLD# 没接上拉,废了大半天功夫才定位到问题。这两个引脚对工业环境来说尤其重要,因为现场电磁干扰比实验室严重得多,浮空引脚很容易被噪声“偷袭”。
2.2 STM32F746ZG 的 SPI 资源分配与接线
STM32F746ZG 上有多个 SPI 外设,我建议优先选择挂在 APB2 总线上的 SPI1,因为 APB2 时钟频率更高,可以让 SPI 跑得更快。实际项目里我把 MR25H40CDF 挂在了 SPI1 上,GPIO 配置如下:
| MR25H40CDF 引脚 | STM32F746ZG 引脚 | 复用功能 |
|---|---|---|
| CS# | PA4 | GPIO 输出 |
| SCK | PA5 | SPI1_SCK |
| MISO (SO) | PA6 | SPI1_MISO |
| MOSI (SI) | PA7 | SPI1_MOSI |
| WP# | 直接上拉 3.3V | - |
| HOLD# | 直接上拉 3.3V | - |
使用 GPIO 软件控制 CS 是最常见的做法,因为可以在读写的开始和结束时刻精确控制片选信号,比硬件 NSS 灵活。STM32F7 的 SPI 硬件 NSS 在单 master 场景下也能用,但一旦涉及多设备分时复用总线,软件 CS 更可控。MR25H40CDF 的 CS# 低电平有效,操作前拉低,操作完成后拉高,时序上注意一点:CS# 拉低后要等一小段时间再开始发时钟,保证芯片完成内部准备。
电平匹配上,STM32F746ZG 的 IO 是 3.3V 兼容,MR25H40CDF 工作电压也是 3.3V,可以直接连接,不需要额外电平转换。如果你的主控是 1.8V 或者 5V 系统,那就得单独处理,比如加电平转换芯片,或者确认芯片手册里是否支持对应电平。
2.3 板级设计的三个关键细节
第一,电源去耦。MR25H40CDF 的 VCC 引脚旁边要放一个 0.1uF 陶瓷电容,距离尽量靠近芯片,不超过 3mm 为佳。SPI 通信过程中电流变化比较快,如果电源纹波过大,可能造成电平误判。
第二,地平面完整性。DFN 封装的底部通常有一个裸露焊盘,一般是 GND,焊接时一定要保证这个焊盘和 PCB 地平面良好接触。我处理办法是在 PCB 上给底部焊盘开方孔或通孔阵列,焊接后从背面检查焊料是否充分填充。底部焊盘虚焊,芯片也能勉强工作,但会出现温度一高就通讯失败、用手一压板子数据就错这种奇怪故障。
第三,CS 信号的走线尽量短,远离大电流走线和继电器等干扰源。SPI 时钟频率高的时候,CS 上叠加的噪声会影响整个传输时序。工业控制板上常有 24V 电源和继电器驱动信号,它们和 SPI 线之间的间距至少保持 3 倍线宽以上,条件允许就用包地处理。这块板的连线顺序是先走完电源和地,再走 SPI 数据线,最后布控制信号,整个存储模块的电气环境非常干净。
3. 驱动代码实现:从初始化到读写
3.1 SPI 外设初始化,选 Mode 0 还是 Mode 3
MR25H40CDF 支持 SPI Mode 0 和 Mode 3,也就是 CPOL=0/CPHA=0 或者 CPOL=1/CPHA=1。我用的是 Mode 0,这是 SPI 设备最常用的模式,也方便和后续其他 SPI 外设共存。STM32F746ZG 的 SPI1 配置使用标准库或 HAL 都可以,我用的是 STM32CubeMX 生成的工程,关键参数如下:
void MRAM_SPI1_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; // CPOL = 0 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA = 0 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; hspi1.Init.CRCPolynomial = 0; HAL_SPI_Init(&hspi1); }这里分频系数选用 /4,是因为 SPI1 挂载的 APB2 时钟在 108MHz 左右,/4 之后得到约 27MHz 的 SPI 时钟,跟 MR25H40CDF 数据手册里标称的最高 SPI 时钟频率还有余量。
关于 CPOL 和 CPHA,一个常见的坑是 STM32 HAL 里的时钟相位定义和芯片手册不一致。HAL 库中 CPHA=1EDGE 表示在第一个时钟边沿采样数据,CPOL=Low 表示空闲时钟为低电平,这就是 SPI Mode 0。如果你配置反了,芯片可能完全没反应,或者读回来的数据整体移位一位,出现一个非常规律的“乱码”。
3.2 最底层的收发函数
MR25H40CDF 的 SPI 通信核心是一个字节一个字节地交换。我在驱动里封装了两个最基础的函数:
static void MRAM_Select(void) { HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_RESET); } static void MRAM_Release(void) { HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_SET); } static uint8_t MRAM_TransferByte(uint8_t byte) { uint8_t rx; HAL_SPI_TransmitReceive(&hspi1, &byte, &rx, 1, HAL_MAX_DELAY); return rx; }注意,SPI 是全双工通信,MOSI 上的数据发送和 MISO 上的数据接收是同时发生的。所以如果你想读一个字节,就要同时发送一个空字节 0x00,用返回值的 rx 作为读到的数据。很多人第一次写驱动时只调用 HAL_SPI_Transmit,忽略了同