一、前言 之前ZYNQ與PC之間的網路連接依賴於外接硬體協議棧晶元,雖然C驅動非常簡單,但網路帶寬受限。現採用LWIP+PS端MAC控制器+PHY晶元的通用架構。關於LWIP庫,已經有很多現成的資料和書籍。其有兩套API,一個是SOCKET,另一個是本例中要用到的RAW。RAW API理解起來較為復 ...
一、前言
之前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 * 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 }
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 {
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 }
accept_callback
內部主要通過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 }
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 * 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; }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 { 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