OpenEdv-开源电子网

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

[XILINX] 【正点原子FPGA连载】第十五章 AXI DMA环路测试--摘自【正点原子】领航者ZYNQ之嵌入式开发指南_V1.2

[复制链接]

1118

主题

1129

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4671
金钱
4671
注册时间
2019-5-8
在线时间
1224 小时
发表于 2020-9-7 12:03:20 | 显示全部楼层 |阅读模式
本帖最后由 正点原子01 于 2020-9-7 12:03 编辑

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-301505-1-1.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:905624739 点击加入群聊
5)关注正点原子公众号,获取最新资料更新
1.jpg
1120.png
第十五章AXI DMA环路测试
DMA(DirectMemory Access,直接存储器访问)是计算机科学中的一种内存访问技术。它允许某些计算机内部的硬件子系统可以独立地直接读写系统内存,而不需中央处理器(CPU)介入处理。DMA是一种快速的数据传送方式,通常用来传送数据量较多的数据块,很多硬件系统会使用DMA,包括硬盘控制器、绘图显卡、网卡和声卡,在使用高速AD/DA时使用DMA也是不错的选择。
本章我们使用PL的AXI DMA IP核实现DMA环路功能,了解DMA的使用。本章包括以下几个部分:
1         
1.1        简介
1.2        实验任务
1.3        硬件设计
1.4        软件设计
1.5        下载验证

1.1 简介
DMA是所有现代计算机的重要特色,它允许不同速度的硬件设备进行沟通,而不需要依于中央处理器的大量中断负载。否则,中央处理器需要从来源把每一片段的数据复制到寄存器,然后把它们再次写回到新的地方。在这个时间里,中央处理器就无法执行其它的任务。
DMA是用硬件实现存储器与存储器之间或存储器与I/O设备之间直接进行高速数据传输。使用DMA时,CPU向DMA控制器发出一个存储传输请求,这样当DMA控制器在传输的时候,CPU执行其它操作,传输操作完成时DMA以中断的方式通知CPU。
为了发起传输事务,DMA控制器必须得到以下数据:
• 源地址   — 数据被读出的地址
• 目的地址 — 数据被写入的地址
• 传输长度 — 应被传输的字节数
image002.jpg

15.1.1 DMA存储传输过程
DMA存储传输的过程如下:
1. 为了配置用DMA传输数据到存储器,处理器发出一条DMA命令
2. DMA控制器把数据从外设传输到存储器或从存储器到存储器,而让CPU腾出手来做其它操作。
3. 数据传输完成后,向CPU发出一个中断来通知它DMA传输可以关闭了。
ZYNQ提供了两种DMA,一种是集成在PS中的硬核DMA,另一种是PL中使用的软核AXI DMA IP。
在ARM CPU设计的过程中,已经考虑到了大量数据搬移的情况,因此在CPU中自带了一个DMA控制器DAMC,这个DAMC驻留在PS内,而且必须通过驻留在内存中的DMA指令编程,这些程序往往由CPU准备,因此需要部分的CPU参与。DMAC支持高达8个通道,所以多个DMA结构的核可以挂在单个DMAC上。DAMC与PL的连接是通过AXI_GP接口,这个接口最高支持到32位宽度,这也限制了这种模式下的传输速率,理论最高速率为600MB/s。这种模式不占用PL资源,但需要对DMA指令编程,会增加软件的复杂性。
为了获取更高的传输速率,可以以空间换时间,在PL中添加AXI DMA IP核,并利用AXI_HP接口完成高速的数据传输。各种接口方式的比较如下表所示:
15.1.1 各种接口方式比较
903380FC-28E8-4a9f-99E4-207BAF7A0876.png

15.1.2  PLDMAAXI_HP接口拓扑图
image004.jpg

可以看到DMA的数据传输经S_AXI_HP接口(以下简称HP接口)。ZYNQ拥有4个HP接口,提供了ZYNQ内最大的总带宽。每一个HP接口都包含控制和数据FIFO。这些FIFO为大数据量突发传输提供缓冲,让HP接口成为理想的高速数据传输接口。对DMA的控制或配置通过M_AXI_GP接口,传输状态通过中断传达到PS的中断控制器。下面我们简单的介绍下PL的DMA,即AXI DMA IP核。
AXI Direct Memory Access(AXI DMA)IP内核在AXI4内存映射和AXI4-Stream IP接口之间提供高带宽直接储存访问。其可选的scattergather功能还可以从基于处理器的系统中的中央处理单元(CPU)卸载数据移动任务。初始化、状态和管理寄存器通过AXI4-Lite从接口访问。核心的功能组成如下图所示:
image006.jpg

