項目描述
上一篇文章我們講解了ZYNQ做Client來進行與PC機進行通信,那麼相應的ZYNQ就可以做Server來進行與PC機得通信,此時PC機就是Client。這種通信方式在板卡與板卡之間得通信中還是比較常見,所以就用這篇文章來進行相應得講解。
工程描述:講ZYNQ當作Server來進行與上位機通信,實現千兆網的循環測試。
本次實驗所用到的軟硬件環境如下:
1、VIVADO 2019.1
2、米聯客MZ7015FA開發板
3、NetAssist網絡調試助手
PL端設計
這裏與上一篇文章相同,我們也沒有在PL端進行相應的設計。所以我們PL端的設計沒有任何代碼只是例化了一個ZYNQ的IP,如下:
PS端設計
與前面ZYNQ做從機的文章相同,這篇文章也用到了開源的系統框架,所以我們也需要設置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的緩存,進而提高千兆網的通信速度。
main.c函數
#include <stdio.h>
#include "xscugic.h"
#include "xparameters.h"
#include "sleep.h"
#include "xscutimer.h"
#include "lwip/err.h"
#include "lwip/tcp.h"
#include "lwip/init.h"
#include "lwipopts.h"
#include "netif/xadapter.h"
#include "lwipopts.h"
#include "lwip/priv/tcp_priv.h"
#include "tcp_transmission.h"
#define GIC_ID XPAR_PS7_SCUGIC_0_DEVICE_ID
#define TIMER_IRPT_INTR XPAR_SCUTIMER_INTR
#define TIMER_DEVICE_ID XPAR_XSCUTIMER_0_DEVICE_ID
#define TIMER_LOAD_VALUE 0x13D92D3F/8 //1S
#define TCP_RXBUFFER_BASE_ADDR 0x10000000
#define PC_TCP_SERVER_PORT 5001
void TimerIntrHandler(void *CallBackRef);
int initimer();
int initSwIntr();
int inittcp(struct netif *netif);
int tcp_recv_init();
err_t tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err);
err_t tcp_recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb, u16_t len);
void send_received_data();
static XScuGic ScuGic;
static XScuGic_Config * ScuGicCfgPtr;
XScuTimer Timer;
XScuTimer_Config *Config;
volatile int TcpTmrFlag;
int flag;
int rec_cnt;
int main()
{
int status;
err_t err;
struct netif *netif, server_netif;
netif = &server_netif;
status = initSwIntr();
status = initimer();
status = inittcp(netif);
if(status != XST_SUCCESS){
return status;
}
tcp_recv_init();
while(1){
if(TcpTmrFlag){
if(request_pcb->state == CLOSED || (request_pcb->state == SYN_SENT && request_pcb->nrtx == TCP_SYNMAXRTX)){
request_pcb = tcp_new();
if (!request_pcb) {
xil_printf("txperf: Error creating PCB. Out of Memory\r\n");
return -1;
}
//ip_set_option(request_pcb, SOF_REUSEADDR);
err = tcp_connect(request_pcb, &ipaddress, port, tcp_connected_callback);
if (err != ERR_OK) {
xil_printf("txperf: tcp_connect returned error: %d\r\n", err);
return err;
}
}
tcp_tmr();
TcpTmrFlag = 0;
}
/*receive input packet and control command from emac*/
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(tcp_client_connected && 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);
status = XScuGic_Connect(&ScuGic,TIMER_IRPT_INTR,(Xil_ExceptionHandler)TimerIntrHandler,&Timer);
if(status != XST_SUCCESS){
return status;
}
XScuGic_Enable(&ScuGic,TIMER_IRPT_INTR);
Xil_ExceptionEnable();
return XST_SUCCESS;
}
int initimer(){
int status;
Config = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
status = XScuTimer_CfgInitialize(&Timer, Config, Config->BaseAddr);
XScuTimer_LoadTimer(&Timer, TIMER_LOAD_VALUE);
//自動裝載
XScuTimer_EnableAutoReload(&Timer);
XScuTimer_Start(&Timer);
XScuTimer_EnableInterrupt(&Timer);//一定等定時器初始化好了之後再開始使能定時器中斷
return status;
}
void TimerIntrHandler(void *CallBackRef){
XScuTimer *TimerInstancePtr = (XScuTimer *) CallBackRef;
XScuTimer_ClearInterruptStatus(TimerInstancePtr);
TcpTmrFlag = 1;
}
int inittcp(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 tcp_recv_init()
{
struct tcp_pcb *pcb;
err_t err;
/* create new TCP PCB structure */
tcp_rx_buffer = (u32 *)TCP_RXBUFFER_BASE_ADDR;
pcb = tcp_new();
if (!pcb) {
xil_printf("tcp_server: Error creating PCB. Out of Memory\r\n");
return -1;
}
/* bind to local port */
err = tcp_bind(pcb, IP_ADDR_ANY, local_port);
if (err != ERR_OK) {
xil_printf("tcp_server: Unable to bind to port %d: err = %d\r\n", local_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("tcp_server: Out of memory while tcp_listen\r\n");
return -3;
}
/* specify callback to use for incoming connections */
tcp_accept(pcb, tcp_connected_callback);
return 0;
}
err_t tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err)
{
xil_printf("txperf: Connected to iperf server\r\n");
/* store state */
connected_pcb = tpcb;
/* set callback values & functions */
//tcp_arg(tpcb, NULL);
tcp_sent(connected_pcb, tcp_sent_callback);
tcp_recv(connected_pcb, tcp_recv_callback);
/* disable nagle algorithm to ensure
* the last small segment of a ADC packet will be sent out immediately
* with no delay
* */
//tcp_nagle_disable(tpcb);
//if(!tcp_nagle_disabled(tpcb))
// xil_printf("tcp nagle disable failed!\r\n");
tcp_client_connected = 1;
/* initiate data transfer */
return ERR_OK;
}
err_t tcp_recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
//err_t error;
struct pbuf *q;
u32 remain_length;
q = p;
flag = 1;
rec_cnt = q->tot_len;
/* close socket if the peer has sent the FIN packet */
if (p == NULL) {
tcp_close(tpcb);
xil_printf("tcp connection closed\r\n");
return ERR_OK;
}
/*if received ip fragment packets*/
if(q->tot_len > q->len)
{
remain_length = q->tot_len;
while(remain_length > 0)
{
memcpy(tcp_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(tcp_rx_buffer, q->payload, q->len);
}
/*change the endian of received command*/
*tcp_rx_buffer = ntohl(*tcp_rx_buffer);
//xil_printf("tcp data come in!%d, %d, %08x\r\n", p->tot_len, p->len, *file);
/* tell lwip we've received the tcp packet */
tcp_recved(tpcb, p->tot_len);
pbuf_free(p);
return ERR_OK;
}
static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
err_t err;
tcp_trans_done = 1;
err = tcp_output(tpcb);
if (err != ERR_OK) {
xil_printf("txperf: Error on tcp_output: %d\r\n",err);
return -1;
}
return ERR_OK;
}
void send_received_data()
{
err_t err;
struct tcp_pcb *tpcb = connected_pcb;
flag = 0;
if (!connected_pcb)
return;
/* if tcp send buffer has enough space to hold the data we want to transmit from PL, then start tcp transmission*/
*tcp_rx_buffer = ntohl(*tcp_rx_buffer);
err = tcp_write(tpcb,tcp_rx_buffer , rec_cnt, TCP_WRITE_FLAG_COPY & (~TCP_WRITE_FLAG_MORE));
if (err != ERR_OK) {
xil_printf("txperf: Error on tcp_write: %d\r\n", err);
connected_pcb = NULL;
return;
}
err = tcp_output(tpcb);
if (err != ERR_OK) {
xil_printf("txperf: Error on tcp_output: %d\r\n",err);
return;
}
packet_index++;
}
tcp_transmission.h頭文件
/*
* tcp_transmission.h
*
* Created on: 2017年3月13日
* Author: 201607062058
*/
#ifndef TCP_TRANSMISSION_H_
#define TCP_TRANSMISSION_H_
#include <stdio.h>
#include "xadcps.h"
#include "xil_types.h"
#include "Xscugic.h"
#include "Xil_exception.h"
#define TCP_START_CMD 0xAA55FFA0
#define TCP_STOP_CMD 0xAA55FFB1
#define TCP_RESET_CMD 0xAA55FFC1
#define PC_TCP_SERVER_PORT 5001
static unsigned local_port = 5010;
#define HEADER_ID0 0xAA55AA55
#define HEADER_ID1 0xAA55AA55
#define HEADER_SIZE (16)
#define ADC_PACKET_LENGTH (16 * 1023)
#define TCP_PACKET_SIZE (ADC_PACKET_LENGTH + HEADER_SIZE)
#define TCP_RXBUFFER_BASE_ADDR 0x10000000
volatile int tcp_trans_start;
volatile int tcp_trans_reset;
unsigned first_trans_start;
volatile u32 packet_index;
volatile unsigned tcp_client_connected;
struct tcp_pcb *connected_pcb;
struct tcp_pcb *request_pcb;
volatile int tcp_trans_done;
volatile u32 file_length;
struct ip4_addr ipaddress;
u16_t port;
u32 *tcp_rx_buffer;
typedef struct packet_header
{
u32 ID0;
u32 ID1;
u32 frame_cnt;
u32 length;
}packet_header;
packet_header *header_p;
#endif /* TCP_TRANSMISSION_H_ */
因爲這裏ZYNQ是所爲Server來使用的,所以這邊接收來自Client的請求信號。相應的代碼及中斷函數的設置代碼如下:
上面的TCP協議使用到了我們前面講解的定時器中斷,因爲TCP協議需要保證穩定的連接,每隔一定時間檢測連接的穩定性。代碼如下:
TCP協議初始化代碼:
TCP協議中斷服務函數如下:
ZYNQ接收中斷響應函數,可以看出,這部分的處理與上一篇ZYNQ做Client的文章非常相似,其實只要是ZYNQ接收中斷幾乎都是下面的代碼。
ZYNQ作爲Server發射數據的函數:
ZYNQ發射中斷服務函數:
因爲這篇文章與上一篇ZYNQ做Client的文章非常相似,包括我們驗證的方法都是循環測試,所以可以先嚐試調通上面文章中的程序再來調試這篇博客。
下板測試
我們利用NetAssist網絡調試助手對其進行TCP循環測試,結果如下:
這裏特別注意一定要讓ZYNQ跑起來,否則無法正常連接,因爲主機沒工作。從上面接過可以看出PC機發送數據與接收數據相一致,且測試了7328個數據,進而證明了我們實驗的正確性。
總結
創作不易,認爲文章有幫助的同學們可以關注、點贊、轉發支持。爲行業貢獻及其微小的一部分。對文章有什麼看法或者需要更近一步交流的同學,可以加入下面的羣: