OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 438|回复: 0

[XILINX] 【阿东手把手教你学FPGA】ZYNQ AXI DMA裸机开发实战:打通ADC数据到以太网的高速通道

[复制链接]

14

主题

21

帖子

3

精华

超级版主

Rank: 8Rank: 8

积分
344
金钱
344
注册时间
2019-11-11
在线时间
57 小时
发表于 2025-11-27 15:19:39 | 显示全部楼层 |阅读模式
本帖最后由 dongguo100 于 2025-11-27 15:19 编辑

  在嵌入式系统设计中,高速数据采集与实时传输一直是工程师面临的挑战,特别是在电力监测、工业自动化等领域,需要同时处理多通道高精度ADC数据并将其可靠地传输到上位机。传统方案往往面临传输带宽不足、CPU负载过高等问题。
  本文将深入探讨基于ZYNQ芯片的AXI DMA技术,如何实现ADC数据到PS端以太网的高速传输,并分享实战中的关键要点。

一、AXI DMA:ZYNQ数据传输的加速引擎
  AXI DMA是Xilinx提供的重要IP核,专为高速数据搬运而生。与传统CPU参与每次数据传输的方式不同,DMA允许外设直接访问内存,大大减轻了CPU的负担。
  在ZYNQ芯片中,AXI DMA利用PS(处理系统)和PL(可编程逻辑)之间的高性能AXI总线,实现了数据在PL端和PS端DDR内存之间的高效传输。其最大优势在于“直接内存访问”机制,数据传输不需要CPU频繁介入,从而实现了更高的传输效率。AXI DMA本质是在AXI4内存映射接口和AXI4-Stream接口之间实现高速数据传输,基于AXI DMA传输数据的示意图如下所示:
AXI DMA传输的示意图.png
  AXI DMA支持可选的Scatter/Gather功能,通过硬件自动化实现非连续内存的高效数据传输,从而减少CPU的干预。
  分散(Scatter):将连续的数据流分割并写入多个非连续的内存区域。例如,网络数据包的不同部分(协议头、载荷)可能存储在不同物理地址的缓冲区中。
  聚集(Gather):从多个非连续的内存区域收集数据,合并为连续的数据流。例如,视频处理中需要将分散存储的YUV平面数据合并为完整帧。
  与传统DMA的对比:普通DMA仅支持连续地址空间的传输,而Scatter/Gather模式通过描述符链表(Descriptor List)管理多段传输,实现非连续内存的自动化操作。
  AXI DMA支持三种模式:
  1.Direct Register Mode:也称为Simple DMA模式,通过AXI4-Lite接口直接配置源地址、目的地址和传输长度寄存器,无需额外的描述符管理,适用于单次大批量连续数据传输,如ADC采样数据和存储等。
  2.Scatter/Gather(SG)Mode:分散/聚集模式,通过内存中的Buffer Descriptor(BD)定义多段非连续传输任务,支持自动链式处理,适用于多段非连续传输任务,如网络数据包处理(分离包头与负载)和视频帧处理(YUV平面数据分散存储)等。
  3.Cyclic DMA Mode:循环模式,基于SG模式的扩展,在遇到尾描述符(TAILDESC)时自动跳回首描述符,形成循环传输,无需CPU的干预即可实现无限循环传输,适用场景于实时信号采集,如雷达信号的持续缓存等。
  三种模式的对比表如下所示:
AXI DMA三种模式的对比表.png
  关于AXI DMA更详细的介绍,可以参考Xilinx官方文档:AXI DMA LogiCORE IP Product Guide(PG021)。
二、UDP协议介绍
  本次案例使用UDP协议将ADC数据传输给上位机,UDP协议是TCP/IP协议栈的传输层协议,是一个简单的面向数据报的协议。UDP不提供数据包的分组与组装功能,也无法对数据包进行排序;且报文发送后,无法确认其是否安全、是否完整到达(即网络性能较差时,易出现数据丢失问题)。UDP除了这些缺点外肯定有它自身的优势,由于UDP不属于连接型协议,因而消耗资源小,处理速度快,所以通常在音频、视频和普通数据传输时使用UDP较多。UDP数据报结构如下表所示:
UDP数据报结构.png
  UDP首部有8个字节,由4个字段构成,每个字段都是两个字节,这些字段的作用如下:
  ①源端口:源端口号,需要对方回信时选用,不需要时全部置0。
  ②目的端口:目的端口号,在终点交付报文的时候需要用到。
  ③长度:UDP的数据报的长度(包括首部和数据)其最小值为8(只有首部)。
  ④校验和:检测UDP数据报在传输中是否有错,有错则丢弃。
  UDP报文由UDP首部+数据区域组成,UDP协议是位于传输层,该层是应用层的下一层,当用户发送数据时候,需要选择使用哪种协议发送出去,如果使用UDP协议,则UDP协议就会简单的把数据封装起来,UDP报文结构如下图所示:
UDP报文封装.png
  Vitis软件中集成了LWIP库,所以我们可以很方便的在ZYNQ上实现UDP协议。
三、ZYNQ开发平台
  本次案例采用的ZYNQ开发平台是正点原子领航者开发板,主控芯片型号是XC7Z020CLG400-2I。该芯片集成双核ARM Cortex-A9处理器,主频可达766MHz。XC7Z020CLG400-2I包含85K LC,4.9Mb BRAM。开发板存储方面包含1GB PS DDR3、8GB eMMC、32MB QSPI Flash和8KB EEPROM。非常适合测量仪表、工业控制、智能车载、图像显示和医疗器械等领域。
  开发板由核心板+底板组成,外设资源丰富,板载1路PS端千兆以太网接口、1路PL端千兆以太网接口、1路HDMI输出接口、4路USB2.0 Host接口、一路USB Slave接口、1个RGB LCD接口、1个DVP摄像头接口、1路USB UART(PS和PL共用)、1路音频输入接口、1路音频输出接口、1路RS232、1路RS485和1路CAN接口等。
  开发板提供了丰富的开发文档和软件资源,涉及FPGA开发、Vitis裸机开发、Linux系统开发和PYNQ开发等教学领域。企业客户可以直接采购核心板进行自己的产品研发,正点原子提供了全面、完善的例程和文档,助力企业客户产品研发。为提高企业用户的开发效率、缩短开发周期,正点原子特地为核心板用户整理了一系列开发阶段会用到的资料,涉及原理图、底板设计资料、机械结构、元器件封装、连接器规格、出厂系统镜像源码、编译器、软件包等,方便企业用户开发。
  领航者ZYNQ开发板购买链接:(复制链接至浏览器打开)
  https://detail.tmall.com/item.htm?id=609032204975
  领航者ZYNQ开发板资料链接:(复制链接至浏览器打开)
  http://www.openedv.com/docs/boards/fpga/zdyz_linhanz(V2).html
领航者开发板.jpg
四、准备工作
  1)一款ZYNQ开发板,本文以正点原子领航者7020开发板为例进行介绍;
  2)高速ADDA模块,本文以正点原子高速AD/DA模块MO8008为例进行介绍;
  3)DDS信号发生器(可选);
  4)网线;
  注:本案例的完整工程会分享在文末。
五、Vivado BlockDesign的工程搭建
  本次案例实现的功能是将数据从PL端到PS端,再到PC的完整传输与显示的链路:通过PL端的AXI DMA IP核将ADC数据写入到PS端的DDR,再借助PS的以太网接口将数据实时上传至PC上位机显示波形。
  由前文对AXI DMA的介绍可知,AXI DMA IP核将AXI4-Stream数据流转成AXI4接口数据以实现数据搬运,所以我们需要将ADC的数据转换成AXI4-Stream数据流,这个转换的操作由adc_to_axis模块完成。此外,在转换的过程中需要缓存ADC数据,并对数据做跨时钟域的处理,所以本次案例还需要包含一个FIFO IP核。
  而PS端主要配置了PS端的网口,使用以太网控制器实现UDP发送ADC数据的功能,将数据发给上位机实时显示ADC波形,整个系统框图如下图所示:
系统框图.png
  需要注意的是,高速ADDA模块需要一个模拟输入源,这个可以由DDS信号发生器产生,也可以由高速ADDA模块的DA芯片产生,所以在上图的系统框图中,我们还添加了一个da_wave_send模块,用于产生DA芯片所需的数据。
  PS通过AXI GPIO IP核来配置adc_to_axis模块什么时候开始传输采集到的ADC数据,当开始采集后,首先将8位的ADC数据缓存至FIFO,然后从FIFO中读出32位的数据,即FIFO IP核的位宽设置是8位进32位出,这个设置和AXI DMA IP核传输的数据位宽一致,以提高AXI DMA IP核搬运数据的效率。
  由系统框图可知,本次案例只有adc_to_axis模块和da_wave_send模块是我们编写的Verilog代码,其余都是Xilinx官方的IP核,接下来简单介绍下这两个模块。
  adc_to_axis模块:这个模块实现了将输入的ADC数据转成AXI4-Stream接口数据,方便跟AXI DMA IP核进行连接,只有当FIFO IP核中的数据缓存至预设值之后,才开始向AXI DMA IP核传输数据(拉高tvalid的信号)。
  da_wave_send模块:这个模块主要是为了控制DA芯片输出正弦波的模拟信号,目的是为了在没有DDS信号发生器的情况下,也可以通过DA芯片产生模拟量,连接到高速ADDA模块的AD输入端,来完成本次案例。
  这里重点介绍下AXI DMA IP核的配置,配置如下图所示:
AXI DMA IP核配置.png
  需要注意的是,本次案例只需要将PL端的ADC数据通过AXI DMA IP核写入到PS的DDR中,而不需要从PS DDR中读出数据到PL,所以我们只需要使用AXI DMA的写功能。为了方便后续对例程进行扩展,比如网口下发数据通过DA芯片进行输出,所以这里同时勾选了读写两个通道。
  Vivado工程搭建完成后的模块连接图如下图所示:
模块连接图.png
六、软件设计
  软件程序主要完成对AXI DMA的配置,将ADC数据搬运至PS DDR中缓存;随后通过PS端的以太网控制器读取DDR中的ADC数据,并发送至上位机以实现波形显示。
  Vitis C代码结构如下图所示:
Vitis C代码结构.png
  接下来我们介绍下main.c代码中的main函数,main函数部分代码如下:
51  int main(void)
52  {
53      axi_dma_cfg();                           // 配置AXI DMA
54      Init_Intr_System(&Intc);             // 初始化中断控制器
55      Setup_Intr_Exception(&Intc);      // 启用来自硬件的中断
56      dma_setup_intr_system(&Intc);   // 建立DMA中断系统
57  
58      lwip_udp_init();                          // UDP通信配置
59  
60      //初始化PL端 AXI GPIO驱动
61      XGpio_Initialize(&axi_gpio_inst, AXI_GPIO_DEVICE_ID);
62       //设置 AXI GPIO 通道 1方向为输入
63      XGpio_SetDataDirection(&axi_gpio_inst, KEY_CHANNEL1, 0);
64      //设置AXI GPIO引脚为高电平,开始采集ADC数据
65      XGpio_DiscreteWrite(&axi_gpio_inst,KEY_CHANNEL1,1);
66  
67      //接收和处理数据包
68      while (1) {
69  
70          xemacif_input(netif);
71  
72          if(dma_start_flag == 0){
73              axi_dma_start(MAX_PKT_LEN);
74              dma_start_flag = 1;
75          }
76  
77          //DMA搬运1024个数据完成后,网口就可以从DDR中取数据进行发送了
78          if(rx_done){
79          Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN);
80           if(start_flag){
81               udp_tx_data(rx_buffer_ptr,MAX_PKT_LEN);
82           }
83              rx_done = 0;
84              dma_start_flag = 0;
85          }
86      }
87      return 0;
88  }
  第53行至56行调用的都是AXI DMA的配置与使用的相关函数。
  第58行代码是调用UDP通信配置函数lwip_udp_init(),该函数用于初始化lwIP协议栈,配置网络接口,并启动一个UDP服务器应用程序。
  第61行至63行代码是对AXI GPIO进行初始化,并设置方向为输出;第65行代码是将AXI GPIO输出的端口设置为1,表示PL端开始接收ADC采集到的数据。
  第70行的xemacif_input()函数是网络接口输入函数,用于处理不同类型的以太网MAC设备的输入数据包。这个函数的设计体现了对于不同硬件设备的灵活处理能力,它能够根据设备类型动态调用不同的接口输入函数,从而实现对各种设备的兼容性。
  第72行至74行用于判断是否开启AXI DMA的传输,如果dma_start_flag的值等于0,则开启AXI DMA传输,并将dma_start_flag的值赋值为1。
