简介这份资源面向使用STM32F429IGT6进行以太网开发的嵌入式工程师与学习者聚焦HAL库环境下LAN8720A PHY芯片的驱动与LWIP协议栈移植解决无操作系统条件下实现网络PING通信的问题。工程基于KEIL MDK_ARM 5.27、STM32CubeMX 6.6.1与HAL V1.27.1搭建LWIP版本为2.1.2其中LAN8720A通过RMII接口与STM32的ETH外设进行数据通信STM32则借助SMI接口读写LAN8720A内部寄存器完整呈现了PHY初始化、链路检测与协议栈对接的关键环节。压缩包共1300个文件以678个c源文件与450个h头文件为主体辅以78个s汇编启动文件、46个icf链接脚本及若干lib、a库文件与hex固件整体约31.16MB工程结构完整、层次分明。目前已有1989人学习下载适合希望掌握STM32以太网底层驱动与LWIP裸机移植、并对照实际工程查漏补缺的开发者参考。1. 一块 LAN8720A 调通 PING 到底卡在哪手头有一块 STM32 板子PHY 用的是 LAN8720A想跑裸机 LWIP 做个最基础的 PING 通结果插上网线后 LED 不亮、ping一直超时、netif状态永远是 link down——这是很多做 stm32 c语言 以太网裸机开发的人都会撞上的第一堵墙。这份资源就是围绕「STM32 HAL 库 LAN8720A LWIP 无操作系统 PING」这条最小可用链路整理的工程包目标很明确不跑 RTOS不接协议栈中间件纯裸机把网口拉起来让 PC 能 ping 通板子。它适合正在做工业网关、串口服务器、设备网口调试的嵌入式工程师也适合刚接触 STM32 以太网外设、想先跑通链路层再往上叠协议的新手。下面按「硬件怎么接、PHY 怎么初始化、LWIP 怎么裸机跑、PING 不通怎么查」的顺序拆开讲。2. 硬件链路与 PHY 地址先把 LAN8720A 的物理层坐实2.1 RMII 接线与 50MHz 时钟来源LAN8720A 支持 RMII 和 MII 两种接口STM32 这边为了省引脚基本都走 RMII。RMII 需要一根 50MHz 参考时钟来源有两种一种是 STM32 的 MCO 引脚输出 50MHz 给 PHY另一种是 PHY 自己外挂 25MHz 晶振再内部倍频。这两种接法直接决定了初始化代码里时钟配置怎么写接错了现象就是 PHY 寄存器读出来全是 0 或 0xFFFF。常见做法是如果板子上 LAN8720A 旁边焊了 25MHz 晶振就把 PHY 的 nINT/REFCLKO 引脚配置成时钟输出模式STM32 侧用外部时钟源如果没焊晶振就得让 STM32 的 PA8MCO1输出 50MHz 给 PHY 的 XTAL1/CLKIN。我一般先拿万用表量 PHY 的 REFCLKO 脚有没有 50MHz没有就先解决时钟别急着调代码。RMII 关键引脚对应关系以常见 STM32F4/F7 系列为例信号STM32 侧LAN8720A 侧说明REF_CLKPA1REFCLKO/XTAL150MHz 参考时钟MDIOPA2MDIO管理数据MDCPC1MDC管理时钟CRS_DVPA7CRS_DV载波侦听RXD0PC4RXD0接收数据 0RXD1PC5RXD1接收数据 1TX_ENPB11TX_EN发送使能TXD0PB12TXD0发送数据 0TXD1PB13TXD1发送数据 12.2 PHY 地址与 SMI 总线扫描LAN8720A 的 PHY 地址由 PHYAD0 引脚在上电时锁存决定接下拉就是 0接上拉就是 1。很多板子默认下拉地址为 0。但如果你用的是某个现成模块PHYAD0 可能被拉高地址变成 1代码里还写 0 就永远读不到寄存器。排查方法很简单写一段 SMI 扫描代码遍历 0~31 地址读 ID 寄存器/* 扫描 SMI 总线上所有可能的 PHY 地址 */ for (uint8_t addr 0; addr 32; addr) { uint32_t id 0; /* HAL_ETH_ReadPHYRegister 返回 HAL_OK 说明该地址有响应 */ if (HAL_ETH_ReadPHYRegister(heth, addr, 0x02, id) HAL_OK) { printf(PHY found at addr %d, ID high 0x%04X\r\n, addr, id); } }逻辑说明0x02是 PHY Identifier Register 1LAN8720A 读出来应该是0x0007。如果全 0 或全 F说明 MDIO/MDC 没通或者时钟没起。参数上HAL_ETH_ReadPHYRegister的第二个参数是 PHY 地址第三个是寄存器偏移。扫描到正确地址后把它填进heth.Init.PhyAddress。提示有些板子 MDIO 需要外部上拉电阻STM32 内部上拉太弱长走线时读寄存器会间歇性失败示波器看 MDC 波形最直接。3. HAL 库 ETH 初始化与 LWIP 裸机移植3.1 CubeMX 配置 ETH 外设的关键项用 CubeMX 配 ETH 时几个参数必须和硬件对上PHY Address 填扫描出来的值Auto Negotiation 打开Speed 和 DuplexMode 先设成自动等协商完成后再读实际值Rx Mode 选中断或轮询裸机 LWIP 一般用中断收包、轮询发包。Advanced Parameters 里 Rx Buffers Length 建议 1524够标准以太网帧。生成的MX_ETH_Init里会调用HAL_ETH_Init但 LAN8720A 的初始化不能只靠 HAL还得手动配 PHY 的 BCR 寄存器开自协商/* 配置 LAN8720A 开启自协商重启协商过程 */ uint32_t bcr 0; HAL_ETH_ReadPHYRegister(heth, PHY_ADDR, 0x00, bcr); bcr | (1 12); /* Auto-Negotiation Enable */ bcr | (1 9); /* Restart Auto-Negotiation */ HAL_ETH_WritePHYRegister(heth, PHY_ADDR, 0x00, bcr); /* 等待协商完成检查 BSR 寄存器的 Link Status 位 */ uint32_t bsr 0; do { HAL_ETH_ReadPHYRegister(heth, PHY_ADDR, 0x01, bsr); } while (!(bsr (1 2))); /* bit2 Link Status */逻辑说明BCR 的 bit12 是自协商使能bit9 是重启协商。BSR 的 bit2 是链路状态为 1 表示链路已建立。参数上PHY_ADDR就是前面扫描出来的地址。这段代码放在MX_ETH_Init之后、LWIP 初始化之前。3.2 LWIP 无操作系统时的sys_check_timeouts调用裸机跑 LWIP 最大的坑是超时处理。LWIP 内部大量依赖sys_check_timeouts()来驱动 ARP 表老化、TCP 重传、DHCP 续租。没有 RTOS 就没有任务去周期调用它必须自己在主循环里塞进去/* 主循环中必须周期性调用建议 1ms 一次 */ while (1) { /* 处理 ETH 接收中断里挂起的包 */ ethernetif_input(gnetif); /* 驱动 LWIP 内部定时器不调用则 ARP 永远不老化、ping 回包延迟巨大 */ sys_check_timeouts(); /* 其他业务代码 */ }逻辑说明ethernetif_input负责从 ETH 中断的 pbuf 队列里取包并交给 LWIP 协议栈sys_check_timeouts是裸机 LWIP 的心跳。参数上调用频率建议 1ms太慢会导致 ARP 请求重发间隔不准太快浪费 CPU。我一般用 SysTick 中断置标志位主循环里判断标志再调用。注意如果sys_check_timeouts漏调现象是第一次 ping 可能通过几秒就断或者 ARP 表里 PC 的条目一直不更新回包发不出去。4. PING 不通的排查从 PHY 寄存器到 ARP 表逐层定位4.1 先看 PHY 链路状态和自协商结果PING 不通时不要一上来就怀疑 LWIP先读 PHY 的 BSR 和 PHYSR 寄存器。BSR bit2 是链路状态PHYSR地址 0x1F能看出速度和双工模式。如果链路状态是 0说明网线、变压器、RJ45 座子这一路有问题代码再对也没用。/* 读取 LAN8720A 的 PHY 特殊状态寄存器确认协商结果 */ uint32_t physr 0; HAL_ETH_ReadPHYRegister(heth, PHY_ADDR, 0x1F, physr); /* bit2: 1全双工 0半双工bit3: 1100M 010M */ printf(Duplex: %s, Speed: %s\r\n, (physr (1 2)) ? Full : Half, (physr (1 3)) ? 100M : 10M);逻辑说明LAN8720A 的 0x1F 寄存器是厂商自定义的能直接读出协商后的速度和双工。如果这里显示 10M 半双工而 PC 端是千兆网卡可能是网线只有两对线或者变压器中心抽头没接对。4.2 检查 MAC 地址和 netif 状态LWIP 的netif必须正确设置 MAC 地址、IP、掩码、网关并且netif_set_up和netif_set_link_up都要调用。少一个协议栈就不发 ARP 请求。/* 设置 MAC 地址注意第一个字节的 bit0 必须是 0单播 */ gnetif.hwaddr[0] 0x02; gnetif.hwaddr[1] 0x00; gnetif.hwaddr[2] 0x00; gnetif.hwaddr[3] 0x00; gnetif.hwaddr[4] 0x00; gnetif.hwaddr[5] 0x01; /* 设置静态 IP注意和 PC 在同一网段 */ IP4_ADDR(gnetif.ip_addr, 192, 168, 1, 100); IP4_ADDR(gnetif.netmask, 255, 255, 255, 0); IP4_ADDR(gnetif.gw, 192, 168, 1, 1); netif_add(gnetif, ip, mask, gw, NULL, ethernetif_init, ethernet_input); netif_set_default(gnetif); netif_set_up(gnetif); netif_set_link_up(gnetif); /* 这行漏了ping 必不通 */逻辑说明netif_set_link_up会触发 LWIP 发送 ARP 请求去探测网关漏掉这行协议栈认为链路没通不会主动发包。参数上MAC 地址第一个字节的 bit0 必须为 0否则是组播地址PC 的 ARP 表不会正常响应。4.3 用 Wireshark 抓包看 ARP 有没有出去如果代码都检查过了还是不通就在 PC 上开 Wireshark 抓包。正常流程是板子发 ARP 请求问「谁是 192.168.1.1」PC 回 ARP 应答然后板子才能发 ICMP。如果抓不到板子的 ARP 请求说明包根本没从 MAC 发出去回去查 ETH 的 DMA 描述符和 TX 使能位。常见现象是 ETH 的DMATDLAR寄存器写了但DMATXPOLLDEMAND没触发或者 TX 描述符的 OWN 位没置 1。HAL 库的HAL_ETH_Transmit内部会处理但如果你自己改过描述符链就要手动确认。5. 避坑与常见问题排查5.1 现象PHY 寄存器读出来全是 0xFFFF原因MDIO 没有上拉或者 MDC 时钟太快超过 PHY 支持的 2.5MHz。STM32 的 MDC 由 AHB 时钟分频得到CubeMX 里 MDC Clock Range 要选对HCLK 168MHz 时选 CSR Clock Range 150-168MHz 那档。解决在 CubeMX 的 ETH 配置里把 MDC Clock Range 调到对应 HCLK 范围或者手动改heth.Init.MDCClockRange。同时确认 MDIO 线上有 1.5k~10k 上拉。5.2 现象链路状态能读到 Link Up但 ping 超时原因sys_check_timeouts没调用或者调用频率太低。LWIP 的 ARP 表项有老化时间不调用超时函数ARP 表永远不更新回包时找不到 MAC 地址就丢弃。解决主循环里确保 1ms 调用一次sys_check_timeouts并且ethernetif_input也要周期调用不能只靠中断。5.3 现象能 ping 通但丢包严重延迟忽大忽小原因ETH 接收中断优先级太低被其他中断打断导致 DMA 溢出或者 pbuf 池太小收包时分配不到内存。解决把 ETH 中断优先级设到较高但不要高过 SysTick在lwipopts.h里把PBUF_POOL_SIZE从默认 16 调到 32 以上MEM_SIZE也相应加大。5.4 现象换一块板子就不通同样的代码原因PHY 地址不同或者 50MHz 时钟来源不同。有的板子用 MCO 输出有的用外部晶振代码里时钟配置没跟着改。解决把 PHY 地址做成宏定义上电时先扫描时钟配置根据板子原理图确认MCO 输出时记得使能__HAL_RCC_MCO1_CONFIG。5.5 现象PC 能 ping 通板子板子 ping 不通 PC原因LWIP 的 ICMP 回显请求发送需要正确的网关和路由。如果板子和 PC 不在同一网段且没配网关或者netif_set_default没调用协议栈不知道从哪个网口出。解决确认netif_set_default(gnetif)已调用网关地址和 PC 的 IP 在同一网段或者直接给板子配和 PC 同网段的静态 IP。6. 进阶验证用 Iperf 和长时间 ping 压出隐藏问题PING 通了只是第一步真正要确认链路稳定我一般会做两件事一是 PC 端开ping -t -l 1472连续跑半小时看有没有偶发丢包二是用 Iperf 打流看 TCP 吞吐能不能到 80Mbps 以上100M 全双工的理论上限。裸机 LWIP 的吞吐瓶颈通常在PBUF_POOL_SIZE和MEM_SIZE以及ethernetif_input的调用频率。# PC 端连续 ping包大小 1472 字节持续 1800 秒 ping 192.168.1.100 -t -l 1472 -n 1800 # Iperf 服务端在 PC 上跑板子做客户端打流 iperf -s -i 1如果 Iperf 跑出来只有几 Mbps先查lwipopts.h里的TCP_SND_BUF和TCP_WND裸机默认值偏小调到 4~8 个 MSS 会有明显改善。另外 ETH 的 RX 描述符数量也要够heth.Init.RxDesc建议不少于 4 个。还有一个容易被忽略的点LAN8720A 的节能模式。有些批次默认开了 Energy Detect链路空闲时会掉速或断链。可以在初始化时读 0x10 寄存器把 bit1 的 Energy Detect 关掉强制保持全速。从那以后我每次调新板子的以太网都强制走一遍「扫 PHY 地址 → 读 BSR 确认链路 → 抓 ARP 包 → 连续 ping 半小时」的流程不再凭感觉猜。希望帮到你。本文还有配套的精品资源点击获取