news 2026/10/26 5:05:47

STM32+LAN8720A+LwIP裸机以太网ping不通排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32+LAN8720A+LwIP裸机以太网ping不通排查

简介:面向STM32F429IGT6以太网开发学习者,提供基于HAL库、无操作系统、LAN8720A PHY与LWIP 2.1.2的PING参考工程。工程用RMII连接ETH外设与PHY,以SMI读写寄存器,配套CubeMX 6.6.1、HAL 1.27.1和MDK-ARM 5.27,便于理解网络初始化、协议栈移植与连通性测试。压缩包1300个文件、约31.16MB,以678个C源文件和450个头文件为主,含汇编、链接、说明、库及工程配置,目录完整,可直接编译验证并二次裁剪。已有1989人学习下载,适合作为STM32以太网与LWIP入门调试实例。

1. 网口灯亮着却 ping 不通:一套 STM32 + LAN8720A + LwIP 裸机以太网工程的落地全流程

手上这块自制的 STM32F407 板子,LAN8720A 焊上去,网线一插,绿灯常亮、黄灯偶尔闪,看着一切正常,电脑上ping 192.168.1.10却全是请求超时。这类问题在 stm32 c语言 的以太网入门里特别典型:硬件没坏,代码也能编译,卡住的往往是自己脑子里那套“顺序”。这里要拆开的是一套基于 HAL 库、LAN8720A 做 PHY、LwIP 跑在无操作系统模式下的以太网外设工程——它要解决的事情很单纯:让板子拿到一个静态 IP,能被同网段的电脑 ping 通,并且稳定地一直通。适合已经会点灯、会串口,第一次碰 ETH 外设的人,也适合那些“能通一次但重启就废”的老手回头对一遍参数。

2. 硬件链路先对齐:RMII 九根线、50MHz REF_CLK 与 PHY 地址的三处关键点

以太网这东西跟串口不一样,串口接反了最多收不到数据,以太网接错了,MAC 和 PHY 谁都不报错,你只能看着网口灯发愣。所以在动 CubeMX 之前,得先把三件事确认死:接口模式、参考时钟来源、PHY 的 SMI 地址。这三件事任何一件错了,后面 LwIP 配得再漂亮都是白搭。

2.1 RMII 与 MII 的取舍:不是选择题,是 LAN8720A 只给你一条路

STM32 的 ETH 外设同时支持 MII 和 RMII 两种媒体接口。MII 是老标准,收发各 4 根数据线,加上时钟、使能、错误信号,一组下来十六七根引脚;RMII 是精简版,数据线砍到各 2 根,时钟只要一根 50MHz 的 REF_CLK,总共 9 根。LAN8720A 本身只出 RMII 接口,所以这里根本没有“选哪个更好”的余地——主机侧必须配成 RMII。

我一般用 STM32F407 这套引脚分配,几乎成了肌肉记忆:

信号引脚方向说明
REF_CLKPA1输入50MHz 参考时钟,来自 PHY 的 REFCLKO
MDIOPA2双向SMI 数据线,需要 1.5k~2.2k 上拉
CRS_DVPA7输入载波侦听/数据有效
MDCPC1输出SMI 时钟,由 HCLK 分频得到
RXD0PC4输入接收数据位 0
RXD1PC5输入接收数据位 1
TX_ENPB11输出发送使能
TXD0PB12输出发送数据位 0
TXD1PB13输出发送数据位 1

注意表中 PA1 的方向是“输入”。这是 RMII 最容易翻车的地方:很多人下意识以为时钟是 MCU 给 PHY 的,实际在 25MHz 晶振方案里恰好相反,是 PHY 把 25MHz 倍频成 50MHz 之后送给 MCU。搞反了方向,CubeMX 里引脚模式就配错,现象是 ETH 初始化能过、MDIO 能读到 PHY ID,但一包数据都收不上来。