15.1.3 AXI DMA框图
AXI DMA用到了三种总线,AXI4-Lite用于对寄存器进行配置,AXI4 Memory Map用于与内存交互,又分为AXI4 Memory Map Read和AXI4 Memory Map Write两个接口,一个是读一个是写。AXI4 Stream 接口用于对外设的读写,其中AXI4 Stream Master(MM2S,Memory Map to Stream)用于对外设写,AXI4-Stream Slave(S2MM,Stream to Memory Map)用于对外设读。总之,在以后的使用中需要知道AXI_MM2S和AXI_S2MM是存储器端映射的AXI4总线,提供对存储器(DDR3)的访问。AXIS_MM2S和AXIS_S2MM是AXI4-streaming总线,可以发送和接收连续的数据流,无需地址。
AXI DMA提供3种模式,分别是Direct Register模式、Scatter/Gather模式和Cyclic DMA模式,这里我们简单的介绍下常用的Direct Register模式和Scatter/Gather模式。
Direct Register DMA模式也就是Simple DMA。Direct Register模式提供了一种配置,用于在MM2S和S2MM通道上执行简单的DMA传输,这需要更少的FPGA资源。Simple DMA允许应用程序在DMA和Device之间定义单个事务。它有两个通道:一个从DMA到Device,另一个从Device到DMA。应用程序必须设置缓冲区地址和长度字段以启动相应通道中的传输。
Scatter/Gather DMA模式允许在单个DMA事务中将数据传输到多个存储区域或从多个存储区域传输数据。它相当于将多个SimpleDMA请求链接在一起。SGDMA允许应用程序在内存中定义事务列表,硬件将在应用程序没有进一步干预的情况下处理这些事务。在此期间,应用程序可以继续添加更多工作以保持硬件工作。用户可以通过轮询或中断来检查事务是否完成。SGDMA处理整个数据包(被定义为表示消息的一系列数据字节)并允许将数据包分解为一个或多个事务。例如,采用以太网IP数据包,该数据包由14字节的报头后跟1个或多个字节的有效负载组成。使用SGDMA,应用程序可以将BD(BufferDescriptor,用于描述事务的对象)指向报头,将另一个BD指向有效负载,然后将它们作为单个消息传输。这种策略可以使TCP / IP堆栈更有效,它允许将数据包标头和数据保存在不同的内存区域,而不是将数据包组装成连续的内存块。
在本设计中,不需要使用scattergather DMA模式,因为可以使用DMA的更简单的寄存器直接模式充分实现系统,从而避免实现scatter gather功能带来的面积成本。在系统需要对DMA进行相对复杂的软件控制时,可以使用scatter gather模式。
1.2 实验任务
本章的实验任务是在领航者ZYNQ开发板上使用PL的AXI DMA IP核从DDR3中读取数据,并将数据写回到DDR3中。
1.3 硬件设计
在实际应用中,DMA一般与产生数据或需求数据的IP核相连接,该IP核可以是带有Stream接口的高速的AD(模拟转数字)或DA(数字转模拟) IP核。不失一般性,在本次实验中,我们使用AXI4 Stream Data FIFO IP核来充当这类IP进行DMA环回实验。大致的系统框图如下(具体的可以看图 16.1.2):
image008.png

15.3.1 AXI DAM环路测试系统框图
PS开启HP0和GP0接口。AXI DMA和AXI4 Stream Data FIFO在PL中实现。处理器通过M_AXI_GP0接口与AXI DMA通信,以设置、启动和监控数据传输。数据传输通过S_AXI_HP0接口。AXI DMA通过S_AXI_HP0接口从DDR3中读取数据后发送给AXI4 Stream Data FIFO,这种情况下AXI4 Stream DataFIFO可以相当于带有Stream接口的高速DA。AXI DMA读取AXI4 Stream Data FIFO中的数据后通过S_AXI_HP0接口写入DDR3的情形,AXI4 Stream Data FIFO相当于带有Stream接口的高速AD。
step1:创建Vivado工程
打开Vivado,创建一个名为“axi_dma_loop”的空白工程,工程路径为F:\zynq\zynq7020文件夹。注意,工程名和路径只能由英文字母、数字和下划线组成不能包含中文、空格以及特殊字符!
step2:使用IP Integrator创建Processing System
2-1 在左侧导航栏(Flow Navigator)中,单击IP Integrator下的Create Block Design。然后在弹出的对话框中指定所创建的BlockDesign的名称,在Design name栏中输入“system”。如下图所示:
image010.jpg

