工作笔记-----基于FreeRTOS的lwIP网络任务初始化问题排查
@@ Author:明月清了个风
@@ Date: 2025/8/10
@@ PS:新项目中在STMF7开发板上基于freeRTOS和lwIP开发网口相关任务,开发过程中遇到了网口无法连接的问题,进行了一系列的排查,找到了问题所在,基本将整个lwIP的启动流程详细的看了一遍,现将整个排查过程记录一下,以供参考.
内容结构如下:
- 问题现象及最终解决方法
- lwIP启动流程的简单介绍(细节的后面单独写一篇吧,涉及的内容太多了,这里只把主要的调用过程写一下)
- 排查问题过程中尝试的方法
4.题外----一个仍然没有想通的问题
一.问题现象及最终解决方法
首先给出问题的现象以及最后找到的问题根源:
现象:初始化lwip后,创建了基于lwIP接口的网络任务,任务正常运行,但无法连通,阻塞在accept_err = netconn_accept(conn, &newconn);
接收消息这一步。主机ping MCU,网口黄色指示灯对应闪烁,表明物理传输层已连通。
解决方法:phy地址设置错误,CubeMX生成的代码默认设置为1
,需根据网口芯片的实际电路连接进行设置,我这里是LAN8742A,phy地址引脚接地,在low_level_init()
函数中修改对应赋值语句或者修改宏定义LAN8742A_PHY_ADDRESS
,在文件stm32f7xx_hal_conf.h
中。注意并不一定完全和你的位置一样,需要自己找到这个设置在哪里,下面有启动流程讲解,帮助你找到它。
二.lwIP启动流程
在排查问题的过程中,基本将lwIP启动涉及到的源码看了一遍,整理后以供参考。(使用的版本应该比较新,且已经进行过修改,可能会有一些不一样)
由CubeMX导出的代码中会有一个MX_LWIP_Init()
函数,这个函数完成了lwIP的初始化,函数的主要调用栈如下:(调试基本就在这些函数里去调就行,基本覆盖了)
MX_LWIP_Init()|---> tcpip_init()|---> lwip_init()|---> sys_thread_new(tcpip_thread)|---> netif_add()|---> ethernetif_init()|---> low_level_init()|---> HAL_EHT_Init()|---> HAL_ETH_MspInit()|---> HAL_ETH_DMATxDescListInit()|---> HAL_ETH_DMARxDescListInit()|---> osThreadCreate()----ethernetif_input|---> netif_set_default()|---> netif_set_up() / netif_set_down()|---> netif_set_link_callback()
-
tcpip_init()
函数该函数中主要进行了两个步骤:
-
首先调用了
lwip_init()
,这个函数完成了lwIP各功能组件的内核初始化,比如lwIP的内存管理,和外部环境基本无关。 -
然后创建了
tcpip_thread
任务,该任务会一直尝试在tcpic_mbox
中获取消息(可以不用知道这个是什么,只要知道他在等待消息就行)
-
-
netif_add()
函数这一句的源码为
netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, &tcpip_input);
这个函数非常长,用于完成虚拟网卡的初始化,传入的参数如下:
netif_add(struct netif *netif, const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw, void *state, netif_init_fn init, netif_input_fn input)
```
也就是虚拟网卡`netif`,IP地址,子网掩码,网关,虚拟网卡初始化函数,网卡输入函数。这个函数中会对`netif`结构体成员进行初始化,并执行传入的`init`函数,也就是`ethernetif_init`函数,接着他会调用`low_level_init()`函数,这个函数完成了lwIP所需的硬件初始化过程,也就是硬件网口的初始化,我的是LAN8742A,根据函数的调用栈可以找到`HAL_ETH_MspInit()`函数,这个函数中可以看到网口相关的GPIO被初始化,然后又开启了DMA收发以及网络中断。在`low_level_init()`函数中还创建了一个任务`ethernetif_input`,这个任务非常短,源码如下:```c
void ethernetif_input( void const * argument )
{struct pbuf *p;struct netif *netif = (struct netif *) argument;for( ;; ){if (osSemaphoreWait( s_xSemaphore, TIME_WAITING_FOR_INPUT)==osOK){do{p = low_level_input( netif );if (p != NULL){if (netif->input( p, netif) != ERR_OK ){pbuf_free(p);}}}while(p!=NULL);}}
}
```他会阻塞等待`s_xSemaphore`信号量,该信号量在`HAL_ETH_IRQHandler()`中断服务函数中释放,接受到消息后,通过`low_level_input()`函数处理并返回。
-
netif_set_default()
函数这个函数很短,将
netif
设置为默认网口 -
然后会根据
netif_is_link_up(&gnetif)
的值来选择执行那个函数,这个函数判断是phy芯片的bit2是否为1,如果为1,表示硬件上已经初始化成功,那么调用netif_set_up()
,否则调用netif_set_down()
-
netif_set_link_callback()
函数设置了一个回调函数,当网络连接状态发生改变时会被执行,主要做硬件上的检查和处理 -
如果你使用的是官方的例程,会发现后面还创建了一个
ethernetif_set_link()
,任务,该任务是用来检测连接状态的,也是通过HAL_ETH_ReadPHYRegister(&EthHandle, PHY_MISR, ®value);
读取PHY芯片寄存器的相关位来判断是否连接。
三.排查问题过程中尝试的方法(以下方法先后顺序不一定啊,试的太多了,我也有点忘了😢)
-
首先尝试ping MCU地址,看是否ping通,如果无法ping通,查看电脑IP是否与MCU IP在同一网段,并且对应IP没有被占用,并查看子网掩码,网关的设置是否正确。
-
硬件检查,观察网口连接处指示灯,绿灯表示物理层连接正常,黄灯表示有数据收发,并判断PHY芯片是否初始化正确,也就是上面的函数中的
HAL_ETH_Init()
中的初始化,这里面需要注意的有:引脚是否对应,PHY芯片地址和原理图对应(这里就是我出的问题)。 -
尝试调整lwIP协议栈的初始化时序,从上面的启动流程可以看出,lwIP的启动中会创建多个处理任务,需要保证你的任务和这些任务之间没有冲突
-
电脑cmd中执行
arp -a
看有没有板子的IP和MAC地址,如果没有就是物理层和驱动的问题,我也是在这定位到了是驱动有问题,重新回去看硬件配置代码的。 -
arp -a
中看不到板子的IP后,确定是网络链路层的问题,在low_level_output()
函数中添加测试代码,初始化串口后在不同位置输出点东西就行; -
在
ethernetif_update_config()
函数中添加以下代码看phy芯片启动状态// 在 ethernetif.c 中添加 void ethernetif_update_config(struct netif *netif) {uint32_t phyreg;HAL_ETH_ReadPHYRegister(&heth, PHY_BSR, &phyreg);if(phyreg & PHY_LINKED_STATUS) {netif_set_link_up(netif);printf("PHY Link UP\n");} else {netif_set_link_down(netif);printf("PHY Link DOWN\n");} }// 在初始化中注册定时器,就是需要你定期查询,可以通过自己的方式实现,不一定要和这里一样 void ethernet_link_thread(void *arg) {for(;;) {ethernetif_update_config(&gnetif);osDelay(500);} }
-
在lwIP启动流程后,也就是进入开启任务调度前,强制软复位PHY芯片,参考代码如下,
HAL_ETH_Start(&heth); osDelay(100);// 软复位PHY HAL_ETH_WritePHYRegister(&heth, PHY_BCR, PHY_RESET); osDelay(100);// 重新配置PHY uint32_t phyreg; HAL_ETH_ReadPHYRegister(&heth, PHY_BCR, &phyreg); phyreg |= PHY_AUTONEGOTIATION; HAL_ETH_WritePHYRegister(&heth, PHY_BCR, phyreg); printf("PHY reinitialized!\n");
-
如果电脑端ping的时候黄灯会对应闪,那么说明物理层正常,那么在
low_level_input()
函数中添加调试代码,也是随便输出点什么,看看有没有进这个函数,然后看调用路径一点点加调试代码 -
看以太网中断
void ETH_IRQHandler(void)
有没有进去,加调试信息或者keil直接调试也行
尝试上面的方法应该从硬件到软件都涉及到了,希望对你有帮助,当然还可以使用物理方法,示波器去抓网口的波形,不过这个不一定都有,这里就说软件上我试了哪些。
题外
这个还有一个坑我没有解决,在一开始没有配置对PHY芯片地址的时候,竟然有一段时间成功连上了TCP,并且能够正确收发数据,这也导致我以为硬件配置没有问题,后续排查了很久,发现还是硬件配置的问题,但是还是不知道为什么会出现这个情况,也没有复现出来过,很不理解.
猜测可能是和上电时序或者电平干扰有关,希望下次能够复现以下.