软件版本: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概述DAQ001是一款8通道16bit 200k采样率的高精度ADC,支持串行和并行采集接口。米联客DAQ001采用串行模式实现200K 8通道同时采样,相比并口方式,串行方式,具有硬件接口简单,节约成本优势。 实验目的: 1:掌握ui_axisbufw配置成非视频模式的情况下的参数设置 2:掌握ADC数据如何通过ui_axisbufw写入到AXI-DMA IP 3:掌握AXI-DMA IP中断的产生方法,以及AXIS时序接口 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位宽), 用户写入的数据经过ui_axisbufw后进入axi-dma,之后通过AXI_interconnect 进入到ZYNQ DDR中。 SDK代码中,PS(ARM)读取内存中保存的ADC采样数值,并且通过插值方式绘制波形,波形先绘制到内中,VDMA从这个开辟的波形显存中主动读取数据,显示到显示器上。这样就完成了波形采集显示方案。 下面具体看下关键几个IP的参数设置 1:ui_axisbufw设置VIVADO_ENABLE:用于设置时序需要支持视频帧同步功能,对于数据流模式设置为0 AXI_DATA_WIDTH:用于设置AXI-Stream数据接口的位宽,这里设置128 W_BUFDEPTH:用于设置FIFO的深度,越大消耗资源越多,这里设置2048 W_XSIZE和W_YSIZE:配合可以一次DMA完成大量数据传输,而不需要太多的FIFO,并且通过W_XSIZE和W_YSIZE合理设置既可以满足速度要求,又可以减少FIFO使用。比如这里设置1024*64也可以设置512*128当设置512*128,那么FIFO就可以设置最大1024足够使用了。 通过这些参数设置,可以看出来,我们一次DMA完成1024*64*128/8=1MB数据传输,也就是8个通道每个通道,完成采集了64K的波形点采集。
2:AXI-DMA设置 其中关键参数是设置合适的Width of Buffer Length Register 大小为23bit 代表最大DMA可以支持2^23=8MB数据,足够使用。 另外我们只用到写通道,所以只勾选写通道即可。
3:修改system_wrapper.v将自动产生的system_wrapper.v复制到本方案工程路径soc_prj/uisrc/01_rtl/system_wrapper.v并对其修改,修改好的代码如下: 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_CLK_N,
output wire HDMI_TX_CLK_P,
output wire [2:0]HDMI_TX_N,
output wire [2:0]HDMI_TX_P,
/***********************daq7606******************************/
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 user_rstn;
wire user_start;
wire pl_clk0;
reg [15:0] test_data;
wire ad7606_cap_en;
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;
//sensor input -W_FIFO--------------
wire W_wclk_i = pl_clk0;
wire W_FS_i = user_start;
wire W_wren_i = ad7606_cap_en && user_start;
wire [127: 0] W_data_i = {test_data,ad_ch6,ad_ch5,ad_ch4,ad_ch3,ad_ch2,ad_ch1,ad_ch0};
//----------axis signals write-------
wire axis_clk =pl_clk0;
wire [127: 0] axis_wdata;
wire axis_wvalid;
wire axis_wready;
wire axis_last;
always @(posedge W_wclk_i)begin
if(user_rstn == 1'b0)
test_data <= 0;
else if(W_wren_i)
test_data <= test_data + 1'b1;
end
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];
/***********************调用 daq7606 IP 这里采用了 SPI 接口********************/
uispi7606#(
.SPI_DIV(10'd5),
.T5US_DIV(10'd499)
)
uispi7606_inst
(
.ad_clk_i(pl_clk0),
.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 ila_debug (
.clk(pl_clk0), // input wire cl
.probe0({ad7606_cap_en,user_rstn,user_start}), // input wire [15:0] probe0
.probe1({test_data,ad_ch6,ad_ch5,ad_ch4,ad_ch3,ad_ch2,ad_ch1,ad_ch0})
);
/***********************
一次 DMA 传输 128*1024*64 字节大小的数据
**************************/
ui_axisbufw#(
.VIDEO_ENABLE(0),
.AXI_DATA_WIDTH(128),
.W_BUFDEPTH(2048),
.W_DATAWIDTH(128),
.W_XSIZE(1024),
.W_YSIZE(64)
)
ui_axisbufw_inst
(
.ui_rstn(user_rstn),
//sensor input -W_FIFO--------------
.W_wclk_i(W_wclk_i),
.W_FS_i(W_FS_i),
.W_wren_i(W_wren_i),
.W_data_i(W_data_i),
//----------axis signals write-------
.axis_clk(axis_clk),
.axis_wdata(axis_wdata),
.axis_wvalid(axis_wvalid),
.axis_wready(axis_wready),
.axis_last(axis_last)
);
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_CLK_N(HDMI_TX_CLK_N),
.HDMI_TX_CLK_P(HDMI_TX_CLK_P),
.HDMI_TX_N(HDMI_TX_N),
.HDMI_TX_P(HDMI_TX_P),
.S_AXIS_S2MM_0_tdata(axis_wdata),
.S_AXIS_S2MM_0_tkeep(16'hffff),
.S_AXIS_S2MM_0_tlast(axis_last),
.S_AXIS_S2MM_0_tready(axis_wready),
.S_AXIS_S2MM_0_tvalid(axis_wvalid),
.pl_clk(pl_clk0),
.user_rstn(user_rstn),
.user_start(user_start)
);
endmodule
|
以上代码中,调用了米联客uispi7606 IP CORE采集模拟数据,并且把采集好的数据写入到ui_axisbufw中,为了方便实验中观察数据,把第八个通道的AD数据改成了计数器。实际项目中可以把这个替换成第八个通道的ADC数据。 4.2设置地址分配需要注意axi-lite接口地址,这个地址我们会在SDK 代码中使用AXI-GPIO控制传输的复位和启动。
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_tcp工程
6SDK程序分析
6.1DMA数据接收原理
每当AXI-DMA发送的中断后,DMA中断函数被调用。 1:PS_RX_intr_Handler以下函数种,每次DMA中断,通过fdma_buf.record[fdma_buf.circle_cnt] = fdma_buf.circle_cnt记录中断的缓存号,同时发起下次DMA传输,并且fdma_buf.pkg_done_cnt++用于计数已经完成的中断次数 static void DMA_RxIntrHandler(void *Callback)
{
u32 IrqStatus;
u32 Status;
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)) {
fdma_buf.record[fdma_buf.circle_cnt] = fdma_buf.circle_cnt;
if(fdma_buf.circle_cnt<2)
{
fdma_buf.circle_cnt ++ ;
//fdma_buf.next = fdma_buf.circle_cnt -1;
}
else
{
fdma_buf.circle_cnt = 0;
//fdma_buf.next = 2;
}
Status = XAxiDma_SimpleTransfer(&AxiDma, (UINTPTR)(RxBufferPtr_DATA[fdma_buf.circle_cnt]),TOTAL_PKG_SIZE,
XAXIDMA_DEVICE_TO_DMA);
if (Status != XST_SUCCESS)
{
xil_printf("axi dma failed! 0 %d\r\n", Status);
}
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;
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;
}
}
|
该函数负责把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;
XAxiDma_SimpleTransfer(&AxiDma, (UINTPTR)(RxBufferPtr_DATA[fdma_buf.circle_cnt]),TOTAL_PKG_SIZE,
XAXIDMA_DEVICE_TO_DMA); //第一次启动DMA
}
/*当 DMA 中断发生后,通过判断 fdma_buf.pkg_done_cnt,知道已经完成的中断次数,并且必须是 0~3 之间,才是有效的,如果大于 2 就是溢出了*/
if(fdma_buf.pkg_done_cnt> 0 && fdma_buf.pkg_done_cnt<3) //ADC数据包1MB 分64次传输
{
if (tcp_sndbuf(tpcb) > TCP_FIRST_SEND_SIZE) //当lwip发送缓存有足够大小才进行传输
{
/*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;
/*对于从PL到PS到DDR数据,需要用Xil_DCacheInvalidateRange确保cache一致性*/
Xil_DCacheInvalidateRange((INTPTR)(bufaddr+HEADER_SIZE), TCP_PACKEG_SIZE);
/*数据写入TCP缓存*/
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); //确保cache一致性
err = tcp_write(tpcb, bufaddr, TCP_PACKEG_SIZE, apiflags);//数据写入TCP缓存
/*记录已经传输的包*/
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;
|
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 5001
|
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端需要注意大小端的问题。 最后如果有一个示波软件显示波形就完美了,敬请期待吧。
|