拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32F413RH 驱动 MR25H40CDF MRAM 实战:SPI 配置、驱动编写与工业数据记录

STM32F413RH 驱动 MR25H40CDF MRAM 实战:SPI 配置、驱动编写与工业数据记录

1. 为什么 MRAM 在嵌入式存储里越来越受关注

搞嵌入式的人大多有过这种经历:设备跑在现场,突然断电,回头一看,关键参数丢了,或者日志文件损坏,只能让运维跑一趟现场重新配置。传统的 EEPROM 写入慢、寿命有限,NOR Flash 擦写前要整块擦除、写入延迟高,FRAM 容量小、价格贵,SRAM 掉电就丢数据还得配电池。这些方案各有各的痛点,而 MRAM(磁性随机存储器)恰好卡在一个很舒服的位置上。

MR25H40CDF 是 Everspin 推出的一款 4Mbit 串行 MRAM,SPI 接口,40MHz 时钟,支持 1.8V 到 3.6V 宽电压。它的核心特点用一句话概括就是:像 SRAM 一样随时读写、像 Flash 一样掉电不丢、像 EEPROM 一样能反复擦写。没有写入延迟,没有擦除操作,字节级随机写入,写入寿命几乎无限。这些特性放在工业数据记录、参数存储、掉电保护这些场景里,简直是量身定做。

STM32F413RH 则是 ST 家的高性能 Cortex-M4 系列,100MHz 主频,1.5MB Flash,320KB SRAM,带硬件浮点单元,外设资源丰富,SPI 接口有好几路。拿它来驱动 MR25H40CDF,硬件上只需要连 SPI 的四根线加一根片选,软件上用 HAL 库或者寄存器操作都能跑起来,整体方案非常干净。

这篇文章面向的是正在做工业嵌入式项目、需要可靠非易失存储方案的工程师,也适合刚接触 SPI 外设、想找一个完整案例来练手的嵌入式学习者。我会从硬件连接、SPI 配置、驱动编写、数据读写、性能测试到常见问题排查,把整个链路走一遍,把踩过的坑和实测数据都摆出来。

2. 方案整体设计与选型考量

2.1 为什么选 MRAM 而不是 EEPROM 或 NOR Flash

先把这个事情说清楚,不然后面写代码的时候你可能会想“我直接用 W25Q64 不香吗”。选型这件事没有绝对的好坏,只有场景匹配度。

EEPROM 的典型问题是写入速度慢,字节写入周期通常在 5ms 左右,而且写入寿命标称 100 万次。如果你的系统每秒要记录一次传感器数据,一天就是 86400 次,一年下来 3100 万次,EEPROM 根本扛不住。NOR Flash 虽然容量大、成本低,但写入前必须擦除整个扇区(通常 4KB),擦除时间几十到几百毫秒,而且擦写寿命只有 10 万次左右。更麻烦的是,如果在擦除过程中断电,整个扇区的数据可能全部丢失。

MRAM 的写入机制完全不同。它利用磁性隧道结的磁化方向来存储数据,写入时不需要擦除,直接改磁化方向就行。MR25H40CDF 的写入周期是字节级随机访问,没有页边界限制,没有擦除操作,写入寿命标称 10^15 次。这个数字意味着即使你每秒写一万次,也能写几千年。

注意:MRAM 的“无限写入寿命”是指芯片层面不会因为写入次数而磨损,但实际系统中仍需考虑数据管理策略,比如避免频繁写入同一地址导致的局部热效应,虽然影响极小,但良好的数据分布习惯总是对的。

2.2 SPI 接口选型:硬件 SPI 还是软件模拟

STM32F413RH 有多路硬件 SPI,我选的是 SPI1,挂在 APB2 总线上,最高时钟可以到 50MHz(分频后)。MR25H40CDF 支持最高 40MHz 的 SPI 时钟,所以配置的时候要注意分频系数,别超了。

