简介:这份资源面向使用STM32F429IGT6进行以太网开发的嵌入式工程师与学习者,聚焦HAL库环境下LAN8720A PHY芯片的驱动与LWIP协议栈移植,解决无操作系统条件下实现网络PING通信的问题。工程基于STM32CubeMX 6.6.1与HAL V1.27.1生成,LWIP版本为2.1.2,PHY通过RMII接口与ETH外设传输数据,并由SMI接口完成寄存器读写配置,开发环境为KEIL MDK_ARM 5.27。压缩包共约1300个文件,以678个c源文件与450个h头文件为主体,另含78个s汇编、46个icf及sct链接脚本、若干lib与a库文件、ioc工程配置、uvprojx与uvoptx工程文件及hex固件,整体约31.16MB,结构完整可直接编译。目前已有1989人学习下载。读者可据此掌握RMII硬件连接、PHY寄存器配置、LWIP初始化与PING回环验证的完整流程,并参考工程目录组织与链接脚本设置,快速搭建可复用的以太网通信模板。
1. 从一块 PING 不通的板子说起:STM32 HAL 库以太网环境到底怎么搭
手头有一块 STM32 开发板,PHY 用的是 LAN8720A,想跑裸机 LWIP 协议栈,最后能 ping 通,这个场景在工业控制和物联网终端里太常见了。但真动手装环境的时候,很多人卡在第一步:CubeMX 里 ETH 外设的引脚怎么配、RMII 时钟从哪来、LWIP 的PHY_ADDRESS填多少、PING请求发出去没回应到底是硬件还是软件的问题。我见过不止一个开发者,代码编译零报错,烧进去网口灯也亮,就是 ping 不通,最后发现是 PHY 地址写错或者复位引脚没拉高。这篇笔记就按我自己的装机顺序,把 STM32 HAL 库 + LAN8720A + LWIP 无操作系统这套环境从零搭一遍,每一步都落到具体参数和验证方法上,新手能照着走通,熟手也能对一下版本和配置取舍。
2. 环境准备:STM32CubeMX 版本选择与 LAN8720A 硬件确认
2.1 工具链版本怎么选才不翻车
这套环境的核心工具是 STM32CubeMX 和对应的 HAL 库固件包。我一般会固定用 CubeMX 6.10 以上的版本,原因是早期 5.x 版本在 ETH 外设的 RMII 模式配置上有个坑:生成的HAL_ETH_MspInit里 GPIO 速度等级默认给的是GPIO_SPEED_FREQ_LOW,跑 RMII 的 50MHz 参考时钟时容易出玄学问题,表现为 ping 通几包就断。6.x 版本默认给的是GPIO_SPEED_FREQ_VERY_HIGH,省得自己改。
固件包选STM32Cube FW_F4 V1.28.0或对应你芯片系列的较新版本。LWIP 中间件版本跟着固件包走,一般是 LWIP 2.1.2。不要混用不同版本的 HAL 和 LWIP,我试过用 F4 的 HAL 配 F7 的 LWIP 中间件,编译能过,但ethernetif_input里的信号量释放逻辑对不上,跑几分钟就死。
安装 CubeMX 本身没什么好说的,官网下载安装包一路下一步。装完第一次打开会提示你下载固件包,建议直接下到本地仓库,别用在线模式,后面离线重建工程方便。
2.2 LAN8720A 硬件连接必须确认的四个点
在打开 CubeMX 之前,先把板子上的 LAN8720A 接线确认一遍,这步省了后面全是血泪。
第一,RMII 参考时钟。LAN8720A 的REF_CLK引脚有两种接法:一种是外部 50MHz 晶振直接供,另一种是从 STM32 的PA1输出 50MHz 给 PHY。我一般用后者,因为省一个晶振。CubeMX 里要把PA1配成RCC_MCO_1或者用ETH_RMII_REF_CLK复用,具体看芯片手册。如果这里配错,现象是网口灯亮但 ping 不通,用示波器量REF_CLK没有 50MHz 波形。
第二,PHY 地址。LAN8720A 的PHYAD0引脚决定地址,悬空或下拉时地址是0x00,上拉时是0x01。我一般会在原理图上确认这个引脚,然后在代码里把LAN8720_PHY_ADDRESS宏改成对应值。这个填错是最常见的 ping 不通原因,没有之一。
第三,复位引脚。LAN8720A 的nRST要接到 STM32 的一个 GPIO 上,CubeMX 里配成输出,初始化时先拉低 100ms 再拉高。有些板子省了这个引脚,靠上电复位,也能跑,但稳定性差,我建议还是接上。
第四,MDIO 和 MDC 引脚。这两个是 PHY 寄存器读写用的,CubeMX 里配成ETH_MDIO和ETH_MDC复用,不要配成普通 GPIO。
提示:硬件确认这步别偷懒,我见过太多人代码改了一周,最后发现是 PHY 地址焊错了。
3. CubeMX 工程配置:ETH 外设与 LWIP 参数逐项拆解
3.1 ETH 外设的 RMII 模式配置
打开 CubeMX,选好芯片型号,第一步进Connectivity -> ETH。Mode选RMII,下面会自动勾上Ethernet MAC和Ethernet PHY。
关键参数在Parameter Settings里:
PHY Address:填你硬件确认的地址,比如0。Auto Negotiation:勾上,让 PHY 自己协商速率和双工。Rx Mode:选Interrupt Mode,裸机下用中断收包比轮询稳。Checksum Offload:我一般关掉,LWIP 自己算校验和,开了反而容易出玄学问题。
引脚分配这块,CubeMX 会自动把PA1、PA2、PA7、PC1、PC4、PC5这些配成 ETH 复用。你要检查的是PA1有没有被配成ETH_RMII_REF_CLK,如果没有,手动在Pinout视图里点PA1选这个复用功能。
时钟树也要看一眼。ETH 的时钟源一般是PLL出来的,确保HCLK和PLL配置让 ETH 时钟在 25MHz 或 50MHz 范围内。F4 系列一般是HCLK=168MHz,ETH 时钟走PLL的Q分频。
3.2 LWIP 中间件参数怎么填
进Middleware -> LWIP,勾上Enabled。下面几个参数是必须改的:
// lwipopts.h 里我一般会改这几项 #define LWIP_DHCP 0 // 裸机调试先用静态 IP,别开 DHCP #define LWIP_ICMP 1 // PING 靠这个,必须开 #define LWIP_UDP 1 // 后面调试用 UDP 方便 #define LWIP_TCP 1 // 需要 TCP 就开,不需要可以关省空间 #define MEM_SIZE (4 * 1024) // 默认 1600 太小,ping 大包会挂 #define PBUF_POOL_SIZE 8 // 收包缓冲池,太小会丢包LWIP_DHCP先关掉,用静态 IP。原因很简单:DHCP 没通的时候你连板子都 ping 不到,没法判断是 DHCP 问题还是网络问题。静态 IP 配好,ping 通了再开 DHCP 调。
静态 IP 在Parameter Settings里填:
IP Address:192.168.1.100,跟你电脑同网段。Netmask:255.255.255.0。Gateway:192.168.1.1,一般填路由器地址。
PHY Address这里再确认一遍,跟 ETH 外设里填的一致。
3.3 生成工程时的几个选项
Project Manager里,Toolchain / IDE选你用的,比如MDK-ARM或STM32CubeIDE。Code Generator里勾上Generate peripheral initialization as a pair of .c/.h files,这样 ETH 和 LWIP 的初始化代码会单独成文件,后面改起来方便。
生成工程后,先编译一遍。如果报错说LAN8720_PHY_ADDRESS未定义,去ethernetif.c里找,CubeMX 生成的代码里这个宏一般叫LAN8720_PHY_ADDRESS或者直接是PHY_ADDRESS,改成你硬件对应的值。
4. 代码补全:从 HAL 初始化到 PING 响应的关键修改
4.1 PHY 复位与自协商代码
CubeMX 生成的MX_ETH_Init里会调用HAL_ETH_Init,但 LAN8720A 的复位和自协商需要自己补。我一般会在main.c里MX_ETH_Init之前加一段:
// LAN8720A 硬件复位,nRST 接在 PD3 上 HAL_GPIO_WritePin(GPIOD, GPIO_PIN_3, GPIO_PIN_RESET); HAL_Delay(100); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_3, GPIO_PIN_SET); HAL_Delay(100); // 等待 PHY 自协商完成 uint32_t phy_reg; do { HAL_ETH_ReadPHYRegister(&heth, LAN8720_PHY_ADDRESS, 0x01, &phy_reg); } while (!(phy_reg & 0x0020)); // 等 BSR 寄存器的 Auto-Negotiate Complete 位这段逻辑是:先硬复位 PHY,等 100ms 让它稳定,然后读 PHY 的状态寄存器0x01,检查 bit5 是否置位。没置位就继续等,置位了说明自协商完成,链路通了。
LAN8720_PHY_ADDRESS这个宏在ethernetif.c里定义,改成你实际的地址。
4.2 中断优先级与ethernetif_input调用
裸机下 LWIP 收包靠HAL_ETH_RxCpltCallback回调,这个回调里会调用ethernetif_input。CubeMX 生成的代码里这个回调是空的,需要自己补:
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth) { ethernetif_input(&gnetif); // gnetif 是 LWIP 的网络接口结构体 }同时要确保 ETH 中断优先级配置正确。在HAL_ETH_MspInit里,HAL_NVIC_SetPriority(ETH_IRQn, 5, 0),优先级别设太高,不然会打断系统滴答定时器。
主循环里只需要周期性调用MX_LWIP_Process(),这个函数会处理 LWIP 的定时器和收包。我一般放在while(1)里,不加延时,让它全速跑。
4.3 静态 IP 与ping响应的验证代码
静态 IP 在lwip.c的MX_LWIP_Init里设置,CubeMX 生成的代码已经帮你填好了。你要确认的是netif_add之后有没有调netif_set_up和netif_set_link_up。有些版本的 CubeMX 生成代码里漏了netif_set_link_up,导致网口状态一直是 down,ping 不通。
补上这两句:
netif_set_up(&gnetif); netif_set_link_up(&gnetif);然后编译烧录,把板子和电脑接到同一个路由器或交换机上。电脑上打开命令行,ping 192.168.1.100。如果通,说明环境搭好了。如果不通,先看板子网口灯:绿灯亮表示链路通,黄灯闪表示有数据。灯不亮就回去查硬件和 RMII 时钟。
5. 避坑排查:LAN8720A 加 LWIP 最常见的五个翻车点
5.1 ping 不通,网口灯也不亮
现象:板子上电后网口灯完全不亮,电脑 ping 不到板子。
原因:九成是 RMII 参考时钟没出来。LAN8720A 需要 50MHz 时钟才能工作,如果PA1没配成ETH_RMII_REF_CLK,或者 STM32 的 MCO 没使能,PHY 就没时钟。
解决:用示波器量PA1引脚,看有没有 50MHz 方波。没有的话,检查 CubeMX 里PA1的复用配置,以及RCC里MCO的输出配置。F4 系列要开RCC_MCO1Config(RCC_MCO1SOURCE_PLLCLK, RCC_MCO1DIV1)。
5.2 ping 通几包就断,或者大包 ping 不通
现象:ping 192.168.1.100前几包通,后面超时。或者ping -l 1472 192.168.1.100直接不通。
原因:LWIP 的MEM_SIZE和PBUF_POOL_SIZE太小。默认MEM_SIZE是 1600 字节,ping 大包或者同时收几个包就溢出了。
解决:把MEM_SIZE改成4 * 1024或更大,PBUF_POOL_SIZE改成8或16。改完重新编译烧录。
5.3 PHY 地址填错,MDIO 读不到寄存器
现象:代码里读 PHY 寄存器返回HAL_ERROR,自协商一直不完成。
原因:LAN8720_PHY_ADDRESS跟硬件实际地址对不上。LAN8720A 的地址由PHYAD0引脚决定,悬空是0,上拉是1。
解决:看原理图确认PHYAD0的接法,把代码里的宏改成对应值。如果不确定,可以写个循环从0到31逐个读 PHY 的ID寄存器,能读到的就是正确地址。
5.4 中断优先级冲突,跑几分钟就死机
现象:ping 通一段时间后板子死机,或者HAL_ETH_RxCpltCallback进不去。
原因:ETH 中断优先级设得比SysTick还高,导致系统滴答定时器被打断,LWIP 的定时器超时逻辑乱掉。
解决:在HAL_ETH_MspInit里把 ETH 中断优先级设成5或更低,确保SysTick优先级是0或1。HAL_NVIC_SetPriority(ETH_IRQn, 5, 0)。
5.5 电脑能 ping 通板子,板子 ping 不通电脑
现象:电脑ping 192.168.1.100通,但在板子上写代码ping 192.168.1.101不通。
原因:LWIP 的ICMP发送功能没开,或者LWIP_ICMP宏是0。
解决:在lwipopts.h里确认LWIP_ICMP是1。另外,板子 ping 电脑需要电脑关防火墙,或者放行 ICMP 入站规则。
6. 装完怎么验证:从 PING 通到稳定跑一小时的检查清单
环境搭完,ping通了只是第一步。我一般会按下面这个清单再走一遍,确认不是“玄学通”。
先看基础连通性。电脑命令行ping 192.168.1.100 -t,连续跑 100 包,看丢包率。正常应该是 0% 丢包,延迟稳定在 1ms 以内。如果有丢包,回去查PBUF_POOL_SIZE和中断优先级。
再测大包。ping 192.168.1.100 -l 1472,这是不分包的最大长度。能通说明MEM_SIZE够用。如果超时,把MEM_SIZE加到8 * 1024再试。
然后看 PHY 状态。在代码里加一段打印,读 LAN8720A 的BSR寄存器0x01,确认 bit2 是Link Status,bit5 是Auto-Negotiate Complete。两个都置位才算链路正常。
uint32_t bsr; HAL_ETH_ReadPHYRegister(&heth, LAN8720_PHY_ADDRESS, 0x01, &bsr); printf("BSR: 0x%08X, Link: %d, AutoNeg: %d\n", bsr, (bsr >> 2) & 1, (bsr >> 5) & 1);最后跑稳定性。让板子连续 ping 一小时,同时用Wireshark抓包看有没有异常重传。我一般会在这步发现一些偶发问题,比如ethernetif_input里没加锁导致 pbuf 泄漏,跑久了内存耗尽。
注意:验证这步别省,我见过太多“实验室通、现场挂”的案例,都是因为没跑长稳测试。
从那以后我每次搭这套环境,都会在 ping 通之后强制跑一遍一小时长稳,再抓包看一遍重传率。这个习惯帮我提前拦住了至少三次现场翻车。希望帮到你。
本文还有配套的精品资源,点击获取