软件版本:VIVADO2021.1
操作系统:WIN10 64bit
硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA
实验平台:米联客-MLK-H3-CZ08-7100开发板
板卡获取平台:https://milianke.tmall.com/
登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!
目录
5:platform_enable_interrupts()
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端需要注意大小端的问题。
最后如果有一个示波软件显示波形就完美了,敬请期待吧。