lwIP(Lightweight IP)是一個為嵌入式系統設計的輕量級TCP/IP協議棧。它旨在為資源受限的環境提供完整的網絡協議功能,同時保持低內存使用和代碼大小。由于其模塊化的設計,開發者可以根據需要選擇包含或排除特定功能,以滿足特定應用的資源要求。
Xilinx的lwIP是基于開源lwIP TCP/IP協議棧的一個適應版本,專門為Xilinx的硬件平臺,如Zynq-7000和MicroBlaze,進行了優化和集成。Xilinx為其硬件平臺提供了lwIP的庫,使得開發者可以輕松地在其FPGA和SoC設計中實現網絡通信功能。
以lwip TCP Perf Client為例,這是一個fpga作為TCP Client,像TCP Server發送批量數據,并測試傳輸性能的例程。

TCP參數
先看幾個TCP相關的參數
TCP_CONN_PORT表示TCP的端口號,在Server中,需要指定該端口號,如果發現tcp一直不通,但ping是可以通的,多半原因是這個端口被占用了;
TCP_SERVER_IP_ADDRESS表示TCP Server的IP地址

FPGA的IP地址是在main.c里面指定的:

如果TCP Server使用網絡調試助手接收數據,設置如下:(需要注意,本地端口號應該是5001,跟代碼中匹配)