另外 MDIO 的上拉电阻别省。有些板子为了省事把 MDIO 直接怼到 3.3V,靠 MCU 内部弱上拉撑着,短距离调试能过,一旦线长一点或者旁边有开关电源,MDIO 读 PHY 寄存器就会间歇性返回 0xFFFF,表现出来就是“有时候能通、有时候初始化直接进 Error_Handler”。

2.2 REF_CLK 从哪来:25MHz 晶振、50MHz 有源晶振、MCO 输出三种方案

板上 50MHz 参考时钟的来源,直接决定了 CubeMX 里怎么配、以及出问题时该拿示波器量哪个点。常见做法是这三种:

第一种,LAN8720A 旁边挂一颗 25MHz 无源晶振,PHY 内部 PLL 倍频后从 nINT/REFCLKO 引脚输出 50MHz,走线到 MCU 的 PA1。这是最主流也最省成本的方案,缺点是那颗 25MHz 晶振的负载电容匹配不好时,REFCLKO 会不起振或者频率偏移,表现是 ETH 初始化偶尔失败。

第二种,板上放一颗 50MHz 有源晶振,输出同时喂给 PHY 的 XTAL1/CLKIN 和 MCU 的 PA1。这种方案最稳,因为时钟是同一个源,不存在两边频偏的问题,代价是多一颗器件。用这种方案时,PHY 的 nINT/REFCLKO 引脚就退化成普通中断脚,不再是时钟输出。

第三种,用 MCU 的 MCO 引脚输出 50MHz 给 PHY。我不推荐,MCO 是从 HCLK 分频出来的,抖动比专用晶振大,短时间 ping 看不出来,跑长时间大流量传输时误码率会明显上升。

判断手上板子属于哪一种,方法很土但有效:先看板子上有没有 25MHz 的贴片晶振(一般标着 25.000),有就是第一种;没有晶振但有一颗四脚的有源振荡器,就是第二种。然后拿示波器或者哪怕一个便宜的频率计,量 PA1 上有没有 50MHz、幅度是不是 3.3V 左右。量不到波形,后面所有软件调试都是浪费时间。

提示:如果量到 PA1 上是 25MHz 而不是 50MHz,说明 PHY 的 REFCLKO 分频配置不对或者晶振方案搞错了,这种情况读 PHY 寄存器可能是正常的,但 MAC 侧收发一定不正常。

2.3 PHY 地址与复位:0x00 还是 0x01 不是猜出来的

LAN8720A 的 SMI 地址由 PHYAD0 引脚在上电复位时的电平决定:下拉到地就是 0x00,上拉到 VDD 就是 0x01。很多原理图里这个引脚被做成了可选焊盘,焊不焊决定了地址,时间一长没人记得当初焊没焊,代码里写死 0x00 就开始玄学了。

与其猜,不如扫一遍。PHY 的寄存器 2 和 3 是 PHY Identifier,LAN8720A 的固定值是 0x0007 和 0xC0F1,扫到哪个地址读出来是这对值,那就是它。

/* 遍历 SMI 地址 0~31,用 PHY ID 反查实际地址 */ static uint8_t phy_scan_address(ETH_HandleTypeDef *heth) { uint32_t id1 = 0, id2 = 0; for (uint8_t addr = 0; addr < 32; addr++) { /* 读不到就跳过,总线上没器件时 MDIO 会返回全 1 */ if (HAL_ETH_ReadPHYRegister(heth, addr, 0x02, &id1) != HAL_OK) continue; if (HAL_ETH_ReadPHYRegister(heth, addr, 0x03, &id2) != HAL_OK) continue; if ((id1 & 0xFFFF) == 0x0007 && (id2 & 0xFFFF) == 0xC0F1) return addr; } return 0xFF; /* 一个都没扫到,先查 MDIO 上拉和 MDC 分频 */ }

