15_AXI_DMA
实验任务:使用PL的AXI DMA IP核从DDR3中读取数据,并将数据写回到DDR3中。
AXI_DMA是一种用于数据传输的接口协议,用于在数字系统中实现高速数据传输。AXI代表Advanced eXtensible Interface,DMA则代表Direct Memory Access。
AXI_DMA主要用于连接处理器和外部设备之间的数据传输。它的作用是解决处理器与外设之间的数据传输速度不匹配的问题。当处理器需要将大量数据发送到外设,或者从外设接收大量数据时,传统的基于CPU的数据传输方式往往无法满足实时性要求。
使用AXI_DMA可以大大提高数据传输的效率和速度。它通过直接访问主存中的数据,避免了CPU的介入,从而实现了高速、并行的数据传输。而且,由于AXI_DMA是专门设计用于处理数据传输的协议,它提供了一系列优化的特性,如数据缓冲、数据对齐、FIFO和错误处理等,可以更好地满足系统的性能需求。
AXI DMA 简介
- AXI DMA简介
概述
AXI DMA:AXI Direct Memory Access 直接内存访问
AXI MDA 为内存和AXI4—Stream 外设之间提供了高带宽的直接内存访问
其可选的S/G功能可以将CPU从数据搬运任务中解放出来。
AXI DMA 通过AXI4—Lite接口对寄存器做一些配置和获取。
MM2S:MemoryMap to Stream ,存储器映射(AXI4—Full)到 AXI4—Stream
S2MM:Stream to MemoryMap ,AXI4—Stream到存储器映射(AXI4—Full)
-
AXI DMA 编程顺序
Direct Register Mode (见到DMA)
此模式提供了在MM2S和S2MM通道上进行简单DMA传输的配置,只需较少的FPGA资源,通过访问DMACR、源地址或者目的地址和长度寄存器发起DMA传输。当传输完成后,如果使能了产生中断输出,那么DMASR寄存器相关联的通道位会有效。
-
DMA的MM2S(存储器映射到Stream)通道的启动顺序
1.开启/使能MM2S通道
2.如果需要的话,可以使能中断
3.写一个有效的源地址到MM2S_SA寄存器;如果没有使能DRE的功能,在指 定起始地址时,需要注意字节地址对齐,哪些地址是对齐或者不对齐的, 取决于Stream流的数据位宽。
4.写传输的字节数到MM2S_LENGTH寄存器,一个长度为0的值是无效的,而一个非0值,将会决定存储器映射到Stream流的数据个数。需要注意的是,必须最后一个配置MM2S_LENGTH寄存器,而其他寄存器的配置顺序没有要求。
-
S/G模式
它把传输的基本参数,存储在内存中,这些参数就是被称为BD(Buffer Descriptor ),在工作时,通过SG接口加载和更新BD中的状态。
实验过程
系统框图如下:
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。
-
配置BD文件
- zynq配置
-
添加IP核 axi_dma
配置:
-
添加IP核axis_data_fifo
该IP核保持默认
-
添加IP核Concat
-
点击“Run connection Automation”自动连接
-
手动连接其余接口
-
运行“Generate Output Products”和“Create HDL Wrapper”,生成BitStream
代码
#include "xaxidma.h"
#include "xparameters.h"
#include "xil_exception.h"
#include "xscugic.h"
/************************** Constant Definitions *****************************/
#define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID
#define RX_INTR_ID XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID
#define TX_INTR_ID XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
#define DDR_BASE_ADDR XPAR_PS7_DDR_0_S_AXI_BASEADDR //0x00100000
#define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x1000000) //0x01100000
#define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000) //0x01200000
#define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000) //0x01400000
#define RESET_TIMEOUT_COUNTER 10000 //复位时间
#define TEST_START_VALUE 0x0 //测试起始值
#define MAX_PKT_LEN 0x100 //发送包长度
/************************** Function Prototypes ******************************/
static int check_data(int length, u8 start_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,
u16 tx_intr_id, u16 rx_intr_id);
static void disable_intr_system(XScuGic * int_ins_ptr, u16 tx_intr_id,
u16 rx_intr_id);
/************************** Variable Definitions *****************************/
static XAxiDma axidma; //XAxiDma实例
static XScuGic intc; //中断控制器的实例
volatile int tx_done; //发送完成标志
volatile int rx_done; //接收完成标志
volatile int error; //传输出错标志
/************************** Function Definitions *****************************/
int main(void)
{
int i;
int status;
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("No config found for %d\r\n", DMA_DEV_ID);
return XST_FAILURE;
}
//初始化DMA引擎
status = XAxiDma_CfgInitialize(&axidma, config);
if (status != XST_SUCCESS) {
xil_printf("Initialization failed %d\r\n", status);
return XST_FAILURE;
}
if (XAxiDma_HasSg(&axidma)) {
xil_printf("Device configured 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("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++) {
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("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");
disable_intr_system(&intc, TX_INTR_ID, RX_INTR_ID);
Done: xil_printf("--- Exiting main() --- \r\n");
return XST_SUCCESS;
}
//检查数据缓冲区
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中断处理函数
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出错
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;
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中断系统
// @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;
//初始化中断控制器驱动
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,
u16 rx_intr_id)
{
XScuGic_Disconnect(int_ins_ptr, tx_intr_id);
XScuGic_Disconnect(int_ins_ptr, rx_intr_id);
}
参考
-
正点原子 启明星ZYNQ之嵌入式SDK开发指南_V2.0.pdf