搜索
bottom↓
回复: 0

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

[复制链接]

出0入234汤圆

发表于 2020-8-1 10:31:58 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-10-24 10:30 编辑

1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900
QQ群头像.png
5)关注正点原子公众号,获取最新资料


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

第十六章AXI DMA环路测试



DMA(Direct Memory Access,直接存储器访问)是计算机科学中的一种内存访问技术。它允许某些计算机内部的硬件子系统可以独立地直接读写系统内存,而不需中央处理器(CPU)介入处理。DMA是一种快速的数据传送方式,通常用来传送数据量较多的数据块,很多硬件系统会使用DMA,包括硬盘控制器、绘图显卡、网卡和声卡,在使用高速AD/DA时使用DMA也是不错的选择。
本章我们使用PL的AXI DMA IP核实现DMA环路功能,了解DMA的使用。本章包括以下几个部分:
1616.1简介
16.2实验任务
16.3硬件设计
16.4软件设计
16.5下载验证


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

图 16.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接口完成高速的数据传输。各种接口方式的比较如下表所示:
表 16.1.1 各种接口方式比较
方式        优点        缺点        建议用途        估计吞吐率
CPU控制的IO        软件简单
最少的逻辑资源
逻辑接口简单        吞吐率最低        控制功能        <25MB/s
PS 的DMAC        最少的逻辑资源
吞吐率中等
多个通道
逻辑接口简单        DMAC配置有一定难度        当PL的DMA不够时        600 MB/s
PL的DMA和AXI_HP        吞吐率最高
多个接口
有FIFO缓存        只能访问OCM和DDR
逻辑设计复杂        大块数据高性能传输        1200 MB/s(每个接口)
PL的DMA和AXI_ACP        吞吐率最高
延时最低
可选的Cache一致性        大块数据传输引起Cache问题
共享了CPU的互联带宽
更复杂的逻辑设计        小块又与Cache直接相关的高速传输        1200 MB/s
PL的DMA和AXI_GP         吞吐率中等        更复杂的逻辑设计        PL到PS的控制功能
PS I/O外设访问        600 MB/s
可见通过PL的DMA和AXI_HP接口的传输适用于大块数据的高性能传输,带宽高。该种传输方式的拓扑图如下(灰色填充的框图或红色边框圈出的框图):
阿莫论坛发帖领航者专用11708.png

图 16.1.2  PL的DMA和AXI_HP接口拓扑图

可以看到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接口之间提供高带宽直接储存访问。其可选的scatter gather功能还可以从基于处理器的系统中的中央处理单元(CPU)卸载数据移动任务。初始化、状态和管理寄存器通过AXI4-Lite从接口访问。核心的功能组成如下图所示:
阿莫论坛发帖领航者专用12151.png

图 16.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事务中将数据传输到多个存储区域或从多个存储区域传输数据。它相当于将多个Simple DMA请求链接在一起。SGDMA允许应用程序在内存中定义事务列表,硬件将在应用程序没有进一步干预的情况下处理这些事务。在此期间,应用程序可以继续添加更多工作以保持硬件工作。用户可以通过轮询或中断来检查事务是否完成。SGDMA处理整个数据包(被定义为表示消息的一系列数据字节)并允许将数据包分解为一个或多个事务。例如,采用以太网IP数据包,该数据包由14字节的报头后跟1个或多个字节的有效负载组成。使用SGDMA,应用程序可以将BD(Buffer Descriptor,用于描述事务的对象)指向报头,将另一个BD指向有效负载,然后将它们作为单个消息传输。这种策略可以使TCP / IP堆栈更有效,它允许将数据包标头和数据保存在不同的内存区域,而不是将数据包组装成连续的内存块。
在本设计中,不需要使用scatter gather DMA模式,因为可以使用DMA的更简单的寄存器直接模式充分实现系统,从而避免实现scatter gather功能带来的面积成本。在系统需要对DMA进行相对复杂的软件控制时,可以使用scatter gather模式。
16.2实验任务
本章的实验任务是在领航者ZYNQ开发板上使用PL的AXI DMA IP核从DDR3中读取数据,并将数据写回到DDR3中。
16.3硬件设计
在实际应用中,DMA一般与产生数据或需求数据的IP核相连接,该IP核可以是带有Stream接口的高速的AD(模拟转数字)或DA(数字转模拟) IP核。不失一般性,在本次实验中,我们使用AXI4 Stream Data FIFO IP核来充当这类IP进行DMA环回实验。大致的系统框图如下(具体的可以看图 16.1.2):
阿莫论坛发帖领航者专用13719.png

