news 2026/10/3 3:35:38

STM32F407+LAN8720移植LwIP全攻略:从CubeMX配置到避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F407+LAN8720移植LwIP全攻略:从CubeMX配置到避坑指南

先把结论放前面:STM32F4 系列跑 FreeRTOS 再挂一个 LwIP 协议栈,搭配 LAN8720 这颗百兆 PHY,是很多物联网设备、工业采集器、远程升级模块的经典组合。这套组合能跑通,但绝不轻松。我得说,哪怕你已经在别的平台上玩过 LwIP,到了 STM32F407 + LAN8720 这里,照样会踩进几个隐蔽的坑里,而且其中一个坑能让你排查整整一个下午。

这篇文章要解决的,就是从零开始把 LwIP 2.1.2 移植到 STM32F4 + FreeRTOS 上,并且把 LAN8720 这个百兆 PHY 配置好,让它真正能 ping 通、能跑 TCP 通信。我会把你当成一个有一定 STM32 基础、但对网络协议栈不算熟悉的开发者,所有步骤都是我在实际板上验证过的。文章里会讲清楚方案为什么这么选、CubeMX 里每个参数背后的含义、代码改哪些地方、PHY 寄存器怎么看,以及在调试阶段最容易出现的几个“诡异现象”到底怎么排查。

适合看这篇文章的人:正在用 STM32F407/417 之类带 MAC 的芯片,准备做以太网通信但不想用 W5500 这种 SPI 网卡,想直接上内置 MAC + 外置 PHY 方案的开发者;或者你已经被 LAN8720 的 PHY 地址、REF_CLK 时钟折磨得头疼,想找一份完整排查思路的朋友。读完你会对“STM32 内置 MAC + 外部 PHY + RTOS + LwIP”这条链路有一个系统级的理解,而不是只复制粘贴代码。

1. 移植前需要想清楚的几件事

1.1 为什么是 LwIP 2.1.2 + LAN8720

先聊选型。STM32F407 这颗芯片内部自带以太网 MAC 控制器,但物理层还需要一颗 PHY 芯片来处理差分信号、编码解码这些事。市面上常见的 PHY 有 LAN8720、DP83848、LAN8742、RTL8201 等,而 LAN8720 几乎是 STM32F407 开发板上出现频率最高的一颗,原因很直接:RMII 接口、引脚少、3.3V 单电源、成本低。

它和 STM32F4 的 RMII 接口配合时,只需要 7 根数据/控制线(TXD0、TXD1、TX_EN、RXD0、RXD1、CRS_DV、REF_CLK),加上 MDC/MDIO 两根管理线,一共 9 根信号线就能跑起来。相比之下,如果选 MII 接口的 PHY,数据线要翻一倍还多,PCB 布线也更痛苦。所以从实际项目出发,LAN8720 是很务实的选择。

再说 LwIP 版本。LwIP 2.1.2 发布于 2018 年,是 2.1.x 系列里很稳定的一个版本。相比老掉牙的 1.4.1,它的 API 更规范,netconn 和 socket 层的兼容性更好,修复了 TCP 重传、内存管理好多个历史 bug。相比 2.0.x,2.1.x 又增加了不少新特性,比如更完善的 IPv6 支持、RAW API 的优化。在 STM32F407 这种资源不算特别充裕的 MCU 上,2.1.2 属于稳定性和功能之间的平衡点。

需要提一句的是,这套方案适合的项目是:需要对 TCP/UDP 传输、HTTP 配置页面、MQTT 上报、固件升级这类功能。如果你只想用一个非常简单的 UDP 收发,甚至可以用裸机 + LwIP 无操作系统模式(NO_SYS=1),代码量更少。但既然项目里已经需要跑多个任务、管理多个外设,FreeRTOS 几乎是逃不掉的选择,那 LwIP 在 RTOS 环境下的优势就体现出来了。

1.2 用 CubeMX 生成基础工程还是纯手动移植

很多人第一次做 LwIP 移植时会纠结要不要用 CubeMX。我的建议非常明确:用 CubeMX 生成基础框架,但别完全信任它生成的 PHY 驱动。

