1.lwip 1.4.1
2.RTOS(本文使用rt-thread)
打开lwipopts.h,将宏定义LWIP_NETIF_LINK_CALLBACK的值设为1,如下:
#define LWIP_NETIF_LINK_CALLBACK 1
这个宏定义被使能后会将void ethernetif_update_config(struct netif *netif)函数加入工程中进行编译。这个函数的功能就是检查当前连接情况,进行不同的处理。
该函数如下:
void ethernetif_update_config(struct netif *netif)
{
__IO uint32_t tickstart = 0;
uint32_t regvalue = 0;
if (netif_is_link_up(netif))
{
/* Restart the auto-negotiation */
if (heth.Init.AutoNegotiation != ETH_AUTONEGOTIATION_DISABLE)
{
/* Enable Auto-Negotiation */
HAL_ETH_WritePHYRegister(&heth, PHY_BCR, PHY_AUTONEGOTIATION);
/* Get tick */
tickstart = HAL_GetTick();
/* Wait until the auto-negotiation will be completed */
do
{
HAL_ETH_ReadPHYRegister(&heth, PHY_BSR, ®value);
/* Check for the Timeout ( 1s ) */
if ((HAL_GetTick() - tickstart) > 1000)
{
/* In case of timeout */
goto error;
}
} while (((regvalue & PHY_AUTONEGO_COMPLETE) != PHY_AUTONEGO_COMPLETE));
/* Read the result of the auto-negotiation */
HAL_ETH_ReadPHYRegister(&heth, PHY_SR, ®value);
if ((regvalue & PHY_DUPLEX_STATUS) != (uint32_t)RESET)
{
heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX;
}
else
{
heth.Init.DuplexMode = ETH_MODE_HALFDUPLEX;
}
if (regvalue & PHY_SPEED_STATUS)
{
/* Set Ethernet speed to 10M following the auto-negotiation */
heth.Init.Speed = ETH_SPEED_10M;
}
else
{
/* Set Ethernet speed to 100M following the auto-negotiation */
heth.Init.Speed = ETH_SPEED_100M;
}
}
else /* AutoNegotiation Disable */
{
error:
/* Check parameters */
assert_param(IS_ETH_SPEED(heth.Init.Speed));
assert_param(IS_ETH_DUPLEX_MODE(heth.Init.DuplexMode));
/* Set MAC Speed and Duplex Mode to PHY */
HAL_ETH_WritePHYRegister(&heth, PHY_BCR,
((uint16_t)(heth.Init.DuplexMode >> 3) |
(uint16_t)(heth.Init.Speed >> 1)));
}
/* ETHERNET MAC Re-Configuration */
HAL_ETH_ConfigMAC(&heth, (ETH_MACInitTypeDef *)NULL);
/* Restart MAC interface */
HAL_ETH_Start(&heth);
}
else
{
/* Stop MAC interface */
HAL_ETH_Stop(&heth);
}
ethernetif_notify_conn_changed(netif);
}
在ethernetif_update_config函数的最后有一个名为ethernetif_notify_conn_changed的函数需要用户自定义功能,由于我们这里使用的是固定IP,因此自定义的状态变化回调函数如下:
void ethernetif_notify_conn_changed(struct netif *netif)
{
if(netif_is_link_up(netif))
{
printf("\r\nLan link up!\r\n");
netif_set_up(netif);
}
else
{
printf("\r\nLan link down!\r\n");
netif_set_down(netif);
}
}
如果需要启用了DHCP功能,可以在发现网线插上后等待路由器分配IP。
前面我们已经编辑好了自定义的回调函数,接下来需要绑定回调函数,在我们的网线连接状态改变时执行回调函数。绑定回调函数只需要添加netif_set_link_callback(&gnetif, ethernetif_update_config)语句到LWIP初始化函数内即可。如下:
netif_set_default(&gnetif);
if (netif_is_link_up(&gnetif))
{
/* When the netif is fully configured this function must be called */
netif_set_up(&gnetif);
}
else
{
/* When the netif link is down this function must be called */
netif_set_down(&gnetif);
}
netif_set_link_callback(&gnetif, ethernetif_update_config);
前面我们已经完成了回调函数编写,同时绑定了回调函数。由于lwip 1.4.1原生没有轮询网口连接状态,因此这部分需要我们来编写。这里我们使用RTOS,将轮询网口连接状态的函数放到软件定时器内执行:
void lan_state_check(void)
{
uint32_t regvalue = 0;
HAL_ETH_ReadPHYRegister(&heth, PHY_BSR, ®value);
if ((regvalue & PHY_LINKED_STATUS) == PHY_LINKED_STATUS)
{
netif_set_flags(&gnetif, NETIF_FLAG_LINK_UP);
if (netif_is_up(&gnetif) != 1)
{
gnetif.link_callback(&gnetif);
}
}
else
{
netif_clear_flags(&gnetif, NETIF_FLAG_LINK_UP);
if (netif_is_up(&gnetif) != 0)
{
gnetif.link_callback(&gnetif);
}
}
}
这个函数主要功能就是读取PHY的BSR寄存器查看连接状态,如果当前连接状态的物理状态和LWIP软件连接状态不一致则执行回调函数。
其中物理连接状态由软件定时器周期性设置,设置的bit为NETIF_FLAG_LINK_UP,软件连接状态由LWIP内核设置,设置的bit为NETIF_FLAG_UP。
上电阶段有时网卡还未建立有效连接,ETH初始化函数会一直阻塞等待连接建立直至超时,默认的超时时间为5000ms,也就是说如果上电后网卡没有建立有效连接会一直阻塞等待5000ms,体验感非常差。为了解决这一问题,将以下2个宏定义修改成2000即可:
#define ETH_TIMEOUT_LINKED_STATE 2000U
#define ETH_TIMEOUT_AUTONEGO_COMPLETED 2000U
(1)上电后再插上网线,随后ping路由器
可以看到打印了“Lan link up!”,程序识别到了这一变化同时执行了回调函数,ping路由器正常。
(2)将网线拔下
可以看到打印了“Lan link down!”,程序识别到了这一变化同时执行了回调函数。
(3)反复执行(1)(2)查看是否正常。
反复执行也能保证稳定,测试结果正常。
(1)LWIP实现热插拔的关键在于识别物理网口连接状态和软件连接状态是否一致,当二者不一致时则将软件连接状态设置为和物理网口连接状态一致。
(2)需要根据实际情况自定义回调函数。
(3)超时时间不要设置太短,实测2000ms有效。