一、实验要求
本文基于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,其余保持默认即可。
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位。
为此需要在Vitis中做如下对应设置,该部分定义于adc_dma.h文件
此外,在用户自定义代码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信号,其余保持默认。
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)
6.ZYNQ7 Processing System
关于ZYNQ7 处理系统的配置主要有以下两点需要注意,其他方面及配置方法可以参考ALINX教程中的实验一体验 ARM,裸机输出“Hello World”,这里不再赘述。
打开Ethernet0并使能MDIO(MDIO连接到MIO52、MIO53)
其次,在Clock Configuration 页面配置PS提供给PL端的时钟PL Fabric Clocks,其余时钟如输入时钟频率(需与核心板上的 PS 端输入时钟频率相同)、CPU 频率、PS 端外设的时钟均保持默认。
最终连接图如下图所示:
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导出硬件信息
硬件导出完成后,在菜单栏中选择 File > Launch Vitis,选择导出的硬件信息所在文件夹,启动 Vitis 开发环境。
3.2 Vitis 程序开发
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()代码如下,由于涉及了DMA与Dcache一致性的问题,该程序中采用的方法是直接将cache关闭,使CPU或PL直接更新ddr中的数据。
void cleanup_platform()
{
Xil_ICacheDisable();
Xil_DCacheDisable();
return;
}
关于DMA与Dcache的一致性可参考以下文章。
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 核进行操作,如下图所示:
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*512,ADC_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 设置大一些,增大缓存空间,提高效率
四、实验结果及分析验证
4.1板上验证
1.连接开发板如图所示
2.本实验将开发板直连到PC,没有DHCP服务器,默认开发板IP地址为192.168.1.10.通过PC的网络和Internet设置将PC的IP地址设为同一网段
3.下载程序至开发板,在putty中查看打印信息如下
4.通过网络调试助手NetAssist进行收发信息
参考文章开头基于udp的通信协议,发送询问命令28 00 01 00 01后,上位机回传应答命令共27字节信息,包含板卡MAC地址、IP地址,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数据
5.最后,通过 ALINX提供的示波器.exe工具可粗略观察波形如下图所示
五、参考资料
[1]course_s2_ALINX_ZYNQ(AX7010_AX7020)开发平台Vitis应用教程V1.04
[2]领航者ZYNQ之嵌入式SDK开发指南_V2.0
[3]ALINX Zynq MPSoC XILINX FPGA视频教程 SDK 裸机开发—ADC以太网传输协议_文章来源:https://www.toymoban.com/news/detail-401498.html
[4]对ZYNQ设备GPIO中断函数的详解 (一)_大千SS的博客-CSDN博客文章来源地址https://www.toymoban.com/news/detail-401498.html
到了这里,关于基于 AN108 模块的ADC 采集以太网传输的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!