CubeMX 能为你在图形界面里完成三件工作量很大的事:第一,配置 STM32F407 内部的 ETH 外设和 DMA 描述符;第二,把 RMII 对应的引脚复用关系全部安排好;第三,自动生成 FreeRTOS 和 LwIP 的代码框架,包括ethernetif.c底层接口文件。

但问题也很明显:CubeMX 默认带的是LAN8742 的驱动,不是 LAN8720。有人会说这俩都是 SMSC 系的 PHY,寄存器结构差不多,但两者的 PHY 地址不一样,默认情况下 LAN8742 是 0x00,LAN8720 是 0x01。你在 CubeMX 里选了 LAN8742,生成的代码里 PHY 地址就是 0x00,直接跑,MDIO 总线根本读不到 LAN8720 的任何寄存器。

我的做法是:用 CubeMX 生成工程骨架和初始化代码,然后自己动手改写 PHY 驱动层,把 LAN8742 的底包换成 LAN8720,或者至少修改 PHY 地址和相关寄存器访问代码。后面第 3 章会详细说这一步怎么操作。

1.3 FreeRTOS 和 LwIP 到底怎么配合

简单说,LwIP 是一个独立于操作系统的 TCP/IP 协议栈,它可以跑在裸机上(以轮询方式驱动),也可以跑在 RTOS 上。当lwipopts.h里的NO_SYS宏被定义为 0 时,LwIP 就会被编译成带操作系统依赖的版本,此时协议栈内部通过信号量、邮箱、互斥锁这些 OS 原语来保护共享数据。

在 FreeRTOS 环境下,LwIP 会启动一个名叫tcpip_thread的核心线程,这个线程负责处理协议栈里的定时器、TCP 重传、IP 分片、网络接口状态变化等任务。你的业务线程通过 netconn API 或 socket API 与 tcpip_thread 通信,比如你要发一个 TCP 数据包,业务线程把数据丢给 tcpip_thread,由它来实际完成协议封装和网卡发送。

这里有个关键点:ETH 中断服务和 DMA 接收完成回调应当在很短的时间内完成,只负责把数据从硬件搬运到内存池,并通过信号量唤醒 tcpip_thread,真正复杂的协议解析都在 tcpip_thread 里完成。CubeMX 生成的代码已经帮你把ETH_IRQHandler和HAL_ETH_RxCpltCallback这些钩子函数挂好了,你不需要从头写,但要理解这个流程,否则后续排查问题会一头雾水。

2. LAN8720 硬件连接:时钟、复位和地址是三大坑

2.1 LAN8720 管脚速查与典型板级接法

LAN8720 是一颗 QFN-24 封装的百兆 PHY,管脚不多,但每一根都不能接错。先看 RMII 接口下 STM32F407 与 LAN8720 的典型对照接法:

STM32F407 引脚STM32F407 端口功能LAN8720 引脚LAN8720 功能
PA1ETH_RMII_REF_CLKREF_CLK50MHz 参考时钟输出
PA2ETH_RMII_MDIOMDIO管理数据输入输出
PA7ETH_RMII_CRS_DVCRS_DV载波侦听/数据有效
PC1ETH_RMII_MDCMDC管理时钟
PC4ETH_RMII_RXD0RXD0接收数据位 0
PC5ETH_RMII_RXD1RXD1接收数据位 1
PB11ETH_RMII_TX_ENTX_EN发送使能
PB12ETH_RMII_TXD0TXD0发送数据位 0
PB13ETH_RMII_TXD1TXD1发送数据位 1

除开这些数据线,LAN8720 还有几个“命运攸关”的引脚:

  • NRST:硬件复位引脚,低电平有效。一般用 STM32 一个 GPIO 控制,或者接 RC 复位电路。
  • PHYAD0:PHY 地址配置引脚。下拉接地时地址为 0x01,上拉时可能是 0x00(具体取决于内部默认值和外部接法)。大多数模块出厂默认接地,地址是 0x01。
  • INT/REGOFF:这个引脚是复用功能,必须拉低以启用内部 1.2V 稳压器输出。如果这个脚悬空或者被拉高,PHY 可能直接不工作,表现就是 MDIO 读什么都是 0xFF。
  • XI/XO:晶振输入输出脚,接 25MHz 时钟源,下面重点讲。
  • LED0/LED1:网络状态指示,接两个 LED 可以直观看到 Link 状态和收发活动。

