[X]关闭
7

S02-CH27 利用LWIP实现以太网数据传输

摘要: 软件版本:VIVADO2017.4操作系统:WIN10 64bit硬件平台:适用米联客 ZYNQ系列开发板米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!27.1 概述 本例程实现PL端发送数据,通过DMA将数据传输到P ...

软件版本:VIVADO2017.4

操作系统:WIN10 64bit

硬件平台:适用米联客 ZYNQ系列开发板

米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!

27.1 概述

      本例程实现PL端发送数据,通过DMA将数据传输到PS端DDR,PS端使用TCP协议将数据发送至PC端的功能,具体实现如下:

     PS 通过AXI GPIO IP核启动PL 不间断循环构造32bit位宽的0~511的数据,利用AXI DMA IP核,通过 PS 的 Slave AXI GP 接口传输至 PS DDR3 的乒乓缓存中。PL 每发完一次 0~511,AXI DMA IP便会产生一个中断信号,PS 得到中断信号后将 DDR3 缓存的数据通过乒乓操作的方式由 TCP 协议发送至 PC机。稍作修改就可以用于将ADC 采集或者图像视频采集数据,通过以太网发送的开发。 

     本例程硬件架构在《AXI_DMA_LOOP 环路测试》基础上修改,如果有不清楚的,请学习《AXI_DMA_LOOP 环路测试》部分内容。

27.2 搭建硬件系统

27.2.1 系统构架

      系统框架如图所示,实际上,这个系统是在AXI_DMA_LOOP 环路测试架构的基础上进行修改。去掉FPGA 读DMA 通道,只有FPGA 往DMA 写输入数据,当 DMA 接收中断产生后,通过 LWIP 协议,把数据通过网口发送出去。网口连接在PS 的ARM 端口。

27.2.2 ZYNQ IP配置

27.2.2.1 启用 HP 接口

      双击 ZYNQ 的IP 之后启动HP 接口,这里只要用到1 个 HP 接口通道。

27.2.2.2 启用PL 到PS 的中断资源

27.2.2.3 启动PS 部分的以太网接口

27.2.2.4 时钟的设置

      将FCLK_CLK0和FCLK_CLK1均设为100Mhz,其中CLK1为PL构造数据部分逻辑的时钟源,在实际应用中可自由调节时钟频率,故与CLK0分开使用。设置如下图所示。

27.2.3 DMA IP 配置

       由于只用到了写通道,因次,只要把勾选写DMA通道既可以,如下图所示:

27.2.4 GPIO的配置

双击axi_gpio_0。设置如下图所示

27.2.5 配置axi_ data_fifo _0 

      双击axis_data_fifo_0,设置如下图所示。fifo在本例程中作为axi_dma_0的S_AXIS_S2MM接口所在的FCLK0时钟域与外部数据生成逻辑stream接口所在的FCLK1时钟域之间的转换媒介。双击S_AXIS设置参数

设置如下

双击FIFO 进行如下设置

27.2.7 地址空间映射

打开Address Editor,设置如下图所示

27.3 FPGA的发送代码

      在生成的顶层文件中,添加发送代码。每次发送512x32bit的数据,即512x32/8=2048 Byte,通过DMA发送到DDR内存。

always@(posedge FCLK_CLK1)

 begin

     if(!peripheral_aresetn) begin

         S_AXIS_tvalid <= 1'b0;

         S_AXIS_tdata <= 32'd0;

         S_AXIS_tlast <= 1'b0;

         state <=0;

     end

     else begin

        case(state)

          0: begin

              if(gpio_tri_o_0&& S_AXIS_tready) begin

                 S_AXIS_tvalid <= 1'b1;

                 state <= 1;

              end

              else begin

                 S_AXIS_tvalid <= 1'b0;

                 state <= 0;

              end

            end

          1:begin

               if(S_AXIS_tready) begin

                   S_AXIS_tdata <= S_AXIS_tdata + 1'b1;

                   if(S_AXIS_tdata == 16'd510) begin

                      S_AXIS_tlast <= 1'b1;

                      state <= 2;

                   end

                   else begin

                      S_AXIS_tlast <= 1'b0;

                      state <= 1;

                   end

               end

               else begin

                  S_AXIS_tdata <= S_AXIS_tdata;                   

                  state <= 1;

               end

            end       

          2:begin

               if(!S_AXIS_tready) begin

                  S_AXIS_tvalid <= 1'b1;

                  S_AXIS_tlast <= 1'b1;

                  S_AXIS_tdata <= S_AXIS_tdata;

                  state <= 2;

               end

               else begin

                  S_AXIS_tvalid <= 1'b0;

                  S_AXIS_tlast <= 1'b0;

                  S_AXIS_tdata <= 32'd0;

                  state <= 0;

               end

            end

         default: state <=0;

         endcase

     end              

 end

27.4 PS部分BSP设置

27.4.1 SDK工程BSP设置

进入sdk后,新建一个名为AXI_DMA_PL_PS_LWIP空工程,同时创建相应的bsp。

点击Modify this BSP’s Settings,对AXI_DMA_PL_PS_LWIP_bsp设置。使能lwip 1.4.1函数库。如下图所示。

27.4.2 lwip函数库设置

       点击lwip141,进行如下设置。

    本例程使用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传输效率。如下图所示。

      MZ7X工业级开发板板载网口芯片是RTL8211FDI,由于默认的驱动不支持RTL8211FDI 的寄存器配置,所以无法支持自动适应速度 (通过自己修改库可以实现自动适应但是工作量大,考虑到投入时间和产出比,这里就不修改了)。所以需要手动修改 LWIP 库让网口芯片工作于1000Mbps。

27.5 PS部分程序分析

27.5.1 main.c分析

main函数的主要流程为:

1):初始化并配置PL侧的AXI GPIO

