AXI DMA环路测试
DMA(Direct Memory Access,直接存储器访问)是计算机科学中的一种内存访问技术。它允许某些计算机内部的硬件子系统可以独立地直接读写系统内存,而不需中央处理器(CPU)介入处理。DMA是一种快速的数据传送方式,通常用来传送数据量较多的数据块,很多硬件系统会使用DMA,包括硬盘控制器、绘图显卡、网卡和声卡,在使用高速AD/DA时使用DMA也是不错的选择。
本章我们使用PL的AXI DMA核实现DMA环路功能,了解DMA的使用。本章包括以下几个部分:
- 简介
- 实验任务
- 硬件设计
- 软件设计
- 下载验证
简介
DMA是所有现代计算机的重要特色,它允许不同速度的硬件设备进行沟通,而不需要依于中央处理器的大量中断负载。否则,中央处理器需要从来源把每一片段的数据复制到寄存器,然后把它们再次写回到新的地方。在这个时间里,中央处理器就无法执行其它的任务。
DMA是用硬件实现存储器与存储器之间或存储器与I/O设备之间直接进行高速数据传输。使用DMA时,CPU向DMA控制器发出一个存储传输请求,这样当DMA控制器在传输的时候,CPU执行其它操作,传输操作完成时DMA以中断的方式通知CPU。
为了发起传输事务,DMA控制器必须得到以下数据:
• 源地址 — 数据被读出的地址
• 目的地址 — 数据被写入的地址
• 传输长度 — 应被传输的字节数
图21.1.1 DMA存储传输过程
DMA存储传输的过程如下:
1. 为了配置用DMA传输数据到存储器,处理器发出一条DMA命令。
2. DMA控制器把数据从外设传输到存储器或从存储器到存储器,而让CPU腾出手来做其它操作。
3. 数据传输完成后,向CPU发出一个中断来通知它DMA传输可以关闭了。
MPSOC中有两个通用DMA控制器:LPD DMA和FPD DMA。除了一致性,命令缓冲区大小和数据宽度外,8通道LPD和FPD DMA控制器在体系结构上是相同的。
Zynq UltraScale+ MPSOC是一种用来存储到存储、存储到IO、IO到存储、IO到IO传输的通用DMA,其模块框图如下图所示:
图21.1.2 DMA模块框图
AXI Direct Memory Access(AXI DMA)IP内核在AXI4内存映射和AXI4-Stream IP接口之间提供高带宽直接储存访问。其可选的scatter gather功能还可以从基于处理器的系统中的中央处理单元(CPU)卸载数据移动任务。初始化、状态和管理寄存器通过AXI4-Lite从接口访问。核心的功能组成如下图所示:
图21.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总线,提供对存储器(DDR)的访问。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模式。
实验任务
本章的实验任务是在MPSOC开发板上使用PL的AXI DMA IP核从DDR中读取数据,并将数据写回到DDR中。
硬件设计
在实际应用中,DMA一般与产生数据或需求数据的IP核相连接,该IP核可以是带有Stream接口的高速的AD(模拟转数字)或DA(数字转模拟) IP核。不失一般性,在本次实验中,我们使用AXI4 Stream Data FIFO IP核来充当这类IP进行DMA环回实验。大致的系统框图如下:
图21.3.1 AXI DAM环路测试系统框图
PS开启Slave AXI HP和Master AXI HPM接口。AXI DMA和AXI4 Stream Data FIFO在PL中实现。处理器通过Master AXI HPM接口与AXI DMA通信,以设置、启动和监控数据传输。数据传输通过Slave AXI HP接口。AXI DMA通过Slave AXI HP接口从DDR中读取数据后发送给AXI4 Stream Data FIFO,这种情况下AXI4 Stream Data FIFO可以相当于带有Stream接口的高速DA。AXI DMA读取AXI4 Stream Data FIFO中的数据后通过Slave AXI HP接口写入DDR的情形,AXI4 Stream Data FIFO相当于带有Stream接口的高速AD。
step1:创建Vivado工程
打开Vivado,创建一个名为“axi_dma_loop”的空白工程,工程路径为F:\ZYNQ\Embedded_System\axi_dma_loop文件夹。注意,工程名和路径只能由英文字母、数字和下划线组成,不能包含中文、空格以及特殊字符!
step2:使用IP Integrator创建Processing System
2-1 在左侧导航栏(Flow Navigator)中,单击IP Integrator下的Create Block Design。然后在弹出的对话框中指定所创建的Block Design的名称,这里使用Design name栏中默认的“design_1”,然后点击“OK”,如下图所示:
图21.3.2 创建 Block Design
2-2 添加Zynq UltraScale+ MPSOC模块。接下来按照《“Hello World”实验》中的步骤2-7、2-8分别配置PS的UART和DDR控制器。需要特别注意的是,我们在《“Hello World”实验》的步骤2-10中,移除了PS中与PL端交互的接口信号,这些接口在我们本次实验中需要予以保留。
2-3 配置时钟。
点击左侧的Clock Configuration页面,然后在右侧界面中打开Output Clocks标签页,依次展开Low Power Domain Clocks->PL Fabric Clocks,可以看到默认勾选PL0,且时钟频率为100MHz,这里我们使用默认的100MHz,如下图所示:
图21.3.3 配置pl_clk0
2-4 开启HP接口。
点击左侧的PS-PL Configuration页面,然后依次展开PS-PL Interfaces->Slave Interface->AXI HP,勾选AXI HP0 FPD,数据位宽使用默认的128bit位宽,如下图所示:
图21.3.4 启用HP0接口
2-5 因为DMA在传输完成后通过发送中断通知CPU,所以我们需要开启PL到PS的中断。
还是在PS-PL Configuration界面中,依次展开General->Interrupts->PL to PS,设置IRQ0[0:7]为1,如下图所示:
图21.3.5 开启PL到PS的中断
2-6 配置Zynq UltraScale+ MPSOC模块完成,点击“OK”,配置完成后的Zynq UltraScale+ MPSOC模块如下图所示:
图21.3.6 配置完成后的Zynq UltraScale+ MPSOC IP
2-7 添加DMA IP。
添加DMA IP,如同添加Zynq UltraScale+ MPSOC IP,只不过搜索词由MPSOC变为dma,如下图所示:
图21.3.7 添加DMA模块
图21.3.8 配置DMA模块
我们双击axi_dma_0,打开配置界面,如图21.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
图21.3.9 添加axis_data_fifo IP
该IP保持默认设置即可。添加该IP核的重点不是了解该IP核如何使用,而是知道该IP核带有AXI4 Stream接口。在以后的实际使用中,需要封装自定义IP核时要注意这一点。
2-9 添加Concat IP。
Concate IP 实现了单个分散的信号,整合成总线信号。这里2个独立的中断信号,可以合并在一起接入到Zynq UltraScale+ MPSOC IP的中断信号上。
图21.3.10 添加Concat IP
2-10 模块连接。
在Diagram窗口中,点击“Run connection Automation”,进行自动连接,在弹出的界面中勾选“All Automation”,然后点击OK,如下图所示:
图21.3.11 Run connection Automation
2-11 自动连接完成后,发现Concat IP未连接,我们手动进行连接,如下图所示:
图21.3.12 手动连接Concat IP
另外添加的axis_data_fifo也未连接,我们同样手动连接。首先将DMA的M_AXIS_MM2S端口与axis_data_fifo的S_AXIS进行连接,如下图所示:
图21.3.13 连接axis_data_fifo的S_AXIS
然后将axis_data_fifo上的M_AXIS端口连接到DMA的S_AXIS_S2MM端口,如下图所示:
图21.3.14 连接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端口,如下图所示:
图21.3.15 axis_data_fifo的时钟和复位
2-12再次点击Run Connection Automation,然后右击空白处选择Regenerate Layout重新布局,最终的IP模块连接图如下图所示:
图21.3.16 模块连接图
2-13 到这里我们的Block Design就设计完成了,按“F6”键进行“Validate Design”验证设计。验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
step3:生成顶层HDL模块
3-1 在Sources窗口中,选中Design Sources下的design_1.bd, 这就是我们刚刚完成的Block Design设计。右键点击design_1.bd,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。注意在执行“Generate Output Products”步骤时,受电脑配置的影响,软件运行时间会较长,要等待一会。
step4:生成Bitstream文件并导出Hardware
4-1在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”。在连续弹出的对话框中依次点击“YES”、“OK”。然后 Vivado 工具开始依次对设计进行综合、实现、并生成 Bitstream 文件。
4-2导出硬件。
在生成Bitstream之后,在菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”。将导出的“design_1_wrapper.xsa”文件放到vitis文件夹,然后在菜单栏选择Tools > Launch Vitis,启动VITIS软件。
软件设计
step5:在VITIS中创建应用工程
5-1 在VITIS软件中新建一个名为“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_PSU_DDR_0_S_AXI_BASEADDR //0x00000000
15 #define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x1100000) //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[i] = value;
89 value = (value + 1) & 0xFF;
90 }
91
92 Xil_DCacheFlushRange((UINTPTR) tx_buffer_ptr, MAX_PKT_LEN); //刷新Data Cache
93
94 status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) tx_buffer_ptr,
95 MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
96 if (status != XST_SUCCESS) {
97 return XST_FAILURE;
98 }
99
100 status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) rx_buffer_ptr,
101 MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
102 if (status != XST_SUCCESS) {
103 return XST_FAILURE;
104 }
105
106 Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN); //刷新Data Cache
107 while (!tx_done && !rx_done && !error)
108 ;
109 //传输出错
110 if (error) {
111 xil_printf("Failed test transmit%s done, "
112 "receive%s done\r\n", tx_done ? "" : " not",
113 rx_done ? "" : " not");
114 goto Done;
115 }
116
117 //传输完成,检查数据是否正确
118 status = check_data(MAX_PKT_LEN, TEST_START_VALUE);
119 if (status != XST_SUCCESS) {
120 xil_printf("Data check failed\r\n");
121 goto Done;
122 }
123
124 xil_printf("Successfully ran AXI DMA Loop\r\n");
125 disable_intr_system(&intc, TX_INTR_ID, RX_INTR_ID);
126
127 Done: xil_printf("--- Exiting main() --- \r\n");
128 return XST_SUCCESS;
129 }
在代码的第14行,我们重新宏定义了XPAR_PSU_DDR_0_S_AXI_BASEADDR,即DDR的基址,打开XPAR_PSU_DDR_0_S_AXI_BASEADDR的定义处,我们可以看到DDR的基址为0x00000000,如下图所示:
图21.4.1 DDR的地址映射
从而DMA读取数据的起始地址TX_BUFFER_BASE为0x01200000,写入到DDR中的起始地址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行向DDR的指定地址写入数据,写入的第一个地址为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,由于DDR中的数据已经更新,但Cache中的数据并没有更新,CPU如果想从DDR中读取数据需要刷新Data Cache。此处使用Xil_DcacheFlushRange函数,也可以使用Xil_DcacheInvalidateRange函数,使Data Cache指定范围的数据无效,函数调用方法相同。第118行的check_data函数检查当DMA传输完成后,写入的数据是否正确。第125行的disable_intr_system函数取消DMA中断。
主函数main中调用的自定义函数实现如下:
131 //检查数据缓冲区
132 static int check_data(int length, u8 start_value)
133 {
134 u8 value;
135 u8 *rx_packet;
136 int i = 0;
137
138 value = start_value;
139 rx_packet = (u8 *) RX_BUFFER_BASE;
140 for (i = 0; i < length; i++) {
141 if (rx_packet[i] != value) {
142 xil_printf("Data error %d: %x/%x\r\n", i, rx_packet[i], value);
143 return XST_FAILURE;
144 }
145 value = (value + 1) & 0xFF;
146 }
147
148 return XST_SUCCESS;
149 }
150
151 //DMA TX中断处理函数
152 static void tx_intr_handler(void *callback)
153 {
154 int timeout;
155 u32 irq_status;
156 XAxiDma *axidma_inst = (XAxiDma *) callback;
157
158 //读取待处理的中断
159 irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DMA_TO_DEVICE);
160 //确认待处理的中断
161 XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DMA_TO_DEVICE);
162
163 //Tx出错
164 if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
165 error = 1;
166 XAxiDma_Reset(axidma_inst);
167 timeout = RESET_TIMEOUT_COUNTER;
168 while (timeout) {
169 if (XAxiDma_ResetIsDone(axidma_inst))
170 break;
171 timeout -= 1;
172 }
173 return;
174 }
175
176 //Tx完成
177 if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
178 tx_done = 1;
179 }
180
181 //DMA RX中断处理函数
182 static void rx_intr_handler(void *callback)
183 {
184 u32 irq_status;
185 int timeout;
186 XAxiDma *axidma_inst = (XAxiDma *) callback;
187
188 irq_status = XAxiDma_IntrGetIrq(axidma_inst, XAXIDMA_DEVICE_TO_DMA);
189 XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DEVICE_TO_DMA);
190
191 //Rx出错
192 if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
193 error = 1;
194 XAxiDma_Reset(axidma_inst);
195 timeout = RESET_TIMEOUT_COUNTER;
196 while (timeout) {
197 if (XAxiDma_ResetIsDone(axidma_inst))
198 break;
199 timeout -= 1;
200 }
201 return;
202 }
203
204 //Rx完成
205 if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
206 rx_done = 1;
207 }
208
209 //建立DMA中断系统
210 // @param int_ins_ptr是指向XScuGic实例的指针
211 // @param AxiDmaPtr是指向DMA引擎实例的指针
212 // @param tx_intr_id是TX通道中断ID
213 // @param rx_intr_id是RX通道中断ID
214 // @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
215 static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
216 u16 tx_intr_id, u16 rx_intr_id)
217 {
218 int status;
219 XScuGic_Config *intc_config;
220
221 //初始化中断控制器驱动
222 intc_config = XScuGic_LookupConfig(INTC_DEVICE_ID);
223 if (NULL == intc_config) {
224 return XST_FAILURE;
225 }
226 status = XScuGic_CfgInitialize(int_ins_ptr, intc_config,
227 intc_config->CpuBaseAddress);
228 if (status != XST_SUCCESS) {
229 return XST_FAILURE;
230 }
231
232 //设置优先级和触发类型
233 XScuGic_SetPriorityTriggerType(int_ins_ptr, tx_intr_id, 0xA0, 0x3);
234 XScuGic_SetPriorityTriggerType(int_ins_ptr, rx_intr_id, 0xA0, 0x3);
235
236 //为中断设置中断处理函数
237 status = XScuGic_Connect(int_ins_ptr, tx_intr_id,
238 (Xil_InterruptHandler) tx_intr_handler, axidma_ptr);
239 if (status != XST_SUCCESS) {
240 return status;
241 }
242
243 status = XScuGic_Connect(int_ins_ptr, rx_intr_id,
244 (Xil_InterruptHandler) rx_intr_handler, axidma_ptr);
245 if (status != XST_SUCCESS) {
246 return status;
247 }
248
249 XScuGic_Enable(int_ins_ptr, tx_intr_id);
250 XScuGic_Enable(int_ins_ptr, rx_intr_id);
251
252 //启用来自硬件的中断
253 Xil_ExceptionInit();
254 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
255 (Xil_ExceptionHandler) XScuGic_InterruptHandler,
256 (void *) int_ins_ptr);
257 Xil_ExceptionEnable();
258
259 //使能DMA中断
260 XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
261 XAxiDma_IntrEnable(&axidma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
262
263 return XST_SUCCESS;
264 }
265
266 //此函数禁用DMA引擎的中断
267 static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
268 u16 rx_intr_id)
269 {
270 XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
271 XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
272 }
代码第132行起的check_data函数用于检查写入到DDR中的数据是否正确。
代码第152行起的DMA TX中断处理函数tx_intr_handler用于处理DMA发送中断。首先通过XAxiDma_IntrGetIrq函数读取待处理的中断,然后使用XAxiDma_IntrAckIrq函数确认待处理的中断。如果发现是接收出现错误的中断,则使用XAxiDma_Reset函数复位DMA,并使用XAxiDma_ResetIsDone函数判断是否复位完成。如果是发送完成的中断,则置位发送完成标志tx_done。代码第182行起的DMA RX中断处理函数与此类似。
代码第215行起的建立DMA中断系统函数setup_intr_system首先初始化中断控制器驱动,然后使用XScuGic_SetPriorityTriggerType函数设置DMA的优先级和触发类型。XScuGic_Connect函数为中断设置中断处理函数,因为有发送中断和接收中断,所以需要分别设置。XScuGic_Enable函数用于使能DMA发送中断和DMA接收中断源。最后启用来自硬件的中断和使用XAxiDma_IntrEnable函数使能DMA中断。
5-3 我们按快捷键Ctrl+S保存 main.c 文件,右击应用工程名,选择Build Project,编译工程。编译完成后 Console 中会出现提示信息“Build Finished”,同时生成elf文件。
下载验证
首先我们将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB_UART(开发板PS PORT)接口与电脑连接,用于串口通信。最后连接开发板的电源,给开发板上电。
step6:板级验证
6-1 进入Debug模式。在前面的实验中,我们右击应用工程名,Run As选择第一项“Launch on Hardware”下载程序。本实验,我们换一种方法。右击应用工程名,Debug As选择第一项“Launch on Hardware”,进入调试界面,如下图所示:
图21.5.1 进入Debug模式
然后在串口终端(Vitis Serial Terminal)中,点击箭头处的加号设置端口号,端口号可以通过自己电脑设备管理器查看,这里选择COM9。
6-2 设置Memory Monitors。
在调试界面Memory窗口中,添加需要监视的储存器地址。首先我们添加地址0x1200000,也就是DMA从DDR中读取数据的起始地址TX_BUFFER_BASE,添加方式如下图所示:
图21.5.2 添加监视存储地址0x1200000
在新添加的Memory监视界面的空白处右击,在弹出的菜单栏中选择“Format…”,设置“Column Size”为1,分别如图21.5.3和图21.5.4所示:
图21.5.3 设置数据显示格式
图21.5.4 设置Column Size大小
设置完成后如下图所示,这样可以更方便观察数据。可以看到,刚下载完程序后,DDR中地址0x1200000处的值为0x71。
图21.5.5 0x1200000地址处的值
现在我们按照同样的方式添加并设置地址0x1400000,也就是DMA将数据写回DDR中的起始地址RX_BUFFER_BASE。从下图可以看到,刚下载完程序后,DDR的0x1400000地址处的值为0x71。
图21.5.6 0x1400000地址处的值
6-3 设置运行断点。
我们在程序的以下几个地方设置断点。
图21.5.7 设置断点
6-4 按F8先将程序运行到断点1处(113行),此时从Memory Monitors中可以看到0x1200000地址处的值变为00,紧随其后的地址的数据值逐次递增1。由于CPU与DDR之间是通过Cache 交互的,数据暂存在Cache中,没有刷新Data Cache数据到DDR,显示的数据是Data Cache中的。
图21.5.8 0x1200000地址数据变化
接着运行到断点2处(115行), Data Cache中的数据已经刷新到DDR4中。此时我们将Memory Monitors窗口中的监视内容切换到0x1400000,看看地址0x1400000处的数据是什么时候更新的,点击左侧的0x1400000即可切换。运行到断点3处(121行),执行完第115行的DMA发送函数,完成从内存中读取数据传输给外设,即DMA从地址0x1200000处读取数据传输给外设,此时地址0x1400000处的数据未更新。运行到断点4处(127行),执行完第121行的DMA接收函数,完成从外设读取数据写入到内存,即将刚才写入到外设的数据读取出来并从DDR的地址0x1400000处开始写入,不过此时我们从下图发现地址0x1400000处的数据还是未更新。其实此时DDR中的数据已经更新,只不过我们Data Cache中的数据未更新,而Memory Monitors窗口显示的正是Data Cache中的数据,所以需要刷新Data Cache。
图21.5.9 0x1400000地址数据未变化
接着运行到断点5处(128行),刷新Data Cache后,此时我们发现地址0x1400000处的值变为00,紧随其后的地址处的数据都变成预期的值。
图21.5.10 刷新Data Cache后0x1400000地址数据变化
到此,使用DMA从DDR中读取数据,并将数据写回到DDR中的实验任务就完成了。按F6继续往下执行,到程序结束,在下方的 VITIS Terminal 中可以看到应用程序打印的信息,如下图所示:
图21.5.11 串口终端中打印的信息