侧边栏壁纸
    • 累计撰写 303 篇文章
    • 累计收到 529 条评论
    【FPGA】AXI DMA详解
    我的学记|刘航宇的博客

    【FPGA】AXI DMA详解

    刘航宇
    2025-01-09 / 0 评论 / 110 阅读 / 正在检测是否收录...

    <

    DMA简介

    DMA是一种内存访问技术,允许某些计算机内部的硬件子系统可以独立的直接读写内存,而不需要CPU介入处理,从而不需要CPU的大量中断负载,否则,CPU需要从来源把每一片段的数据复制到寄存器,然后在把他们再次写回到新的地方,在这个时间里,CPU就无法执行其他的任务。
    DMA是一种快速数据传送方式,通常用来传送数据量较多的数据块。使用DMA时,CPU向DMA控制器发送一个存储器传输请求,这样当DMA控制器在传输的时候,CPU执行其他的操作,传输完成时DMA以中断的方式通知CPU。
    DMA传输过程的示意图为:

    DMA的传输过程为:
    1、为了配置用DMA传输数据到存储器,处理器(Cortex-A9)发出一条指令。
    2、DMA控制器把数据从外设传输到存储器或者从存储器传输到存储器,从而较少CPU处理的事务量。
    3、输出传输完成后,向CPU发出一个中断通知DMA传输可以关闭。
    为了发起传输事务,DMA控制器必须得到以下信息:
    (1)、源地址——数据被读出的地址
    (2)、目的地址——数据被写入的地址
    (3)、传输长度——应传输的字节数

    ZYNQ DMA简介

    ZYNQ提供了两种DMA,一种是集成在PS中的硬核DMA,另一种是PL中使用的软核AXI DMA IP。

    在ARM APU(Application Processor Unit,应用处理单元)设计过程中,已经考虑到大量数据搬移的情况,因此在APU中自带了一个DMA控制器DAMC,这个DMAC驻留在PS内,而且必须通过驻留在内存中的DMA指令编程,这些程序往往需要CPU准备,因此需要部分的CPU参与。DMAC支持多达8个通道,所以多个DMA结构的核可以挂载在单个DMAC上。

    DMAC与PL的连接是通过AXI-GP接口,这个接口最高支持到32位宽,这也限制了这种模式下的传输速率,理论上最大为600MB/s,这种模式不占用PL资源,但需要对DMA指令编程,会增加软件的复杂性。

    为了获取更高的速率,可以空间换时间,在PL中添加AXI DMA IP core,并利用AXI_HP接口完成高速的数据传输,各种接口的传输比较为:

    ZYNQ中ACI_HP接口的分布为:

    通过PL的DMA和AXI_HP接口传输方式的拓扑图为:

    DMA的数据传输经过S_AXI_HP接口,每一个HP接口都含有控制和数据fifo,这些fifo为大数据量突发传输提供缓冲,使得HP成为理想的高速数据接口。

    对DMA的控制或配置通过M_AXI_GP接口(M代表master为PS),传输状态通过中断传达到PS的中断控制器。

    对M_AXI_GP0理解是:

    在ZYNQ7处理器系统IP core中,在PS-PL Configuration下的AXI Non Secure Enablement下有一个GP Master AXI Interface选项,可选一个M_AXI_GP0接口。

    该接口的作用是对PL侧的IP core通过AXI-Lite总线进行配置,如果不仅需要的话直接不使能即可。

    三、AXI DMA IP简介

    ZYNQ提供了两种DMA,一种是集成在PS中的硬核DMA,另一种是PL中使用的软核AXI DMA IP。
    AXI DMA IP核在AXI4-Stream IP接口之间提供高带宽直接存储访问。其可选的scatter gather(SG,链式相关)功能还可以从基于处理器的系统中的中央处理单元(CPU)卸载数据搬运任务。初始化、状态和管理寄存器通过AXI-Lite从接口访问(即数据发出方为PL,PS为Slave),核心功能组成为(这张图很有助于理解DMA中断以及SDK代码,下面会解释):

    原图位于AXI_DMA数据手册的第五页。
    AXI DMA使用了三种总线,分别是:
    (1)、AXI Memory Map,用于内存交互,AXI4 Memory Map Read用于从DMA读取,AXI4 Memory Map用于向DMA写入。

    (2)、AXI4-Lite同于对寄存器的配置。

    (3)、AXI4-Stream接口用于对外设的读写,S2MM(Stream to Memory Mapped,数据流向内存映射)用于对外设读取。AXI_MM2S和AXI_S2MM是AXI_Stream总线,可以发送和接收连续的数据流,无需地址。

    AXI DMA提供3种模式:
    (1)、Direct Register模式:用于在MM2S和S2MM通道上执行简单的DMA传输,小的FPGA资源少。有两个通道:一个从Device到DMA,另一个从DMA到Device。应用程序必须设置缓冲区地址和长度字段以启动相应通道中的传输。
    (2)、Scatter/Gather模式:允许在单个DMA事务中将数据传输到多个存储区域传输数据。
    (3)、Cyclic DMA模式:

    四、AXI DMA参数与接口分析


    1、接口分析:
    (1)、M_AXI_MM2S:DMA的读通道,从DDR中读取数据。受Enable Read Channel控制,表现为M_AXI_MM2S。
    (2)、M_AXI_S2MM:DMA的写通道,将数据写入DDR中。受Enable Write Channel控制,表现为M_AXI_S2MM。
    (3)、M_AXIS_MM2S:DMA将数据发送到具有stream接口IP。
    (4)、S_AXIS_S2MM:DMA将数据从具有Stream接口的IP中将数据读入。
    (5)、mm2s_introut:DMA将数据从DDR的映射单元中读出,然后将数据发送到具有Stream接口的IP完成信号。
    (6)、s2mm_introut:DMA将数据从具有stream接口的IP中读入,并写入到内存映射单元的完成中断信号。
    2、参数分析

    (1)、Enable Scatter Gatter Engine
    链式DMA操作,取消选中该选项可启用directregister模式操作。
    (2)、Enable Micro DMA
    改选项会生成高度优化的DMA,资源数量较少,用于传输极少量数据的应用程序。
    (3)、Width of Buffer Length Register
    根据IP手册pg021,在direct register模式下,此整数值用于指定控制字段缓冲区长度的有效位数,字节数等于2^(width),即字节读取和字节写入的有效长度都是2^(width)。比如宽度设置为26,可传输的字节数为2^(26)字节。(pg021,78页)。
    (4)、Address Width
    指定地址空间的宽度,默认32。
    (5)、Enable Read Channel
    Memory Map Data Width:AXI MM2S存储映射读取总线的数据位宽,可为32、64、128、256、512、1024。
    Stream Data Width:AXI MM2S AXI-Stream数据总线的位宽,该值必须小于等于Memory Map Data Width,可以为8、16、32、64、128、512、1024。
    Max Burst Size:最大突发长度设置,指定的是MM2S的AXI4-Memory Map侧的突发周期的最大值,可为2、4、8、16、32、64、128、256。
    (6)、Enable Write channel:同Read channel。
    3、关于中断的理解
    (1)、M_AXI_MM2S:DMA的读通道,从DDR中读取数据。受Enable Read Channel控制,表现为M_AXI_MM2S。

    在AXI_DMA ip core的输出信号中,有两个中断信号,分别是s2mm_introut和mm2s_introut,mm指的是Memory Mapped,S指的是Stream。

    Memory Map指的是什么?根据AXI DMA的介绍,AXI DMA提供一个介于AXI4 Memory Mapped 与AXI4 Stream IP之间的高带宽DMA:

    原话位于IP参考的page5:

    The AXI DirectMemory Access (AXI DMA) IP core provides high-bandwidth direct memory accessbetween the AXI4 memory mapped and AXI4-Stream IP interfaces.

    所以,对于DMA来说,S2MM,就是Stream形式的数据到达DDR映射空间,具体的实现方式是Stream数据流先进入DMA,之后再从DMA到Memeory Mapped。

    MM2S是Memory Mapped将数据送入具有AXI Stream接口的IP。

    从这里分析mm2s_introut与s2mm_introut信号的区别是分析不出来的,因为数据都是先到DMA,再从DMA发送出去。

    在第5页还有一张图,讲述AXI DMA的架构:

    分析这张图,DDR内存映射空间的读写都是通过AXI4Memory Map完成的,也就是说s2mm与mm2s的重点不在PS DDR侧,重点在PL侧,当Stream接口的数据将输出传到DMA时候,这个过程叫做DMA的接收,DMA将映射单元的数据写到stream接口的IP,这个过程叫做DMA的发送。

    所以!也就可以理解在SDK中将s2mm_introut定义为DMA接收中断,将mm2s_introut定义为发送中断了!

    所以以下语句就很容易理解了:

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

    Xilinx FPGA里面的AXI DMA IP核的简单用法

    在FPGA里面,AXI DMA这个IP核的主要作用,就是在Verilog语言和C语言之间传输大批量的数据,使用的通信协议为AXI4-Stream。
    Xilinx很多IP核都是基于AXI4-Stream协议的,例如浮点数Floating-point IP核,以及以太网Tri Mode Ethernet MAC IP核。要想将Verilog层面的数据搬运到C语言里面处理,就要使用DMA IP核。
    本文以浮点数Floating-point IP核将定点数转换为浮点数为例,详细讲解AXI DMA IP核的使用方法。
    浮点数IP核的输入输出数据都是32位,协议均为AXI4-Stream。C语言程序首先将要转换的定点数数据通过DMA发送给浮点数IP核,浮点数IP核转换完成后再通过DMA将单精度浮点数结果发回C语言程序,再通过printf打印出来。
    定点数的数据类型为int,小数点定在第四位上,即:XXXXXXX.X。整数部分占28位,小数部分占4位。
    转换后浮点数的数据类型为float,可以用printf的%f直接打印出来。

    工程下载地址:https://pan.baidu.com/s/1SXppHMdhroFT8vGCIysYTQ(提取码:u7wf)

    MicroBlaze C语言工程的建法不再赘述,请参阅:https://blog.csdn.net/ZLK1214/article/details/111824576
    以读写Floating-point IP核数据为例

    首先添加Floating-point IP核,作为DMA的外设端:(主存端为BRAM)




    这里要注意一下,一定要勾选上TLAST,否则DMA接收端会出现DMA Internal Error的错误:

    下面是Xilinx DMA手册里面对DMA Internal Error错误的描述:

    添加AXI DMA IP核:

    IP核添加好了,但还没有连线:

    点击Run Connection Automation,自动连接DMA的S_AXI_LITE接口:





    自动连接浮点数IP核的时钟引脚:





    添加BRAM控制器:


    最终的连线结果:

    修改新建的BRAM的容量为64KB:


    最终的地址分配方式:

    保存Block Design,然后生成Bitstream:

    Bitstream生成后,导出xsa文件:

    Vitis Platform工程重新导入xsa文件:


    修改C程序(helloworld.c)的代码:

    (这里面XPAR_BRAM_2_BASEADDR最好改成0xc0000000,因为生成的xparameters.h配置文件里面BRAM号可能有变化)

    /*
     * helloworld.c: simple test application
     *
     * This application configures UART 16550 to baud rate 9600.
     * PS7 UART (Zynq) is not initialized by this application, since
     * bootrom/bsp configures it to baud rate 115200
     *
     * ------------------------------------------------
     * | UART TYPE   BAUD RATE                        |
     * ------------------------------------------------
     *   uartns550   9600
     *   uartlite    Configurable only in HW design
     *   ps7_uart    115200 (configured by bootrom/bsp)
     */
     
    #include <stdio.h>
    #include <xaxidma.h>
    #include "platform.h"
     
    // DMA无法通过AXI Interconnect访问Microblaze本身的BRAM内存
    // 只能访问挂接在AXI Interconnect上的内存
    #define _countof(arr) (sizeof(arr) / sizeof(*(arr)))
    typedef struct
    {
        int numbers_in[40];
        float numbers_out[40];
    } BRAM2_Data;
     
    static BRAM2_Data *bram2_data = (BRAM2_Data *)XPAR_BRAM_2_BASEADDR;
    static XAxiDma xaxidma;
     
    int main(void)
    {
        int i, ret = 0;
        XAxiDma_Config *xaxidma_cfg;
     
        init_platform();
     
        printf("Hello World\n");
        printf("Successfully ran Hello World application\n");
     
        // 初始化DMA
        xaxidma_cfg = XAxiDma_LookupConfig(XPAR_AXIDMA_0_DEVICE_ID);
        XAxiDma_CfgInitialize(&xaxidma, xaxidma_cfg);
        ret = XAxiDma_Selftest(&xaxidma);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_Selftest() failed! ret=%d\n", ret);
            goto err;
        }
     
        // 初始化DMA的输入数据
        printf("numbers_in=%p, numbers_out=%p\n", bram2_data->numbers_in, bram2_data->numbers_out);
        for (i = 0; i < _countof(bram2_data->numbers_in); i++)
        {
            bram2_data->numbers_in[i] = 314 * (i + 1);
            if (i & 1)
                bram2_data->numbers_in[i] = -bram2_data->numbers_in[i];
        }
     
        // DMA开始发送数据 (Length参数的单位为字节)
        ret = XAxiDma_SimpleTransfer(&xaxidma, (uintptr_t)bram2_data->numbers_in, sizeof(bram2_data->numbers_in), XAXIDMA_DMA_TO_DEVICE);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_SimpleTransfer(XAXIDMA_DMA_TO_DEVICE) failed! ret=%d\n", ret);
            goto err;
        }
     
        // DMA开始接收数据
        ret = XAxiDma_SimpleTransfer(&xaxidma, (uintptr_t)bram2_data->numbers_out, sizeof(bram2_data->numbers_out), XAXIDMA_DEVICE_TO_DMA);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_SimpleTransfer(XAXIDMA_DEVICE_TO_DMA) failed! ret=%d\n", ret);
            goto err;
        }
     
        // 等待DMA发送完毕
        i = 0;
        while (XAxiDma_Busy(&xaxidma, XAXIDMA_DMA_TO_DEVICE))
        {
            i++;
            if (i == 200000)
            {
                // 必须确保DMA访问的内存是直接挂接在AXI Interconnect上的
                // 否则这里会报DMA Decode Error的错误 (the address request points to an invalid address)
                printf("DMA Tx timeout! DMASR=0x%08lx\n", XAxiDma_ReadReg(xaxidma.RegBase + XAXIDMA_TX_OFFSET, XAXIDMA_SR_OFFSET));
                goto err;
            }
        }
        printf("DMA Tx complete!\n");
     
        // 等待DMA接收完毕
        i = 0;
        while (XAxiDma_Busy(&xaxidma, XAXIDMA_DEVICE_TO_DMA))
        {
            i++;
            if (i == 200000)
            {
                // floating-point IP核的配置里面一定要把A通道的tlast复选框勾选上, 使输入端和输出端都有tlast信号
                // 否则s_axis_s2mm_tlast一直为0, DMA以为数据还没接收完, 就会报DMA Internal Error的错误
                // (the incoming packet is bigger than what is specified in the DMA length register)
                printf("DMA Rx timeout! DMASR=0x%08lx\n", XAxiDma_ReadReg(xaxidma.RegBase + XAXIDMA_RX_OFFSET, XAXIDMA_SR_OFFSET));
                goto err;
            }
        }
        printf("DMA Rx complete!\n");
     
    err:
        for (i = 0; i < _countof(bram2_data->numbers_out); i++)
            printf("numbers_out[%d]=%f\n", i, bram2_data->numbers_out[i]);
     
        cleanup_platform();
        return 0;
    }

    C程序的运行结果:


    接下来讲一下我们刚才禁用掉的Scatter Gather接口的用法。取消禁用后,之前的C代码就不能运行了。
    之前没有启用Scatter Gather的时候,我们一次只能提交一个DMA请求,等这个DMA请求的数据传输完毕后,我们才能提交下一个DMA传输请求。
    有了Scatter Gather接口,我们就可以一次性提交很多很多DMA请求,然后CPU去干其他的事情。这可以大大提高传输效率。
    除此以外,Scatter Gather还可以将多个位于不同内存地址的缓冲区合并成一个AXI4-Stream数据包传输。

    下面的示例演示了如何利用Scatter Gather功能批量收发3组数据包。
    启用了Scatter Gather后,DMA里面多出了一个M_AXI_SG接口,点击Run Connection Automation,连接到AXI Interconnect上:


    Vivado工程Generate Bitstream,然后导出xsa文件。回到Vitis后,必须把Platform工程删了重建,不然XPAR_AXI_DMA_0_INCLUDE_SG的值得不到更新。



    原有的C程序不再可用,修改一下程序代码:

     
    /*
     * helloworld.c: simple test application
     *
     * This application configures UART 16550 to baud rate 9600.
     * PS7 UART (Zynq) is not initialized by this application, since
     * bootrom/bsp configures it to baud rate 115200
     *
     * ------------------------------------------------
     * | UART TYPE   BAUD RATE                        |
     * ------------------------------------------------
     *   uartns550   9600
     *   uartlite    Configurable only in HW design
     *   ps7_uart    115200 (configured by bootrom/bsp)
     */
     
    #include <stdio.h>
    #include <xaxidma.h>
    #include "platform.h"
     
    /* Xilinx的官方例程:C:\Xilinx\Vitis\2020.1\data\embeddedsw\XilinxProcessorIPLib\drivers\axidma_v9_11\examples\xaxidma_example_sg_poll.c */
     
    // DMA无法通过AXI Interconnect访问Microblaze本身的BRAM内存
    // 只能访问挂接在AXI Interconnect上的内存
    #define _countof(arr) (sizeof(arr) / sizeof(*(arr)))
    typedef struct
    {
        int numbers_in[40];
        float numbers_out[40];
    } BRAM2_Data;
     
    typedef struct
    {
        uint8_t txbuf[640];
        uint8_t rxbuf[640];
    } BRAM2_BdRingBuffer;
     
    static BRAM2_Data *bram2_data = (BRAM2_Data *)0xc0000000;
    static BRAM2_BdRingBuffer *bram2_bdringbuf = (BRAM2_BdRingBuffer *)0xc0008000;
    static XAxiDma xaxidma;
     
    int main(void)
    {
        int i, n, ret = 0;
        XAxiDma_Bd *bd, *p;
        XAxiDma_BdRing *txring, *rxring;
        XAxiDma_Config *cfg;
     
        init_platform();
     
        printf("Hello World\n");
        printf("Successfully ran Hello World application\n");
     
        // 初始化DMA
        cfg = XAxiDma_LookupConfig(XPAR_AXIDMA_0_DEVICE_ID);
        XAxiDma_CfgInitialize(&xaxidma, cfg);
        ret = XAxiDma_Selftest(&xaxidma);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_Selftest() failed! ret=%d\n", ret);
            goto err;
        }
     
        if (!XAxiDma_HasSg(&xaxidma))
        {
            printf("XPAR_AXI_DMA_0_INCLUDE_SG=%d\n", XPAR_AXI_DMA_0_INCLUDE_SG);
            printf("Please recreate and build Vitis platform project!\n");
            goto err;
        }
     
        // 初始化DMA的输入数据
        printf("[0] numbers_in=%p, numbers_out=%p\n", bram2_data[0].numbers_in, bram2_data[0].numbers_out);
        printf("[1] numbers_in=%p, numbers_out=%p\n", bram2_data[1].numbers_in, bram2_data[1].numbers_out);
        printf("[2] numbers_in=%p, numbers_out=%p\n", bram2_data[2].numbers_in, bram2_data[2].numbers_out);
        for (i = 0; i < _countof(bram2_data[0].numbers_in); i++)
        {
            bram2_data[0].numbers_in[i] = 314 * (i + 1);
            bram2_data[1].numbers_in[i] = -141 * (i + 1);
            bram2_data[2].numbers_in[i] = -2718 * (i + 1);
            if (i & 1)
            {
                bram2_data[0].numbers_in[i] = -bram2_data[0].numbers_in[i];
                bram2_data[1].numbers_in[i] = -bram2_data[1].numbers_in[i];
                bram2_data[2].numbers_in[i] = -bram2_data[2].numbers_in[i];
            }
        }
     
        // 配置DMA发送描述符
        txring = XAxiDma_GetTxRing(&xaxidma);
        n = XAxiDma_BdRingCntCalc(XAXIDMA_BD_MINIMUM_ALIGNMENT, sizeof(bram2_bdringbuf->txbuf));
        ret = XAxiDma_BdRingCreate(txring, (uintptr_t)bram2_bdringbuf->txbuf, (uintptr_t)bram2_bdringbuf->txbuf, XAXIDMA_BD_MINIMUM_ALIGNMENT, n);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_BdRingCreate(txring) failed! ret=%d\n", ret);
            goto err;
        }
        printf("BdRing Tx count: %d\n", n);
     
        ret = XAxiDma_BdRingAlloc(txring, 3, &bd);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_BdRingAlloc(txring) failed! ret=%d\n", ret);
            goto err;
        }
     
        p = bd;
        for (i = 0; i < 3; i++)
        {
            XAxiDma_BdSetBufAddr(p, (uintptr_t)bram2_data[i].numbers_in);
            XAxiDma_BdSetLength(p, sizeof(bram2_data[i].numbers_in), txring->MaxTransferLen);
            XAxiDma_BdSetCtrl(p, XAXIDMA_BD_CTRL_TXSOF_MASK | XAXIDMA_BD_CTRL_TXEOF_MASK);
            XAxiDma_BdSetId(p, i);
            p = (XAxiDma_Bd *)XAxiDma_BdRingNext(txring, p);
        }
     
        ret = XAxiDma_BdRingToHw(txring, 3, bd);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_BdRingToHw(txring) failed! ret=%d\n", ret);
            goto err;
        }
     
        // 配置DMA接收描述符
        rxring = XAxiDma_GetRxRing(&xaxidma);
        n = XAxiDma_BdRingCntCalc(XAXIDMA_BD_MINIMUM_ALIGNMENT, sizeof(bram2_bdringbuf->rxbuf));
        ret = XAxiDma_BdRingCreate(rxring, (uintptr_t)bram2_bdringbuf->rxbuf, (uintptr_t)bram2_bdringbuf->rxbuf, XAXIDMA_BD_MINIMUM_ALIGNMENT, n);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_BdRingCreate(rxring) failed! ret=%d\n", ret);
            goto err;
        }
        printf("BdRing Rx count: %d\n", n);
     
        ret = XAxiDma_BdRingAlloc(rxring, 3, &bd);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_BdRingAlloc(rxring) failed! ret=%d\n", ret);
            goto err;
        }
     
        p = bd;
        for (i = 0; i < 3; i++)
        {
            XAxiDma_BdSetBufAddr(p, (uintptr_t)bram2_data[i].numbers_out);
            XAxiDma_BdSetLength(p, sizeof(bram2_data[i].numbers_out), rxring->MaxTransferLen);
            XAxiDma_BdSetCtrl(p, 0);
            XAxiDma_BdSetId(p, i);
            p = (XAxiDma_Bd *)XAxiDma_BdRingNext(rxring, p);
        }
     
        ret = XAxiDma_BdRingToHw(rxring, 3, bd);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_BdRingToHw(rxring) failed! ret=%d\n", ret);
            goto err;
        }
     
        // 开始发送数据
        ret = XAxiDma_BdRingStart(txring);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_BdRingStart(txring) failed! ret=%d\n", ret);
            goto err;
        }
     
        // 开始接收数据
        ret = XAxiDma_BdRingStart(rxring);
        if (ret != XST_SUCCESS)
        {
            printf("XAxiDma_BdRingStart(rxring) failed! ret=%d\n", ret);
            goto err;
        }
     
        // 等待收发结束
        n = 0;
        while (n < 6)
        {
            // 检查发送是否结束
            ret = XAxiDma_BdRingFromHw(txring, XAXIDMA_ALL_BDS, &bd);
            if (ret != 0)
            {
                n += ret;
                p = bd;
                for (i = 0; i < ret; i++)
                {
                    printf("DMA Tx%lu Complete!\n", XAxiDma_BdGetId(p));
                    p = (XAxiDma_Bd *)XAxiDma_BdRingNext(txring, p);
                }
     
                ret = XAxiDma_BdRingFree(txring, ret, bd);
                if (ret != XST_SUCCESS)
                    printf("XAxiDma_BdRingFree(txring) failed! ret=%d\n", ret);
            }
     
            // 检查接收是否结束
            ret = XAxiDma_BdRingFromHw(rxring, XAXIDMA_ALL_BDS, &bd);
            if (ret != 0)
            {
                n += ret;
                p = bd;
                for (i = 0; i < ret; i++)
                {
                    printf("DMA Rx%lu Complete!\n", XAxiDma_BdGetId(p));
                    p = (XAxiDma_Bd *)XAxiDma_BdRingNext(rxring, p);
                }
     
                ret = XAxiDma_BdRingFree(rxring, ret, bd);
                if (ret != XST_SUCCESS)
                    printf("XAxiDma_BdRingFree(rxring) failed! ret=%d\n", ret);
            }
        }
     
    err:
        for (i = 0; i < _countof(bram2_data[0].numbers_out); i++)
            printf("numbers_out[%d]=%f,%f,%f\n", i, bram2_data[0].numbers_out[i], bram2_data[1].numbers_out[i], bram2_data[2].numbers_out[i]);
     
        cleanup_platform();
        return 0;
    }

    1
    SoC架构、通信举例、AHB、APB接口
    下一篇 » 2024-08-23

    评论 (0)

    取消