10.3 AXI DMA
在天上飞的TTTTT
2023年02月13日 23:02
收录于文集
共20篇

一、本节任务&基本概念

参考资料:

        "AXI DMA v7.1 LogiCORE IP Product Guide Vivado Design Suite PG021 June 14, 2019"

1.DMA简介

       DMA 是所有现代计算机的重要特色,它允许不同速度的硬件设备进行沟通,而不需要依于中央处理器的大量中断负载。否则,中央处理器需要从来源把每一片段的数据复制到寄存器,然后把它们再次写回到新的地方。在这个时间里,中央处理器就无法执行其它的任务。 DMA是用硬件实现存储器与存储器之间或存储器与 I/O 设备之间直接进行高速数据传输。使用 DMA时,CPU向DMA控制器发出一个存储传输请求,这样当DMA控制器在传输的时候,CPU 执行其它操作, 传输操作完成时DMA以中断的方式通知CPU。

2.AXI DMA

        ZYNQ 提供了两种 DMA,一种是集成在 PS 中的硬核 DMA,另一种是 PL 中使用的软核 AXI DMA IP。 此次我们使用XILINX官方提供的 AXI DMA IP实现DMA。

        AXI DMA IP在内存(AXI4 内存映射)和AXI4-Stream外设间提供了高带宽的直接内存访问,其scatter/gather功能可以让CPU从数据搬运中脱离出来,执行其他工作。

        从Block Diagram中可以看出AXI DMA IP实现了AXI4的内存映射到AXI4 Stream的转换(上方的MM2S,MemoryMap to Stream,和下方的S2MM,Stream to MemoryMap),在S/G模式下中间的功能才可用。

一个典型的应用连接

时钟

        有四个时钟接口,可以使用同步或异步时钟的模式,如果选用同步模式,mm2s,s2mm,sg都要使用同一个时钟源,axi-lite使用更慢的时钟;如果选用异步模式,axi-lite时钟必须小于等于sg,sg小于等于mm2s和s2mm

复位

        复位需要将复位信号激活最少16个最慢的时钟周期,即与axi-lite时钟同步。

3.Direct Register Mode (Simple DMA)

        该模式提供了在MM2S和S2MM信道上进行简单的DMA传输的配置,只需较少的FPGA资源。通过访问DMACR,the Source or Destination  Address,the Length registers来进行传输。如果开启中断,当传输完成后,DMASR相关联的中断会激活。

MM2S的DMA设置

  1. 使能MM2S信道,向MM2S_DMACR.RS寄存器写1;

  2. 使能中断(如果有需求),MM2S_DMACR.IOC_IrqEn 和 MM2S_DMACR.Err_IrqEn寄存器写1;

  3. 写一个有效的源地址到MM2S_SA寄存器,如果没有配置Data Re-Alignment(DRE),在指定起始地址时要注意对其,是否对其取决于stream流宽度。例如,存储器映射数据位宽为32位(4字节),那么在给地址的之后就要每次+4,如0x00,0x04,0x08,0x0c...如果启用了DRE功能且stream位宽小于128,那么地址可以任意字节偏移;

  4. 写需要传输的字节数到MM2S_LENGTH寄存器。字节数为0无效,非0值会决定从存储器映射到Stream的数据长度,该寄存器必须最后才配置,其他寄存器的配置顺序没有要求。

S2MM的DMA设置

        与MM2S类似

4.Scatter/Gather Mode

        该模式将传输的基本参数BD(Buffer Descriptor)储存在内存中,在工作时通过SG接口加载、更新BD。这里不做详细介绍,参考:https://blog.csdn.net/qq_43541062/article/details/126180031

5.设计(使用)流程

        XILINX文档也给我们提供了在VIVADO中IP核的配置流程。实际上通过设置这些参数,就不用像上面说的对寄存器进行读写操作。

  • Enable Asynchronous Clocks 使能异步时钟

  • Enable Scatter Gather Engine 使能SG模式

  • Enable Micro DMA 使能MicroDMA,会产生高优化的DMA,资源占用小,但性能更差、

  • Width of Buffer Length Register 在简单DMA模式中,用于指示传输数据的数量,等于2^Length Width

  • Address Width (32 - 64)  设置地址位宽,范围为32~64

  • Enable MultiChannel DMA 使能多通道DMA

  • Enable Read Channel 和 Enable Write Channel 使能读、写通道,需要的话都要勾选

  • Number of Channels 信道数量

  • Memory Map Data Width 存储器映射数据位宽

  • Stream Data Width 流数据位宽

  • Max Burst Size突发传输大小

  • Allow Unaligned Transfers 允许非对其传输

二、程序设计

1.VIVADO程序设计

        加入PS IP核、AXI DMA IP、AXI Stream FIFO IP。注意PS要添加HP从机端口和中断端口。

        配置AXI DMA

        完成后互联,这里还需要自己连接S2MM和MM2S。