15.3.2 创建 Block Design
2-2点击“OK”按钮。 添加ZYNQ7 Processing System模块。接下来按照《“Hello World”实验》中的步骤2-7、2-8分别配置PS的UART和DDR3控制器。需要特别注意的是,我们在《“Hello World”实验》的步骤2-10中,移除了PS中与PL端交互的接口信号,这些接口在我们本次实验中需要予以保留。
2-3 配置时钟。
点击左侧的ClockConfiguration页面,展开PL-Fabric Clocks,可以看到默认勾选FCLK_CLK0,且时钟频率为50MHz,这里我们将其修改为100MHz,如下图所示:
image012.jpg

15.3.3 配置FCLK_CLK0
2-4 开启HP接口。
点击左侧的PS-PLConfiguration页面,然后在右侧展开General下的HP Slave AXI Interface,可以看到有4个HP接口,这里我们只用其中的S AXI HP0 interface,DATA WIDTH保持默认即可,如下图所示:
image014.jpg

15.3.4 启用HP0接口
2-5 因为DMA在传输完成后通过发送中断通知CPU,所以我们需要开启PL到PS的中断。
点击左侧的Interrupts页面,勾选右侧的Fabric interrupts并展开,勾选PL-PS Interrupt Ports下的IRQ_F2P[15:0],如下图所示:
image016.jpg

15.3.5 开启PLPS的中断
2-6 配置ZYNQ7 Processing System完成,点击“OK”,配置完成后的ZYNQ7 Processing System IP模块如下图所示:
image017.png

15.3.6 配置完成后的ZYNQ7 Processing System IP
2-7 添加DMA IP。
添加DMA IP,如同添加ZYNQ7 Processing System IP,只不过搜索词由zynq变为dma,如下图所示:
image019.jpg

15.3.7 添加DMA模块
image021.jpg

15.3.8 配置DMA模块
我们双击axi_dma_0,打开配置界面,如图16.3.8所示,此处我们只需要取消勾选Enable Scatter Gather Engine即可。不过还是介绍下与配置相关的选项。
Enable Scatter Gather Engine
选中此选项可启用ScatterGather模式操作,并在AXI DMA中包含ScatterGather Engine。取消选中此选项可启用Direct Register模式操作,但不包括AXI DMA中的Scatter Gather Engine。禁用Scatter Gather Engine会使Scatter/Gather Engine的所有输出端口都绑定为零,并且所有输入端口都将保持打开状态。此处我们取消勾选Enable Scatter Gather Engine。
Enable Micro DMA
选中此选项会生成高度优化的DMA,资源数量较少。此设置可用于传输极少量数据的应用程序。此处我们不勾选。
Width of Buffer Length Register
此整数值指定用于控制字段缓冲区长度的有效位数和在Scatter/Gather描述符中传输的Status字段的字节数。字节数等于file:///C:/Users/WCY/AppData/Local/Temp/msohtmlclip1/01/clip_image023.png。因此,长度为26时,字节数为节。对于多通道模式,此值应设置为23。此处我们保持默认设置14。
Address Width (32 - 64)
指定地址空间的宽度,可以是32到64之间的任何值。此处保持默认值32。
EnableRead Channel
开启AXI DMA的读通道MM2S,相关选项如下:
Number of Channels:指定通道数。保持默认值1。
Memory Map DataWidth:AXI MM2S存储映射读取数据总线的数据位宽。有效值为32,64,128,256,512和1024。此处保持默认值32。
Stream DataWidth:AXI MM2S AXI4-Stream数据总线的数据位宽。该值必须小于或等于Memory Map Data Width。有效值为8、16、32、64、128、512和1024。此处保持默认值32。
Max Burst Size:突发分区粒度设置。此设置指定MM2S的AXI4-Memory Map侧的突发周期的最大大小。有效值为2,4,8,16,32,64,128和256。此处保持默认值16。
Allow UnalignedTransfers:启用或禁用MM2S数据重新排列引擎(DataRealignment Engine,DRE)。选中时,DRE被使能并允许在MM2S存储映射数据路径上数据重新对齐到8位的字节水平。对于MM2S通道,从内存中读取数据。如果DRE被使能,则数据读取可以从任何缓冲区地址字节偏移开始,并且读取数据被对齐,使得第一个字节读取是AXI4-Stream上的第一个有效字节输出。
注意:如果为相应通道禁用DRE,则不支持未对齐的缓冲区、源或目标地址。在禁用DRE的情况下使用未对齐的地址会产生未定义的结果。DRE支持仅适用于512位及以下的AXI4-Stream数据宽度设置。
Enable Write Channel
开启AXI DMA的写通道S2MM,相关选项可参考读通道。
2-8 添加axis_data_fifo IP
image026.png