在模块化的 LAN8720 板子(比如网上常见的 10Pin 排针小板)上,这些引脚基本都被引出到排针了,你只需要把排针对应接到 STM32F407 的引脚上。但如果自己画板,请务必参照数据手册检查每个引脚的上下拉,尤其不能忽略 REGOFF 这种功能引脚。

2.2 REF_CLK 50MHz 从哪来?时钟接错最隐蔽

这是我见过最多人栽跟头的地方,得掰开了讲。

RMII 模式要求 MAC 和 PHY 共享一个 50MHz 的参考时钟,这是接口规范决定的,STM32F4 内部的 MAC 不会自己产生这个 50MHz。REF_CLK 必须从外部进来,接到 PA1。

但 LAN8720 这颗 PHY 本身非常“聪明”:它内部自带一个 PLL,可以用 25MHz 的晶振输入,在内部倍频到 50MHz,然后通过 REF_CLK 引脚把 50MHz 参考时钟输出给 MCU。这就是绝大多数开发板采用的方案:LAN8720 的 XI/XO 之间接一颗 25MHz 无源晶振,然后 REF_CLK 输出 50MHz 到 STM32 的 PA1。

整个时钟链路按顺序是:25MHz 晶振 → LAN8720 内部 PLL 倍频 → REF_CLK 引脚输出 50MHz → STM32F407 PA1(ETH_RMII_REF_CLK)。

还有一种方案:如果你不想在 LAN8720 旁边放晶振,可以用 STM32F407 的 MCO 引脚输出 25MHz 时钟,接到 LAN8720 的 XI。比如用 PA8(MCO1)输出 HSE 二分频后的 25MHz,LAN8720 内部照常倍频,REF_CLK 依然输出 50MHz。这种做法的好处是省一颗晶振,但代码里必须额外配置 MCO 时钟。

最坑的地方来了。有朋友会以为 STM32F407 的 PA1 可以直接接 25MHz 或者认为 PHY 会把 25MHz 转成 RMII 时钟而不需要 REF_CLK 输出。实测结果是:链接指示灯可能亮、PHY 寄存器也能访问,但 ping 永远不通,或者能收到包但发不出去,因为 MAC 采样的参考时钟频率不对,数据位全都错乱了。

调试时我没有示波器怎么办?有一个笨办法:先确认 PHY 是否正常工作的前提下(PHY ID 能读出来、Link 状态是 up),你还收不到任何数据,大概率就是 REF_CLK 没进来或者频率不对。这时候重点检查 PA1 这一路信号,看看板子上的 LAN8720 模块是否把 REF_CLK 引出来了,以及 25MHz 晶振是否起振。用万用表量晶振引脚电压能粗略判断是否起振,但最可靠的还是拿示波器量一下 PA1 有没有 50MHz。

2.3 PHY 地址、软复位和模式引脚

PHY 地址是 STM32 通过 MDIO/MDC 总线访问 PHY 寄存器时的“设备地址”,标准的 MDIO 协议里地址范围是 0~31。LAN8720 的 PHYAD0 引脚决定它的 SMI 地址。

这里有一个巨大无比的历史坑:ST 官方探索板板载的是 LAN8742,地址默认为 0x00,而市面上买到的单个 LAN8720 模块,PHYAD0 默认被拉低,地址为0x01。CubeMX 的 PHY 下拉列表里没有 LAN8720,只有 LAN8742,很多人就直接选了 LAN8742,生成的代码里LAN8742_PHY_ADDR宏是 0x00,结果 MDIO 总线在地址 0x00 上读出了 0xFF 0xFF,于是开始疯狂怀疑硬件。

解决方式很简单:把 PHY 地址宏改成 0x01,或者直接换用 LAN8720 的驱动代码。

