发 帖  
原厂入驻New
[资料] 【正点原子FPGA连载】第十六章AXI DMA环路测试交互--领航者ZYNQ之嵌入式开发指南
2020-9-4 11:12:20  88 FPGA DMA 开发板 嵌入式
分享
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
5)关注正点原子公众号,获取最新资料


第十六章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控制器必须得到以下数据:
• 源地址 — 数据被读出的地址
• 目的地址 — 数据被写入的地址
• 传输长度 — 应被传输的字节数



图 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接口的传输适用于大块数据的高性能传输,带宽高。该种传输方式的拓扑图如下(灰色填充的框图或红色边框圈出的框图):




图 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从接口访问。核心的功能组成如下图所示:



图 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):




图 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”。如下图所示:



图 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,如下图所示:



图 16.3.3 配置FCLK_CLK0

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



图 16.3.4 启用HP0接口

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



图 16.3.5 开启PL到PS的中断

2-6 配置ZYNQ7 Processing System完成,点击“OK”,配置完成后的ZYNQ7 Processing System IP模块如下图所示:



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

2-7 添加DMA IP。
添加DMA IP,如同添加ZYNQ7 Processing System IP,只不过搜索词由zynq变为dma,如下图所示:



图 16.3.7 添加DMA模块




图 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



图 16.3.9 添加axis_data_fifo IP

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



图 16.3.10 添加Concat IP

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



图 16.3.11 Run connection Automation

点击“Run Block Automation”,在弹出的界面中勾选“All Automation”,然后点击OK,如下图所示:



图 16.3.12 Run Block Automation

2-11 自动连接完成后,发现Concat IP未连接,我们手动进行连接,如下图所示:



图 16.3.13 手动连接未连接的IP

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



图 16.3.14 连接axis_data_fifo的S_AXIS

然后将axis_data_fifo上的M_AXIS端口连接到DMA的S_AXIS_S2MM端口,如下图所示:



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



图 16.3.16 axis_data_fifo的时钟和复位

2-12 为了方便截图显示,我们对axis_data_fifo模块进行了左右翻转。最终的IP模块连接图如下图所示:



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

在代码的第14行,我们重新宏定义了XPAR_PS7_DDR_0_S_AXI_BASEADDR,即DDR3的基址,打开XPAR_PS7_DDR_0_S_AXI_BASEADDR的定义处,我们可以看到DDR3的基址为0x00100000,如下图所示:



图 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中调用的自定义函数实现如下:
  • 132 //检查数据缓冲区
  • 133 static int check_data(int length, u8 start_value)
  • 134 {
  • 135 u8 value;
  • 136 u8 *rx_packet;
  • 137 int i = 0;
  • 138
  • 139 value = start_value;
  • 140 rx_packet = (u8 *) RX_BUFFER_BASE;
  • 141 for (i = 0; i < length; i++) {
  • <span><span>142 if (rx_packet != value) {
  • 143 xil_printf("Data error %d: %x/%x\r\n", i, rx_packet</span><span>, value);
  • 144 return XST_FAILURE;
  • 145 }
  • 146 value = (value + 1) & 0xFF;
  • 147 }
  • 148
  • 149 return XST_SUCCESS;
  • 150 }
  • 151
  • 152 //DMA TX中断处理函数
  • 153 static void tx_intr_handler(void *callback)
  • 154 {
  • 155 int timeout;
  • 156 u32 irq_status;
  • 157 XAxiDma *axidma_inst = (XAxiDma *) callback;
  • 158
  • 159 //读取待处理的中断
  • 160 irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DMA_TO_DEVICE);
  • 161 //确认待处理的中断
  • 162 XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DMA_TO_DEVICE);
  • 163
  • 164 //Tx出错
  • 165 if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
  • 166 error = 1;
  • 167 XAxiDma_Reset(axidma_inst);
  • 168 timeout = RESET_TIMEOUT_COUNTER;
  • 169 while (timeout) {
  • 170 if (XAxiDma_ResetIsDone(axidma_inst))
  • 171 break;
  • 172 timeout -= 1;
  • 173 }
  • 174 return;
  • 175 }
  • 176
  • 177 //Tx完成
  • 178 if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
  • 179 tx_done = 1;
  • 180 }
  • 181
  • 182 //DMA RX中断处理函数
  • 183 static void rx_intr_handler(void *callback)
  • 184 {
  • 185 u32 irq_status;
  • 186 int timeout;
  • 187 XAxiDma *axidma_inst = (XAxiDma *) callback;
  • 188
  • 189 irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DEVICE_TO_DMA);
  • 190 XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DEVICE_TO_DMA);
  • 191
  • 192 //Rx出错
  • 193 if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
  • 194 error = 1;
  • 195 XAxiDma_Reset(axidma_inst);
  • 196 timeout = RESET_TIMEOUT_COUNTER;
  • 197 while (timeout) {
  • 198 if (XAxiDma_ResetIsDone(axidma_inst))
  • 199 break;
  • 200 timeout -= 1;
  • 201 }
  • 202 return;
  • 203 }
  • 204
  • 205 //Rx完成
  • 206 if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
  • 207 rx_done = 1;
  • 208 }
  • 209
  • 210 //建立DMA中断系统
  • 211 // @param int_ins_ptr是指向XScuGic实例的指针
  • 212 // @param AxiDmaPtr是指向DMA引擎实例的指针
  • 213 // @param tx_intr_id是TX通道中断ID
  • 214 // @param rx_intr_id是RX通道中断ID
  • 215 // @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
  • 216 static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
  • 217 u16 tx_intr_id, u16 rx_intr_id)
  • 218 {
  • 219 int status;
  • 220 XScuGic_Config *intc_config;
  • 221
  • 222 //初始化中断控制器驱动
  • 223 intc_config = XScuGic_LookupConfig(INTC_DEVICE_ID);
  • 224 if (NULL == intc_config) {
  • 225 return XST_FAILURE;
  • 226 }
  • 227 status = XScuGic_CfgInitialize(int_ins_ptr, intc_config,
  • 228 intc_config->CpuBaseAddress);
  • 229 if (status != XST_SUCCESS) {
  • 230 return XST_FAILURE;
  • 231 }
  • 232
  • 233 //设置优先级和触发类型
  • 234 XScuGic_SetPrioritytriggerType(int_ins_ptr, tx_intr_id, 0xA0, 0x3);
  • 235 XScuGic_SetPriorityTriggerType(int_ins_ptr, rx_intr_id, 0xA0, 0x3);
  • 236
  • 237 //为中断设置中断处理函数
  • 238 status = XScuGic_Connect(int_ins_ptr, tx_intr_id,
  • 239 (Xil_InterruptHandler) tx_intr_handler, axidma_ptr);
  • 240 if (status != XST_SUCCESS) {
  • 241 return status;
  • 242 }
  • 243
  • 244 status = XScuGic_Connect(int_ins_ptr, rx_intr_id,
  • 245 (Xil_InterruptHandler) rx_intr_handler, axidma_ptr);
  • 246 if (status != XST_SUCCESS) {
  • 247 return status;
  • 248 }
  • 249
  • 250 XScuGic_Enable(int_ins_ptr, tx_intr_id);
  • 251 XScuGic_Enable(int_ins_ptr, rx_intr_id);
  • 252
  • 253 //启用来自硬件的中断
  • 254 Xil_ExceptionInit();
  • 255 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
  • 256 (Xil_ExceptionHandler) XScuGic_InterruptHandler,
  • 257 (void *) int_ins_ptr);
  • 258 Xil_ExceptionEnable();
  • 259
  • 260 //使能DMA中断
  • 261 XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
  • 262 XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
  • 263
  • 264 return XST_SUCCESS;
  • 265 }
  • 266
  • 267 //此函数禁用DMA引擎的中断
  • 268 static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
  • 269 u16 rx_intr_id)
  • 270 {
  • 271 XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
  • 272 XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
  • 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”,如下图所示:



图 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,添加方式如下图所示:

[img=402,185][/img]" border="0" alt="">

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

如果出现下图所示选项,选择“Hex Integer”,即16进制整数,便于观看,然后点击右边的“Add Rendering(s)”按钮。



图 16.5.3 选择数据显示类型

从下图可以看到,刚下载完程序后,DDR3的0x1200000地址处的值为FF。



图 16.5.4 0x1200000地址处的值

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



图 16.5.5 选择“Format…”

在弹出的界面中,选择Column Size为1,然后点击“OK”按钮,如下图所示:



图 16.5.6 修改列大小

地址数据显示变成如下图所示,更方便观看。



图 16.5.7 0x1200000地址处的值

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

[img=558,216][/img]" border="0" alt="">

图 16.5.8 0x1400000地址处的值

6-4 设置运行断点。
我们在程序的以下几个地方设置断点,



图 16.5.9 设置断点

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



图 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。



图 16.5.11 0x1400000地址数据未变化

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



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

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



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


1
分享淘帖 显示全部楼层

评论

高级模式
您需要登录后才可以回帖 登录 | 注册

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
发资料
关闭

站长推荐 上一条 /7 下一条

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