第78行至84行代码是接收到ADC的数据后,通过调用udp_tx_data()函数将ADC的数据发送给上位机。

1   /***************************** Include Files*********************************/
2   #include "axi_dma.h"
3   
4   /************************** ConstantDefinitions *****************************/
5   #defineDMA_DEV_ID            XPAR_AXIDMA_0_DEVICE_ID
6   #defineRX_INTR_ID            XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
7   #defineINTC_DEVICE_ID        XPAR_SCUGIC_SINGLE_DEVICE_ID
8   #defineDDR_BASE_ADDR         XPAR_PS7_DDR_0_S_AXI_BASEADDR   //0x00100000
9   #defineMEM_BASE_ADDR          (DDR_BASE_ADDR+ 0x1000000)     //0x01100000
10  #defineRX_BUFFER_BASE         (MEM_BASE_ADDR+ 0x00300000)    //0x01400000
11  #defineRESET_TIMEOUT_COUNTER  10000    //复位时间
12  
13  /************************** Variable Definitions*****************************/
14  
15  static XAxiDma axidma;     //XAxiDma实例
16  volatile int rx_done=0;    //接收完成标志
17  volatile int error;        //传输出错标志
18  u8 *rx_buffer_ptr;
19  
20  /************************** Function Definitions*****************************/
21  
22  intaxi_dma_cfg(void)
23  {
24  
25      intstatus;
26  
27      XAxiDma_Config *config;
28  
29      rx_buffer_ptr =(u8 *) RX_BUFFER_BASE;
30  
31      xil_printf("\r\n---Entering axi_dma_cfg --- \r\n");
32  
33      config =XAxiDma_LookupConfig(DMA_DEV_ID);
34      if(!config) {
35          xil_printf("Noconfig found for %d\r\n", DMA_DEV_ID);
36          return XST_FAILURE;
37      }
38  
39      //初始化DMA引擎
40      status =XAxiDma_CfgInitialize(&axidma, config);
41      if(status != XST_SUCCESS) {
42          xil_printf("Initializationfailed %d\r\n", status);
43          return XST_FAILURE;
44      }
45  
46      if(XAxiDma_HasSg(&axidma)) {
47          xil_printf("Deviceconfigured as SG mode \r\n");
48          return XST_FAILURE;
49      }
50  
51      xil_printf("AXIDMA CFG Success\r\n");
52      return XST_SUCCESS;
53  
54  }
  第22行至第54行代码是对AXI DMA引擎进行初始化,如果AXI DMA使能了SG模式,会返回错误。

56  //启用AXI DMA
57  intaxi_dma_start(u32 pkt_len)
58  {
59      intstatus;
60      //初始化标志信号
61      error   = 0;
62  
63      status =XAxiDma_SimpleTransfer(&axidma, (UINTPTR)rx_buffer_ptr,
64              pkt_len,XAXIDMA_DEVICE_TO_DMA);
65      if(status != XST_SUCCESS) {
66          xil_printf("AXIDMA Start FAILURE\r\n");
67          return XST_FAILURE;
68      }
69      return XST_SUCCESS;
70  }
  第56行至第70行代码是对AXI DMA引擎进行初始化,如果AXI DMA使能了SG模式,会返回错误。