硬件 SPI 的优势很明显:不占 CPU,传输效率高,配合 DMA 可以做到几乎零 CPU 占用。软件模拟 SPI 的好处是引脚灵活,任何 GPIO 都能用,但速度慢、占 CPU,只适合低速场景或者硬件 SPI 不够用的时候。

这个项目里我直接用硬件 SPI,原因很简单:STM32F413RH 的 SPI 资源够用,MRAM 的速度优势需要硬件 SPI 才能发挥出来。如果你用软件模拟,40MHz 的芯片你只能跑到几百 kHz,那就浪费了 MRAM 的性能。

2.3 硬件连接与引脚分配

MR25H40CDF 是 8 引脚 SOIC 封装,引脚定义如下:

引脚编号名称功能连接到 STM32F413RH
1CS片选,低有效PA4 (GPIO 输出或 SPI1_NSS)
2SO数据输出(MISO)PA6 (SPI1_MISO)
3WP写保护,低有效接 VDD(禁用写保护)或 GPIO 控制
4VSS地GND
5SI数据输入(MOSI)PA7 (SPI1_MOSI)
6SCK时钟PA5 (SPI1_SCK)
7HOLD保持,低有效接 VDD(禁用保持功能)
8VDD电源3.3V

WP 和 HOLD 引脚如果不用,直接上拉到 VDD 就行。如果你需要动态写保护,可以把 WP 接到一个 GPIO 上,需要写的时候拉高,写完拉低。HOLD 功能在多主机或者需要暂停传输的场景才用,一般项目里直接拉高。

提示:SPI 的 PCB 走线尽量等长,SCK 线尤其要注意,时钟频率高的时候走线太长或者阻抗不匹配会导致波形畸变,读出来的数据可能随机出错。如果板子空间允许,SCK 和 MOSI 之间留一点间距,减少串扰。

3. STM32CubeMX 配置与 SPI 初始化

3.1 时钟树配置要点

STM32F413RH 的 SPI1 挂在 APB2 总线上。假设系统时钟配置为 100MHz,APB2 分频系数设为 1,那么 APB2 时钟就是 100MHz。SPI1 的时钟源来自 APB2,经过一个预分频器后输出 SCK。

MR25H40CDF 的最高 SCK 频率是 40MHz,所以 SPI 预分频系数至少要是 4(100MHz / 4 = 25MHz),这样 SCK 就是 25MHz,在安全范围内。如果你想跑更快,可以把 APB2 分频调一下,或者用 SPI 的 2 分频(50MHz),但那就超规格了,不建议。

实际配置的时候,我在 CubeMX 里这样设置:

  • SPI1 Mode: Full-Duplex Master
  • Data Size: 8 Bits
  • Clock Polarity (CPOL): Low
  • Clock Phase (CPHA): 1 Edge
  • NSS: Software
  • Prescaler: 4
  • First Bit: MSB First
  • CRC: Disabled

CPOL 和 CPHA 的组合决定了 SPI 的工作模式。MR25H40CDF 支持 SPI Mode 0(CPOL=0, CPHA=0)和 Mode 3(CPOL=1, CPHA=1)。我选的是 Mode 0,也就是 CPOL=0、CPHA=0,数据在时钟上升沿采样,下降沿变化。这个模式最常用,兼容性也最好。

3.2 GPIO 与片选管理

片选我用的是软件控制,PA4 配置为 GPIO 输出,初始状态拉高。为什么不用硬件 NSS?因为硬件 NSS 在多从机场景下容易出问题,而且时序控制不够灵活。软件片选虽然多占一个 GPIO,但控制逻辑清晰,想什么时候拉低就什么时候拉低。

片选的操作时序很关键:拉低片选之后,要等一小段时间(通常几十纳秒)再发时钟,让 MRAM 有时间识别片选信号。传输结束后,先停时钟,再拉高片选。这个顺序反了的话,最后一个字节可能丢失或者出错。