复位也有讲究。LAN8720 硬件复位至少需要把 NRST 拉低一段时间(数据手册建议几十微秒到几毫秒),然后拉高,再等待 PHY 内部初始化完成。我在代码里通常的做法是:

HAL_GPIO_WritePin(PHY_RESET_GPIO_Port, PHY_RESET_Pin, GPIO_PIN_RESET); HAL_Delay(50); HAL_GPIO_WritePin(PHY_RESET_GPIO_Port, PHY_RESET_Pin, GPIO_PIN_SET); HAL_Delay(200);

拉低 50ms 实际比数据手册要求长很多,但这足够保证 PHY 完全复位,拉高后再等 200ms 让 PHY 内部 PLL 锁定和自协商启动。不要嫌这个延时太长,初始化阶段多等几百毫秒在绝大多数嵌入式系统里都能接受。

除了硬件复位,PHY 还支持软件复位。方法是在寄存器 0(BMCR)的 bit15 写 1,然后轮询等待该位自动清零。软件复位在一些需要“热重启 PHY”的场景下很有用,排查问题也经常用,后面第 4 章会给出完整函数。

3. CubeMX 配置与关键代码落地

3.1 时钟树和 ETH 外设配置

在 CubeMX 里的操作顺序我建议这样来,能少走不少弯路。

第一步,配置时钟树。这里以 STM32F407VET6 为例,外部晶振 HSE = 8MHz,系统时钟跑到 168MHz,APB1 分频系数为 4 得到 42MHz,APB2 分频系数为 2 得到 84MHz。ETH 外设的时钟挂在 AHB1 上,所以 AHB1 不分频,也就是 168MHz。这些在 Clock Configuration 页面里鼠标点一下就能配好,需要注意的就是别为了省电把 AHB 分频搞太大,否则 MAC 和 DMA 的时钟不够用。

第二步,在左侧 Categories 里勾选Connectivity -> ETH,Mode 选择RMII。CubeMX 会自动把前面表格里那些 GPIO 复用关系配好,不再需要手动去折腾寄存器。

第三步,在 ETH 配置里要留意一个叫PHY Address的参数,把它从默认的 0 改成 1。不同 CubeMX 版本这个参数的显示位置可能略有差异,有的在 ETH 配置的Advanced Parameters里,有的直接可见。改完以后,生成的eth句柄结构体里heth.Init.PhyAddress就是 1,后续 HAL 库用这个地址访问 PHY 寄存器。

第四步,如果你决定用 MCO 输出 25MHz 给 LAN8720,需要额外配置RCC -> MCO。我建议新手一开始直接用板上 25MHz 晶振方案,让 MCO 这层先别碰,减少变量。

配置完后先不急着加入 LwIP,可以先编译一次,确保 ETH 底层初始化没有大问题。然后再回到 CubeMX,在Middleware -> LWIP里把 LwIP 打开,Mode 选择Raw API或者Netconn API都行。如果你的业务比较简单,我建议选 Netconn API,写起来更接近 socket 编程,逻辑更清晰。

3.2 LwIP 与 FreeRTOS 参数调整

CubeMX 生成 LwIP 工程后,lwipopts.h里默认的参数偏向“能工作”而不是“好用”,你需要按实际需求调整。

我最先动的是内存相关参数。STM32F407 有 192KB SRAM,FreeRTOS 自己也要占堆,所以 LwIP 的内存不能给得太小,否则 TCP 窗口一大就会内存不足。我的经验值:

#define MEM_ALIGNMENT 4 #define MEM_SIZE (40 * 1024) #define PBUF_POOL_SIZE 16 #define PBUF_POOL_BUFSIZE 1518 #define TCP_MSS 1460 #define TCP_WND (4 * TCP_MSS) #define TCP_SND_BUF (4 * TCP_MSS)

MEM_SIZE是 LwIP 动态内存池的总大小,默认可能只有几千字节,太少了。我调到 40KB 是基于 F407 的资源来考虑的,如果你用的是 F429 甚至 H7,可以再加大。PBUF_POOL_SIZE是接收缓冲区池大小,调大一些能防止突发流量下丢包。

