[米联客-XILINX-H3_CZ08_7100] FPGA_SDK高级篇连载-17DAQ7606 以太网 LWIP TCP 传输方案(FDMA)

软件版本:VIVADO2021.1

操作系统:WIN10 64bit

硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA

实验平台:米联客-MLK-H3-CZ08-7100开发板

板卡获取平台:https://milianke.tmall.com/

登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!

目录

1概述

2系统框图

3硬件电路分析

4搭建SOC系统工程

4.1PL图形化编程

1:uifdma_dbuf设置

2:uiFDMA设置

3:AXI Interconnect设置

4:修改system_wrapper.v

4.2设置地址分配

4.3添加PIN约束

4.4编译并导出平台文件

5搭建Vitis-sdk工程

5.1创建SDK Platform工程

5.2创建DAQ001 _lwip工程

6SDK程序分析

6.1FDMA数据接收原理

1:PS_RX_intr_Handler

6.2数据包设计

6.3帧头设计

6.4主程序分析

1:init_intr_sys()

2:lwip_init()

3:xemac_add ()

4:netif_set_default()

5:platform_enable_interrupts()

6:启动DHCP配置

7:start_appication()

8:while循环

9:tcp_send_perf_traffic()函数

10:本地IP地址设置

11:远程主机IP设置

7方案演示

7.1硬件准备

7.2实验结果


1概述

DAQ7606是一款8通道16bit 200k采样率的高精度ADC,支持串行和并行采集接口。米联客DAQ7606采用串行模式实现200K 8通道同时采样,相比并口方式,串行方式,具有硬件接口简单,节约成本优势。

实验目的:

1:掌握uifdmadbuf配置成非视频模式的情况下的参数设置

2:掌握ADC数据如何通过uifdmadbuf写入到uifdma ip

3:ps实现fdma中断到来,读取DDR缓存中的数据

4:使用lwip tcp方式把采集的数据从DDR中发送出去

2系统框图

PS 通过AXI GPIO IP核启动PL不间断循环构造128bit位宽的1024个数据,利用AXI DMA IP 核,通过 PS的 Slave AXI GP接口传输至PS DDR的乒乓缓存中。PL 每发完一次1024个,AXI DMA IP 核便会产生一个中断信号,PS 得到中断信号后将DDR缓存的数据通过3缓存操作的方式由TCP协议发送至PC机。

FPGA端发只发送数据,每次发送128bit*1024大小的数据包,即8通道16bit ADC 长度是1024

FPGA端数据格式如下:

ADC 数据通道

127:0

127:112

111:96

95:80

79:64

63:48

47:32

31:16

15:0

ADC7

ADC6

ADC5

ADC4

ADC3

ADC2

ADC1

ADC0

ARM端的数据格式和FPGA端存在大小端的问题,以及排列顺序差异,具体数据在ARM端的定义如下:

typedef struct packet_data
{
	u16 ADC0;
	u16 ADC1;
	u16 ADC2;
	u16 ADC3;
	u16 ADC4;
	u16 ADC5;
	u16 ADC6;
	u16 ADC7;
}packet_data;

ARM端收到FPGA通过DMA发送到DDR中的数据后,组织数据以数据包的形式打包好发送出去。首先发送帧头,帧头有16字节即128bits数据,数据格式如下:

帧头 128bits

特殊字符

特殊字符

采样率

精度  通道 符号位

采样长度

帧计数器

HEADER1

HEADER2

KSPS

Resolution

Channels

Sign bit

length

frame counter

32bits

32bits

16bits

8bits

4bits

4bitss

16bits

16bits

0XAA55AA55

0XAA55AA55

200

16

8

1

1024

frame_counter

帧头数据格式定义如下:

typedef struct packet_header
{
	u32 ID0;
	u32 ID1;
	u16 KSPS;
	u8  Resolution;
	u8  channels_signbit;
	u16 length
	u16 fram_counter
}packet_header;

以太网接收控制命令协议格式

启动:0xAA55FFA0

停止:0xAA55FFB1

