【LWIP】STM32F429 + LWIP + DP83848 热插拔问题总结
1️⃣ 问题描述
在使用 STM32F429 搭配 DP83848 PHY 和 LWIP + FreeRTOS 的项目中,遇到以下网络问题:
- 拔掉网线再插上,网络无法恢复。
- 先上电再插网线,通信异常。
- CubeMX 生成的低级初始化
low_level_init()
中,如果启用 PHY_MISR_LINK_INT_EN,程序会卡死。 - 原本尝试检测链路状态函数无法打印提示信息或检测不到变化。
2️⃣ 现象分析
- 拔插网线无法恢复:因为 HAL/PHY 初始化时只配置了一次链路状态,没有动态处理链路变化。
- 先上电后插网线通信异常:PHY 在上电时链路未建立,MAC 启动时认为链路 DOWN,LWIP 不会正常工作。
- PHY_MISR_LINK_INT_EN 导致卡死:DP83848 的 MISR 寄存器部分位是只读/自清零,直接读-改-写会导致 MDIO 总线挂起,HAL_ETH_WritePHYRegister 卡死。
- Check_Link_Status 不打印:原掩码使用
PHY_LINK_STATUS=0x0001
,DP83848 实际链路状态位在 BSR 寄存器 bit2(PHY_LINKED_STATUS=0x0004
),导致热插拔状态未被检测到。
3️⃣ 原理说明
-
PHY 链路状态寄存器
-
DP83848 PHY_BSR(0x01)寄存器,bit2 为链路状态位:
#define PHY_LINKED_STATUS 0x0004
-
只有当链路建立或断开时,这个位会变化。
-
-
PHY 中断机制
- MICR (0x11) 用于全局中断使能
- MISR (0x12) 用于选择中断源
- DP83848 对 MISR 某些位写操作会阻塞 MDIO 总线,因此不适合直接写。
-
LWIP + MAC 启动
- 当链路 DOWN 时,MAC 启动不会发送/接收数据,LWIP 不可用。
- 必须在链路状态变化时 重新启动 MAC 并设置 netif_up/netif_down 才能恢复通信。
-
轮询 vs 中断
- 对于 DP83848,使用轮询检测 PHY_BSR 链路状态最稳定,避免 MISR 写操作导致卡死。
- 轮询周期建议 200ms~500ms。
4️⃣ 解决方案
- 取消 PHY_MISR_LINK_INT_EN 写操作,避免卡死。
- 使用轮询任务检测链路状态变化。
- 链路变化时重新启动 MAC,并调用
netif_set_up()
/netif_set_down()
。 - 使用正确的链路状态掩码:
PHY_LINKED_STATUS = 0x0004
。 - printf 输出需立即刷新,确保串口提示可见。
5️⃣ 代码示例
取消 PHY_MISR_LINK_INT_EN 写操作,避免卡死:
在ethernetif.c文件中,找到static void low_level_init(struct netif *netif)这个函数,注释掉regvalue |= PHY_MISR_LINK_INT_EN这一句:
static void low_level_init(struct netif *netif)
{uint32_t regvalue = 0;HAL_StatusTypeDef hal_eth_init_status;
/* USER CODE BEGIN OS_THREAD_ATTR_CMSIS_RTOS_V2 */osThreadAttr_t attributes;
/* USER CODE END OS_THREAD_ATTR_CMSIS_RTOS_V2 *//* Init ETH */uint8_t MACAddr[6] ;heth.Instance = ETH;heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;heth.Init.Speed = ETH_SPEED_100M;heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX;heth.Init.PhyAddress = DP83848_PHY_ADDRESS;MACAddr[0] = 0x00;MACAddr[1] = 0x80;MACAddr[2] = 0xE1;MACAddr[3] = 0x00;MACAddr[4] = 0x00;MACAddr[5] = 0x00;heth.Init.MACAddr = &MACAddr[0];heth.Init.RxMode = ETH_RXINTERRUPT_MODE;heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_MII;/* USER CODE BEGIN MACADDRESS *//* USER CODE END MACADDRESS */hal_eth_init_status = HAL_ETH_Init(&heth);if (hal_eth_init_status == HAL_OK){/* Set netif link flag */netif->flags |= NETIF_FLAG_LINK_UP;}/* Initialize Tx Descriptors list: Chain Mode */HAL_ETH_DMATxDescListInit(&heth, DMATxDscrTab, &Tx_Buff[0][0], ETH_TXBUFNB);/* Initialize Rx Descriptors list: Chain Mode */HAL_ETH_DMARxDescListInit(&heth, DMARxDscrTab, &Rx_Buff[0][0], ETH_RXBUFNB);#if LWIP_ARP || LWIP_ETHERNET/* set MAC hardware address length */netif->hwaddr_len = ETH_HWADDR_LEN;/* set MAC hardware address */netif->hwaddr[0] = heth.Init.MACAddr[0];netif->hwaddr[1] = heth.Init.MACAddr[1];netif->hwaddr[2] = heth.Init.MACAddr[2];netif->hwaddr[3] = heth.Init.MACAddr[3];netif->hwaddr[4] = heth.Init.MACAddr[4];netif->hwaddr[5] = heth.Init.MACAddr[5];/* maximum transfer unit */netif->mtu = 1500;/* Accept broadcast address and ARP traffic *//* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */#if LWIP_ARPnetif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;#elsenetif->flags |= NETIF_FLAG_BROADCAST;#endif /* LWIP_ARP *//* create a binary semaphore used for informing ethernetif of frame reception */s_xSemaphore = osSemaphoreNew(1, 1, NULL);/* create the task that handles the ETH_MAC */
/* USER CODE BEGIN OS_THREAD_NEW_CMSIS_RTOS_V2 */memset(&attributes, 0x0, sizeof(osThreadAttr_t));attributes.name = "EthIf";attributes.stack_size = INTERFACE_THREAD_STACK_SIZE;attributes.priority = osPriorityRealtime;osThreadNew(ethernetif_input, netif, &attributes);
/* USER CODE END OS_THREAD_NEW_CMSIS_RTOS_V2 *//* Enable MAC and DMA transmission and reception */HAL_ETH_Start(&heth);/* USER CODE BEGIN PHY_PRE_CONFIG *//* USER CODE END PHY_PRE_CONFIG *//**** Configure PHY to generate an interrupt when Eth Link state changes ****//* Read Register Configuration */HAL_ETH_ReadPHYRegister(&heth, PHY_MICR, ®value);regvalue |= (PHY_MICR_INT_EN | PHY_MICR_INT_OE);/* Enable Interrupts */HAL_ETH_WritePHYRegister(&heth, PHY_MICR, regvalue );/* Read Register Configuration */HAL_ETH_ReadPHYRegister(&heth, PHY_MISR, ®value);// regvalue |= PHY_MISR_LINK_INT_EN; //-------------------------------------------------------------------------------------------------/* Enable Interrupt on change of link status */HAL_ETH_WritePHYRegister(&heth, PHY_MISR, regvalue);/* USER CODE BEGIN PHY_POST_CONFIG *//* USER CODE END PHY_POST_CONFIG */#endif /* LWIP_ARP || LWIP_ETHERNET *//* USER CODE BEGIN LOW_LEVEL_INIT *//* USER CODE END LOW_LEVEL_INIT */
}
在lwip文件头部,定义以下两个函数:
(1)链路状态检测函数
#define LINK_STABLE_COUNT 3
static uint8_t link_up_counter = 0;
static uint8_t link_down_counter = 0;uint8_t Check_Link_Status(void)
{uint32_t regvalue = 0;HAL_ETH_ReadPHYRegister(&heth, PHY_BSR, ®value);regvalue &= PHY_LINKED_STATUS; // 0x0004if (regvalue) // 链路 UP{link_up_counter++;link_down_counter = 0;}else // 链路 DOWN{link_down_counter++;link_up_counter = 0;}// 链路稳定3次才认为状态变化if (link_up_counter >= LINK_STABLE_COUNT && Link_Reg == 0){Link_Reg = 1;printf("Ethernet link is UP.\r\n");fflush(stdout);return 1;}else if (link_down_counter >= LINK_STABLE_COUNT && Link_Reg == 1){Link_Reg = 0;printf("Ethernet link is DOWN.\r\n");fflush(stdout);return 1;}return 0; // 状态未变化
}
(2)链路变化处理函数
void ETH_LinkChangeHandler(void)
{if (Link_Reg) // Link Up{/* 重新配置 MAC,防止速度/双工不匹配 */HAL_ETH_Stop(&heth);HAL_ETH_Start(&heth);netif_set_up(&gnetif);#if LWIP_DHCPdhcp_start(&gnetif);
#endif}else // Link Down{netif_set_down(&gnetif);HAL_ETH_Stop(&heth);}
}
FreeRTOS 轮询任务示例
void PHY_Link_Task(void *argument)
{for (;;){if (Check_Link_Status()){ETH_LinkChangeHandler();}osDelay(200); // 每 200ms 检查一次}
}
6️⃣ 说明
-
这种方式可以同时解决:
- 拔插网线无法恢复
- 先上电再插网线通信异常
- printf 提示链路状态变化
-
避免了 PHY 硬件中断引起的卡死问题。