工作筆記-----基于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,并且能夠正確收發數據,這也導致我以為硬件配置沒有問題,后續排查了很久,發現還是硬件配置的問題,但是還是不知道為什么會出現這個情況,也沒有復現出來過,很不理解.
猜測可能是和上電時序或者電平干擾有關,希望下次能夠復現以下.