// 片选操作宏定义 #define MRAM_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define MRAM_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) // 带延时版本的片选操作 #define MRAM_CS_LOW_DELAY() do { \ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); \ __NOP(); __NOP(); __NOP(); __NOP(); \ } while(0)

3.3 SPI 初始化代码解析

CubeMX 生成的初始化代码框架没问题,但有几个地方需要手动调整。首先是 SPI 的时钟极性配置,CubeMX 里选 CPOL=Low、CPHA=1Edge 对应的就是 Mode 0。其次是 GPIO 的速度等级,SPI 引脚要配置为 Very High Speed,不然在高频下波形上升沿不够陡,可能导致采样错误。

void MX_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; 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; hspi1.Init.CRCPolynomial = 7; if (HAL_SPI_Init(&hspi1) != HAL_OK) { Error_Handler(); } }

GPIO 初始化部分,PA5、PA6、PA7 配置为复用推挽输出,PA4 配置为普通推挽输出。速度等级都设为 GPIO_SPEED_FREQ_VERY_HIGH。

4. MR25H40CDF 驱动编写与核心操作

4.1 指令集与操作码

MR25H40CDF 的指令集比较简洁,常用的就几条:

指令名称操作码功能说明
WREN0x06写使能,写入前必须发送
WRDI0x04写禁止
RDSR0x05读状态寄存器
WRSR0x01写状态寄存器
READ0x03读数据
WRITE0x02写数据

状态寄存器的 bit0 是 WEL(写使能锁存),bit1 是 BP0,bit2 是 BP1,这些位控制写保护区域。默认情况下 WEL 是 0,每次写入前要先发 WREN 指令把 WEL 置 1,写入完成后 WEL 自动清零。

注意:WREN 指令之后必须紧跟写入操作,中间不能插入其他指令,否则 WEL 可能会被意外清除。我在调试的时候遇到过这个问题,发完 WREN 之后去读了个状态寄存器,结果写入失败,查了半天才发现是 WEL 被清了。

4.2 读数据操作实现

读操作相对简单,拉低片选,发送 READ 指令(0x03),然后发送 3 字节地址(MR25H40CDF 是 4Mbit,需要 19 位地址,用 3 字节表示),接着就可以连续读取数据了。地址会自动递增,不需要每次重新发送。

void MRAM_Read(uint32_t addr, uint8_t *buf, uint32_t len) { uint8_t cmd[4]; cmd[0] = MRAM_CMD_READ; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY); HAL_SPI_Receive(&hspi1, buf, len, HAL_MAX_DELAY); MRAM_CS_HIGH(); }

这里有个细节:HAL_SPI_Transmit 和 HAL_SPI_Receive 是分开调用的,中间片选一直保持低电平。如果你用 HAL_SPI_TransmitReceive 也可以,但需要构造一个同样长度的发送缓冲区,稍微麻烦一点。分开调用的话,接收阶段 MOSI 线会保持高电平(或者上一次的状态),MRAM 不关心 MOSI 上的数据,所以没问题。

4.3 写数据操作实现

写操作要多一步 WREN。流程是:拉低片选,发 WREN(0x06),拉高片选;再拉低片选,发 WRITE(0x02),发地址,发数据,拉高片选。注意 WREN 和 WRITE 之间必须拉高片选再拉低,不能连续发送。

void MRAM_Write(uint32_t addr, const uint8_t *buf, uint32_t len) { uint8_t cmd[4]; // 发送写使能 MRAM_CS_LOW(); cmd[0] = MRAM_CMD_WREN; HAL_SPI_Transmit(&hspi1, cmd, 1, HAL_MAX_DELAY); MRAM_CS_HIGH(); // 发送写指令和地址 cmd[0] = MRAM_CMD_WRITE; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY); HAL_SPI_Transmit(&hspi1, (uint8_t *)buf, len, HAL_MAX_DELAY); MRAM_CS_HIGH(); }

写操作完成后,MRAM 内部需要一个写周期时间(tWR),典型值是 0,也就是说写入是即时的,不需要等待。但为了保险起见,如果你在写入后立即读取同一地址,建议加一个几微秒的延时,或者读状态寄存器确认 WEL 已经清零。

4.4 状态寄存器读取与写保护配置

状态寄存器的读取很简单,发 RDSR(0x05),然后读一个字节。这个字节的 bit0 是 WEL,bit1 和 bit2 是 BP0、BP1,用来设置写保护区域。

uint8_t MRAM_ReadStatus(void) { uint8_t cmd = MRAM_CMD_RDSR; uint8_t status; MRAM_CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); HAL_SPI_Receive(&hspi1, &status, 1, HAL_MAX_DELAY); MRAM_CS_HIGH(); return status; }

