前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AXI DMA详解与应用篇 | 第二讲、AXI DMA工程搭建及SDK代码分析

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

作者头像
根究FPGA
发布2020-06-30 11:18:06
6.8K0
发布2020-06-30 11:18:06
举报
文章被收录于专栏:根究FPGA根究FPGA

本次的重点就是搭建一个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实现的!

代码分析:

代码语言:javascript
复制
#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);
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-03-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 根究FPGA 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、AXI_DMA工程设计
  • 二、SDK代码分析
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档