1 AXI4(AXI-full)总线学习
1.1 什么是AXI
AXI(Advanced eXtensible Interface)是一种总协议,该协议的第一个版本AXI3是ARM公司提出的AMBA(Advanced Microcontroller Bus Architecture)3.0协议中最重要的部分。2010发布的AMBA4.0包含了AXI的第二个版本AXI4。
AXI4包含3种类型的接口(或者说ZYNQ的三种AXI总线)。
(1)AXI4:(For high-performance memory-mapped requirements.)主要面向高性能地址映射通信的需求,是面向地址映射的接口,允许最大256轮的数据突发传输。
(2)AXI4-Lite:(For simple, low-throughput memory-mapped communication )是一个轻量级的地址映射单次传输接口,占用很少的逻辑单元。适用于吞吐量较小的地址映射通信总线。
(3)AXI4-Stream:(For high-speed streaming data.)面向高速流数据传输;去掉了地址项,允许无限制的数据突发传输规模。
现在说明AXI的优势。
(1)高效:通过标准化的AXI接口,开发者只需要学习一种IP核的通讯协议即可;
(2)易用:针对具体应用提供合适的接口协议。AXI4:面向地址映射的接口,在单地址传输的情况下最大允许256个时钟周期的数据突发长度;AXI4-Lite:一个轻量级的地址映射单次传输接口,占用较少的资源;AXI4-Stream:去掉了地址传输的功能,允许无限制的数据突发传输,无需考虑地址映射。
(3)易得:标准化的AXI接口协议资源,不仅可以在xilinx官网上获得,也可以在全球范围内ARM的所有合作伙伴处获得。大量的IP core支持AXI4协议;大量的第三方AXI工具可提供多样的系统开发、验证和功能定制。
1.2 AXI管脚说明
AXI4总线和AXI4-Lite总线具有相同的组成部分:
(1)读地址通道:包含ARVALID,ARADDR,ARREADY信号;
(2)读数据通道:包含RVALID, RDATA,RREADY,RRESP信号;
(3)写地址通道:包含AWVALID,AWADDR,AWREADY信号;
(4)写数据通道:包含WVALID,WDATA,WSTRB, WREADY信号;
(5)写应答通道:包含BVALID,BRESP,BREADY信号;
(6)系统通道:包含:ACLK,ARESETN信号。
而AXI4-Stream总线的组成有:
(1)ACLK信号:总线时钟,上升沿有效;
(2)ARESETN信号:总线复位,低电平有效;
(3)TVALID信号:主机告诉从机数据本次传输有效;
(4)TREADY信号:从机告诉主机做好传输准备;
(5) TLAST信号:主机告诉从机该次传输为突发传输的结尾;当TLAST为高时,表明当前数据是最后一次传输。
(6)TDATA信号:数据,可选宽度32、64、128、256bit;
(7)TKEEP信号:类似于字节修饰符,表示数据是有效的。
(8) TSTRB信号:每bit对应TDATA的一个有效字节,宽度为TDATA/8;
(9)TUSER信号:用户定义信号,宽度为128bit。
下面对这些信号的管脚做一个具体的说明。
1.3 zynq的三种AXI总线
AXI DMA 用到了三种总线:
(1)AXI4-Lite :AXI4-Lite用于对寄存器进行配置和获取状态,从而控制DMA的数据传输。选用AXI-lite(AXI4-Lite )接口的原因为AXI-lite(AXI4-Lite )接口一般用于简单的低吞吐量内存映射通信(例如,与控制和状态寄存器之间的通信)。一般用于低速少量的数据通信,比如传递参数。
(2)AXI4 Memory Map:AXI4 Memory Map 用于与存储器交互的接口。又分为 AXI4 Memory Map Read 和 AXI4 Memory Map Write 两个接口,一个是读一个是写。
(3)AXI4 Stream 接口:AXI4 Stream 接口用于对外设的读写。其中 AXI4 Stream Master(MM2S,Memory Map to Stream,存储器映射AXI4-full(AXI4)到AXI4-stream)用于对外设写,AXI4-Stream Slave(S2MM,Stream to Memory Map,,AXI4-stream到存储器映射AXI4-full(AXI4))用于对外设读。
总之,在以后的使用中需要知道 AXI_MM2S 和AXI_S2MM 是存储器端映射的 AXI4 总线,提供对存储器(DDR3)的访问。AXIS_MM2S 和 AXIS_S2MM是 AXI4-streaming (外设)总线,可以发送和接收连续的数据流,无需地址。
1.4 AXI的三种接口
总共有9个接口:
(1)AXI-GP接口(4个):是通用的AXI接口,包括两个32位主设备接口和两个32位从设备接口,用该接口可以访问PS中的片内外设。这个对应AXI-lite总线使用。这接口我用的挺多,传输速度也不快,用于PS与PL少慢数据通信。
(2)AXI-HP接口(4个):是高性能/带宽的标准的接口,PL模块作为主设备连接。主要用于PL访问PS上的存储器(DDR和On-Chip RAM)。PS都作为从设备,对应AXI-full总线。因为这个高速传输接口,PS太慢了,要顺着PL走。
(3)AXI-ACP接口(1个):是ARM多核架构下定义的一种接口,中文翻译为加速器一致性端口,用来管理DMA之类的不带缓存的AXI外设,PS端是Slave接口。这个直接通往ARM内部,不经过DDR,所以速度是最快的。
1.5 AXI协议
协议的制定是要建立在总线构成之上的。因此说AXI4,AXI4-Lite,AXI4-Stream都是AXI4协议。AXI总线协议的两端可以分为分为主(master)、从(slave)两端,他们之间一般需要通过一个AXI Interconnect相连接。作用是提供将一个或多个AXI主设备连接到一个或多个AXI从设备的一种交换机制。
AXI Interconnect的主要作用是:当存在多个主机以及从机器时,AXI Interconnect负责将它们联系并管理起来。由于AXI支持乱序发送,乱序发送需要主机的ID信号支撑,而不同的主机发送的ID可能相同,而AXI Interconnect解决了这一问题,他会对不同主机的ID信号进行处理让ID变得唯一。
AXI协议将读地址通道,读数据通道,写地址通道,写数据通道,写响应通道分开,各自通道都有自己的握手协议。每个通道互不干扰却又彼此依赖。这是AXI高效的原因之一。
AXI握手协议:
AXI4 所采用的是一种 READY,VALID 握手通信机制。简单来说主从双方进行数据通信前,有一个握手的过程。传输源产生 VLAID 信号来指明何时数据或控制信息有效。而目地源产生READY信号来指明已经准备好接受数据或控制信息。传输发生在VALID和 READY信号同时为高的时候,如下图所示。
2 AXI DMA简介
2.1 基本介绍
ZYNQ 提供了两种 DMA,一种是集成在 PS 中的硬核 DMA,另一种是 PL 中使用的软核AXI DMA。下面我们简单的介绍下 PL 的 DMA,即 AXI DMA IP 核。
AXI DMA:AXI Direct Memory Access,直接内存访问。是指外部设备不通过CPU,直接与系统内存交换数据的接口技术。官方解释为内存与AXI4-Stream外设之间提供高带宽的直接存储访问,其可选的scatter/gather功能可将CPU从数据搬移任务中解放出来。在ZYNQ中,AXI DMA就是FPGA访问DDR3的桥梁,不过该过程受ARM的监控和管理。
要将外设数据读入内存或将内存传送到外设,一般都要通过 CPU 控制完成,如采用查询或中断方式。虽然中断方式可以提高CPU的利用率,但是也会有效率问题。对于批量传送数据的情况,采用 DMA 方式,可解决效率与速度问题,CPU只需要提供地址和长度给DMA,DMA 即可接管总线,访问内存。等 DMA 完成工作后,告知CPU,交出总线控制权。也就是说,使用DMA时,CPU 向 DMA 控制器发出一个存储传输请求,这样当DMA 控制器在传输的时候,CPU 执行其它操作。传输操作完成时,DMA 以中断的方式通知 CPU。
为了发起传输事务,DMA 控制器必须得到以下数据:
• 源地址 — 数据被读出的地址
• 目的地址 — 数据被写入的地址
• 传输长度 — 应被传输的字节数
DMA 存储传输的过程如下:
- 为了配置用 DMA 传输数据到存储器,处理器发出一条 DMA 命令
- DMA 控制器把数据从外设传输到存储器,而让 CPU 腾出手来做其它操作。
- 数据传输完成后,向 CPU 发出一个中断来通知它,DMA传输可以关闭了。
在 ARM CPU 设计的过程中,已经考虑到了大量数据搬移的情况,因此在 CPU 中自带了一个 DMA 控制器 DMAC,这个 DMAC 驻留在 PS 内,而且必须通过驻留在内存中的 DMA 指令编程,这些程序往往由CPU 准备,因此需要部分的 CPU 参与。DMAC与PL的连接是通过 AXI_GP 接口,这个接口最高支持到 32 位宽度,这也限制了这种模式下的传输速率,理论最高速率为 600MB/s。这种模式不占用 PL 资源,但需要对 DMA 指令编程,会增加软件的复杂性。
通过 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 从接口访问。核心的功能组成如下图所示:
在ZYNQ中,AX IDMA就是FPGA访问DDR3的桥梁,不过该过程受ARM的监控和管理。AXI DMA IP有6个接口,S_AXI_LITE是ARM配置DMA寄存器的接口,M_AXI_SG是从存储器加载buffer descriptor的接口,剩下4个接口构成两对,S2MM和MM2S表示数据的方向,AXI是存储器一侧的接口,AXIS是FPGA一侧的接口。AXI DMA IP和ARM自带的DMA是很像的,只不过不具备从存储器到存储器的功能,当然也可以将S2MM和MM2S接口与AXIS接口直接相连,其结构如下图所示。
2.2 Direct Register Mode(简单DMA模式)
Direct Register DMA 模式也就是 Simple DMA。Direct Register 模式提供了一种配置,用于在 MM2S 和S2MM 通道上执行简单的 DMA 传输,这需要更少的 FPGA 资源。Simple DMA 允许应用程序在 DMA 和Device 之间定义单个事务。它有两个通道:一个从 DMA 到 Device,另一个从 Device 到 DMA。应用程序必须设置缓冲区地址和长度字段以启动相应通道中的传输。
通过访问DMACR(DMA控制寄存器)、源地址或者目的地址和长度寄存器发起DMA传输。当传输完成后,如果使能了产生中断输出,那么DMASR(状态寄存器)相关联的通道位会有效,即产生中断输出。
Direct Register Mode具备DMA的基本功能,除了控制寄存器和状态寄存器之外,给出目的地址和传输长度之后就可以开启一次传输了。但是,Direct Register Mode模式配置完一次寄存器之后只能完成存储器连续地址空间的读写,如果有需求往不同空间搬运数据的话,那就需要重新配置寄存器开启一次新的传输。
简单DMA模式的编程顺序如下:
S2MM 通道的 DMA 传输的设置和启动顺序
(1)开启/使能S2MM通道。
(2)如果需要的话,可以使能中断(一般都会使能中断)。
(3)写一个有效的源地址到S2MM_SA寄存器(源地址寄存器)。即指定数据从哪里开始搬运。
如果没有使能DRE功能,在指定起始地址时,需要注意字节地址对齐。哪些地址是对齐或者不对齐的,取决于Stream流的数据位宽。For example, if Memory Map Data Width = 32, data is aligned if it is located at word offsets (32-bit offset), that is 0x0, 0x4, 0x8, 0xC, and so forth. If DRE is enabled and Streaming Data Width < 128, then the Source Addresses can be of any byte offset.
(4)写待传输的字节数到S2MM_LENGTH寄存器。需要注意的是,一个长度为0的值是无效的。而一个非零的值,将决定存储器映射到Stream流的数据个数。除此之外,S2MM_LENGTH寄存器需要最后配置,而其他寄存器的配置顺序没有要求。
3 DMA LOOPBACK实验
3.1 实验目的
使用PL的AXI DMA IP核从DDR3中读取数据,并将数据写回到DDR3中。在实际应用中,DMA一般与产生数据或需求数据的 IP 核相连接,该 IP 核可以是带有 Stream 接口的高速的 AD(模拟转数字)或 DA(数字转模拟) IP 核。不失一般性,在本次实验中,我们使用 AXI4 StreamData FIFO IP 核来充当这类 IP 进行 DMA 环回实验。大致的系统框图如下,具体的见图6.1所示的PL 的 DMA 和 AXI_HP 接口拓扑图。
3.2 实验步骤
Address Width (32 - 64):
指定地址空间的宽度,可以是 32 到 64 之间的任何值。此处保持默认值 32。
Memory Map Data Width:
AXI MM2S 存储映射读取数据总线的数据位宽。有效值为 32,64,128,256,512和 1024。此处保持默认值 32。
Stream Data Width:
AXI MM2S AXI4-Stream 数据总线的数据位宽。该值必须小于或等于 Memory MapData Width。有效值为 8、16、32、64、128、512 和 1024。此处保持默认值 32。
3.2.1 建立BD文件
具体BD文件的建立参考黑金SDK开发或者正点原子的SDK开发教材=程。
3.2.2 SDK工程文件
/*
* 本章的实验任务是在领航者 ZYNQ 开发板上使用 PL 的 AXI DMA IP 核从 DDR3 中读取数据,并将数据写回到 DDR3 中
*
* TX_BUFFER_BASE,RX_BUFFER_BASE;
* 0x01200000,0x01400000;
*/
#include "xaxidma.h"
#include "xparameters.h"
#include "xil_exception.h"
#include "xscugic.h"
/************************** Constant Definitions *****************************/
// DMA的器件ID
#define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID //位于PL侧的DMA,xparameters.h
//定义设备号
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID // 器件中断ID
//定义DDR的基地址
#define DDR_BASE_ADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR // 0x00100000 DDR的基地址
//定义内存的地址
#define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x01000000) // 0x01100000
//定义发送缓冲区的基地址
#define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000) // DMA读取数据的起始地址0x01200000
//定义接收缓冲区的基地址
#define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000) // 写到 DDR3中的起始地址0x01400000
// DMA接收与发送通道的中断ID
#define RX_INTR_ID XPAR_FABRIC_AXI_DMA_0_S2MM_INTROUT_INTR//接收中断ID
#define TX_INTR_ID XPAR_FABRIC_AXI_DMA_0_MM2S_INTROUT_INTR//发送中断ID
//定义一个复位时间计数器
#define RESET_TIMEOUT_COUNTER 10000 // 复位时间
//定义一个测试的起始值
#define TEST_START_VALUE 0x0 // 测试起始值
//定义测试长度
#define MAX_PKT_LEN 0x100 // DMA传输的数据包的长度(256)
/************************** 函数声明 ******************************/
//数据核验函数
static int check_data(int length, u8 start_value);//检查写入到 DDR3 中的数据是否正确
//发送中断的处理函数
static void tx_intr_handler(void *callback);//DMA TX中断处理函数
//接收中断的处理函数
static void rx_intr_handler(void *callback);//DMA RX中断处理函数
//建立中断系统
static int setup_intr_system(XScuGic * int_ins_ptr, XAxiDma * axidma_ptr,
u16 tx_intr_id, u16 rx_intr_id); //建立 DMA中断系统函数
//禁用中断函数
static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
u16 rx_intr_id); //禁用DMA引擎的中断函数
/************************** Variable Definitions *****************************/
static XAxiDma axidma; //XAxiDma实例
static XScuGic intc; //中断控制器的实例
volatile int tx_done; //发送完成标志
volatile int rx_done; //接收完成标志
volatile int error; //传输出错标志
int main(void)
{
int i;
int status;//是否完成标志位
u8 value;
/* 等价于
u8 tx_buffer_ptr[MAX_PKT_LEN]; //发送缓冲区指针,指针指向的数据为8bit无符号数
u8 rx_buffer_ptr[MAX_PKT_LEN]; //接受缓冲区指针,指针指向的数据为8bit无符号数
*/
u8 *tx_buffer_ptr;//发送缓冲区指针,指针指向的数据为8bit无符号数
u8 *rx_buffer_ptr;//接受缓冲区指针,指针指向的数据为8bit无符号数
/*
XAxiDma_Config是一个AXI_DMA配置的信息结构体,它里面包含需要配置的各种信息,
类似于一个空表,表里面有各种需要填的事项,
填表的方式是将AXI_DMA的设备号作为传入参数传递到XAxiDma_LookupConfig查找函数中,
如果传输的设备号和函数内部的设备号一样的话,就将根据PL侧的设计参数传递给查找表
*/
XAxiDma_Config *config;
/*
从C语言的角度看指针.TX_BUFFER_BASE与RX_BUFFER_BASE都是一个地址,在地址前面加上(u8 *)修饰符,
这样理解:
a=8'b1;
u8* p;
*p=(u8*)(&a);
p=就是a的地址
此处解释:
(u8 *) TX_BUFFER_BASE;
将TX_BUFFER_BASE转换成指向8位无符号数指针的内容, 然后这个地址传递给tx_buffer_ptr
(u8*)的作用是指针该地址指向的数据为8bit无符号数,不可以多操作或者少操作
*/
tx_buffer_ptr = (u8 *) TX_BUFFER_BASE;
rx_buffer_ptr = (u8 *) RX_BUFFER_BASE;
xil_printf("\r\n--- Entering main() --- \r\n");
/*
进行DMA配置参数传递
通过调用DMA查找配置函数,传入设备ID,获取设备参数
需要注意的是,其中的参数是根据PL端的IP core的配置选项生成的参数
*/
//Look up the hardware configuration for a device instance
config = XAxiDma_LookupConfig(DMA_DEV_ID);
if (!config) {
xil_printf("No config found for %d\r\n", DMA_DEV_ID);
return XST_FAILURE;
}
/*
//初始化DMA引擎
根据PL端对DMA core的配置参数,PS对DMA进行真正的配置初始化过程,
axidma还存储在PS端的AXI——DMA配置表,根据对PL参数的读取,
PS运行对PL侧的DMA配置,这个配置过程是通过GP0接口对AXI_Lite4总线的控制完成的
*/
status = XAxiDma_CfgInitialize(&axidma, config);
if (status != XST_SUCCESS) {
xil_printf("Initialization failed %d\r\n", status);
return XST_FAILURE;
}
/*
我们配置的是使用PL侧DMA的直接寄存器访问模式,所以数据传递也是通过该方式运行的,
为了以防万一,在这里运行一下SG查询函数看看是不是配置成了SG模式
*/
if (XAxiDma_HasSg(&axidma)) {//判断是不是工作在SG模式
xil_printf("Device configured as SG mode \r\n");
return XST_FAILURE;
}
/*
建立中断系统,详见函数定义
CallBackRef is the callback reference, usually the instance pointer of the connecting driver.
CallBackRef是回调引用,通常是连接驱动程序的实例指针。
axidma是这些传入参数里面最没用的东西,不过还是保留的
对于中断系统,分为PPI(私有外设中断)、SGI(软件生成中断)、SPI(共享外设中断)
我们在中断系统中根据AXI_DMA的接收发送中断号注册两种中断
axidma作用不大,起码没有直接感觉出来,解释中只是说axidma是回调引用,通常连接到驱动程序的实例指针,所以前面有一个取地址&
*/
status = setup_intr_system(&intc, &axidma, TX_INTR_ID, RX_INTR_ID);
if (status != XST_SUCCESS) {
xil_printf("Failed intr 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++) { // 向 DDR3 的指定地址写入数据,写入的第一个地址为TX_BUFFER_BASE
tx_buffer_ptr[i] = value; // 即 0x01200000,数据值为 TEST_START_VALUE 即 0x0
value = (value + 1) & 0xFF; // 写入的地址长度为MAX_PKT_LEN,即 0x100(256)
}
//将要写入fifo的数据刷入Cache
Xil_DCacheFlushRange((UINTPTR) tx_buffer_ptr, MAX_PKT_LEN); //刷新 Data Cache,以防 Data Cache缓存数据
//开启 DMA传输
status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) tx_buffer_ptr,
MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);//DDR到外设
if (status != XST_SUCCESS) {
return XST_FAILURE;
}
//开始接收
status = XAxiDma_SimpleTransfer(&axidma, (UINTPTR) rx_buffer_ptr,
MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);//外设到DDR
if (status != XST_SUCCESS) {
return XST_FAILURE;
}
//传输结束.为接收数据刷新cache
//再次刷新 Data Cache,由于 DDR3中的数据已经更新,但 Cache 中的数据并没有更新,CPU 如果想从 DDR3 中读取数据需要刷新 Data Cache
Xil_DCacheFlushRange((UINTPTR) rx_buffer_ptr, MAX_PKT_LEN);
while (!tx_done && !rx_done && !error)
;
//传输出错
if (error) {
xil_printf("Failed test 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("Data check failed\r\n");
goto Done;
}
xil_printf("Successfully ran AXI DMA Loop\r\n");
//取消 DMA 中断
disable_intr_system(&intc, TX_INTR_ID, RX_INTR_ID);//取消 DMA 中断
Done: xil_printf("--- Exiting main() --- \r\n");
return XST_SUCCESS;
}
//检查数据缓冲区
//检查写入到 DDR3 中的数据是否正确
static int check_data(int length, u8 start_value)
{
u8 value;
u8 *rx_packet;
int i = 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中断处理函数 ,用于处理 DMA 发送中断
/*当发送中断时间发生时*/
static void tx_intr_handler(void *callback)
{
int timeout;
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出错时候,复位驱动实例,即axidma_inst
if ((irq_status & XAXIDMA_IRQ_ERROR_MASK)) {
error = 1;
XAxiDma_Reset(axidma_inst); // 如果发现是接收出现错误的中断,则使用 XAxiDma_Reset 函数复位 DMA,
timeout = RESET_TIMEOUT_COUNTER; // 并使用 XAxiDma_ResetIsDone 函数判断是否复位完成
while (timeout) {
if (XAxiDma_ResetIsDone(axidma_inst))
break;
timeout -= 1;
}
return;
}
// Tx完成 如果是发送完成的中断,则置位发送完成标志 tx_done
if ((irq_status & XAXIDMA_IRQ_IOC_MASK))
tx_done = 1;
}
//DMA RX中断处理函数 与TX中断处理函数类似
//DMA RX中断处理函数
static void rx_intr_handler(void *callback)
{
u32 irq_status;
int timeout;
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 中断系统函数 setup_intr_system
// @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,
u16 tx_intr_id, u16 rx_intr_id)
{
int status;
XScuGic_Config *intc_config;
//1、初始化中断控制器驱动
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;
}
//2、设置DMA的优先级和触发类型
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);//用于使能 DMA 发送中断和 DMA 接收中断源
XScuGic_Enable(int_ins_ptr, rx_intr_id);
//3/启用来自硬件的中断
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,
u16 rx_intr_id)
{
XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
}
4 结果分析
4.1 实验条件
定义的读取数据的起始地址:0x01200000和写入到 DDR3 中的起始地址:0x01400000
#define DDR_BASE_ADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR //0x00100000 DDR的基地址
#define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x01000000) //0x01100000
#define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000) //DMA 读取数据的起始地址0x01200000
#define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000) //写入到 DDR3 中的起始地址0x01400000
发送数据的初始值和DMA传输的数据包长度
#define TEST_START_VALUE 0x0 // 测试起始值
#define MAX_PKT_LEN 0x100 // DMA传输的数据包的长度(256)
4.2 实验结果
起始地址:
发送端
接收端:
终止地址:
发送端:
接收端:
4.3 程序验证
一定要按照这个步骤一步一步来,否则ILa调试界面会出现no stream
4.3.1 选择 Debug Configurations,采用 Debug 模式,点击 Debug
4.3.2 打开 ILA,设置触发条件 axi_dma_0_s2mm_introut 上升沿,点击运行
4.3.3 回到 SDK 的 Debug 界面,不用设置断点,点击 Resume
4.3.4 此时可以看到 ILA 已经触发,可以观察采集到的数据。
整体图如下:
将仿真结果放大后
从上图我们可以看到,只有当TVAILD和TREADY信号同时为高电平时,此时握手成功,才传输数据。否则,则保持当前数据。传输的第一个数据为03020100。这和4.2所示的实验结果也是对得上的。TKEEP信号:类似于字节修饰符,表示数据是有效的。图中,TKEEP信号为15,转化为二进制为1111,说明一次传输的四个字节都有效。比如,03020100一共四个字节,说明这四个字节传输都有效。
当TLAST为高时,表明当前数据是最后一次传输。如下图所示。
4.3.5 在串口调试工具中可以看到打印信息,中断了两次,并且测试成功
4.4 实验结论
完成在DDR端产生数据,读出DDR数据,通过DMA传输到外设fifo,并由外设通过DMA写到DDR中。
5 本实验的注意点
1、在程序验证部分,一定要按照步骤一步一步来,否则ILa调试界面会出现no stream。