基於ZYNQ的千兆網項目(3)

項目簡述

前面的文章講解了TCP客戶機與主機在ZYNQ上面的實現,其實說白了就是調用現成的API函數,這點與FPGA的設計其安全不同,因爲進行FPGA設計必須要完全理解底層纔可以進行相應的設計。本篇博客我們將講解ZYNQ實現UDP協議。關於UDP的理論部分,我們在使用FPGA實現的時候已經進行了完整的講解,詳細請查看基於FPGA的千兆以太網的實現,上面對UDP的特點、組包、CRC進行了詳細的論證。這裏建議大家一定要學習, 雖然即使不明白UDP原理也可以進行相應的學習,但是那樣一來我們和嵌入式工程師相比就完全沒了優勢。

工程描述:講ZYNQ當作UDP來進行與上位機通信,實現千兆網的循環測試。

本次實驗所用到的軟硬件環境如下:
1、VIVADO 2019.1
2、米聯客MZ7015FA開發板
3、NetAssist網絡調試助手

UDP簡述

UDP不像TCP一樣又三次握手甚至四次握手,UDP協議是不可靠傳輸,通常爲了提高其可靠性會在其之上設計額外的傳輸協議, 並加上類似傳輸握手的功能。

用戶數據報協議(UDP):UDP(用戶數據報協議)是一個簡單的面向數據報的傳輸層協議。提供的是非面向連接的、不可靠的數據流傳輸。UDP不提供可靠性,也不提供報文到達確認、排序以及流量控制等功能。它只是把應用程序傳給IP層的數據報發送出去,但是並不能保證它們能到達目的地。因此報文可能會丟失、重複以及亂序等。但由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快。

更加詳細的論述請查看博主之前的博客。

PL端設計

童顏我們主要講解的千兆網的ZYNQ實現,在PL段沒有進行設計,所以我們PL端的設計沒有任何代碼只是例化了一個ZYNQ的IP,如下:
在這裏插入圖片描述
關於ZYNQ實現千兆網的三篇文章其實都是這個PL設計,一模一樣。

PS端設計

這篇文章也用到了開源的系統框架,所以我們也需要設置BSP文件。
首先右擊相應工程的bsp文件,選擇Board Support Package Setting
在這裏插入圖片描述
然後點擊相應的lwip
在這裏插入圖片描述
然後重新生成相應的bsp文件即可。

本例程使用 RAW API,即函數調用不依賴操作系統。傳輸效率也比 SOCKET API 高, (具體可參考 xapp1026)。
將 use_axieth_on_zynq 和 use_emaclite_on_zynq 設爲 0。如下圖所示。
在這裏插入圖片描述
修改 lwip_memory_options 設置,將 mem_size, memp_n_pbuf, mem_n_tcp_pcb, memp_n_tcp_seg 這 4 個參數
值設大,這樣會提高 TCP 傳輸效率。如下圖所示。
在這裏插入圖片描述
修改 pbuf_options 設置,將 pbuf_pool_size 設大,增加可用的 pbuf 數量,這樣同樣會提高 TCP 傳輸效率。如下
圖所示。
在這裏插入圖片描述
修改 tcp_options 設置,將 tcp_snd_buf, tcp_wnd 參數設大,這樣同樣會提高 TCP 傳輸效率。如下圖所示。
在這裏插入圖片描述
修改 temac_adapter_options 設置,將 n_rx_descriptors 和 n_tx_descriptors 參數設大。這樣可以提高 zynq 內部 emac
dma 的數據遷移效率,同樣能提高 TCP 傳輸效率。如下圖所示。
在這裏插入圖片描述
需要手動修改 LWIP 庫讓網口芯片工作於 1000Mbps。
在這裏插入圖片描述
其餘選項的參數默認即可,不用修改。點擊 OK,重建 bsp。 一般情況下,修改完會自動更新,如果沒有更新,手動更新一下,選中 bsp—>右鍵—> Re-generate BSP Sources。重新生成一下 BSP 包。上面進行這樣設置的原因是爲了增加lwip的緩存,進而提高千兆網的通信速度。

這裏的代碼設計與UDP做Server的設計非常相似,這裏總結出幾個不同點,如下:
1、UDP協議沒法設置發送中斷,TCP設置了發送中斷
2、UDP Server自動匹配port,UDP需要我們進行手動設置
3、UDP相應的發送函數udp_send並不是指向數據指針,而是一個網絡變量,TCP則完全是個數據指針,並寫上發送數據的長度,UDP的長度是依靠動態分配內存區間的大小,這裏沒有TCP靈活。

在這裏插入圖片描述
4、UDP代碼不用每隔固定時間就開始測試連接是否正常,但是TCP網絡需要,官方給出的代碼是0.25s。

代碼如下:

#include <stdio.h>
#include "xscugic.h"
#include "xparameters.h"
#include "sleep.h"
#include "xscutimer.h"
#include "lwip/err.h"
#include "lwip/udp.h"
#include "lwip/init.h"
#include "lwipopts.h"
#include "netif/xadapter.h"
#include "lwipopts.h"
#include "lwip/priv/tcp_priv.h"


#define GIC_ID 						XPAR_PS7_SCUGIC_0_DEVICE_ID

#define TCP_RXBUFFER_BASE_ADDR  	0x10000000


int initSwIntr();
int initudp(struct netif *netif);
int udp_recv_init();
void udp_recv_callback(void *arg, struct udp_pcb *tpcb,struct pbuf *p, struct ip4_addr *addr, u16_t port);
void send_received_data();


static XScuGic ScuGic;
static XScuGic_Config * ScuGicCfgPtr;

XScuTimer Timer;
XScuTimer_Config  *Config;