2):初始化并配置PL侧的AXI DMA

3):初始化并配置PS的中断控制器

4):初始化lwip协议栈和PS的以太网控制器

5):配置TCP传输所需的相关参数,并与服务器建立TCP连接

6):通过AXI GPIO启动PL进行数据生成和传输

7):通过AXI DMA接收PL传输的数据,通过TCP发送至PC机,并不断循环该过程。


#include "dma_intr.h"

#include "timer_intr.h"

#include "sys_intr.h"

#include "xgpio.h"

//#include "OLED.h"


#include "lwip/err.h"

#include "lwip/tcp.h"

#include "lwipopts.h"

#include "netif/xadapter.h"

#include "lwipopts.h"



static  XScuGic Intc; //GIC

static  XScuTimer Timer;//timer

XAxiDma AxiDma;

u16 *RxBufferPtr[2];  /* ping pong buffers*/


volatile u32 RX_success;

volatile u32 TX_success;


volatile u32 RX_ready=1;

volatile u32 TX_ready=1;


#define TIMER_LOAD_VALUE    XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ / 8 //0.25S



extern void send_received_data(void);

extern unsigned tcp_client_connected;


char oled_str[17]="";

static XGpio Gpio;


#define AXI_GPIO_DEV_ID         XPAR_AXI_GPIO_0_DEVICE_ID


int init_intr_sys(void)

{

DMA_Intr_Init(&AxiDma,0);//initial interrupt system

Timer_init(&Timer,TIMER_LOAD_VALUE,0);

Init_Intr_System(&Intc); // initial DMA interrupt system

Setup_Intr_Exception(&Intc);

DMA_Setup_Intr_System(&Intc,&AxiDma,0,RX_INTR_ID);//setup dma interrpt system

Timer_Setup_Intr_System(&Intc,&Timer,TIMER_IRPT_INTR);

DMA_Intr_Enable(&Intc,&AxiDma);


}


int main(void)

{


int Status;

struct netif *netif, server_netif;

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 };


/* Initialize the ping pong buffers for the data received from axidma */

RxBufferPtr[0] = (u16 *)RX_BUFFER0_BASE;

RxBufferPtr[1] = (u16 *)RX_BUFFER1_BASE;


XGpio_Initialize(&Gpio, AXI_GPIO_DEV_ID);

XGpio_SetDataDirection(&Gpio, 1, 0);

init_intr_sys();

TcpTmrFlag = 0;


netif = &server_netif;

//**************************此处设置的是开发板IP地址******************

IP4_ADDR(&ipaddr,  192, 168,   1,  10);

IP4_ADDR(&netmask, 255, 255, 255,  0);

IP4_ADDR(&gw,      192, 168,   1,  10);

//**********************************************************************

/*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);


/* initialize tcp pcb */

tcp_send_init();


XGpio_DiscreteWrite(&Gpio, 1, 1);

//oled_fresh_en();// enable oled

Timer_start(&Timer);


while (1)

{

/* call tcp timer every 250ms */

if(TcpTmrFlag)

{

tcp_tmr();

TcpTmrFlag = 0;

}

/*receive input packet from emac*/

xemacif_input(netif);//将MAC队列里的packets传输到你的LwIP/IP stack里

/* if connected to the server, start receive data from PL through axidma, then transmit the data to the PC software by TCP*/

if(tcp_client_connected)

send_received_data();

}

return 0;


}