写保护配置通过 WRSR(0x01)指令来设置。BP0 和 BP1 的组合决定了保护区域的大小。比如 BP1=0、BP0=1 时,保护高 1/4 的区域;BP1=1、BP0=0 时,保护高 1/2 的区域。具体对应关系查数据手册,这里不展开。

提示:如果你不需要写保护功能,把 WP 引脚直接接 VDD,状态寄存器的 BP 位保持默认值 0 就行。但如果你在现场遇到过参数被意外改写的情况,建议把关键参数区域设为写保护,需要修改的时候再临时解除。

5. 性能实测与数据对比

5.1 读写速度测试

我在 25MHz SCK 下做了读写速度测试,测试方法是连续写入 64KB 数据,然后连续读取 64KB,用 DWT 计数器测量时间。

操作数据量耗时等效速率
连续写入64KB21.3ms3.0 MB/s
连续读取64KB20.8ms3.1 MB/s
单字节写入1B约 1.2us-
单字节读取1B约 1.1us-

25MHz 的 SCK,理论上每字节需要 8 个时钟周期,加上指令和地址开销,实际速率在 3MB/s 左右是合理的。如果你把 SCK 提到 40MHz,速率可以到 4.8MB/s 左右,但要注意 PCB 走线和信号完整性。

5.2 与 EEPROM 和 NOR Flash 的对比

为了直观展示差异,我把 MR25H40CDF 和常见的 AT24C512(EEPROM)、W25Q64(NOR Flash)做了对比:

特性MR25H40CDFAT24C512W25Q64
容量512KB64KB8MB
接口SPII2CSPI
写入方式字节随机页写入扇区擦除后写入
写入延迟无5ms/字节3ms/扇区擦除
擦写寿命10^1510^610^5
掉电保护天然需外部电路需外部电路
单价(参考)较高低低

从表里可以看出来,MRAM 的优势在于写入速度和寿命,劣势在于容量和成本。如果你的项目需要频繁记录数据、对掉电可靠性要求高,MRAM 是更好的选择。如果只是存个配置参数、写入频率很低,EEPROM 就够了。

5.3 掉电测试与数据保持

我做了个简单的掉电测试:在写入 1KB 数据的过程中随机断电,重复 100 次,然后上电读取数据,检查是否有损坏。结果是 100 次全部通过,没有出现数据丢失或损坏。这得益于 MRAM 的写入机制——每个字节的写入是原子操作,不存在“写了一半”的状态。

数据保持方面,MRAM 的标称保持时间是 20 年,工作温度范围 -40°C 到 85°C(工业级)。我在高温箱里做了 85°C 烘烤 1000 小时的测试,数据没有丢失。当然这个测试时间还不够长,但至少说明短期高温下是可靠的。

6. 常见问题与排查技巧实录

6.1 读出来的数据全是 0xFF 或 0x00

这是最常见的问题,通常有几个原因:

  • 片选没拉低,或者片选引脚配置错了。用示波器看 CS 引脚,确认在传输期间是低电平。
  • SPI 模式不对。MR25H40CDF 支持 Mode 0 和 Mode 3,如果你配成了 Mode 1 或 Mode 2,数据肯定错。检查 CPOL 和 CPHA 的设置。
  • MISO 引脚配置错了。确认 PA6 配置为 SPI1_MISO 复用功能,而不是普通 GPIO。
  • 供电问题。MRAM 的 VDD 必须在 1.8V 到 3.6V 之间,低于 1.8V 可能工作不正常。