volatile int TcpTmrFlag;
int flag;
int rec_cnt;

static unsigned local_port = 5010;
static unsigned remote_port = 8080;
struct udp_pcb *connected_pcb;
u8 *udp_rx_buffer;
static struct pbuf *pbuf_to_be_sent = NULL;
struct ip4_addr ipaddress;
volatile u32 file_length;


int main()
{
	int status;
	struct netif *netif, server_netif;
	netif = &server_netif;
	status = initSwIntr();
	status = initudp(netif);
	if(status != XST_SUCCESS){
		return status;
	}
	udp_recv_init();
	while(1){

		xemacif_input(netif);//將MAC隊列裏的packets傳輸到你的LwIP/IP stack裏

		/* if connected to the server and received start command,
		 * start receiving data from PL through AXI DMA,
		 * then transmit the data to the PC using TCP
		 * */
		if(flag == 1)
			send_received_data();
	}
	return 0;
}



int initSwIntr(){
	int status;
	Xil_ExceptionInit();
	ScuGicCfgPtr = XScuGic_LookupConfig(GIC_ID);
	status = XScuGic_CfgInitialize(&ScuGic,ScuGicCfgPtr,ScuGicCfgPtr->CpuBaseAddress);
	if(status != XST_SUCCESS){
		return status;
	}
	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler,&ScuGic);
	Xil_ExceptionEnable();

	return XST_SUCCESS;
}


int initudp(struct netif *netif){
	struct ip4_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 };
	/*local ip address*/
	IP4_ADDR(&ipaddr,  192, 168,   2,  10);
	IP4_ADDR(&netmask, 255, 255, 255,  0);
	IP4_ADDR(&gw,      192, 168,   2,  1);
	/*lwip library init*/
	lwip_init();
	/* Add network interface to the netif_list, and set it as default */
	if (!xemac_add(netif, &ipaddr, &netmask, &gw, mac_ethernet_address, XPAR_XEMACPS_0_BASEADDR)) {
		xil_printf("Error adding N/W interface\r\n");
		return -1;
	}
	netif_set_default(netif);
	/* specify that the network if is up */
	netif_set_up(netif);
	return XST_SUCCESS;
}

int udp_recv_init(){
	err_t err;

	udp_rx_buffer = (u8 *)TCP_RXBUFFER_BASE_ADDR;
	connected_pcb = udp_new();
	if (!connected_pcb) {
		xil_printf("txperf: Error creating PCB. Out of Memory\r\n");
		return -1;
	}
	err = udp_bind(connected_pcb, IP_ADDR_ANY, local_port);
	/* connect to tcp server */
	IP4_ADDR(&ipaddress,  192, 168,   2, 26);		/* tcp server address */
	err = udp_connect(connected_pcb, &ipaddress, remote_port);
	if (err != ERR_OK) {
		xil_printf("txperf: tcp_connect returned error: %d\r\n", err);
		return err;
	}

	udp_recv(connected_pcb, (udp_recv_fn)udp_recv_callback, NULL);
	return XST_SUCCESS;
}

void udp_recv_callback(void *arg, struct udp_pcb *tpcb,struct pbuf *p, struct ip4_addr *addr, u16_t port)
{

	struct pbuf *q;
	u32 remain_length;
	flag = 1;
	q = p;
	rec_cnt = q->tot_len;
	/*if received ip fragment packets*/
    if(q->tot_len > q->len){
    	remain_length = q->tot_len;
    	file_length = 0;
    	while(remain_length > 0){
			memcpy(udp_rx_buffer + file_length, q->payload, q->len);
			file_length += q->len;
			remain_length -= q->len;
			/*go to next pbuf pointer*/
			q = q->next;
    	}
	}
    	/*if received no ip fragment packets*/
    else{
    	memcpy(udp_rx_buffer, q->payload, q->len);
    }
    //xil_printf("udp data come in!%d, %d\r\n", p->tot_len, p->len);

	pbuf_free(p);
}

void send_received_data(){
	err_t err;
	flag = 0;
	struct udp_pcb *tpcb = connected_pcb;
	if(rec_cnt < 18){
		pbuf_to_be_sent = pbuf_alloc(PBUF_TRANSPORT, 18, PBUF_POOL);
		memset(pbuf_to_be_sent->payload, 0, 18);
	}
	else{
		pbuf_to_be_sent = pbuf_alloc(PBUF_TRANSPORT, rec_cnt, PBUF_POOL);
	}
	memcpy(pbuf_to_be_sent->payload, (u8 *)udp_rx_buffer, rec_cnt);
	err = udp_send(tpcb, pbuf_to_be_sent);
	if (err != ERR_OK){
		xil_printf("Error on udp_send: %d\r\n", err);
		pbuf_free(pbuf_to_be_sent);
		return;
	}
	pbuf_free(pbuf_to_be_sent);

}


上面的代碼主要是API函數。因爲用到了UDP接收中斷,所以需要初始化GUI中斷控制器:
在這裏插入圖片描述
UDP協議的初始化如下:
在這裏插入圖片描述
UDP協議的接收中斷服務函數如下:
在這裏插入圖片描述
UDP協議發送數據函數:
在這裏插入圖片描述
上面的的代碼整體較簡單,相信同學們可以詳細閱讀,參閱API函數的作用可以學會。

下板測試

我們利用NetAssist網絡調試助手對其進行TCP循環測試,結果如下:
在這裏插入圖片描述
在經過限額是的時候也可以發現,該協議確實要比TCP協議快上一點。

總結

創作不易,認爲文章有幫助的同學們可以關注、點贊、轉發支持。爲行業貢獻及其微小的一部分。對文章有什麼看法或者需要更近一步交流的同學,可以加入下面的羣:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章