本帖最后由 FPGA课程 于 2024-10-8 09:06 编辑
软件版本: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并对其修改,修改好的代码如下: - `timescale 1ns / 1ps
- /*******************************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
- *********************************************************************/
- 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,
- /***********************DAQ001******************************/
- 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];
- /***********************调用DAQ001 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_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),
- .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;
- 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;
- }
- }
复制代码
该函数负责把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;
- 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 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端需要注意大小端的问题。 最后如果有一个示波软件显示波形就完美了,敬请期待吧。 |