1. 项目缘起与方案选型
1.1 为什么要在工业场景里折腾 MRAM 这颗料
做工业嵌入式这行十来年,掉电丢数据这个坑我踩过太多次了。早些年用 EEPROM 存标定参数,写一次要等 5ms,产线工人一天要标定几百台设备,光等写入就能把人急死;后来换 FRAM,速度快了但容量小、价格高,而且很多型号供货周期飘忽不定。再后来接触到 MRAM(磁性随机存储器),才算找到一个相对平衡的方案。
MR25H40CDF 是 Everspin 出的 4Mbit 串行 MRAM,SPI 接口,512K x 8 的组织方式。它最吸引我的地方有三个:第一,写入不需要擦除,字节级随机写,不像 Flash 那样必须先擦一个扇区;第二,写入速度是纳秒级,SPI 时钟能跑到 40MHz,写一个字节基本就是总线传输时间;第三,理论上无限次擦写寿命,没有 Flash 那种 10 万次的焦虑。这三点放在工业数据记录、参数存储、掉电保护这些场景里,简直是量身定做。
STM32L162ZE 是 ST 家的低功耗 Cortex-M3,带 192KB Flash、16KB SRAM,外设资源够用,关键是它有硬件 SPI 和 LCD 控制器,做工业 HMI 面板很合适。L 系列主打低功耗,很多工业现场设备是电池或者 24V 供电,功耗敏感,选它没毛病。
这个组合的核心价值在于:用一颗几乎不用操心寿命和写入延迟的存储器,配合一颗低功耗主控,做出一套“数据想存就存、掉电也不怕”的存储方案。适合做工业仪表、数据记录仪、设备参数管理、黑匣子这类应用的工程师参考。
1.2 硬件连接方案与引脚分配
MR25H40CDF 是标准的 8 脚 SOIC 封装,引脚定义很清晰:CS、SO、SI、SCK、VDD、VSS、WP、HOLD。实际用的时候,WP 和 HOLD 这两个脚我一般直接拉高,除非你确实需要硬件写保护或者暂停传输,否则没必要占用 MCU 的 GPIO。
和 STM32L162ZE 连接,我用的是 SPI2,因为 SPI1 留给板子上的另一颗 Flash 了。具体分配如下:
| MR25H40CDF 引脚 | STM32L162ZE 引脚 | 说明 |
|---|---|---|
| CS | PB12 | 硬件片选,也可以用软件片选 |
| SO | PB14 | MISO |
| SI | PB15 | MOSI |
| SCK | PB13 | 时钟 |
| WP | 3.3V | 硬件写保护关闭 |
| HOLD | 3.3V | 不暂停传输 |
| VDD | 3.3V | 供电 |
| VSS | GND | 地 |
这里有个细节要注意:MR25H40CDF 的供电范围是 2.7V 到 3.6V,STM32L162ZE 的 IO 也是 3.3V,电平匹配没问题,不需要电平转换。但如果你用的是 5V 主控,那就必须加电平转换芯片,否则会打坏 MRAM。
关于片选,我强烈建议用硬件片选(NSS 由 SPI 外设自动控制),而不是软件 GPIO 拉低拉高。原因很简单:MRAM 的 SPI 时序对 CS 的建立和保持时间有要求,软件片选在中断打断的时候容易出问题,硬件片选由 SPI 外设硬件控制,时序更稳。当然,如果你 SPI 外设的 NSS 引脚被占用了,那就只能用软件片选,但记得在 CS 拉低和拉高之间不要插入任何可能被打断的操作。
1.3 SPI 模式与时钟配置的取舍
MR25H40CDF 支持 SPI Mode 0 和 Mode 3,也就是 CPOL=0/CPHA=0 或者 CPOL=1/CPHA=1。我一般用 Mode 0,因为大部分 STM32 的 SPI 例程默认就是 Mode 0,改起来麻烦少。
时钟频率方面,MR25H40CDF 最高支持 40MHz。STM32L162ZE 的 SPI2 挂在 APB1 上,最高 32MHz。所以实际能跑到的最高时钟是 32MHz,达不到 40MHz。不过 32MHz 已经够快了,读一个字节大概 0.25us,写一个字节也差不多这个量级。如果你对速度没那么极致的要求,跑 16MHz 或者 8MHz 也完全够用,而且时序余量更大,PCB 布线不好的时候更稳。
这里给一个 SPI 初始化的关键参数表:
| 参数 | 值 | 说明 |
|---|---|---|
| 模式 | Master | 主模式 |
| 数据宽度 | 8 bit | 字节传输 |
| 时钟极性 | Low | CPOL=0 |
| 时钟相位 | 1 Edge | CPHA=0 |
| NSS | Hardware | 硬件片选 |
| 预分频 | 2 | 32MHz |
| 首比特 | MSB | 高位先出 |
| CRC | Disable | 不用 CRC |
注意:MRAM 的 SPI 时序里,CS 拉低到第一个时钟沿之间需要至少 5ns 的建立时间,STM32 硬件 NSS 自动满足这个要求,但软件片选的时候要确保拉低 CS 之后稍微延时一下再发数据。
2. MRAM 读写机制与 STM32 驱动实现
2.1 MR25H40CDF 的指令集与操作逻辑
MR25H40CDF 的指令集不复杂,常用的就几条:WREN(写使能,0x06)、WRDI(写禁止,0x04)、RDSR(读状态寄存器,0x05)、WRSR(写状态寄存器,0x01)、READ(读数据,0x03)、WRITE(写数据,0x02)。还有高速读指令 0x0B,但一般用不上。
这里要重点说一下写操作的流程。MRAM 虽然不需要擦除,但写之前必须先发 WREN 指令,把内部的写使能锁存器置位。然后发 WRITE 指令,后面跟 3 个字节的地址(因为 4Mbit = 512KB,需要 19 位地址,用 3 个字节表示),再跟要写的数据。CS 拉高之后,数据才真正写入。如果你发了 WRITE 指令但没先发 WREN,写操作会被忽略,而且状态寄存器里的 WEL 位不会置位。
读操作就简单了,直接发 READ 指令 + 3 字节地址,然后时钟继续打,数据就从 SO 脚出来了。可以连续读,地址会自动递增,读到 0x7FFFF 之后会回卷到 0x00000。
状态寄存器只有 bit0(WEL)和 bit1(BP0)、bit2(BP1)有用。WEL 表示写使能锁存器状态,BP0/BP1 是块保护位,可以保护部分地址区域不被写入。我一般不用块保护,因为工业场景里经常需要全片读写,保护了反而麻烦。
2.2 底层驱动代码的编写要点
先贴一段我实际项目里用的 SPI 初始化代码,基于 STM32 HAL 库:
void MRAM_SPI_Init(void) { hspi2.Instance = SPI2; hspi2.Init.Mode = SPI_MODE_MASTER; hspi2.Init.Direction = SPI_DIRECTION_2LINES; hspi2.Init.DataSize = SPI_DATASIZE_8BIT; hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; hspi2.Init.CLKPhase = SPI_PHASE_1EDGE; hspi2.Init.NSS = SPI_NSS_HARD_OUTPUT; hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi2.Init.TIMode = SPI_TIMODE_DISABLE; hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi2.Init.CRCPolynomial = 10; HAL_SPI_Init(&hspi2); }然后是写使能和写操作的封装:
void MRAM_WriteEnable(void) { uint8_t cmd = 0x06; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi2, &cmd, 1, 100); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); } void MRAM_Write(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd[4]; cmd[0] = 0x02; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; MRAM_WriteEnable(); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi2, cmd, 4, 100); HAL_SPI_Transmit(&hspi2, data, len, 1000); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); }读操作:
void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { uint8_t cmd[4]; cmd[0] = 0x03; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET); HAL_SPI_Transmit(&hspi2, cmd, 4, 100); HAL_SPI_Receive(&hspi2, buf, len, 1000); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET); }这里有个坑要提醒:HAL_SPI_Transmit 和 HAL_SPI_Receive 在连续调用的时候,中间 CS 不能拉高,否则指令序列就断了。我上面代码里 CS 是在整个操作前后才动的,中间不动,这是对的。但如果你用 HAL_SPI_TransmitReceive 分开写,要注意 CS 的管理。
还有一个细节:MRAM 写完之后,数据是立即生效的,不需要等待写完成。但如果你连续写多个字节,中间不需要重新发 WREN,因为 WREN 在 CS 拉高之后才会被清除。所以一次 WRITE 操作可以写多个字节,只要 CS 保持低。
2.3 地址管理与数据组织策略
512KB 的空间说大不大,说小不小。工业应用里,我一般把空间分成几个区域:
| 地址范围 | 用途 | 说明 |
|---|---|---|
| 0x00000 - 0x00FFF | 系统参数区 | 设备ID、版本号、标定系数 |
| 0x01000 - 0x01FFF | 备份参数区 | 系统参数的备份,用于恢复 |
| 0x02000 - 0x7FFFF | 数据记录区 | 循环记录,带时间戳 |
系统参数区我一般用结构体来组织,写的时候直接按结构体大小写入,读的时候按结构体解析。但要注意字节对齐问题,STM32 是 little-endian,MRAM 也是按字节存,所以结构体直接 memcpy 就行,不需要额外转换。
数据记录区我用循环队列的方式,每一条记录包含时间戳、数据长度、数据内容和 CRC 校验。写指针存在参数区里,每次写记录之前先更新写指针,再写数据,最后更新写指针。这样即使写数据的过程中掉电,最多丢一条记录,不会把整个队列搞乱。
实操心得:MRAM 虽然写入快,但也不是无限快。连续写的时候,SPI 总线的带宽是瓶颈。32MHz 时钟下,理论最大写入速度是 4MB/s,但实际因为指令开销和 CS 切换,能到 1MB/s 就不错了。如果你要记录高频数据,建议先在 RAM 里缓冲,攒够一批再写 MRAM。
3. 工业场景下的数据存储与读取实战
3.1 掉电保护与数据完整性设计
工业现场最怕的就是掉电丢数据。MRAM 本身不怕掉电,但如果你在写数据的过程中掉电,数据可能只写了一半。所以需要一套完整性保护机制。
我的做法是:每条记录后面跟一个 CRC16 校验,读的时候先校验 CRC,校验不过就认为这条记录无效,跳过。写的时候,先写数据,再写 CRC,最后更新写指针。这样即使掉电发生在写数据的过程中,写指针还没更新,下次上电会从旧的写指针位置重新写,覆盖掉不完整的数据。
具体流程:
- 读当前写指针
- 在写指针位置写入数据
- 计算 CRC,写入 CRC
- 更新写指针到下一个位置
- 如果写指针超出范围,回卷到起始地址
这个流程里,第 4 步是关键。写指针更新必须是原子操作,不能被打断。我一般用关中断的方式来保护,因为写指针只有 4 个字节,关中断时间很短,不影响系统实时性。
还有一个细节:MRAM 的写入是字节级的,但 SPI 传输是字节流。如果你写一个 4 字节的写指针,中间掉电,可能只写了 2 个字节。所以写指针本身也需要保护。我的做法是写指针存两份,一份在参数区,一份在备份区,上电的时候比较两份,如果不一样,用 CRC 校验哪份是完整的。
3.2 参数存储与版本管理
工业设备经常需要现场标定,标定参数需要保存。我用 MRAM 存参数的时候,会加一个版本号和 CRC。版本号每次参数修改就加一,CRC 用来校验参数完整性。
参数结构体大概长这样:
typedef struct { uint16_t version; uint32_t device_id; float calib_k; float calib_b; uint16_t crc; } DeviceParams_t;写参数的时候,先算 CRC,再整体写入。读参数的时候,先读出来,校验 CRC,如果 CRC 不对,就从备份区读。备份区是参数区的一个镜像,每次写参数的时候同时写两份。
这里有个经验:参数区不要放在地址 0 开始的地方,因为有些 MRAM 在上电的时候地址 0 可能会有不确定的状态。我一般从 0x100 开始放参数,前面留一点空间做保留。
3.3 高速数据记录的实现
有些应用需要记录高速数据,比如振动传感器、电流波形。这种场景下,SPI 的带宽是瓶颈。我的做法是:
- 在 RAM 里开一个环形缓冲区,大小根据 RAM 剩余空间定,一般 4KB 到 8KB
- 数据先写入 RAM 缓冲区
- 当缓冲区半满或者全满的时候,触发 DMA 传输到 MRAM
- DMA 传输完成中断里更新写指针
用 DMA 的好处是 CPU 不用管数据传输,可以继续处理其他任务。STM32L162ZE 的 SPI2 支持 DMA,配置起来也不复杂。
DMA 配置的关键点:
// DMA 发送配置 hdma_spi2_tx.Instance = DMA1_Channel5; hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_spi2_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_spi2_tx.Init.Mode = DMA_NORMAL; hdma_spi2_tx.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_spi2_tx); __HAL_LINKDMA(&hspi2, hdmatx, hdma_spi2_tx);注意:DMA 传输的时候,CS 必须保持低电平。如果你用硬件 NSS,SPI 外设在 DMA 传输期间会自动控制 CS,没问题。但如果你用软件 NSS,必须在启动 DMA 之前拉低 CS,DMA 完成中断里拉高 CS,中间不能被打断。
3.4 读取性能优化与缓存策略
读 MRAM 比写简单,但也有一些优化空间。如果你的应用需要频繁读取某些参数,可以在 RAM 里做缓存。比如设备参数,上电的时候读一次,缓存在 RAM 里,后续直接读 RAM,只有参数修改的时候才写 MRAM 并更新缓存。
对于数据记录区的读取,如果数据量大,可以用批量读取。MRAM 支持连续读,地址自动递增,所以一次 SPI 传输可以读很多字节。我一般一次读 256 字节或者 512 字节,减少 CS 切换次数。
还有一个技巧:如果你需要读取的数据在 MRAM 里是连续存放的,可以用 DMA 接收,进一步降低 CPU 占用。DMA 接收的配置和发送类似,只是方向反过来。
4. 常见问题排查与避坑指南
4.1 SPI 通信失败排查流程
SPI 通信失败是最常见的问题,我一般按以下顺序排查:
| 步骤 | 检查项 | 可能问题 | 解决方法 |
|---|---|---|---|
| 1 | 供电 | MRAM 没供电或电压不对 | 万用表量 VDD,确保 3.3V |
| 2 | 片选 | CS 没拉低或拉低不够 | 示波器看 CS 波形 |
| 3 | 时钟 | SCK 没输出或频率不对 | 示波器看 SCK,确认预分频 |
| 4 | 模式 | CPOL/CPHA 不对 | 改成 Mode 0 试试 |
| 5 | 数据 | MOSI/MISO 接反 | 检查 PCB 连线 |
| 6 | 指令 | WREN 没发或发错 | 逻辑分析仪抓 SPI 波形 |
我遇到过最诡异的一次是 CS 拉低了但 SCK 没输出,查了半天发现是 SPI 外设的时钟没使能。STM32 的 SPI2 挂在 APB1 上,__HAL_RCC_SPI2_CLK_ENABLE() 这行代码漏了,SPI 寄存器写不进去,自然没输出。
还有一次是 MISO 一直高电平,读出来全是 0xFF。后来发现是 MRAM 的 HOLD 脚悬空了,被干扰拉低,导致 MRAM 一直处于暂停状态。把 HOLD 拉高就好了。
4.2 写入失败与数据错乱分析
写入失败一般有几个原因:
第一,WREN 没发或者发完之后 CS 拉高了。WREN 指令必须在 CS 拉低的状态下发,发完之后 CS 不能拉高,要紧接着发 WRITE 指令。如果你用 HAL_SPI_Transmit 发 WREN,函数返回之后 CS 会被 HAL 库自动拉高(如果你配置了硬件 NSS),那 WREN 就失效了。所以 WREN 和 WRITE 必须在同一个 CS 低电平周期内完成。
第二,地址越界。MR25H40CDF 的地址范围是 0x00000 到 0x7FFFF,如果你写了 0x80000,地址会回卷到 0x00000,数据就写到别的地方去了。所以写之前一定要检查地址范围。
第三,块保护位被设置了。状态寄存器的 BP0/BP1 如果被置位,对应的地址区域会被保护,写不进去。上电的时候读一下状态寄存器,确认 BP0/BP1 是 0。
第四,电源不稳。MRAM 在写入的时候电流会比读的时候大,如果电源纹波太大,可能导致写入失败。建议在 MRAM 的 VDD 脚旁边放一个 0.1uF 的陶瓷电容,离引脚越近越好。
4.3 时序余量与 PCB 布线建议
SPI 跑高速的时候,PCB 布线很关键。我总结了几条经验:
- SCK 走线尽量短,不要有过孔,如果必须过孔,不要超过 2 个
- MISO 和 MOSI 不要平行走太长,避免串扰
- CS 走线远离 SCK,避免耦合
- MRAM 的 VDD 和 GND 之间加 0.1uF 电容,越近越好
- 如果 SPI 频率超过 16MHz,建议在 SCK 上串一个 22 欧姆的电阻,减少反射
还有一点:MRAM 的 WP 和 HOLD 脚如果不用,直接拉高,不要悬空。悬空的话,外部干扰可能让这两个脚电平跳变,导致写保护或者传输暂停。
4.4 低功耗设计中的注意事项
STM32L162ZE 主打低功耗,但 MRAM 在待机的时候也有功耗。MR25H40CDF 的待机电流大概是 10uA 左右,不算大,但如果你的系统要求极致低功耗,可以在不用的时候把 MRAM 的供电切断。
不过切断供电有个问题:MRAM 虽然是非易失的,但上电之后需要一点时间才能稳定。如果你频繁切断供电,每次上电都要等 MRAM 稳定,反而影响响应速度。我的做法是:如果系统进入低功耗模式,MRAM 的 CS 保持高电平,SPI 外设关闭,MRAM 进入待机模式,功耗降到 10uA 以下。如果系统完全断电,那 MRAM 数据也不会丢,下次上电直接读就行。
还有一个细节:STM32L162ZE 的 SPI 外设在低功耗模式下可以关闭,但关闭之前要确保没有正在进行的传输。如果 DMA 还在传输,直接关 SPI 会导致数据错乱。所以进低功耗之前,先检查 DMA 状态,等传输完成再关。
5. 性能实测与方案对比
5.1 读写速度实测数据
我用逻辑分析仪抓了实际波形,测了一下读写速度。测试条件:SPI 时钟 32MHz,Mode 0,硬件 NSS。
| 操作 | 数据量 | 耗时 | 等效速度 |
|---|---|---|---|
| 单字节写 | 1 字节 | 约 1.2us | 0.83MB/s |
| 256 字节连续写 | 256 字节 | 约 68us | 3.76MB/s |
| 单字节读 | 1 字节 | 约 0.9us | 1.11MB/s |
| 256 字节连续读 | 256 字节 | 约 66us | 3.88MB/s |
可以看到,连续读写的时候速度接近 SPI 总线的理论极限(32MHz / 8 = 4MB/s),单字节操作因为指令开销,速度会低一些。所以批量操作的时候尽量一次多传一些数据,减少指令开销。
5.2 与 EEPROM、FRAM、Flash 的对比
| 特性 | MR25H40CDF | EEPROM | FRAM | NOR Flash |
|---|---|---|---|---|
| 写入速度 | 纳秒级 | 毫秒级 | 纳秒级 | 毫秒级(需擦除) |
| 擦写寿命 | 无限 | 100万次 | 10^14次 | 10万次 |
| 写入方式 | 字节级 | 字节级 | 字节级 | 扇区级 |
| 容量 | 512KB | 通常小于 128KB | 通常小于 256KB | 1MB 到 16MB |
| 功耗 | 低 | 中 | 低 | 中 |
| 成本 | 高 | 低 | 高 | 低 |
从表里可以看出,MRAM 的优势在于写入速度和寿命,劣势在于成本和容量。如果你的应用需要频繁写入、掉电保护、长寿命,MRAM 是首选。如果只是存一些不常改的参数,EEPROM 就够了。如果需要大容量存储,Flash 更合适。
5.3 实际项目中的选型建议
我做过一个工业数据记录仪的项目,要求每秒记录 100 次传感器数据,每次 32 字节,设备寿命 10 年。算一下总写入量:100 次/秒 * 32 字节 * 3600 秒 * 24 小时 * 365 天 * 10 年 = 约 1TB 的写入量。
如果用 EEPROM,100 万次擦写寿命,每个地址写 100 万次,总容量 128KB,那总写入量是 128KB * 100万 = 128GB,不够。如果用 Flash,10 万次擦写,1MB 容量,总写入量 100GB,也不够。只有 MRAM,无限擦写,512KB 容量,可以循环写,总写入量不受寿命限制,只要容量够周转就行。
所以这个项目我选了 MR25H40CDF,实际跑下来,数据记录很稳,没有丢过数据。
6. 进阶应用与扩展思路
6.1 多设备共享 SPI 总线的注意事项
如果你的系统里有多颗 SPI 设备,共享 SCK、MOSI、MISO,每颗设备独立 CS,那要注意几点:
第一,同一时刻只能有一颗设备的 CS 拉低,否则数据会冲突。软件上要保证 CS 互斥,最好用 RTOS 的互斥锁来保护。
第二,不同设备的 SPI 模式可能不一样。比如 MRAM 用 Mode 0,但另一颗 Flash 可能用 Mode 3。切换设备的时候要重新配置 SPI 模式,不能直接切换 CS。
第三,MISO 线上如果有多颗设备,不用的设备 MISO 应该是高阻态。但有些设备 MISO 不是高阻态,会拉低总线,导致通信失败。这种情况下需要加一个多路复用器,或者用二极管隔离。
6.2 数据加密与安全存储
工业设备的数据有时候需要保密,比如配方参数、标定系数。MRAM 本身没有加密功能,但可以在写入之前用软件加密。
我一般用 AES-128 加密,密钥存在 STM32L162ZE 的 Flash 里,或者用芯片的唯一 ID 派生。加密之后的数据写入 MRAM,读出来再解密。这样即使有人把 MRAM 拆下来读,也拿不到明文数据。
不过加密会增加 CPU 开销,AES-128 加密 32 字节大概需要几十微秒,如果数据量大,可能影响实时性。所以加密策略要根据实际需求来定,不是所有数据都需要加密。
6.3 固件升级与参数迁移
设备固件升级的时候,参数可能需要迁移。比如新固件增加了新的参数项,旧参数需要保留,新参数给默认值。我的做法是在参数区加一个版本号,升级的时候读旧版本参数,根据版本号决定怎么迁移。
MRAM 的好处是,升级过程中如果掉电,参数不会丢。下次上电继续升级,读到的还是旧参数,不会出现参数丢失的情况。这一点比 Flash 强,Flash 在擦除的时候掉电,整个扇区都可能丢。
7. 调试工具与实操技巧
7.1 逻辑分析仪抓 SPI 波形的要点
调试 SPI 的时候,逻辑分析仪是必备工具。我一般用 8 通道的,接 CS、SCK、MOSI、MISO 四根线就够了。
抓波形的时候要注意几点:
- 采样率要足够高,至少是 SPI 时钟的 10 倍。32MHz 的 SPI,采样率至少 320MHz,一般逻辑分析仪达不到,那就用 100MHz 采样,虽然波形有点失真,但看指令和数据还是够的。
- 触发条件设成 CS 下降沿,这样每次 CS 拉低都会触发,方便抓完整的操作序列。
- 解码的时候选 SPI 协议,设置好 CPOL、CPHA、MSB/LSB,逻辑分析仪会自动解析出指令和数据。
我遇到过一个问题:逻辑分析仪抓到的数据是对的,但 MRAM 就是写不进去。后来发现是逻辑分析仪的探头电容太大,影响了 SPI 信号质量。换了一根短的探头线就好了。所以抓波形的时候,探头线尽量短,不要绕来绕去。
7.2 用 STM32CubeMX 快速生成初始化代码
STM32CubeMX 是个好东西,SPI 初始化代码可以自动生成,省得自己算分频系数。但有几个地方要注意:
- 在 SPI 配置界面,Mode 选 Full-Duplex Master,Hardware NSS Signal 选 Hardware NSS Output,这样 CS 由硬件控制。
- 时钟配置界面,确认 APB1 的时钟频率,SPI2 挂在 APB1 上,分频系数根据 APB1 频率算。
- 生成代码的时候,选 HAL 库,不要选 LL 库,LL 库虽然效率高,但可读性差,调试不方便。
生成的代码里,MX_SPI2_Init() 函数就是初始化代码,直接调用就行。但要注意,CubeMX 生成的代码默认是 Mode 0,如果你需要 Mode 3,要手动改。
7.3 常见故障速查表
| 现象 | 可能原因 | 排查方法 | 解决方法 |
|---|---|---|---|
| 读出来全是 0xFF | MISO 没接或 HOLD 拉低 | 量 MISO 电压 | 检查连线,HOLD 拉高 |
| 读出来全是 0x00 | MISO 接地或 CS 没拉低 | 量 MISO 电压 | 检查 CS 信号 |
| 写入后读出来不对 | WREN 没发或 CS 提前拉高 | 逻辑分析仪抓波形 | 确保 WREN 和 WRITE 在同一 CS 周期 |
| 偶尔写入失败 | 电源纹波大 | 示波器看 VDD | 加滤波电容 |
| 高速时通信不稳定 | PCB 布线不好 | 降低 SPI 频率试试 | 优化布线,加串阻 |
| 地址越界 | 地址计算错误 | 检查地址范围 | 确保地址在 0x00000-0x7FFFF |
这张表是我这些年踩坑总结出来的,基本上覆盖了 90% 的 SPI MRAM 问题。遇到问题先查表,能省不少时间。
8. 写在最后的一些个人体会
这个方案我在三个项目里用过,最长的已经跑了 5 年,数据记录一直很稳。MRAM 这颗料虽然贵,但在工业场景里,可靠性比成本更重要。一次现场故障的维修成本,可能比十颗 MRAM 还贵。
STM32L162ZE 和 MR25H40CDF 的搭配,我觉得最适合的是中等数据量、高频写入、掉电保护要求高的场景。如果你的数据量特别大,比如视频记录,那 MRAM 的容量不够,还是得用 Flash 或者 SD 卡。如果你的写入频率很低,比如一天写一次,那 EEPROM 就够了,没必要上 MRAM。
最后分享一个小技巧:MRAM 的 CS 脚上可以加一个下拉电阻,比如 10K,这样在 MCU 还没初始化的时候,CS 保持低电平,MRAM 不会误触发。虽然 MRAM 本身有上电复位电路,但加个下拉电阻更保险。这个电阻在 PCB 上占不了多少地方,但能避免一些莫名其妙的问题。