这段逻辑的关键在于两点。一是每次读之前不清缓存,直接依赖 HAL 的返回值判断——HAL_ETH_ReadPHYRegister内部有超时,总线挂死时会返回HAL_TIMEOUT,比读出一个假值更好判断。二是 ID2 的低 4 位是 revision,不同批次的片子会变,所以只比高 12 位更稳妥;我这里图省事直接全字比较,实际项目里建议写成掩码比较。

顺带说复位。LAN8720A 的 BCR(寄存器 0)bit15 是软复位位,写 1 之后芯片自己会在几十毫秒内清零。我见过有人在写完复位后立刻去读 BSR 判断链路,结果读回来全是 0,以为 PHY 坏了。正确姿势是写完复位后延时 100ms 以上,再轮询 BCR 直到 bit15 归零,然后重新触发一次自协商。

3. CubeMX 到 HAL:ETH 外设初始化与 LwIP 无 OS 移植的四步启动链

硬件确认完,接下来是把 CubeMX 生成的那堆文件理顺。无操作系统模式下的 LwIP 跟跑在 FreeRTOS 上的完全是两个东西,最核心的差别是:没有 tcpip 线程,所有的协议栈处理、定时器超时、ARP 老化,全靠你在主循环里一遍一遍地喂。理解这一点,后面很多“ping 通几包就死”的问题就都有解释了。

3.1 CubeMX 里真正影响 ping 的那几项

CubeMX 里 ETH 和 LwIP 的配置项加起来上百个,但真正决定“能不能 ping 通”的其实就下面这些,其余保持默认即可。

配置位置选项建议值影响
ETH 基本参数Auto NegotiationEnable关掉后必须手动指定速率双工
ETH 基本参数PHY Address0 或 1按 2.3 节扫出来的结果填
ETH 基本参数Media InterfaceRMII配成 MII 直接不通
ETH 高级参数Rx Descriptor / Tx Descriptor4 / 4太少容易丢包,太多占 RAM
ETH 高级参数Checksum ModeBy hardware与 lwipopts 里的 CHECKSUM 开关必须配套
ETH 中断ETH global interruptEnable轮询模式可关,中断模式必须开
LwIPNO_SYS1无操作系统模式的核心开关
LwIPLWIP_DHCP0先用静态 IP 排除 DHCP 干扰
LwIPLWIP_ICMP1关掉就永远不会回 ping
LwIPMEM_SIZE起手 4~8KB太小会在 pbuf 分配时返回 NULL
LwIPPBUF_POOL_SIZE8~16收包缓冲池,跟描述符数量配套

这里有一个容易被忽略的联动:Checksum Mode选 By hardware 之后,CubeMX 会在 lwipopts.h 里把CHECKSUM_BY_HARDWARE置 1,同时把CHECKSUM_GEN_IP、CHECKSUM_GEN_UDP、CHECKSUM_GEN_TCP、CHECKSUM_GEN_ICMP全部置 0。这意味着软件侧不再计算任何校验和,全靠 ETH 外设在发送描述符里填。如果描述符里的 CIC 位没配好,发出去的 ICMP 回包校验和是 0,电脑的协议栈直接丢弃,现象就是“板子收到了 request,也发了 reply,但 ping 就是不通”。这个坑放到第 5 章细说。

3.2 MX_ETH_Init 与 MX_LWIP_Init 的调用顺序不能反

生成的 main 函数骨架,顺序必须是先 ETH 后 LwIP:

int main(void) { HAL_Init(); SystemClock_Config(); /* HCLK 拉到 168MHz,MDC 分频才够用 */ MX_GPIO_Init(); MX_ETH_Init(); /* 1. 配 MAC 参数、描述符、缓冲区 */ MX_LWIP_Init(); /* 2. 建 netif、挂网卡、设 IP */ while (1) { MX_LWIP_Process(); /* 3. 收包 + 协议栈定时器 */ phy_link_poll(); /* 4. 链路状态巡检,见 4.2 */ } }

MX_ETH_Init()里干的事情是填heth.Init这一坨结构体然后调HAL_ETH_Init:

heth.Instance = ETH; heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE; heth.Init.Speed = ETH_SPEED_100M; /* 自协商成功后会被覆盖 */ heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX; heth.Init.PhyAddress = LAN8720A_PHY_ADDR; /* 2.3 节扫出来的 */ heth.Init.MACAddr = (uint8_t *)g_mac; /* 6 字节,全网唯一 */ heth.Init.RxMode = ETH_RXINTERRUPT_MODE; /* 中断收包 */ heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII; heth.Init.RxDesc = DMARxDscrTab; /* 描述符必须是 4 字节对齐的全局数组 */ heth.Init.TxDesc = DMATxDscrTab; heth.Init.RxBuffLen = ETH_MAX_PACKET_SIZE; /* 1524,够一个标准以太帧 */

参数里最值得盯的是三个:PhyAddress填错会直接初始化失败;MACAddr必须全局唯一,两台板子用同一个 MAC 在同一个交换机下会互相抢包,现象非常诡异;RxBuffLen小于 1524 时,大于该长度的帧会被硬件截断,ARP 和 ICMP 这种小包没事,一跑 TCP 大包就出问题。

MX_LWIP_Init()则负责把 LwIP 的 netif 挂上去。无 OS 模式下它并不会创建任何线程,本质就是几次函数调用:

struct netif gnetif; ip4_addr_t ipaddr, netmask, gw; IP_ADDR4(&ipaddr, 192, 168, 1, 10); IP_ADDR4(&netmask, 255, 255, 255, 0); IP_ADDR4(&gw, 192, 168, 1, 1); netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, &ethernetif_init, &ethernet_input); netif_set_default(&gnetif); netif_set_up(&gnetif);

ethernetif_init是 ST 提供的网卡驱动入口,它会把low_level_init、low_level_output这些底层钩子挂到 netif 上。需要注意的是,不同版本的 CubeMX 固件包生成的ethernetif.c差别挺大:老版本把HAL_ETH_Init塞在low_level_init()里面,新版本挪到了MX_ETH_Init()里,ethernetif只负责描述符和缓冲区的初始化。如果照抄别人的工程,很容易出现HAL_ETH_Init被调两次,第二次返回HAL_ERROR,然后进了 Error_Handler 死循环。稳妥的做法是打开自己工程里的ethernetif.c,搜一下HAL_ETH_Init到底在哪个函数里被调。

3.3 主循环里必须喂的两件事:收包和协议栈定时器

MX_LWIP_Process()里其实就两句,但这两句是无 OS 模式的命脉:

void MX_LWIP_Process(void) { ethernetif_input(&gnetif); /* 从 DMA 描述符里取帧,交给 LwIP */ sys_check_timeouts(); /* 驱动 LwIP 内部所有定时器 */ }

ethernetif_input会检查接收描述符的 OWN 位,有帧就low_level_input取出来,剥掉以太网头交给ethernet_input,最后进到 ARP 或 ICMP 处理。sys_check_timeouts则负责 ARP 表老化(默认 5 分钟一轮)、IP 分片重组超时、TCP 重传定时这些。两个都是“不调用就不工作”的类型。

血泪经验是:主循环里千万别放大延时。见过不少人在 while 里加个HAL_Delay(1000)去闪灯或者读传感器,结果 ARP 表项老化后不再重建,ping 通几十秒就断,断了再也回不来。如果确实有慢速任务,要么改成状态机分片,要么把延时压到 10ms 以内。另外,如果开了ETH_RXINTERRUPT_MODE,接收完成中断回调里只置一个标志位就够了,真正的ethernetif_input放到主循环跑——在中断里做 pbuf 分配和 memcpy,栈不够深的时候会直接 HardFault,而且极难查。

4. 静态 IP 到第一包 ICMP:让 ping 通起来的最小可复现路径

到这一步,硬件和初始化链路都对齐了,接下来是最有成就感的部分:让电脑上那行ping从“请求超时”变成“来自 192.168.1.10 的回复”。这个过程我一般按“网络层参数 → 链路层状态 → 抓包验证”三层往下走,每一层都有明确的判据,不靠猜。