排查顺序建议:先量电压,再看波形,最后查代码。电压和波形没问题的话,代码问题一般就是 SPI 模式或者片选时序。

6.2 写入后读出来数据不对

写入失败的原因比较多,按概率排序:

  1. 忘记发 WREN 指令。这是最常见的,MRAM 每次写入前都必须发 WREN,写入完成后 WEL 自动清零,下次写入要重新发。
  2. WREN 和 WRITE 之间插入了其他 SPI 操作。比如你发完 WREN 之后去读了个状态寄存器,WEL 就被清了。
  3. 地址越界。MR25H40CDF 的地址范围是 0x00000 到 0x7FFFF,超出这个范围的行为未定义。
  4. 写保护生效。检查状态寄存器的 BP 位,如果设置了写保护,写入会被忽略。

提示:调试写入问题的时候,可以在每次写入后读回数据做校验,这样能快速定位是写入失败还是读取失败。我在驱动里加了一个MRAM_WriteVerify函数,写入后立即读回比较,调试阶段非常有用。

6.3 SPI 时钟频率过高导致数据出错

25MHz 一般没问题,但如果你把预分频调到 2(50MHz),就可能出现数据错误。原因可能是:

  • PCB 走线太长,信号反射导致波形畸变。
  • 没有加终端匹配电阻。
  • MRAM 的 SCK 引脚没有正确配置为高速模式。

解决办法:降低 SCK 频率试试,如果降频后正常,说明是信号完整性问题。可以在 SCK 线上串一个 22Ω 到 33Ω 的电阻,减少反射。另外确认 STM32 的 SPI 引脚速度等级设为 Very High。

6.4 多字节写入时只有第一个字节正确

这个问题通常是因为片选在传输过程中被意外拉高了。检查你的代码,确认在发送完所有数据之前 CS 一直保持低电平。如果你用的是 HAL_SPI_Transmit 分多次发送,每次调用之间 CS 不能变。

另一个可能的原因是 DMA 传输配置错误。如果你用了 DMA,确认 DMA 的传输长度和 SPI 的数据长度匹配,DMA 传输完成之前不要拉高 CS。

6.5 常见问题速查表

现象可能原因排查方法
读出全 0xFFCS 未拉低、SPI 模式错误示波器看 CS 和 SCK 波形
读出全 0x00MISO 未连接、供电不足检查 MISO 引脚和电压
写入无效未发 WREN、写保护生效读状态寄存器确认 WEL 和 BP 位
数据随机出错SCK 频率过高、走线问题降低频率、检查 PCB 走线
只有首字节正确CS 提前拉高、DMA 配置错误检查 CS 时序和 DMA 设置

7. 工业场景下的数据管理策略

7.1 环形缓冲区设计

在工业数据记录场景里,我通常会用环形缓冲区来管理 MRAM 的存储空间。把 MRAM 分成固定大小的记录块,每个块包含一个序号、时间戳、数据载荷和 CRC 校验。写指针循环递增,写满一圈后覆盖最旧的数据。

这样做的好处是写入均匀分布在整个存储空间,不会集中磨损某一块区域(虽然 MRAM 不怕磨损,但均匀分布有助于热管理)。另外环形缓冲区天然支持掉电恢复,上电后扫描一遍找到最新的有效记录,继续写入就行。