3硬件电路分析

硬件接口和子卡模块请阅读“附录1”

配套工程的FPGA PIN脚定义路径为soc_prj/uisrc/04_pin/ fpga_pin.xdc。

4搭建SOC系统工程

4.1PL图形化编程

以上代码中用户数据位宽为128bit(DAQ001是8通道16bit 所以是128bit位宽), 用户写入的数据经过uifdma_dbuf后进入fdma,之后通过AXI_interconnect 进入到ZYNQ DDR中。

SDK代码中,PS(ARM)读取内存中保存的ADC采样数值,并且通过LWIP TCP用以太网传输数据。下面具体看下关键几个IP的参数设置

1:uifdma_dbuf设置

由于输入数据是数据流形式,所以不需要使用视频传输功能,这里也只使用到了写通道,所以读通道也不需要使能。

由于采用DAQ001的速度相对比较忙,设置3帧缓存就够用。WBaseaddr缓存的基地址只要设置合适的值即可,这里设置0x10000000 = 256MB,这样保留了低256MB给应用程序使用。

WDsizebits设置每个缓存的大小,2^20次方=1MBYTE。

所以下面的参数XSize*YSize*W_Datawidth/8=1MB.其中已知AXI_DATA_WIDTH=128,所以只要正确设置XSize和YSize。通常来说设置越大的XSize传输效率也高,但是需要消耗的资源也会更多。我们这里设置XSize=2048,Ysize设置32代表。2048*32*128/8=1MB

在SDK中定义如下地址:

#define UIFDMA_DBUF_WBASEADDR 0x10000000

#define UIFDMA_DBUF_BUFSIZE 0x100000 //2^20

#define RX_BUFFER0_BASE    (UIFDMA_DBUF_WBASEADDR +  UIFDMA_DBUF_BUFSIZE*0)

#define RX_BUFFER1_BASE    (UIFDMA_DBUF_WBASEADDR +  UIFDMA_DBUF_BUFSIZE*1)

#define RX_BUFFER2_BASE    (UIFDMA_DBUF_WBASEADDR +  UIFDMA_DBUF_BUFSIZE*2)

2:uiFDMA设置

Fdma的数据位宽可以设置128这样效率最高。

3:AXI Interconnect设置

设置FIFO可以增加数据的吞吐能力

4:修改system_wrapper.v

将自动产生的system_wrapper.v复制到本方案工程路径soc_prj/uisrc/01_rtl/system_wrapper.v并对其修改,修改好的代码如下:

/*******************************MILIANKE*******************************
*Company : MiLianKe Electronic Technology Co., Ltd.
*WebSite:https://www.milianke.com
*TechWeb:https://www.uisrc.com
*tmall-shop:https://milianke.tmall.com
*jd-shop:https://milianke.jd.com
*taobao-shop1: https://milianke.taobao.com
*Create Date: 2021/10/15
*Module Name:system_wrapper
*File Name:system_wrapper.v
*Description: 
*The reference demo provided by Milianke is only used for learning. 
*We cannot ensure that the demo itself is free of bugs, so users 
*should be responsible for the technical problems and consequences
*caused by the use of their own products.
*Copyright: Copyright (c) MiLianKe
*All rights reserved.
*Revision: 1.0
*Signal description
*1) _i input
*2) _o output
*3) _n activ low
*4) _dg debug signal 
*5) _r delay or register
*6) _s state mechine
*********************************************************************/
`timescale 1 ps / 1 ps
module system_wrapper
(
inout wire [14:0]DDR_addr,
inout wire [2:0]DDR_ba,
inout wire DDR_cas_n,
inout wire DDR_ck_n,
inout wire DDR_ck_p,
inout wire DDR_cke,
inout wire DDR_cs_n,
inout wire [3:0]DDR_dm,
inout wire [31:0]DDR_dq,
inout wire [3:0]DDR_dqs_n,
inout wire [3:0]DDR_dqs_p,
inout wire DDR_odt,
inout wire DDR_ras_n,
inout wire DDR_reset_n,
inout wire DDR_we_n,
inout wire FIXED_IO_ddr_vrn,
inout wire FIXED_IO_ddr_vrp,
inout wire [53:0]FIXED_IO_mio,
inout wire FIXED_IO_ps_clk,
inout wire FIXED_IO_ps_porb,
inout wire FIXED_IO_ps_srstb,
//******************************
output wire hdmi_tx_0_tmds_clk_n,
output wire hdmi_tx_0_tmds_clk_p,
output wire [2:0]hdmi_tx_0_tmds_data_n,
output wire [2:0]hdmi_tx_0_tmds_data_p,
//******************************
input  wire ad7606_busy_i, 
output wire ad7606_cs_o,        //ad7606 AD cs
output wire ad7606_sclk_o,      //ad7606 AD data read
output wire ad7606_rst_o,       //ad7606 AD reset
output wire ad7606_convsta_o,   //ad7606 AD convert start
output wire ad7606_convstb_o,   //ad7606 AD convert start       
output wire ad7606_range_o,
input  wire ad7606_out_a_i,
input  wire ad7606_out_b_i,
output wire ad7606card_en
);
assign ad7606card_en = 1'b1;
wire pl_clk;
wire user_rstn;
wire user_start;
wire [127:0]ud_wdata_0;
wire ud_wde_0;
wire ud_wclk_0;
reg [15:0]test_data;
always@(posedge pl_clk)begin
        if(user_rstn == 1'b0)begin
                test_data <= 12'd0;
        end
        else if(ud_wde_0) begin
               test_data <= test_data + 1'b1; 
        end
end
wire [63:0] ad7606_out_a,ad7606_out_b;
wire [15:0] ad_ch0,ad_ch1,ad_ch2,ad_ch3,ad_ch4,ad_ch5,ad_ch6,ad_ch7;
assign ad_ch0 = ad7606_out_a[63:48];
assign ad_ch1 = ad7606_out_a[47:32];
assign ad_ch2 = ad7606_out_a[31:16];
assign ad_ch3 = ad7606_out_a[15: 0];
assign ad_ch4 = ad7606_out_b[63:48];
assign ad_ch5 = ad7606_out_b[47:32];
assign ad_ch6 = ad7606_out_b[31:16];
assign ad_ch7 = ad7606_out_b[15: 0];
assign ud_wdata_0 = {test_data,ad_ch6,ad_ch5,ad_ch4,ad_ch3,ad_ch2,ad_ch1,ad_ch0};
assign ud_wde_0   = user_start & ad7606_cap_en;
assign ud_wclk_0  = pl_clk;
uispi7606#(
.SPI_DIV(10'd5),
.T5US_DIV(10'd499)
)
uispi7606_inst
(
.ad_clk_i(pl_clk),            
.ad_rst_i(user_rstn==1'b0),
.ad_busy_i(ad7606_busy_i),                   
.ad_cs_o(ad7606_cs_o), 
.ad_sclk_o(ad7606_sclk_o),      
.ad_rst_o(ad7606_rst_o),          
.ad_convsta_o(ad7606_convsta_o),      
.ad_convstb_o(ad7606_convstb_o),  
.ad_range_o(ad7606_range_o),
.ad_out_a_i(ad7606_out_a_i),
.ad_out_b_i(ad7606_out_b_i),
.ad_out_a(ad7606_out_a),
.ad_out_b(ad7606_out_b),
.ad_cap_en(ad7606_cap_en)
);
ila_0 ila0_dg 
(
.clk(pl_clk),
.probe0({test_data[15:0],ad_ch0,ud_wde_0,user_start,user_rstn})
);
system system_i
(
.DDR_addr(DDR_addr),
.DDR_ba(DDR_ba),
.DDR_cas_n(DDR_cas_n),
.DDR_ck_n(DDR_ck_n),
.DDR_ck_p(DDR_ck_p),
.DDR_cke(DDR_cke),
.DDR_cs_n(DDR_cs_n),
.DDR_dm(DDR_dm),
.DDR_dq(DDR_dq),
.DDR_dqs_n(DDR_dqs_n),
.DDR_dqs_p(DDR_dqs_p),
.DDR_odt(DDR_odt),
.DDR_ras_n(DDR_ras_n),
.DDR_reset_n(DDR_reset_n),
.DDR_we_n(DDR_we_n),
.FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
.FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
.FIXED_IO_mio(FIXED_IO_mio),
.FIXED_IO_ps_clk(FIXED_IO_ps_clk),
.FIXED_IO_ps_porb(FIXED_IO_ps_porb),
.FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
.hdmi_tx_0_tmds_clk_n(hdmi_tx_0_tmds_clk_n),
.hdmi_tx_0_tmds_clk_p(hdmi_tx_0_tmds_clk_p),
.hdmi_tx_0_tmds_data_n(hdmi_tx_0_tmds_data_n),
.hdmi_tx_0_tmds_data_p(hdmi_tx_0_tmds_data_p),
.pl_clk(pl_clk),
.ud_wclk_0(ud_wclk_0),
.ud_wdata_0(ud_wdata_0),
.ud_wde_0(ud_wde_0),
.user_rstn(user_rstn),
.user_start(user_start)
);
endmodule

以上代码中,调用了米联客uispi7606 IP CORE采集模拟数据,并且把采集好的数据写入到uifdmadbuf中,为了方便实验中观察数据,把第八个通道的AD数据改成了计数器。实际项目中可以把这个替换成第八个通道的ADC数据。

4.2设置地址分配

需要注意uifdma_dbuf的axi-lite接口地址,这个地址我们会在SDK 代码中用到读寄存器。 

4.3添加PIN约束

1:选中PROJECT  MANAGERà Add SourcesàAdd or create constraints,添加XDC约束文件。

2:打开提供例程,复制约束文件中的管脚约束到XDC文件,或者查看原理图,自行添加管脚约束,并保存。

以下是添加配套工程路径下已经提供的pin脚文件。配套工程的pin脚约束文件在uisrc/04_pin路径

4.4编译并导出平台文件

1:单击Block文件à右键àGenerate the Output ProductsàGlobalàGenerate。

2:单击Block文件à右键à Create a HDL wrapper(生成HDL顶层文件)àLet vivado manager wrapper and auto-update(自动更新)。

3:生成Bit文件。

4:导出到硬件: FileàExport HardwareàInclude bitstream

5:导出完成后,对应工程路径的soc_hw路径下有硬件平台文件:system_wrapper.xsa的文件。根据硬件平台文件system_wrapper.xsa来创建需要Platform平台。

5搭建Vitis-sdk工程

创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。 

5.1创建SDK Platform工程

LWIP库的修改:

1:新版本系列工业级开发板板载网口芯片是RTL8211FDI,由于默认的驱动不支持,需要手动自己修改库文件。我们这里已经提供了修改好的库,解压到vivado的安装路径下的对于路径下:

修改好后,需要关闭vitis-sdk然后重新打开sdk,否则无法识别修改的库

2:为了创建lwip工程需要先对soc_base中的board support package简称bsp设置lwip库的支持

3:对lwip库参数修改以达到最佳性能。

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

修改完成后重新编译soc_base

5.2创建DAQ001 _lwip工程

6SDK程序分析

6.1FDMA数据接收原理

每当uifdmadbuf发送的中断后,该函数被调用,通过读取uifdmadbuf axi-lite的寄存器,获取当前哪一个缓存产生了中断(代表数据发送到PS DDR了)。

为了增加数据的吞吐能力,在中断中,不宜进行数据搬运,我们设计了要给结构体,可以用于标记已经写入DDR的数据。

1:PS_RX_intr_Handler
void PS_RX_intr_Handler(void *param)
{
		fdma_buf.record[fdma_buf.circle_cnt]= Xil_In32((UINTPTR)FDMA_DBUF_BASE_ADDR);
		if(fdma_buf.circle_cnt<2)
			fdma_buf.circle_cnt ++ ;
		else
			fdma_buf.circle_cnt = 0;
		fdma_buf.pkg_done_cnt++;
}

6.2数据包设计

Lwip ip作为轻量级的协议栈,不能一次性发送所有数据,因此需要对数据分多次传输。本文中,传输的ADC数据包大小为16*1024*64 = 1024KB,数据设计为每包传输1024*16即16KB。因此传输完所有数据需要经过64次。

#define TOTAL_PKG_SIZE              1024*16*64
#define TCP_PACKEG_SIZE 			(1024*16)
#define TCP_SEND_TIMES			TOTAL_PKG_SIZE/TCP_PACKEG_SIZE
#define TCP_SEND_LAST_SIZE      TOTAL_PKG_SIZE-(TCP_PACKEG_SIZE*TCP_SEND_TIMES)
#define TCP_FIRST_SEND_SIZE     HEADER_SIZE + TCP_PACKEG_SIZE

6.3帧头设计

为了让上位机知道接收ADC的数据帧头、采样速度、分辨率、有效通道、一包数据长度、帧计数,设计了如下数据帧头:

typedef struct packet_header
{
	u32 ID0; //0xAA55AA55
	u32 ID1;
	u16 KSPS;
	u8  Resolution;
	u8  channels_signbit;
	u16 length;
	u16 fram_counter;
}packet_header;

6.4主程序分析

main函数中完成中断资源的初始化,lwip的初始化,并且通过一个while循环完成, tcp连接监听、数据的接收函数调用、数据的发送函数调用。本文的实验只需要,tcp连接监听和数据的发送功能。

通过定时器,每间隔250ms会判断一次request_pcb->state的状态,如果以太网没有连接,则会创建一个新的TCP连接。

1:init_intr_sys()

该函数初始化中断,包括PL中断和以太网传输需要用到的定时器中断

其中init_platform函数会对以太网定时器中断部分以及回调函数进行初始化。

可以关键看下platform_zynq.c中被调用的相关函数:

void
timer_callback(XScuTimer * TimerInstance)
{
	/* we need to call tcp_fasttmr & tcp_slowtmr at intervals specified
	 * by lwIP. It is not important that the timing is absoluetly accurate.
	 */
	static int odd = 1;
#if LWIP_DHCP==1
    static int dhcp_timer = 0;
#endif
	 TcpFastTmrFlag = 1;
	odd = !odd;
#ifndef USE_SOFTETH_ON_ZYNQ
	ResetRxCntr++;
#endif
	if (odd) {
		TcpSlowTmrFlag = 1;
#if LWIP_DHCP==1
		dhcp_timer++;
		dhcp_timoutcntr--;
		dhcp_fine_tmr();
		if (dhcp_timer >= 120) {
			dhcp_coarse_tmr();
			dhcp_timer = 0;
		}
#endif
	}
	/* For providing an SW alternative for the SI #692601. Under heavy
	 * Rx traffic if at some point the Rx path becomes unresponsive, the
	 * following API call will ensures a SW reset of the Rx path. The
	 * API xemacpsif_resetrx_on_no_rxdata is called every 100 milliseconds.
	 * This ensures that if the above HW bug is hit, in the worst case,
	 * the Rx path cannot become unresponsive for more than 100
	 * milliseconds.
	 */
#ifndef USE_SOFTETH_ON_ZYNQ
	if (ResetRxCntr >= RESET_RX_CNTR_LIMIT) {
		xemacpsif_resetrx_on_no_rxdata(&server_netif);
		ResetRxCntr = 0;
	}
#endif
	XScuTimer_ClearInterruptStatus(TimerInstance);
}
void platform_setup_timer(void)
{
	int Status = XST_SUCCESS;
	XScuTimer_Config *ConfigPtr;
	int TimerLoadValue = 0;
	ConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
	Status = XScuTimer_CfgInitialize(&TimerInstance, ConfigPtr,
			ConfigPtr->BaseAddr);
	if (Status != XST_SUCCESS) {
		xil_printf("In %s: Scutimer Cfg initialization failed...\r\n",
		__func__);
		return;
	}
	Status = XScuTimer_SelfTest(&TimerInstance);
	if (Status != XST_SUCCESS) {
		xil_printf("In %s: Scutimer Self test failed...\r\n",
		__func__);
		return;
	}
	XScuTimer_EnableAutoReload(&TimerInstance);
	/*
	 * Set for 250 milli seconds timeout.
	 */
	TimerLoadValue = XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ / 8;
	XScuTimer_LoadTimer(&TimerInstance, TimerLoadValue);
	return;
}
void platform_setup_interrupts(void)
{
	/*
	 * Connect the device driver handler that will be called when an
	 * interrupt for the device occurs, the handler defined above performs
	 * the specific interrupt processing for the device.
	 */
	XScuGic_RegisterHandler(INTC_BASE_ADDR, TIMER_IRPT_INTR,
					(Xil_ExceptionHandler)timer_callback,
					(void *)&TimerInstance);
	/*
	 * Enable the interrupt for scu timer.
	 */
	XScuGic_EnableIntr(INTC_DIST_BASE_ADDR, TIMER_IRPT_INTR);
	return;
}
void platform_enable_interrupts()
{
	/*
	 * Enable non-critical exceptions.
	 */
	Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
	XScuTimer_EnableInterrupt(&TimerInstance);
	XScuTimer_Start(&TimerInstance);
	return;
}
void init_platform()
{
	platform_setup_timer();
	platform_setup_interrupts();
	return;
}
2:lwip_init()

初始化lwip

3:xemac_add ()

添加以太网的MAC地址,MAC地址定义如下:

unsigned char mac_ethernet_address[] ={0x00,0x0a,0x35,0x00,0x01,0x02};

4:netif_set_default()

设置默认的以太网接口,这里定义了一个server_netif的全局变量,并且对其初始化。

struct netif server_netif;

netif = &server_netif;

netif_set_default(netif);

5:platform_enable_interrupts()

该函数会使能platform_zynq.c中的定时器,启动定时器,这样每间隔250msTcpFastTmrFlag变量就会设设置1,每间隔500ms TcpSlowTmrFlag变量就会设置1

6:启动DHCP配置
dhcp_start(netif);
	dhcp_timoutcntr = 24;
	while (((netif->ip_addr.addr) == 0) && (dhcp_timoutcntr > 0))
		xemacif_input(netif);
	if (dhcp_timoutcntr <= 0) {
		if ((netif->ip_addr.addr) == 0) {
			xil_printf("ERROR: DHCP request timed out\r\n");
			assign_default_ip(&(netif->ip_addr),
					&(netif->netmask), &(netif->gw));
		}
	}
7:start_appication()

该函数首先

void start_application(void)
{
	err_t err;
	ip_addr_t remote_addr;
	u32_t i;
	cam_init();/* 初始化摄像头 */
	first_trans_start = 0; /* 该变量判断是否第一次传输 */
	client_connected =0; /* 判断是否连接状态 */
#if LWIP_IPV6==1
	remote_addr.type= IPADDR_TYPE_V6;
	err = inet6_aton(TCP_SERVER_IPV6_ADDRESS, &remote_addr);
#else
	err = inet_aton(TCP_SERVER_IP_ADDRESS, &remote_addr); /* 设置服务器IP地址 */
#endif /* LWIP_IPV6 */
	if (!err) {
		xil_printf("Invalid Server IP address: %d\r\n", err);
		return;
	}
	/* Create Client PCB */
	request_pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); /* 创建一个客户端PCB */
	if (!request_pcb) {
		xil_printf("Error in PCB creation. out of memory\r\n");
		return;
	}
	err = tcp_connect(request_pcb, &remote_addr, TCP_CONN_PORT,
			tcp_client_connected); /* 设置客户端和主机连接上的回调函数 tcp_client_connected */
	if (err) {
		xil_printf("Error on tcp_connect: %d\r\n", err);
		tcp_client_close(request_pcb);
		return;
	}
	client.client_id = 0;
	return;
}
8:while循环

该循环中,每过250ms调用tcp_fasttmr(),每间隔500ms调用tcp_slowTmr()函数。tcp_fasttmr()每250ms处理延时发送的ack报文和fin报文,并且通知上层应用处理数据。tcp_slowTmr()每500ms调用,该函数负责超时重传以及移除TIME-WAIT 足够时间的 PCB,同时将PCB中unsent队列中的数据发送出去。一般使用tcp_write();写入数据后,数据不会马上发送,而是在定时任务中发送。

While循环中还会检测当前连接状体,如果当前连接状态不存在会每间隔250ms重新尝试连接一次。

最后当连接建立后,并且收到上位机发送的启动命令,会调用transfer_data()完成数据从DDR到以太网的发送。

while (1) {
		if (TcpFastTmrFlag) {
			if(request_pcb->state == CLOSED || (request_pcb->state == SYN_SENT && request_pcb->nrtx == TCP_SYNMAXRTX))//check conditions for create new tcp connection
			{
				start_application();
			}
			tcp_fasttmr();
			TcpFastTmrFlag = 0;
		}
		if (TcpSlowTmrFlag) {
			tcp_slowtmr();
			TcpSlowTmrFlag = 0;
		}
		xemacif_input(netif);
		/* if connected to the server, start receive data from PL through axidma, then transmit the data to the PC software by TCP*/
		if(client_connected && tcp_trans_start)// if tcp connection is setup
			transfer_data();//call send_received_data() function sent data from ddr
		else
		{
			fdma_wr_set(0);
			first_trans_start = 0;
		}
	}

8:transfer_data()

该函数负责把DDR中摄像头的图像数据发送出去,是本方案的核心。该函数会调用tcp_send_perf_traffic()函数。

9:tcp_send_perf_traffic()函数

当FDMA摄像头的缓存中存在数据,首选发送帧头,然后连续发送TCP_SEND_TIMES次TCP_PACKEG_SIZE大小的数据包,直到所有数据完成发送。

static err_t tcp_send_perf_traffic(void)
{
	err_t err;
	u8_t apiflags = TCP_WRITE_FLAG_COPY | TCP_WRITE_FLAG_MORE;
	if (c_pcb == NULL) {
		return ERR_CONN;
	}
#ifdef __MICROBLAZE__
	/* Zero-copy pbufs is used to get maximum performance for Microblaze.
	 * For Zynq A9, ZynqMP A53 and R5 zero-copy pbufs does not give
	 * significant improvement hense not used. */
	apiflags = 0;
#endif
	struct tcp_pcb *tpcb = c_pcb;
	if (!tpcb)
	return;
	if(first_trans_start==0)
	{
		fdma_buf.circle_cnt=0;
		fdma_buf.next=0;
		fdma_buf.pkg_done_cnt=0;
		fdma_buf.pkg_cnt=0;
		fdma_buf.fram_cnt=0;
		fdma_wr_set(1);
		pkg_offset =0;
		first_trans_start =1;
	}
	/*if the last fdma transmission is done, the interrupt triggered, then start TCP transmission*/
	if(fdma_buf.pkg_done_cnt> 0 && fdma_buf.pkg_done_cnt<4) //1MB divide in to 64 times
	{
			/*set fdma buffer transmission when the current transmission is done*/
				/* 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) > TCP_FIRST_SEND_SIZE)
		{
			/*transmit received data through TCP*/
			//xil_printf("bufaddr1=: %x\r\n",bufaddr);
			if(fdma_buf.pkg_cnt==0)
			{
				bufaddr = (u8*)(RxBufferPtr[fdma_buf.record[fdma_buf.next]]);//16 = packet_header size
				header_p = (packet_header *)bufaddr;
				header_p->ID0 = HEADER_ID0;
				header_p->ID1 = HEADER_ID1;
				header_p->KSPS= HEADER_KSPS;
				header_p->Resolution= HEADER_RESOLUTION;
				header_p->channels_signbit= HEADER_CHANNLES_SIGNBIT;
				header_p->length = HEADER_LENGTH;
				header_p->fram_counter = fdma_buf.fram_cnt;
				Xil_DCacheInvalidateRange((u32)bufaddr + HEADER_SIZE, TCP_FIRST_SEND_SIZE);
				err = tcp_write(tpcb, bufaddr, TCP_FIRST_SEND_SIZE, apiflags);
				bufaddr = bufaddr + HEADER_SIZE;
				pkg_offset = pkg_offset + TCP_PACKEG_SIZE;
			}
			else if(fdma_buf.pkg_cnt < TCP_SEND_TIMES)
			{
				bufaddr = bufaddr + TCP_PACKEG_SIZE;
				Xil_DCacheInvalidateRange((u32)bufaddr, TCP_PACKEG_SIZE);
				err = tcp_write(tpcb, bufaddr, TCP_PACKEG_SIZE, apiflags);
				pkg_offset = pkg_offset + TCP_PACKEG_SIZE;
			}
			else if(TCP_SEND_LAST_SIZE>0)
			{
				bufaddr = bufaddr + TCP_PACKEG_SIZE;
				Xil_DCacheInvalidateRange((u32)bufaddr, TCP_SEND_LAST_SIZE);
				err = tcp_write(tpcb, bufaddr, TCP_SEND_LAST_SIZE, apiflags);
				pkg_offset = pkg_offset + TCP_SEND_LAST_SIZE;
			}
		    if (err != ERR_OK) {
				xil_printf("txperf: Error on tcp_write: %d\r\n", err);
				return;
			}
			err = tcp_output(tpcb);
			if (err != ERR_OK) {
				xil_printf("txperf: Error on tcp_output: %d\r\n",err);
				return;
			}
			fdma_buf.pkg_cnt++;
			if(pkg_offset == TOTAL_PKG_SIZE)
			{
				pkg_offset=0;
				fdma_buf.fram_cnt++;
				fdma_buf.pkg_done_cnt--;
				fdma_buf.pkg_cnt = 0;
				if(fdma_buf.next<2)
					fdma_buf.next++;
				else
					fdma_buf.next=0;
			}
		}
	}
	else if(fdma_buf.pkg_done_cnt > 2) //如果缓存不能处理,通过设置irst_trans_start = 0再次同步
	{
		xil_printf("error pkg_done_cnt = %d \r\n", fdma_buf.pkg_done_cnt);
		first_trans_start = 0;
	}
/*
	if (client.end_time || client.i_report.report_interval_time) {
		u64_t now = get_time_ms();
		if (client.i_report.report_interval_time) {
			if (client.i_report.start_time) {
				u64_t diff_ms = now - client.i_report.start_time;
				u64_t rtime_ms = client.i_report.report_interval_time;
				if (diff_ms >= rtime_ms) {
					tcp_conn_report(diff_ms, INTER_REPORT);
					client.i_report.start_time = 0;
					client.i_report.total_bytes = 0;
				}
			} else {
				client.i_report.start_time = now;
			}
		}
	}*/
	return ERR_OK;
}
10:本地IP地址设置

在主程序tcp_lwip_test.c中定义

#define DEFAULT_IP_ADDRESS	"192.168.137.10"
#define DEFAULT_IP_MASK		"255.255.255.0"
#define DEFAULT_GW_ADDRESS	"192.168.1.1"
11:远程主机IP设置

在头文件tcp_client.h

#define TCP_SERVER_IP_ADDRESS "192.168.137.209"
#define TCP_CONN_PORT 50

7方案演示

7.1硬件准备

7.2实验结果

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

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

通过以太网启动数据发送

通过以太网暂停数据发送

查看网速

利用SDK查看内存中数据

利用wireshark观察数据,可以看到我的测试电脑上数据包每个大小1446bytes,一共传输了1446*11+494= 16400bytes,正好和我们SDK代码中发送的数据一致。

从wireshark抓到的数据看,X86端需要注意大小端的问题。

最后如果有一个示波软件显示波形就完美了,敬请期待吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值