4.1 IP、掩码、网关:最容易错的是电脑那一侧

板子侧的三个参数必须和电脑在同一个网段,这个道理谁都懂,但实际翻车的往往是电脑侧。典型场景是笔记本同时连着 WiFi 和网线,WiFi 拿到 192.168.1.x,网线口配了 192.168.1.100,两个网卡同网段,操作系统按接口跃点数选路,ICMP 请求很可能从 WiFi 出去了,板子当然收不到。

我一般的做法是先把 WiFi 关掉,只留有线,然后按顺序确认:

# Windows 下看网线口的地址和掩码是否正确 ipconfig # 看 ARP 缓存里有没有板子的 MAC,没有说明二层都没通 arp -a # 清掉旧表项,避免换过 IP 之后还拿着老 MAC arp -d 192.168.1.10 # 只发 4 包,先确认能通,再做长跑 ping -n 4 192.168.1.10 # Linux / macOS 下等价命令 ping -c 4 192.168.1.10

板子侧的参数建议就用 192.168.1.10 / 255.255.255.0 / 192.168.1.1 这一组,网关填一个同网段里不存在的地址也无所谓——ping 是二层的,不出网关不影响,但填了能避免以后加 TCP 客户端时路由表缺默认网关。

MAC 地址这块多说一句。ST 的例程里给的是一个固定的00:80:E1:00:00:00之类的值,很多人直接抄。单板调试没问题,一旦实验室里同时上两台,交换机的 MAC 表就会来回翻动,两边都时通时不通。我的习惯是拿 MCU 的 UID 后三个字节拼一个 MAC,保证唯一:

/* 用 96 位唯一 ID 的低 24 位拼一个本地管理的 MAC */ uint8_t g_mac[6]; uint32_t uid0 = HAL_GetUIDw0(); g_mac[0] = 0x02; /* 本地管理、单播 */ g_mac[1] = (uint8_t)(uid0 >> 16); g_mac[2] = (uint8_t)(uid0 >> 8); g_mac[3] = (uint8_t)(uid0); g_mac[4] = 0x00; g_mac[5] = 0x01;

首字节的 bit1 置 1 表示本地管理地址,bit0 保持 0 表示单播,这样不会和厂商 OUI 冲突,也不会被交换机当成组播处理。

4.2 网口灯只是参考,寄存器才是判据

网口的绿灯亮只说明 PHY 检测到了对端的链路信号,不代表自协商成功,更不代表 MAC 侧已经准备好。我见过不少板子灯亮着,实际协商在 10M 半双工,或者自协商根本没完成,这时候发出去的包对端能收到,回来的包因为双工不匹配被大量丢弃。

真正的判据是读 PHY 的 BSR(寄存器 1)。这里有一个反直觉的细节:BSR 的 bit2(Link Status)是 latch-low 的,链路断过一下之后这个位会一直保持 0,直到你读它一次才刷新。也就是说读一次不准,得读两次,第二次才是当前真实状态。

/* 链路巡检:读两次 BSR,第二次的 bit2 才是当前链路状态 */ static void phy_link_poll(void) { uint32_t bsr = 0, scsr = 0; if (HAL_ETH_ReadPHYRegister(&heth, LAN8720A_PHY_ADDR, 0x01, &bsr) != HAL_OK) return; if (HAL_ETH_ReadPHYRegister(&heth, LAN8720A_PHY_ADDR, 0x01, &bsr) != HAL_OK) return; if (bsr & 0x0004) /* bit2 = 1,链路 up */ { /* Reg31 里能读到协商结果,位定义按手上那份手册来 */ HAL_ETH_ReadPHYRegister(&heth, LAN8720A_PHY_ADDR, 0x1F, &scsr); g_link_up = 1; g_link_speed = (scsr & (1U << 2)) ? 100 : 10; /* 示意,按手册核对 */ g_link_duplex = (scsr & (1U << 4)) ? 1 : 0; } else { g_link_up = 0; } }

