软件版本:VIVADO2017.4 操作系统:WIN10 硬件平台: ARTIX-7 系列开发板 米联客(MSXBO)论坛www.osrc.cn答疑解惑专栏开通,欢迎大家给我提供!!! 11.1 概述 本章节开始将为大家介绍MicroBlaze中高速接口的设计,这一章将首先为大家介绍DMA的使用,本章是后面DMA课程的基础,读者务必认真先阅读和学习。 本课程是设计一个最基本的DMA环路,实现DMA的环路测试,在SDK里面发送数据到DMA然后DMA在把数据发回到DDR里面,SDK读取内存地址里面的数据,对比接收的数据是否和发送出去的一致。DMA的接口部分使用了data_fifo IP链接。本课程会详细介绍创建工程的每个步骤,后面的课程将不再详细介绍创建工程的步骤。 11.2 硬件电路搭建Step1:创建一个新的vivado工程,命令为System. Step2:将第一章生成的tcl文件复制到当前文件目录中来,然后使用tcl创建一个BD文件。 Step3:点击IP图标,输入关键字DMA,添加一个DMA Step4:双击DMA的IP图标,然后如下图所示配置: Step5:将DMA的驱动时钟信号s_axi_lite_aclk(S_AXI_LITE驱动时钟),m_axi_s2mm_aclk(S_AXIS_S2MM驱动时钟),m_axi_mm2s_aclk(M_AXIS_MM2S驱动时钟)全部与clk_wiz_1的clk_out1连接。 Step6:如下图所示连接DMA的axi_resetn引脚: Step7:点击Run Connect Automation,然后在弹出来的窗口中勾选所有复选框,单击OK。 Step8:点击IP添加图标,输入关键字intc,添加一个intc. Step9:点击Run connection Automation,然后直接单击OK。 Step10:连接intc的Interrupt和MicroBlaze的INTERRUPT。 Step11:点击IP添加图标,搜索concat,添加一个Concat。 Step12:双击Concat图标,如下图所示设置: Step13:将Concat的dout引脚与INTC的intr引脚连接。 Step14:将DMA的中断输出引脚与Concat的In0和In1连接。 Step15:单击IP添加图标,搜索FIFO,添加一个AXI4_Stream Data FIFO. Step16:如下图所示连接Data FIFO. Step17:给DMA的输入、输出和中断引脚添加Debug信号,添加方法是选中要添加的信号连线,然后右单击,选择Debug命令。 Step18:点击Run Connection Automation Step19:选中调试组,点击OK。系统会自动添加调试核。 Step20:双击打开IP,可以采用深度进行设置。这里选择默认选项。 Step21:添加ila核,如下设置,点击OK。 Step22:将ila的clk与clk_wiz_1的clk_out1连接。 将ila的probe0、probe1与axi_dma_0的mm2s_introut、s2mm_introut连接。 Step23:选中top.bd,右单击然后选择Generate Output Products。 Step24:选中top.bd,右单击然后选择Create HDL Wrapper,在弹出来的窗口中直接点击OK。 Step25:添加一个名为XDC约束文件。 Step26:点击Generate Bitstream,生成BIT文件。 Step27:生成Bit文件之后,单击File-Export-Export Hardware。 Step28:单击File-Launch SDK,加载SDK。 11.3软件设计Step1:新建一个名为AXI_DMA_LOOP的SDK工程。 Step2:在我们提供的源代码中,找到sdk_src文件夹,然后复制里面的文件到src目录下。 Step3:选中SDK工程文件,右单击选择Debug as-Debug configuration。 Step4:在弹出来的新窗口中,双击下图圈出部分,然后勾选箭头所示参数 Step5:单击Apply,然后单击Debug(进行这一步之前,先给开发板上电)。 Step6:在下图所示区域找到SDK Terminal,然后单击加号图标 Step7:单击加号图标之后,再新弹出来的窗口中设置好对应的端口号和波特率,然后单击OK。 11.4 程序分析11.4.1 main.c源码的分析函数名称 | 功能 | init_intr_sys() | 对中断资源的初始化,使能中断资源。这个函数里面调用的函数是笔者封装好的初始化函数,使用起来比较方便。一般只要给出中断对象,中断号,就可以对中断进行初始化。 | DMA_Intr_Init(&AxiDma,0) | DMA初始化,第一参数是DMA的对象,第二参数是硬件ID。 | Init_Intr_System(&Intc) | 对象是中断对象 | DMA_Setup_Intr_System (&Intc,&AxiDma,TX_INTR_ID,RX_INTR_ID) | 注册中断函数,最后2个参数是中断号 | DMA_Intr_Enable(&Intc,&AxiDma) | 启动DMA传输 | XAxiDma_SimpleTransfer | 启动一次DMA发送传输 | DMA_CheckData | 对接收的数据进行校验和对比 |
表11-4-1-1 init_intr_sys函数 int init_intr_sys(void) { DMA_Intr_Init(&AxiDma,0);//initial interrupt system Init_Intr_System(&Intc); // initial DMA interrupt system Setup_Intr_Exception(&Intc); DMA_Setup_Intr_System(&Intc,&AxiDma,TX_INTR_ID,RX_INTR_ID);//setup dma interrpt system DMA_Intr_Enable(&Intc,&AxiDma); }
|
为了发送的数据是已知是确定数据,先对TxBufferPtr 发送缓冲进行初始化,初始化后用Xil_DCacheFlushRange 函数把数据全部刷到DDR中。XAxiDma_SimpleTransfer 函数为启动一次DMA接收传输。
表11-4-1-2 main.c主函数 int axi_dma_test() { int Status; TxDone = 0; RxDone = 0; Error = 0;
xil_printf("\r\n----DMA Test----\r\n");
xil_printf("PKT_LEN=%d\r\n",MAX_PKT_LEN);
//sprintf(oled_str,"PKT_LEN=%d",MAX_PKT_LEN); //print_message(oled_str,1);//oled print
//while(1) for(i = 0; i < Tries; i ++) { Value = TEST_START_VALUE + (i & 0xFF); for(Index = 0; Index < MAX_PKT_LEN; Index ++) { TxBufferPtr[Index] = Value;
Value = (Value + 1) & 0xFF; }
/* Flush the SrcBuffer before the DMA transfer, in case the Data Cache * is enabled */ Xil_DCacheFlushRange((u32)TxBufferPtr, MAX_PKT_LEN);
Status = XAxiDma_SimpleTransfer(&AxiDma,(u32) RxBufferPtr, MAX_PKT_LEN, XAXIDMA_DEVICE_TO_DMA);
if (Status != XST_SUCCESS) { return XST_FAILURE; }
Status = XAxiDma_SimpleTransfer(&AxiDma,(u32) TxBufferPtr, MAX_PKT_LEN, XAXIDMA_DMA_TO_DEVICE);
if (Status != XST_SUCCESS) { return XST_FAILURE; }
/* * Wait TX done and RX done */ while (!TxDone || !RxDone) { /* NOP */ }
success++; TxDone = 0; RxDone = 0;
if (Error) { xil_printf("Failed test transmit%s done, " "receive%s done\r\n", TxDone? "":" not", RxDone? "":" not"); goto Done; } /* * Test finished, check data */ Status = DMA_CheckData(MAX_PKT_LEN, (TEST_START_VALUE + (i & 0xFF))); if (Status != XST_SUCCESS) { xil_printf("Data check failed\r\n"); goto Done; }
} xil_printf("AXI DMA interrupt example test passed\r\n"); xil_printf("success=%d\r\n",success); //sprintf(oled_str,"success=%d",success); //print_message(oled_str,2); /* Disable TX and RX Ring interrupts and return success */ DMA_DisableIntrSystem(&Intc, TX_INTR_ID, RX_INTR_ID); Done: xil_printf("--- Exiting Test --- \r\n"); //print_message("--Exiting Test---",3); return XST_SUCCESS;
}
int init_intr_sys(void) { DMA_Intr_Init(&AxiDma,0);//initial interrupt system Init_Intr_System(&Intc); // initial DMA interrupt system Setup_Intr_Exception(&Intc); DMA_Setup_Intr_System(&Intc,&AxiDma,TX_INTR_ID,RX_INTR_ID);//setup dma interrpt system DMA_Intr_Enable(&Intc,&AxiDma);
return XST_SUCCESS; }
int main(void) { xil_printf("---DMA Test Start-----"); init_intr_sys(); //oled_fresh_en();// enable oled axi_dma_test();
while(1);
}
|
11.4.2 dma_intr.c 源码分析语句1:XAxiDma *AxiDmaInst = (XAxiDma *)Callback; 分析:这句代码是为了获取当前中断的对象。void *Callback是一个无符号的指针,传递进来的阐述可以强制转换成其他任何的对象,这里就是强制转换成 XAxiDma 对象了。
语句2:XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE);XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE); 分析:IrqStatus =XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE)这个函数获取当前中断号; XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE),这个函数是响应当前中断,通知CPU 当前中断已经被接收,并且清除中断标志位。如果中断全部正确,TxDone将被置为1表示发送中断完成。如果有错误,则复位DMA,并且设置超时参数。
表11-4-2-1 DMA_TxIntrHandler函数 /*****************************************************************************/ /* * * This is the DMA TX Interrupt handler function. * * It gets the interrupt status from the hardware, acknowledges it, and if any * error happens, it resets the hardware. Otherwise, if a completion interrupt * is present, then sets the TxDone.flag * * @param Callback is a pointer to TX channel of the DMA engine. * * @return None. * * @note None. * ******************************************************************************/ static void DMA_TxIntrHandler(void *Callback) {
u32 IrqStatus; int TimeOut; XAxiDma *AxiDmaInst = (XAxiDma *)Callback;
/* Read pending interrupts */ IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE);
/* Acknowledge pending interrupts */
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE);
/* * If no interrupt is asserted, we do not do anything */ if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
return; }
/* * If error interrupt is asserted, raise error flag, reset the * hardware to recover from the error, and return with no further * processing. */ if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
Error = 1;
/* * Reset should never fail for transmit channel */ XAxiDma_Reset(AxiDmaInst);
TimeOut = RESET_TIMEOUT_COUNTER;
while (TimeOut) { if (XAxiDma_ResetIsDone(AxiDmaInst)) { break; }
TimeOut -= 1; }
return; }
/* * If Completion interrupt is asserted, then set the TxDone flag */ if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
TxDone = 1; } } |
接收中断函数的原理和发送一样 语句3: XAxiDma *AxiDmaInst = (XAxiDma *)Callback 分析:这句代码是为了获取当前中断的对象。void *Callback是一个无符号的指针,传递进来的阐述可以强制转换成其他任何的对象,这里就是强制转换成 XAxiDma 对象了。 语句4:IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA); XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA) 分析:IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA),这个函数是获取当前中断号。 XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA),这个函数是响应当前中断,通知CPU 当前中断已经被接收,并且清除中断标志位。如果中断全部正确,RxDone将被置为1表示接收中断完成。 如果有错误,则复位DMA,并且设置超时参数
表11-4-2-2 DMA_RxIntrHandler函数 /*****************************************************************************/ /* * * This is the DMA RX interrupt handler function * * It gets the interrupt status from the hardware, acknowledges it, and if any * error happens, it resets the hardware. Otherwise, if a completion interrupt * is present, then it sets the RxDone flag. * * @param Callback is a pointer to RX channel of the DMA engine. * * @return None. * * @note None. * ******************************************************************************/ static void DMA_RxIntrHandler(void *Callback) { u32 IrqStatus; int TimeOut; XAxiDma *AxiDmaInst = (XAxiDma *)Callback;
/* Read pending interrupts */ IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);
/* Acknowledge pending interrupts */ XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);
/* * If no interrupt is asserted, we do not do anything */ if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) { return; }
/* * If error interrupt is asserted, raise error flag, reset the * hardware to recover from the error, and return with no further * processing. */ if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
Error = 1;
/* Reset could fail and hang * NEED a way to handle this or do not call it?? */ XAxiDma_Reset(AxiDmaInst);
TimeOut = RESET_TIMEOUT_COUNTER;
while (TimeOut) { if(XAxiDma_ResetIsDone(AxiDmaInst)) { break; }
TimeOut -= 1; }
return; } /* * If completion interrupt is asserted, then set RxDone flag */ if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
RxDone = 1; } } |
表11-4-2-3 DMA_CheckData函数 /*****************************************************************************/ /* * * This function checks data buffer after the DMA transfer is finished. * * We use the static tx/rx buffers. * * @param Length is the length to check * @param StartValue is the starting value of the first byte * * @return * - XST_SUCCESS if validation is successful * - XST_FAILURE if validation is failure. * * @note None. * ******************************************************************************/ int DMA_CheckData(int Length, u8 StartValue) { u8 *RxPacket; int Index = 0; u8 Value;
RxPacket = (u8 *) RX_BUFFER_BASE; Value = StartValue;
/* Invalidate the DestBuffer before receiving the data, in case the * Data Cache is enabled */ #ifndef __aarch64__ Xil_DCacheInvalidateRange((u32)RxPacket, Length); #endif
for(Index = 0; Index < Length; Index++) { if (RxPacket[Index] != Value) { xil_printf("Data error %d: %x/%x\r\n", Index, RxPacket[Index], Value);
return XST_FAILURE; } Value = (Value + 1) & 0xFF; }
return XST_SUCCESS; } |
11.4.3 dam_intr.h 文件分析一般把DMA相关变量、常量、函数的声明或者定义放到头文件中,dam_intr.h比较关键的参数有
TX_BUFFER_BASE :定义了DMA发送缓存的基地址 RX_BUFFER_BASE :定义了DMA接收缓存的基地址 MAX_PKT_LEN :表示每一包数据传输的长度 NUMBER_OF_TRANSFERS :用在连续测试的时候的测试次数 TEST_START_VALUE :用于测试的起始参数
int DMA_CheckData(int Length, u8 StartValue); int DMA_Setup_Intr_System(XIntc * IntcInstancePtr,XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId); int DMA_Intr_Enable(XIntc * IntcInstancePtr,XAxiDma *DMAPtr); int DMA_Intr_Init(XAxiDma *DMAPtr,u32 DeviceId);
|
表11-4-3-1 dam_intr.h /* * * www.osrc.cn * www.milinker.com * copyright by liyang mi lian dian zi www.osrc.cn */ #ifndef DMA_INTR_H #define DMA_INTR_H #include "xaxidma.h" #include "xparameters.h" #include "xil_exception.h" #include "xdebug.h" #include "xintc.h"
/************************** Constant Definitions *****************************/ /* * Device hardware build related constants. */ #define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID #define INTC_DEV_ID XPAR_AXI_INTC_0_DEVICE_ID #define DDR_BASE_ADDR 0x80000000
#define MEM_BASE_ADDR (DDR_BASE_ADDR+0x01000000)
#define RX_INTR_ID XPAR_INTC_0_AXIDMA_0_S2MM_INTROUT_VEC_ID #define TX_INTR_ID XPAR_INTC_0_AXIDMA_0_MM2S_INTROUT_VEC_ID
#define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000) #define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000) #define RX_BUFFER_HIGH (MEM_BASE_ADDR + 0x004FFFFF)
/* Timeout loop counter for reset */ #define RESET_TIMEOUT_COUNTER 10000 /* test start value */ #define TEST_START_VALUE 0xC /* * Buffer and Buffer Descriptor related constant definition */ #define MAX_PKT_LEN 2047//4MB /* * transfer times */ #define NUMBER_OF_TRANSFERS 5
extern volatile int TxDone; extern volatile int RxDone; extern volatile int Error;
int DMA_CheckData(int Length, u8 StartValue); int DMA_Setup_Intr_System(XIntc * IntcInstancePtr,XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId); int DMA_Intr_Enable(XIntc * IntcInstancePtr,XAxiDma *DMAPtr); int DMA_Intr_Init(XAxiDma *DMAPtr,u32 DeviceId); #endif |
11.5 测试结果Step1:在VIVADO工程中点击Open Target 然后点击Auto Connect(前面必须先启动SDK) Step2:连接成功后入下图 Step4: 点击添加接收内存部分地址用于观察内存中的数据,地址为 0X81100000 为了观察数据,需要设置断点,如图所示: 然后重新下载SDK程序,让收发程序先跑一次,可以看到第一个数据是0X0C 后面是依次加1 |