15.3.9 添加axis_data_fifo IP
该IP保持默认设置即可。添加该IP核的重点不是了解该IP核如何使用,而是知道该IP核带有AXI Stream接口。在以后的实际使用中,需要封装自定义IP核时要注意这一点。
2-9 添加Concat IP。
Concate IP 实现了单个分散的信号,整合成总线信号。这里 2 个独立的中断信号,可以合并在一起接入到 ZYNQ IP 的中断信号上。
image027.png

15.3.10 添加Concat IP
2-10 模块连接。
在Diagram窗口中,点击“Run connection Automation”,进行自动连接,在弹出的界面中勾选“AllAutomation”,然后点击OK,如下图所示:
image029.jpg

15.3.11 Run connection Automation
点击“Run BlockAutomation”,在弹出的界面中勾选“All Automation”,然后点击OK,如下图所示:
image031.jpg

15.3.12 Run Block Automation
2-11 自动连接完成后,发现Concat IP未连接,我们手动进行连接,如下图所示:
image033.jpg

15.3.13 手动连接未连接的IP
另外添加的axis_data_fifo也未连接,我们同样手动连接。首先将DMA的M_AXIS_MM2S端口与axis_data_fifo的S_AXIS进行连接,如下图所示:
image035.jpg

15.3.14 连接axis_data_fifoS_AXIS
然后将axis_data_fifo上的M_AXIS端口连接到DMA的S_AXIS_S2MM端口,如下图所示:
image037.jpg
15.3.15 连接axis_data_fifo上的M_AXIS
现在我们连接axis_data_fifo的时钟和复位。单击axis_data_fifo的s_axis_aresetn端口并将其连接到DMA的axi_resetn端口,单击axis_data_fifo的s_axis_aclk端口并将其连接到DMA的m_axi_mm2s_aclk端口,如下图所示:
image039.jpg

15.3.16 axis_data_fifo的时钟和复位
2-12 为了方便截图显示,我们对axis_data_fifo模块进行了左右翻转。最终的IP模块连接图如下图所示:
image041.jpg