图 16.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 Data FIFO可以相当于带有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。然后在弹出的对话框中指定所创建的Block Design的名称,在Design name栏中输入“system”。如下图所示:
阿莫论坛发帖领航者专用14391.png

图 16.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 配置时钟。
点击左侧的Clock Configuration页面,展开PL-Fabric Clocks,可以看到默认勾选FCLK_CLK0,且时钟频率为50MHz,这里我们将其修改为100MHz,如下图所示:
阿莫论坛发帖领航者专用14740.png

图 16.3.3 配置FCLK_CLK0

2-4 开启HP接口。
点击左侧的PS-PL Configuration页面,然后在右侧展开General下的HP Slave AXI Interface,可以看到有4个HP接口,这里我们只用其中的S AXI HP0 interface,DATA WIDTH保持默认即可,如下图所示:
阿莫论坛发帖领航者专用14949.png

图 16.3.4 启用HP0接口

2-5 因为DMA在传输完成后通过发送中断通知CPU,所以我们需要开启PL到PS的中断。
点击左侧的Interrupts页面,勾选右侧的Fabric interrupts并展开,勾选PL-PS Interrupt Ports下的IRQ_F2P[15:0],如下图所示:
阿莫论坛发帖领航者专用15146.png

图 16.3.5 开启PL到PS的中断

2-6 配置ZYNQ7 Processing System完成,点击“OK”,配置完成后的ZYNQ7 Processing System IP模块如下图所示:
阿莫论坛发帖领航者专用15291.png

图 16.3.6 配置完成后的ZYNQ7 Processing System IP

2-7 添加DMA IP。
添加DMA IP,如同添加ZYNQ7 Processing System IP,只不过搜索词由zynq变为dma,如下图所示:
阿莫论坛发帖领航者专用15457.png

图 16.3.7 添加DMA模块

阿莫论坛发帖领航者专用15519.png

图 16.3.8 配置DMA模块

