
刚拿到 STM32H750B-DK 那块板子的时候我干的第一件事就是烧一个带 lwIP 的以太网例程进去。PC 配好静态 IP插上网线清一遍 ARP 缓存然后 ping 192.168.1.10。等来的不是 Reply from而是连续四个 Request timed out。相信不少人都经历过这个瞬间STM32H750B-DK 的 Ethernet 接口从现象上看完全不响应 Ping可是复位、重烧、换网线、改 IP 反复试了一圈问题纹丝不动。这种看似简单却怎么都不通的问题最磨人。H750 的硬件链路从 PHY 芯片、RMII 信号线、时钟源到 MAC 控制器、DMA 描述符、lwIP 协议栈再到 PC 端的网卡和防火墙中间任何一环出错最终表现都是同一个ping 不通。这篇文章我按自己调板时真实的排查顺序来写从链路指示灯、MDIO 寄存器、50MHz 参考时钟、引脚复用到 lwIP 协议栈和 PC 端抓包一条线捋到底。无论你是刚拿到 H750B-DK 的新手还是正在调自研板卡的工程师里面应该都有可以直接照做的检查点。1. PHY指示灯与MDIO寄存器先确认芯片有没有活过来1.1 三种指示灯状态对应的问题范围STM32H750B-DK 板载的 PHY 是 Microchip LAN8742A它在 RJ45 座子旁边有两个状态灯。调试的第一步不是打开代码而是看灯。如果插上网线后链路灯完全不亮问题大概率在网络物理链路这一层。先做三件最基础的事换一根确认没问题的网线确认 PC 网卡已经启用确认直连电脑时用的是正常网口而不是 IPMI 之类的管理口。H750B-DK 的 RJ45 支持 Auto-MDI/X直连电脑不需要交叉线但凡说得用交叉线的说法基本都过时了。如果链路灯亮了但 activity 灯在 ping 的时候没有闪烁说明 PHY 已经建立物理链路但数据收发可能没有跑起来。这时候故障已经进入 PHY 初始化和 MAC 层范畴。如果两个灯都正常ping 还是不通那就要把目光转移到 lwIP 协议栈配置、MAC 地址、PC 端 ARP 缓存甚至防火墙上了。1.2 用HAL_ETH_ReadPHYRegister直读PHY状态眼睛会骗人寄存器不会。我排查网络问题从来不会在没读寄存器之前就乱改代码。打开 STM32CubeIDE 的调试模式全速跑起来在 Expression 窗口直接调用 HAL 库函数读 PHY 寄存器uint32_t bcr HAL_ETH_ReadPHYRegister(heth, 0x00, 0x00); // Basic Control Register uint32_t bsr HAL_ETH_ReadPHYRegister(heth, 0x00, 0x01); // Basic Status Register uint32_t phy_id1 HAL_ETH_ReadPHYRegister(heth, 0x00, 0x02); // PHY ID1 uint32_t phy_id2 HAL_ETH_ReadPHYRegister(heth, 0x00, 0x03); // PHY ID2LAN8742A 的标准 PHY 地址是 0x00但好在 H750B-DK 的 PHYAD[1:0] 引脚默认接地所以绝大多数情况下地址就是 0。读出来的值里重点关注 BSR 的 Bit 2这一位是 Link Status寄存器地址关键位正常现象BCR0x00Bit 15 软件复位复位后自返 0BSR0x01Bit 2 Link 状态插上网线应为 1PHY ID10x02厂商 ID 高 16 位LAN8742A 通常读到 0x0007PHY ID20x03厂商 ID 低 16 位通常读到 0xC130 附近如果 PHY ID 能正常读出来就说明 MDC/MDIO 通信链路是通的PHY 芯片的核心逻辑也活着。这时候 PHY 的 Link 状态位如果已经置 1那物理层基本没有大问题可以放心往上查。1.3 PHY地址和复位引脚读不到寄存器的两个高频原因如果读回来全是 0xFFFF或者 HAL 函数直接超时最常见的原因有两个。第一是 PHY 地址配置不对。CubeMX 里 ETH 外设配置页面有一个PHY address参数很多人习惯性填 1但板子上 LAN8742A 实际地址是 0。地址错了MDIO 总线上根本找不到从设备返回值自然异常。这种情况在自研板或者从别的工程复制过来的代码里特别多拿到 H750B-DK 开发板反而少一点但也不能完全排除用户自己改过配置。第二是 PHY 复位引脚没有正确拉高。LAN8742A 的 NRST 如果被拉低芯片一直处于复位态读任何寄存器都是无效的。H750B-DK 上这个引脚由 MCU 控制你在 CubeMX 里应该能看到一个 GPIO 输出口初始化为高电平。检查一下这个引脚的初始化代码是否在 ETH 外设初始化之前执行了。2. 50MHz参考时钟RMII模式下LAN8742A起不来的头号嫌疑2.1 为什么RMII模式必须在PHY外部给50MHz很多第一次调 H7 以太网的人都会在这个地方卡住因为 STM32 其他外设的时钟通常由内部 PLL 自动分配根本不用管。但 RMII 模式的以太网不一样PHY 需要一个独立的外部 50MHz 参考时钟。RMII 的数据通道是 2 位 RXD、2 位 TXD配合 TX_EN、CRS_DV、REF_CLK 信号工作。数据线上每个时钟周期传输 2 个比特要在 100Mbps 速率下工作参考时钟必须稳定在 50MHz。LAN8742A 内部没有像某些 PHY 那样的 PLL 倍频电路不能自己从 25MHz 晶振里变出 50MHz 来必须由外部给它送入一个干净的 50MHz 时钟。这里有个很多人踩过的坑以为 MDIO 能读到 PHY ID 就万事大吉。实际上如果 PHY 没有 50MHz 参考时钟某些状态下 MCU 通过 MDIO 仍然可能读到部分寄存器值但以太网数据通路完全不工作。也就是寄存器活着数据死了。2.2 MCO2输出和板载晶振两种供电方案怎么区分开发板上这个 50MHz 从哪来有两种常见设计。一种是板载独立 50MHz 晶振直接接到 LAN8742A 的 XI 引脚和 STM32 没关系。这种方式下只要 PHY 上电就有参考时钟软件层面不用专门配置时钟源。另一种是 STM32 通过 MCO2 引脚PC9输出 50MHz 时钟给 LAN8742A然后 PHY 再把这个 REF_CLK 信号反馈回 STM32 的 PA1ETH_RMII_REF_CLK。这种方案的目的是少一颗晶振降低成本但代价是必须在代码里把 MCO2 正确配置成 50MHz 输出。怎么区分你的板子是哪种最快的办法是看原理图。找到 LAN8742A 的 REF_CLK 或 XI 引脚看它连着晶振还是连着 MCU 的 PC9。H750B-DK 官方板子用户如果你是从 CubeMX 例程生成的工程大概率已经在 CubeMX 的 ETH 配置里帮你选好了时钟来源但如果你是自己从头配置的就很容易漏掉这一项。2.3 在CubeMX时钟树里检查MCO2的典型配置如果你确认板子是 MCO2 供电方案那就在 CubeMX 的 Clock Configuration 页面里往下找找到 MCO2将它设置为 50MHz。设置路径通常是让 PLL 的某个分频输出恰好等于 50MHz比如 PLL1Q 或者 PLL2P具体选哪个取决于整体时钟树如何分配。设置完成之后生成代码时会在HAL_RCC_MCOConfig相关的初始化代码中体现。你可以直接编译烧录然后用示波器量 PC9 引脚或者更直接一点量 PHY 的 REF_CLK 引脚看是否有 50MHz 方波。如果你的板子是外部晶振方案那么 PC9 这个引脚可以挪作他用但必须在 CubeMX 的 ETH 配置里正确选择时钟源类型。ETH 外设的 RMII 时钟源有两种选项External PHY clock和From MCO。选错会导致 MAC 侧收不到正确的 REF_CLK表现同样是 ping 不通。这一点非常隐蔽因为它不会报错也不影响编译只是在运行时数据通路完全瘫痪。3. RMII引脚复用与信号波形逐个核对寄存器之外的物理层细节3.1 STM32H750的RMII引脚映射与AF配置ETH 外设的 GPIO 复用CubeMX 在勾选 ETH 后一般会自动配好但如果你的工程是从别的地方复制过来的或者手动改过引脚功能就需要逐项核对。STM32H750 的 RMII 引脚和 AF 号如下信号引脚复用功能ETH_REF_CLKPA1AF11ETH_MDIOPA2AF11ETH_MDCPC1AF11ETH_CRS_DVPA7AF11ETH_RXD0PC4AF11ETH_RXD1PC5AF11ETH_TX_ENPG11AF11ETH_TXD0PG13AF11ETH_TXD1PG14AF11这里有一个值得注意的细节MDIO 通常被配置为开漏模式并外接上拉MDC 是推挽输出其他数据信号线也应该配置成推挽复用模式。CubeMX 自动生成时这些细节都已经处理好了。但如果你发现自己手动改过 GPIO 初始化代码一定要检查 GPIO Speed 和 Pull Up/Down 设置。速度设置太慢比如 Low在 50MHz 时钟下会导致信号边沿严重劣化让数据出现偶发性错误这种问题最难查。3.2 用示波器抓关键信号判断收发通道如果说 PHY 寄存器和 Link 状态是软件能看到的现象那么信号完整性就是物理世界的事实。当代码配置看着没问题但 ping 不通的时候示波器是下一步最有效的工具。先抓 REF_CLK。探头点到 PA1 或 PHY 的 REF_CLK 引脚确认是干净的 50MHz 方波。这里有一个典型现象如果时钟有毛刺或者频率不准PHY 可能处于一种半工作状态Link 灯正常但数据传输错误率极高。再抓发送通道。PC 端持续 ping示波器点 TX_ENPG11应该能看到明显的脉冲。再点 TXD0/TXD1PG13/PG14能看到数据跳变。如果 TX_EN 始终为低说明 MCU 这边根本没有把数据交给 PHY问题在 MAC 或 DMA。如果 TX_EN 有脉冲但 TXD 没有同步数据那大概率是描述符链表或者 DMA 配置有问题。最后抓接收通道。PC ping 发出来的其实是 ARP 请求这个帧会从 PC 网卡进入开发板的 RXD0/RXD1PC4/PC5同时 CRS_DVPA7会拉高。如果 PC 已经发出了数据但板子这边 RXD 没有任何波形说明物理接收链路有问题。拔掉网线量下 RXD 焊点是否虚焊或者看看串联电阻/共模电感是否损坏。3.3 DCache和DMA地址问题H7独有的隐蔽坑STM32H7 和 F4 系列最大的差异之一就是带 D-Cache而 D-Cache 和以太网 DMA 之间如果不处理好会出现一个非常诡异的现象PHY 寄存器正常、Link 正常、能收到数据但发不出去或者发送的报文全是错误数据。原因解释起来也不复杂。H7 的 ETH DMA 直接通过 AXI 总线访问 RAM而 CPU 读写同一块 RAM 时会经过 D-Cache。如果这块 RAM 被配置成 cacheableDMA 写入的数据可能还留在 cache 里没有被刷新到内存MAC 发出去的其实是旧数据反过来DMA 接收的数据已经写进了内存但 CPU 读取时命中 cache 里的旧值收到的包内容就是乱的。解决方法是把 ETH DMA 描述符和缓冲区所在的 RAM 区域通过 MPU 配置成非缓存Non-cacheable或者 Device 内存。CubeMX 生成的带 lwIP 的工程一般会默认做掉这一步但如果你是从一个裸机模板开始自己加网络很容易漏掉 MPU 配置。检查方法工程里搜索MPU_Config看看是否覆盖了以太网缓冲区地址。如果确认漏了在 MPU 初始化里增加对应区域配置再把 lwIP 的缓冲区内存分配到这段 non-cacheable 区域。4. lwIP协议栈与PC端从MAC地址到ARP缓存的最后一公里4.1 CubeMX生成的lwIP配置DHCP还是静态地址硬件层的工作做完之后就要看协议栈这一侧了。CubeMX 生成 lwIP 工程时会让你选择 DHCP 还是静态 IP。这里有个很直观的经验调试初期我建议直接用静态 IP放弃 DHCP。原因很简单DHCP 依赖网络里存在 DHCP 服务器。你拿开发板直连电脑时电脑默认不会开 DHCP 服务开发板的 DHCP 请求会一直得不到回应表现就是永远拿不到 IPping 一个未知地址当然全超时。如果用静态 IP两边各设一个地址比如板子 192.168.1.10PC 设 192.168.1.2子网掩码都是 255.255.255.0不配网关问题范围能缩小一大截。在 CubeMX 的 lwIP 配置页面里选择 Disable DHCP然后把 IP 地址、掩码、网关填好。生成的代码会在lwip.c的MX_LWIP_Init()里体现出来你可以确认IP_ADDRESS0、NETMASK_ADDRESS0这些宏是不是你预期的值。4.2 netif_set_up和MAC地址那些容易被忽略的前提很多人烧完例程就直接 ping但忽略了协议栈网络接口是否已经处于 up 状态。CubeMX 生成的代码通常会调用netif_set_up但有些版本或者手动裁剪过的工程里link 状态回调没有正确触发导致netif一直处于 down 状态。这时候 ETH 寄存器看起来全正常但 lwIP 根本不会处理收上来的报文。检查代码中是否调用了netif_set_up(gnetif)和netif_set_link_up(gnetif)。尤其要注意 Link 中断回调在HAL_ETH_LinkCallback或者 ethernetif.c 的ethernetif_set_link中状态变化时要同步调用netif_set_link_up/down。你可以在主循环里直接调一次netif_set_link_up绕过回调先验证问题是否出在这里。另一个高频坑是 MAC 地址。lwIP 初始化时需要给 netif 赋值 MAC 地址如果你忘了设置或者 MAC 地址是全零那么 ARP 请求发出去之后PC 端收到的响应源 MAC 是 00:00:00:00:00:00这种帧基本会被网卡直接丢弃。CubeMX 生成的默认代码里会有一个MAC_ADDR0到MAC_ADDR5的宏默认值虽然能用但所有开发板都一样如果同一个局域网里有多块板子会地址冲突。更稳妥的做法是基于 MCU 的 UID 生成一个唯一 MAC这在产品化的代码里几乎是必须的。4.3 PC端ARP缓存、防火墙与Wireshark抓包验证板子这一侧暂时查不到问题别忘了 PC 也可能捣乱。第一步清 ARP 缓存。以前这块开发板可能被分配过旧的 IPPC 的 ARP 缓存里留着旧 MAC 地址。运行arp -d清空再ping 192.168.1.10看会不会有新的 ARP 响应。第二步检查防火墙。Windows 防火墙默认状态会拦截外来的 ICMP 回显请求开发板回的包到不了 ping 程序。你可以临时把防火墙关掉试一下如果通了再去入站规则里放行文件和打印机共享 (回显请求 - ICMPv4-In)。第三步用 Wireshark 抓包这是所有手段里最能说明问题的。PC 端抓包观察下列现象抓包现象问题定位没有发出任何 ARP 请求PC 端 IP 配置不对或者 ping 的不是同一网段有 ARP 请求但无 ARP 应答开发板 MAC/协议栈问题有 ARP 应答但 ping request 无 replyICMP 处理或路由问题有 ping request 没有 reply且板子端没有收到帧开发板接收通道或 DMA 问题如果 ARP 能通但 ICMP 不通说明 MAC 层和 IP 层已经正常工作问题收缩到 lwIP 的 ICMP 处理和 netif 状态上。如果 ARP 都不通那还是要回到 PHY 和 DMA 去查。5. 完整复盘一次从PHY地址配置错误到ping通的排查实录5.1 现场经过寄存器能读、Link灯亮但数据就是不通把我自己实际遇到的一次情况完整讲一遍。当时是用一块自研板H750 主控加上 LAN8742A硬件基本照抄官方 DK 板烧的是官方例程改的工程。上电后 Link 灯常亮PHY ID 读出来也完全正常。按前面的判断物理层应该没问题但 ping 的时候 PC 端始终是超时。我当时的排查链路是先抓 REF_CLK有干净的 50MHz 方波。再抓 TX_EN发现 ping 的过程中没有任何脉冲。这说明 MCU 根本没有往 PHY 发数据问题出在 MAC 或者 DMA 或者协议栈一侧。于是回头看 lwIP 初始化流程检查MX_LWIP_Init()的返回值结果是ERR_IF。可是整个初始化过程没有报任何明显错误只是 netif_add 返回失败。继续往底层查最后在 HAL 库里看到HAL_ETH_Init()里有一个读取 PHY ID 并检查匹配的步骤。它通过 MDIO 读取 PHY 寄存器但如果 PHY 地址跟你传给 HAL 的地址不一致就会返回 HAL_ERROR而 lwIP 的 ethernetif 初始化这时没有认真检查这个错误只是把它当成 NULL 返回导致 netif_add 失败。问题根源找到了CubeMX 里 PHY 地址我填的是 1但板子上 LAN8742A 的 PHYAD 引脚接线生成的实际地址是 0。地址错了HAL 读到的 PHY ID 全是 0xFFFF虽然 Link 灯照样亮、寄存器看起来能读读到的只是总线上没有设备时返回的 0xFFFF但上层初始化链条已经断掉。把 PHY 地址改成 0重新烧录ping 立刻通了。这个案例最有价值的点在于Link 灯和 MDIO 读操作本身并不保证 PHY 地址正确。你读到的 0xFFFF 如果不仔细看还以为是芯片有响应。一定要核对读到的 PHY ID 是否符合预期而不是只关心有没有返回值。5.2 把这次排查整理成可复用的快速检查清单经过这几次折腾之后我把 H750 以太网排查流程整理成了一张固定清单每次拿到不工作的板卡都按这个顺序走网线插好看 PHY Link 灯是否亮。调试器读 PHY 寄存器确认 PHY ID 正常确认 Link 状态位为 1。示波器量 REF_CLK确认 50MHz 方波存在且频率准确。确认 CubeMX 里 PHY 地址与板卡实际 PHYAD 引脚一致。检查 RMII 引脚复用表格确认每一根信号线都连到了正确的引脚和 AF11。确认 ETH DMA 描述符内存区域在 MPU 里被配置为 non-cacheable。检查 lwIP netif 是否 upMAC 地址是否有效。PC 端清 ARP 缓存临时关闭防火墙测试。Wireshark 抓包确认 ARP 请求是否有去有回。这张清单的顺序不是随便排的每走一步都能缩小一半的排查范围。前 6 步确认芯片级问题第 7 步确认协议栈级问题最后两步排除PC 侧误判。5.3 写在最后的一点经验调试网络问题和一个普通外设的最大区别在于网络是一个闭环系统。MCU 发出数据要经过 MAC、PHY、网线、PC 网卡、驱动、协议栈才能到达 ping 程序PC 的回复又要原路反向走一遍。任何一环断了表面现象都是同一个。面对这种问题我个人的习惯是永远先看底层物理事实再逐步往上推断而不是盯着代码一遍遍 review。寄存器、示波器、抓包工具这三样东西能解决 90% 的ping 不通。还有一个小经验学会在工程里保留一个调试用的 PHY 寄存器读取函数做成命令行或者串口打印一上电就输出 BCR、BSR、PHY ID。这样每次拿到新板子我不用重新接调试器串口打开一眼就能看出 PHY 有没有活过来。这个习惯帮我省下了大量排障时间也推荐你试试。