
很多人第一次在STM32F407上外接LAN8720都会在同一个地方栽跟头板子画好了工程建好了程序烧进去了网线也插上了电脑右下角就是倔强地显示“未识别的网络”或是干脆“网络电缆被拔出”。如果运气好一点能在路由器后台看到一个设备反复上下线但就是死活ping不通。我当年调试这个组合时前后折腾了整整两个晚上最后发现问题不在软件而在RMII的参考时钟配置。后来帮同事排查过几次同样的问题发现大家踩的坑高度重合。这篇文章就把我自己的调试过程和踩坑记录整理出来从CubeMX图形化配置到底层HAL库代码修改再到示波器实测波形、寄存器回环测试一步步说清楚。不管是刚接触STM32以太网的新手还是被这个组合折磨过但没找到方向的工程师照着这篇文章顺一遍基本能解决九成以上的“网口不通”问题。1. 项目背景F407LAN8720这个组合难点到底在哪1.1 为什么都选LAN8720而不是PHY芯片STM32F407自带以太网MAC控制器这个MAC可以工作在MII模式也可以工作在RMII模式但它本身不是一个完整的以太网物理层芯片必须外接一颗PHY芯片才能把数字信号变成网线上跑的差分模拟信号。PHY芯片的选择很多常见的有LAN8720A、DP83848、KSZ8081这些而LAN8720A几乎是做小体积嵌入式设备时最常见的搭配。LAN8720A是Microchip原SMSC推出的一款低功耗10/100M以太网物理层芯片封装小、外围器件少、价格便宜而且支持RMII模式用到的MAC引脚数量比MII少很多。MII模式需要16根数据和控制线RMII模式只需要7根这对PCB布局来说简直是解脱。同时RMII的工作频率从MII的25MHz降到了50MHz虽然频率高了但引脚少、布线简单整体设计起来反而更容易。但凡事都有两面。RMII模式节省引脚的关键在于它把TX和RX数据线从8位收窄到2位通过提高时钟频率来补偿带宽。这个设计带来一个硬性要求MAC和PHY必须共享同一个50MHz参考时钟而且这个时钟的质量直接决定链路能不能建立。很多网口不通的问题追根溯源都出在这颗50MHz时钟上。1.2 网口不通的故障类型和排查思路从我自己的经验来看F407LAN8720的“网口不通”通常分成三种情况第一插上网线后电脑完全没有反应设备管理器和路由器后台都看不到设备。这种情况多半是PHY芯片没有正常工作优先检查电源、晶振、复位、PHY地址这几个硬件相关项。第二电脑能识别到链路但反复“正在识别”始终获取不到IP地址。这种情况硬件链路大概率是通的问题出在软件初始化、MAC地址或者LWIP协议栈配置上。第三能获取到IP地址、能ping通局域网但偶尔掉线、大量丢包。这种情况多半是时钟质量不好、PCB布线干扰、或者中断处理不及时导致的。下面几个章节我就按照“硬件检查 → CubeMX配置 → 代码修改 → 实测调试”的顺序把每一步做什么、为什么这么做、常见坑在哪全部摊开来讲。2. 动手之前先把原理图和硬件检查一遍2.1 LAN8720的PHY地址是怎么决定的很多人在CubeMX里配置ETH外设时都会看到一个“PHY Address”选项默认值是0。如果这个值和实际硬件不匹配MDIO总线读写PHY寄存器会全部超时这时候不管软件怎么写都是白搭。LAN8720A的PHY地址由芯片的RXER/PHYAD0引脚决定。这个引脚在RMII模式下不承担接收错误指示功能RMII模式没有RX_ER引脚而是被复用为PHY地址位。把它拉低PHY地址就是0x00把它拉高PHY地址就是0x01。市面上大部分LAN8720模块和开发板比如正点原子、野火的各种板子几乎都把RXER引脚通过电阻拉低所以PHY地址是0x00。但这并不是绝对的有个别模块会把RXER拉高把地址设置成0x01。我建议拿到模块后先看原理图或者用万用表量一下RXER引脚的电平。如果模块没有原理图最简单的方式就是写一段读PHY寄存器的代码把地址0和1都试一遍看哪个能读到非0xFFFF的芯片ID。LAN8720A的芯片ID在寄存器2和寄存器3里正常读出来是0x0007C0F1。2.2 50MHz REF_CLK到底由谁来提供这是整个项目里最核心的配置点没有之一。RMII接口要求MAC和PHY都使用50MHz的参考时钟。问题在于STM32F407内部并不产生这个50MHz信号。有的朋友可能会想F407的MCO引脚能输出时钟比如PA8可以输出PLL分频后的时钟那是不是可以把PA8配置成50MHz输出接到LAN8720的REF_CLK理论上可以但实际上非常难做因为F407的MCO输出频率是由系统PLL分频得到的而PLL是根据系统主频设计的。要让MCO精确输出50MHz需要把PLL配置成某个能被50MHz整除的频率这往往意味着要牺牲系统主频或者使用非常规的时钟树方案并不推荐。最靠谱的做法也是绝大多数官方评估板和第三方模块采用的做法在LAN8720的XI和XO引脚之间接一颗25MHz的无源晶振LAN8720内部通过PLL把25MHz倍频到50MHz然后从CLKOUT引脚输出50MHz信号接到STM32F407的PA1ETH_RMII_REF_CLK。这样MAC和PHY共用同一个时钟源相位一致时序完全匹配。实际操作中要注意两个细节第一LAN8720的CLKOUT引脚默认是输出50MHz的但也有的模块把这个引脚引出来接了一个LED指示或者悬空。拿到模块后要确认一下原理图确保CLKOUT确实接到了F407的PA1。第二25MHz晶振的两个负载电容一般取18pF到22pF之间具体值要看晶振的规格书。如果电容配得明显不对晶振可能起振困难或者输出波形幅度不够这会导致RMII时钟不稳定网口能识别但丢包严重。2.3 除了时钟硬件还有哪些隐蔽的坑硬件方面我踩过比较典型的坑有三个。第一个是复位引脚。LAN8720的NRST是低电平复位有的模块把这根引脚直接接了一个上拉电阻靠内部的电源上电复位电路自动复位有的模块则把NRST引出来让MCU控制。如果让MCU控制上电顺序务必注意MCU初始化完成后要把NRST拉低至少1毫秒再拉高然后延时200毫秒左右等PHY内部稳定后再去访问MDIO总线。如果刚上电就去读PHY寄存器大概率读到的全是0xFFFF。第二个是网络变压器的中心抽头。LAN8720的发送和接收差分对需要通过网络变压器连接到RJ45。变压器中心抽头的接法直接决定信号质量。很多小模块把中心抽头直接接地这也没问题但如果设计自己的底板要按照PHY芯片和变压器厂家手册的要求来接有的需要接电源有的需要接地接反了会出现信号幅度不足的现象。第三个是MDIO的上拉电阻。MDIO是双向开漏信号必须在外部接一个上拉电阻阻值一般取2.2kΩ到10kΩ。有些模块内部已经自带上拉了有些则需要自己加。如果MDIO线上没有上拉PHY寄存器读取会非常不稳定时好时坏表现为偶尔能ping通复位之后就又不行了。3. CubeMX配置图形化界面里的关键设置3.1 时钟树配置别让网络时钟偷工减料打开STM32CubeMX新建F407系列芯片的工程首先进入“Clock Configuration”页面。F407系统主频最高168MHz。我一般用外部高速晶振HSE设为25MHz正点原子探索者板上的晶振就是25MHz如果是别的板子请按实际晶振频率修改。配置好系统时钟后我要特意强调一句网络外设的时钟并不直接等于系统时钟。在STM32F4系列里以太网MAC的时钟来自AHB1总线而RMII的REF_CLK是外部输入的。所以时钟树页面其实不需要专门为ETH添加什么特殊配置系统时钟正常即可。关键点在于如果你用的不是LAN8720自带的25MHz晶振和CLKOUT方案而是想用MCO输出50MHz时钟那么就必须在时钟树里仔细调整PLL参数。如果你跟我一样使用LAN8720的CLKOUT输出50MHz给F407那么时钟树这部分就只需要保证系统时钟正常没有额外负担。3.2 使能ETH外设并配置RMII引脚在“Pinout Configuration”页面左侧找到“Connectivity” → “ETH”勾选激活以太网外设。在右侧的“Mode”中选择“RMII”模式。这时候你会看到芯片封装图上自动分配好了一组引脚PA1REF_CLK、PA2MDC、PA7CRS_DV、PB11TX_EN、PB12TXD0、PB13TXD1、PC1MDIO、PC4RXD0、PC5RXD1。如果发现某些引脚被其他外设占用比如PC1被用作了ADC通道那就需要手动解除冲突。RMII这组引脚基本是固定的F407的以太网外设只有这一组复用功能可选不能更改。这是硬件设计阶段就要决定的如果PCB已经画好但引脚对不上那就只能重新画板了。我建议在CubeMX里把这些引脚名字在芯片视图上确认一遍然后看一下是否有引脚没有自动分配。有时候因为芯片封装图显示问题个别引脚需要手动选中然后选择复用功能“ETH_RMII...”。但正常情况下勾选ETH的RMII模式后CubeMX会自动完成引脚分配不用手动干预。3.3 参数配置PHY地址和MAC地址别填错在ETH外设的参数设置里有一个“Parameter Settings”选项卡里面比较重要的是PHY Address填0。前提是硬件上RXER/PHYAD0引脚拉低如果硬件拉高则填1。MAC Address这里默认生成一个基于芯片唯一ID的MAC地址可以直接用。但要注意MAC地址的第一字节最低两位有特殊含义bit0是单播/组播标志必须为0bit1是全局/本地标志建议为1本地管理。CubeMX生成的MAC地址通常已经处理好了不用太担心。在“Advanced”里ETH的DMA参数一般保持默认即可。主要关注的是接收和发送描述符的数量CubeMX默认各4个如果之后要跑高流量应用可以适当增大到8或16。另外如果你打算直接用LWIP协议栈那么在左侧的“Middleware and Software Packs”中找到“LWIP”勾选启用。这里有几个关键设置IP地址默认是192.168.1.10子网掩码255.255.255.0网关192.168.1.1。如果想通过DHCP自动获取IP把“IP Address”设置成“DHCP”即可。内存设置里的“MEM_SIZE”、“PBUF_SIZE”这些参数用默认值就行大多数场景不用动。3.4 中断配置收包靠它了ETH外设的全局中断“ETH”要勾选使能注意它位于NVIC设置列表中名字就叫“ETH”或者“ETH global interrupt”。如果不使能这个中断LWIP收包只能靠轮询CPU占用高且容易丢包。使能后中断优先级建议设一个较低的数值比如5避免和SysTick、定时器等实时性要求高的中断冲突。还有一个容易忽略的中断是“ETH_WKUP”即唤醒中断这个在普通网络应用中用不到不需要使能。CubeMX还有一个贴心的功能它可以自动生成LWIP的初始化代码和ethernetif.c底层接口文件。但别高兴太早生成的代码通常在转发收包中断回调这里是空的需要你在用户代码区自己补充这个会在下一章详细说。3.5 代码生成配置选对工具链在“Project Manager”里根据你自己使用的工具链选择IDE比如MDK-ARM、STM32CubeIDE或者Makefile。CubeMX会生成对应的工程文件。如果之后习惯用VSCode配合Makefile开发也可以选择MakefileCubeMX生成的Makefile结构清晰稍作整理就能在VSCode中编译调试。不过作为新手我还是建议先用STM32CubeIDE或者Keil把流程跑通再折腾工具链的配置。4. HAL库代码层CubeMX生成的代码还缺什么4.1 上电后的PHY复位延时CubeMX生成的main.c中已经调用了MX_LWIP_Init()和MX_ETH_Init()但这两个初始化是否一定成功取决于PHY是否已经稳定工作。如果你用的是模块方案PHY的复位引脚可能直接悬空或者接了上拉靠内部上电复位那么硬件上电到PHY稳定的时间通常比较长。代码里在MX_ETH_Init()和MX_LWIP_Init()之前最好加一段延时。我习惯在main函数里外设时钟使能完成后立刻加一个500毫秒的延时然后再调用MX_ETH_Init()和MX_LWIP_Init()。延时可以用HAL_Delay(500)。如果PHY的复位引脚是由GPIO控制的那么在此之前还要先执行拉低、延时、拉高的操作之后再做这个500毫秒延时。4.2 验证PHY寄存器是否可读CubeMX生成的ethernetif.c中有一个low_level_init()函数里面会调用HAL_ETH_ReadPHYRegister()读取PHY的ID以验证MDIO通信是否正常。但生成的代码默认是针对STM32官方评估板上的PHY芯片LAN8742A的它的PHY地址默认是0。如果你用的是LAN8720且硬件上PHY地址是0那这段代码大概率是能跑通的。但如果你的PHY地址是1就必须改成1。否则PHY ID读出来全部是0xFFFFETH初始化会超时LWIP无法启动。有些新手不知道如何确认PHY是否被正确访问我提供一个简单的方法在main函数中加入一段手动读取PHY寄存器的代码。用HAL_ETH_ReadPHYRegister(heth, 0, 0x02)和读寄存器0x03打印出两个16位的值如果拼起来是0x0007C0F1说明MDIO通信正常。这段代码在调试期间很有用确认无误后可以删掉。4.3 收包中断回调的补充这是“能发不能收”的经典原因。CubeMX生成的代码中ETH的接收中断回调函数HAL_ETH_RxCpltCallback()默认是一个弱定义的空函数。很多人的程序里网线插上后上电时请求DHCP会有反应但之后就再也没有数据了或者只能发给别人数据但收不到别人的数据问题就在这个回调。你需要在自己的用户代码中实现这个回调并把收到的数据包交给LWIP协议栈处理。网络接口的netif结构体在ethernetif.c中已经定义好了通过extern声明即可引用。典型写法是void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth) { struct pbuf *p; // 通知LWIP底层有数据到达 while (HAL_ETH_GetReceivedFrame_IT(heth) HAL_OK) { p low_level_input(heth); if (p ! NULL) { if (heth-Init.RxMode ETH_RXINTERRUPT_MODE) { ethernetif_input(gnetif, p); } else { pbuf_free(p); } } HAL_ETH_Start_IT(heth); } }这段代码在不同CubeMX版本里略有差异但核心逻辑就是把收到的帧从DMA描述符里取出封装成LWIP的pbuf然后交给ethernetif_input()处理最后重新使能接收中断。如果漏掉这个回调即使底层接收到数据包协议栈也不知道有数据进来表现出来就是网络断断续续甚至完全不通。4.4 发送超时的处理另一个常见问题是发送流程卡死。HAL库的HAL_ETH_Transmit_IT()或HAL_ETH_Transmit()在某些异常情况下会一直返回HAL_BUSY然后LWIP线程就卡住了。CubeMX生成的low_level_output()函数中已经有相应的错误处理但如果你的代码是自己写的一定要注意在发送失败时释放pbuf并检查返回错误码不能无限等待。有一种情况让我印象很深网线拔插几十次之后传输突然中断再也无法恢复。追查发现是发送描述符在异常情况下没有正确释放导致DMA一直认为描述符被占用。最终的解决办法是在low_level_output()里增加超时判断如果连续几次发送超时就调用HAL_ETH_Stop()再HAL_ETH_Start()重新初始化DMA。虽然粗暴但在实际产品中很有效。5. 实测与调试网口不通的排查手法5.1 最简单的链路测试看PHY中断和LED在没有示波器和逻辑分析仪的情况下也有一招能粗略判断PHY的状态。LAN8720模块上通常有两个LED指示灯一个标记“Link/Act”一个标记“Speed”。插上网线后如果Link/Act灯亮了说明PHY已经和交换机/电脑建立起了物理链路这个阶段PHY的模拟前端、网络变压器、RJ45基本没问题。如果Link/Act灯不亮重点检查PHY电源、时钟、变压器和RJ45。需要注意的是LAN8720默认输出的是10M速度指示如果网络是100MSpeed灯应该亮。如果Speed灯不亮而Link灯亮说明PHY可能工作在了错误的速率模式或者线缆质量差导致协商失败。5.2 用示波器实测50MHz时钟调试以太网示波器是必须的。把示波器探头接到LAN8720的CLKOUT引脚在模块上通常标注REF_CLK或CLKOUT或者接到F407的PA1引脚应该能看到稳定的50MHz方波。这个信号的幅度应当在3.3V左右上升沿应当干净不能有过大的振铃。如果看不到50MHz信号问题多半出在晶振电路。用示波器看25MHz晶振的XI引脚确认是否起振。如果晶振不起振检查负载电容是否合适、晶振是否虚焊、芯片供电是否正常。如果晶振起振但CLKOUT没有输出可能是芯片内部PLL没锁定此时需要排查供电和复位。还有一个值得注意的细节REF_CLK信号的相位抖动会影响通信稳定性。如果你的示波器带有时钟抖动分析功能可以看下峰峰值抖动。如果抖动过大说明时钟源质量不好很多时候是LAN8720的供电纹波太大或者PCB布线时REF_CLK走线太长太细。解决方法是LAN8720的供电引脚就近放一个0.1μF陶瓷电容加上一个10μF钽电容并且REF_CLK走线越短越好尽量在地平面完整的地方走。5.3 MDIO寄存器回环测试自证清白当网口一直不通而又无法确定是MAC问题还是PHY问题的时候用PHY的回环模式可以快速分割故障范围。通过MDIO总线向PHY的寄存器0写入0x4000使能数字回环。此时PHY会把发送的数据直接回环到接收路径不经过网络变压器和外网。如果在回环模式下LWIP能自己ping通自己说明MAC→MDIO→PHY寄存器→PHY收发通道基本是完好的。再关掉回环问题就锁定在外部链路网络变压器、RJ45、网线、对端设备。这个操作在应用里不常用但调试阶段价值极高。我可以提供一段参考代码uint16_t reg_val 0; HAL_ETH_ReadPHYRegister(heth, PHY_ADDR, 0, reg_val); reg_val | 0x4000; // 置位回环 HAL_ETH_WritePHYRegister(heth, PHY_ADDR, 0, reg_val);设置回环之后如果LWIP的DHCP获取到了IP地址通常是自己分配的链路本地地址169.254.x.x或者能ping通自己设置的IP那就说明整个DMA、描述符、MAC核心、MDIO、PHY数字部分都工作正常。5.4 用Wireshark抓包定位协议栈问题有时候物理链路没问题PHY也正常但始终获取不到IP地址此时就需要抓包分析了。在PC上打开Wireshark选择连接开发板的那个网卡接口然后给开发板上电。正常情况下如果开启了DHCP能看到开发板发出的DHCP Discover广播报文。如果看不到任何报文说明LWIP协议栈没有工作重点检查ethernetif_input是否被正确调用、收包中断是否触发。如果能看到Discover但没有Offer响应说明DHCP服务器没收到或者没回复这时候排查交换机端口、网线以及路由器的DHCP配置。如果不想抓包这么麻烦也可以在调试串口上打开LWIP自带的debug打印把LWIP_DEBUG打开。CubeMX生成的LWIP默认关闭了debug输出你可以在lwipopts.h里打开LWIP_DEBUG并设置LWIP_DHCP为1。调试信息会直接打印到串口虽然信息比较庞大但对定位问题非常有帮助。5.5 用静态IP绕开DHCP的排查捷径如果你的网络环境没有DHCP服务器或者路由器配置有问题那DHCP获取不到IP并不代表网络不通。为了验证基本通信能力我建议先把LWIP设置成静态IP比如192.168.1.10子网掩码255.255.255.0然后把电脑的网卡手动设置为192.168.1.100子网掩码255.255.255.0用一根网线直连开发板和电脑。如果能ping通192.168.1.10那恭喜你网络链路和协议栈都通了剩下的问题就是网络环境和DHCP配置。如果静态IP都ping不通再回头检查前面的硬件和初始化步骤。5.6 中断风暴和CPU占用还有一个比较隐蔽的问题如果ETH中断处理函数写得不合理比如在中断回调里做了大量耗时的操作或者没有正确清中断标志会导致中断持续触发看起来像是系统卡死。排查方法是在中断回调函数入口设置一个GPIO翻转用示波器看这个GPIO的翻转频率。如果翻转频率达到了MHz级别说明中断风暴。此时需要优化回调逻辑尽量缩短中断处理时间把耗时的协议栈处理放到主循环或低优先级线程中。6. 常见问题速查表与实操心得整理一下我在项目实战中遇到频率最高的问题和对应的解决办法做成一个速查表方便大家在调试时快速定位。现象可能原因解决方案插网线完全无反应Link灯不亮LAN8720供电异常、晶振不起振、RJ45虚焊检查供电引脚电压用示波器查看晶振波形重新焊接RJ45Link灯亮但ping不通RMII时钟未接或质量差、PHY地址不对、复位时序不对示波器确认PA1处50MHz时钟确认RXER引脚电平检查复位延时能获取IP但ping丢包严重REF_CLK抖动过大、PCB布线干扰、网线质量差检查电源纹波缩短REF_CLK走线更换网线测试只能发不能收ETH中断未使能、HAL_ETH_RxCpltCallback未实现使能ETH全局中断在回调里调用ethernetif_inputMDIO读写全部超时PHY地址不对、MDIO无上拉、PHY没有正常上电确认PHY地址检查MDIO上拉电阻检查NRST复位状态PHY ID读出来是0x0000PHY没有稳定复位上电后访问太早延长复位后的延时至少等200ms以上再访问MDIOping通但DHCP失败DHCP服务器未开启、LWIP配置错误先手动设置静态IP验证通路再排查DHCP配置根据我个人的使用经验调试STM32F407LAN8720这个组合最忌讳的就是“一上来就写业务代码”。先把PHY读通、把链路调通哪怕只是点个灯也算是在正确的道路上迈出了一大步。CubeMX虽然能省去很多代码工作但它毕竟只是工具它生成的是“常规情况”的代码不是“你的板子”的代码。理解RMII时钟来源、PHY地址、复位时序这几个底层问题远远比会点鼠标配置界面重要得多。最后再分享一个小技巧在调试阶段把LWIP的DHCP关掉用固定IP直连电脑能极大减少变量。等确认物理链路和基本IP通信没毛病之后再开DHCP去接入真实网络。这是一个看起来很基础、但真的能让调试效率翻倍的经验。