STM32H7+LAN8720A以太网Ping不通?MPU与Cache配置是关键
1. 为什么STM32H7配LAN8720A总在Ping这一步卡住STM32H7 这颗芯片在以太网应用里算是“高配选手”主频能跑到 480MHz带 32KB 的以太网专用 DMA 描述符内存还有独立的 L1 Cache 和 MPU。但恰恰是这些高级特性让很多从 F103、F407 转过来的朋友在调 LAN8720A 时翻车——代码编译通过、PHY 寄存器读写正常、网线插上灯也亮结果一 Ping 就是Request timed out。我前后在 H743 和 H723 两块板子上各踩过一轮最后发现问题集中在三个地方MPU 配置没覆盖以太网描述符区域、Cache 一致性没处理、LAN8720A 的地址和自协商配置跟 CubeMX 默认值对不上。这篇内容就是把这套组合的完整调试链路拆开讲。适合正在用 STM32H7 系列H743/H723/H750 等搭配 LAN8720A 做以太网通信的嵌入式开发者尤其是已经跑通 CubeMX 生成代码、但卡在 Ping 不通这一步的人。我会从硬件参考电路的关键细节讲起再到 CubeMX 里 LWIP 的配置逻辑、MPU 与 Cache 的处理、PHY 地址确认最后给出完整的排查顺序。每一步都说明“为什么这么做”而不是只丢一堆配置截图。先给一个结论性的判断STM32H7 LAN8720A LWIP 这套组合Ping 不通的原因分布大概是——MPU/Cache 配置问题占四成PHY 地址或复位问题占三成时钟配置RMII REF_CLK问题占两成剩下是 LWIP 参数和硬件焊接问题。你按这个比例去排查效率会高很多。2. LAN8720A 参考电路里那几个容易被忽略的引脚2.1 RMII 模式下 REF_CLK 到底谁提供LAN8720A 支持 MII 和 RMII 两种接口STM32H7 这边为了省引脚基本都用 RMII。RMII 需要一根 50MHz 的 REF_CLK这根时钟可以由 MAC 侧STM32提供也可以由 PHY 侧提供。LAN8720A 内部有一个 PLL可以从外部 25MHz 晶振倍频出 50MHz通过nINT/REFCLKO引脚输出。关键点在于LAN8720A 的nINT/REFCLKO引脚功能由第 5 个 strap 引脚RXER/PHYAD0 复用那个区域的配置决定。具体来说芯片上电时会锁存一批配置引脚的状态其中REGOFF引脚第 16 脚拉低时内部稳压器开启nINT/REFCLKO的 REF_CLK 输出使能与否跟这个也有关系。实际电路里最常见的做法是给 LAN8720A 接一颗 25MHz 无源晶振nINT/REFCLKO配置为 REF_CLK 输出模式输出 50MHz 给 STM32 的ETH_RMII_REF_CLKPA1STM32 侧 RMII 的 REF_CLK 配置为外部输入如果你反过来让 STM32 输出 50MHz 给 PHY那 PA1 要配成 AF11 的 ETH 复用同时 PHY 侧要配置成接收外部时钟。两种方案都行但混着配就必挂。我见过一个案例CubeMX 里 ETH 的 REF_CLK 选了“外部提供”但板子上 PHY 的 REF_CLK 输出没使能结果 MAC 侧根本没有时钟MDIO 都读不到 PHY ID。提示用示波器量 PA1 引脚正常应该有干净的 50MHz 方波。如果量不到或者频率不对先别动软件把时钟源这条链路查清楚。2.2 PHY 地址由谁决定LAN8720A 的 SMI 地址由PHYAD0引脚在上电时锁存决定。这个引脚和RXER复用所以电路上通常用一个下拉或上拉电阻来设定。如果PHYAD0下拉地址是 0上拉则是 1。CubeMX 里 LWIP 的 PHY 地址默认填 0如果你的板子把PHYAD0上拉了那代码里就得改成 1否则HAL_ETH_ReadPHYRegister一直返回错误LWIP 初始化直接失败。判断方法很简单在MX_LWIP_Init之前手动调一次HAL_ETH_ReadPHYRegister(heth, 0, PHY_BSR, reg)如果返回HAL_ERROR换成地址 1 再试。两个都不行那就是硬件或时钟问题不是地址问题。2.3 复位电路与 nRST 引脚LAN8720A 的nRST是低电平复位。很多参考电路直接用一个 10K 上拉到 3.3V靠上电自然复位。但 STM32H7 上电到 GPIO 初始化完成这段时间如果 PHY 已经跑起来了可能出现状态不一致。稳妥的做法是用一个 GPIO 控制nRST在初始化 ETH 之前先拉低至少 100us 再拉高然后延时 100ms 等 PHY 内部稳定。我在 H723 板子上就遇到过不控制复位时冷启动 Ping 不通但按一下复位键就能通。原因就是 PHY 上电时序和 MCU 不同步。加了一个 GPIO 复位控制之后冷启动一次成功。3. CubeMX 里 ETH 和 LWIP 的配置逻辑3.1 ETH 外设的参数怎么填在 CubeMX 的 Connectivity 里选 ETHMode 选 RMII下面几个参数要跟硬件对上配置项常见值说明PHY InterfaceRMII跟硬件接口一致Auto NegotiationEnable让 PHY 自协商Speed100Mbps自协商成功后自动切换Duplex ModeFull全双工PHY Address0 或 1由 PHYAD0 决定Rx ModeInterrupt中断模式配合 LWIPTX/RX Descriptor根据内存规划H7 上要注意放对内存区这里重点说 PHY Address 和 Rx Mode。PHY Address 前面讲过了必须跟硬件一致。Rx Mode 选 Interrupt 还是 Polling取决于你的 LWIP 配置。如果用NO_SYS0加 FreeRTOS一般用中断模式让 ETH 中断触发信号量唤醒 LWIP 线程。如果用裸机NO_SYS1Polling 模式更简单但 CPU 占用高。3.2 LWIP 的关键参数CubeMX 的 Middleware 里选 LWIPGeneral Settings 里几个参数直接影响能不能 Ping 通LWIP_DHCP如果直连电脑建议先关掉 DHCP用静态 IP减少变量IP_ADDRESS、NETMASK、GATEWAY静态 IP 要跟电脑同网段LWIP_ICMP必须开Ping 靠的就是 ICMPLWIP_NETIF_HOSTNAME可选不影响 PingMEM_SIZEH7 内存充足给 16KB 以上比较稳我一般调试阶段的做法是先关 DHCP设静态 IP192.168.1.100掩码255.255.255.0网关192.168.1.1。电脑网卡设成192.168.1.10掩码一样。这样排除掉 DHCP 服务器和路由器的影响Ping 通了再开 DHCP。3.3 内存池和描述符放哪里STM32H7 的内存分了好几块DTCM、AXI SRAM、SRAM1/2/3、SRAM4。以太网的 DMA 描述符和缓冲区必须放在 DMA 能访问的内存区。DTCM 是紧耦合内存DMA 访问不了放这里必挂。CubeMX 默认会把 LWIP 的内存池放在0x30000000SRAM1/2/3 区域这个区域 DMA 能访问。但 ETH 的描述符数组DMARxDscrTab和DMATxDscrTab有时候会被链接脚本放到 DTCM导致 DMA 读到全 0。检查方法编译后看 map 文件确认这两个数组的地址不在0x20000000开头的 DTCM 区域。如果发现放错了有两个办法一是在链接脚本里把相关段定位到 AXI SRAM 或 SRAM1二是用__attribute__((section(.RAM_D1)))手动指定。我习惯在ethernetif.c里直接给描述符加 section 属性比改链接脚本直观。4. MPU 与 CacheH7 上 Ping 不通的头号元凶4.1 为什么 F4 上好好的代码到 H7 就不行STM32F4 没有 CacheDMA 和 CPU 访问同一块内存时数据是一致的。H7 有 L1 CacheCPU 读写内存会经过 Cache而 DMA 直接访问物理内存。这就导致一个经典问题CPU 写了一段数据到发送缓冲区数据还在 Cache 里没写回内存DMA 就去读读到的还是旧数据反过来DMA 收到数据写进内存CPU 读的时候 Cache 里还是旧内容读不到新数据。Ping 的过程正好踩中这两个方向发 ICMP 请求时 CPU 填包、DMA 发送收 ICMP 回复时 DMA 写内存、CPU 读包。任何一侧 Cache 不一致Ping 就失败。4.2 MPU 配置的正确姿势解决 Cache 一致性有两条路一是把以太网相关内存区配置成 Write-Through 或 Non-Cacheable二是手动做 Cache 维护Clean/Invalidate。实际项目里最省事的做法是用 MPU 把 ETH 描述符和缓冲区所在的内存区配成 Non-Cacheable 或 Write-Through。在 CubeMX 里可以配置 MPU也可以手动在main.c里调HAL_MPU_Config。我一般配三个区域ETH 描述符区比如0x30040000起 512 字节Non-CacheableDevice 或 Normal Non-CacheableETH 缓冲区区比如0x30040200起 16KBNon-CacheableLWIP 内存池区Write-Through或者也 Non-Cacheable配置时注意MPU_InitStruct.Number从 0 开始BaseAddress要按区域大小对齐。比如 16KB 区域基地址必须是 16KB 对齐的。注意MPU 配置必须在使能 Cache 之前完成。CubeMX 生成的代码里HAL_MPU_Config在main开头调用顺序是对的。如果你手动加确保在SCB_EnableDCache之前。4.3 一个真实的排查案例有次在 H743 上Ping 偶尔通一次大部分超时。用逻辑分析仪抓 RMII 信号发现 MAC 确实在发 ARP 请求但收到的回复包内容不对。查了半天最后发现是接收缓冲区没配 Non-CacheableDMA 写入新包后 CPU 读到的还是上一次的旧数据导致 ARP 表项一直建不起来。改成 Non-Cacheable 后立刻稳定。这个案例说明Ping 不通不一定是完全不通有时候是“看起来在通信但数据是错的”这种更难查。所以 MPU 配置一定要在调试初期就做对不要等出了问题再回头找。5. 从 PHY 寄存器到 Ping 回复的完整排查链路5.1 第一步确认 PHY 能读到在MX_LWIP_Init之前加一段测试代码uint32_t phyreg 0; if (HAL_ETH_ReadPHYRegister(heth, 0, PHY_BSR, phyreg) ! HAL_OK) { printf(PHY read failed\r\n); } else { printf(PHY BSR 0x%08X\r\n, phyreg); }PHY_BSR是基本状态寄存器地址 0x01。正常读出来 bit2Link Status在插网线后应该是 1。如果读失败检查 PHY 地址、MDIO/MDC 引脚、REF_CLK。如果读成功但 Link Status 是 0检查网线、电脑网卡、PHY 自协商。5.2 第二步确认 MAC 中断在跑ETH 初始化完成后插网线应该能看到 ETH 中断触发。在ETH_IRQHandler里加个计数器或者翻转一个 GPIO用示波器看。如果中断一直不触发说明 MAC 没收到包问题在 PHY 到 MAC 的 RMII 数据线上。RMII 的数据线是RXD0、RXD1和CRS_DV。用示波器量CRS_DV有包的时候应该有脉冲。没有脉冲就是 PHY 没收到或者没转发。5.3 第三步看 LWIP 的 netif 状态LWIP 初始化后会创建一个 netif。在MX_LWIP_Init之后打印netif-ip_addr、netif-flags。flags里NETIF_FLAG_LINK_UP和NETIF_FLAG_UP都应该是 1。如果LINK_UP是 0说明 PHY 链路没起来回到第一步查 PHY。如果 netif 状态正常但 Ping 不通用ping命令时在ethernetif_input里加打印看有没有收到包。收到包但没回复问题在 LWIP 的 ICMP 处理或发送路径。5.4 第四步抓包看 ARPPing 之前先要 ARP 解析。用 Wireshark 抓电脑网卡的包看有没有 STM32 发的 ARP 请求电脑有没有回 ARP 回复。如果 STM32 发了 ARP 请求但电脑没回可能是电脑防火墙拦了或者 IP 不在同网段。如果电脑回了但 STM32 没处理问题在接收路径。这个排查顺序的核心逻辑是从物理层往上一层一层确认。不要一上来就改 LWIP 参数那样只会越改越乱。6. 几个让我印象深刻的坑和对应的解法6.1 坑一H723 的 ETH 时钟配置跟 H743 不一样STM32H723 的 ETH 时钟源选择比 H743 少一些CubeMX 里如果选错ETH 根本工作不了。H723 的 ETH 时钟可以从 PLL1Q 或 PLL2P 来要确保选的时钟在 RMII 模式下是 50MHz或者 25MHz 再倍频。我一开始照搬 H743 的配置结果 ETH 初始化返回错误查了半天才发现时钟源选错了。解法在 CubeMX 的 Clock Configuration 里确认 ETH 的时钟输入频率。RMII 模式下如果 REF_CLK 是外部 50MHz 输入ETH 时钟就选这个如果 PHY 输出 50MHz 给 MAC那 MAC 侧时钟也是 50MHz。H723 上还要注意ETH1和ETH2的区别别选错实例。6.2 坑二LWIP 的MEM_SIZE太小导致大包丢Ping 默认包是 32 字节一般不会触发内存不足。但如果 Ping 带大包比如ping -l 1472LWIP 的MEM_SIZE不够就会丢包。H7 内存充足我一般把MEM_SIZE设成 16384 以上PBUF_POOL_SIZE设成 16 以上。另外TCP_SND_BUF和TCP_WND如果后面要跑 TCP 服务也要相应调大。调试阶段先保证 Ping 通这些参数可以后面再优化。6.3 坑三电脑防火墙拦 ICMP这个坑不在 STM32 侧但很容易被误判成 STM32 问题。Windows 默认防火墙会拦 ICMP Echo Request导致 STM32 Ping 电脑时电脑不回。测试时先把电脑防火墙关掉或者加一条入站规则允许 ICMP。判断方法用另一台电脑 Ping 这台电脑如果也 Ping 不通那就是防火墙问题跟 STM32 无关。6.4 坑四网线质量和水晶头听起来很基础但我确实遇到过网线水晶头压接不良导致链路时通时断的情况。表现是 Ping 偶尔通几个包然后全超时。换根网线立刻好。调试时手边备一根确认没问题的网线能省很多时间。7. 调试通过后的验证与稳定性测试Ping 通了只是第一步还要验证稳定性。我一般做三组测试第一组连续 Ping 1000 个包看丢包率。正常应该是 0% 丢包。如果有丢包检查 Cache 配置和中断优先级。第二组大包 Pingping -l 1472 -n 100验证缓冲区够不够。第三组长时间运行Ping 跑几个小时看会不会断。有些问题比如描述符泄漏、内存碎片要跑久了才暴露。如果这三组都过基本可以认为以太网底层是稳的。接下来再往上跑 TCP/UDP 应用就放心了。提示调试阶段可以在ethernetif_input里加一个全局计数器记录收到的包数。Ping 1000 个包计数器应该增加 1000 左右加上 ARP 包会多一点。对不上就说明有包在底层丢了。8. 关于这套组合的一些个人经验STM32H7 LAN8720A 这套组合本身是成熟的问题基本都出在 H7 特有的 Cache 和 MPU 上。我的建议是新板子第一次调试时先把 MPU 配好、Cache 处理好再动 LWIP 参数。顺序反了会浪费很多时间。另外 CubeMX 生成的代码只是起点ETH 的ethernetif.c里low_level_output和low_level_input这两个函数建议自己通读一遍理解数据从 LWIP 到 DMA 的流转过程。理解了这条链路出问题时就知道该在哪里加打印、在哪里下断点。最后说一个细节H7 的 ETH 中断优先级不要设太高也不要太低。太高会影响系统其他中断太低会导致收包不及时丢包。我一般设成中等优先级配合 FreeRTOS 时注意configMAX_SYSCALL_INTERRUPT_PRIORITY的限制别设成比它还高的优先级否则在中断里调 FreeRTOS API 会触发断言。