
做嵌入式以太网这些年我最怕听到的一句话就是按教程配完了但PING不通。尤其是STM32H743搭配LAN8720这种组合芯片本身没问题运行频率480MHz又能跑到1MB RAM但网口这个外设的坑位密度远比你想象的高。我最近用CubeMX v6.5.0重新搭了一套LWIPFreeRTOS的模板从裸板到稳定跑通花了两天时间其中至少一天半在处理各种匪夷所思的隐形问题。这篇文章就把整个配置过程、每一处容易踩坑的细节、以及完整的排错链路一次说清楚。适合刚开始用STM32H743做以太网通信的工程师也适合那些明明照着别人工程抄却跑不起来、正在抓狂的人。我会尽量把为什么也讲透——只告诉你怎么点鼠标没意义你会点不一定知道点错了什么。1. 硬件底子先自查LAN8720与H743的RMII连接很多人在CubeMX里设置了半天最后发现是硬件连接本身有问题。这个检查必须放在最前面省得后面白忙活。1.1 RMII的7根信号线少一根都不行STM32H743自带100M以太网MAC控制器但要跑物理链路还需要一颗PHY芯片。LAN8720是当前性价比最高的选择之一几块钱一片10/100M自适应RMII接口只需要7根信号线就能和MCU对接TXD0、TXD1、TX_EN、RXD0、RXD1、CRS_DV、REF_CLK再加上MDIO和MDC管理总线一共9根。相比MII接口的16根线PCB走线负担小很多。H743这边对应的RMII引脚映射是固定的CubeMX里勾选ETH外设后会自动分配但你在原理图阶段就得对照确认信号H743引脚接到LAN8720RMII_REF_CLKPA1REF_CLK/CLKOUTRMII_MDIOPA2MDIORMII_MDCPC1MDCRMII_CRS_DVPA7CRS_DVRMII_RXD0PC4RXD0RMII_RXD1PC5RXD1RMII_TXD0PB12TXD0RMII_TXD1PB13TXD1RMII_TX_ENPG11TX_EN这里最容易出问题的是接线顺序搞反尤其是TXD和RXD两组。有些人画板子时把TXD0和TXD1弄反了或者把CRS_DV漏接了后期软件怎么调都出不来。我吃过一次亏最后用万用表一根根量才发现RXD0和RXD1互换飞线解决。1.2 时钟谁给谁25M晶振与REF_CLK方向LAN8720的RMII接口需要50MHz的参考时钟。现在市面上绝大多数的LAN8720模块都是板载一颗25MHz晶振接到PHY的XI/XO引脚由PHY内部PLL倍频到50MHz然后通过REF_CLK引脚输出给MCU。这种情况下H743的PA1ETH_RMII_REF_CLK是输入模式它接收的是PHY吐出来的50MHz时钟。另一种方案是让MCU输出50MHz给PHY做时钟源此时H743的ETH_CLK引脚用于输出LAN8720的REF_CLK作为输入参考。这种方式在H743上需要额外配置时钟输出一般没那个必要。你在确认硬件方案时只有一个问题要问LAN8720模块上有没有25M晶振有就说明它是PHY提供时钟方案没有那大概率是MCU提供时钟方案。开发板上最常见的是前者不必在CubeMX时钟树里再费力气生成50M给PHY。1.3 PHY地址的默认值0还是1这是坑中之坑。LAN8720A的SMI地址由PHYAD0引脚决定绝大多数模块把PHYAD0接地所以默认地址是0x00。但有些国产兼容芯片或定制板卡会把PHYAD0上拉地址就变成0x01。如果你的工程里写死了0x00而实际上模块是0x01结果就是MDIO通信阳光灿烂但PHY完全不应答。我习惯拿新板子时先把CubeMX里ETH的PHY地址参数分别试0x00和0x01配合下面的状态寄存器读取10秒钟就能确定真实地址。另外一提DP83848和KSZ8081这类芯片默认地址是0x01经常有人从F429的DP83848工程迁移到H743LAN8720时忘了改这个白白浪费一下午。2. CubeMX v6.5.0里的ETH配置哪些参数不能偷懒2.1 时钟树先摆平ETH 50M从哪来CubeMX v6.5.0里选中STM32H743后第一步我会先把RCC时钟树配到最大480MHz通过HSEPLL1这一步顺手操作就行。但你要注意到时钟树界面上有一个ETH相关的分支名称通常显示为Ethernet或ETHREFCLK。CubeMX会让你在某个PLL输出里选频率这里务必保证给ETH的是50MHz。很多人栽在这一点MAC控制器内部用的是AHB总线时钟但RMII接口的时序基准来自外部提供的50MHz REF_CLK。如果你选的是PHY提供时钟方案ETHREFCLK这个时钟树节点只需要设置为旁路或外部时钟即可如果你选的是MCU提供时钟那就得保证Clock Output产生50MHz。我的建议是永远使用PHY本地晶振方案这样时钟树里的事最少也最不容易把LAN8720的REF_CLK引脚搞成冲突状态。调试时只要能通过软件读到PHY芯片ID说明时钟链已经通了。2.2 ETH参数页的正确填法CubeMX左侧Connectivity里找到ETH模式选择RMII。进入Parameter Settings几个关键字段务必手动确认而不是轻信默认值Media InterfaceRMII不是MII。PHY Address按硬件实际地址填常见是0x00。MAC Address建议直接填一组自己的地址比如00:80:E1:00:00:01别留全零或全FF否则某些交换机会直接丢弃报文。Checksum offloadH7的MAC支持硬件校验和卸载建议保持使能能减轻CPU负担。还有一个比较隐蔽的选项是MDC时钟分频。HAL库会自动计算但如果你的SMI总线上MDC频率太高LAN8720可能反应不过来表现为偶发读取失败。手动把MDC分频调到2.5MHz以下是一个稳妥的选择不过我实测H743LAN8720在默认配置下是稳定的这个放到后面排错章节细说。2.3 同时勾选FreeRTOS和LWIP时的顺序问题在CubeMX里同时启用FreeRTOS和LWIP后生成的工程会多出两个中间件目录LWIP/App、LWIP/Target以及Middlewares/Third_Party/LwIP源码。生成完代码后最容易被忽视的是main.c里的初始化顺序。CubeMX默认生成的是MX_GPIO_Init(); MX_ETH_Init(); MX_LWIP_Init(); MX_FREERTOS_Init();注意顺序ETH初始化必须在LWIP之前。因为MX_LWIP_Init内部要调用底层网卡驱动的初始化底层驱动又依赖ETH的DMA描述符和MAC寄存器已经就绪。如果你手欠调换顺序轻则协议栈起不来重则进HardFault。另外如果你改了ETH的DMA描述符缓冲区的内存段比如想把描述符放到DTCM以外的高速RAM记得同时调整链接脚本里的内存区域定义和使用。CubeMX默认生成的工程里ETH的DMA描述符放在了ETH_DMA_DESC段内存对齐要求32字节这些细节不建议乱动。3. LWIP不是默认就能跑的内存与协议参数的调优3.1 内存池有多重要MEM_SIZE与PBUF_POOLLWIP被诟病的地方之一就是内存参数多且晦涩。在CubeMX的Middleware and Software Packs - LWIP配置页面里你能看到一大堆零点几几的宏实质影响跑不跑得起来的就那么几项参数建议值说明MEM_SIZE10*1024或更大协议栈堆内存用于PBUF、UDP/TCP控制块等PBUF_POOL_SIZE16接收缓冲区池数量太少会导致拥塞丢包PBUF_POOL_BUFSIZE1512单包最大长度1500MTU14字节以太网头留几个字节余量TCP_MSS1460最大段大小经典值TCP_WND4*TCP_MSS接收窗口影响大流量吞吐TCP_SND_BUF保持一致发送缓冲影响TCP发送速率很多人跑TCP客户端时发现发送卡顿把TCP_SND_BUF调大就明显改善。而UDP收包丢包严重时先考虑PBUF_POOL_SIZE是不是只有4或者8。H743有1MB RAM大方一点没问题我实际配的是MEM_SIZE60*1024PBUF_POOL_SIZE32。从裸机移植模型转过来的朋友要特别注意CubeMX里如果NO_SYS设为0说明走的是带OS的版本LWIP内部会创建tcpip_thread和一个邮箱队列。理论上邮箱长度也要足够LWIP_MBOX_SIZE建议至少等于PBUF_POOL_SIZE否则高负载下消息堆积会丢事件。3.2 DHCP还是静态IP调试期的选择CubeMX的LWIP配置页有DHCP选项开关。生产环境用DHCP没毛病但调试网络通不通的第一天请务必关掉DHCP使用静态IP。原因很简单DHCP需要协议栈完整跑起来、底层PHY link也要稳定才能通过UDP广播拿到地址。一旦DHCP失败你会分不清是协议栈坏了还是DHCP服务器不响应排错链路直接多一层迷雾。静态IP配置非常简单在CubeMX里给LWIP填入#define LWIP_IPADDR 192.168.1.10 #define LWIP_NETMASK 255.255.255.0 #define LWIP_GW 192.168.1.1设好之后用一个直连网线连电脑电脑IP配成192.168.1.x网段然后直接PING 192.168.1.10。能通说明底层和协议栈都稳了不通恭喜你进入第5章的排错链路。3.3 任务优先级和tcpip_thread的分工CubeMX为LWIP生成的线程方案里tcpip_thread负责协议栈核心循环它是整个网络的中枢。FreeRTOS里它的优先级默认是osPriorityNormalCMSIS-RTOS2的140这个值在大多数场景够用。但如果你主循环里跑着复杂的计算任务或者有其他高优先级任务频繁抢占tcpip_thread被饿死就会表现为网络慢、偶发超时。我的做法是单独建一个网络配置线程优先级设为osPriorityHigh类似Normal2主要负责建Socket、收数据做业务处理而tcpip_thread保持默认。至于CubeMX生成的defaultTask我建议直接改名成一个idle风格的占位任务或干脆删掉别在里面跑可能导致阻塞的业务逻辑否则会和LWIP的消息队列抢CPU时间。4. 中断优先级和FreeRTOS的红线4.1 为什么ETH中断不能设成最高优先级这句已经是老生常谈了但还是有人踩。Cortex-M7的NVIC在H743上使用4位抢占优先级数值范围0~15数字越小优先级越高。FreeRTOS移植时通常把configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY设为5意思是从中断里只能调用优先级数值大于等于5的中断服务函数内的API。ETH中断服务函数里会调用osSemaphoreReleaseFromISR这种FreeRTOS API所以它的优先级数值必须小于等于5也就是比FreeRTOS允许的临界区优先级别更低或相等。如果把它设成0~4一旦ETH中断在FreeRTOS临界区内打断执行再从ISR里尝试获取调度相关资源系统就会死锁或HardFault。CubeMX里ETH的NVIC配置我始终给抢占优先级5子优先级0。还有个容易忽略的点DMA相关错误中断也要单独使能否则某种异常下DMA停摆了你连个中断都看不到排查起来极其痛苦。4.2 从ETH中断到tcpip_thread的数据流搞清楚一条数据从网线进来之后怎么流动是后期排错的关键。H743的ETH收到一帧后会通过DMA把报文存入RAM中的接收描述符然后产生ETH中断。中断服务函数里HAL库会调用HAL_ETH_RxCpltCallback这个回调CubeMX在这个回调里释放一个二值信号量RxPktSemaphore。底层网络线程等在信号量上一旦拿到就调用tcpip_input(p, g_eth)把报文喂给LWIP协议栈。协议栈再根据协议头决定是给UDP、TCP还是ICMP去处理最终数据才会进入你业务线程的Socket里。理解了这条链路你排错就有了地图中断产生了吗信号量释放了吗tcpip_input被调用了吗TCP/IP层回包了吗每层都有一个明确的判断点。4.3 任务堆栈给多少才够用BeCheap的做法是每个线程都给2048字节栈空间。LWIP的tcpip_thread如果配置支持大量TCP连接和较大MSS栈需求会明显上涨。CubeMX默认给tcpip_thread 1024字节栈实测跑TCP下载时容易溢出。建议直接改成2048或4096字节反正H743 RAM多。还有一个非常实用的办法开启FreeRTOS的栈溢出检测功能在FreeRTOS配置里将configCHECK_FOR_STACK_OVERFLOW设为2然后实现vApplicationStackOverflowHook在断言处用LED或串口打印提示。这样栈爆了能第一时间发现而不是系统行为诡异时无从下手。5. 排错全过程三个典型故障的排查链路5.1 故障ASMI读不到PHY连ID都读不出来这是最绝望的故障代码烧进去初始化函数没报错但PHY ID读出来全是0xFF或者0x00。排查链路按顺序走检查PHY地址。先把PHY地址在0x00和0x01之间切换重新编译烧录仍读不到继续下一步。用示波器或逻辑分析仪抓MDC和MDIO引脚的信号。如果MDC完全没有波形说明HAL以太网初始化时SMI分频有问题或GPIO复用没生效如果有波形但MDIO一直是高没有翻转说明PHY没有应答也就是没上电或复位被拉低了。检查LAN8720的NRST引脚。很多模块设计里这个引脚直接连MCU的某个GPIO或者通过RC电路实现上电延迟复位。如果你的板子刚好用一个GPIO控制NRST但GPIO默认输出低PHY就永远被按在复位状态SMI自然读不到任何东西。我遇到过一只板子的LAN8720电源脚到地只有1uF电容上电瞬间纹波极大导致芯片没起来加大到10uF后一切正常。硬件问题优先怀疑电源和复位这个方向永远不会错。5.2 故障BPHY能读到ID但link状态始终downSMI通了芯片ID也对但PHY的状态寄存器里link bit始终为0网口灯也不亮。这种情况先排除软件问题确认你的网线设备端是通迅的电脑插上去能识别。然后把PC网卡强制成100M全双工看link能不能上来能上来说明LAN8720自适应功能可能被关掉了。软件上真正常见的坑是RMII时钟没配对。此时PHY芯片虽然活着但它没有稳定的50MHz REF_CLK参考时钟内部收发路径起不来link状态就永远不对。用示波器量PA1引脚确认是不是有50MHz方波。如果没有回头检查你的时钟树配置或硬件晶振焊接。另一个因素是LAN8720的PHY地址寄存器里有一个专门控制link状态的测试位有个别代码调试PHY时误把寄存器改乱了重新上电后才能恢复。5.3 故障C能PING通但丢包越来越严重这属于能用但不舒服的情况。PING几十个包丢一两个压力测试直接超时。最常见的原因是DMA描述符或PBUF池耗尽。接收方向处理不过来新到的报文被丢弃。优先检查MEM_SIZE和PBUF_POOL_SIZE是否过小。另外ETH中断服务函数里如果处理时间过长也会导致DMA描述符来不及回收。可以试试把ETH中断优先级从5调到6数值越大优先级越低反而可能改善——因为高优先级中断频繁打断tcpip_thread反而拖慢了协议栈处理速度。还有一次我遇到的丢包是电源噪声引起的LAN8720的3.3V和H743的3.3V之间缺少磁珠隔离板子电机一转就疯狂丢包。这种问题纯属硬件但软件上可以通过观察PHY状态寄存器里的接收错误计数来佐证——那个字段疯狂跳说明物理层就有问题。6. 实测数据与后续扩展6.1 我这边测出的性能数字这套配置稳定下来之后我做了一轮简单压力测试。PC端用iperf往开发板灌TCP数据测试环境是H743跑480MHzLAN8720模块CAT5e网线直连电脑静态IPFreeRTOSLWIP 2.1.2实测结果如下测试项结果Ping延迟ping 192.168.1.101000个包平均0.8ms无丢包TCP下行吞吐iperf 60秒稳定在92Mbps左右UDP接收1000字节包并发速率约80Mbps开始偶发丢包持续运行时间72小时无断链无死机这个数据在百兆以太网里已经相当能打性能瓶颈基本在协议栈和PHY而不是H743的MAC。如果你的测试数据远低于这个优先怀疑内存池配太小或者中断优先级配得不合理。6.2 几个越用越顺手的技巧说几个我用下来觉得非常值的小技巧。第一调试时在LWIP的ethernetif.c里加入PHY状态打印每次link状态变化时通过串口输出能帮你快速定位网线插拔时的问题。第二开启LWIP的LWIP_STATS宏在调试阶段观察lwip_stats里的丢包计数和内存使用率比猜靠谱得多。第三如果你要上生产环境记得把LWIP_DEBUG关掉否则大量调试输出会挤占CPU直接影响吞吐。还有一个经验是保存一份最小可用工程。每次换芯片型号、换PHY时在这个基础上改而不是从零新建工程。CubeMX虽然方便但每次配置不同中间件版本都会带来一些隐藏差异拿着一份验证过的工程做底子能省去大量重复排错时间。现在这套H743LAN8720的模板已经成了我们团队做以太网项目的标准起点。你把这篇文章里的配置思路走一遍大概率也能从PING不通顺利走到稳定跑一周。如果还有更刁钻的故障建议先查PHY寄存器、先量时钟信号九成问题都出在这两者上。