[X]关闭

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

文档创建者:FPGA课程
浏览次数:185
最后更新:2024-10-08
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 1_SDK应用方案(仅旗舰型号) » 2-SDK高级应用方案
本帖最后由 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机。
591fcc9a91fb4c8c91be38f623084f20.jpg
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图形化编程
image.jpg
以上代码中用户数据位宽为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的波形点采集。
e9fc20ea71c7452f869b66b3b6f9d193.jpg
2:AXI-DMA设置
其中关键参数是设置合适的Width of Buffer Length Register 大小为23bit 代表最大DMA可以支持2^23=8MB数据,足够使用。
另外我们只用到写通道,所以只勾选写通道即可。
6aaeadcd65c04552afb54b073c65ca74.jpg
3:修改system_wrapper.v
将自动产生的system_wrapper.v复制到本方案工程路径soc_prj/uisrc/01_rtl/system_wrapper.v并对其修改,修改好的代码如下:
  1. `timescale 1ns / 1ps
  2. /*******************************MILIANKE*******************************
  3. *Company : MiLianKe Electronic Technology Co., Ltd.
  4. *WebSite:https://www.milianke.com
  5. *TechWeb:https://www.uisrc.com
  6. *tmall-shop:https://milianke.tmall.com
  7. *jd-shop:https://milianke.jd.com
  8. *taobao-shop1: https://milianke.taobao.com
  9. *Create Date: 2021/10/15
  10. *Module Name:system_wrapper
  11. *File Name:system_wrapper.v
  12. *Description:
  13. *The reference demo provided by Milianke is only used for learning.
  14. *We cannot ensure that the demo itself is free of bugs, so users
  15. *should be responsible for the technical problems and consequences
  16. *caused by the use of their own products.
  17. *Copyright: Copyright (c) MiLianKe
  18. *All rights reserved.
  19. *Revision: 1.0
  20. *Signal description
  21. *1) _i input
  22. *2) _o output
  23. *3) _n activ low
  24. *4) _dg debug signal
  25. *5) _r delay or register
  26. *6) _s state mechine
  27. *********************************************************************/
  28. module system_wrapper(
  29. inout wire [14:0]DDR_addr,
  30. inout wire [2:0]DDR_ba,
  31. inout wire DDR_cas_n,
  32. inout wire DDR_ck_n,
  33. inout wire DDR_ck_p,
  34. inout wire DDR_cke,
  35. inout wire DDR_cs_n,
  36. inout wire [3:0]DDR_dm,
  37. inout wire [31:0]DDR_dq,
  38. inout wire [3:0]DDR_dqs_n,
  39. inout wire [3:0]DDR_dqs_p,
  40. inout wire DDR_odt,
  41. inout wire DDR_ras_n,
  42. inout wire DDR_reset_n,
  43. inout wire DDR_we_n,
  44. inout wire FIXED_IO_ddr_vrn,
  45. inout wire FIXED_IO_ddr_vrp,
  46. inout wire [53:0]FIXED_IO_mio,
  47. inout wire FIXED_IO_ps_clk,
  48. inout wire FIXED_IO_ps_porb,
  49. inout wire FIXED_IO_ps_srstb,
  50. //******************************
  51. output wire hdmi_tx_0_tmds_clk_n,
  52. output wire hdmi_tx_0_tmds_clk_p,
  53. output wire [2:0]hdmi_tx_0_tmds_data_n,
  54. output wire [2:0]hdmi_tx_0_tmds_data_p,
  55. /***********************DAQ001******************************/               
  56. input  wire ad7606_busy_i,
  57. output wire ad7606_cs_o,        //ad7606 AD cs
  58. output wire ad7606_sclk_o,      //ad7606 AD data read
  59. output wire ad7606_rst_o,       //ad7606 AD reset
  60. output wire ad7606_convsta_o,   //ad7606 AD convert start
  61. output wire ad7606_convstb_o,   //ad7606 AD convert start   
  62. output wire ad7606_range_o,
  63. input  wire ad7606_out_a_i,
  64. input  wire ad7606_out_b_i,
  65. output wire ad7606card_en
  66. );
  67. assign      ad7606card_en=1'b1;
  68. wire        user_rstn;
  69. wire        user_start;
  70. wire        pl_clk0;
  71. reg  [15:0] test_data;
  72. wire ad7606_cap_en;
  73. wire [63:0] ad7606_out_a,ad7606_out_b;
  74. wire [15:0] ad_ch0,ad_ch1,ad_ch2,ad_ch3,ad_ch4,ad_ch5,ad_ch6,ad_ch7;
  75. //sensor input -W_FIFO--------------
  76. wire                 W_wclk_i = pl_clk0;
  77. wire                 W_FS_i   = user_start;
  78. wire                 W_wren_i = ad7606_cap_en && user_start;
  79. wire    [127: 0]     W_data_i = {test_data,ad_ch6,ad_ch5,ad_ch4,ad_ch3,ad_ch2,ad_ch1,ad_ch0};
  80. //----------axis signals write-------         
  81. wire                 axis_clk =pl_clk0;                           
  82. wire    [127: 0]     axis_wdata;
  83. wire                 axis_wvalid;
  84. wire                 axis_wready;
  85. wire                 axis_last;
  86. always @(posedge W_wclk_i)begin
  87.     if(user_rstn == 1'b0)
  88.         test_data <= 0;
  89.     else if(W_wren_i)
  90.         test_data <= test_data + 1'b1;
  91. end
  92. assign ad_ch0 = ad7606_out_a[63:48];
  93. assign ad_ch1 = ad7606_out_a[47:32];
  94. assign ad_ch2 = ad7606_out_a[31:16];
  95. assign ad_ch3 = ad7606_out_a[15: 0];
  96. assign ad_ch4 = ad7606_out_b[63:48];
  97. assign ad_ch5 = ad7606_out_b[47:32];
  98. assign ad_ch6 = ad7606_out_b[31:16];
  99. assign ad_ch7 = ad7606_out_b[15: 0];
  100. /***********************调用DAQ001 IP这里采用了SPI接口********************/   
  101. uispi7606#(
  102. .SPI_DIV(10'd5),
  103. .T5US_DIV(10'd499)
  104. )
  105. uispi7606_inst
  106. (
  107. .ad_clk_i(pl_clk0),            
  108. .ad_rst_i(user_rstn==1'b0),
  109. .ad_busy_i(ad7606_busy_i),                  
  110. .ad_cs_o(ad7606_cs_o),
  111. .ad_sclk_o(ad7606_sclk_o),      
  112. .ad_rst_o(ad7606_rst_o),         
  113. .ad_convsta_o(ad7606_convsta_o),      
  114. .ad_convstb_o(ad7606_convstb_o),  
  115. .ad_range_o(ad7606_range_o),
  116. .ad_out_a_i(ad7606_out_a_i),
  117. .ad_out_b_i(ad7606_out_b_i),
  118. .ad_out_a(ad7606_out_a),
  119. .ad_out_b(ad7606_out_b),
  120. .ad_cap_en(ad7606_cap_en)
  121. );

  122.    
  123. ila_0 ila_debug (
  124. .clk(pl_clk0), // input wire cl
  125. .probe0({ad7606_cap_en,user_rstn,user_start}), // input wire [15:0]  probe0  
  126. .probe1({test_data,ad_ch6,ad_ch5,ad_ch4,ad_ch3,ad_ch2,ad_ch1,ad_ch0})                  
  127. );  
  128. /***********************
  129. 一次DMA传输128*1024*64 字节大小的数据
  130. **************************/   
  131. ui_axisbufw#(
  132. .VIDEO_ENABLE(0),  
  133. .AXI_DATA_WIDTH(128),
  134. .W_BUFDEPTH(2048),
  135. .W_DATAWIDTH(128),
  136. .W_XSIZE(1024),
  137. .W_YSIZE(64)
  138. )
  139. ui_axisbufw_inst
  140. (
  141. .ui_rstn(user_rstn),
  142. //sensor input -W_FIFO--------------
  143. .W_wclk_i(W_wclk_i),
  144. .W_FS_i(W_FS_i),
  145. .W_wren_i(W_wren_i),
  146. .W_data_i(W_data_i),
  147. //----------axis signals write-------         
  148. .axis_clk(axis_clk),                              
  149. .axis_wdata(axis_wdata),
  150. .axis_wvalid(axis_wvalid),
  151. .axis_wready(axis_wready),
  152. .axis_last(axis_last)
  153. );  
  154. system system_i
  155. (
  156. .DDR_addr(DDR_addr),
  157. .DDR_ba(DDR_ba),
  158. .DDR_cas_n(DDR_cas_n),
  159. .DDR_ck_n(DDR_ck_n),
  160. .DDR_ck_p(DDR_ck_p),
  161. .DDR_cke(DDR_cke),
  162. .DDR_cs_n(DDR_cs_n),
  163. .DDR_dm(DDR_dm),
  164. .DDR_dq(DDR_dq),
  165. .DDR_dqs_n(DDR_dqs_n),
  166. .DDR_dqs_p(DDR_dqs_p),
  167. .DDR_odt(DDR_odt),
  168. .DDR_ras_n(DDR_ras_n),
  169. .DDR_reset_n(DDR_reset_n),
  170. .DDR_we_n(DDR_we_n),
  171. .FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
  172. .FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
  173. .FIXED_IO_mio(FIXED_IO_mio),
  174. .FIXED_IO_ps_clk(FIXED_IO_ps_clk),
  175. .FIXED_IO_ps_porb(FIXED_IO_ps_porb),
  176. .FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
  177. .hdmi_tx_0_tmds_clk_n(hdmi_tx_0_tmds_clk_n),
  178. .hdmi_tx_0_tmds_clk_p(hdmi_tx_0_tmds_clk_p),
  179. .hdmi_tx_0_tmds_data_n(hdmi_tx_0_tmds_data_n),
  180. .hdmi_tx_0_tmds_data_p(hdmi_tx_0_tmds_data_p),
  181. .S_AXIS_S2MM_0_tdata(axis_wdata),
  182. .S_AXIS_S2MM_0_tkeep(16'hffff),
  183. .S_AXIS_S2MM_0_tlast(axis_last),
  184. .S_AXIS_S2MM_0_tready(axis_wready),
  185. .S_AXIS_S2MM_0_tvalid(axis_wvalid),
  186. .pl_clk(pl_clk0),
  187. .user_rstn(user_rstn),
  188. .user_start(user_start)
  189. );
  190.         
  191. endmodule
复制代码

以上代码中,调用了米联客uispi7606 IP CORE采集模拟数据,并且把采集好的数据写入到ui_axisbufw中,为了方便实验中观察数据,把第八个通道的AD数据改成了计数器。实际项目中可以把这个替换成第八个通道的ADC数据。
4.2设置地址分配
需要注意axi-lite接口地址,这个地址我们会在SDK 代码中使用AXI-GPIO控制传输的复位和启动。
512cf997acae4782a4a9f3fcb541cff3.jpg
4.3添加PIN约束
1:选中PROJECT  MANAGERà Add SourcesàAdd or create constraints,添加XDC约束文件。
11733ebaace04518bdb2cef745ae1732.jpg
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平台。
a2240f1be0ea4dc6bf7644d502933dba.jpg
5搭建Vitis-sdk工程
创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。
5.1创建SDK Platform工程
cdf80e63c66146509d80c086ec62c2b1.jpg
LWIP库的修改:
1:新版本系列工业级开发板板载网口芯片是RTL8211FDI,由于默认的驱动不支持,需要手动自己修改库文件。我们这里已经提供了修改好的库,解压到vivado的安装路径下的对于路径下:
b45a6bb5e359478abc6571d40d396005.jpg
10edbcdb9e28458bafe4876a987da67b.jpg
修改好后,需要关闭vitis-sdk然后重新打开sdk,否则无法识别修改的库
2:为了创建lwip工程需要先对soc_base中的board support package简称bsp设置lwip库的支持
df373e15d52741c68e177f11c40ae916.jpg
3:对lwip库参数修改以达到最佳性能。
本例程使用 RAW API,即函数调用不依赖操作系统。传输效率也比 SOCKET API 高,(具体可参考 xapp1026)。 将 use_axieth_on_zynq 和 use_emaclite_on_zynq 设为 0。如下图所示。
12e72a99f1074b108afb7833869fab46.jpg
修改 lwip_memory_options 设置,将 mem_size,memp_n_pbuf,mem_n_tcp_pcb,memp_n_tcp_seg 这 4 个参数 值设大,这样会提高 TCP 传输效率。如下图所示。
ab0190f2338844978c46fc971ad11bc5.jpg
修改 pbuf_options 设置,将 pbuf_pool_size 设大,增加可用的 pbuf 数量,这样同样会提高 TCP 传输效率。如下 图所示。
be936b8fafb34c7cb461e769547ce8bb.jpg
修改 tcp_options 设置,将 tcp_snd_buf,tcp_wnd 参数设大,这样同样会提高 TCP 传输效率。如下图所示。
1861d7a20c684fa08484ac73b7aba0c3.jpg
修改 temac_adapter_options 设置,将 n_rx_descriptors 和 n_tx_descriptors 参数设大。这样可以提高 zynq 内部 emac dma 的数据迁移效率,同样能提高 TCP 传输效率。如下图所示。
91a4edc308604ce7b536b4553a89d8bd.jpg
修改完成后重新编译soc_base
5.2创建DAQ001_lwip_tcp工程
bf35c654395641a4a07d370399e90714.jpg
6SDK程序分析
6.1DMA数据接收原理
e25173a9f3bc429bb1d36bccae301f52.jpg
每当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++用于计数已经完成的中断次数
  1. static void DMA_RxIntrHandler(void *Callback)
  2. {
  3.         u32 IrqStatus;
  4.         u32 Status;
  5.         XAxiDma *AxiDmaInst = (XAxiDma *)Callback;
  6.         /* Read pending interrupts */
  7.         IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);
  8.         /* Acknowledge pending interrupts */
  9.         XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);
  10.         /*
  11.          * If no interrupt is asserted, we do not do anything
  12.          */
  13.         if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
  14.                 return;
  15.         }
  16.         /*
  17.          * If error interrupt is asserted, raise error flag, reset the
  18.          * hardware to recover from the error, and return with no further
  19.          * processing.
  20.          */
  21.         if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
  22.                 xil_printf("rx error! \r\n");
  23.                 return;
  24.         }
  25.         /*
  26.          * If completion interrupt is asserted, then set RxDone flag
  27.          */
  28.         if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
  29.                 fdma_buf.record[fdma_buf.circle_cnt] = fdma_buf.circle_cnt;
  30.                 if(fdma_buf.circle_cnt<2)
  31.                 {
  32.                                 fdma_buf.circle_cnt ++ ;
  33.                                 //fdma_buf.next = fdma_buf.circle_cnt -1;
  34.                 }
  35.                 else
  36.                 {
  37.                                 fdma_buf.circle_cnt = 0;
  38.                                 //fdma_buf.next = 2;
  39.                 }
  40.                         Status = XAxiDma_SimpleTransfer(&AxiDma, (UINTPTR)(RxBufferPtr_DATA[fdma_buf.circle_cnt]),TOTAL_PKG_SIZE, XAXIDMA_DEVICE_TO_DMA);
  41.                         if (Status != XST_SUCCESS)
  42.                         {
  43.                                 xil_printf("axi dma failed! 0 %d\r\n", Status);
  44.                         }
  45.                         fdma_buf.pkg_done_cnt++;
  46.         }
  47. }
复制代码

6.2数据包设计
Lwip ip作为轻量级的协议栈,不能一次性发送所有数据,因此需要对数据分多次传输。本文中,传输的ADC数据包大小为16*1024*64 = 1024KB,数据设计为每包传输1024*16即16KB。因此传输完所有数据需要经过64次。
  1. #define TOTAL_PKG_SIZE              1024*16*64
  2. #define TCP_PACKEG_SIZE                         (1024*16)
  3. #define TCP_SEND_TIMES                        TOTAL_PKG_SIZE/TCP_PACKEG_SIZE
  4. #define TCP_SEND_LAST_SIZE      TOTAL_PKG_SIZE-(TCP_PACKEG_SIZE*TCP_SEND_TIMES)
  5. #define TCP_FIRST_SEND_SIZE     HEADER_SIZE + TCP_PACKEG_SIZE
复制代码

6.3帧头设计
为了让上位机知道接收ADC的数据帧头、采样速度、分辨率、有效通道、一包数据长度、帧计数,设计了如下数据帧头:
  1. typedef struct packet_header
  2. {
  3.         u32 ID0; //0xAA55AA55
  4.         u32 ID1;
  5.         u16 KSPS;
  6.         u8  Resolution;
  7.         u8  channels_signbit;
  8.         u16 length;
  9.         u16 fram_counter;
  10. }packet_header;
复制代码

6.4主程序分析
main函数中完成中断资源的初始化,lwip的初始化,并且通过一个while循环完成, tcp连接监听、数据的接收函数调用、数据的发送函数调用。本文的实验只需要,tcp连接监听和数据的发送功能。
通过定时器,每间隔250ms会判断一次request_pcb->state的状态,如果以太网没有连接,则会创建一个新的TCP连接。
1:init_intr_sys()
该函数初始化中断,包括PL中断和以太网传输需要用到的定时器中断
c724d827b9db46f2b346800be0e1c900.jpg
其中init_platform函数会对以太网定时器中断部分以及回调函数进行初始化。
可以关键看下platform_zynq.c中被调用的相关函数:
  1. void
  2. timer_callback(XScuTimer * TimerInstance)
  3. {
  4.         /* we need to call tcp_fasttmr & tcp_slowtmr at intervals specified
  5.          * by lwIP. It is not important that the timing is absoluetly accurate.
  6.          */
  7.         static int odd = 1;
  8. #if LWIP_DHCP==1
  9.     static int dhcp_timer = 0;
  10. #endif
  11.          TcpFastTmrFlag = 1;
  12.         odd = !odd;
  13. #ifndef USE_SOFTETH_ON_ZYNQ
  14.         ResetRxCntr++;
  15. #endif
  16.         if (odd) {
  17.                 TcpSlowTmrFlag = 1;
  18. #if LWIP_DHCP==1
  19.                 dhcp_timer++;
  20.                 dhcp_timoutcntr--;
  21.                 dhcp_fine_tmr();
  22.                 if (dhcp_timer >= 120) {
  23.                         dhcp_coarse_tmr();
  24.                         dhcp_timer = 0;
  25.                 }
  26. #endif
  27.         }
  28.         /* For providing an SW alternative for the SI #692601. Under heavy
  29.          * Rx traffic if at some point the Rx path becomes unresponsive, the
  30.          * following API call will ensures a SW reset of the Rx path. The
  31.          * API xemacpsif_resetrx_on_no_rxdata is called every 100 milliseconds.
  32.          * This ensures that if the above HW bug is hit, in the worst case,
  33.          * the Rx path cannot become unresponsive for more than 100
  34.          * milliseconds.
  35.          */
  36. #ifndef USE_SOFTETH_ON_ZYNQ
  37.         if (ResetRxCntr >= RESET_RX_CNTR_LIMIT) {
  38.                 xemacpsif_resetrx_on_no_rxdata(&server_netif);
  39.                 ResetRxCntr = 0;
  40.         }
  41. #endif
  42.         XScuTimer_ClearInterruptStatus(TimerInstance);
  43. }
  44. void platform_setup_timer(void)
  45. {
  46.         int Status = XST_SUCCESS;
  47.         XScuTimer_Config *ConfigPtr;
  48.         int TimerLoadValue = 0;
  49.         ConfigPtr = XScuTimer_LookupConfig(TIMER_DEVICE_ID);
  50.         Status = XScuTimer_CfgInitialize(&TimerInstance, ConfigPtr,
  51.                         ConfigPtr->BaseAddr);
  52.         if (Status != XST_SUCCESS) {
  53.                 xil_printf("In %s: Scutimer Cfg initialization failed...\r\n",
  54.                 __func__);
  55.                 return;
  56.         }
  57.         Status = XScuTimer_SelfTest(&TimerInstance);
  58.         if (Status != XST_SUCCESS) {
  59.                 xil_printf("In %s: Scutimer Self test failed...\r\n",
  60.                 __func__);
  61.                 return;
  62.         }
  63.         XScuTimer_EnableAutoReload(&TimerInstance);
  64.         /*
  65.          * Set for 250 milli seconds timeout.
  66.          */
  67.         TimerLoadValue = XPAR_CPU_CORTEXA9_0_CPU_CLK_FREQ_HZ / 8;
  68.         XScuTimer_LoadTimer(&TimerInstance, TimerLoadValue);
  69.         return;
  70. }
  71. void platform_setup_interrupts(void)
  72. {
  73.         /*
  74.          * Connect the device driver handler that will be called when an
  75.          * interrupt for the device occurs, the handler defined above performs
  76.          * the specific interrupt processing for the device.
  77.          */
  78.         XScuGic_RegisterHandler(INTC_BASE_ADDR, TIMER_IRPT_INTR,
  79.                                         (Xil_ExceptionHandler)timer_callback,
  80.                                         (void *)&TimerInstance);
  81.         /*
  82.          * Enable the interrupt for scu timer.
  83.          */
  84.         XScuGic_EnableIntr(INTC_DIST_BASE_ADDR, TIMER_IRPT_INTR);
  85.         return;
  86. }
  87. void platform_enable_interrupts()
  88. {
  89.         /*
  90.          * Enable non-critical exceptions.
  91.          */
  92.         Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
  93.         XScuTimer_EnableInterrupt(&TimerInstance);
  94.         XScuTimer_Start(&TimerInstance);
  95.         return;
  96. }
  97. void init_platform()
  98. {
  99.         platform_setup_timer();
  100.         platform_setup_interrupts();
  101.         return;
  102. }
复制代码

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配置
  1. dhcp_start(netif);
  2.         dhcp_timoutcntr = 24;
  3.         while (((netif->ip_addr.addr) == 0) && (dhcp_timoutcntr > 0))
  4.                 xemacif_input(netif);
  5.         if (dhcp_timoutcntr <= 0) {
  6.                 if ((netif->ip_addr.addr) == 0) {
  7.                         xil_printf("ERROR: DHCP request timed out\r\n");
  8.                         assign_default_ip(&(netif->ip_addr),
  9.                                         &(netif->netmask), &(netif->gw));
  10.                 }
  11.         }
复制代码

7:start_appication()
该函数首先
  1. void start_application(void)
  2. {
  3.         err_t err;
  4.         ip_addr_t remote_addr;
  5.         u32_t i;
  6.         cam_init();/* 初始化摄像头 */
  7.         first_trans_start = 0; /* 该变量判断是否第一次传输 */
  8.         client_connected =0; /* 判断是否连接状态 */
  9. #if LWIP_IPV6==1
  10.         remote_addr.type= IPADDR_TYPE_V6;
  11.         err = inet6_aton(TCP_SERVER_IPV6_ADDRESS, &remote_addr);
  12. #else
  13.         err = inet_aton(TCP_SERVER_IP_ADDRESS, &remote_addr); /* 设置服务器IP地址 */
  14. #endif /* LWIP_IPV6 */
  15.         if (!err) {
  16.                 xil_printf("Invalid Server IP address: %d\r\n", err);
  17.                 return;
  18.         }
  19.         /* Create Client PCB */
  20.         request_pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); /* 创建一个客户端PCB */
  21.         if (!request_pcb) {
  22.                 xil_printf("Error in PCB creation. out of memory\r\n");
  23.                 return;
  24.         }
  25.         err = tcp_connect(request_pcb, &remote_addr, TCP_CONN_PORT,
  26.                         tcp_client_connected); /* 设置客户端和主机连接上的回调函数 tcp_client_connected */
  27.         if (err) {
  28.                 xil_printf("Error on tcp_connect: %d\r\n", err);
  29.                 tcp_client_close(request_pcb);
  30.                 return;
  31.         }
  32.         client.client_id = 0;
  33.         return;
  34. }
复制代码

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到以太网的发送。
  1. while (1) {
  2.                 if (TcpFastTmrFlag) {
  3.                         if(request_pcb->state == CLOSED || (request_pcb->state == SYN_SENT && request_pcb->nrtx == TCP_SYNMAXRTX))//check conditions for create new tcp connection
  4.                         {
  5.                                 start_application();
  6.                         }
  7.                         tcp_fasttmr();
  8.                         TcpFastTmrFlag = 0;
  9.                 }
  10.                 if (TcpSlowTmrFlag) {
  11.                         tcp_slowtmr();
  12.                         TcpSlowTmrFlag = 0;
  13.                 }
  14.                 xemacif_input(netif);
  15.                 /* if connected to the server, start receive data from PL through axidma, then transmit the data to the PC software by TCP*/
  16.                 if(client_connected && tcp_trans_start)// if tcp connection is setup
  17.                         transfer_data();//call send_received_data() function sent data from ddr
  18.                 else
  19.                 {
  20.                         fdma_wr_set(0);
  21.                         first_trans_start = 0;
  22.                 }
  23.         }
复制代码

该函数负责把DDR中摄像头的图像数据发送出去,是本方案的核心。该函数会调用tcp_send_perf_traffic()函数。
9:tcp_send_perf_traffic()函数
当FDMA摄像头的缓存中存在数据,首选发送帧头,然后连续发送TCP_SEND_TIMES次TCP_PACKEG_SIZE大小的数据包,直到所有数据完成发送。
  1. static err_t tcp_send_perf_traffic(void)
  2. {
  3.         err_t err;
  4.         u8_t apiflags = TCP_WRITE_FLAG_COPY | TCP_WRITE_FLAG_MORE;
  5.         if (c_pcb == NULL) {
  6.                 return ERR_CONN;
  7.         }
  8. #ifdef __MICROBLAZE__
  9.         /* Zero-copy pbufs is used to get maximum performance for Microblaze.
  10.          * For Zynq A9, ZynqMP A53 and R5 zero-copy pbufs does not give
  11.          * significant improvement hense not used. */
  12.         apiflags = 0;
  13. #endif
  14.         struct tcp_pcb *tpcb = c_pcb;
  15.         if (!tpcb)
  16.         return;
  17.         if(first_trans_start==0)
  18.         {
  19.                 fdma_buf.circle_cnt=0;
  20.                 fdma_buf.next=0;
  21.                 fdma_buf.pkg_done_cnt=0;
  22.                 fdma_buf.pkg_cnt=0;
  23.                 fdma_buf.fram_cnt=0;
  24.                 fdma_wr_set(1);
  25.                 pkg_offset =0;
  26.                 first_trans_start =1;
  27.                 XAxiDma_SimpleTransfer(&AxiDma, (UINTPTR)(RxBufferPtr_DATA[fdma_buf.circle_cnt]),TOTAL_PKG_SIZE, XAXIDMA_DEVICE_TO_DMA); //第一次启动DMA
  28.         }
  29.         /*当DMA中断发生后,通过判单fdma_buf.pkg_done_cnt,知道已经完成的中断次数,并且必须是0~3之间,才是有效的,如果大于2就是溢出了*/
  30.         if(fdma_buf.pkg_done_cnt> 0 && fdma_buf.pkg_done_cnt<3) //ADC数据包1MB 分64次传输
  31.         {
  32.                 if (tcp_sndbuf(tpcb) > TCP_FIRST_SEND_SIZE) //当lwip发送缓存有足够大小才进行传输
  33.                 {
  34.                         /*transmit received data through TCP*/
  35.                         //xil_printf("bufaddr1=: %x\r\n",bufaddr);
  36.                         if(fdma_buf.pkg_cnt==0) //第一帧需要传输帧头,通过移动指针,在数据头部插入帧头
  37.                         {
  38.                                 bufaddr = (u8*)(RxBufferPtr[fdma_buf.record[fdma_buf.next]]);//16 = packet_header size
  39.                                 header_p = (packet_header *)bufaddr;
  40.                                 header_p->ID0 = HEADER_ID0;
  41.                                 header_p->ID1 = HEADER_ID1;
  42.                                 header_p->KSPS= HEADER_KSPS;
  43.                                 header_p->Resolution= HEADER_RESOLUTION;
  44.                                 header_p->channels_signbit= HEADER_CHANNLES_SIGNBIT;
  45.                                 header_p->length = HEADER_LENGTH;
  46.                                 header_p->fram_counter = fdma_buf.fram_cnt;
  47. /*对于从PL到PS到DDR数据,需要用Xil_DCacheInvalidateRange确保cache一致性*/
  48.                                 Xil_DCacheInvalidateRange((INTPTR)(bufaddr+HEADER_SIZE), TCP_PACKEG_SIZE);
  49. /*数据写入TCP缓存*/
  50.                                 err = tcp_write(tpcb, bufaddr, TCP_FIRST_SEND_SIZE, apiflags);
  51. /*修改地址*/
  52.                                 bufaddr = bufaddr + HEADER_SIZE;
  53. /*记录已经传输的包*/
  54.                                 pkg_offset = pkg_offset + TCP_PACKEG_SIZE;
  55.                         }
  56. else if(fdma_buf.pkg_cnt < TCP_SEND_TIMES) /*继续发送剩余包*/
  57.                         {
  58.         bufaddr = bufaddr + TCP_PACKEG_SIZE; /*下一小包数据的首地址*/
  59.                                 Xil_DCacheInvalidateRange((u32)bufaddr, TCP_PACKEG_SIZE); //确保cache一致性
  60.                                 err = tcp_write(tpcb, bufaddr, TCP_PACKEG_SIZE, apiflags);//数据写入TCP缓存
  61. /*记录已经传输的包*/
  62.                                 pkg_offset = pkg_offset + TCP_PACKEG_SIZE;
  63.                         }
  64.         else if(TCP_SEND_LAST_SIZE>0) /*如果还有最后的非整小包数据,继续发送剩余包*/
  65.                         {
  66.                                 bufaddr = bufaddr + TCP_PACKEG_SIZE;
  67.                                 Xil_DCacheInvalidateRange((u32)bufaddr, TCP_SEND_LAST_SIZE);
  68.                                 err = tcp_write(tpcb, bufaddr, TCP_SEND_LAST_SIZE, apiflags);
  69.                                 pkg_offset = pkg_offset + TCP_SEND_LAST_SIZE;
  70.                         }
  71.                     if (err != ERR_OK) {
  72.                                 xil_printf("txperf: Error on tcp_write: %d\r\n", err);
  73.                                 return;
  74.                         }
  75.                         err = tcp_output(tpcb);
  76.                         if (err != ERR_OK) {
  77.                                 xil_printf("txperf: Error on tcp_output: %d\r\n",err);
  78.                                 return;
  79.                         }
  80.                         fdma_buf.pkg_cnt++;
  81. /*判断数据是否发完*/
  82.                         if(pkg_offset == TOTAL_PKG_SIZE)
  83.                         {
  84.                                 pkg_offset=0;
  85.                                 fdma_buf.fram_cnt++;
  86.                                 fdma_buf.pkg_done_cnt--;
  87.                                 fdma_buf.pkg_cnt = 0;
  88.                                 if(fdma_buf.next<2)
  89.                                         fdma_buf.next++;
  90.                                 else
  91.                                         fdma_buf.next=0;
  92.                         }
  93.                 }
  94.         }
  95.         else if(fdma_buf.pkg_done_cnt > 2) //如果缓存不能处理,通过设置irst_trans_start = 0再次同步
  96.         {
  97.                 xil_printf("error pkg_done_cnt = %d \r\n", fdma_buf.pkg_done_cnt);
  98.                 first_trans_start = 0;
  99.         }
  100. /*
  101.         if (client.end_time || client.i_report.report_interval_time) {
  102.                 u64_t now = get_time_ms();
  103.                 if (client.i_report.report_interval_time) {
  104.                         if (client.i_report.start_time) {
  105.                                 u64_t diff_ms = now - client.i_report.start_time;
  106.                                 u64_t rtime_ms = client.i_report.report_interval_time;
  107.                                 if (diff_ms >= rtime_ms) {
  108.                                         tcp_conn_report(diff_ms, INTER_REPORT);
  109.                                         client.i_report.start_time = 0;
  110.                                         client.i_report.total_bytes = 0;
  111.                                 }
  112.                         } else {
  113.                                 client.i_report.start_time = now;
  114.                         }
  115.                 }
  116.         }*/
  117.         return ERR_OK;
  118. }
复制代码

10:本地IP地址设置
在主程序tcp_lwip_test.c中定义
  1. #define DEFAULT_IP_ADDRESS        "192.168.137.10"
  2. #define DEFAULT_IP_MASK                "255.255.255.0"
  3. #define DEFAULT_GW_ADDRESS        "192.168.1.1"
复制代码

11:远程主机IP设置
在头文件tcp_client.h中
  1. #define TCP_SERVER_IP_ADDRESS "192.168.137.209"
  2. #define TCP_CONN_PORT 5001
复制代码


7方案演示
7.1硬件准备
3903b64c701b4f9ead2c135e8236c1aa.jpg
7.2实验结果
把开发板网卡通过网线接到 PC 网口上,修改 IP 地址如下图:
cbf1383900554967aa1d361d25096507.jpg
打开网络调试助手,第一次用的时候 windows 会提示你是否允许访问网络一定要选择是,否则你就无法通信了。 设置电脑为 TCP Server 本机 IP 为刚才设置的 192.168.10.209 端口号为 5001.
470436f0ac794f0186bb34d455f29839.jpg
通过以太网启动数据发送
89b7b9e95a95459cb51132618f42b341.jpg
通过以太网暂停数据发送
2e1fbc094c83479d8c9315a0d7b1f341.jpg
查看网速
3695305e00d149158d4a2856da681900.jpg
利用SDK查看内存中数据
29537df57aeb4540b2a663815a8747bb.jpg
利用wireshark观察数据,可以看到我的测试电脑上数据包每个大小1446bytes,一共传输了1446*11+494= 16400bytes,正好和我们SDK代码中发送的数据一致。
fddb47c957264527a9e678ba027fa644.jpg
从wireshark抓到的数据看,X86端需要注意大小端的问题。
最后如果有一个示波软件显示波形就完美了,敬请期待吧。
de578165acd9445389ca83c1339ebc04.jpg
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则