这段代码里,寄存器 31 的具体位定义不同批次手册略有出入,我上面写的 bit2 判速率、bit4 判双工只是示意,实际项目请翻自己手上那份 LAN8720A 手册的 Reg31 章节核对一遍再落代码,别照抄。真正不会错的是 BSR 的 bit2,这个在 IEEE 802.3 里是标准定义。

把g_link_up打印到串口上,插拔网线看它是不是能跟着变 0/1,这一步过了,说明 MAC 和 PHY 的 SMI 通道完全是通的,剩下的事情都在协议栈里。

4.3 抓包确认:ARP 有没有回,ICMP 有没有回,校验和对不对

板子能 ping 通的那一刻,其实中间发生了三件事:电脑发 ARP 请求问“192.168.1.10 是谁”,板子回 ARP 应答告诉对方自己的 MAC,电脑才把 ICMP echo request 发过来,板子回 echo reply。任何一环断了,你看到的都是“请求超时”,但原因完全不同。所以别只盯着 ping 的结果,抓个包一看便知。

Wireshark 里过滤arp || icmp,然后看三个点:

现象说明往下查
只有 ARP request,没有 reply二层没通,或 IP 配错描述符、Rx 缓冲区、IP 设置
ARP 有来有回,没有 ICMP request电脑侧路由走错网卡关 WiFi、查 route print
有 ICMP request,没有 reply板子收到但没发或发错ICMP 开关、校验和、发送描述符
有 reply,但电脑不认校验和错误或 MAC 不对硬件校验和配置

第四行是最坑的:抓包里明明能看到 echo reply,源 IP、目的 IP 都对,电脑就是不认。把那一帧展开看 IP Header Checksum 或 ICMP Checksum,如果是 0x0000 或者明显不对,基本可以确定是硬件校验和没生效,具体排查见下一章。

5. 避坑排查:ping 不通时按这五条现象逐层往下剥

以太网调试最怕的就是“什么都配了但就是不通”,然后在 CubeMX 里反复改参数。我的习惯是先分类现象,不同现象指向完全不同的层,从物理层往上剥,比瞎改配置快得多。下面这五条是我这些年踩得最狠的。

5.1 现象:网口灯不亮,或者亮了但一直显示 10M

原因:链路层就没建立起来。常见的是 REF_CLK 没到 MCU,或者 PHY 的复位没释放,或者自协商被禁用但速率双工又填错了。还有一种比较少见的,25MHz 晶振的负载电容选大了,起振慢,上电瞬间 PHY 内部状态机乱掉。

解决:先量 PA1 上的 50MHz,量不到就查晶振和 REFCLKO;量得到就用 2.3 节的扫描代码确认 PHY 地址;地址对了还不行,读 BCR 看自协商有没有使能(bit12),读 BSR 看自协商完成位(bit5)。如果自协商一直不完成,把AutoNegotiation关掉,手动指定 100M 全双工试一次——能通说明是对端交换机自协商有问题,不能通说明是本地 PHY 配置问题。

5.2 现象:ARP 能正常应答,ping 就是不通,抓包看到 reply 但电脑丢弃

原因:硬件校验和。lwipopts.h 里CHECKSUM_BY_HARDWARE置 1 之后,CHECKSUM_GEN_IP、CHECKSUM_GEN_ICMP这些全被置 0,所有校验和都指望 ETH 外设在发送时插入。而 ETH 是否插入校验和,取决于发送描述符控制位里的 CIC 字段。如果描述符初始化时没把ETH_TXDESC_IC | ETH_TXDESC_TCPCS | ETH_TXDESC_UDPCS塞进最后一个描述符的 Status,硬件就不会做校验和插入,发出去的包校验和是 0。

