项目场景:
STM32F407+LAN8720使用Cube MX HAL库生成工程;
问题描述
使用Cube MX生成STM32F407+Free RTOS + LAN8720工程,在可以ping通后打开了LWIP->Key Options->LWIP_NETIF_LINK_CALLBACK选项,希望可以实现自动连接状态检测,发现在不插网线上电后,无法初始化完成,无法ping通,只有插着网线上电才可以;而插着网线上电后,在插拔2次之后发生卡死;
看了网上其他解决方法,多为旧版SDK在初始化阶段如果没有插入网线则会超时卡死,和新版SDK问题不同;
原因分析:
经过查阅源码发现了在LWIP初始化阶段会检测是否有网线连接,如果有连接则执行netif_set_up相关代码,如下:
文件路径: LWIP\Target\ethernetif.c
...
...
/**
* @brief In this function, the hardware should be initialized.
* Called from ethernetif_init().
*
* @param netif the already initialized lwip network interface structure
* for this ethernetif
*/
static void low_level_init(struct netif *netif)
{
...
...
/* Get MAC Config MAC */
HAL_ETH_GetMACConfig(&heth, &MACConf);
MACConf.DuplexMode = duplex;
MACConf.Speed = speed;
HAL_ETH_SetMACConfig(&heth, &MACConf);
HAL_ETH_Start_IT(&heth);
netif_set_up(netif);
netif_set_link_up(netif);
...
...
在增加了LWIP_NETIF_LINK_CALLBACK选项后,会在lwip.c中,增加ethernet_link_thread线程,用来监控网线是否插拔
文件路径:LWIP\App\lwip.c
/**
* LwIP initialization function
*/
void MX_LWIP_Init(void)
{
...
...
/* Set the link callback function, this function is called on change of link status*/
netif_set_link_callback(&gnetif, ethernet_link_status_updated);
/* Create the Ethernet link handler thread */
/* USER CODE BEGIN H7_OS_THREAD_NEW_CMSIS_RTOS_V2 */
memset(&attributes, 0x0, sizeof(osThreadAttr_t));
attributes.name = "EthLink";
attributes.stack_size = INTERFACE_THREAD_STACK_SIZE;
attributes.priority = osPriorityBelowNormal;
osThreadNew(ethernet_link_thread, &gnetif, &attributes);
...
...
}
但是在检测到插入网线后,进行的netif_set_up相关代码中,却和low_level_init中不同,没有使用中断方式,如下:
文件路径: LWIP\Target\ethernetif.c
/**
* @brief Check the ETH link state then update ETH driver and netif link accordingly.
* @retval None
*/
void ethernet_link_thread(void* argument)
{
...
...
if(linkchanged)
{
/* Get MAC Config MAC */
HAL_ETH_GetMACConfig(&heth, &MACConf);
MACConf.DuplexMode = duplex;
MACConf.Speed = speed;
HAL_ETH_SetMACConfig(&heth, &MACConf);
HAL_ETH_Start(&heth); // 此处没有使用 HAL_ETH_Start_IT(&heth);
netif_set_up(netif);
netif_set_link_up(netif);
}
...
...
尝试修改了此处代码为如下,问题解决;
HAL_ETH_Start_IT(&heth);
解决方案:
将ethernet_link_thread线程中,检测到网线连接后的处理函数修改为中断模式,如下:
文件路径:LWIP\Target\ethernetif.c
/**
* @brief Check the ETH link state then update ETH driver and netif link accordingly.
* @retval None
*/
void ethernet_link_thread(void* argument)
{
ETH_MACConfigTypeDef MACConf = {0};
int32_t PHYLinkState = 0;
uint32_t linkchanged = 0U, speed = 0U, duplex = 0U;
struct netif *netif = (struct netif *) argument;
/* USER CODE BEGIN ETH link init */
/* USER CODE END ETH link init */
for(;;)
{
PHYLinkState = LAN8742_GetLinkState(&LAN8742);
if(netif_is_link_up(netif) && (PHYLinkState <= LAN8742_STATUS_LINK_DOWN))
{
HAL_ETH_Stop_IT(&heth);
netif_set_down(netif);
netif_set_link_down(netif);
}
else if(!netif_is_link_up(netif) && (PHYLinkState > LAN8742_STATUS_LINK_DOWN))
{
switch (PHYLinkState)
{
case LAN8742_STATUS_100MBITS_FULLDUPLEX:
duplex = ETH_FULLDUPLEX_MODE;
speed = ETH_SPEED_100M;
linkchanged = 1;
break;
case LAN8742_STATUS_100MBITS_HALFDUPLEX:
duplex = ETH_HALFDUPLEX_MODE;
speed = ETH_SPEED_100M;
linkchanged = 1;
break;
case LAN8742_STATUS_10MBITS_FULLDUPLEX:
duplex = ETH_FULLDUPLEX_MODE;
speed = ETH_SPEED_10M;
linkchanged = 1;
break;
case LAN8742_STATUS_10MBITS_HALFDUPLEX:
duplex = ETH_HALFDUPLEX_MODE;
speed = ETH_SPEED_10M;
linkchanged = 1;
break;
default:
break;
}
if(linkchanged)
{
/* Get MAC Config MAC */
HAL_ETH_GetMACConfig(&heth, &MACConf);
MACConf.DuplexMode = duplex;
MACConf.Speed = speed;
HAL_ETH_SetMACConfig(&heth, &MACConf);
HAL_ETH_Start_IT(&heth); // <<<<<<<<<<<<< 此处
netif_set_up(netif);
netif_set_link_up(netif);
}
}
/* USER CODE BEGIN ETH link Thread core code for User BSP */
/* USER CODE END ETH link Thread core code for User BSP */
osDelay(100);
}
}
问题得以解决。
回顾:
在新版Cube MX SDK中,LWIP已经可以实现了网线热插拔,只需打开LWIP_NETIF_LINK_CALLBACK选项即可生成相关代码;会开辟一个线程每隔100ms检测一次连接状态,在检测到插拔后会自动调用netif_set_up和netif_set_down,不需要特殊处理,并且会自动触发APP/lwip.c中相关的回调函数;只是这里调用的函数需要修改一下,可能是没有处理到这部分代码的生成。只需修改此处问题可以解决,增加回调函数打印,串口调试输出如下图。
/**
* @brief Notify the User about the network interface config status
* @param netif: the network interface
* @retval None
*/
static void ethernet_link_status_updated(struct netif *netif)
{
if (netif_is_up(netif))
{
/* USER CODE BEGIN 5 */
printf("netif_is_up...\r\n");
/* USER CODE END 5 */
}
else /* netif is down */
{
/* USER CODE BEGIN 6 */
printf("netif_is_down...\r\n");
/* USER CODE END 6 */
}
}