15.3.17 模块连接图
2-13 到这里我们的Block Design就设计完成了,按“F6”键进行“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
step3:生成顶层HDL模块
3-1 在Sources窗口中,选中Design Sources下的sysetm.bd, 这就是我们刚刚完成的Block Design设计。右键点击sysetm.bd,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。
step4:生成Bitstream文件并导出到SDK
4-1 在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”。在连续弹出的对话框中依次点击“YES”、“OK”。然后 Vivado 工具开始依次对设计进行综合、实现、并生成 Bitstream 文件。
4-2 导出硬件。
在生成Bitstream之后,在菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。然后在菜单栏选择File > LaunchSDK,启动SDK软件。
1.4 软件设计
step5:在SDK中创建应用工程
5-1 在SDK软件中新建一个名为“axi_dma_loop”的空白应用工程。
5-2 为应用工程新建一个名为“main.c”源文件,我们在新建的main.c文件中输入本次实验的代码。代码的主体部分如下所示:
  1. /*****************************Include Files *********************************/

  2. #include "xaxidma.h"
  3. #include "xparameters.h"
  4. #include "xil_exception.h"
  5. #include "xscugic.h"

  6. /**************************Constant Definitions *****************************/

  7. #defineDMA_DEV_ID         XPAR_AXIDMA_0_DEVICE_ID
  8. #defineRX_INTR_ID         XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
  9. #defineTX_INTR_ID         XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
  10. #defineINTC_DEVICE_ID     XPAR_SCUGIC_SINGLE_DEVICE_ID
  11. #defineDDR_BASE_ADDR       XPAR_PS7_DDR_0_S_AXI_BASEADDR   //0x00100000
  12. #defineMEM_BASE_ADDR       (DDR_BASE_ADDR+ 0x1000000)     //0x01100000
  13. #defineTX_BUFFER_BASE      (MEM_BASE_ADDR+ 0x00100000)    //0x01200000
  14. #defineRX_BUFFER_BASE      (MEM_BASE_ADDR+ 0x00300000)    //0x01400000
  15. #defineRESET_TIMEOUT_COUNTER   10000    //复位时间
  16. #defineTEST_START_VALUE        0x0      //测试起始值
  17. #defineMAX_PKT_LEN             0x100    //发送包长度

  18. /**************************Function Prototypes ******************************/

  19. static int check_data(int length, u8start_value);
  20. static void tx_intr_handler(void *callback);
  21. static void rx_intr_handler(void *callback);
  22. static int setup_intr_system(XScuGic *int_ins_ptr, XAxiDma * axidma_ptr,
  23.          u16tx_intr_id, u16 rx_intr_id);
  24. static void disable_intr_system(XScuGic *int_ins_ptr, u16 tx_intr_id,
  25.          u16rx_intr_id);

  26. /**************************Variable Definitions *****************************/

  27. static XAxiDma axidma;     //XAxiDma实例
  28. static XScuGic intc;       //中断控制器的实例
  29. volatile int tx_done;      //发送完成标志
  30. volatile int rx_done;      //接收完成标志
  31. volatile int error;        //传输出错标志

  32. /**************************Function Definitions *****************************/

  33. intmain(void)
  34. {
  35.      inti;
  36.      intstatus;
  37.      u8 value;
  38.      u8 *tx_buffer_ptr;
  39.      u8 *rx_buffer_ptr;
  40.      XAxiDma_Config *config;

  41.      tx_buffer_ptr =(u8 *) TX_BUFFER_BASE;
  42.      rx_buffer_ptr =(u8 *) RX_BUFFER_BASE;

  43.      xil_printf("\r\n---Entering main() --- \r\n");

  44.      config =XAxiDma_LookupConfig(DMA_DEV_ID);
  45.      if(!config) {
  46.          xil_printf("Noconfig found for %d\r\n", DMA_DEV_ID);
  47.          return XST_FAILURE;
  48.      }

  49.      //初始化DMA引擎
  50.      status =XAxiDma_CfgInitialize(&axidma, config);
  51.      if(status != XST_SUCCESS) {
  52.          xil_printf("Initializationfailed %d\r\n", status);
  53.          return XST_FAILURE;
  54.      }

  55.      if(XAxiDma_HasSg(&axidma)) {
  56.          xil_printf("Deviceconfigured as SG mode \r\n");
  57.          return XST_FAILURE;
  58.      }

  59.      //建立中断系统
  60.      status =setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);
  61.      if(status != XST_SUCCESS) {
  62.          xil_printf("Failedintr setup\r\n");
  63.          return XST_FAILURE;
  64.      }

  65.      //初始化标志信号
  66.      tx_done =0;
  67.      rx_done =0;
  68.      error =0;

  69.      value =TEST_START_VALUE;
  70.      for(i = 0;i < MAX_PKT_LEN; i++){
  71.         tx_buffer_ptr[i = value;
  72.          value =(value + 1)& 0xFF;
  73.      }

  74.     Xil_DCacheFlushRange((UINTPTR)tx_buffer_ptr, MAX_PKT_LEN);   //刷新Data Cache

  75.      //传送数据
  76.      status =XAxiDma_SimpleTransfer(&axidma, (UINTPTR)tx_buffer_ptr,
  77.      MAX_PKT_LEN,XAXIDMA_DMA_TO_DEVICE);
  78.      if(status != XST_SUCCESS) {
  79.          return XST_FAILURE;
  80.      }

  81.      status =XAxiDma_SimpleTransfer(&axidma, (UINTPTR)rx_buffer_ptr,
  82.      MAX_PKT_LEN,XAXIDMA_DEVICE_TO_DMA);
  83.      if(status != XST_SUCCESS) {
  84.          return XST_FAILURE;
  85.      }

  86.     Xil_DCacheFlushRange((UINTPTR)rx_buffer_ptr, MAX_PKT_LEN);   //刷新Data Cache
  87.      while (!tx_done && !rx_done&& !error)
  88.          ;
  89.      //传输出错
  90.      if(error) {
  91.          xil_printf("Failedtest transmit%s done, "
  92.                  "receive%s done\r\n",tx_done ? "" : "not",
  93.                 rx_done ? "" : "not");
  94.          goto Done;
  95.      }

  96.      //传输完成,检查数据是否正确
  97.      status =check_data(MAX_PKT_LEN,TEST_START_VALUE);
  98.      if(status != XST_SUCCESS) {
  99.          xil_printf("Datacheck failed\r\n");
  100.          goto Done;
  101.      }

  102.      xil_printf("Successfullyran AXI DMA Loop\r\n");
  103.     disable_intr_system(&intc, TX_INTR_ID,RX_INTR_ID);

  104.      Done:xil_printf("--- Exiting main() --- \r\n");
  105.      return XST_SUCCESS;
  106. }