解决:两条路。要么在描述符初始化时补齐这几个标志位,让硬件干活;要么干脆把CHECKSUM_BY_HARDWARE改成 0,让软件算,代价是每包多几百个周期的开销,对 ping 这种低频场景完全无所谓。调试阶段我一般先走第二条,通了之后再切回硬件校验和,这样能明确区分是“协议栈问题”还是“硬件卸载问题”。

5.3 现象:ping 通十几包之后彻底断掉,重启才恢复

原因:接收描述符或 pbuf 耗尽。low_level_input从描述符取走帧之后,必须调用pbuf_free释放 pbuf,并且把描述符归还给 DMA(重新置 OWN 位)。如果某条错误分支上直接 return 忘了归还,跑几十包之后所有 Rx 描述符都被占住,DMA 再也收不到新帧。另一种可能是主循环里塞了延时或阻塞操作,sys_check_timeouts长时间不执行,ARP 表项老化后不再重建。

解决:检查low_level_input里所有返回路径,确保pbuf_alloc失败、长度异常这些分支上也把描述符还回去。同时把主循环里的HAL_Delay全部清掉,改成基于HAL_GetTick()的非阻塞状态机。判断是不是描述符耗尽,可以在主循环里打印heth.RxDesc指向的描述符 Status,看 OWN 位是不是一直为 0。

5.4 现象:改过 IP 之后,板子明明在发 ARP,电脑就是不理

原因:电脑的 ARP 缓存里还留着“192.168.1.10 对应旧 MAC”的记录,或者反向,板子改 IP 后电脑还往老地址发。ARP 缓存在 Windows 上默认存活几分钟,改 IP 调试的时候特别容易撞上。

解决:电脑侧执行arp -d 192.168.1.10清表项,或者干脆换一个没用过的 IP 段重新试。板子侧如果要在运行中改 IP,不能只改ipaddr变量,得走netif_set_down→ 改地址 →netif_set_up这一套,否则 netif 内部缓存的地址不会更新,抓包能看到源 IP 还是旧的。

5.5 现象:调试器复位后必须断电重启才能通

原因:调试器的软复位只复位了 MCU,没有复位 PHY。LAN8720A 是独立供电的,MCU 复位期间它一直保持上一次的自协商状态,而 MCU 侧的 MAC 重新初始化后,两边状态机对不上。另外一种情况是 PHY 的复位引脚直接接到了 MCU 的 GPIO 但代码里没拉过,上电时靠 RC 电路延时释放,时序不保证。

解决:在MX_ETH_Init的最开头手动拉一次 PHY 复位脚(如果有的话),拉低 10ms 再拉高,然后延时 100ms 等 PHY 内部就绪,再开始HAL_ETH_Init。没有独立复位脚的板子,用 BCR 的软复位位代替,写完等 bit15 自动清零再往下走。这一步加上之后,调试器反复复位的稳定性会好很多。

6. 把 ping 当仪表用:RTT 统计与描述符、缓冲区的调参边界

ping 通了只是起点,真正能说明这条链路靠不靠谱的,是 RTT 的分布和长时间跑的丢包率。我习惯用ping -n 1000 -l 1024跑一轮,然后看三件事:最小 RTT、平均 RTT、有没有偶发的几百毫秒尖峰。裸机 LwIP 在 168MHz 的 F4 上,最小 RTT 一般在 0.3~0.8ms 之间,如果平均 RTT 超过 3ms,多半是主循环里有别的东西在抢时间;如果有规律的 200ms 级尖峰,八成是 ARP 老化重建或者某个地方在等超时。

参数调优上,我一般只动三个地方,而且每次只动一个,动完跑一轮 1000 包对比:

参数默认值调整方向观察指标
Rx Descriptor 数量4加到 8大流量下的丢包率
PBUF_POOL_SIZE8加到 16pbuf_alloc返回 NULL 的次数
MEM_SIZE4KB加到 8KBTCP 场景下的连接建立成功率