27.5.2 AXI DMA数据传输过程 

      例程中axi dma采用了simple transfer方式,通过XAxiDma_SimpleTransfer函数完成。每次dma传输都需要PS主动发起,PS通过AXI总线配置PL侧axi dma内部寄存器,发起一次dma传输。dma传输发起后,axi dma开始通过S_AXIS_S2MM接口接收数据,当其中的tlast信号被拉高,则代表当次传输所需要的数据发送完毕,当该次dma传输结束,axi dma通过s2mm_introut产生中断信号,触发PS中断控制器产生中断,PS通过中断服务函数Dma_RxIsr清除axi dma的中断状态,在DM中断函数中,拉高dma完成指示信号packet_trans_done,一次完整的simple transfer的dma传输结束。下面是dma中断接收函数,接收来自PL的中断信号,并且设置packet_trans_done。

DMA_RxIntrHandler DMA中断接收函数

/*****************************************************************************/

/*

*

* This is the DMA RX interrupt handler function

*

* It gets the interrupt status from the hardware, acknowledges it, and if any

* error happens, it resets the hardware. Otherwise, if a completion interrupt

* is present, then it sets the RxDone flag.

*

* @param Callback is a pointer to RX channel of the DMA engine.

*

* @return None.

*

* @note None.

*

******************************************************************************/

static void DMA_RxIntrHandler(void *Callback)

{

u32 IrqStatus;

int TimeOut;

XAxiDma *AxiDmaInst = (XAxiDma *)Callback;


/* Read pending interrupts */

IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);


/* Acknowledge pending interrupts */

XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);


/*

 * If no interrupt is asserted, we do not do anything

 */

if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {

return;

}


/*

 * If error interrupt is asserted, raise error flag, reset the

 * hardware to recover from the error, and return with no further

 * processing.

 */

if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {

xil_printf("rx error! \r\n");

return;

}


/*

 * If completion interrupt is asserted, then set RxDone flag

 */

if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {

if(packet_trans_done)

xil_printf("last transmission has not finished!\r\n");

else

/*set the axidma done flag*/

    packet_trans_done = 1;

}

}


       PS的dma数据接收采用了乒乓操作的模式,两个缓冲区交替进行数据接收。

       需要注意的是, XAxiDma_SimpleTransfer函数中Length,以字节为单位,每次发起dma时,所设置的Length的值必须大于或等于PL实际传输的数据长度,否则会出现错误。本例程中设置的长度为2048字节。

       first_trans_start是为了进行第一次先进行一次DMA中断传输,这样完成后设置first_trans_start为0。以后每次完成网络传输后,再启动DMA接受。

       TCP数据包的发送主要依赖于tcp_write和tcp_output两个函数,tcp_write将所需要发送的数据写入tcp发送缓冲区等待发送,tcp_output函数则将缓存区内数据包发送出去。在发送TCP数据包时,这两个函数往往要同时配合使用。收发送函数的具体源码如下所示。

send_received_data() 发送函数源码

void send_received_data()

{

#if __arm__

int copy = 3;

#else

int copy = 0;

#endif

err_t err;

int Status;

struct tcp_pcb *tpcb = connected_pcb;


/*initial the first axdma transmission, only excuse once*/

if(!first_trans_start)

{

Status = XAxiDma_SimpleTransfer(&AxiDma, (u32)RxBufferPtr[0],

(u32)(PAKET_LENGTH), XAXIDMA_DEVICE_TO_DMA);

if (Status != XST_SUCCESS)

{

xil_printf("axi dma failed! 0 %d\r\n", Status);

return;

}

/*set the flag, so this part of code will not excuse again*/

first_trans_start = 1;

}


/*if the last axidma transmission is done, the interrupt triggered, then start TCP transmission*/

if(packet_trans_done)

{


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*/

if (tcp_sndbuf(tpcb) > SEND_SIZE)

{

/*transmit received data through TCP*/

err = tcp_write(tpcb, RxBufferPtr[packet_index & 1], SEND_SIZE, copy);

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++;

/*clear the axidma done flag*/

packet_trans_done = 0;


/*initial the other axidma transmission when the current transmission is done*/

Status = XAxiDma_SimpleTransfer(&AxiDma, (u32)RxBufferPtr[(packet_index + 1)&1],

(u32)(PAKET_LENGTH), XAXIDMA_DEVICE_TO_DMA);

if (Status != XST_SUCCESS)

{

xil_printf("axi dma %d failed! %d \r\n", (packet_index + 1), Status);

return;

}


}

}

}