NO_SYS这个宏必须为 0。如果你到 lwipopts.h 里看到NO_SYS 1,那说明 LwIP 被编译成了裸机模式,不会创建 tcpip_thread,一定要改过来。

FreeRTOS 这边,CubeMX 生成的代码会自动处理tcpip_thread和default_task的创建,你不用自己动手。但建议检查一下任务的优先级和栈大小:tcpip_thread优先级不宜太高,如果它抢占得太狠,你的业务任务可能饿死;但也不能太低,否则网络事件响应慢。我用默认优先级一般没问题,任务栈给它 512 或 1024 字节,看编译后的链接情况再调。

还有一点容易被忽略:HAL 库本身也依赖一个时基(timebase),默认用 SysTick。但 FreeRTOS 也占用 SysTick,两者会冲突。在 CubeMX 的SYS配置里,把Timebase Source从SysTick改成TIM7之类的外设定时器。这一步不做,FreeRTOS 跑起来会非常诡异。

3.3 替换 PHY 驱动:把 LAN8742 改成 LAN8720

CubeMX 生成工程时如果选了 LAN8742,会生成lan8742.c和lan8742.h。这些代码在ethernetif.c里被调用,完成 PHY 的复位、初始化、链路状态获取。LAN8742 和 LAN8720 底层寄存器基本兼容,但 PHY 地址不一样,这是需要处理的第一个差异。

最省事的做法是,直接编辑lan8742.c里的 PHY 地址宏:

// 原来 #define LAN8742_PHY_ADDR ((uint8_t)0x00U) // 改成 #define LAN8742_PHY_ADDR ((uint8_t)0x01U)

改完以后,大部分功能其实已经能跑了。但我在实际项目里还是建议你换成 LAN8720 的专用驱动,或者自己写一个精简版。原因有两个:第一,LAN8742 驱动里针对该芯片做的寄存器配置序列与 LAN8720 并不完全一致,虽然很多值能通用,但不如原生驱动放心;第二,如果后续 PHY 芯片换品牌(比如 DP83848),你必须自己掌握底层逻辑。

下面是一个简化的 PHY 底层操作代码,包括读取 PHY ID、软件复位、获取链路状态:

#define PHY_BMCR_REG 0x00U #define PHY_BMSR_REG 0x01U #define PHY_ID1_REG 0x02U #define PHY_ID2_REG 0x03U #define PHY_BMCR_RESET (1U << 15) #define PHY_BMSR_LINK_STATUS (1U << 2) uint8_t lan8720_Init(void) { uint32_t id1 = 0, id2 = 0; // 硬件复位 HAL_GPIO_WritePin(PHY_RESET_GPIO_Port, PHY_RESET_Pin, GPIO_PIN_RESET); HAL_Delay(50); HAL_GPIO_WritePin(PHY_RESET_GPIO_Port, PHY_RESET_Pin, GPIO_PIN_SET); HAL_Delay(200); // 读取 PHY ID,确认芯片在线 HAL_ETH_ReadPHYRegister(&heth, PHY_ID1_REG, &id1); HAL_ETH_ReadPHYRegister(&heth, PHY_ID2_REG, &id2); if (id1 != 0x0007U || (id2 & 0xFFF0U) != 0xA120U) { return 1; // PHY 不在线或不是 LAN8720 } // 软件复位 HAL_ETH_WritePHYRegister(&heth, PHY_BMCR_REG, PHY_BMCR_RESET); uint32_t bmcr = 1; while ((bmcr & PHY_BMCR_RESET) != 0U) { HAL_ETH_ReadPHYRegister(&heth, PHY_BMCR_REG, &bmcr); } return 0; }

这段代码里的PHY_RESET_GPIO_Port、PHY_RESET_Pin、heth都要根据你实际工程里的符号名替换。其中HAL_ETH_ReadPHYRegister和HAL_ETH_WritePHYRegister是 STM32 HAL 库自带的接口,不需要自己实现。

