1️⃣ 问题描述

在使用 STM32F429 搭配 DP83848 PHY 和 LWIP + FreeRTOS 的项目中,遇到以下网络问题:

  1. 拔掉网线再插上,网络无法恢复
  2. 先上电再插网线,通信异常
  3. CubeMX 生成的低级初始化 low_level_init() 中,如果启用 PHY_MISR_LINK_INT_EN,程序会卡死。
  4. 原本尝试检测链路状态函数无法打印提示信息或检测不到变化。

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️⃣ 原理说明

  1. PHY 链路状态寄存器

    • DP83848 PHY_BSR(0x01)寄存器,bit2 为链路状态位:

      #define PHY_LINKED_STATUS 0x0004
      
    • 只有当链路建立或断开时,这个位会变化。

  2. PHY 中断机制

    • MICR (0x11) 用于全局中断使能
    • MISR (0x12) 用于选择中断源
    • DP83848 对 MISR 某些位写操作会阻塞 MDIO 总线,因此不适合直接写。
  3. LWIP + MAC 启动

    • 当链路 DOWN 时,MAC 启动不会发送/接收数据,LWIP 不可用。
    • 必须在链路状态变化时 重新启动 MAC 并设置 netif_up/netif_down 才能恢复通信。
  4. 轮询 vs 中断

    • 对于 DP83848,使用轮询检测 PHY_BSR 链路状态最稳定,避免 MISR 写操作导致卡死。
    • 轮询周期建议 200ms~500ms。

4️⃣ 解决方案

  1. 取消 PHY_MISR_LINK_INT_EN 写操作,避免卡死。
  2. 使用轮询任务检测链路状态变化
  3. 链路变化时重新启动 MAC,并调用 netif_set_up() / netif_set_down()
  4. 使用正确的链路状态掩码PHY_LINKED_STATUS = 0x0004
  5. 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, &regvalue);

  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, &regvalue);

//  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, &regvalue);
    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️⃣ 说明

  • 这种方式可以同时解决:

    1. 拔插网线无法恢复
    2. 先上电再插网线通信异常
    3. printf 提示链路状态变化
  • 避免了 PHY 硬件中断引起的卡死问题。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