描述符加到 8 之后,如果丢包率没变化,说明瓶颈不在这里,别再加了,白占 RAM。我见过有人一口气加到 32,结果 CC RAM 被吃满,链接直接报 memory overflow。判断pbuf_alloc有没有失败,最土的办法是在low_level_input里加个计数器,跑完一轮打印出来,比看任何日志都直接。

还有一个我自己一直用的自检习惯:在工程里加一个开机自检函数,上电后依次做四件事——扫 PHY 地址、读 BSR 确认链路、发一个 ARP 请求给自己、检查最近一次HAL_ETH_ReadPHYRegister的返回值。四件事全过才点“系统正常”的灯,任何一件失败就把对应的错误码闪出来。这套东西在实验室里看着多余,到了现场调试能省掉大量“到底是板子坏了还是网线坏了”的扯皮。

从那以后我每接一块新的以太网板子,都强制按“量 PA1 的 50MHz → 扫 PHY 地址 → 读两次 BSR 看链路 → 抓包看 ARP 和 ICMP”这个顺序走一遍,一步都不跳。跳过哪一步,后面就一定会在那一步上多花三倍的时间找回来。希望帮到你。

本文还有配套的精品资源,点击获取

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

GDAL像素坐标转地理坐标原理与工业级实操指南

1. 项目概述&#xff1a;为什么像素坐标和地理坐标非得“对上号”做遥感图像处理、GIS空间分析或者无人机测绘的朋友&#xff0c;几乎每天都在跟两个坐标系打交道&#xff1a;一个是图像左上角为原点、向右向下递增的像素坐标系&#xff08;Pixel Coordinate System&#xff09…

作者头像 李华
网站建设 2026/10/26 5:03:55

VirtualBox与openKylin共享目录设置指南

1. 项目概述在开发测试环境中&#xff0c;经常需要在虚拟机和宿主机之间共享文件。VirtualBox作为一款开源的虚拟化软件&#xff0c;配合国产操作系统openKylin使用&#xff0c;能够提供稳定高效的开发环境。本文将详细介绍如何在VirtualBox的openKylin虚拟机中设置共享目录&am…

作者头像 李华
网站建设 2026/10/26 5:03:38

LLM强化学习实战:RLHF优化与工程挑战解析

1. 项目背景与核心挑战去年开始接触LLM后训练这个领域时&#xff0c;我完全没想到强化学习&#xff08;RL&#xff09;会成为整个过程中最棘手的部分。当时主流方案都集中在监督微调&#xff08;SFT&#xff09;阶段&#xff0c;但当我们试图让模型在特定领域表现更稳定时&…

作者头像 李华
网站建设 2026/10/26 5:02:58

提示工程安全评估:核心维度与实践指南

1. 提示工程安全评估的核心价值在AI交互领域&#xff0c;提示工程&#xff08;Prompt Engineering&#xff09;正成为人机协作的关键纽带。作为架构师&#xff0c;我们不仅要关注提示词的效果优化&#xff0c;更要建立完善的安全评估体系。去年某金融企业就曾因提示模板泄露敏感…

作者头像 李华
网站建设 2026/10/26 5:02:01

Vue渲染器架构与虚拟DOM跨平台原理详解

1. Vue 渲染器 Renderer 的架构设计解析虚拟DOM作为现代前端框架的核心抽象层&#xff0c;其渲染机制直接决定了框架的性能上限与跨平台能力。Vue的渲染器采用分层设计理念&#xff0c;将平台相关逻辑与核心diff算法彻底解耦。这种架构使得Vue在保持核心算法统一的同时&#xf…

作者头像 李华
网站建设 2026/10/26 5:01:35

AI工程实践指南:从Agent搭建到模型部署的关键技术解析

1. 今日AI要闻速览&#xff1a;2026年10月5日的几个关键信号先说说今天整体盘面。2026年的AI行业已经不像前两年那样天天炸场&#xff0c;但信息密度反而更高了——大家不再单纯围观某个模型的参数数字&#xff0c;而是开始关心这些技术到底怎么落地、怎么赚钱、怎么改 workflo…

作者头像 李华