我们双击axi_dma_0,打开配置界面,如图 16.3.8所示,此处我们只需要取消勾选Enable Scatter Gather Engine即可。不过还是介绍下与配置相关的选项。
Enable Scatter Gather Engine
选中此选项可启用Scatter Gather模式操作,并在AXI DMA中包含Scatter Gather 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字段的字节数。字节数等于。因此,长度为26时,字节数为字节。对于多通道模式,此值应设置为23。此处我们保持默认设置14。
Address Width (32 - 64)
指定地址空间的宽度,可以是32到64之间的任何值。此处保持默认值32。
Enable Read Channel
开启AXI DMA的读通道MM2S,相关选项如下:
Number of Channels:指定通道数。保持默认值1。
Memory Map Data Width:AXI MM2S存储映射读取数据总线的数据位宽。有效值为32,64,128,256,512和1024。此处保持默认值32。
Stream Data Width: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 Unaligned Transfers:启用或禁用MM2S数据重新排列引擎(Data Realignment 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
阿莫论坛发帖领航者专用17018.png

图 16.3.9 添加axis_data_fifo IP

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

图 16.3.10 添加Concat IP

2-10 模块连接。
在Diagram窗口中,点击“Run connection Automation”,进行自动连接,在弹出的界面中勾选“All Automation”,然后点击OK,如下图所示:
阿莫论坛发帖领航者专用17433.png

图 16.3.11 Run connection Automation

点击“Run Block Automation”,在弹出的界面中勾选“All Automation”,然后点击OK,如下图所示:
阿莫论坛发帖领航者专用17579.png

图 16.3.12 Run Block Automation

2-11 自动连接完成后,发现Concat IP未连接,我们手动进行连接,如下图所示:
阿莫论坛发帖领航者专用17699.png

图 16.3.13 手动连接未连接的IP

另外添加的axis_data_fifo也未连接,我们同样手动连接。首先将DMA的M_AXIS_MM2S端口与axis_data_fifo的S_AXIS进行连接,如下图所示:
阿莫论坛发帖领航者专用17852.png

图 16.3.14 连接axis_data_fifo的S_AXIS

然后将axis_data_fifo上的M_AXIS端口连接到DMA的S_AXIS_S2MM端口,如下图所示:
阿莫论坛发帖领航者专用17986.png

图 16.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端口,如下图所示:
16316.png

图 16.3.16 axis_data_fifo的时钟和复位

2-12 为了方便截图显示,我们对axis_data_fifo模块进行了左右翻转。最终的IP模块连接图如下图所示:
阿莫论坛发帖领航者专用18348.png

图 16.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 > Launch SDK,启动SDK软件。
16.4软件设计
step5:在SDK中创建应用工程
5-1 在SDK软件中新建一个名为“axi_dma_loop”的空白应用工程。
5-2 为应用工程新建一个名为“main.c”源文件,我们在新建的main.c文件中输入本次实验的代码。代码的主体部分如下所示:
  1. 1   /***************************** Include Files *********************************/
  2. 2   
  3. 3   #include "xaxidma.h"
  4. 4   #include "xparameters.h"
  5. 5   #include "xil_exception.h"
  6. 6   #include "xscugic.h"
  7. 7   
  8. 8   /************************** Constant Definitions *****************************/
  9. 9   
  10. 10  #define DMA_DEV_ID          XPAR_AXIDMA_0_DEVICE_ID
  11. 11  #define RX_INTR_ID          XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
  12. 12  #define TX_INTR_ID          XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
  13. 13  #define INTC_DEVICE_ID      XPAR_SCUGIC_SINGLE_DEVICE_ID
  14. 14  #define DDR_BASE_ADDR       XPAR_PS7_DDR_0_S_AXI_BASEADDR   //0x00100000
  15. 15  #define MEM_BASE_ADDR       (DDR_BASE_ADDR + 0x1000000)     //0x01100000
  16. 16  #define TX_BUFFER_BASE      (MEM_BASE_ADDR + 0x00100000)    //0x01200000
  17. 17  #define RX_BUFFER_BASE      (MEM_BASE_ADDR + 0x00300000)    //0x01400000
  18. 18  #define RESET_TIMEOUT_COUNTER   10000    //复位时间
  19. 19  #define TEST_START_VALUE        0x0      //测试起始值
  20. 20  #define MAX_PKT_LEN             0x100    //发送包长度
  21. 21  
  22. 22  /************************** Function Prototypes ******************************/
  23. 23  
  24. 24  static int check_data(int length, u8 start_value);
  25. 25  static void tx_intr_handler(void *callback);
  26. 26  static void rx_intr_handler(void *callback);
  27. 27  static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
  28. 28          u16 tx_intr_id, u16 rx_intr_id);
  29. 29  static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
  30. 30          u16 rx_intr_id);
  31. 31  
  32. 32  /************************** Variable Definitions *****************************/
  33. 33  
  34. 34  static XAxiDma axidma;     //XAxiDma实例
  35. 35  static XScuGic intc;       //中断控制器的实例
  36. 36  volatile int tx_done;      //发送完成标志
  37. 37  volatile int rx_done;      //接收完成标志
  38. 38  volatile int error;        //传输出错标志
  39. 39  
  40. 40  /************************** Function Definitions *****************************/
  41. 41  
  42. 42  int main(void)
  43. 43  {
  44. 44      int i;
  45. 45      int status;
  46. 46      u8 value;
  47. 47      u8 *tx_buffer_ptr;
  48. 48      u8 *rx_buffer_ptr;
  49. 49      XAxiDma_Config *config;
  50. 50  
  51. 51      tx_buffer_ptr = (u8 *) TX_BUFFER_BASE;
  52. 52      rx_buffer_ptr = (u8 *) RX_BUFFER_BASE;
  53. 53  
  54. 54      xil_printf("\r\n--- Entering main() --- \r\n");
  55. 55  
  56. 56      config = XAxiDma_LookupConfig(DMA_DEV_ID);
  57. 57      if (!config) {
  58. 58          xil_printf("No config found for %d\r\n", DMA_DEV_ID);
  59. 59          return XST_FAILURE;
  60. 60      }
  61. 61  
  62. 62      //初始化DMA引擎
  63. 63      status = XAxiDma_CfgInitialize(&axidma, config);
  64. 64      if (status != XST_SUCCESS) {
  65. 65          xil_printf("Initialization failed %d\r\n", status);
  66. 66          return XST_FAILURE;
  67. 67      }
  68. 68  
  69. 69      if (XAxiDma_HasSg(&axidma)) {
  70. 70          xil_printf("Device configured as SG mode \r\n");
  71. 71          return XST_FAILURE;
  72. 72      }
  73. 73  
  74. 74      //建立中断系统
  75. 75      status = setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);
  76. 76      if (status != XST_SUCCESS) {
  77. 77          xil_printf("Failed intr setup\r\n");
  78. 78          return XST_FAILURE;
  79. 79      }
  80. 80  
  81. 81      //初始化标志信号
  82. 82      tx_done = 0;
  83. 83      rx_done = 0;
  84. 84      error = 0;
  85. 85  
  86. 86      value = TEST_START_VALUE;
  87. 87      for (i = 0; i < MAX_PKT_LEN; i++) {
  88. 88          tx_buffer_ptr = value;
  89. 89          value = (value + 1) & 0xFF;
  90. 90      }
  91. 91  
  92. 92      Xil_DCacheFlushRange((UINTPTR) tx_buffer_ptr, MAX_PKT_LEN);   //刷新Data Cache
  93. 93  
  94. 94      //传送数据
  95. 95      status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) tx_buffer_ptr,
  96. 96      MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
  97. 97      if (status != XST_SUCCESS) {
  98. 98          return XST_FAILURE;
  99. 99      }
  100. 100
  101. 101     status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) rx_buffer_ptr,
  102. 102     MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
  103. 103     if (status != XST_SUCCESS) {
  104. 104         return XST_FAILURE;
  105. 105     }
  106. 106
  107. 107     Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN);   //刷新Data Cache
  108. 108     while (!tx_done && !rx_done && !error)
  109. 109         ;
  110. 110     //传输出错
  111. 111     if (error) {
  112. 112         xil_printf("Failed test transmit%s done, "
  113. 113                 "receive%s done\r\n", tx_done ? "" : " not",
  114. 114                 rx_done ? "" : " not");
  115. 115         goto Done;
  116. 116     }
  117. 117
  118. 118     //传输完成,检查数据是否正确
  119. 119     status = check_data(MAX_PKT_LEN, TEST_START_VALUE);
  120. 120     if (status != XST_SUCCESS) {
  121. 121         xil_printf("Data check failed\r\n");
  122. 122         goto Done;
  123. 123     }
  124. 124
  125. 125     xil_printf("Successfully ran AXI DMA Loop\r\n");
  126. 126     disable_intr_system(&intc, TX_INTR_ID, RX_INTR_ID);
  127. 127
  128. 128     Done: xil_printf("--- Exiting main() --- \r\n");
  129. 129     return XST_SUCCESS;
  130. 130 }
