利用ZYNQ SOC快速開啟演算法驗證通路(6)——LWIP實現千兆TCP/IP網路傳輸
一、前言
之前ZYNQ與PC之間的網路連線依賴於外接硬體協議棧晶片,雖然C驅動非常簡單,但網路頻寬受限。現採用LWIP+PS端MAC控制器+PHY晶片的通用架構。 關於LWIP庫,已經有很多現成的資料和書籍。其有兩套API,一個是SOCKET,另一個是本例中要用到的RAW。RAW API理解起來較為複雜,整個程式基於中斷機制執行,通過函式指標完成多層回撥函式的執行。SOCKET API需要支援多執行緒作業系統的支援,也犧牲了效率,但理解和程式設計都較為容易。實際上SOCKET API是對RAW API的進一步封裝。
二、LWIP Echo Server demo解讀
首先開啟Xilinx SDK自帶的LwIP Echo Server demo.
1 /****************************************************************************** 2 * 3 * Copyright (C) 2009 - 2014 Xilinx, Inc.All rights reserved. 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a copy 6 * of this software and associated documentation files (the "Software"), to deal 7 * in the Software without restriction, including without limitation the rights 8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 * copies of the Software, and to permit persons to whom the Software is 10 * furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice shall be included in 13 * all copies or substantial portions of the Software. 14 * 15 * Use of the Software is limited solely to applications: 16 * (a) running on a Xilinx device, or 17 * (b) that interact with a Xilinx device through a bus or interconnect. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 22 * XILINXBE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 24 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 * SOFTWARE. 26 * 27 * Except as contained in this notice, the name of the Xilinx shall not be used 28 * in advertising or otherwise to promote the sale, use or other dealings in 29 * this Software without prior written authorization from Xilinx. 30 * 31 ******************************************************************************/ 32 33 #include <stdio.h> 34 35 #include "xparameters.h" 36 37 #include "netif/xadapter.h" 38 39 #include "platform.h" 40 #include "platform_config.h" 41 #if defined (__arm__) || defined(__aarch64__) 42 #include "xil_printf.h" 43 #endif 44 45 #include "lwip/tcp.h" 46 #include "xil_cache.h" 47 48 #if LWIP_DHCP==1 49 #include "lwip/dhcp.h" 50 #endif 51 52 /* defined by each RAW mode application */ 53 void print_app_header(); 54 int start_application(); 55 int transfer_data(); 56 void tcp_fasttmr(void); 57 void tcp_slowtmr(void); 58 59 /* missing declaration in lwIP */ 60 void lwip_init(); 61 62 #if LWIP_DHCP==1 63 extern volatile int dhcp_timoutcntr; 64 err_t dhcp_start(struct netif *netif); 65 #endif 66 67 extern volatile int TcpFastTmrFlag; 68 extern volatile int TcpSlowTmrFlag; 69 static struct netif server_netif; 70 struct netif *echo_netif; 71 72 void 73 print_ip(char *msg, struct ip_addr *ip) 74 { 75print(msg); 76xil_printf("%d.%d.%d.%d\n\r", ip4_addr1(ip), ip4_addr2(ip), 77ip4_addr3(ip), ip4_addr4(ip)); 78 } 79 80 void 81 print_ip_settings(struct ip_addr *ip, struct ip_addr *mask, struct ip_addr *gw) 82 { 83 84print_ip("Board IP: ", ip); 85print_ip("Netmask : ", mask); 86print_ip("Gateway : ", gw); 87 } 88 89 #if defined (__arm__) && !defined (ARMR5) 90 #if XPAR_GIGE_PCS_PMA_SGMII_CORE_PRESENT == 1 || XPAR_GIGE_PCS_PMA_1000BASEX_CORE_PRESENT == 1 91 int ProgramSi5324(void); 92 int ProgramSfpPhy(void); 93 #endif 94 #endif 95 96 #ifdef XPS_BOARD_ZCU102 97 #ifdef XPAR_XIICPS_0_DEVICE_ID 98 int IicPhyReset(void); 99 #endif 100 #endif 101 102 int main() 103 { 104struct ip_addr ipaddr, netmask, gw; 105 106/* the mac address of the board. this should be unique per board */ 107unsigned char mac_ethernet_address[] = 108{ 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 }; 109 110echo_netif = &server_netif; 111 #if defined (__arm__) && !defined (ARMR5) 112 #if XPAR_GIGE_PCS_PMA_SGMII_CORE_PRESENT == 1 || XPAR_GIGE_PCS_PMA_1000BASEX_CORE_PRESENT == 1 113ProgramSi5324(); 114ProgramSfpPhy(); 115 #endif 116 #endif 117 118 /* Define this board specific macro in order perform PHY reset on ZCU102 */ 119 #ifdef XPS_BOARD_ZCU102 120IicPhyReset(); 121 #endif 122 123init_platform(); 124 125 #if LWIP_DHCP==1 126ipaddr.addr = 0; 127gw.addr = 0; 128netmask.addr = 0; 129 #else 130/* initliaze IP addresses to be used */ 131IP4_ADDR(&ipaddr,192, 168,1, 10); 132IP4_ADDR(&netmask, 255, 255, 255,0); 133IP4_ADDR(&gw,192, 168,1,1); 134 #endif 135print_app_header(); 136 137lwip_init();//網路引數初始化 138 139/* Add network interface to the netif_list, and set it as default */ 140if (!xemac_add(echo_netif, &ipaddr, &netmask, 141&gw, mac_ethernet_address, 142PLATFORM_EMAC_BASEADDR)) { 143xil_printf("Error adding N/W interface\n\r"); 144return -1; 145} 146netif_set_default(echo_netif); 147 148/* now enable interrupts */ 149platform_enable_interrupts(); 150 151/* specify that the network if is up */ 152netif_set_up(echo_netif); 153 154 #if (LWIP_DHCP==1) 155/* Create a new DHCP client for this interface. 156* Note: you must call dhcp_fine_tmr() and dhcp_coarse_tmr() at 157* the predefined regular intervals after starting the client. 158*/ 159dhcp_start(echo_netif); 160dhcp_timoutcntr = 24; 161 162while(((echo_netif->ip_addr.addr) == 0) && (dhcp_timoutcntr > 0)) 163xemacif_input(echo_netif); 164 165if (dhcp_timoutcntr <= 0) { 166if ((echo_netif->ip_addr.addr) == 0) { 167xil_printf("DHCP Timeout\r\n"); 168xil_printf("Configuring default IP of 192.168.1.10\r\n"); 169IP4_ADDR(&(echo_netif->ip_addr),192, 168,1, 10); 170IP4_ADDR(&(echo_netif->netmask), 255, 255, 255,0); 171IP4_ADDR(&(echo_netif->gw),192, 168,1,1); 172} 173} 174 175ipaddr.addr = echo_netif->ip_addr.addr; 176gw.addr = echo_netif->gw.addr; 177netmask.addr = echo_netif->netmask.addr; 178 #endif 179 180print_ip_settings(&ipaddr, &netmask, &gw);//列印關鍵網路引數 181 182/* start the application (web server, rxtest, txtest, etc..) */ 183start_application();//設定回撥函式,這些函式在特定事件發生時以函式指標的方式被呼叫 184 185/* receive and process packets */ 186while (1) { 187if (TcpFastTmrFlag) {//傳送處理,如差錯重傳,通過定時器置位標誌位 188tcp_fasttmr(); 189TcpFastTmrFlag = 0; 190} 191if (TcpSlowTmrFlag) { 192tcp_slowtmr(); 193TcpSlowTmrFlag = 0; 194} 195xemacif_input(echo_netif);//連續接收資料包,並將資料包存入LWIP 196transfer_data();//空函式 197} 198 199/* never reached */ 200cleanup_platform(); 201 202return 0; 203 } echo
整體流程為:初始化LWIP、新增網路介面(MAC)、使能中斷、設定回撥函式。最終進入主迴圈,內部不斷檢測定時器中斷標誌位,當標誌位TcpFastTmrFlag或TcpSlowTmrFlag為1則呼叫相應的處理函式,完成超時重傳等任務。接下來查看回調函式的設定:
int start_application() { struct tcp_pcb *pcb;//protocol control block 簡稱PCB err_t err; unsigned port = 7; /* create new TCP PCB structure */ pcb = tcp_new(); if (!pcb) { xil_printf("Error creating PCB. Out of Memory\n\r"); return -1; } /* bind to specified @port */ err = tcp_bind(pcb, IP_ADDR_ANY, port); if (err != ERR_OK) { xil_printf("Unable to bind to port %d: err = %d\n\r", port, err); return -2; } /* we do not need any arguments to callback functions */ tcp_arg(pcb, NULL); /* listen for connections */ pcb = tcp_listen(pcb); if (!pcb) { xil_printf("Out of memory while tcp_listen\n\r"); return -3; } /* specify callback to use for incoming connections */ tcp_accept(pcb, accept_callback); xil_printf("TCP echo server started @ port %d\n\r", port); return 0; } start_application
建立PCB(protocol control block)建立連線、繫結IP地址和埠號、監聽請求,最後tcp_accept函式用於指定當監聽到連線請求時呼叫的函式accept_callback。進入該函式內部檢視:
1 err_t accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err) 2 { 3static int connection = 1; 4 5/* set the receive callback for this connection */ 6tcp_recv(newpcb, recv_callback); 7 8/* just use an integer number indicating the connection id as the 9callback argument */ 10tcp_arg(newpcb, (void*)(UINTPTR)connection); 11 12/* increment for subsequent accepted connections */ 13connection++; 14 15return ERR_OK; 16 } accept_callback
內部主要通過tcp_recv函式來指定當收到TCP包後呼叫的函式recv_callback。我們再次觀察其內容:
1 err_t recv_callback(void *arg, struct tcp_pcb *tpcb, 2struct pbuf *p, err_t err) 3 { 4/* do not read the packet if we are not in ESTABLISHED state */ 5if (!p) { 6tcp_close(tpcb); 7tcp_recv(tpcb, NULL); 8return ERR_OK; 9} 10 11/* indicate that the packet has been received */ 12tcp_recved(tpcb, p->len); 13 14/* echo back the payload */ 15/* in this case, we assume that the payload is < TCP_SND_BUF */ 16if (tcp_sndbuf(tpcb) > p->len) { 17err = tcp_write(tpcb, p->payload, p->len, 1); 18} else 19xil_printf("no space in tcp_sndbuf\n\r"); 20 21/* free the received pbuf */ 22pbuf_free(p); 23 24return ERR_OK; 25 } recv_callback
tcp_recved函式指示用來告知LWIP接收資料量,然後檢測傳送緩衝區是否足夠容納接收內容,若大於則呼叫tcp_write函式將接收資料寫入傳送緩衝區等待發送。綜上,整體的呼叫流程為:tcp_accept -> accept_callback -> tcp_recv -> recv_callback -> tcp_recved和tcp_write。前四個用於接收,後兩個用於傳送。
函式解析完畢,之後改動上位機網路引數,使PC機IP地址與Board在同一網段內,這裡設定為192.168.1.11.開啟網路除錯助手,設定PC為TCP Client。以下是ZYNQ串列埠列印及網路除錯結果。
三、TCP Client Send data
現在我們來改動demo,設計一個客戶端傳送資料包的示例工程,功能是迴圈傳送一個常數陣列中資料到遠端伺服器。該工程參考米聯客教程中相關章節內容。程式碼如下:
/****************************************************************************** * * Copyright (C) 2009 - 2014 Xilinx, Inc.All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * Use of the Software is limited solely to applications: * (a) running on a Xilinx device, or * (b) that interact with a Xilinx device through a bus or interconnect. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * XILINXBE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Except as contained in this notice, the name of the Xilinx shall not be used * in advertising or otherwise to promote the sale, use or other dealings in * this Software without prior written authorization from Xilinx. * ******************************************************************************/ #include <stdio.h> #include "xparameters.h" #include "netif/xadapter.h" #include "platform.h" #include "platform_config.h" #if defined (__arm__) || defined(__aarch64__) #include "xil_printf.h" #endif #include "lwip/tcp.h" #include "xil_cache.h" #if LWIP_DHCP==1 #include "lwip/dhcp.h" #endif /* defined by each RAW mode application */ void print_app_header(); int client_application(); //int start_application(); //int transfer_data(); int send_data(); void tcp_fasttmr(void); void tcp_slowtmr(void); /* missing declaration in lwIP */ void lwip_init(); #if LWIP_DHCP==1 extern volatile int dhcp_timoutcntr; err_t dhcp_start(struct netif *netif); #endif extern volatile int TcpFastTmrFlag; extern volatile int TcpSlowTmrFlag; static struct netif server_netif; struct netif *echo_netif; void print_ip(char *msg, struct ip_addr *ip) { print(msg); xil_printf("%d.%d.%d.%d\n\r", ip4_addr1(ip), ip4_addr2(ip), ip4_addr3(ip), ip4_addr4(ip)); } void print_ip_settings(struct ip_addr *ip, struct ip_addr *mask, struct ip_addr *gw) { print_ip("Board IP: ", ip); print_ip("Netmask : ", mask); print_ip("Gateway : ", gw); } int main() { uint cycle = 0; struct ip_addr ipaddr, netmask, gw; /* the mac address of the board. this should be unique per board */ unsigned char mac_ethernet_address[] = { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 }; echo_netif = &server_netif; /* Define this board specific macro in order perform PHY reset on ZCU102 */ init_platform(); /* initliaze IP addresses to be used */ IP4_ADDR(&ipaddr,192, 168,1, 10); IP4_ADDR(&netmask, 255, 255, 255,0); IP4_ADDR(&gw,192, 168,1,1); print_app_header(); lwip_init(); /* Add network interface to the netif_list, and set it as default */ if (!xemac_add(echo_netif, &ipaddr, &netmask, &gw, mac_ethernet_address, PLATFORM_EMAC_BASEADDR)) { xil_printf("Error adding N/W interface\n\r"); return -1; } netif_set_default(echo_netif); /* now enable interrupts */ platform_enable_interrupts(); /* specify that the network if is up */ netif_set_up(echo_netif); print_ip_settings(&ipaddr, &netmask, &gw); /* start the application (web server, rxtest, txtest, etc..) */ //start_application(); client_application(); /* receive and process packets */ while (1) { if (TcpFastTmrFlag) { tcp_fasttmr(); TcpFastTmrFlag = 0; } if (TcpSlowTmrFlag) { tcp_slowtmr(); TcpSlowTmrFlag = 0; } xemacif_input(echo_netif); //transfer_data(); if(cycle == 9999){ cycle = 0; send_data(); } else cycle++; } return 0; } main
函式定義:
1 /* 2* tcp_trans.c 3* 4*Created on: 2018年10月18日 5*Author: s 6*/ 7 8 9 #include <stdio.h> 10 #include <string.h> 11 12 #include "lwip/err.h" 13 #include "lwip/tcp.h" 14 #include "lwipopts.h" 15 #include "xil_cache.h" 16 #include "xil_printf.h" 17 #include "sleep.h" 18 19 #define TX_SIZE 10 20 21 static struct tcp_pcb*connected_pcb = NULL; 22 unsigned client_connected = 0; 23 //靜態全域性函式 外部檔案不可見 24 uint tcp_trans_done = 0; 25 26 u_char data[TX_SIZE] = {0,1,2,3,4,5,6,7,8,9}; 27 28 int send_data() 29 { 30err_t err; 31struct tcp_pcb *tpcb = connected_pcb; 32 33if (!tpcb) 34return -1; 35 36//判斷髮送資料長度是否小於傳送緩衝區剩餘可用長度 37if (TX_SIZE < tcp_sndbuf(tpcb)) { 38//Write data for sending (but does not send it immediately). 39err = tcp_write(tpcb, data, TX_SIZE, 1); 40if (err != ERR_OK) { 41xil_printf("txperf: Error on tcp_write: %d\r\n", err); 42connected_pcb = NULL; 43return -1; 44} 45 46//Find out what we can send and send it 47err = tcp_output(tpcb); 48if (err != ERR_OK) { 49xil_printf("txperf: Error on tcp_output: %d\r\n",err); 50return -1; 51} 52} 53else 54xil_printf("no space in tcp_sndbuf\n\r"); 55 56return 0; 57 } 58 59 static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb,u16_t len) 60 { 61tcp_trans_done ++; 62return ERR_OK; 63 } 64 65 //tcp連接回調函式 設定為靜態函式,外部檔案不可見 66 static err_t tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err) 67 { 68/* store state */ 69connected_pcb = tpcb; 70 71/* set callback values & functions */ 72tcp_arg(tpcb, NULL); 73 74//傳送到遠端主機後呼叫tcp_sent_callback 75tcp_sent(tpcb, tcp_sent_callback); 76 77client_connected = 1; 78 79/* initiate data transfer */ 80return ERR_OK; 81 } 82 83 int client_application() 84 { 85struct tcp_pcb *pcb; 86struct ip_addr ipaddr; 87err_t err; 88unsigned port = 7; 89 90/* create new TCP PCB structure */ 91pcb = tcp_new(); 92if (!pcb) { 93xil_printf("Error creating PCB. Out of Memory\n\r"); 94return -1; 95} 96 97/* connect to iperf tcp server */ 98IP4_ADDR(&ipaddr,192, 168,1, 209);//設定要連線的主機的地址 99 100//當連線到主機時,呼叫tcp_connected_callback 101err = tcp_connect(pcb, &ipaddr, port, tcp_connected_callback); 102if (err != ERR_OK) { 103xil_printf("txperf: tcp_connect returned error: %d\r\n", err); 104return err; 105} 106 107return 0; 108 } tcp_trans
可以看出還是一樣的套路,在client_application函式中設定回撥函式。首先新建PCB,tcp_connect函式設定要連線遠端伺服器的IP地址和埠號,連線建立時將呼叫回撥函式tcp_connected_callback。tcp_connected_callback內部tcp_sent函式用於指定當傳送資料包完成後執行的tcp_sent_callback。tcp_sent_callback內部只利用tcp_trans_done變數計數傳送次數。而真正的傳送處理任務則交給主迴圈中的send_data。若處於連線狀態,且傳送緩衝區容量比帶傳送資料量大,則呼叫tcp_write將待發送資料寫入傳送緩衝區,之後呼叫tcp_output函式立即傳輸傳送緩衝區內容。如果不呼叫tcp_output,LWIP會等待資料量達到一定值時一起傳送來提高效率,是否呼叫tcp_output函式可根據具體需求而定。
接下來看下實驗結果:
PC端正確接收到常數陣列,實驗無誤。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
參考文獻:
1 LWIP 無OS RAW-API 函式 - 專注的力量 - CSDN部落格 https://blog.csdn.net/liang890319/article/details/8574603
2 解讀TCP 四種定時器 - xiaofei0859的專欄 - CSDN部落格 https://blog.csdn.net/xiaofei0859/article/details/52794576
3 米聯 《ZYNQ SOC修煉祕籍》