2.VITIS程序设计

        导入简单传输中断的例程,这个例程实际上完成了一次DMA循环,在内存中划定了一片发送缓冲区和接收缓冲区,并在发送缓冲区写入给定的数据后开始发送,同时接收,实现一次数据的DMA搬运。发送、传输完成后会触发中断。

①宏定义与声明

代码块
clike
自动换行
复制代码
// Created by:          正点原子
#include "xaxidma.h"
#include "xparameters.h"
#include "xil_exception.h"
#include "xscugic.h"
#define DMA_DEV_ID          XPAR_AXIDMA_0_DEVICE_ID		//AXI DMA设备ID
#define RX_INTR_ID          XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID //接收中断ID(Stream到存储器映射)
#define TX_INTR_ID          XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID //发射中断ID(存储器映射到Stream)
#define INTC_DEVICE_ID      XPAR_SCUGIC_SINGLE_DEVICE_ID //中断设备ID
#define DDR_BASE_ADDR       XPAR_PS7_DDR_0_S_AXI_BASEADDR   //0x00100000 内存基地址
#define MEM_BASE_ADDR       (DDR_BASE_ADDR + 0x1000000)     //0x01100000 基地址+2MB
#define TX_BUFFER_BASE      (MEM_BASE_ADDR + 0x00100000)    //0x01200000 储存基地址+128KB 发射BUFFER基地址
#define RX_BUFFER_BASE      (MEM_BASE_ADDR + 0x00300000)    //0x01400000 储存基地址+384KB 接收BUFFER基地址
#define RESET_TIMEOUT_COUNTER   10000    //复位时间
#define TEST_START_VALUE        0x0      //测试起始值
#define MAX_PKT_LEN             0x100    //发送包长度256字节

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

static XAxiDma axidma;     //XAxiDma实例
static XScuGic intc;       //中断控制器的实例
volatile int tx_done;      //发送完成标志
volatile int rx_done;      //接收完成标志
volatile int error;        //传输出错标志
复制成功

②初始化DMA器件

代码块
clike
自动换行
复制代码

    int status;
    u8 value;
    u8 *tx_buffer_ptr;
    u8 *rx_buffer_ptr;
    XAxiDma_Config *config;
	 //指向BUFFER的指针
    tx_buffer_ptr = (u8 *) TX_BUFFER_BASE;
    rx_buffer_ptr = (u8 *) RX_BUFFER_BASE;

    xil_printf("\r\n--- Entering main() --- \r\n");
	 //查找DMA设备ID
    config = XAxiDma_LookupConfig(DMA_DEV_ID);
    if (!config) {
        xil_printf("No config found for %d\r\n", DMA_DEV_ID);
        return XST_FAILURE;
    }

    //初始化DMA
    status = XAxiDma_CfgInitialize(&axidma, config);
    if (status != XST_SUCCESS) {
        xil_printf("Initialization failed %d\r\n", status);
        return XST_FAILURE;
    }
复制成功

③设置中断

代码块
clike
自动换行
复制代码
//建立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;
    }

    //设置优先级和触发类型
    //优先级为160?上升沿有效
    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;
    }



    //arm异常程序处理函数三驾马车
    Xil_ExceptionInit();
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
            (Xil_ExceptionHandler) XScuGic_InterruptHandler,
            (void *) int_ins_ptr);
    Xil_ExceptionEnable();

    //使能DMA中断
    XScuGic_Enable(int_ins_ptr, tx_intr_id);
    XScuGic_Enable(int_ins_ptr, rx_intr_id);
    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;
}
复制成功

④中断处理函数

代码块
clike
自动换行
复制代码

//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出错
    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;

    xil_printf("Tx is done\r\n");
}

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

    xil_printf("Rx is done\r\n");
}
复制成功

⑤传输部分

代码块
clike
自动换行
复制代码
	//在发送区写入需要发送的数据
	 for (i = 0; i < MAX_PKT_LEN; i++) {
        tx_buffer_ptr[i] = value;
        value = (value + 1) & 0xFF;
    }

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

    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;
复制成功

3.上板验证

       在数据传输部分打上断点,然后Debug As上板子。

        打开Memory Monitors可以观察对应地址数据的变化。

三、总结

        1.用了AXI DMA 但没有完全用,如果使用PS自带的硬核DMA呢?

        2.对于AXI-Stream的传输流程并不了解,这只能再看看datasheet了

        3.之前对于中断处理函数那块不清楚,我还在想中断处理函数执行结束后不应该清除中断状态吗,和同学、正点原子客服讨论之后又看了ug585,感觉应该是XAxiDma_IntrAckIrq(axidma_inst, irq_status, XAXIDMA_DMA_TO_DEVICE);这个函数实现了中断状态清除。

        开学倒计时,计划这周边复习器件物理、模寄,一边整OV5640 LCD显示,正点原子SDK开发手册里面给了一个VDMA的例程,可以参考,另外我想的是做一个简化版的,如果不使用DMA,在PL里面做一个data_reg[800*480:0][23:0]用来储存一帧的数据呢?(会不会太大了?)