复制代码

在代码的第14行,我们重新宏定义了XPAR_PS7_DDR_0_S_AXI_BASEADDR,即DDR3的基址,打开XPAR_PS7_DDR_0_S_AXI_BASEADDR的定义处,我们可以看到DDR3的基址为0x00100000,如下图所示:
阿莫论坛发帖领航者专用113827.png

图 16.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中读取数据需要刷新Data Cache。此处使用Xil_DcacheFlushRange函数,也可以使用Xil_DcacheInvalidateRange函数,使Data Cache指定范围的数据无效,函数调用方法相同。第119行的check_data函数检查当DMA传输完成后,写入的数据是否正确。第126行的disable_intr_system函数取消DMA中断。
主函数main中调用的自定义函数实现如下:
  1. 132 //检查数据缓冲区
  2. 133 static int check_data(int length, u8 start_value)
  3. 134 {
  4. 135     u8 value;
  5. 136     u8 *rx_packet;
  6. 137     int i = 0;
  7. 138
  8. 139     value = start_value;
  9. 140     rx_packet = (u8 *) RX_BUFFER_BASE;
  10. 141     for (i = 0; i < length; i++) {
  11. <span style="font-style: italic;"><span style="font-style: normal;">142         if (rx_packet != value) {
  12. 143             xil_printf("Data error %d: %x/%x\r\n", i, rx_packet</span><span style="font-style: normal;">, value);
  13. 144             return XST_FAILURE;
  14. 145         }
  15. 146         value = (value + 1) & 0xFF;
  16. 147     }
  17. 148
  18. 149     return XST_SUCCESS;
  19. 150 }
  20. 151
  21. 152 //DMA TX中断处理函数
  22. 153 static void tx_intr_handler(void *callback)
  23. 154 {
  24. 155     int timeout;
  25. 156     u32 irq_status;
  26. 157     XAxiDma *axidma_inst = (XAxiDma *) callback;
  27. 158
  28. 159     //读取待处理的中断
  29. 160     irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DMA_TO_DEVICE);
  30. 161     //确认待处理的中断
  31. 162     XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DMA_TO_DEVICE);
  32. 163
  33. 164     //Tx出错
  34. 165     if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
  35. 166         error = 1;
  36. 167         XAxiDma_Reset(axidma_inst);
  37. 168         timeout = RESET_TIMEOUT_COUNTER;
  38. 169         while (timeout) {
  39. 170             if (XAxiDma_ResetIsDone(axidma_inst))
  40. 171                 break;
  41. 172             timeout -= 1;
  42. 173         }
  43. 174         return;
  44. 175     }
  45. 176
  46. 177     //Tx完成
  47. 178     if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
  48. 179         tx_done = 1;
  49. 180 }
  50. 181
  51. 182 //DMA RX中断处理函数
  52. 183 static void rx_intr_handler(void *callback)
  53. 184 {
  54. 185     u32 irq_status;
  55. 186     int timeout;
  56. 187     XAxiDma *axidma_inst = (XAxiDma *) callback;
  57. 188
  58. 189     irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DEVICE_TO_DMA);
  59. 190     XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DEVICE_TO_DMA);
  60. 191
  61. 192     //Rx出错
  62. 193     if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
  63. 194         error = 1;
  64. 195         XAxiDma_Reset(axidma_inst);
  65. 196         timeout = RESET_TIMEOUT_COUNTER;
  66. 197         while (timeout) {
  67. 198             if (XAxiDma_ResetIsDone(axidma_inst))
  68. 199                 break;
  69. 200             timeout -= 1;
  70. 201         }
  71. 202         return;
  72. 203     }
  73. 204
  74. 205     //Rx完成
  75. 206     if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
  76. 207         rx_done = 1;
  77. 208 }
  78. 209
  79. 210 //建立DMA中断系统
  80. 211 //  @param   int_ins_ptr是指向XScuGic实例的指针
  81. 212 //  @param   AxiDmaPtr是指向DMA引擎实例的指针
  82. 213 //  @param   tx_intr_id是TX通道中断ID
  83. 214 //  @param   rx_intr_id是RX通道中断ID
  84. 215 //  @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
  85. 216 static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
  86. 217         u16 tx_intr_id, u16 rx_intr_id)
  87. 218 {
  88. 219     int status;
  89. 220     XScuGic_Config *intc_config;
  90. 221
  91. 222     //初始化中断控制器驱动
  92. 223     intc_config = XScuGic_LookupConfig(INTC_DEVICE_ID);
  93. 224     if (NULL == intc_config) {
  94. 225         return XST_FAILURE;
  95. 226     }
  96. 227     status = XScuGic_CfgInitialize(int_ins_ptr, intc_config,
  97. 228             intc_config->CpuBaseAddress);
  98. 229     if (status != XST_SUCCESS) {
  99. 230         return XST_FAILURE;
  100. 231     }
  101. 232     
  102. 233     //设置优先级和触发类型
  103. 234     XScuGic_SetPriorityTriggerType(int_ins_ptr, tx_intr_id, 0xA0, 0x3);
  104. 235     XScuGic_SetPriorityTriggerType(int_ins_ptr, rx_intr_id, 0xA0, 0x3);
  105. 236
  106. 237     //为中断设置中断处理函数
  107. 238     status = XScuGic_Connect(int_ins_ptr, tx_intr_id,
  108. 239             (Xil_InterruptHandler) tx_intr_handler, axidma_ptr);
  109. 240     if (status != XST_SUCCESS) {
  110. 241         return status;
  111. 242     }
  112. 243
  113. 244     status = XScuGic_Connect(int_ins_ptr, rx_intr_id,
  114. 245             (Xil_InterruptHandler) rx_intr_handler, axidma_ptr);
  115. 246     if (status != XST_SUCCESS) {
  116. 247         return status;
  117. 248     }
  118. 249
  119. 250     XScuGic_Enable(int_ins_ptr, tx_intr_id);
  120. 251     XScuGic_Enable(int_ins_ptr, rx_intr_id);
  121. 252
  122. 253     //启用来自硬件的中断
  123. 254     Xil_ExceptionInit();
  124. 255     Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
  125. 256             (Xil_ExceptionHandler) XScuGic_InterruptHandler,
  126. 257             (void *) int_ins_ptr);
  127. 258     Xil_ExceptionEnable();
  128. 259
  129. 260     //使能DMA中断
  130. 261     XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
  131. 262     XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
  132. 263
  133. 264     return XST_SUCCESS;
  134. 265 }
  135. 266
  136. 267 //此函数禁用DMA引擎的中断
  137. 268 static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
  138. 269         u16 rx_intr_id)
  139. 270 {
  140. 271     XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
  141. 272     XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
  142. 273 }</span></span>