复制代码
在代码的第14行,我们重新宏定义了XPAR_PS7_DDR_0_S_AXI_BASEADDR,即DDR3的基址,打开XPAR_PS7_DDR_0_S_AXI_BASEADDR的定义处,我们可以看到DDR3的基址为0x00100000,如下图所示:
图 15.4.1 DDR3的地址映
从而DMA读取数据的起始地址TX_BUFFER_BASE为0x01200000,写入到DDR3中的起始地址RX_BUFFER_BASE为0x01400000。代码第19行TEST_START_VALUE为测试起始值,此处我们将其设为0x0,也可以改为其它任意值。第20行的MAX_PKT_LEN是DMA传输的数据包的长度,此处为0x100,即256。
代码第42行的main函数是程序的主体。第63行的XAxiDma_CfgInitialize函数初始化DMA引擎,第75行的setup_intr_system函数建立DMA中断系统。第87~90行向DDR3的指定地址写入数据,写入的第一个地址为TX_BUFFER_BASE即0x01200000,值为TEST_START_VALUE即0x0,写入的地址长度为MAX_PKT_LEN,即0x100。DMA从TX_BUFFER_BASE读取数据长度为MAX_PKT_LE的数据,然后写入到地址RX_BUFFER_BAS处。第92行的Xil_DcacheFlushRange函数刷新Data Cache,以防Data Cache缓存数据。从第95行到第105行配置并开启DMA传输数据。第107行再次刷新Data Cache,由于DDR3中的数据已经更新,但Cache中的数据并没有更新,CPU如果想从DDR3中读取数据需要刷新DataCache。此处使用Xil_DcacheFlushRange函数,也可以使用Xil_DcacheInvalidateRange函数,使Data Cache指定范围的数据无效,函数调用方法相同。第119行的check_data函数检查当DMA传输完成后,写入的数据是否正确。第126行的disable_intr_system函数取消DMA中断。
主函数main中调用的自定义函数实现如下:
  1. //检查数据缓冲区
  2. static int check_data(int length, u8start_value)
  3. {
  4.      u8 value;
  5.      u8 *rx_packet;
  6.      inti = 0;

  7.      value =start_value;
  8.      rx_packet =(u8 *) RX_BUFFER_BASE;
  9.      for(i = 0;i < length; i++){
  10.          if(rx_packet[i != value) {
  11.             xil_printf("Data error %d: %x/%x\r\n",i, rx_packet[i], value);
  12.              return XST_FAILURE;
  13.          }
  14.          value =(value + 1)& 0xFF;
  15.      }

  16.      return XST_SUCCESS;
  17. }

  18. //DMA TX中断处理函数
  19. static void tx_intr_handler(void *callback)
  20. {
  21.      inttimeout;
  22.      u32 irq_status;
  23.      XAxiDma *axidma_inst= (XAxiDma *) callback;

  24.      //读取待处理的中断
  25.      irq_status =XAxiDma_IntrGetIrq(axidma_inst,XAXIDMA_DMA_TO_DEVICE);
  26.      //确认待处理的中断
  27.     XAxiDma_IntrAckIrq(axidma_inst, irq_status,XAXIDMA_DMA_TO_DEVICE);

  28.      //Tx出错
  29.      if((irq_status & XAXIDMA_IRQ_ERROR_MASK)){
  30.          error =1;
  31.         XAxiDma_Reset(axidma_inst);
  32.          timeout =RESET_TIMEOUT_COUNTER;
  33.          while (timeout) {
  34.              if(XAxiDma_ResetIsDone(axidma_inst))
  35.                  break;
  36.              timeout -=1;
  37.          }
  38.          return;
  39.      }

  40.      //Tx完成
  41.      if((irq_status & XAXIDMA_IRQ_IOC_MASK))
  42.          tx_done =1;
  43. }

  44. //DMA RX中断处理函数
  45. static void rx_intr_handler(void *callback)
  46. {
  47.      u32 irq_status;
  48.      inttimeout;
  49.      XAxiDma *axidma_inst= (XAxiDma *) callback;

  50.      irq_status =XAxiDma_IntrGetIrq(axidma_inst,XAXIDMA_DEVICE_TO_DMA);
  51.     XAxiDma_IntrAckIrq(axidma_inst, irq_status,XAXIDMA_DEVICE_TO_DMA);

  52.      //Rx出错
  53.      if((irq_status & XAXIDMA_IRQ_ERROR_MASK)){
  54.          error =1;
  55.         XAxiDma_Reset(axidma_inst);
  56.          timeout =RESET_TIMEOUT_COUNTER;
  57.          while (timeout) {
  58.              if(XAxiDma_ResetIsDone(axidma_inst))
  59.                  break;
  60.              timeout -=1;
  61.          }
  62.          return;
  63.      }

  64.      //Rx完成
  65.      if((irq_status & XAXIDMA_IRQ_IOC_MASK))
  66.          rx_done =1;
  67. }

  68. //建立DMA中断系统
  69. //  @param  int_ins_ptr是指向XScuGic实例的指针
  70. //  @param  AxiDmaPtr是指向DMA引擎实例的指针
  71. //  @param  tx_intr_id是TX通道中断ID
  72. //  @param  rx_intr_id是RX通道中断ID
  73. //  @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
  74. static int setup_intr_system(XScuGic *int_ins_ptr, XAxiDma * axidma_ptr,
  75.          u16tx_intr_id, u16 rx_intr_id)
  76. {
  77.      intstatus;
  78.      XScuGic_Config *intc_config;

  79.      //初始化中断控制器驱动
  80.      intc_config =XScuGic_LookupConfig(INTC_DEVICE_ID);
  81.      if(NULL ==intc_config) {
  82.          return XST_FAILURE;
  83.      }
  84.      status =XScuGic_CfgInitialize(int_ins_ptr,intc_config,
  85.             intc_config->CpuBaseAddress);
  86.      if(status != XST_SUCCESS) {
  87.          return XST_FAILURE;
  88.      }
  89.      
  90.      //设置优先级和触发类型
  91.     XScuGic_SetPriorityTriggerType(int_ins_ptr,tx_intr_id, 0xA0,0x3);
  92.     XScuGic_SetPriorityTriggerType(int_ins_ptr,rx_intr_id, 0xA0,0x3);

  93.      //为中断设置中断处理函数
  94.      status =XScuGic_Connect(int_ins_ptr, tx_intr_id,
  95.              (Xil_InterruptHandler)tx_intr_handler, axidma_ptr);
  96.      if(status != XST_SUCCESS) {
  97.          return status;
  98.      }

  99.      status =XScuGic_Connect(int_ins_ptr, rx_intr_id,
  100.              (Xil_InterruptHandler)rx_intr_handler, axidma_ptr);
  101.      if(status != XST_SUCCESS) {
  102.          return status;
  103.      }

  104.      XScuGic_Enable(int_ins_ptr,tx_intr_id);
  105.      XScuGic_Enable(int_ins_ptr,rx_intr_id);

  106.      //启用来自硬件的中断
  107.     Xil_ExceptionInit();
  108.     Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
  109.              (Xil_ExceptionHandler)XScuGic_InterruptHandler,
  110.              (void *) int_ins_ptr);
  111.     Xil_ExceptionEnable();

  112.      //使能DMA中断
  113.     XAxiDma_IntrEnable(&axidma,XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
  114.     XAxiDma_IntrEnable(&axidma,XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);

  115.      return XST_SUCCESS;
  116. }

  117. //此函数禁用DMA引擎的中断
  118. static void disable_intr_system(XScuGic *int_ins_ptr, u16 tx_intr_id,
  119.          u16rx_intr_id)
  120. {
  121.     XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
  122.     XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
  123. }
复制代码
代码第133行起的check_data函数用于检查写入到DDR3中的数据是否正确。
代码第153行起的DMA TX中断处理函数tx_intr_handler用于处理DMA发送中断。首先通过XAxiDma_IntrGetIrq函数读取待处理的中断,然后使用XAxiDma_IntrAckIrq函数确认待处理的中断。如果发现是接收出现错误的中断,则使用XAxiDma_Reset函数复位DMA,并使用XAxiDma_ResetIsDone函数判断是否复位完成。如果是发送完成的中断,则置位发送完成标志tx_done。代码第183行起的DMARX中断处理函数与此类似。
代码第216行起的建立DMA中断系统函数setup_intr_system首先初始化中断控制器驱动,然后使用XScuGic_SetPriorityTriggerType函数设置DMA的优先级和触发类型。XScuGic_Connect函数为中断设置中断处理函数,因为有发送中断和接收中断,所以需要分别设置。XScuGic_Enable函数用于使能DMA发送中断和DMA接收中断源。最后启用来自硬件的中断和使用XAxiDma_IntrEnable函数使能DMA中断。
5-3 我们按快捷键Ctrl+S保存 main.c 文件,工具会自动进行编译,编译过程可以在SDK下方的控制台(Console)中看到。编译完成后 Console 中会出现提示信息“Build Finished”,同时生成elf文件。
1.5 下载验证
首先我们将下载器与领航者底板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用Mini USB连接线将开发板左侧的USB_UART接口与电脑连接,用于串口通信。最后连接开发板的电源,并打开电源开关。
step6:板级验证
6-1 在SDK软件下方的SDK Terminal窗口中点击右上角的加号并连接串口,并在弹出的窗口中对串口进行设置。
6-2 下载程序。因为本次实验使用了PL内的资源,因此我们在下载软件编译生成的elf文件之前,需要先下载硬件设计过程中生成的bitstream文件,对PL部分进行配置。
在菜单栏中点击“Xilinx”,然后选择“Program FPGA”。在弹出的对话框中Bitstream一栏,确认已经加载了本次实验硬件设计过程中所生成的 BIT 文件——“system_wrapper.bit”。如 果 没 有加 载 该 文 件 , 则 需 要 通 过 点 击 右 侧 的Browse按 钮 ,在 工 程 目 录 下 的axi_dma_loop\axi_dma_loop.runs\impl_1 文件夹中选择system_wrapper.bit 文件。
最后点击右下角的“Program”,如下图所示:
image044.jpg

15.5.1 配置 PL
配置PL完成后,接下来我们下载软件程序。在应用工程axi_dma_loop上右击,选择“Debug As”,然后选择第一项“1 Launch on Hardware (System Debugger)”,进入调试界面。
6-3 设置Memory Monitors。
在调试界面的右下角,打开Memory窗口,添加需要监视的储存器地址。首先我们添加地址0x1200000,也就是DMA从DDR3中读取数据的起始地址TX_BUFFER_BASE,添加方式如下图所示:
image046.jpg

15.5.2 添加监视存储地址0x1200000
如果出现下图所示选项,选择“HexInteger”,即16进制整数,便于观看,然后点击右边的“AddRendering(s)”按钮。
image048.jpg

15.5.3 选择数据显示类型
从下图可以看到,刚下载完程序后,DDR3的0x1200000地址处的值为FF。
image050.jpg

15.5.4 0x1200000地址处的值
由于此种格式不方便查看具体地址的数据,我们将其设置成每个地址显示一个数据。鼠标右键点击Memory窗口右边的任意位置,在弹出的菜单中选择“Format…”,
image052.jpg

15.5.5 选择“Format…”
在弹出的界面中,选择ColumnSize为1,然后点击“OK”按钮,如下图所示:
image054.jpg

15.5.6 修改列大小
地址数据显示变成如下图所示,更方便观看。
image056.jpg

15.5.7 0x1200000地址处的值
现在我们按照同样的方式添加并设置地址0x1400000,也就是DMA将数据写回DDR3中的起始地址RX_BUFFER_BASE。从下图可以看到,刚下载完程序后,DDR3的0x1400000地址处的值也为FF。
image066.jpg

15.5.8 0x1400000地址处的值
  6-4 设置运行断点。
我们在程序的以下几个地方设置断点,
image060.jpg

15.5.9 设置断点
6-5 我们先将程序运行到断点1处,此时从Memory Monitors中可以看到0x1200000地址处的值变为00,紧随其后的地址的数据值逐次递增1。由于CPU与DDR3之间是通过Cache 交互的,数据暂存在Cache中,没有刷新Data Cache数据到DDR3,显示的数据是Data Cache中的。
image062.jpg

15.5.10 0x1200000地址数据变化
接着运行到断点2处, Data Cache中的数据已经刷新到DDR3中。此时我们将Memory Monitors窗口中的监视内容切换到0x1400000,看看地址0x1400000处的数据是什么时候更新的,点击左侧的0x1400000即可切换。运行到断点3处,执行完第115行的DMA发送函数,完成从内存中读取数据传输给外设,即DMA从地址0x1200000处读取数据传输给外设,此时地址0x1400000处的数据未更新。运行到断点4处,执行完第121行的DMA接收函数,完成从外设读取数据写入到内存,即将刚才写入到外设的数据读取出来并从DDR3的地址0x1400000处开始写入,不过此时我们从下图发现地址0x1400000处的数据还是未更新。其实此时DDR3中的数据已经更新,只不过我们Data Cache中的数据未更新,而Memory Monitors窗口显示的正是Data Cache中的数据,所以需要刷新Data Cache。
image064.jpg

15.5.11 0x1400000地址数据未变化
接着运行到断点5处,刷新Data Cache后,此时我们发现地址0x1400000处的值变为00,紧随其后的地址处的数据都变成预期的值。

15.5.12 刷新Data Cache0x1400000地址数据变化
到此,使用DMA从DDR3中读取数据,并将数据写回到DDR3中的实验任务就完成了。继续往下执行,到程序结束,在下方的 SDK Terminal 中可以看到应用程序打印的信息,如下图所示:
image067.png

15.5.13  串口终端中打印的信息




正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

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

GMT+8, 2024-11-23 19:57

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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