ZYNQ之DMA基本用法

DMA 环路测试
涉及到高速数据传输时,DMA就显得非常重要了,本文的DMA主要是对PL侧的AXI DMA核进行介绍(不涉及PS侧的DMA控制器)。AXI DMA的用法基本是:PS通过AXI-lite向AXI DMA发送指令,AXI DMA通过HP通路和DDR交换数据,PL通过AXI-S读写DMA的数据。

实验思路
首先设计一个最基本的DMA环路

实验思路是:首先PS通过AXI-lite配置DMA的工作模式,然后,PS将数据写入DDR,再然后,PS控制DMA读出之前写入的数据,将数据流写入FIFO(读完后DMA会向PS发送中断),再然后,PS控制DMA将FIFO的数据流再通过AXI总线写回DDR(写完后DMA会向PS发送中断),PS判断写入的数据和读出的数据是否一致,完成实验

搭建硬件环境
1.新建工程、原理图文件等
2.加入实验需要的IP核

3.配置PS核相应的参数

设置PS板上输入时钟,设置PS输出时钟为100M

设置板上DDR型号

添加HP高速接口,用于DMA和DDR控制器间通信

添加中断
最后添加串口设备(用于调试)、设置正确的BANK电压

4.配置DMA核相关参数

不使能高性能模式和精简模式
5.点击自动生成模块和自动布线
6.将中断信号合并送到PS中

7.将FIFO加入到数据环路中,完成后效果如下

红色标记为数据的流向,其余两个标记线分别为100M时钟和复位信号
8.进行以下操作后,生成bit文件
Gerate Output products、Create wrappers、Generate Bitstream

进入SDK环境
main.c文件:
定义Tries为测试次数,TxBufferPtr的地址为DMA发送的数据到设备的地址,RxBufferPtr地址则是设备发给DMA的存储地址,MAX_PKT_LEN为一次测试的测试长度,数据从0x55开始递增。
一次测试的过程:
PS的 for 循环产生数据—>PS的数据写入DDR—>PS配置DMA接收通道—>PS配置DMA发送通道(数据开始发送)—>发送的数据经过FIFO回到DMA—>DMA中断到达后观察数据—>进行下一次测试

#include "dma_intr.h"
#include "sys_intr.h"

static  XScuGic Intc; //GIC中断控制器
static  XAxiDma AxiDma;

int Tries = 6;
int i;
int Index;
u8 *TxBufferPtr= (u8 *)TX_BUFFER_BASE;
u8 *RxBufferPtr= (u8 *)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);

    for(i = 0; i < Tries; i ++)
    {
        Value = 0x55 + (i & 0xFF);
        for(Index = 0; Index < MAX_PKT_LEN; Index ++) {
                TxBufferPtr[Index] = Value;

                Value = (Value + 1) & 0xFF;
        }

        Xil_DCacheFlushRange((u32)TxBufferPtr, MAX_PKT_LEN);//数据刷新到DDR中

        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;}

        while (!TxDone || !RxDone) { }//等待收发中断都到达

        TxDone = 0;
        RxDone = 0;
        if (Error) {return XST_FAILURE;}

        Xil_DCacheInvalidateRange((u32)RxBufferPtr, MAX_PKT_LEN);//刷新cache,观察DDR的最新数据
    }

    DMA_DisableIntrSystem(&Intc, TX_INTR_ID, RX_INTR_ID);//失能DMA中断
    xil_printf("--- Exiting Test --- \r\n");
    return XST_SUCCESS;
}

int main(void)
{
    DMA_Intr_Init(&AxiDma,0);//DMA初始化
    Init_Intr_System(&Intc); //系统初始化
    Setup_Intr_Exception(&Intc);//使能硬件中断
    DMA_Setup_Intr_System(&Intc,&AxiDma,TX_INTR_ID,RX_INTR_ID);//注册DMA收发中断
    DMA_Intr_Enable(&Intc,&AxiDma);//使能系统中断

    axi_dma_test();
}

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 RESET_TIMEOUT_COUNTER   10000//超时计数值
#define MAX_PKT_LEN     2047 //一次测试的长度

extern volatile int TxDone;
extern volatile int RxDone;
extern volatile int Error;

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);
void DMA_DisableIntrSystem(XScuGic * IntcInstancePtr,u16 TxIntrId, u16 RxIntrId);
#endif

dma_intr.c文件:
DMA配置的相关函数

#include "dma_intr.h"

volatile int TxDone;
volatile int RxDone;
volatile int Error;

/*****************************************************************************/
/**
*
* 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.
*
* @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);

    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;
    }

    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;
}

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_ */

sys_intr.c文件:

#include "sys_intr.h"
void Setup_Intr_Exception(XScuGic * IntcInstancePtr)
{
    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;
    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;
}

调试观察现象

此处设置断点,此时收发中断均已到达,可以直接观察TXBUFFER里的数据的变化,从右侧表格可以看出TXBUFFER里的数据已经更新为0x55开头

虽然接收的数据在上一个断点就以及存在DDR中了,但是要经过一个刷新cache的函数Xil_DCacheInvalidateRange,才能直接观察到数据的变化,在图中可以看到RXBUFFER的数据和TXBUFFER的数据完全一致,本次测试则成功结束

————————————————
版权声明:本文为CSDN博主「long_fly」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/long_fly/article/details/79702222

Zynq DMA(Direct Memory Access)是一个针对Xilinx的Zynq系列芯片的外设模块,用于实现高速数据传输Zynq芯片集成了FPGA和ARM Cortex-A9处理器,DMA模块位于Processing System(PS)和Programmable Logic(PL)之间,提供了高性能的数据传输通道。 在Linux操作系统的支持下,Zynq DMA可以实现快速的数据传输和处理。Linux提供了对DMA控制器的驱动程序,使得用户可以在操作系统上使用DMA功能。用户可以通过在Linux上进行编程,使用DMA来实现高效的数据传输和处理,从而提高系统的性能。 使用Zynq DMA的好处是它可以通过减少CPU负载和系统总线使用来提高系统性能。由于DMA是一种直接从外设到内存的数据传输方式,可以在不占用CPU资源的情况下完成数据传输。因此,使用Zynq DMA可以提高系统的并发性和效率。 在Linux上使用Zynq DMA需要进行一些配置和编程。用户需要配置DMA引擎及其相关寄存器,以确保正确的数据传输。然后,用户可以使用Linux上的API或驱动程序来访问DMA模块,并指定数据传输的方向、缓冲区等参数。通过这种方式,用户可以灵活地控制DMA的操作,实现高效的数据传输和处理。 总而言之,Zynq DMA PS-PL Linux是一种在Zynq芯片上使用DMA模块实现高速数据传输的方法。通过在Linux操作系统上进行配置和编程,用户可以灵活地控制DMA的操作,提高系统的性能和效率。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值