基于 AN108 模块的ADC 采集以太网传输

这篇具有很好参考价值的文章主要介绍了基于 AN108 模块的ADC 采集以太网传输。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、实验要求

本文基于AN108模块,将ADC采集的数据通过以太网传输到上位机。

二、任务分析

本实验的硬件设计部分及vitis均参照了ALINX FPGA ZYNQ Ultrascale+ MPSOC教程中实验 基于 AN9280模块的 ADC 采集以太网传输,其B站视频链接如下

【62】ALINX Zynq MPSoC XILINX FPGA视频教程 SDK 裸机开发—ADC以太网传输协议_哔哩哔哩_bilibili

首先AD9280模块进行AD转换的模拟信号来源,根据实验二ADDA测试的经验,本实验通过FPGA+DAC产生正弦波,通过同轴电缆自环将正弦波输入到AD9280模块进行AD转换。

 FPGA实验二:ADDA测试_Laid-back guy的博客-CSDN博客https://blog.csdn.net/weixin_45303812/article/details/123762811?spm=1001.2014.3001.5502

然后,我们需要用zynq读取ADC数据,该过程涉及了PL与PS之间的数据交换,对于传输速度要求较高、数据量大、地址连续的场合,如高速AD/DA,可以通过 AXI DMA 来完成。通过在 PL 中添加 AXI DMA IP 核,并利用 AXI_HP 接口完 成高速的数据传输(ZYNQ 提供了两种 DMA,一种是集成在 PS 中的硬核 DMA,另一种是PL中使用的软核AXI DMA IP,关于DMA(Direct Memory Access,直接存储器访问)的具体内容计划另写一篇文章呈现,先挖个坑)

另外,由于ADC采集模块需要将ADC数据发送到DMA,AXI DMA IP 核的接口形式为AXIS接口,因此我们需要创建一个带有 AXI 接口的 IP 核来实现ADC数据的发送。

关于以太网传输方面,采用UDP将ADC采集的数据传输到上位机,因此采用了以下基于UDP传输的通信协议,该协议包含在UDP数据包中:

1、获取板卡信息

①询问命令(共5字节,由上位机通过以太网发送)

字节数

1

4

命令信息

Header

0x00000000或0x00010001

②应答命令(共27字节,由开发板通过以太网发送)

字节数

命令信息

1

Header|0x01

4

0x00010001

6

板卡MAC地址

4

板卡IP地址

1

符号位 0x00:无符号数 0x01 有符号数此功能无效,上位机要求无符号数

1

ADC有效数据长度,如AD9280为8位,即ADC_BITS=8

1

采集一次ADC的字节数,此功能无效,上位机要求数据位宽为两个字节

1

采样通道,此功能上位机未实现,本实验为单通道采样

4

采样率,即采样的频率,程序中设为32MHz

4

缓存的ADC数据长度,单位为字节

2、获取数据

①、控制命令(共19字节,上位机发送数据请求)

字节数

命令信息

1

Header

4

0x00010002

6

板卡MAC地址,确认是本地的MAC地址

4

采样通道(本功能未实现)

4

采样次数(采集数据为8位,采样次数位缓存数据长度的一半)

②、应答命令(共1029字节,由开发板通过以太网发送)

字节数

命令信息

1

Header|0x01

4

0x00010002

1024

ADC数据

每个UDP包都含有 Header,第一个字节其格式及意义如下:

比特位

值(0)

值(1)

bit0

查询或控制

应答

bit1-7

随机数据

开发板应答时,Header|0x01即高7位随机数据不变,bit0设置为1

整个通讯流程分为以下几点:

  • 获取板卡信息:1、上位机发送询问命令;2、开发板应答询问
  • 获取数据:3、上位机发送控制命令请求数据;4、开发板应答发送数据
  • 重复步骤3、4

三、基于 AN108 模块的ADC 采集以太网传输

3.1硬件环境搭建

相关IP核的配置如下所示:

FPGA+DAC产生正弦波的部分分别为一个DDS IP核与ad9708_send模块

1.DDS Compiler

System Clock为100MHz,Parameter Selection为System  Parameter,输出波形为正弦波,频率1MHz,其余保持默认即可。

基于 AN108 模块的ADC 采集以太网传输

基于 AN108 模块的ADC 采集以太网传输

基于 AN108 模块的ADC 采集以太网传输

2.ad9708_send

 DA数据发送模块通过编写Verilog代码生成ad9708_send RTL模块,其Verilog代码如下所示:

`timescale 1ns / 1ps
module ad9708_send(
     input                 dac_clk    ,  //时钟
     input                 dac_rst_n  ,  //复位信号,低电平有效
     output reg [7:0]            dac_data,
     data to DAC
     input        [7:0]          s_axis_tdata,  
     output                      s_axis_tready,     // 由于DAC的工作频率小于DDS工作频率,所                            以DAC接口控制器给FIFO的RDY信号应该一直为高。
     input                       s_axis_tvalid
     );
 //*****************************************************
 //**                    main code
 //*****************************************************
 
assign  s_axis_tready = 1'b1;
reg [7:0]   dac_buf_data ;  //fifo read data
/* When s_axis_tready and s_axis_tvalid both valid, write data from fifo to da9708_send*/
always@(posedge dac_clk or negedge dac_rst_n)
begin
	if(dac_rst_n == 1'b0)
	begin
	  dac_buf_data <= 8'd0 ;
	end
	else if (s_axis_tready & s_axis_tvalid)
	begin
      dac_buf_data <= s_axis_tdata ;
	end
end

always@(posedge dac_clk or negedge dac_rst_n)
begin
	if(dac_rst_n == 1'b0)
	begin
		dac_data     <= 8'd0 ;
	end
	else
            begin
                  dac_data <= dac_buf_data^'h0x80;   //将读到的DDS数据与0x80取异或赋值给DA数据端口  
            end
end
endmodule

AD数据接受模块引用了ALINX教程中实验第十七章 DMA 使用之 ADC 示波器(AN108)中的自定义IP核ad9280_sample

3.ad9280_sample

本实验引用了ALINX教程第十七章 DMA 使用之 ADC 示波器(AN108)中的ADC自定义IP核,需要注意的是

在模块ad9280_sample_v1_0_S00_AXI例化用户自定义代码ad9280_sample时,将采样长度与开始采样标志分别连接至寄存器地址1对应的数据与寄存器地址0对应数据低1位。

基于 AN108 模块的ADC 采集以太网传输

为此需要在Vitis中做如下对应设置,该部分定义于adc_dma.h文件

基于 AN108 模块的ADC 采集以太网传输

此外,在用户自定义代码ad9280_sample中使用XPM定义了一个读写深度1024,数据位宽为8的异步FIFO进行跨时钟域数据处理 

xpm_fifo_async #(
   .CDC_SYNC_STAGES      (2),        
   .DOUT_RESET_VALUE     ("1"),      
   .ECC_MODE             ("no_ecc"), 
   .FIFO_MEMORY_TYPE     ("auto"),   
   .FIFO_READ_LATENCY    (1),        
   .FIFO_WRITE_DEPTH     (1024),     
   .FULL_RESET_VALUE     (0),        
   .PROG_EMPTY_THRESH    (10),       
   .PROG_FULL_THRESH     (10),       
   .RD_DATA_COUNT_WIDTH  (11),       
   .READ_DATA_WIDTH      (8),        
   .READ_MODE            ("std"),    
   .RELATED_CLOCKS       (0),        
   .USE_ADV_FEATURES     ("0707"),   
   .WAKEUP_TIME          (0),        
   .WRITE_DATA_WIDTH     (8),        
   .WR_DATA_COUNT_WIDTH  (11)        
)
xpm_fifo_async_inst (
   .rst            (~adc_rst_n),
   .wr_clk         (adc_clk),
   .wr_en          (adc_buf_wr),
   .din            (adc_buf_data),
   .rd_clk         (M_AXIS_CLK),
   .rd_en          (adc_buf_rd),
   .dout           (M_AXIS_tdata),
   .empty          (empty),
   .full           (),
   .almost_empty   (),
   .almost_full    (),
   .wr_data_count  (),
   .rd_data_count  (),    
   .prog_empty     (),
   .prog_full      (),    
   .data_valid     (),
   .dbiterr        (),
   .sbiterr        (),
   .overflow       (),
   .underflow      (),
   .wr_ack         (),   
   .wr_rst_busy    (),   
   .rd_rst_busy    (),
   .injectdbiterr  (1'b0),
   .injectsbiterr  (1'b0),   
   .sleep          (1'b0)   
   );

4.AXI4-Stream Register Slice

查阅官方文档pg085-axi4stream-infrastructure.pdf ,该IP核在Master和Slave接口之间插入了pipeline register,利用两个两个深度寄存器创建时序隔离和流水线主从,大概意思是利用寄存器使时序关系更好,其配置如下图所示,使能TKEEP与TLAST信号,其余保持默认。

基于 AN108 模块的ADC 采集以太网传输

5.AXI Direct Memory Access

本实验AXI DMA IP核采用了效率更高的S/G模式,即Scatter/Gather(分散/聚集)。SG DMA模式允许在单个 DMA 事务中将数据传输到多个存储区域或从多个存储区域传输数据,相当于将多个 Simple DMA 请求链接在一起,因此其效率更高。

配置AXI DMA IP核,使能Enable Scatter Gather Engine时,会出现 M_AXI_SG 接口,用于读写链表。

使能Enable Write Channel

Memory Map Data Width:AXI S2MM存储映射读取总线的数据位宽,设置为64

Stream Data Width:AXI S2MM AXI-Stream数据总线的位宽,设置为8(该值必须小于 Memory Map Data Width)

基于 AN108 模块的ADC 采集以太网传输

6.ZYNQ7 Processing System

关于ZYNQ7 处理系统的配置主要有以下两点需要注意,其他方面及配置方法可以参考ALINX教程中的实验一体验 ARM,裸机输出“Hello World”,这里不再赘述。

打开Ethernet0并使能MDIO(MDIO连接到MIO52、MIO53)

基于 AN108 模块的ADC 采集以太网传输

其次,在Clock Configuration 页面配置PS提供给PL端的时钟PL Fabric Clocks,其余时钟如输入时钟频率(需与核心板上的 PS 端输入时钟频率相同)、CPU 频率、PS 端外设的时钟均保持默认。

基于 AN108 模块的ADC 采集以太网传输

 最终连接图如下图所示:基于 AN108 模块的ADC 采集以太网传输

 generate output products后create HDL warpper生成顶层文件,随后绑定引脚,包括AD9708及AD9280各自的数据及时钟。

set_property PACKAGE_PIN G18 [get_ports adc_clk]
set_property PACKAGE_PIN L17 [get_ports {adc_data[0]}]
set_property PACKAGE_PIN L16 [get_ports {adc_data[1]}]
set_property PACKAGE_PIN M18 [get_ports {adc_data[2]}]
set_property PACKAGE_PIN M17 [get_ports {adc_data[3]}]
set_property PACKAGE_PIN D20 [get_ports {adc_data[4]}]
set_property PACKAGE_PIN D19 [get_ports {adc_data[5]}]
set_property PACKAGE_PIN E19  [get_ports {adc_data[6]}]
set_property PACKAGE_PIN E18  [get_ports {adc_data[7]}]


set_property IOSTANDARD LVCMOS33 [get_ports adc_clk]
set_property IOSTANDARD LVCMOS33 [get_ports {adc_data[*]}]

set_property PACKAGE_PIN F20 [get_ports {dac_clk}]
set_property PACKAGE_PIN F19  [get_ports {dac_data[7]}]
set_property PACKAGE_PIN G20 [get_ports {dac_data[6]}]
set_property PACKAGE_PIN G19 [get_ports {dac_data[5]}]
set_property PACKAGE_PIN H18 [get_ports {dac_data[4]}]
set_property PACKAGE_PIN J18  [get_ports {dac_data[3]}]
set_property PACKAGE_PIN L20 [get_ports {dac_data[2]}]
set_property PACKAGE_PIN L19 [get_ports {dac_data[1]}]
set_property PACKAGE_PIN M20  [get_ports {dac_data[0]}]

set_property IOSTANDARD LVCMOS33 [get_ports dac_clk]
set_property IOSTANDARD LVCMOS33 [get_ports {dac_data[*]}]

 对该设计进行综合、实现并生成 Bitstream 文件,随后在菜单栏选择 File > Export > Export hardware导出硬件信息

基于 AN108 模块的ADC 采集以太网传输

硬件导出完成后,在菜单栏中选择 File > Launch Vitis,选择导出的硬件信息所在文件夹,启动 Vitis 开发环境。

基于 AN108 模块的ADC 采集以太网传输

3.2 Vitis 程序开发

基于 AN108 模块的ADC 采集以太网传输

src文件夹下包含了平台相关文件platform.h、platform.c、platform_config.h、platform_zynq.c

主函数main.c

adc采集相关宏定义文件adc_dma.h

dma配置相关文件dma_bd.c、dma_bd.h

ZYNQ中断服务相关文件zynq_interrupt.c、zynq_interrupt.h

lwip 控制部分相关文件lwip_app.c

下面将对涉及的关键代码部分进行分析

1.main.c

在 main.c 文件的 main 函数中,调用函数init_platform(),中断初始化,进行 DMA 的初始化,中断连接,建立 BD链表,调用函数lwip_loop(),调用函数cleanup_platform()

int main()
{
    init_platform();

    InterruptInit(INT_DEVICE_ID,&XScuGicInstance);
    /* Initialize DMA */
    XAxiDma_Initial(DMA_DEV_ID, S2MM_INTR_ID, &AxiDma, &XScuGicInstance) ;
    /* Interrupt register */
    InterruptConnect(&XScuGicInstance,S2MM_INTR_ID,Dma_Interrupt_Handler, &AxiDma,0,3);
    /* Create BD chain */
    CreateBdChain(BdChainBuffer, BD_COUNT, ADC_SAMPLE_NUM, (unsigned char *)DmaRxBuffer, RXPATH) ;

    lwip_loop();
    cleanup_platform();
    return 0;
}

其中函数init_platform与cleanup_platform均定义于platform_zynq.c文件,前者用于使能caches和初始化uart,后者的作用是取消使能caches。

cleanup_platform()代码如下,由于涉及了DMADcache一致性的问题,该程序中采用的方法是直接将cache关闭,使CPU或PL直接更新ddr中的数据。

void cleanup_platform()
{
	Xil_ICacheDisable();
	Xil_DCacheDisable();
	return;
}

关于DMADcache的一致性可参考以下文章。

Xilinx的Zynq系列,ARM和PL通过DMA通信时如何保证DDR数据的正确性。_拾贝壳的大男孩的博客-CSDN博客

其余函数的分析会在其定义的文件中再做分析。

此外,在main.c文件中定义了4个函数并将s2mm_flag赋值为-1

int s2mm_flag = -1 ;


int XAxiDma_Initial(u16 DeviceId, u16 IntrID, XAxiDma *XAxiDma, XScuGic *InstancePtr) ; //DMA初始化函数
void Dma_Interrupt_Handler(void *CallBackRef);//DMA中断服务函数
void XAxiDma_Adc(u32 *BdChainBuffer, u32 adc_addr, u32 adc_len, u16 BdCount, XAxiDma *AxiDma) ;//DMA传输数据
void ad9280_sample(u32 adc_addr, u32 adc_len) ;//adc采集

①DMA初始化XAxiDma_Initial()

该函数主要包含的操作有:

CfgPtr = XAxiDma_LookupConfig(DeviceId);//进行DMA配置参数传递.

通过调用DMA查找配置函数,传入设备ID,获取设备参数并将返回这赋值给CfgPtr 。

Status = XAxiDma_CfgInitialize(XAxiDma, CfgPtr);//初始化DMA引擎

PS对DMA进行真正的配置初始化过程,通过axidma存储在PS端的AXI_DMA配置表,根据对PL参数的读取,PS运行对PL侧的DMA配置,这个配置过程是通过GP0接口对AXI_Lite4总线的控制完成的。

XAxiDma_IntrEnable(XAxiDma, XAXIDMA_IRQ_IOC_MASK,XAXIDMA_DEVICE_TO_DMA);	//使能DMA中断

由于实验需要,使能SS2M中断,关闭使能MM2S中断。

②DMA中断服务函数Dma_Interrupt_Handler()

该函数主要包含的操作有:

s2mm_sr = XAxiDma_IntrGetIrq(XAxiDmaPtr, XAXIDMA_DEVICE_TO_DMA) ;//读取待处理的中断
XAxiDma_IntrAckIrq(XAxiDmaPtr, XAXIDMA_IRQ_IOC_MASK,AXIDMA_DEVICE_TO_DMA) ;//确认待处理的中断

以及令s2mm_flag = 1;

③DMA传输数据函数XAxiDma_Adc()

/* Clear BD Status */
Bd_StatusClr(BdChainBuffer, BdCount) ;//调用DMA状态清除函数
/* Start sample */
ad9280_sample(adc_addr, adc_len)  ;//调用ADC采集数据函数
/* start DMA translation from ADC channel 0 to DDR3 */
Bd_Start(BdChainBuffer, BdCount, AxiDma, RXPATH) ;//调用配置DMA寄存器启动函数

该函数主要包含以上三个操作,此外,该函数将在函数lwip_loop()中被调用

④ADC采集数据函数ad9280_sample()

该函数代码如下:

void ad9280_sample(u32 adc_addr, u32 adc_len)
{
	/* provide length to AD9280 module */
	AD9280_SAMPLE_mWriteReg(adc_addr, AD9280_LENGTH, adc_len)  ;
	/* start sample AD9280 */
	AD9280_SAMPLE_mWriteReg(adc_addr, AD9280_START, 1) ;
}

按住Ctrl键单击函数AD9280_SAMPLE_mWriteReg(),即可在文件ad9280_sample.h中找到该函数的解释如下:

#define AD9280_SAMPLE_mWriteReg(BaseAddress, RegOffset, Data) \
  	Xil_Out32((BaseAddress) + (RegOffset), (u32)(Data))

Vitis中,对于寄存器的读和写可通过函数Xil_Out32()和Xil_In32()操作。此外,IP 核封装完成后,Vivado 软件会在 IP 核所在路径(...\ad9280_sample\drivers\ad9280_sample_v1_0\src)目录下自动生成.c 和.h 文件,方便在Vitis软件中对 IP 核进行操作,如下图所示:

基于 AN108 模块的ADC 采集以太网传输

2.dma_bd.c

该文件主要定义了以下几个函数

①创建链表函数CreateBdChain()

int CreateBdChain(u32 *BdDesptr, u16 BdCount, u32 TotalByteLen, u8 *DmaBufferPtr, u32 Direction)//创建描述符(bd)

CreateBdChain 为创建链表函数,根据DMA传输字节总长度TotalByteLen,将连续DMA缓冲地址空间分为BdCount份,每一份空间即一个 Descriptor。由于本实验是 RXPATH,不需要配置TXSOF 和 TXEOF,因此只配置NEXDESC,BUFFER_ADDRESS,CONTROL 三部分。

②配置寄存器启动SG DMA Bd_Start()

int Bd_Start(u32 *BdDesptr, u16 BdCount, XAxiDma *XAxiDmaPtr, u32 Direction)//开始BD提取

将当前Descriptor地址写入CURDESC寄存器,将Frame的开始、结束、每个 Pachage的长度信息写入DMACR寄存器(DMA控制寄存器),将尾部Descriptor地址写入TAILDESC寄存器,写入TAILDESC后,SG将开始提取描述符

③状态清除Bd_StatusClr()


void Bd_StatusClr(u32 *BdDesptr, u16 BdCount) //状态清除函数

在BD_ Start之前清除BD status,否则SG将不会启动

3.ZYNQ中断程序zynq_interrupt.c

中断程序里定义了 3 个函数,分别为中断系统建立函数,中断初始化函数,以及中断响应函数连接。

①中断系统建立函数

int InterruptSystemSetup(XScuGic *XScuGicInstancePtr)//寄存器GIC中断处理器

该函数调用了Xilinx提供的通用异常处理函数Xil_ExceptionRegisterHandler(),以及使能异常处理函数Xil_ExceptionEnable()来建立中断系统

中断初始化函数

int InterruptInit(u16 DeviceId,XScuGic *XScuGicInstancePtr)

该函数分别调用了

查询中断配置函数XScuGic_LookupConfig()

初始化SCU GIC配置XScuGic_CfgInitialize()

寄存器GIC中断处理器函数InterruptSystemSetup() 

③中断响应函数连接InterruptConnect()

int InterruptConnect(XScuGic *XScuGicInstancePtr,u32 Int_Id,void * Handler,void *CallBackRef,u8 Priority, u8 Trigger)

该函数分别调用了

断服务函数的注册XScuGic_Connect(),在中断源的中断Id和识别中断时需运行的相关处理程序之间建立连接,

设置中断优先级及中断触发方式XScuGic_SetPriorityTriggerType(),

SCU GIC使能函数XScuGic_Enable()

4.lwip 控制lwip_app.c

关于初始化以及dhcp的部分不再赘述。

①udp初始化start_udp()

该函数通过调用三个函数完成了udp服务的创建。

/*创建新pcb,绑定pcb和端口,设置回调功能*/
int start_udp(unsigned int port) {
	err_t err;
	udp8080_pcb = udp_new();//创建可用于UDP通信的新UDP pcb
	if (!udp8080_pcb) {
		xil_printf("Error creating PCB. Out of Memory\n\r");
		return -1;
	}
	/* bind to specified @port */
	err = udp_bind(udp8080_pcb, IP_ADDR_ANY, port); //  将UDP pcb与本地地址ipaddr和端口绑定。
	if (err != ERR_OK) {
		xil_printf("Unable to bind to port %d: err = %d\n\r", port, err);
		return -2;
	}
	udp_recv(udp8080_pcb, udp_recive, 0);为UDP PCB设置接收回调。当接收到pcb的数据报时,将调用此回调。入口参数分别为:设置recv回调的pcb、回调函数的函数指针、传递给回调函数的附加参数
	IP4_ADDR(&target_addr, 192,168,1,35);

	return 0;
}

在此函数中,首先利用 udp_new 创建一个 pcb 结构体,返回类型为 udp_pcb 的指针,并赋值给 udp8080_pcb; 利用 udp_bind函数绑定端口号和本地IP地址(入口参数分别为pcb 结构体变量,IP 地址,端口号)。利用 udp_recv 函数绑定回调函数,本实验中绑定的回调函数为udp_receive,回调函数实现服务端的功能,其代码如下

②回调函数udp_receive()

void udp_recive(void *arg, struct udp_pcb *pcb, struct pbuf *p_rx,
		const ip_addr_t *addr, u16_t port) {
	char *pData;
	char buff[28];
	if (p_rx != NULL)
	{
		pData = (char *) p_rx->payload;

		if (p_rx->len >= 5) {
			if (((pData[0] & 0x01) == 0) && (pData[1] == 0x00)
					&& ((pData[2] == 0x01) || (pData[2] < 0x00))
					&& (pData[3]) == 0x00)
			{
				if (pData[4] == 1)//上位机发送询问命令
				{
					buff[0] = pData[0] | 1;
					buff[1] = 0x00;
					buff[2] = 0x01;
					buff[3] = 0x00;
					buff[4] = 0x01;
					//MAC address
					memcpy(buff + 5, mac_export, 6);  //拷贝函数
					memcpy(buff + 5 + 6, ip_export, 4);
					buff[15] = 1;
					buff[16] = ADC_BITS;
					buff[17] = 2;
					buff[18] = 1;
					buff[19] = (unsigned char) (32000000 >> 24);
					buff[20] = (unsigned char) (32000000 >> 16);
					buff[21] = (unsigned char) (32000000 >> 8);
					buff[22] = (unsigned char) (32000000 >> 0);
					buff[23] = (unsigned char) (MAX_SEND_LEN >> 24);
					buff[24] = (unsigned char) (MAX_SEND_LEN >> 16);
					buff[25] = (unsigned char) (MAX_SEND_LEN >> 8);
					buff[26] = (unsigned char) (MAX_SEND_LEN >> 0);
					transfer_data(buff, 27, addr);
				}
				else if (pData[4] == 2)上位机发送控制命令请求数据
				{
					int sample_len = (pData[15] << 24) | (pData[16] << 16)
											| (pData[17] << 8) | (pData[18] << 0);//记录采样次数

					TargetHeader[0] = pData[0] | 1;
					target_addr.addr = addr->addr;
					FrameLengthCurr = (FrameLengthCurr == 0)? sample_len*2 :FrameLengthCurr;
				}
			}
		}
		pbuf_free(p_rx);
	}
}

回调函数udp_receive()的功能为接收上位机 的udp 命令,并判断是否是自定义的协议。如果是询问命令,启动应答。如果是控制命令, 根据命令利用FrameLengthCurr请求发送ADC数据。

上位机发送询问命令,将命令均写入buff后,通过transfer_data()函数发送至上位机

int transfer_data(const char *pData, int len, const ip_addr_t *addr)// 回答上位机命令

该函数主要利用udp_sendto()来实现发送数据

Udp通信中的发送函数

udp_sendto(struct udp_pcb *pcb, struct pbuf *p,const ip_addr_t *dst_ip, u16_t dst_port)

入口参数

pcb

p

dst_ip

dst_port

解析

用于UDP pcb发送的数据

要发送的pbuf链

目标ip地址

目标端口

/* start the application (web server, rxtest, txtest, etc..) */
	start_udp(8080);

	/* Start ADC channel 0 */
	XAxiDma_Adc(BdChainBuffer, AD9280_BASE, ADC_SAMPLE_NUM, BD_COUNT, &AxiDma) ;

	/* receive and process packets */
	while (1) {
		xemacif_input(echo_netif);
		/* Wait for times */
		usleep(1000) ;
		/* Check current frame length */
		if (FrameLengthCurr > 0)//判断是否接收到请求ADC数据的控制命令
		{
			/* Check if DMA completed */
			if (s2mm_flag >= 0)//判断DMA中断是否完成
			{
				int udp_len;
				Xil_DCacheInvalidateRange((u32) DmaRxBuffer, FrameLengthCurr);//刷新cache
				for(int i = 0 ; i < ADC_SAMPLE_NUM ; i++)
				{
					DmaBufferTmp[i] = (short)(DmaRxBuffer[i] - 128) ;	//无符号数转换为有符号数,并扩展为short类型
				}

				/* Separate data into package */
				for (int i = 0; i < FrameLengthCurr; i += 1024)
				{
					if ((i + 1024) > FrameLengthCurr)
						udp_len = FrameLengthCurr - i;
					else
						udp_len = 1024;

					send_adc_data((const char *) DmaBufferTmp + i, udp_len);//将ADC数据分包发送
				}
				/* Start ADC channel 0 */
				XAxiDma_Adc(BdChainBuffer, AD9280_BASE, ADC_SAMPLE_NUM, BD_COUNT, &AxiDma) ;
				/* Clear DMA flag and frame length */
				s2mm_flag = -1;
				FrameLengthCurr = 0;
			}
		}
	}

	/* never reached */
	cleanup_platform();

该部分为定义在lwip_app.c中的lwip_loop()函数主要功能实现部分,通过一个while循环来持续判断是否接收到上位机发送的请求数据的控制命令,如果FrameLengthCurr > 0,执行if语句内的代码段并通过send_adc_data()函数将ADC数据分包发送

int send_adc_data(const char *frame, int data_len)

send_adc_data ()函数用于发送 ADC 数据到上位机

至此,代码的分析部分到此结束,需要注意的是上位机设置的缓存大小为1MB,由于上位机固定为数据位宽为两个字节,且为无符号位,因此在 adc_dma.h 中将 AD9280 的采集数设置为 1024*512ADC_BYTE 设为2并将数据转换为两个字节,有符号数。

#define ADC_BYTE           2              /* ADC data byte number */

#define ADC_SAMPLE_NUM  (1024*512)

此外,需要添加lwip211,dhcp_options 打开 dhcp 功能,pbuf_options 选项将 pbuf_pool_size 设置大一些,增大缓存空间,提高效率 

基于 AN108 模块的ADC 采集以太网传输

四、实验结果及分析验证

4.1板上验证

1.连接开发板如图所示

基于 AN108 模块的ADC 采集以太网传输

2.本实验将开发板直连到PC,没有DHCP服务器,默认开发板IP地址为192.168.1.10.通过PC的网络和Internet设置将PC的IP地址设为同一网段

 ​​​​​基于 AN108 模块的ADC 采集以太网传输

 3.下载程序至开发板,在putty中查看打印信息如下

基于 AN108 模块的ADC 采集以太网传输

4.通过网络调试助手NetAssist进行收发信息

参考文章开头基于udp的通信协议,发送询问命令28 00 01 00 01后,上位机回传应答命令共27字节信息,包含板卡MAC地址、IP地址,ADC有效数据长度等,可参考通信协议进行一一对应 

基于 AN108 模块的ADC 采集以太网传输

随后,发送控制命令请求数据 28 00 01 00 02 00 0A 35 00 01 02 00 00 00 00 00 08 00 00,板卡回传应答命令,包含28|0x01,00 01 00 02以及1024字节的ADC数据基于 AN108 模块的ADC 采集以太网传输

5.最后,通过 ALINX提供的示波器.exe工具可粗略观察波形如下图所示

基于 AN108 模块的ADC 采集以太网传输

五、参考资料

[1]course_s2_ALINX_ZYNQ(AX7010_AX7020)开发平台Vitis应用教程V1.04

[2]领航者ZYNQ之嵌入式SDK开发指南_V2.0

[3]ALINX Zynq MPSoC XILINX FPGA视频教程 SDK 裸机开发—ADC以太网传输协议_

[4]对ZYNQ设备GPIO中断函数的详解 (一)_大千SS的博客-CSDN博客文章来源地址https://www.toymoban.com/news/detail-401498.html

到了这里,关于基于 AN108 模块的ADC 采集以太网传输的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于FPGA 以太网gmii_to_rgmii模块编写 附源码

    笔者使用的开发板是米联客zynq UitraScale+ xczu4ev-sfvc784-2-i开发板进行测试 由于米联客协议族源码不开源,自己写了一个简易的以太网接口转换模块只支持1000M速率。 (1)接收时序(PHYFPGA) RXC 的上下边沿与 RXD 和 RX_CTL 信号对齐,相位相同。(非延时模式) RXC 的上下边沿与

    2024年02月06日
    浏览(41)
  • 车载以太网 - 传输层 - TCP/IP

    一、传输层基础介绍 传输层主要包括两种协议 传输层端口号 二、UDP通信  UDP协议介绍 UDP 通信特点: UDP Segment结构 UDP通信过程 三、TCP通信 TCP通信特点: TCP Segment结构 传输层的寻址方式: 端口号         包括传输层的寻址方式,TCP/UDP 传输层协议工作流程,及TCP/UDP Packe

    2024年02月09日
    浏览(67)
  • 车载以太网 - 传输层 - TCP通信过程

    TCP 通信阶段 1、连接建立Connection establishment 2、数据传输 Data transfer 3、连接释放 Connection release TCP通信的三个阶段: TCP连接(三次握手) 1、Client(ECUA) - Server(ECU B)第一次握手 2、Server - Client 第二次握手 3、Client - Server 第三次握手 数据传输 1、Client - ServerTCP 数据请求 2、TC

    2024年02月11日
    浏览(48)
  • FPGA 以太网传输ov5640视频

    使用 DFZU4EV MPSoC 开发板及双目 OV5640 摄像头其中一个摄像头实现图像采集,并通过开发板上的以太网接口发送给上位机实时显示。 时钟模块用于为 I2C 驱动模块、以太网顶层模块和开始传输控制模块提供驱动时钟;I2C 驱动模块和 I2C 配置模块用于初始化 OV5640 图像传感器;摄像

    2024年04月10日
    浏览(54)
  • 【车载以太网测试从入门到精通】——传输层测试

    【车载以太网测试从入门到精通】系列文章目录汇总

    2023年04月27日
    浏览(59)
  • FPGA实现千兆/百兆自适应以太网UDP传输

    笔者最近在项目中需要使用到ZYNQ中PL端做以太网UDP传输并且需要支持100M/1000M自适应切换。使用的PHY型号为RTL8211。以下分享的主要为利用已有的1000M协议栈修改为100M并且实现二者自适应切换,IP核主要实现以下功能 1、实现100M/1000M自适应 2、回环测试 PS:完整的IP核文件下载地

    2024年01月21日
    浏览(56)
  • 基于FPGA的数据采集、编码、通讯和存储系统设计(即FPGA+RTL8211千兆以太网+SD卡存储+RTC+Uart+AD7606数模转换+电流放大采集等硬件设计与程序验证)

    介绍一个小项目,加强对FPGA相关接口的整体把握。 硬件及软件代码梳理: 硬件系统的主要功能框图,其中FPGA作为处理单元,实现了包括电流和电压的采集、千兆以太网通讯、SD卡本地数据存储和串口通讯等。已经过板级测试,测试包含:千兆网通讯收发测试、AD采集的数据

    2024年04月13日
    浏览(56)
  • 千兆以太网传输层 UDP 协议原理与 FPGA 实现(UDP接收)

    相关文章: (1)千兆以太网网络层 ARP 协议的原理与 FPGA 实现 (2)千兆以太网硬件设计及链路层 MAC 协议格式 (3)CRC校验原理及实现 (4)RGMII 与 GMII 转换电路设计 (5)千兆以太网网络层 IP 协议介绍与 IP 校 验和算法实现 (6)千兆以太网传输层 UDP 协议原理与 FPGA 实现(

    2024年02月04日
    浏览(128)
  • ZYNQ AXI_DMA_UDP以太网传输(二)问题记录

    上一篇文章只是简单的记录一下调试成功的代码 但调试成功这个过程很痛苦,踩了很多坑,特此记录,留眼以后查看 参考博客 xilinx dma调试笔记 ZYNQ AXI DMA调试细节 在调试过程中出现这类问题基本上都是这一句代码出了问题: 再往里面跳可以看见这样一个函数,在正点原子提

    2024年03月14日
    浏览(66)
  • FPGA——三速自适应以太网设计(1)基本模块

    每层都嵌套在上层的数据字段 以太网帧长: 64B~1518B IP层 ARP层 UDP层 ICMP层 要双端口ram读写缓存帧的数据和FIFO配合存储帧长度和类型 类型是0800就是IP 0806就是ARP,进行分流操作 两个数据流都加个FIFO,以帧为单位,先输出A,A输出完成后再输出B。还要进行流控:要切换通道输出

    2024年03月14日
    浏览(75)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包