还有一点:ethernetif.c里的low_level_init函数需要在初始化的时候调用 PHY 驱动去读取链路状态,并把这个状态传给netif。如果 PHY 驱动写得不完善,会出现netif的网络状态一直不对,导致 LwIP 认为网线没插。所以务必确认ethernetif.c里调用 PHY 驱动的位置能正确拿到 Link 状态。

3.4 一个最小可用的 TCP Server Demo

驱动层打通以后,验证协议栈最直接的方式是跑一个简单的 TCP Server。我用 netconn API 写了一个最小可用的例程,在 FreeRTOS 的任务里直接跑,逻辑很清晰,适合初学者照着改:

#include "lwip/netconn.h" static void tcp_server_thread(void *argument) { struct netconn *conn = netconn_new(NETCONN_TCP); if (conn == NULL) { return; } netconn_bind(conn, IP_ADDR_ANY, 8080); netconn_listen(conn); struct netconn *client; struct netbuf *buf; for (;;) { err_t err = netconn_accept(conn, &client); if (err != ERR_OK) { continue; } while (netconn_recv(client, &buf) == ERR_OK) { void *data; u16_t len; netbuf_data(buf, &data, &len); // 这里处理收到的数据,比如回显或者解析指令 netconn_write(client, data, len, NETCONN_COPY); netbuf_delete(buf); } netconn_close(client); netconn_delete(client); } }

创建任务的方式取决于你用什么方式管理任务。CubeMX 生成的 FreeRTOS 代码里可以直接在MX_FREERTOS_Init里添加osThreadNew;如果是手工工程,用xTaskCreate也一样。需要注意 netconn API 必须在 tcpip_thread 启动之后才能正常工作,所以任务创建顺序要放在协议栈初始化完成之后,CubeMX 生成环境下一般不会有问题。

这个 TCP Server 监听 8080 端口,收到什么就回什么。板子启动后,你先 ping 通,再用网络调试助手连 8080 端口发消息,能回显就算链路线、协议栈、PHY 驱动全通了。

4. 验证方法与坑位排查实录

4.1 先确认 PHY 层:Link 状态和 PHY ID

我一直主张一个原则:网络问题必须从底往上查。不要一上来就 ping 网关,先确认 PHY 这一层有没有正常工作。

最直接的手段就是读 PHY 寄存器。前面 3.3 的代码里读 PHY ID 就是一个很好的自检手段,如果 ID1 读到 0x0007、ID2 读到 0xA120,说明 MDIO/MDC 通信正常,PHY 芯片在线且供电正常。

第二步看 Link 状态。读寄存器 1(BMSR)的 bit2:

