1、前言web
以前ZYNQ與PC之間的網絡鏈接依賴於外接硬件協議棧芯片,雖然C驅動很是簡單,但網絡帶寬受限。現採用LWIP+PS端MAC控制器+PHY芯片的通用架構。關於LWIP庫,已經有不少現成的資料和書籍。其有兩套API,一個是SOCKET,另外一個是本例中要用到的RAW。RAW API理解起來較爲複雜,整個程序基於中斷機制運行,經過函數指針完成多層回調函數的執行。SOCKET API須要支持多線程操做系統的支持,也犧牲了效率,但理解和編程都較爲容易。實際上SOCKET API是對RAW API的進一步封裝。編程
2、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 * XILINX BE 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 { 75 print(msg); 76 xil_printf("%d.%d.%d.%d\n\r", ip4_addr1(ip), ip4_addr2(ip), 77 ip4_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
84 print_ip("Board IP: ", ip); 85 print_ip("Netmask : ", mask); 86 print_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 { 104 struct ip_addr ipaddr, netmask, gw; 105
106 /* the mac address of the board. this should be unique per board */
107 unsigned char mac_ethernet_address[] =
108 { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 }; 109
110 echo_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
113 ProgramSi5324(); 114 ProgramSfpPhy(); 115 #endif
116 #endif
117
118 /* Define this board specific macro in order perform PHY reset on ZCU102 */
119 #ifdef XPS_BOARD_ZCU102 120 IicPhyReset(); 121 #endif
122
123 init_platform(); 124
125 #if LWIP_DHCP==1
126 ipaddr.addr = 0; 127 gw.addr = 0; 128 netmask.addr = 0; 129 #else
130 /* initliaze IP addresses to be used */
131 IP4_ADDR(&ipaddr, 192, 168, 1, 10); 132 IP4_ADDR(&netmask, 255, 255, 255, 0); 133 IP4_ADDR(&gw, 192, 168, 1, 1); 134 #endif
135 print_app_header(); 136
137 lwip_init();//網絡參數初始化
138
139 /* Add network interface to the netif_list, and set it as default */
140 if (!xemac_add(echo_netif, &ipaddr, &netmask, 141 &gw, mac_ethernet_address, 142 PLATFORM_EMAC_BASEADDR)) { 143 xil_printf("Error adding N/W interface\n\r"); 144 return -1; 145 } 146 netif_set_default(echo_netif); 147
148 /* now enable interrupts */
149 platform_enable_interrupts(); 150
151 /* specify that the network if is up */
152 netif_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 */
159 dhcp_start(echo_netif); 160 dhcp_timoutcntr = 24; 161
162 while(((echo_netif->ip_addr.addr) == 0) && (dhcp_timoutcntr > 0)) 163 xemacif_input(echo_netif); 164
165 if (dhcp_timoutcntr <= 0) { 166 if ((echo_netif->ip_addr.addr) == 0) { 167 xil_printf("DHCP Timeout\r\n"); 168 xil_printf("Configuring default IP of 192.168.1.10\r\n"); 169 IP4_ADDR(&(echo_netif->ip_addr), 192, 168, 1, 10); 170 IP4_ADDR(&(echo_netif->netmask), 255, 255, 255, 0); 171 IP4_ADDR(&(echo_netif->gw), 192, 168, 1, 1); 172 } 173 } 174
175 ipaddr.addr = echo_netif->ip_addr.addr; 176 gw.addr = echo_netif->gw.addr; 177 netmask.addr = echo_netif->netmask.addr; 178 #endif
179
180 print_ip_settings(&ipaddr, &netmask, &gw);//打印關鍵網絡參數
181
182 /* start the application (web server, rxtest, txtest, etc..) */
183 start_application();//設置回調函數,這些函數在特定事件發生時以函數指針的方式被調用
184
185 /* receive and process packets */
186 while (1) { 187 if (TcpFastTmrFlag) {//發送處理,如差錯重傳,經過定時器置位標誌位
188 tcp_fasttmr(); 189 TcpFastTmrFlag = 0; 190 } 191 if (TcpSlowTmrFlag) { 192 tcp_slowtmr(); 193 TcpSlowTmrFlag = 0; 194 } 195 xemacif_input(echo_netif);//連續接收數據包,並將數據包存入LWIP
196 transfer_data();//空函數
197 } 198
199 /* never reached */
200 cleanup_platform(); 201
202 return 0; 203 }
總體流程爲:初始化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; }
建立PCB(protocol control block)創建鏈接、綁定IP地址和端口號、監聽請求,最後tcp_accept函數用於指定當監聽到鏈接請求時調用的函數accept_callback。進入該函數內部查看:多線程
1 err_t accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err) 2 { 3 static int connection = 1; 4
5 /* set the receive callback for this connection */
6 tcp_recv(newpcb, recv_callback); 7
8 /* just use an integer number indicating the connection id as the 9 callback argument */
10 tcp_arg(newpcb, (void*)(UINTPTR)connection); 11
12 /* increment for subsequent accepted connections */
13 connection++; 14
15 return ERR_OK; 16 }
內部主要經過tcp_recv函數來指定當收到TCP包後調用的函數recv_callback。咱們再次觀察其內容:架構
1 err_t recv_callback(void *arg, struct tcp_pcb *tpcb, 2 struct pbuf *p, err_t err) 3 { 4 /* do not read the packet if we are not in ESTABLISHED state */
5 if (!p) { 6 tcp_close(tpcb); 7 tcp_recv(tpcb, NULL); 8 return ERR_OK; 9 } 10
11 /* indicate that the packet has been received */
12 tcp_recved(tpcb, p->len); 13
14 /* echo back the payload */
15 /* in this case, we assume that the payload is < TCP_SND_BUF */
16 if (tcp_sndbuf(tpcb) > p->len) { 17 err = tcp_write(tpcb, p->payload, p->len, 1); 18 } else
19 xil_printf("no space in tcp_sndbuf\n\r"); 20
21 /* free the received pbuf */
22 pbuf_free(p); 23
24 return ERR_OK; 25 }
tcp_recved函數指示用來告知LWIP接收數據量,而後檢測發送緩衝區是否足夠容納接收內容,若大於則調用tcp_write函數將接收數據寫入發送緩衝區等待發送。綜上,總體的調用流程爲:tcp_accept -> accept_callback -> tcp_recv -> recv_callback -> tcp_recved和tcp_write。前四個用於接收,後兩個用於發送。app
函數解析完畢,以後改動上位機網絡參數,使PC機IP地址與Board在同一網段內,這裏設置爲192.168.1.11.打開網絡調試助手,設置PC爲TCP Client。如下是ZYNQ串口打印及網絡調試結果。 tcp
3、TCP Client Send dataide
如今咱們來改動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 * XILINX BE 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; }
函數定義:
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 { 30 err_t err; 31 struct tcp_pcb *tpcb = connected_pcb; 32 33 if (!tpcb) 34 return -1; 35 36 //判斷髮送數據長度是否小於發送緩衝區剩餘可用長度 37 if (TX_SIZE < tcp_sndbuf(tpcb)) { 38 //Write data for sending (but does not send it immediately). 39 err = tcp_write(tpcb, data, TX_SIZE, 1); 40 if (err != ERR_OK) { 41 xil_printf("txperf: Error on tcp_write: %d\r\n", err); 42 connected_pcb = NULL; 43 return -1; 44 } 45 46 //Find out what we can send and send it 47 err = tcp_output(tpcb); 48 if (err != ERR_OK) { 49 xil_printf("txperf: Error on tcp_output: %d\r\n",err); 50 return -1; 51 } 52 } 53 else 54 xil_printf("no space in tcp_sndbuf\n\r"); 55 56 return 0; 57 } 58 59 static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb,u16_t len) 60 { 61 tcp_trans_done ++; 62 return 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 */ 69 connected_pcb = tpcb; 70 71 /* set callback values & functions */ 72 tcp_arg(tpcb, NULL); 73 74 //發送到遠程主機後調用tcp_sent_callback 75 tcp_sent(tpcb, tcp_sent_callback); 76 77 client_connected = 1; 78 79 /* initiate data transfer */ 80 return ERR_OK; 81 } 82 83 int client_application() 84 { 85 struct tcp_pcb *pcb; 86 struct ip_addr ipaddr; 87 err_t err; 88 unsigned port = 7; 89 90 /* create new TCP PCB structure */ 91 pcb = tcp_new(); 92 if (!pcb) { 93 xil_printf("Error creating PCB. Out of Memory\n\r"); 94 return -1; 95 } 96 97 /* connect to iperf tcp server */ 98 IP4_ADDR(&ipaddr, 192, 168, 1, 209);//設置要鏈接的主機的地址 99 100 //當鏈接到主機時,調用tcp_connected_callback 101 err = tcp_connect(pcb, &ipaddr, port, tcp_connected_callback); 102 if (err != ERR_OK) { 103 xil_printf("txperf: tcp_connect returned error: %d\r\n", err); 104 return err; 105 } 106 107 return 0; 108 }
能夠看出仍是同樣的套路,在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修煉祕籍》