复制代码

代码第133行起的check_data函数用于检查写入到DDR3中的数据是否正确。
代码第153行起的DMA TX中断处理函数tx_intr_handler用于处理DMA发送中断。首先通过XAxiDma_IntrGetIrq函数读取待处理的中断,然后使用XAxiDma_IntrAckIrq函数确认待处理的中断。如果发现是接收出现错误的中断,则使用XAxiDma_Reset函数复位DMA,并使用XAxiDma_ResetIsDone函数判断是否复位完成。如果是发送完成的中断,则置位发送完成标志tx_done。代码第183行起的DMA RX中断处理函数与此类似。
代码第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文件。
16.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”,如下图所示:
阿莫论坛发帖领航者专用120189.png

图 16.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,添加方式如下图所示:
阿莫论坛发帖领航者专用120481.png

图 16.5.2 添加监视存储地址0x1200000

如果出现下图所示选项,选择“Hex Integer”,即16进制整数,便于观看,然后点击右边的“Add Rendering(s)”按钮。
阿莫论坛发帖领航者专用120622.png

图 16.5.3 选择数据显示类型

从下图可以看到,刚下载完程序后,DDR3的0x1200000地址处的值为FF。
阿莫论坛发帖领航者专用120726.png

图 16.5.4 0x1200000地址处的值

