[X]关闭

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

文档创建者:FPGA课程
浏览次数:275
最后更新:2024-10-10
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 1_SDK应用方案(仅旗舰型号) » 2-SDK高级应用方案
本帖最后由 FPGA课程 于 2024-10-10 18:19 编辑

​ 软件版本: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概述
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机。
1ee65a37e4f5428b8aec0b1688e032df.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端的定义如下:
  1. typedef struct packet_data
  2. {
  3.         u16 ADC0;
  4.         u16 ADC1;
  5.         u16 ADC2;
  6.         u16 ADC3;
  7.         u16 ADC4;
  8.         u16 ADC5;
  9.         u16 ADC6;
  10.         u16 ADC7;
  11. }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
                        
帧头数据格式定义如下:
  1. typedef struct packet_header
  2. {
  3.         u32 ID0;
  4.         u32 ID1;
  5.         u16 KSPS;
  6.         u8  Resolution;
  7.         u8  channels_signbit;
  8.         u16 length
  9.         u16 fram_counter
  10. }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位宽), 用户写入的数据经过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)
c1d0639d8ea5461091da6fa71266e0a1.jpg
2:uiFDMA设置
Fdma的数据位宽可以设置128这样效率最高。
016bea3127ab4631ab5a5caa3ea346ca.jpg
3:AXI Interconnect设置
设置FIFO可以增加数据的吞吐能力
51185b97a4734eed9c8ef86312e25023.jpg
4:修改system_wrapper.v
将自动产生的system_wrapper.v复制到本方案工程路径soc_prj/uisrc/01_rtl/system_wrapper.v并对其修改,修改好的代码如下:
  1. /*******************************MILIANKE*******************************
  2. *Company : MiLianKe Electronic Technology Co., Ltd.
  3. *WebSite:https://www.milianke.com
  4. *TechWeb:https://www.uisrc.com
  5. *tmall-shop:https://milianke.tmall.com
  6. *jd-shop:https://milianke.jd.com
  7. *taobao-shop1: https://milianke.taobao.com
  8. *Create Date: 2021/10/15
  9. *Module Name:system_wrapper
  10. *File Name:system_wrapper.v
  11. *Description:
  12. *The reference demo provided by Milianke is only used for learning.
  13. *We cannot ensure that the demo itself is free of bugs, so users
  14. *should be responsible for the technical problems and consequences
  15. *caused by the use of their own products.
  16. *Copyright: Copyright (c) MiLianKe
  17. *All rights reserved.
  18. *Revision: 1.0
  19. *Signal description
  20. *1) _i input
  21. *2) _o output
  22. *3) _n activ low
  23. *4) _dg debug signal
  24. *5) _r delay or register
  25. *6) _s state mechine
  26. *********************************************************************/
  27. `timescale 1 ps / 1 ps
  28. module system_wrapper
  29. (
  30. inout wire [14:0]DDR_addr,
  31. inout wire [2:0]DDR_ba,
  32. inout wire DDR_cas_n,
  33. inout wire DDR_ck_n,
  34. inout wire DDR_ck_p,
  35. inout wire DDR_cke,
  36. inout wire DDR_cs_n,
  37. inout wire [3:0]DDR_dm,
  38. inout wire [31:0]DDR_dq,
  39. inout wire [3:0]DDR_dqs_n,
  40. inout wire [3:0]DDR_dqs_p,
  41. inout wire DDR_odt,
  42. inout wire DDR_ras_n,
  43. inout wire DDR_reset_n,
  44. inout wire DDR_we_n,
  45. inout wire FIXED_IO_ddr_vrn,
  46. inout wire FIXED_IO_ddr_vrp,
  47. inout wire [53:0]FIXED_IO_mio,
  48. inout wire FIXED_IO_ps_clk,
  49. inout wire FIXED_IO_ps_porb,
  50. inout wire FIXED_IO_ps_srstb,
  51. //******************************
  52. output wire hdmi_tx_0_tmds_clk_n,
  53. output wire hdmi_tx_0_tmds_clk_p,
  54. output wire [2:0]hdmi_tx_0_tmds_data_n,
  55. output wire [2:0]hdmi_tx_0_tmds_data_p,
  56. //******************************
  57. input  wire ad7606_busy_i,
  58. output wire ad7606_cs_o,        //ad7606 AD cs
  59. output wire ad7606_sclk_o,      //ad7606 AD data read
  60. output wire ad7606_rst_o,       //ad7606 AD reset
  61. output wire ad7606_convsta_o,   //ad7606 AD convert start
  62. output wire ad7606_convstb_o,   //ad7606 AD convert start      
  63. output wire ad7606_range_o,
  64. input  wire ad7606_out_a_i,
  65. input  wire ad7606_out_b_i,
  66. output wire ad7606card_en
  67. );
  68. assign ad7606card_en = 1'b1;
  69. wire pl_clk;
  70. wire user_rstn;
  71. wire user_start;
  72. wire [127:0]ud_wdata_0;
  73. wire ud_wde_0;
  74. wire ud_wclk_0;
  75. reg [15:0]test_data;
  76. always@(posedge pl_clk)begin
  77.         if(user_rstn == 1'b0)begin
  78.                 test_data <= 12'd0;
  79.         end
  80.         else if(ud_wde_0) begin
  81.                test_data <= test_data + 1'b1;
  82.         end
  83. end
  84. wire [63:0] ad7606_out_a,ad7606_out_b;
  85. wire [15:0] ad_ch0,ad_ch1,ad_ch2,ad_ch3,ad_ch4,ad_ch5,ad_ch6,ad_ch7;
  86. assign ad_ch0 = ad7606_out_a[63:48];
  87. assign ad_ch1 = ad7606_out_a[47:32];
  88. assign ad_ch2 = ad7606_out_a[31:16];
  89. assign ad_ch3 = ad7606_out_a[15: 0];
  90. assign ad_ch4 = ad7606_out_b[63:48];
  91. assign ad_ch5 = ad7606_out_b[47:32];
  92. assign ad_ch6 = ad7606_out_b[31:16];
  93. assign ad_ch7 = ad7606_out_b[15: 0];
  94. assign ud_wdata_0 = {test_data,ad_ch6,ad_ch5,ad_ch4,ad_ch3,ad_ch2,ad_ch1,ad_ch0};
  95. assign ud_wde_0   = user_start & ad7606_cap_en;
  96. assign ud_wclk_0  = pl_clk;
  97. uispi7606#(
  98. .SPI_DIV(10'd5),
  99. .T5US_DIV(10'd499)
  100. )
  101. uispi7606_inst
  102. (
  103. .ad_clk_i(pl_clk),            
  104. .ad_rst_i(user_rstn==1'b0),
  105. .ad_busy_i(ad7606_busy_i),                  
  106. .ad_cs_o(ad7606_cs_o),
  107. .ad_sclk_o(ad7606_sclk_o),      
  108. .ad_rst_o(ad7606_rst_o),         
  109. .ad_convsta_o(ad7606_convsta_o),      
  110. .ad_convstb_o(ad7606_convstb_o),  
  111. .ad_range_o(ad7606_range_o),
  112. .ad_out_a_i(ad7606_out_a_i),
  113. .ad_out_b_i(ad7606_out_b_i),
  114. .ad_out_a(ad7606_out_a),
  115. .ad_out_b(ad7606_out_b),
  116. .ad_cap_en(ad7606_cap_en)
  117. );
  118. ila_0 ila0_dg
  119. (
  120. .clk(pl_clk),
  121. .probe0({test_data[15:0],ad_ch0,ud_wde_0,user_start,user_rstn})
  122. );
  123. system system_i
  124. (
  125. .DDR_addr(DDR_addr),
  126. .DDR_ba(DDR_ba),
  127. .DDR_cas_n(DDR_cas_n),
  128. .DDR_ck_n(DDR_ck_n),
  129. .DDR_ck_p(DDR_ck_p),
  130. .DDR_cke(DDR_cke),
  131. .DDR_cs_n(DDR_cs_n),
  132. .DDR_dm(DDR_dm),
  133. .DDR_dq(DDR_dq),
  134. .DDR_dqs_n(DDR_dqs_n),
  135. .DDR_dqs_p(DDR_dqs_p),
  136. .DDR_odt(DDR_odt),
  137. .DDR_ras_n(DDR_ras_n),
  138. .DDR_reset_n(DDR_reset_n),
  139. .DDR_we_n(DDR_we_n),
  140. .FIXED_IO_ddr_vrn(FIXED_IO_ddr_vrn),
  141. .FIXED_IO_ddr_vrp(FIXED_IO_ddr_vrp),
  142. .FIXED_IO_mio(FIXED_IO_mio),
  143. .FIXED_IO_ps_clk(FIXED_IO_ps_clk),
  144. .FIXED_IO_ps_porb(FIXED_IO_ps_porb),
  145. .FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
  146. .hdmi_tx_0_tmds_clk_n(hdmi_tx_0_tmds_clk_n),
  147. .hdmi_tx_0_tmds_clk_p(hdmi_tx_0_tmds_clk_p),
  148. .hdmi_tx_0_tmds_data_n(hdmi_tx_0_tmds_data_n),
  149. .hdmi_tx_0_tmds_data_p(hdmi_tx_0_tmds_data_p),
  150. .pl_clk(pl_clk),
  151. .ud_wclk_0(ud_wclk_0),
  152. .ud_wdata_0(ud_wdata_0),
  153. .ud_wde_0(ud_wde_0),
  154. .user_rstn(user_rstn),
  155. .user_start(user_start)
  156. );
  157. endmodule
复制代码


以上代码中,调用了米联客uispi7606 IP CORE采集模拟数据,并且把采集好的数据写入到uifdmadbuf中,为了方便实验中观察数据,把第八个通道的AD数据改成了计数器。实际项目中可以把这个替换成第八个通道的ADC数据。
4.2设置地址分配
需要注意uifdma_dbuf的axi-lite接口地址,这个地址我们会在SDK 代码中用到读寄存器。
5b375c7f88d546408313a919da72466f.jpg
4.3添加PIN约束
1:选中PROJECT  MANAGERà Add SourcesàAdd or create constraints,添加XDC约束文件。
1346e1017d6947fbbd4e2d0e79b8a52b.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平台。
74f4a7be97294772a7ec0337af76f678.jpg
5搭建Vitis-sdk工程
创建soc_base sdk platform和APP工程的过程不再重复,如果不清楚请参考本章节第一个demo。
5.1创建SDK Platform工程
b71da7c2a5794e2f8f421f0b0d07cca9.jpg
LWIP库的修改:
1:新版本系列工业级开发板板载网口芯片是RTL8211FDI,由于默认的驱动不支持,需要手动自己修改库文件。我们这里已经提供了修改好的库,解压到vivado的安装路径下的对于路径下:
20624cd852344d72a0b318c77779d8ec.jpg
a2d292d2b59248aeb771486bedd9e307.jpg
修改好后,需要关闭vitis-sdk然后重新打开sdk,否则无法识别修改的库
2:为了创建lwip工程需要先对soc_base中的board support package简称bsp设置lwip库的支持
c29c2e6d09ec4235a30018bf5dfa5714.jpg
3:对lwip库参数修改以达到最佳性能。
本例程使用 RAW API,即函数调用不依赖操作系统。传输效率也比 SOCKET API 高,(具体可参考 xapp1026)。 将 use_axieth_on_zynq 和 use_emaclite_on_zynq 设为 0。如下图所示。
0774c9d9cace4cda828a90c783da66e7.jpg
修改 lwip_memory_options 设置,将 mem_size,memp_n_pbuf,mem_n_tcp_pcb,memp_n_tcp_seg 这 4 个参数 值设大,这样会提高 TCP 传输效率。如下图所示。
b7912caedb2d45d099158b7e8b568815.jpg
修改 pbuf_options 设置,将 pbuf_pool_size 设大,增加可用的 pbuf 数量,这样同样会提高 TCP 传输效率。如下 图所示。
103f0f78b45f42e282a62e977bfd3839.jpg
修改 tcp_options 设置,将 tcp_snd_buf,tcp_wnd 参数设大,这样同样会提高 TCP 传输效率。如下图所示。
ff90350a8c4d40d790b1b0eed87bf9b6.jpg
修改 temac_adapter_options 设置,将 n_rx_descriptors 和 n_tx_descriptors 参数设大。这样可以提高 zynq 内部 emac dma 的数据迁移效率,同样能提高 TCP 传输效率。如下图所示。
3b624d493a7c4662910b38944d28fdcc.jpg
修改完成后重新编译soc_base
5.2创建DAQ001 _lwip工程
902652c148b74fb7a569d5a918e8b53e.jpg
6SDK程序分析
6.1FDMA数据接收原理
1d3f4ec81c794b7fa22e8d4306fea645.jpg
每当uifdmadbuf发送的中断后,该函数被调用,通过读取uifdmadbuf axi-lite的寄存器,获取当前哪一个缓存产生了中断(代表数据发送到PS DDR了)。
为了增加数据的吞吐能力,在中断中,不宜进行数据搬运,我们设计了要给结构体,可以用于标记已经写入DDR的数据。
1:PS_RX_intr_Handler
  1. void PS_RX_intr_Handler(void *param)
  2. {
  3.                 fdma_buf.record[fdma_buf.circle_cnt]= Xil_In32((UINTPTR)FDMA_DBUF_BASE_ADDR);
  4.                 if(fdma_buf.circle_cnt<2)
  5.                         fdma_buf.circle_cnt ++ ;
  6.                 else
  7.                         fdma_buf.circle_cnt = 0;
  8.                 fdma_buf.pkg_done_cnt++;
  9. }
复制代码
  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;<img width="15" _height="15" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" border="0" alt="" style="background-color: rgb(255, 255, 255);">
复制代码
6.4主程序分析
main函数中完成中断资源的初始化,lwip的初始化,并且通过一个while循环完成, tcp连接监听、数据的接收函数调用、数据的发送函数调用。本文的实验只需要,tcp连接监听和数据的发送功能。
通过定时器,每间隔250ms会判断一次request_pcb->state的状态,如果以太网没有连接,则会创建一个新的TCP连接。
1:init_intr_sys()
该函数初始化中断,包括PL中断和以太网传输需要用到的定时器中断
973654c53c8d48c499f8279583159a8d.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.         }
复制代码

8:transfer_data()
该函数负责把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.         }
  28.         /*if the last fdma transmission is done, the interrupt triggered, then start TCP transmission*/
  29.         if(fdma_buf.pkg_done_cnt> 0 && fdma_buf.pkg_done_cnt<4) //1MB divide in to 64 times
  30.         {
  31.                         /*set fdma buffer transmission when the current transmission is done*/
  32.                                 /* if tcp send buffer has enough space to hold the data we want to transmit from PL, then start tcp transmission*/
  33.                 if (tcp_sndbuf(tpcb) > TCP_FIRST_SEND_SIZE)
  34.                 {
  35.                         /*transmit received data through TCP*/
  36.                         //xil_printf("bufaddr1=: %x\r\n",bufaddr);
  37.                         if(fdma_buf.pkg_cnt==0)
  38.                         {
  39.                                 bufaddr = (u8*)(RxBufferPtr[fdma_buf.record[fdma_buf.next]]);//16 = packet_header size
  40.                                 header_p = (packet_header *)bufaddr;
  41.                                 header_p->ID0 = HEADER_ID0;
  42.                                 header_p->ID1 = HEADER_ID1;
  43.                                 header_p->KSPS= HEADER_KSPS;
  44.                                 header_p->Resolution= HEADER_RESOLUTION;
  45.                                 header_p->channels_signbit= HEADER_CHANNLES_SIGNBIT;
  46.                                 header_p->length = HEADER_LENGTH;
  47.                                 header_p->fram_counter = fdma_buf.fram_cnt;
  48.                                 Xil_DCacheInvalidateRange((u32)bufaddr + HEADER_SIZE, TCP_FIRST_SEND_SIZE);
  49.                                 err = tcp_write(tpcb, bufaddr, TCP_FIRST_SEND_SIZE, apiflags);
  50.                                 bufaddr = bufaddr + HEADER_SIZE;
  51.                                 pkg_offset = pkg_offset + TCP_PACKEG_SIZE;
  52.                         }
  53.                         else if(fdma_buf.pkg_cnt < TCP_SEND_TIMES)
  54.                         {
  55.                                 bufaddr = bufaddr + TCP_PACKEG_SIZE;
  56.                                 Xil_DCacheInvalidateRange((u32)bufaddr, TCP_PACKEG_SIZE);
  57.                                 err = tcp_write(tpcb, bufaddr, TCP_PACKEG_SIZE, apiflags);
  58.                                 pkg_offset = pkg_offset + TCP_PACKEG_SIZE;
  59.                         }
  60.                         else if(TCP_SEND_LAST_SIZE>0)
  61.                         {
  62.                                 bufaddr = bufaddr + TCP_PACKEG_SIZE;
  63.                                 Xil_DCacheInvalidateRange((u32)bufaddr, TCP_SEND_LAST_SIZE);
  64.                                 err = tcp_write(tpcb, bufaddr, TCP_SEND_LAST_SIZE, apiflags);
  65.                                 pkg_offset = pkg_offset + TCP_SEND_LAST_SIZE;
  66.                         }
  67.                     if (err != ERR_OK) {
  68.                                 xil_printf("txperf: Error on tcp_write: %d\r\n", err);
  69.                                 return;
  70.                         }
  71.                         err = tcp_output(tpcb);
  72.                         if (err != ERR_OK) {
  73.                                 xil_printf("txperf: Error on tcp_output: %d\r\n",err);
  74.                                 return;
  75.                         }
  76.                         fdma_buf.pkg_cnt++;
  77.                         if(pkg_offset == TOTAL_PKG_SIZE)
  78.                         {
  79.                                 pkg_offset=0;
  80.                                 fdma_buf.fram_cnt++;
  81.                                 fdma_buf.pkg_done_cnt--;
  82.                                 fdma_buf.pkg_cnt = 0;
  83.                                 if(fdma_buf.next<2)
  84.                                         fdma_buf.next++;
  85.                                 else
  86.                                         fdma_buf.next=0;
  87.                         }
  88.                 }
  89.         }
  90.         else if(fdma_buf.pkg_done_cnt > 2) //如果缓存不能处理,通过设置irst_trans_start = 0再次同步
  91.         {
  92.                 xil_printf("error pkg_done_cnt = %d \r\n", fdma_buf.pkg_done_cnt);
  93.                 first_trans_start = 0;
  94.         }
  95. /*
  96.         if (client.end_time || client.i_report.report_interval_time) {
  97.                 u64_t now = get_time_ms();
  98.                 if (client.i_report.report_interval_time) {
  99.                         if (client.i_report.start_time) {
  100.                                 u64_t diff_ms = now - client.i_report.start_time;
  101.                                 u64_t rtime_ms = client.i_report.report_interval_time;
  102.                                 if (diff_ms >= rtime_ms) {
  103.                                         tcp_conn_report(diff_ms, INTER_REPORT);
  104.                                         client.i_report.start_time = 0;
  105.                                         client.i_report.total_bytes = 0;
  106.                                 }
  107.                         } else {
  108.                                 client.i_report.start_time = now;
  109.                         }
  110.                 }
  111.         }*/
  112.         return ERR_OK;
  113. }
复制代码

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 50
复制代码

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


20230724024159.jpg?origin_url=file%3A%2F%2F%2FC%3A%5CUsers%5CADMINI~1%5CAppData%5CLocal%5CTemp%5Cksohtml1868%5Cwps16.jpg&pos_id=uaTT2LGt
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则