软件环境:vivado 2017.4 硬件平台:XC7Z020
讲道理,AXI_DMA还是很关键的,后面PS与PL之间的数据传递,很大程度上都要依赖AXI_DMA,手册PG021,走起。
可以看到AXI_DMA内部的整体结构和外部的连接关系, AXI_DMA主要有两种模式,分别是Direct Register Mode和Scatter/Gather Mode,其中Direct Register Mode包含DMA的基本功能,源地址和目标地址写入寄存器以后,传输长度一旦设定,便直接启动了DMA的传输,要求存储器有连续的地址空间,而Scatter/Gather Mode不需要连续的存储器空间。另外,DMA的多通道模式,也只有在Scatter/Gather Mode下才有。当前仅讨论Direct Register Mode模式。
接下来说一下模块对外连接线意义,左边中间AXI4_LITE实际模块中是S_AXI_LITE主要是用来配置DMA寄存器的,AXI4_Memory Map Read和AXI4_Memory Map Write实际模块中是M_AXI_MM2S和M_AXI_S2MM是PS端的接口,用于与内存之间的交互,AXI4_Stream Master(MM2S)和AXI4_Stream Slave(S2MM)是PL端的接口,用于逻辑端数据流的传输。
Direct Register Mode寄存器含义及偏移量如下所示。
当前AXI_DMA_LOOP测试原理如下图所示。
Vivado 2017.4 Create Block Design后,按照上图搭建模块并连接完毕后如下所示。
AXI_DMA设置如下。
Generate Bitstream,Export Hardware(注意勾选include biestream),最后launch SDK进行测试。 测试是按照突发1024*4bytes发送的,MM2S到AXI_DMA再到FIFO再回AXI_DMA最后通过S2MM送进DDR,比对发出的数和收到的数是否一致。
main.c
#include "dma_intr.h"
#include "sys_intr.h"
static XScuGic Intc; //GIC
static XAxiDma AxiDma;
volatile u32 success;
int Tries = NUMBER_OF_TRANSFERS;
int i;
int Index;
u32 *TxBufferPtr= (u32 *)TX_BUFFER_BASE;
u32 *RxBufferPtr=(u32 *)RX_BUFFER_BASE;
u8 Value;
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);
//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*sizeof(u32));
Status = XAxiDma_SimpleTransfer(&AxiDma,(u32) RxBufferPtr,
MAX_PKT_LEN*sizeof(u32), XAXIDMA_DEVICE_TO_DMA);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
Status = XAxiDma_SimpleTransfer(&AxiDma,(u32) TxBufferPtr,
MAX_PKT_LEN*sizeof(u32), XAXIDMA_DMA_TO_DEVICE);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
Xil_DCacheFlushRange((u32)RxBufferPtr, MAX_PKT_LEN*sizeof(u32));
/*
* 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);
/* 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");
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);
}
int main(void)
{
init_intr_sys();
axi_dma_test();
}
sys_intr.c
#include "sys_intr.h"
void Setup_Intr_Exception(XScuGic * IntcInstancePtr)
{
/* Enable interrupts from the hardware */
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
(void *)IntcInstancePtr);
Xil_ExceptionEnable();
}
int Init_Intr_System(XScuGic * IntcInstancePtr)
{
int Status;
XScuGic_Config *IntcConfig;
/*
* Initialize the interrupt controller driver so that it is ready to
* use.
*/
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
if (NULL == IntcConfig) {
return XST_FAILURE;
}
Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
IntcConfig->CpuBaseAddress);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
return XST_SUCCESS;
}
sys_intr.h
#ifndef SYS_INTR_H_
#define SYS_INTR_H_
#include "xparameters.h"
#include "xil_exception.h"
#include "xdebug.h"
#include "xscugic.h"
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
int Init_Intr_System(XScuGic * IntcInstancePtr);
void setup_Intr_Exception(XScuGic * IntcInstancePtr);
#endif /* SYS_INTR_H_ */
dma_intr.c
#include "dma_intr.h"
volatile int TxDone;
volatile int RxDone;
volatile int Error;
/*****************************************************************************/
/*
*
* 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)
{
u32 *RxPacket;
int Index = 0;
u8 Value;
RxPacket = (u32 *) RX_BUFFER_BASE;
Value = StartValue;
/* Invalidate the DestBuffer before receiving the data, in case the
* Data Cache is enabled
* 如果启用了数据高速缓存,则在接收数据之前使DestBuffer无效
*/
#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;
}
/*****************************************************************************/
/**
*
* This function disables the interrupts for DMA engine.
*
* @param IntcInstancePtr is the pointer to the INTC component instance
* @param TxIntrId is interrupt ID associated w/ DMA TX channel
* @param RxIntrId is interrupt ID associated w/ DMA RX channel
*
* @return None.
*
* @note None.
*
******************************************************************************/
void DMA_DisableIntrSystem(XScuGic * IntcInstancePtr,
u16 TxIntrId, u16 RxIntrId)
{
#ifdef XPAR_INTC_0_DEVICE_ID
/* Disconnect the interrupts for the DMA TX and RX channels */
XIntc_Disconnect(IntcInstancePtr, TxIntrId);
XIntc_Disconnect(IntcInstancePtr, RxIntrId);
#else
XScuGic_Disconnect(IntcInstancePtr, TxIntrId);
XScuGic_Disconnect(IntcInstancePtr, RxIntrId);
#endif
}
/*****************************************************************************/
/*
*
* 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.回调是指向DMA引擎的TX通道的指针。
*
* @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;
}
}
/*****************************************************************************/
/*
*
* 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;
}
}
/*****************************************************************************/
/*
*
* This function setups the interrupt system so interrupts can occur for the
* DMA, it assumes INTC component exists in the hardware system.
*
* @param IntcInstancePtr is a pointer to the instance of the INTC.
* @param AxiDmaPtr is a pointer to the instance of the DMA engine
* @param TxIntrId is the TX channel Interrupt ID.
* @param RxIntrId is the RX channel Interrupt ID.
*
* @return
* - XST_SUCCESS if successful,
* - XST_FAILURE.if not succesful
*
* @note None.
*
******************************************************************************/
int DMA_Setup_Intr_System(XScuGic * IntcInstancePtr,XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId)
{
int Status;
XScuGic_SetPriorityTriggerType(IntcInstancePtr, TxIntrId, 0xA0, 0x3);
XScuGic_SetPriorityTriggerType(IntcInstancePtr, RxIntrId, 0xA0, 0x3);
/*
* Connect the device driver handler that will be called when an
* interrupt for the device occurs, the handler defined above performs
* the specific interrupt processing for the device.
*/
Status = XScuGic_Connect(IntcInstancePtr, TxIntrId,
(Xil_InterruptHandler)DMA_TxIntrHandler,
AxiDmaPtr);
if (Status != XST_SUCCESS) {
return Status;
}
Status = XScuGic_Connect(IntcInstancePtr, RxIntrId,
(Xil_InterruptHandler)DMA_RxIntrHandler,
AxiDmaPtr);
if (Status != XST_SUCCESS) {
return Status;
}
XScuGic_Enable(IntcInstancePtr, TxIntrId);
XScuGic_Enable(IntcInstancePtr, RxIntrId);
return XST_SUCCESS;
}
int DMA_Intr_Enable(XScuGic * IntcInstancePtr,XAxiDma *DMAPtr)
{
/* Disable all interrupts before setup */
XAxiDma_IntrDisable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrDisable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DEVICE_TO_DMA);
/* Enable all interrupts */
XAxiDma_IntrEnable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DMA_TO_DEVICE);
XAxiDma_IntrEnable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DEVICE_TO_DMA);
return XST_SUCCESS;
}
int DMA_Intr_Init(XAxiDma *DMAPtr,u32 DeviceId)
{
int Status;
XAxiDma_Config *Config=NULL;
Config = XAxiDma_LookupConfig(DeviceId);
if (!Config) {
xil_printf("No config found for %d\r\n", DeviceId);
return XST_FAILURE;
}
/* Initialize DMA engine */
Status = XAxiDma_CfgInitialize(DMAPtr, Config);
if (Status != XST_SUCCESS) {
xil_printf("Initialization failed %d\r\n", Status);
return XST_FAILURE;
}
if(XAxiDma_HasSg(DMAPtr)){
xil_printf("Device configured as SG mode \r\n");
return XST_FAILURE;
}
return XST_SUCCESS;
}
dma_intr.h
#ifndef DMA_INTR_H
#define DMA_INTR_H
#include "xaxidma.h"
#include "xparameters.h"
#include "xil_exception.h"
#include "xdebug.h"
#include "xscugic.h"
/************************** Constant Definitions *****************************/
/*
* Device hardware build related constants.
*/
#define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID
#define MEM_BASE_ADDR 0x01000000
#define RX_INTR_ID XPAR_FABRIC_AXI_DMA_0_S2MM_INTROUT_INTR
#define TX_INTR_ID XPAR_FABRIC_AXI_DMA_0_MM2S_INTROUT_INTR
#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 1023//4MB
/*
* transfer times
*/
#define NUMBER_OF_TRANSFERS 100000
extern volatile int TxDone;
extern volatile int RxDone;
extern volatile int Error;
int DMA_CheckData(int Length, u8 StartValue);
int DMA_Setup_Intr_System(XScuGic * IntcInstancePtr,XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId);
int DMA_Intr_Enable(XScuGic * IntcInstancePtr,XAxiDma *DMAPtr);
int DMA_Intr_Init(XAxiDma *DMAPtr,u32 DeviceId);
#endif
测试结果如下所示。
通过vivado实时抓取波形,可以看到发送与接收见有略微延迟,传送数据正确。
把需要注意的地方在这里表述一下:
1.通过XScuGic_SetPriorityTriggerType(IntcInstancePtr, TxIntrId, 0xA0, 0x3);配置写通道、优先级、触发方式;
2.通过XScuGic_SetPriorityTriggerType(IntcInstancePtr, RxIntrId, 0xA0, 0x3);配置读通道、优先级、触发方式;
3.通过XScuGic_Connect(IntcInstancePtr, TxIntrId,(Xil_InterruptHandler)DMA_TxIntrHandler,AxiDmaPtr);链接写中断;
4.通过XScuGic_Connect(IntcInstancePtr, RxIntrId,(Xil_InterruptHandler)DMA_RxIntrHandler,AxiDmaPtr);链接读中断;
5.通过XScuGic_Enable(IntcInstancePtr, TxIntrId);使能写中断;
6.通过XScuGic_Enable(IntcInstancePtr, RxIntrId);使能读中断;
7.通过XAxiDma_IntrDisable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DMA_TO_DEVICE);关断DMA写通道;
8.通过XAxiDma_IntrDisable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);关断DMA读通道;
9.通过XAxiDma_IntrEnable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DMA_TO_DEVICE);使能DMA写通道;
10.通过XAxiDma_IntrEnable(DMAPtr, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);使能DMA读通道;
11.通过Xil_DCacheFlushRange((u32)TxBufferPtr, MAX_PKT_LEN*sizeof(u32));刷新DCache,以防DCache被使能;
12.通过XAxiDma_SimpleTransfer(&AxiDma,(u32) RxBufferPtr,MAX_PKT_LEN*sizeof(u32), XAXIDMA_DEVICE_TO_DMA);启动读取,实际是给长度寄存器写一个大于0的数,就直接开启读了;
13.通过XAxiDma_SimpleTransfer(&AxiDma,(u32) TxBufferPtr,MAX_PKT_LEN*sizeof(u32), XAXIDMA_DMA_TO_DEVICE);启动发送,实际是给长度寄存器写一个大于0的数,就自动开始发送了;
14.最最最最最重要的一点是,u32 *TxBufferPtr= (u32 *)TX_BUFFER_BASE;如果你指针和传送的最小单位是8 bits也就是1个字节,那网上的例子太多了,一搜一大堆,但是如果你是按32 bits发送和接收,指针肯定也是按u32,那切记切记,在所有涉及TxBufferPtr、RxBufferPtr前面,都要改成u32,在所有MAX_PKT_LEN的后面,都要*sizeof(u32)跟指针的u32保持一致。