main函數
main函數的內容如下:
intmain(void)
{
structnetif*netif;
/*themacaddressoftheboard.thisshouldbeuniqueperboard*/
unsignedcharmac_ethernet_address[]={
0x00,0x0a,0x35,0x00,0x01,0x02};
netif=&server_netif;
#ifdefined(__arm__)&&!defined(ARMR5)
#ifXPAR_GIGE_PCS_PMA_SGMII_CORE_PRESENT==1||
XPAR_GIGE_PCS_PMA_1000BASEX_CORE_PRESENT==1
ProgramSi5324();
ProgramSfpPhy();
#endif
#endif
/*DefinethisboardspecificmacroinorderperformPHYreset
*onZCU102
*/
#ifdefXPS_BOARD_ZCU102
IicPhyReset();
#endif
init_platform();
xil_printf("
");
xil_printf("-----lwIPRAWModeTCPClientApplication-----
");
/*initializelwIP*/
lwip_init();
/*Addnetworkinterfacetothenetif_list,andsetitasdefault*/
if(!xemac_add(netif,NULL,NULL,NULL,mac_ethernet_address,
PLATFORM_EMAC_BASEADDR)){
xil_printf("ErroraddingN/Winterface
");
return-1;
}
netif_set_default(netif);
/*nowenableinterrupts*/
platform_enable_interrupts();
/*specifythatthenetworkifisup*/
netif_set_up(netif);
assign_default_ip(&(netif->ip_addr),&(netif->netmask),&(netif->gw));
print_ip_settings(&(netif->ip_addr),&(netif->netmask),&(netif->gw));
xil_printf("
");
/*printappheader*/
print_app_header();
/*starttheapplication*/
start_application();
xil_printf("
");
while(1){
if(TcpFastTmrFlag){
tcp_fasttmr();
TcpFastTmrFlag=0;
}
if(TcpSlowTmrFlag){
tcp_slowtmr();
TcpSlowTmrFlag=0;
}
xemacif_input(netif);
transfer_data();
}
/*neverreached*/
cleanup_platform();
return0;
}
在main函數中,首先就是定義各種網口接口相關的變量,并定義了MAC地址。
netif
這個netif的指針,需要多關注一下。
在lwIP中,netif(網絡接口)是一個核心的結構體,它代表了一個網絡接口,例如以太網接口、Wi-Fi接口等。netif結構體用于定義和管理這些接口,使lwIP可以在多個接口上運行并進行路由決策。
具體來說,netif結構體包括了以下幾個主要的部分:
硬件地址:例如MAC地址。
IP地址、子網掩碼和網關:這些用于IP層的路由和地址決策。
狀態標志:表示接口的狀態,例如是否激活、是否為默認接口等。
輸入和輸出函數指針:這些函數用于處理從該接口接收到的數據包或向該接口發送數據包。
其他驅動特定的數據:例如用于DMA的描述符、緩沖區等。
當你在lwIP中添加一個新的網絡接口時,你通常會初始化一個netif結構體并使用netif_add()函數將其添加到lwIP的接口列表中。這樣,lwIP就可以開始在該接口上接收和發送數據包了。
簡而言之,netif是lwIP中用于表示和管理網絡接口的關鍵結構體。
init_platform
在init_platform()函數中,初始化定時器和中斷。

接下來就是lwip的初始化,這三個初始化都是在platform的庫里面寫好的,直接調用就行。
xemac_add
后面xemac_add的原型如下,可以簡單理解為設置網口的mac地址,此處沒有設置IP的信息,可以看到傳進去的參數都是NULL。
structnetif* xemac_add(structnetif*netif, ip_addr_t*ipaddr,ip_addr_t*netmask,ip_addr_t*gw, unsignedchar*mac_ethernet_address, UINTPTRmac_baseaddr)
netif_set_default
netif_set_default函數在lwIP中用于設置默認的網絡接口。在一個系統中可能存在多個網絡接口,但通常只有一個被視為默認接口。當lwIP需要發送數據包,但不知道應該通過哪個接口發送時,它會選擇默認接口。
函數原型如下:
/**
*@ingroupnetif
*Setanetworkinterfaceasthedefaultnetworkinterface
*(usedtooutputallpacketsforwhichnospecificrouteisfound)
*
*@paramnetifthedefaultnetworkinterface
*/
void
netif_set_default(structnetif*netif)
{
LWIP_ASSERT_CORE_LOCKED();
if(netif==NULL){
/*removedefaultroute*/
mib2_remove_route_ip4(1,netif);
}else{
/*installdefaultroute*/
mib2_add_route_ip4(1,netif);
}
netif_default=netif;
LWIP_DEBUGF(NETIF_DEBUG,("netif:settingdefaultinterface%c%c
",
netif?netif->name[0]:''',netif?netif->name[1]:'''));
}
其中,netif是你希望設置為默認的網絡接口的指針。
當你調用這個函數時,傳入的netif結構體會被設置為默認網絡接口。這意味著,除非有特定的路由決策指示其他接口,否則所有的出站數據包都會通過這個接口發送。
例如,如果你有一個以太網接口和一個Wi-Fi接口,并且你希望所有的通信默認通過Wi-Fi接口進行,那么你會在初始化Wi-Fi接口后調用netif_set_default函數,并傳入Wi-Fi接口的netif結構體指針。
這個函數對于確保正確的網絡通信行為非常重要,特別是在存在多個網絡接口的系統中。
platform_enable_interrupts
這個函數就很容易理解了,就是使能中斷,函數原型如下:
voidplatform_enable_interrupts()
{
/*
*Enablenon-criticalexceptions.
*/
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
XScuTimer_EnableInterrupt(&TimerInstance);
XScuTimer_Start(&TimerInstance);
return;
}
netif_set_up
netif_set_up函數在lwIP中用于激活一個網絡接口。當你初始化一個網絡接口并準備好開始接收和發送數據時,你需要調用這個函數來標記該接口為"up"狀態。
函數原型如下:
void
netif_set_up(structnetif*netif)
{
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("netif_set_up:invalidnetif",netif!=NULL,return);
if(!(netif->flags&NETIF_FLAG_UP)){
netif_set_flags(netif,NETIF_FLAG_UP);
MIB2_COPY_SYSUPTIME_TO(&netif->ts);
NETIF_STATUS_CALLBACK(netif);
#ifLWIP_NETIF_EXT_STATUS_CALLBACK
{
netif_ext_callback_args_targs;
args.status_changed.state=1;
netif_invoke_ext_callback(netif,LWIP_NSC_STATUS_CHANGED,&args);
}
#endif
netif_issue_reports(netif,NETIF_REPORT_TYPE_IPV4|NETIF_REPORT_TYPE_IPV6);
#ifLWIP_IPV6
nd6_restart_netif(netif);
#endif/*LWIP_IPV6*/
}
}
其中,netif是你希望激活的網絡接口的指針。
當你調用netif_set_up函數時,它會執行以下操作:
設置netif結構體中的flags字段,標記該接口為"up"狀態。
如果配置了lwIP的相關回調,例如NETIF_STATUS_CALLBACK,那么這些回調函數也會被觸發,通知應用程序該接口的狀態已經改變。
通常,在你完成網絡接口的硬件初始化、分配了必要的資源,并確信接口已經準備好進行通信后,你會調用netif_set_up函數。這樣,lwIP就知道它可以開始在該接口上接收和發送數據包了。
相反地,如果你需要將一個接口標記為"down"狀態,例如在接口遇到錯誤或需要進行維護時,你可以調用netif_set_down函數。這會告訴lwIP停止在該接口上的通信,直到接口再次被設置為"up"狀態。
assign_default_ip
從名字也可以看到出來,就是設置ip地址、Netmask和gate way
函數原型也非常直觀,不做過多解釋了
staticvoidassign_default_ip(ip_addr_t*ip,ip_addr_t*mask,ip_addr_t*gw)
{
interr;
xil_printf("ConfiguringdefaultIP%s
",DEFAULT_IP_ADDRESS);
err=inet_aton(DEFAULT_IP_ADDRESS,ip);
if(!err)
xil_printf("InvaliddefaultIPaddress:%d
",err);
err=inet_aton(DEFAULT_IP_MASK,mask);
if(!err)
xil_printf("InvaliddefaultIPMASK:%d
",err);
err=inet_aton(DEFAULT_GW_ADDRESS,gw);
if(!err)
xil_printf("Invaliddefaultgatewayaddress:%d
",err);
}
start_application
start_application函數是一個啟動網絡應用的函數。在很多lwIP的示例應用中,這個函數被用來初始化和啟動特定的網絡應用,例如啟動一個HTTP服務器、TCP客戶端、UDP回聲服務等。具體的功能和行為取決于應用的需求和設計。這個函數可能會初始化所需的網絡資源,設置回調函數,并開始監聽網絡事件。
初始化變量:函數開始時,初始化了一些變量,如err用于錯誤處理,pcb代表TCP控制塊,remote_addr用于存儲遠程服務器的IP地址,以及一個循環計數器i。
設置遠程服務器的IP地址:
如果啟用了IPv6(LWIP_IPV6==1),則使用inet6_aton函數將TCP_SERVER_IPV6_ADDRESS字符串轉換為IPv6地址格式并存儲在remote_addr中。
如果未啟用IPv6,則使用inet_aton函數將TCP_SERVER_IP_ADDRESS字符串轉換為IPv4地址格式。
檢查IP地址的有效性:如果IP地址轉換失敗,函數會打印錯誤消息并返回。
創建TCP控制塊(PCB):使用tcp_new_ip_type函數為客戶端創建一個新的TCP控制塊。
連接到遠程服務器:使用tcp_connect函數嘗試連接到遠程服務器的指定IP地址和端口TCP_CONN_PORT。如果連接成功,tcp_client_connected回調函數將被注冊,以便在連接建立后進行處理。
錯誤處理:如果在上述步驟中出現任何錯誤,函數會打印相應的錯誤消息并關閉TCP連接。
初始化發送緩沖區:為send_buf緩沖區填充數據,數據內容是0到9的數字字符。
總的來說,start_application函數的主要目的是初始化一個TCP客戶端,嘗試連接到指定的遠程服務器,并準備發送數據。
函數原型如下:
voidstart_application(void)
{
err_terr;
structtcp_pcb*pcb;
ip_addr_tremote_addr;
u32_ti;
#ifLWIP_IPV6==1
remote_addr.type=IPADDR_TYPE_V6;
err=inet6_aton(TCP_SERVER_IPV6_ADDRESS,&remote_addr);
#else
err=inet_aton(TCP_SERVER_IP_ADDRESS,&remote_addr);
#endif/*LWIP_IPV6*/
if(!err){
xil_printf("InvalidServerIPaddress:%d
",err);
return;
}
/*CreateClientPCB*/
pcb=tcp_new_ip_type(IPADDR_TYPE_ANY);
if(!pcb){
xil_printf("ErrorinPCBcreation.outofmemory
");
return;
}
err=tcp_connect(pcb,&remote_addr,TCP_CONN_PORT,
tcp_client_connected);
if(err){
xil_printf("Errorontcp_connect:%d
",err);
tcp_client_close(pcb);
return;
}
client.client_id=0;
/*initializedatabufferbeingsentwithsameasusediniperf*/
for(i=0;i
tcp_fasttmr和tcp_slowtmr
在lwip的TCP視線中,快速定時器(tcp_fasttmr)和慢速定時器(tcp_slowtmr)都是為了TCP連接的維護而存在的,但它們關注的方面和執行頻率是不同的。
運行頻率:
快速定時器:通常每250毫秒被調用一次(這是默認值,但可以配置)。
慢速定時器:通常每500毫秒被調用一次(這也是默認值,但同樣可以配置)。
關注的方面:
連接的生命周期管理:例如,關閉那些已經結束但還沒有完全關閉的連接。
持續活動檢測:例如,檢查長時間沒有活動的連接,并可能發送探測數據段來檢查對方是否仍然活躍。
超時管理:管理那些因為長時間沒有響應而需要關閉的連接。
擁塞控制:調整窗口大小和其他與流量控制相關的參數。
重傳管理:如果一個數據段沒有得到確認,它會被重新發送。快速定時器負責處理這些重傳。
延遲確認:TCP不會立刻確認每一個接收到的數據段,而是稍作延遲,以期待有數據可以與確認一同發送,從而減少網絡的數據包數量。快速定時器可以觸發這些延遲確認的發送。
快速定時器 (tcp_fasttmr)
主要關注:
慢速定時器 (tcp_slowtmr)
主要關注:
簡而言之,快速定時器主要關注與數據傳輸直接相關的事務,如重傳和確認,而慢速定時器則更多地關注連接的維護、超時和流控制。
tcp_write
tcp_write 函數用于將數據排入到一個TCP連接的發送隊列。它是應用程序與 lwIP TCP層之間的一個關鍵接口,允許應用程序發送數據到其TCP連接。
以下是關于 tcp_write 函數的一些關鍵點:
非阻塞:與某些TCP/IP實現不同,tcp_write 是非阻塞的。這意味著,如果當前沒有足夠的可用緩沖區來容納你想發送的數據,函數將不會阻塞,而是返回一個錯誤。
排隊,不是直接發送:當你調用 tcp_write 時,你實際上是將數據放入發送隊列,而不是立即發送數據。真正的數據傳輸將在后續的 lwIP 處理中進行,這可能涉及與其他TCP機制的交互,如擁塞控制。
參數:該函數通常接受以下參數:
pcb:代表TCP連接的控制塊。
data:指向要發送數據的指針。
len:要發送的數據的長度。
flags:與數據發送相關的標志。例如,TCP_WRITE_FLAG_COPY 表示應從應用程序的數據緩沖區復制數據(而不是直接引用)。
確認機制:使用 tcp_write 發送的數據將在對方確認收到之后才從發送隊列中移除。這意味著,即使你已經調用了 tcp_write,你也需要確保你的應用程序繼續處理(例如,通過調用 tcp_output 或等待 lwIP 的主循環)來確保數據被實際發送和確認。
合適的調用時間:為了避免不必要的網絡擁塞和效率低下,建議在連接建立后或在接收到數據或發送緩沖區有可用空間時(通過相關的TCP回調函數)再調用 tcp_write。
tcp_write 是 lwIP 的TCP API的一部分,與其他函數(如 tcp_connect, tcp_listen, tcp_close 等)一起,提供了完整的TCP功能。在使用它時,重要的是要理解其工作原理,以及與其他TCP操作的交互方式。
審核編輯:劉清
-
FPGA
+關注
關注
1662文章
22476瀏覽量
638376 -
嵌入式系統
+關注
關注
41文章
3804瀏覽量
133814 -
SoC設計
+關注
關注
1文章
151瀏覽量
19589 -
TCP
+關注
關注
8文章
1430瀏覽量
83701 -
LwIP協議棧
+關注
關注
0文章
19瀏覽量
7918
原文標題:lwip代碼分析
文章出處:【微信號:傅里葉的貓,微信公眾號:傅里葉的貓】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
基于MM32F5270的Ethernet實現LwIP協議棧移植
lwip協議棧源碼詳解說明
lwip協議棧代碼分析
評論