uint32_t bmsr = 0; HAL_ETH_ReadPHYRegister(&heth, PHY_BMSR_REG, &bmsr); if (bmsr & PHY_BMSR_LINK_STATUS) { // 链路已建立 } else { // 链路未建立,检查网线和对端设备 }

注意,Link 状态位在 BMSR 寄存器里是个只读状态位,如果读出来是 0,先别怪软件。拿根网线把板子接到路由器或者电脑上,看 LAN8720 模块上的 LED 有没有亮。LED 不亮,基本可以确定是硬件链路问题。常见原因:网线不通、对端网口没起来、PHY 没复位成功、REGOFF 引脚接错导致内部稳压器关闭。

4.2 ping 不通的排查顺序

先说结论:我在调试过程中遇到的 ping 不通,八成都不是协议栈问题,而是底层没跑通。按这个顺序排查,效率最高:

第一,确认 PHY ID 能正确读出来。读出来是 0xFFFF 或者 0x0000,基本是 PHY 地址不对、复位引脚没拉高、MDIO/MDC 引脚复用错误。这时候检查heth.Init.PhyAddress是不是 1,检查 CubeMX 里的 ETH 引脚配置,检查 PHY 的供电脚和 REGOFF 引脚。

第二,确认 REF_CLK 是 50MHz。用示波器量 PA1 引脚。没有示波器的话,至少用逻辑分析仪或者万用表测一下 PA1 的直流电平,有 50MHz 时钟时这里会有明显的 DC 偏置。如果完全没波形,查 LAN8720 的 25MHz 晶振是否起振,查 REF_CLK 引脚有没有接出来。

第三,确认 PHY 的 Link 状态。PHY 没协商成功,MAC 收不到数据,ping 自然不通。如果 Link 状态是 up,再看下一步。

第四,确认 LwIP 的网络接口状态。在代码里可以周期性检查netif_is_link_up(&gnetif),如果为 0,说明ethernetif.c里的链路检测逻辑没把状态更新到 LwIP。这个细节很多人会漏,比如 PHY 驱动里的get_link_status函数返回类型或者位判断写错,导致 LwIP 一直认为链路断开。

第五,检查 IP 配置。板子的 IP 地址、子网掩码、网关和电脑要在一个网段。最稳妥的做法是先用静态 IP,把 DHCP 关掉。电脑端要关掉防火墙,或者放行 ICMP 协议。不少新手在这里卡住,觉得代码没问题,其实是 Windows 防火墙把 ping 请求拦了。

最后,如果以上都没问题还 ping 不通,用 Wireshark 抓包看一下板子有没有回 ARP 请求。板子回 ARP,说明二层是通的,问题可能在 IP 层;连 ARP 都不回,问题基本还在更底层。

4.3 TCP 连接不稳定的原因分析

ping 通了之后,TCP 连接不稳定是另一个高频问题。现象通常是:TCP 能建立连接,但传输大文件时会断;或者连接建立后发送数据,对端收不到完整数据;又或者过一段时间应用层就无响应。

这里最隐蔽的坑其实是内存不足。LwIP 的 TCP 协议栈发送数据时需要分配内存存放分段,TCP_SND_BUF决定了单条 TCP 连接的发送缓冲区大小。如果这个值太小,比如只有 2K,而你要一次发送 10K 数据,发送缓冲区就爆了,协议栈会返回内存不足错误。

还有一个容易被忽略的点:DMA 描述符数量不够。STM32F4 的 ETH DMA 使用描述符环形队列,CubeMX 生成代码里默认定义了一个ETH_RX_DESC_CNT和ETH_TX_DESC_CNT。如果板子在高速接收数据时出现 HALT 或者溢出错误,可以适当增加接收描述符数量。但注意每增加一个描述符都会多占 RAM,需要权衡。

另外要注意网线质量。百兆以太网要求网线是 4 对 8 芯的,如果是早期那种只有 4 芯的百兆线,虽然也能通,但信号质量在稍长距离上就会变差,表现出来就是 ping 偶发超时、TCP 重传频繁。我做调试时换过一根质量差的网线,整整排查了两天才发现是线的问题,从那以后我调试网络项目都会先换一根新网线排除干扰。

4.4 常见问题速查表

把我在这个项目里遇到过的典型问题整理成一张表,方便你对照排查:

现象可能原因解决办法
MDIO 读 PHY ID 全是 0xFFFFPHY 地址不对 / PHY 未上电 / REGOFF 悬空改PhyAddress为 1;检查 PHY 供电;REGOFF 拉低
PHY ID 能读,但 Link 始终 down网线问题 / 对端没启动 / PHY 未正确复位换网线;查对端设备;加大复位延时
能 link 但 ping 不通REF_CLK 不是 50MHz / MAC 时钟异常用示波器量 PA1;查 25MHz 晶振和 REF_CLK 连接
ping 偶尔通偶尔超时网线质量差 / DMA 描述符不足 / 内存池太小换网线;加大ETH_RX_DESC_CNT;调大MEM_SIZE
TCP 连接建立但发不出去数据TCP_SND_BUF 太小 / 内存耗尽增大TCP_SND_BUF和MEM_SIZE
FreeRTOS 任务不调度SysTick 被 HAL 占用冲突在 CubeMX 里把 HAL 时基改为 TIM7
系统跑一段时间后 HardFault任务栈溢出 / LWIP 内存访问越界调大任务栈;检查heap_4.c堆大小;增加栈溢出检测

最后说点我的实际感受

这套东西我做过不止一次,从最早在野火指南者上跑通,到后来在公司产品上用 STM32F407 + LAN8720 做远程升级功能,中间踩过的坑基本都写在上面了。要说最重要的心得,就是别急着写业务代码,一定要先确保 PHY 能被稳定地访问,Link 状态和 PHY ID 能读出来,再往上层走。很多人一上来就搜“LwIP ping 不通”,其实问题压根不在 LwIP,而在 MDIO 总线上那颗 PHY 就没被正确识别。

另外一个小建议:调试网络时,无论你用 Keil 还是 IAR,都可以把ethernetif.c里关键函数加上调试输出,通过串口把 PHY ID、Link 状态、DMA 错误标记打出来,比盯着调试器变量窗口直观得多。如果串口输出正常而网络依然异常,再集中排查硬件连线。

这套组合的灵活性在于,掌握了 LAN8720 的驱动方式,换成 RTL8201、DP83848 等 PHY 也是类似的思路,无非是寄存器地址、PHY ID、自协商配置不同。等你把底层打通一次,后面再做网络相关的功能,基本就是写应用层的事了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 3:35:17

STM32 OTA固件CRC校验失败?用srec_cat精准生成物理镜像

1. 为什么STM32 OTA升级总在CRC校验这一步“卡住”&#xff1f;——从srec_cat切入的真实产线痛点你是不是也遇到过这样的场景&#xff1a;固件烧录到STM32板子上能跑&#xff0c;但一走OTA流程&#xff0c;bootloader就报“CRC mismatch”然后直接跳回DFU模式&#xff1f;串口…

作者头像 李华
网站建设 2026/10/3 3:34:38

机器学习理论全自动验证:从手写证明到机器检查的范式革命

前几天刷到“华威大学首次实现机器学习理论的全自动验证”这条消息时&#xff0c;我的第一反应并不是“好厉害”&#xff0c;而是“这事终于有人做成体系了”。在机器学习这个圈子里待久了你会发现一个很微妙的矛盾&#xff1a;理论论文的产出速度越来越快&#xff0c;但审稿人…

作者头像 李华
网站建设 2026/10/3 3:33:42

MySQL表连接查询:从笛卡尔积到索引优化,内外连接实战解析

在MySQL里摸爬滚打这些年&#xff0c;我越来越觉得表的内外连接是SQL查询里最值得花时间吃透的一个点。不管是写业务报表、做数据汇总&#xff0c;还是优化接口响应速度&#xff0c;JOIN几乎无处不在。很多人刚开始学的时候&#xff0c;能把INNER JOIN和LEFT JOIN的语法背下来&…

作者头像 李华
网站建设 2026/10/3 3:33:25

MVDR与MMSE自适应波束形成:原理、工程实现与调试

简介&#xff1a;面向无线通信和声学信号处理研究者的自适应波束形成MATLAB源码包&#xff0c;聚焦最小均方误差&#xff08;MMSE&#xff09;与最小方差无失真响应&#xff08;MVDR&#xff09;两类经典算法&#xff0c;并给出二者结合的实现思路。压缩包共7个文件&#xff0c…

作者头像 李华
网站建设 2026/10/3 3:33:24

MySQL索引设计避坑指南:从原理到实践的注意事项

1. 索引设计前&#xff0c;先想清楚这几个问题做 MySQL 开发或者 DBA 的朋友应该都有体会&#xff0c;索引这东西用好了是神器&#xff0c;用不好就是定时炸弹。面试题里问"建索引有哪些注意事项"&#xff0c;看起来是个基础题&#xff0c;但真正能把这个问题讲透的人…

作者头像 李华
网站建设 2026/10/3 3:33:23

PyQt5机器学习预测系统实战:多模型房价预测与GUI可视化

简介&#xff1a;这是一份基于Python的机器学习预测系统合集&#xff0c;内置图形化操作界面&#xff0c;覆盖贝叶斯网络、马尔科夫模型、线性回归、岭回归、多项式回归、决策树回归及深度神经网络等主流算法&#xff0c;适合课程设计、毕业设计以及希望快速上手完整预测流程的…

作者头像 李华