本文转载自: 根究FPGA
在上一篇中着重讲解了DMA的含义和AXI_DMA_IP,本次的重点就是搭建一个AXI_DMA环路工程,并从C语言角度分析其SDK代码
一、AXI_DMA工程设计
在工程设计中,DMA一般与产生数据或需求数据的IP相连,该IP core可以是带有AXI_Stream接口的高速AD或DA IP核,实验中使用AXI-Stream Data Fifo IP核作为该类IP进行DMA环回实验:
处理器通过M_AXI_GP0接口和AXI_DMA通信,以设置、启动和监控数据传输。数据传输通过S_AXI_HP0接口。
BD框图:
核心部分为:
在处理器中含有之前的:
在处理器系统中,PL侧的DMA通过HP接口从DDR中读取数据,AXI DMA核作为AXIS Data FIFO和AXI4内存映射之间提供高宽带直接存储访问。
二、SDK代码分析
在工程设计中,PL侧配置好IP core之后生成含有配置参数的比特流文件,将其导出到SDK中,PS侧通过对PL侧配置参数的查询,执行IP核的配置。
注意:位于PL侧的属于PS的可配置模块的配置是由PL完成的,但是执行是由PS实现的!
代码分析:
#include "xaxidma.h" #include "xparameters.h" #include "xil_exception.h" #include "xscugic.h" /************************** Constant Definitions *****************************/ #define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID //位于PL侧的DMA,xparameters.h // DMA接收与发送通道的中断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 //定义DDR的基地址 #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 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的配置选项生成的参数 */ 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)) { 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 (int i = 0; i < MAX_PKT_LEN; i++) { tx_buffer_ptr[i] = value; value = (value + 1) & 0xFF; } //将要写入fifo的数据刷入Cache 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; } //传输结束为接收数据刷新cache 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出错时候,复位驱动实例,即axidma_inst 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 Cache一致性问题分析