基于ZYNQ的千兆网项目(3)

基于ZYNQ的UDP实现

  • 项目简述
  • UDP简述
  • PL端设计
  • PS端设计
  • 下板测试
  • 总结

项目简述

前面的文章讲解了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 
#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中断控制器:
基于ZYNQ的千兆网项目(3)_第1张图片
UDP协议的初始化如下:
基于ZYNQ的千兆网项目(3)_第2张图片
UDP协议的接收中断服务函数如下:
基于ZYNQ的千兆网项目(3)_第3张图片
UDP协议发送数据函数:
基于ZYNQ的千兆网项目(3)_第4张图片
上面的的代码整体较简单,相信同学们可以详细阅读,参阅API函数的作用可以学会。

下板测试

我们利用NetAssist网络调试助手对其进行TCP循环测试,结果如下:
基于ZYNQ的千兆网项目(3)_第5张图片
在经过限额是的时候也可以发现,该协议确实要比TCP协议快上一点。

总结

创作不易,认为文章有帮助的同学们可以关注、点赞、转发支持。为行业贡献及其微小的一部分。对文章有什么看法或者需要更近一步交流的同学,可以加入下面的群:
在这里插入图片描述

你可能感兴趣的:(FPGA,Xilinx,FPGA开发,ZYNQ,VIVADO)