由于此种格式不方便查看具体地址的数据,我们将其设置成每个地址显示一个数据。鼠标右键点击Memory窗口右边的任意位置,在弹出的菜单中选择“Format…”,
阿莫论坛发帖领航者专用120875.png

图 16.5.5 选择“Format…”

在弹出的界面中,选择Column Size为1,然后点击“OK”按钮,如下图所示:
阿莫论坛发帖领航者专用120983.png

图 16.5.6 修改列大小

地址数据显示变成如下图所示,更方便观看。
阿莫论坛发帖领航者专用121064.png

图 16.5.7 0x1200000地址处的值

现在我们按照同样的方式添加并设置地址0x1400000,也就是DMA将数据写回DDR3中的起始地址RX_BUFFER_BASE。从下图可以看到,刚下载完程序后,DDR3的0x1400000地址处的值也为FF。
阿莫论坛发帖领航者专用121238.png

图 16.5.8 0x1400000地址处的值

  6-4 设置运行断点。
我们在程序的以下几个地方设置断点,
阿莫论坛发帖领航者专用121339.png

图 16.5.9 设置断点

6-5 我们先将程序运行到断点1处,此时从Memory Monitors中可以看到0x1200000地址处的值变为00,紧随其后的地址的数据值逐次递增1。由于CPU与DDR3之间是通过Cache 交互的,数据暂存在Cache中,没有刷新Data Cache数据到DDR3,显示的数据是Data Cache中的。
阿莫论坛发帖领航者专用121557.png

图 16.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。
阿莫论坛发帖领航者专用122048.png

图 16.5.11 0x1400000地址数据未变化

接着运行到断点5处,刷新Data Cache后,此时我们发现地址0x1400000处的值变为00,紧随其后的地址处的数据都变成预期的值。
阿莫论坛发帖领航者专用122189.png

图 16.5.12 刷新Data Cache后0x1400000地址数据变化

到此,使用DMA从DDR3中读取数据,并将数据写回到DDR3中的实验任务就完成了。继续往下执行,到程序结束,在下方的 SDK Terminal 中可以看到应用程序打印的信息,如下图所示:
阿莫论坛发帖领航者专用122367.png

图 16.5.13  串口终端中打印的信息


阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-24 19:00

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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