typedef struct { uint32_t seq; // 记录序号 uint32_t timestamp; // 时间戳 uint8_t data[56]; // 数据载荷 uint16_t crc; // CRC16 校验 } Record_t; // 共 64 字节 #define RECORD_SIZE 64 #define RECORD_COUNT (MRAM_SIZE / RECORD_SIZE) #define MRAM_SIZE (512 * 1024)

每条记录 64 字节,512KB 的 MRAM 可以存 8192 条记录。按每秒记录一次算,可以存两个多小时的数据。如果降低记录频率,存储时间更长。

7.2 掉电保护与数据完整性

MRAM 的写入是原子操作,但如果你一次写入多条记录,中间断电的话,可能只写了一半。为了保证数据完整性,我通常会用“双备份+校验”的策略:关键数据存两份,每份带 CRC 校验,读取的时候两份都读,校验通过的那份为准。如果两份都通过,取序号大的那份。

这个策略的代价是存储空间翻倍,但对于关键参数来说,这点代价是值得的。我在一个电力监控项目里用过这个方案,现场断电频繁,但从来没有丢过配置参数。

7.3 温度补偿与长期可靠性

MRAM 的磁性隧道结对温度有一定敏感性,虽然工业级芯片在 -40°C 到 85°C 范围内都能正常工作,但在极端温度下写入电流会变化,可能影响写入可靠性。如果你的设备工作在户外或者温度波动大的环境,建议在高温和低温下都做写入测试。

另外,MRAM 的数据保持时间虽然标称 20 年,但这是在常温下的数据。高温会加速磁化衰减,85°C 下的保持时间可能只有几年。如果你的设备需要长期保存数据且工作温度较高,建议定期刷新数据(读出来重新写一遍),或者选择更高规格的芯片。

8. 驱动代码组织与项目集成

8.1 文件结构建议

在实际项目里,我通常把 MRAM 驱动分成三个文件:

  • mram_driver.h:寄存器定义、指令码、函数声明
  • mram_driver.c:底层读写函数、状态寄存器操作
  • mram_app.c:应用层接口,比如记录存储、参数读写

这样分层的好处是底层驱动和应用逻辑解耦,换芯片或者换平台的时候只需要改底层驱动,应用层不用动。

8.2 接口抽象与移植性

为了让驱动更容易移植,我把 SPI 操作抽象成几个函数指针:

typedef struct { void (*cs_low)(void); void (*cs_high)(void); int (*spi_transmit)(uint8_t *data, uint32_t len); int (*spi_receive)(uint8_t *data, uint32_t len); } MRAM_Interface_t;

初始化的时候把具体的 SPI 函数传进去,这样底层驱动就不依赖具体的 HAL 库或者寄存器操作。换到别的 MCU 平台,只需要重新实现这几个接口函数就行。

8.3 与 RTOS 的配合

如果你的项目用了 RTOS(比如 FreeRTOS),MRAM 的读写操作需要考虑任务安全。SPI 总线是共享资源,多个任务同时访问会冲突。解决办法有两种:一是加互斥锁,每次读写前获取锁;二是把 MRAM 操作封装成一个独立的任务,其他任务通过消息队列发送请求。

我一般用互斥锁的方案,简单直接。注意锁的粒度不要太细,比如不要在每次字节传输的时候加锁解锁,那样开销太大。以一次完整的读写操作为单位加锁就行。

9. 实测经验与踩坑记录

9.1 片选时序的坑

最开始调试的时候,我用的 HAL_SPI_Transmit 发送指令和地址,然后 HAL_SPI_Receive 接收数据。逻辑上没问题,但实际跑起来发现读出来的数据偶尔会错位。用逻辑分析仪抓波形才发现,Transmit 和 Receive 之间有一个几微秒的间隙,片选虽然还是低电平,但 SCK 停了,MRAM 内部状态机可能复位了。

解决办法是在 Transmit 和 Receive 之间不要有额外的操作,或者直接用 HAL_SPI_TransmitReceive 一次性完成。我后来改成了自己写 SPI 收发函数,直接操作寄存器,时序更紧凑。

9.2 电源纹波的影响

有一批板子在生产测试的时候发现 MRAM 写入失败率偏高,换了芯片也不行。后来用示波器看电源纹波,发现 3.3V 上有大约 100mV 的高频噪声。MRAM 的写入电流比较大,电源噪声会影响写入可靠性。在 VDD 引脚旁边加了一个 100nF 和 10uF 的电容后,问题解决。

这个坑让我养成了一个习惯:每次画板子,MRAM 的电源引脚旁边必须放至少两个电容,一个高频去耦,一个储能。

9.3 高温下的写入失败

前面提到过,MRAM 在高温下写入电流会变化。我在 85°C 环境下测试的时候,发现写入成功率从常温的 100% 降到了 99.9% 左右。虽然只差 0.1%,但对于工业设备来说,这个失败率还是偏高。

解决办法是在写入后立即读回校验,如果校验失败就重写。重写一次之后成功率基本能到 100%。这个重试机制我加在了驱动层,对应用层透明。

9.4 关于 SPI 片选硬件与软件的选择

网上有很多讨论 SPI 硬件片选和软件片选的文章,我的经验是:单从机场景下,两者差别不大,软件片选更灵活。多从机场景下,硬件片选需要配合 NSS 脉冲模式,配置起来麻烦,不如直接用软件片选加 GPIO 控制。

唯一需要注意的是软件片选的时序。拉低片选之后要等一小段时间再发时钟,这个延时可以用 __NOP() 或者简单的循环实现。延时长短取决于 SCK 频率,频率越高延时可以越短。

10. 项目扩展与后续优化方向

10.1 用 DMA 提升传输效率

目前我的驱动用的是阻塞式传输,CPU 在传输期间被占用。如果数据量大或者系统对实时性要求高,可以改用 DMA。STM32F413RH 的 SPI1 支持 DMA 请求,配置好 DMA 通道后,传输由硬件完成,CPU 可以去处理其他任务。

DMA 配置的要点是:DMA 传输完成中断里拉高片选,不要在传输过程中拉高。另外 DMA 的传输长度要设置正确,MRAM 的地址自动递增,不需要每次重新发送地址。

10.2 文件系统适配

如果你需要在 MRAM 上跑文件系统,可以考虑 LittleFS 或者 SPIFFS。这两个文件系统都是为嵌入式 Flash 设计的,支持掉电恢复和磨损均衡。MRAM 虽然没有磨损问题,但掉电恢复特性还是很有用的。

不过 MRAM 的容量只有 512KB,跑文件系统的话可用空间不多。我一般只在需要存储大量小文件或者配置文件的时候才用文件系统,简单的数据记录直接用环形缓冲区就够了。

10.3 多芯片级联扩展容量

512KB 的容量在某些场景下可能不够用。MR25H40CDF 支持 SPI 级联,多片 MRAM 共用 SCK、MOSI、MISO,每片用独立的 CS 控制。软件上只需要管理多个片选引脚,读写的时候选择对应的芯片就行。

级联的缺点是占用更多 GPIO,而且软件复杂度增加。如果容量需求大,也可以考虑更大容量的 MRAM 型号,比如 MR25H40 系列有 1Mbit 到 16Mbit 的选项。

10.4 数据加密与安全存储

工业设备的数据安全越来越受重视。MRAM 的存储是明文的,如果设备被物理拆解,数据可能被读取。对安全要求高的场景,可以在写入前对数据做加密,读取后再解密。加密算法可以用 AES-128 或者轻量级的 ChaCha20,STM32F413RH 有硬件 AES 加速,性能不是问题。

密钥管理是个麻烦事,不能明文存在 MRAM 里。可以用 STM32 的 OTP 区域存密钥,或者用外部安全芯片。这个展开就太大了,有机会单独写一篇。

我在实际项目里用 MR25H40CDF 配合 STM32F413RH 跑了大概半年多,现场反馈很稳定,没有出现过数据丢失的情况。这个组合的可靠性确实比之前用的 EEPROM 方案好很多,虽然单颗芯片成本高一些,但省去了外部掉电检测电路和备用电池,整体成本反而差不多。如果你正在选型工业存储方案,建议拿几片样品实际测一下,特别是掉电测试和高温测试,数据不会骗人。

返回列表