本帖最后由 正点原子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)关注正点原子公众号,获取最新资料更新
第十五章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控制器必须得到以下数据: • 源地址 — 数据被读出的地址 • 目的地址 — 数据被写入的地址 • 传输长度 — 应被传输的字节数
图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 各种接口方式比较
图 15.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接口之间提供高带宽直接储存访问。其可选的scattergather功能还可以从基于处理器的系统中的中央处理单元(CPU)卸载数据移动任务。初始化、状态和管理寄存器通过AXI4-Lite从接口访问。核心的功能组成如下图所示:
图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):
图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”。如下图所示:
图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,如下图所示:
图15.3.3 配置FCLK_CLK0 2-4 开启HP接口。 点击左侧的PS-PLConfiguration页面,然后在右侧展开General下的HP Slave AXI Interface,可以看到有4个HP接口,这里我们只用其中的S AXI HP0 interface,DATA WIDTH保持默认即可,如下图所示:
图15.3.4 启用HP0接口 2-5 因为DMA在传输完成后通过发送中断通知CPU,所以我们需要开启PL到PS的中断。 点击左侧的Interrupts页面,勾选右侧的Fabric interrupts并展开,勾选PL-PS Interrupt Ports下的IRQ_F2P[15:0],如下图所示:
图15.3.5 开启PL到PS的中断 2-6 配置ZYNQ7 Processing System完成,点击“OK”,配置完成后的ZYNQ7 Processing System IP模块如下图所示:
15.3.6 配置完成后的ZYNQ7 Processing System IP 2-7 添加DMA IP。 添加DMA IP,如同添加ZYNQ7 Processing System IP,只不过搜索词由zynq变为dma,如下图所示:
图15.3.7 添加DMA模块
图 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
图15.3.9 添加axis_data_fifo IP 该IP保持默认设置即可。添加该IP核的重点不是了解该IP核如何使用,而是知道该IP核带有AXI Stream接口。在以后的实际使用中,需要封装自定义IP核时要注意这一点。 2-9 添加Concat IP。 Concate IP 实现了单个分散的信号,整合成总线信号。这里 2 个独立的中断信号,可以合并在一起接入到 ZYNQ IP 的中断信号上。
图15.3.10 添加Concat IP 2-10 模块连接。 在Diagram窗口中,点击“Run connection Automation”,进行自动连接,在弹出的界面中勾选“AllAutomation”,然后点击OK,如下图所示:
15.3.11 Run connection Automation 点击“Run BlockAutomation”,在弹出的界面中勾选“All Automation”,然后点击OK,如下图所示:
图15.3.12 Run Block Automation 2-11 自动连接完成后,发现Concat IP未连接,我们手动进行连接,如下图所示:
15.3.13 手动连接未连接的IP 另外添加的axis_data_fifo也未连接,我们同样手动连接。首先将DMA的M_AXIS_MM2S端口与axis_data_fifo的S_AXIS进行连接,如下图所示:
图15.3.14 连接axis_data_fifo的S_AXIS 然后将axis_data_fifo上的M_AXIS端口连接到DMA的S_AXIS_S2MM端口,如下图所示: 图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端口,如下图所示:
图15.3.16 axis_data_fifo的时钟和复位 2-12 为了方便截图显示,我们对axis_data_fifo模块进行了左右翻转。最终的IP模块连接图如下图所示:
图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文件中输入本次实验的代码。代码的主体部分如下所示: - /*****************************Include Files *********************************/
-
- #include "xaxidma.h"
- #include "xparameters.h"
- #include "xil_exception.h"
- #include "xscugic.h"
-
- /**************************Constant Definitions *****************************/
-
- #defineDMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID
- #defineRX_INTR_ID XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
- #defineTX_INTR_ID XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
- #defineINTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
- #defineDDR_BASE_ADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR //0x00100000
- #defineMEM_BASE_ADDR (DDR_BASE_ADDR+ 0x1000000) //0x01100000
- #defineTX_BUFFER_BASE (MEM_BASE_ADDR+ 0x00100000) //0x01200000
- #defineRX_BUFFER_BASE (MEM_BASE_ADDR+ 0x00300000) //0x01400000
- #defineRESET_TIMEOUT_COUNTER 10000 //复位时间
- #defineTEST_START_VALUE 0x0 //测试起始值
- #defineMAX_PKT_LEN 0x100 //发送包长度
-
- /**************************Function Prototypes ******************************/
-
- static int check_data(int length, u8start_value);
- static void tx_intr_handler(void *callback);
- static void rx_intr_handler(void *callback);
- static int setup_intr_system(XScuGic *int_ins_ptr, XAxiDma * axidma_ptr,
- u16tx_intr_id, u16 rx_intr_id);
- static void disable_intr_system(XScuGic *int_ins_ptr, u16 tx_intr_id,
- u16rx_intr_id);
-
- /**************************Variable Definitions *****************************/
-
- static XAxiDma axidma; //XAxiDma实例
- static XScuGic intc; //中断控制器的实例
- volatile int tx_done; //发送完成标志
- volatile int rx_done; //接收完成标志
- volatile int error; //传输出错标志
-
- /**************************Function Definitions *****************************/
-
- intmain(void)
- {
- inti;
- intstatus;
- u8 value;
- u8 *tx_buffer_ptr;
- u8 *rx_buffer_ptr;
- XAxiDma_Config *config;
-
- tx_buffer_ptr =(u8 *) TX_BUFFER_BASE;
- rx_buffer_ptr =(u8 *) RX_BUFFER_BASE;
-
- xil_printf("\r\n---Entering main() --- \r\n");
-
- config =XAxiDma_LookupConfig(DMA_DEV_ID);
- if(!config) {
- xil_printf("Noconfig found for %d\r\n", DMA_DEV_ID);
- return XST_FAILURE;
- }
-
- //初始化DMA引擎
- status =XAxiDma_CfgInitialize(&axidma, config);
- if(status != XST_SUCCESS) {
- xil_printf("Initializationfailed %d\r\n", status);
- return XST_FAILURE;
- }
-
- if(XAxiDma_HasSg(&axidma)) {
- xil_printf("Deviceconfigured as SG mode \r\n");
- return XST_FAILURE;
- }
-
- //建立中断系统
- status =setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);
- if(status != XST_SUCCESS) {
- xil_printf("Failedintr setup\r\n");
- return XST_FAILURE;
- }
-
- //初始化标志信号
- tx_done =0;
- rx_done =0;
- error =0;
-
- value =TEST_START_VALUE;
- for(i = 0;i < MAX_PKT_LEN; i++){
- tx_buffer_ptr[i = value;
- value =(value + 1)& 0xFF;
- }
-
- Xil_DCacheFlushRange((UINTPTR)tx_buffer_ptr, MAX_PKT_LEN); //刷新Data Cache
-
- //传送数据
- status =XAxiDma_SimpleTransfer(&axidma, (UINTPTR)tx_buffer_ptr,
- MAX_PKT_LEN,XAXIDMA_DMA_TO_DEVICE);
- if(status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- status =XAxiDma_SimpleTransfer(&axidma, (UINTPTR)rx_buffer_ptr,
- MAX_PKT_LEN,XAXIDMA_DEVICE_TO_DMA);
- if(status != XST_SUCCESS) {
- return XST_FAILURE;
- }
- Xil_DCacheFlushRange((UINTPTR)rx_buffer_ptr, MAX_PKT_LEN); //刷新Data Cache
- while (!tx_done && !rx_done&& !error)
- ;
- //传输出错
- if(error) {
- xil_printf("Failedtest transmit%s done, "
- "receive%s done\r\n",tx_done ? "" : "not",
- rx_done ? "" : "not");
- goto Done;
- }
- //传输完成,检查数据是否正确
- status =check_data(MAX_PKT_LEN,TEST_START_VALUE);
- if(status != XST_SUCCESS) {
- xil_printf("Datacheck failed\r\n");
- goto Done;
- }
- xil_printf("Successfullyran AXI DMA Loop\r\n");
- disable_intr_system(&intc, TX_INTR_ID,RX_INTR_ID);
- Done:xil_printf("--- Exiting main() --- \r\n");
- return XST_SUCCESS;
- }
复制代码在代码的第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中调用的自定义函数实现如下: - //检查数据缓冲区
- static int check_data(int length, u8start_value)
- {
- u8 value;
- u8 *rx_packet;
- inti = 0;
- value =start_value;
- rx_packet =(u8 *) RX_BUFFER_BASE;
- for(i = 0;i < length; i++){
- if(rx_packet[i != value) {
- xil_printf("Data error %d: %x/%x\r\n",i, rx_packet[i], value);
- return XST_FAILURE;
- }
- value =(value + 1)& 0xFF;
- }
- return XST_SUCCESS;
- }
- //DMA TX中断处理函数
- static void tx_intr_handler(void *callback)
- {
- inttimeout;
- u32 irq_status;
- XAxiDma *axidma_inst= (XAxiDma *) callback;
- //读取待处理的中断
- irq_status =XAxiDma_IntrGetIrq(axidma_inst,XAXIDMA_DMA_TO_DEVICE);
- //确认待处理的中断
- XAxiDma_IntrAckIrq(axidma_inst, irq_status,XAXIDMA_DMA_TO_DEVICE);
- //Tx出错
- if((irq_status & XAXIDMA_IRQ_ERROR_MASK)){
- error =1;
- XAxiDma_Reset(axidma_inst);
- timeout =RESET_TIMEOUT_COUNTER;
- while (timeout) {
- if(XAxiDma_ResetIsDone(axidma_inst))
- break;
- timeout -=1;
- }
- return;
- }
- //Tx完成
- if((irq_status & XAXIDMA_IRQ_IOC_MASK))
- tx_done =1;
- }
- //DMA RX中断处理函数
- static void rx_intr_handler(void *callback)
- {
- u32 irq_status;
- inttimeout;
- XAxiDma *axidma_inst= (XAxiDma *) callback;
- irq_status =XAxiDma_IntrGetIrq(axidma_inst,XAXIDMA_DEVICE_TO_DMA);
- XAxiDma_IntrAckIrq(axidma_inst, irq_status,XAXIDMA_DEVICE_TO_DMA);
- //Rx出错
- if((irq_status & XAXIDMA_IRQ_ERROR_MASK)){
- error =1;
- XAxiDma_Reset(axidma_inst);
- timeout =RESET_TIMEOUT_COUNTER;
- while (timeout) {
- if(XAxiDma_ResetIsDone(axidma_inst))
- break;
- timeout -=1;
- }
- return;
- }
- //Rx完成
- if((irq_status & XAXIDMA_IRQ_IOC_MASK))
- rx_done =1;
- }
- //建立DMA中断系统
- // @param int_ins_ptr是指向XScuGic实例的指针
- // @param AxiDmaPtr是指向DMA引擎实例的指针
- // @param tx_intr_id是TX通道中断ID
- // @param rx_intr_id是RX通道中断ID
- // @return:成功返回XST_SUCCESS,否则返回XST_FAILURE
- static int setup_intr_system(XScuGic *int_ins_ptr, XAxiDma * axidma_ptr,
- u16tx_intr_id, u16 rx_intr_id)
- {
- intstatus;
- XScuGic_Config *intc_config;
- //初始化中断控制器驱动
- intc_config =XScuGic_LookupConfig(INTC_DEVICE_ID);
- if(NULL ==intc_config) {
- return XST_FAILURE;
- }
- status =XScuGic_CfgInitialize(int_ins_ptr,intc_config,
- intc_config->CpuBaseAddress);
- if(status != XST_SUCCESS) {
- return XST_FAILURE;
- }
-
- //设置优先级和触发类型
- XScuGic_SetPriorityTriggerType(int_ins_ptr,tx_intr_id, 0xA0,0x3);
- XScuGic_SetPriorityTriggerType(int_ins_ptr,rx_intr_id, 0xA0,0x3);
- //为中断设置中断处理函数
- status =XScuGic_Connect(int_ins_ptr, tx_intr_id,
- (Xil_InterruptHandler)tx_intr_handler, axidma_ptr);
- if(status != XST_SUCCESS) {
- return status;
- }
- status =XScuGic_Connect(int_ins_ptr, rx_intr_id,
- (Xil_InterruptHandler)rx_intr_handler, axidma_ptr);
- if(status != XST_SUCCESS) {
- return status;
- }
- XScuGic_Enable(int_ins_ptr,tx_intr_id);
- XScuGic_Enable(int_ins_ptr,rx_intr_id);
- //启用来自硬件的中断
- Xil_ExceptionInit();
- Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
- (Xil_ExceptionHandler)XScuGic_InterruptHandler,
- (void *) int_ins_ptr);
- Xil_ExceptionEnable();
- //使能DMA中断
- XAxiDma_IntrEnable(&axidma,XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
- XAxiDma_IntrEnable(&axidma,XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
- return XST_SUCCESS;
- }
- //此函数禁用DMA引擎的中断
- static void disable_intr_system(XScuGic *int_ins_ptr, u16 tx_intr_id,
- u16rx_intr_id)
- {
- XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
- XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
- }
复制代码代码第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”,如下图所示:
图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,添加方式如下图所示:
图15.5.2 添加监视存储地址0x1200000 如果出现下图所示选项,选择“HexInteger”,即16进制整数,便于观看,然后点击右边的“AddRendering(s)”按钮。
图15.5.3 选择数据显示类型 从下图可以看到,刚下载完程序后,DDR3的0x1200000地址处的值为FF。
图15.5.4 0x1200000地址处的值 由于此种格式不方便查看具体地址的数据,我们将其设置成每个地址显示一个数据。鼠标右键点击Memory窗口右边的任意位置,在弹出的菜单中选择“Format…”,
图15.5.5 选择“Format…” 在弹出的界面中,选择ColumnSize为1,然后点击“OK”按钮,如下图所示:
图15.5.6 修改列大小 地址数据显示变成如下图所示,更方便观看。
图15.5.7 0x1200000地址处的值 现在我们按照同样的方式添加并设置地址0x1400000,也就是DMA将数据写回DDR3中的起始地址RX_BUFFER_BASE。从下图可以看到,刚下载完程序后,DDR3的0x1400000地址处的值也为FF。
图15.5.8 0x1400000地址处的值 6-4 设置运行断点。 我们在程序的以下几个地方设置断点,
图15.5.9 设置断点 6-5 我们先将程序运行到断点1处,此时从Memory Monitors中可以看到0x1200000地址处的值变为00,紧随其后的地址的数据值逐次递增1。由于CPU与DDR3之间是通过Cache 交互的,数据暂存在Cache中,没有刷新Data Cache数据到DDR3,显示的数据是Data Cache中的。
图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。
图15.5.11 0x1400000地址数据未变化 接着运行到断点5处,刷新Data Cache后,此时我们发现地址0x1400000处的值变为00,紧随其后的地址处的数据都变成预期的值。
图15.5.12 刷新Data Cache后0x1400000地址数据变化 到此,使用DMA从DDR3中读取数据,并将数据写回到DDR3中的实验任务就完成了。继续往下执行,到程序结束,在下方的 SDK Terminal 中可以看到应用程序打印的信息,如下图所示:
图15.5.13 串口终端中打印的信息
|