做了这么多年嵌入式最怕的不是算法写不对而是网口这种“看起来简单、一调就炸”的外设。尤其是STM32H743搭配LAN8720再加一层LWIP协议栈CubeMX生成的工程下载进去插上网线PC端Ping包发过去却永远等不来回包。我早期在这个组合上栽过好几次后来把H7的总线结构、DMA内存布局、PHY芯片驱动挨个捋了一遍才算彻底解决。这篇文章就把整个配置过程和关键代码修改直接摊开讲按这个步骤走基本能让你一次点亮。这篇内容适合正在用H7系列H743/H750/H723都适用做以太网通信的人也适合从F4平台迁移到H7后发现“同一套代码居然跑不通”的人。我会从CubeMX配置开始讲到必须手改的几处代码最后再整理一份Ping不通的排查清单全是实际操作经验没有空话。1. 为什么H743LAN8720这么容易踩坑1.1 这套方案到底难在哪先说结论LAN8720本身没问题LWIP协议栈也没问题问题大多出在STM32H7这颗芯片的架构和CubeMX生成的默认代码上。第一H7的内存架构和F4完全不一样。H7内部有DTCM、ITCM、AXI SRAM、SRAM1/2/3/4等多个RAM区域不同总线主设备能访问的RAM区域是有限制的。以太网MAC的DMA在D2域默认情况下访问不到DTCM那一段而CubeMX生成的LWIP缓冲数组如果被链接到不合适的RAM区域DMA根本读不到数据表现就是丢包严重或者完全不通。第二RMII接口对参考时钟要求很敏感。LAN8720是通过RMII接口和MCU连接的一对差分线变成了8根数字信号线时钟频率50MHz。这个时钟的提供方、方向、频率必须在硬件设计和CubeMX配置里保持一致。第三CubeMX生成的PHY驱动是针对ST自家评估板上的LAN8742优化的LAN8720虽然和它高度兼容但PHY ID不一样部分寄存器的细节行为也有差异。很多工程生成出来的代码带的是LAN8742的驱动文件直接拿去驱动LAN8720运气好能跑运气不好就卡在PHY初始化上。第四LWIP是一个独立的协议栈CubeMX只是帮你生成了初始化代码和网卡驱动模板很多关键参数还藏在ethernetif.c和lwipopts.h里不手动改的话默认配置并不一定适配你的硬件和RAM布局。1.2 动手之前先把硬件关系摸清楚LAN8720是一颗10/100M以太网PHY芯片物理层收发信号就靠它MAC层则由STM32H743内置的以太网控制器承担。两者之间走RMII接口只需要以下信号线TX_EN、TXD0、TXD1发送数据RXD0、RXD1、CRS_DV接收数据REF_CLK50MHz参考时钟MDIO、MDC管理接口用于读写PHY寄存器另外一般还会接RESET复位脚以及可选的INT中断脚。接线时特别注意REF_CLK的方向你的LAN8720模块如果是自己带25MHz晶振那就由PHY芯片内部倍频后输出50MHz给STM32的ETH_REF_CLK引脚如果你的设计是外部有源晶振提供50MHz时钟那就要接在LAN8720的REF_CLK输入上同时STM32那边也要配置成外部时钟输入模式。这里有一个非常常见的坑不同厂家做的LAN8720迷你模块默认工作模式不一样。有的模块把PHY的地址引脚拉低PHY地址就是0有的模块通过拨码或电阻配置成其他地址。如果你不知道模组的PHY地址配置成CubeMX默认的0也没问题但最好看一下原理图确认。另外LAN8720的RESET引脚如果悬空芯片上电后不一定能正常工作建议接一个RC复位电路或者由MCU的GPIO控制复位时序。STM32H743配对LAN8720是很多开发板的默认组合硬件设计本身不复杂但正因为“看起来简单”很多人忽略了数据手册里关于RMII时钟和PHY地址的细节反而在最基础的地方卡了很久。2. CubeMX配置一步都不能省2.1 RCC、SYS与时钟树配置打开CubeMX第一步先把芯片选好我这里以STM32H743VIT6为例。在System Core里RCC选择外部高速时钟HSE我这里用的是25MHz无源晶振这也是多数H743开发板的标配。SYS里面Debug选项建议选Serial Wire否则后面想用ST-Link调试会发现Debug口被占用了。Timebase Source建议从SysTick换成TIM6或者TIM7因为后面如果上FreeRTOSSysTick会被RTOS占用现在先改掉省得以后踩坑。时钟树部分H743最高可以跑480MHz。你可以在Clock Configuration界面里直接输入想要的频率CubeMX会自动推导分频系数。这里只需留意以太网部分ETH的时钟源来自系统时钟经过分配后的RCC时钟只要CubeMX把ETH的时钟链路标成有效即可不用手动去改分频因为RMII的50MHz参考时钟来自PHY芯片或者外部时钟源不直接由MCU系统时钟决定。2.2 ETH外设与RMII参数配置在Connectivity目录下找到ETHMode选择RMII。如果你用的是带部分连接的开发板CubeMX会自动把引脚分配到对应的PA、PB、PC引脚上这时候检查一下引脚分配表确保REF_CLK、MDIO、MDC、CRS_DV、RXD0、RXD1、TXD0、TXD1都在。Advanced parameters里面要注意几个参数PHY Address填0前提是你的LAN8720模块地址确实是0。MAC Address默认会生成一个随机MAC可以直接用但建议改成自己定义的值防止和局域网内其他设备冲突。MDC时钟分频默认值通常没问题但如果PHY寄存器读写不正常可以尝试把分频调大让MDC频率降到2.5MHz以下。这里需要说明CubeMX里ETH的PHY驱动并没有直接针对LAN8720的选项。新版CubeMX在Middleware和组件里会带LAN8742的驱动模板后面需要手动替换或者适配这一步先不急等代码生成后再处理。2.3 LWIP中间件配置在Middleware and Software Packs里勾选LWIP。默认选项带DHCPPing测试更推荐手动指定静态IP不然每次上电都去找路由器拿地址查问题会多一层变量。我习惯的配置是DHCPDisableIP地址192.168.1.10子网掩码255.255.255.0网关192.168.1.1其他参数保持默认。需要注意的是LWIP的内存配置和你的应用密切相关如果后面要跑TCP服务器MEM_SIZE、PBUF_POOL_SIZE、TCP_MSS这些都要看具体场景调整单纯做Ping测试的话默认值完全够用。2.4 生成代码后第一件事生成代码前我建议勾选Generate peripheral initialization as a pair of .c/.h files per peripheral这样每个外设独立文件后续好维护。代码生成后不要急着下载测试先用IDE打开工程检查两个地方。第一检查main.c里是否调用了MX_LWIP_Init()以及在主循环里是否调用了MX_LWIP_Process()。如果LWIP使用的是轮询模式这个函数必须一直在循环里跑否则协议栈根本不处理收包。第二检查stm32h7xx_hal_conf.h里的HAL_ETH_MODULE_ENABLED是否是打开的以及stm32h7xx_hal_eth.c、stm32h7xx_hal_eth.h有没有被正确加入工程。有些精简配置模板会把ETH的HAL驱动漏掉编译能过但一调用就是undefined reference。3. 关键代码修改照着抄就行3.1 把DMA缓冲区放到SRAM3这一步是H743LAN8720能不能Ping通的分水岭。CubeMX默认生成的ethernetif.c里定义了DMA描述符和收发缓冲区__ALIGN_BEGIN ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __ALIGN_END; __ALIGN_BEGIN ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __ALIGN_END; __ALIGN_BEGIN uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUF_SIZE] __ALIGN_END; __ALIGN_BEGIN uint8_t Tx_Buff[ETH_TX_DESC_CNT][ETH_TX_BUF_SIZE] __ALIGN_END;这几个数组具体落在哪块RAM取决于链接脚本和编译器。在某些编译环境里它们会被放在D1域的AXI SRAM区域内。理论上有部分场景能工作但一旦开了D-Cache或者DMA访问仲裁没配置好就会出现“能发出ARP但没有回包”“Ping丢包严重”之类的诡异问题。最稳妥的做法是把这些数组强制放到H7的SRAM3也就是0x30040000那一段地址。以太网DMA在D2域和SRAM3同域访问效率最高也不容易碰到Cache一致性问题。以Keil环境下在分散加载文件里增加一个段为例先在ethernetif.c里把定义改成#if defined ( __CC_ARM ) __ALIGN_BEGIN ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __ALIGN_END __attribute__((section(ETH_RAM))); __ALIGN_BEGIN ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __ALIGN_END __attribute__((section(ETH_RAM))); __ALIGN_BEGIN uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUF_SIZE] __ALIGN_END __attribute__((section(ETH_RAM))); __ALIGN_BEGIN uint8_t Tx_Buff[ETH_TX_DESC_CNT][ETH_TX_BUF_SIZE] __ALIGN_END __attribute__((section(ETH_RAM))); #else __ALIGN_BEGIN ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT] __ALIGN_END; __ALIGN_BEGIN ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT] __ALIGN_END; __ALIGN_BEGIN uint8_t Rx_Buff[ETH_RX_DESC_CNT][ETH_RX_BUF_SIZE] __ALIGN_END; __ALIGN_BEGIN uint8_t Tx_Buff[ETH_TX_DESC_CNT][ETH_TX_BUF_SIZE] __ALIGN_END; #endif然后在Keil的分散加载文件里增加RW_ETH_RAM 0x30040000 0x8000 { *(.ETH_RAM) }IAR环境下可以用location关键字GCC环境下把section名改成你自己定义的段再在链接脚本里映射到0x30040000。这块区域大小是32KB我们的描述符加缓冲区总共也就十几KB空间完全够。如果后续要加大收发缓冲区数量注意不要超过SRAM3的32KB上限超过就需要把一部分放到SRAM1或SRAM2去。3.2 配置MPU避免Cache一致性问题H7的D-Cache是个好东西但也会让网络驱动头痛。DMA把数据写进内存CPU却可能从Cache里读到旧数据这就导致你收到以太网帧了应用层拿到的却是之前的老数据。解决办法有两个一个是做Cache的clean和invalidate操作这需要每个数据包都手动调函数麻烦且容易漏另一个是配置MPU把以太网缓冲区所在的SRAM3区域设置为non-cacheable让CPU访问这段RAM时绕过Cache。CubeMX里在System Core - MPU中可以直接加RegionRegion Number0Base Address0x30040000Size64KB覆盖整个SRAM3TEX000CacheableNot cacheableBufferableNot bufferableShareableNot shareable这样生成的代码会初始化MPU你再在main()里配合SCB_EnableDCache()打开数据Cache以太网缓冲区不受影响其他RAM区域还能享受Cache带来的性能提升。如果不用MPU也可以在每次DMA收发前后手动调用SCB_CleanDCache()和SCB_InvalidateDCache()但这不是长久之计。做过网络通信的人都明白数据包一多手动维护Cache绝对是一场灾难。3.3 PHY驱动与地址对准CubeMX生成的H7工程默认会带一个LAN8742的PHY驱动文件比如lan8742.c。这个驱动通过一批标准的ReadReg/WriteReg函数操作PHY寄存器LAN8720的大部分寄存器和LAN8742是兼容的但PHY ID不同。如果驱动里做了PHY ID校验你需要把ID改成LAN8720对应的值。LAN8720的首个ID寄存器通常读到0x0007第二个ID寄存器读到0xC110具体看你手上模组的具体版本。找到驱动文件里的ID宏#define LAN8742_ID 0x0007C130改成#define LAN8720_ID 0x0007C110如果你不想折腾最省事的方法是直接不校验ID很多驱动在初始化时并不会强制校验ID即便校验失败也只是打印一个警告并不影响后续功能。但既然都要做产品了该严谨还是严谨一点把ID改对再继续。另外还要确认ethernetif.c里传递的PHY地址和实际硬件一致heth.Init.PhyAddress 0;如果你的LAN8720模组地址是1记得改成1否则HAL_ETH_Init里读写PHY寄存器全是超时错误。3.4 确保复位引脚和中断处理正确LAN8720的RESET引脚必须保证上电后能正确释放。很多模组上一共有两个引脚容易被忽略一个是RESET另一个是nINT/REGOFF。RESET引脚在CubeMX配置里可能没关联到任何GPIO但实际硬件通过一个RC延时电路自动复位那就不用管。如果是MCU的GPIO控制记得在HAL_ETH_Init之前先把引脚拉低一小段时间再拉高释放。中断处理方面LWIP可以跑在轮询模式也就是主循环里不断调MX_LWIP_Process()。这种做法能Ping通但CPU占用率并不低。想要高效一点需要开启ETH的全局中断。CubeMX里在NVIC设置中打开ETH全局中断然后在stm32h7xx_it.c里添加void ETH_IRQHandler(void) { HAL_ETH_IRQHandler(heth); }需要注意的是LWIP的协议栈处理仍然是在主循环的MX_LWIP_Process()里完成的中断里只负责把接收事件标记出来真正解包和响应仍在主循环上下文执行。官方这种设计能避免在中断里调用复杂协议栈逻辑也符合LWIP的线程模型。4. Ping不通按下面顺序排查4.1 第一眼看链路层拿到一块H743LAN8720板子先别急着看LWIP先把串口调试信息打开。在main()初始化完ETH后加一段读PHY寄存器的代码uint32_t phyidr1 0, phyidr2 0; uint32_t bsr 0; HAL_ETH_ReadPHYRegister(heth, 2, phyidr1); HAL_ETH_ReadPHYRegister(heth, 3, phyidr2); printf(PHY ID: 0x%04X 0x%04X\r\n, phyidr1, phyidr2); HAL_ETH_ReadPHYRegister(heth, 1, bsr); printf(PHY BSR: 0x%04X\r\n, bsr);寄存器1是PHY的基本状态寄存器bit2是链路状态位。如果读到BSR的bit2为1说明物理链路已经起来了。如果一直读不到正确的ID或者BSR的link位一直是0大概率是PHY地址配置不对、MDC/MDIO引脚接错、或者PHY复位没释放。看网口指示灯也是一种快速判断方式。LAN8720模组上一般有两个LED一个指示Link状态一个指示Activity。上电插网线后Link灯必须亮如果不亮硬件链路就没通软件层面再怎么折腾都白搭。4.2 再看IP层和PC端链路层通了PC端还是Ping不通接下来看网络层。先把PC的IP地址手动改成和板子同一个网段比如板子是192.168.1.10PC就设192.168.1.2子网掩码255.255.255.0网关可以留空。然后打开命令行用arp -a看ARP缓存表里有没有板子的条目。如果ARP表里根本没出现板子的MAC地址说明PC发出去的ARP请求板子没有回复问题可能出在LWIP接收路径上。如果ARP表里有板子MAC但Ping包一直得不到响应可能是ICMP处理逻辑或者发送路径的问题。Windows防火墙默认会拦截ICMP回显请求如果已经确定板子和PC在同一网段、ARP也能学到就检查一下防火墙设置在高级设置里允许入站的“回显请求”或者直接临时关闭防火墙测试。4.3 常见故障速查表现象常见原因解决思路网口Link灯不亮网线/供电/PHY未复位换网线确认复位引脚释放查看LAN8720供电PHY寄存器读取超时PHY地址不对、MDIO/MDC接线错确认PHY地址是0还是1核对引脚映射能学到MAC但Ping超时D-Cache一致性问题、LWIP缓冲区配置过小把缓冲区放SRAM3并配置MPU适当调大PBUF_POOL_SIZE板子恢复出厂配置后失联DHCP或静态IP未生效确认LWIP初始化成功串口打印IP地址长跑几分钟后断网内存配置或DMA描述符溢出检查LWIP内存池加大描述符数量检查描述符是否越界能Ping通但数据量大时丢包缓冲区放错位置或Cache未处理按要求改SRAM3配置MPU为non-cacheable4.4 我反复踩过的几个细节有一个很容易被忽略的细节是STM32H743的ETH时钟需要确保RMII的50MHz参考时钟干净稳定。有些开发板布局不合理REF_CLK走线太长串扰干扰大表现为PHY初始化时好时坏。这时候可以在REF_CLK引脚上加一个22Ω到33Ω的串联电阻实测能改善不少。还有一个更隐蔽的问题某些LAN8720模组会输出一个50MHz时钟给MCU但也有部分模组设计成需要MCU输出时钟给它。如果你的CubeMX配置里ETH的RMII参考时钟来源选错了PHY根本不会正常工作表现出来就是PHY寄存器读取偶尔成功偶尔失败让人误以为是代码问题。这种情况一定要回到原理图上去确认REF_CLK的流向。5. 一点个人体会现在我再拿到一块新的H743板子配置网络部分的固定流程已经非常固定了先确认LAN8720的PHY地址和REF_CLK方向再把CubeMX生成的LWIP缓冲数组强制放到SRAM3并配好MPU最后用串口打印PHY ID验证物理层然后才跑Ping测试。这套流程帮我省掉了大量排查时间。以前我总觉得一个网口而已STM32固件库都封装好了怎么可能调不通。但实际做过之后才明白H7系列和F4系列的差距不只是主频变高了总线架构、内存分布、Cache一致性这些底层因素才是真正拉开差距的地方。如果你也卡在H743LAN8720网络不通这个问题上建议从这篇文章里的几个修改点逐项核对尤其是SRAM3和MPU那一块大概率能解决你的问题。