27.5.3 TCP发送流程

      在本例程中,zynq作为客户端,PC作为服务器。由zynq向PC主动发起TCP连接请求,通过tcp_connect函数便可以完成这个过程。该函数的参数包含了一个回调函数指针tcp_connected_fn,该回调函数将在TCP连接请求三次握手完成后被自动调用。该回调函数被调用时代表客户端和服务器之间的TCP连接建立完成。在本例程中,该回调函数被定义为tcp_connected_callback,在该函数中,拉高连接建立完成信号tcp_client_connected,并通过tcp_sent函数配置另一个TCP发送完成的回调函数。该回调函数在每个TCP包发送完成后会被自动调用,代表TCP包发送完成。该回调函数在本例程中被定义为tcp_sent_callback,仅作发送完成数据包的计数。

tcp_connected_callback 回调函数

static 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(tpcb, tcp_sent_callback);


tcp_client_connected = 1;

/* initiate data transfer */

return ERR_OK;

}


tcp_sent_callback 回调函数

/*this fuction just used to count the tcp transmission times*/

static err_t

tcp_sent_callback(void *arg, struct tcp_pcb *tpcb, u16_t len)

{

tcp_trans_done ++;

return ERR_OK;

}

27.5.4 开发板与电脑IP地址设置

开发板IP地址在main.c文件中设置。

IP4_ADDR(&ipaddr,  192, 168,   1,  10);

IP4_ADDR(&netmask, 255, 255, 255,  0);

IP4_ADDR(&gw,      192, 168,   1,  10);

电脑IP地址设置在tcp_transmission.c文件中设置。

/* connect to iperf tcp server */

IP4_ADDR(&ipaddress,  192, 168, 1, 209); /* iperf server address */

port = 5001; /* iperf default port */

27.6 连接测试

       把开发板网卡通过网线接到PC网口上,修改 IP地址如下图

      打开网络调试助手,第一次用的时候windows会提示你是否允许访问网络一定要选择是,否则你就无法通信了。设置电脑为TCP Server 本机IP为刚才设置的192.168.1.209 端口号为5001.

       在SDK里面打开串口,并且启动SDK调试(调试方法前面已经讲过),板子是client可以看到成功连接到了PCB上。

       这个时候可以看到网络调试助手接收到数据了,由于数据量大,刷新数据显示,会导致电脑变慢,这里把勾选暂停显示。右下角是接收数据的计数器,可以看到计数器在飞奔中。

       网络调试助手暂停接收后,现在检测下网速,可以看到时间速度在500Mbps/s左右查看网速,大概是在62MB/S-70MB/S的速度,当然我们也可以通过优化实现更高速度的传输。

    在SDK里面设定内存空间的查看地址,查看内存中的数据,可以看到正是PL 发出的数据。

用wireshark抓包,并分析数据

用wireshark导出纯裸数据,并且分析数据

27.7 连不上调试助手解决

       当连不上调试助手的时候,把防火墙全部关掉。如果测试的网速过低,请关闭其他占用网速的软件,并查看是否在网络调试助手暂停接收后观察网速。

27.8注意事项

      如果重新打开程序,或者升级程序后遇到lwip库无法编译,可以尝试重新勾选下BSP里面的lwip库


路过

雷人

握手

鲜花

鸡蛋
发表评论

最新评论

引用 。_lr7Im 2024-1-3 12:04
很奇怪我用乒乓操作 其中一个缓存区的数据不更新,另一个正常,用的例程代码
引用 F._mZH4w 2023-12-1 16:49
网络调试助手没有接收到数据,是怎么回事
引用 abkai wehiyehe 2022-1-13 15:36
eydapbbe: 您好,按照上述流程操作,发现link speed for phy address 7:1000后并没有输出txperf: Connected to iperf server,也就是说没有进入connect回调函数,请问这种 ...
应该是网络调试助手未设置准确,或未打开
引用 abkai wehiyehe 2022-1-11 21:21
雪莲: 使用的是千兆网口,现在实测出来的速率是500Mbps,那网口速率还能再提高吗?需要优化哪些函数?
同问,请问您解决这个问题了吗?
引用 eydapbbe 2022-1-9 17:51
您好,按照上述流程操作,发现link speed for phy address 7:1000后并没有输出txperf: Connected to iperf server,也就是说没有进入connect回调函数,请问这种问题如何处理
引用 雪莲 2020-11-16 10:39
使用的是千兆网口,现在实测出来的速率是500Mbps,那网口速率还能再提高吗?需要优化哪些函数?
引用 GReeeeeeeN!! 2020-3-29 10:26
请问本课程的完整工程可以在哪里下载?

查看全部评论(7)

本文作者
2019-9-6 19:35
  • 7
    粉丝
  • 17483
    阅读
  • 7
    回复

关注米联客

扫描关注,了解最新资讯

联系人:汤经理
电话:0519-80699907
EMAIL:270682667@qq.com
地址:常州溧阳市天目云谷3号楼北楼201B
热门评论
排行榜