【LWIP】STM32F429 + LWIP + DP83848 热插拔问题总结
/ 初始为无效值// 读取 PHY_BSR// 保留链路状态位// 0x0004else// 确保串口立即输出return 1;return 0;
·
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_ARP
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
#else
netif->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; // 0x0004
if (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_DHCP
dhcp_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 硬件中断引起的卡死问题。
更多推荐



所有评论(0)