AXI DMA详解与应用篇 | 第二讲、AXI DMA工程搭建及SDK代码分析

本文转载自:  根究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一致性问题分析

最新文章

最新文章