记一次 STM32H723 硬件 Bug 排查:写个 ETH 寄存器,SDRAM 数据飞了
前言
如果你的 STM32H723 用着外部 SDRAM(挂在 FMC 上),同时使能了以太网(ETH MAC),然后发现 SDRAM 里的数据莫名其妙被篡改了——这篇文章大概率能帮你省两天时间。
先剧透结论:STM32H723 的 ETH MAC 在复位后,短时间内连续写两个 MAC CSR 寄存器会触发虚假的 AHB 写事务,静默破坏外部 SDRAM 中的数据。 这个 Bug 不在 ST 官方的 ES0491 Errata Sheet 中,纯属偶遇。
跟用什么操作系统没有关系——裸机、FreeRTOS、RT-Thread、ThreadX 都会中招。
一、案发现场
系统硬件拓扑:
STM32H723ZG (Cortex-M7, 550MHz)
├── 64MB 外部 SDRAM @ 0xC0000000 (FMC 接口, IS42S16320F)
├── SD 卡 (SDMMC2)
├── 以太网 PHY LAN8740A (RMII)
└── 固件存放在 SD 卡,上电后加载到 SDRAM 执行
上电流程:
上电 → 初始化外设 → 初始化 ETH MAC → 从 SD 卡加载固件到 SDRAM
→ CRC32 校验固件完整性 → 跳转执行固件
现象:每次上电,固件的 CRC32 校验都失败。不是偶尔失败,是每一次。更诡异的是——computed CRC 永远是 固定的 0xD28F81A5。
不管固件内容怎么变,只要 ETH 初始化了,CRC 就是 0xD28F81A5。
二、逐一排除
2.1 是不是栈溢出了?
检查栈水位——栈空间绰绰有余。不是。
2.2 是不是 DCache 没刷?
Cortex-M7 有 DCache。如果 DMA 写的 SDRAM 内容还在 Cache 里,CPU 读到的是旧数据。
验证:CRC 前手动 invalidate 整个 DCache,把 SDRAM 区域配成 Write-Through 模式。
结果:照旧。不是 Cache 问题。
2.3 是不是 SD 卡内容本来就有问题?
同一张 SD 卡,写一段不初始化 ETH 的测试代码——CRC 通过。
SD 卡排除。
2.4 是不是内存布局冲突?
固件目标地址是 0xC0001000(SDRAM 起始偏移 4KB),Heap 在内部 SRAM,不存在重叠。
至此,常规怀疑对象全军覆没。问题一定出在某个外设在偷偷往 SDRAM 写数据。
三、锁定凶手:ETH MAC
3.1 谁有资格写 SDRAM?
在 STM32H7 的 AHB 总线矩阵上,能当 AHB Master 发起写事务的模块:
| AHB Master | 功能 |
|---|---|
| Cortex-M7 | CPU |
| DMA1 / DMA2 | 通用 DMA |
| MDMA | 多主机 DMA |
| ETH MAC | 以太网 DMA 引擎 |
| SDMMC2 IDMA | SD 卡内部 DMA |
| USB OTG | USB DMA |
SDMMC2 用 IDMA 把固件从 SD 卡搬到 SDRAM——这步是正常的,CRC 在 IDMA 传输完成后才做。所以问题一定发生在"固件加载完成之后"到"CRC 校验执行之前"这短短的时间窗口里。
3.2 对比实验
直接把整个 ETH 初始化函数砍掉(什么也不干直接返回)——CRC 通过。
问题锁定在 ETH 初始化过程中。
3.3 逐步缩小——二分注释法
ETH 初始化的步骤大概是这样:
ETH 初始化()
├── 配置 GPIO
├── 配置 MDIO (PHY 管理接口)
├── ETH MAC 复位 (AHB reset + DMAMR_SWR)
├── PHY 初始化 (自协商等)
├── MAC/DMA 配置
└── 注册组播 MAC 地址 ← 这里会写 MACHT + MACPFR 两个寄存器
实验记录:
| 实验 | 做 法 | 结 果 |
|---|---|---|
| 1 | ETH 初始化直接返回 | ✅ |
| 2 | GPIO 配完就返回 | ✅ |
| 3 | MAC 复位做完就返回 | ✅ |
| 4 | 注册组播 MAC 地址直接 return | ✅ |
| 5 | 只写 MACHT,不写 MACPFR | ✅ |
| 6 | 只写 MACPFR,不写 MACHT | ✅ |
| 7 | 两个都写(原始逻辑) | ❌ |
| 8 | 两个都写,但中间隔 10ms | ✅ |
| 9 | 两个都写,中间加 __DSB() |
❌ |
3.4 铁证
- 单独写任何一个寄存器 → 安全
- 连续写两个 → SDRAM 被破坏
- 中间加延时 → 安全
- 加 CPU barrier (
__DSB()) → 无效
__DSB() 无效说明问题不在 ARM 内核侧。是 MAC 外设内部的状态机出了岔子。
四、硬件层面的根因
4.1 关键硬件拓扑
AHB Bus Matrix (64-bit)
│
┌──────────────┼──────────────┐
│ │ │
Cortex-M7 ETH MAC FMC
(Master) (Slave+Master) (Slave)
│ │
ETH DMA Engine SDRAM
(AHB Master) (外部芯片)
ETH MAC 是一个双角色 AHB 设备:
- AHB Slave:接收 CPU 对 CSR 寄存器(MACHT、MACPFR 等)的读写
- AHB Master:DMA 引擎可以主动发起 AHB 读写事务,把网络数据包搬到系统内存
4.2 推测的故障机制
正常情况:
CPU 写 MACHT → MAC Slave 接口接收 → 更新内部寄存器 ✓
CPU 写 MACPFR → MAC Slave 接口接收 → 更新内部寄存器 ✓
ETH MAC 刚复位后(异常窗口):
CPU 写 MACHT → MAC Slave 接收,但内部状态机部分误判为"DMA 描述符就绪"
CPU 写 MACPFR → MAC Slave 接收,但内部误判为"启动 DMA 传输"
→ DMA 引擎用残留的内部寄存器值做地址,发起一次 AHB 写事务
→ AHB 矩阵把写事务路由到 FMC
→ FMC 把数据写到外部 SDRAM
→ 你的数据被静默覆盖
这也能解释为什么 corrupted CRC 是固定的:
- 虚假写事务的目标地址由 MAC 内部残留寄存器决定
- 写出去的数据也由残留状态决定
- 每次复位后残留状态相同 → 每次破坏的位置和内容都一样
4.3 验算:固件 CRC 的异常值
固件放在 0xC0001000(SDRAM 偏移 4KB 处)。如果 ETH MAC 的 DMA 引擎发出了一个地址在 FMC SDRAM 区域内的写事务,这笔数据就会直接覆盖固件镜像中的某个位置。
CRC32 是位置敏感的——只要有一个字节变了,整个 32-bit CRC 就会完全不同。0xD28F81A5 这个值就是固件镜像中被篡改后的 CRC。
4.4 ST 官方 Errata 有没有覆盖?
翻了 ES0491(STM32H72xx/73xx Errata, Rev 9, 2024-06-17)里全部 11 个 ETH 相关 errata(2.24.1 ~ 2.24.11),没有一条描述这个现象。
现有的 ETH errata 覆盖的是 DMA 仲裁优先级 bug、Bus error 恢复 bug、过滤/offload 逻辑 bug 等——没有一个提到"CSR 写触发虚假 AHB 事务"。
这是一个未被 ST 文档化的硬件 errata。
五、修复方案
推荐修复
在 ETH MAC 复位完成后加 10ms 延时,让 MAC 内部 AHB Master 状态机稳定下来:
void eth_mac_reset(void)
{
/* AHB 总线复位 */
RCC->AHB1RSTR |= RCC_AHB1RSTR_ETH1MACRST;
RCC->AHB1RSTR &= ~RCC_AHB1RSTR_ETH1MACRST;
/* DMAMR 软件复位 */
ETH->DMAMR |= ETH_DMAMR_SWR;
while (ETH->DMAMR & ETH_DMAMR_SWR); // 等待复位完成
/* 配置 DMAMR、DMASBMR 等寄存器 */
ETH->DMAMR = (ETH->DMAMR & ~DMAMR_CLEAR_MASK) | DMAMR_SET_MASK;
ETH->DMASBMR = (ETH->DMASBMR & ~DMASBMR_CLEAR_MASK) | DMASBMR_SET_MASK;
/*
* Workaround for undocumented STM32H723 ETH errata:
*
* After AHB reset + DMAMR_SWR, the MAC AHB master interface is not yet
* fully stable. Consecutive CSR writes (e.g. MACHT followed by MACPFR)
* can be misinterpreted by the internal DMA control logic, triggering a
* spurious AHB write transaction that gets routed through the bus matrix
* to FMC, silently corrupting external SDRAM.
*
* A delay here allows the internal state machine to stabilize.
* 10ms is sufficient; 1ms was also verified to work.
*/
delay_ms(10);
}
备选方案
- 调整初始化顺序:把 ETH 初始化放到所有 SDRAM 关键数据加载完成之后。
- 连续 CSR 写之间插入读回:
write_reg(MACHT, val); read_reg(MACHT); write_reg(MACPFR, val);
不建议的做法
- 依赖
__DSB()/__DMB()—— 这是 CPU 侧的 barrier,管不到 MAC 外设内部状态机。 - 单纯注释掉某个 CSR 写 —— 会破坏 MAC 功能(比如组播过滤失效)。
六、排查经验
6.1 方法论
- 先判断故障的"确定性":固定 CRC 值 = 确定性 bug,不是随机电气干扰。这是最重要的第一判断。
- 了解总线拓扑:知道哪些外设有 AHB Master 能力,才能知道谁有资格"偷偷写内存"。
- 对照实验是王道:每次只改一个变量(单写 MACHT vs 单写 MACPFR vs 都写 vs 都写+delay),结论才可靠。
- 先查官方 Errata:虽然这次是未公开的 bug,但先查一遍避免白费力气。
- 信任实验数据,不信任假设:中断洪水、DCache、栈溢出——都是合理的怀疑方向,但实验数据才是最终裁判。
6.2 教训
- 外设 DMA 引擎也是 AHB Master,它可以在 CPU 完全不知情的情况下读写任意 AHB 地址。MPU 只保护 CPU 侧访问,管不到外设 Master。
- “复位完成"不等于"状态机完全稳定”。数据手册里说的"复位后即可操作"可能只是指寄存器接口就绪,内部跨时钟域的状态机同步可能还需要额外时间。
- CSR 写不是无副作用的。你以为只是在写一个配置寄存器,实际上可能在 MAC 内部触发了你完全意想不到的状态迁移。
七、附录:复现最小代码
// 不依赖任何 OS,纯寄存器操作即可复现
// 1. 使能 ETH MAC 时钟
RCC->AHB1ENR |= RCC_AHB1ENR_ETH1MACEN;
// 2. ETH MAC 复位
RCC->AHB1RSTR |= RCC_AHB1RSTR_ETH1MACRST;
RCC->AHB1RSTR &= ~RCC_AHB1RSTR_ETH1MACRST;
ETH->DMAMR |= ETH_DMAMR_SWR;
while (ETH->DMAMR & ETH_DMAMR_SWR);
// 3. 往 SDRAM 写一些测试数据
uint32_t *sdram = (uint32_t *)0xC0001000;
sdram[0] = 0xDEADBEEF;
sdram[1] = 0xCAFEBABE;
// 4. 连续两次 CSR 写(不延时)
uint32_t tmp;
tmp = ETH->MACHT0R;
tmp |= (1 << 5);
ETH->MACHT0R = tmp; // 第 1 次写
tmp = ETH->MACPFR;
tmp |= (ETH_MACPFR_HMC | ETH_MACPFR_HPF);
ETH->MACPFR = tmp; // 第 2 次写 ← 可能破坏 SDRAM
// 5. 校验 SDRAM 数据
if (sdram[0] != 0xDEADBEEF || sdram[1] != 0xCAFEBABE) {
// SDRAM 数据被破坏了!
while (1);
}
相关寄存器地址
| 寄存器 | 地址 | 说明 |
|---|---|---|
| ETH_MACHT0R | 0x40028100 | Hash Table Register 0 |
| ETH_MACHT1R | 0x40028104 | Hash Table Register 1 |
| ETH_MACPFR | 0x40028108 | Packet Filter Register |
| ETH_DMAMR | 0x40028000 | DMA Mode Register (SWR = bit 0) |
后记
这个问题前前后后花了两天,从"CRC 不对"追到"写个寄存器 SDRAM 就被踩",跨了软件、驱动、总线矩阵、外设内部状态机四个层次。
目前已经整理成英文技术报告准备提交给 ST FAE。如果 ST 确认并给出了官方 Errata 编号,我会更新这篇文章。
更多推荐


所有评论(0)