72  //DMA RX中断处理函数
73  voidrx_intr_handler(void*callback)
74  {
75      u32 irq_status;
76      inttimeout;
77      XAxiDma *axidma_inst= (XAxiDma *) callback;
78  
79      irq_status =XAxiDma_IntrGetIrq(axidma_inst,XAXIDMA_DEVICE_TO_DMA);
80     XAxiDma_IntrAckIrq(axidma_inst, irq_status,XAXIDMA_DEVICE_TO_DMA);
81  
82      //Rx出错               0x00004000 /**< Errorinterrupt */
83      if((irq_status & XAXIDMA_IRQ_ERROR_MASK)){
84          error =1;
85          xil_printf("XAxiDmaerror");
86         XAxiDma_Reset(axidma_inst);
87          timeout =RESET_TIMEOUT_COUNTER;
88          while (timeout) {
89              if(XAxiDma_ResetIsDone(axidma_inst))
90                  break;
91              timeout-= 1;
92          }
93          return;
94      }
95      //Rx完成    0x00001000 /**< Completion intr */
96      if((irq_status & XAXIDMA_IRQ_IOC_MASK))
97          rx_done =1;
98  
99      irq_status =XAxiDma_IntrGetIrq(axidma_inst,XAXIDMA_DEVICE_TO_DMA);
100 }
  第72行至第100行代码是AXI DMA的中断处理函数,当接收到AXI DMA的中断后,拉高rx_done信号,表示AXI DMA传输完成,最后清除中断状态标志位。

102 //建立DMA中断系统
103 //  @param  int_ins_ptr是指向XScuGic实例的指针
104 //  @param  AxiDmaPtr是指向DMA引擎实例的指针
105 //  @param   rx_intr_id是RX通道中断ID
106 //  @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
107 int dma_setup_intr_system(XScuGic* int_ins_ptr)
108 {
109     intstatus;
110     //设置优先级和触发类型
111    XScuGic_SetPriorityTriggerType(int_ins_ptr,RX_INTR_ID, 0xA0,0x3);
112
113    //为中断设置中断处理函数
114     status =XScuGic_Connect(int_ins_ptr, RX_INTR_ID,
115             (Xil_InterruptHandler)rx_intr_handler, &axidma);
116     if(status != XST_SUCCESS) {
117         return status;
118     }
119     XScuGic_Enable(int_ins_ptr,RX_INTR_ID);
120
121    //使能DMA中断
122    XAxiDma_IntrEnable(&axidma,XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
123
124     return XST_SUCCESS;
125 }
  第102行至第122行代码是配置AXI DMA的中断,并使能中断。
七、上板验证
  将高速AD/DA模块插入开发板的J3扩展口,连接时注意扩展口电源引脚方向和高速AD/DA模块的电源引脚方向要一致,如下图所示:
高速ADDA模块连接实物图.png
  接下来需要给高速ADDA模块的模拟输入(AD_IN)提供模拟输入源,大家可以使用DDS信号发生器产生模拟输入源,也可以直接将模块的模拟输出(DA_OUT)通过杜邦线或者SMA转SMA线接到模拟输入(AD_IN),这里我们直接使用杜邦线进行连接,如下图所示:
杜邦线连接模拟输出(DA_OUT)和模拟输入(AD_IN).png
  连接电源线、下载器和PS UART串口线,网线一端连接电脑的网口,另一端连接开发板的PS网口(GE_PS),并打开电源开关。
  下载本次示例程序,下载完成后,在下方的Vitis Serial Terminal中可以看到应用程序打印的信息,如下图所示:
打印信息.png
  串口终端首先会打印AXI DMA配置成功,然后打印开发板的IP地址为192.168.1.10和端口号为1234。接下来设置电脑的静态IP地址,这里设置为192.168.1.102,如下图所示:
电脑设置为静态IP地址.png
  打开ATK-XADC软件,打开后的软件界面如下图所示:
ATK-XADC软件打开界面.png
  按照上图所示设置完成后,点击“开始传输”按钮,此时上位机可以显示出正弦波(DA芯片固定输出正弦波的波形),如下图所示:
ATK-XADC软件显示正弦波波形.png
  领航者ZYNQ开发板购买链接:(复制链接至浏览器打开)
  https://detail.tmall.com/item.htm?id=609032204975
  领航者ZYNQ开发板资料链接:(复制链接至浏览器打开)
  http://www.openedv.com/docs/boards/fpga/zdyz_linhanz(V2).html
  本案例完整工程源码和上位机网盘链接:
  https://pan.baidu.com/s/14egt0D2SfFBSx0YswwPacg?pwd=zdyz 提取码:zdyz
领航者开发板.jpg
正点原子FPGA公众号:
二维码3.png




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-12-13 14:13

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表