1. 今日摸鱼计划

通过 DMA 实现 PS PL 间的数据循环。

小梅哥教材:

03_【裸机教程】基于C编程的Zynq裸机程序设计与应用教程v2.4.5.pdf

17.6  设计实例

整个系统在工作时,首先 PS 侧会通过 AXI4-Lite DMA 核进行配置,随后数据被写入 DDR 中。
AXI DMA 核会被控制去读取 DDR 中的数据,并将数据写入到位于 PL 侧的 FIFO 中。
当完成了该步骤,会产生一个输出给中断控制器的中断。
再然后,PS 控制 DMA FIFO 中的数据读出,通过 AXI 总线再次写回到 DDR中。
完成了该步骤后,DMA 会产生另一个中断给中断控制器。
重新写回 DDR 的数据会被拿来与最初读出的数据进行比较,判断数据是否一致。

2. 硬件逻辑系统设计

①Block Design

IP核 功能
ZYNQ PS PL 间的逻辑连接
Concat核 多路信号转换为一路信号
DMA
数据在 PS PL 间的高速传输

AXI4-Stream

Data FIFO

存储 AXI-Stream 类型的数据

② ZYNQ核配置

a. 配置串口

     使用串口打印回环数据对比结果,用于检测实验正确性配置UART1

b. 配置 DDR 的型号

c. 使能 HP 接口

    PS PL 之间,有 HPGPACP 三种 AXI 接口,其中只有 HP 是用于高速的数据传输

HP GP ACP
全称 High Performance General Purpose

Accelerator

Coherency Port

带宽

高带宽

(通常32/64位,

高时钟频率)

中等带宽

(通常32位,

较低时钟频率)

中等带宽

(通常64位,

支持一致性访问)

延迟 低延迟 较高延迟

中等延迟

(需维护一致性)

数据一致性 无缓存一致性 无缓存一致性

支持缓存一致性

(与CPU缓存同步)

连接对象

PL作为主设备

访问PS的存储器(DDR/OCM)

PS与PL双向通信

(主从设备均可)

PL作为主设备

访问PS的缓存

应用场景

高速数据传输

(如视频流、DMA传输)

控制寄存器访问

低速数据交换

需要与CPU共享数据

且避免手动缓存维护的场景

优势

高吞吐量

适合大数据块传输

灵活性高

配置简单

自动维护数据一致性

减少软件开销

局限性 需手动管理数据同步

带宽和延迟性能

较低

带宽低于HP

且可能因一致性维护,

引入额外延迟

适用IP核

DMA引擎

高速数据采集模块

传感器控制

状态寄存器交互

算法加速器

(如机器学习、信号处理)

选择建议:

  • 优先HP:当PL需频繁读写大量数据且不依赖CPU缓存时(如DMA传输)。

  • 优先ACP:当PL加速器需要与CPU共享数据且一致性关键时(如多核SOC中的算法加速)。

  • 优先GP:仅需简单控制或调试时(如配置IP核寄存器)。

d. 使能中断

AXI DMA 每完成一次读出写入,便会产生一个对应中断,这些中断由 DMA核(可编程逻辑)产生,输出到中断控制器,由中断控制器管理并分配到对应 CPU

由上图可知,为共享外设中断(Shared Peripheral InterruptsSPI

e. 配置完成

 DMA 核配置

Enable Asynchronous Clocks(Auto):使能异步时钟,该项会根据连接到 AXI DMA 的时钟自动设置。
Enable Scatter Gather Engine:使能 SG模式,并使用 SG引擎;
                                                      禁用该项会启用直接寄存器模式(简单 DMA 模式)。
Enable Micro DMA:生成高度优化的 DMA,资源数量较少,用于传输极少量数据的应用程序。
Enable Multi Channel Support:启用 DMA 的多信道功能,并可选择 MM2S S2MM 的信道数。
Enable Control/Status Stream:启动 AXI4 控制和状态流
                                                       SG 模式下的两组信号,用于传输应用程序元数据。
Width of Buffer Length Registers(8-26):SG 模式传输的控制字段缓冲区长度和状态字段有效位数。
                                     直接寄存器模式,指定MM2S_LENGH和 S2MM_LENGH寄存器中有效位长度。
                                     该值指定的字节数为 2^(width),eg设置为 26,则有效长度为 2^26 字节。
Address Width(32-64):该项指定地址空间的宽度。它可以是 32 到 64之间的任意值。
Number of Channels:该项可以指定通道个数,有效值为 1 16
Memory Map Data WidthAXI MM2S 内存映射读数据总线的数据宽度(以位为单位)。
                                             有效值为 3264128256512 1024
Stream Data WidthAXI MM2S AXI4 流数据总线的数据宽度(以位为单位)。
                                    该值必须等于或小于内存映射数据宽度。
                                    有效值为 81632、64128512 1024
Max Burst Size:最大突发长度设置。该项指定 MM2S AXI4 内存映射侧突发周期的最大值。
                             有效值为248163264128256
Allow Unaligned Transefers:允许非对齐传输,使能后会启用MM2S数据重新校准引擎(DRE),
                                                   允许数据重新对齐到 MM2S 内存映射数据路径上的字节(8 位)级别。
Use Rxlength In Status Stream:如果启用了控制/状态流,则选中该选项将允许
                               AXI DMA 在状态数据包的 App4 字段中使用 S2MM 目标 IP 提供的接收长度字段。
                               该选项为需要更大吞吐量的系统提供了更高带宽的解决方案。
Enable Single AXI4 Data Interface:此选项仅在 Vivado IP integrater 中使用时适用。
                                         用户可以使用此选项将两个 AXI4 接口(MM2S S2MM)组合为一个接口。
                                         此选项不会影响资源或性能。

配置后为直接寄存器模式

 Concat核配置

保持默认

⑤AXI4-Stream Data FIFO 核配置

保持默认

TDATA 的位宽此时并不是正确的值,但该项可以由 IP 核自动设置,在后续连接好整个硬件系统后会根据输入数据的 TDATA 位宽自动设置该项,所以这里不需要修改。

⑥端口连接

AXI DMA
M_AXIS_MM2S S_AXIS AXI4-Stream Data FIFO
S_AXIS_S2MM M_AXIS AXI4-Stream Data FIFO
mm2s_introut In0[0:0] Concat
s2mm_introut In1[0:0] Concat
Concat
dout[1:0] IRQ_F2P[0:0] ZYNQ
依次:Run Block Automation 、Run Connection Automation (S2MM还未连接)、
           Run Connection Automation后可以得到:
验证无误后,生成.bit与.hdf

3. CPU 软件程序设计

AXI_DMA.h

#ifndef _AXI_DMA_H_
#define _AXI_DMA_H_

#include "ISR.h"
#include "xaxidma.h"
#include "SCU_GIC.h"

//循环计数器复位的超时次数
#define RESET_TIMEOUT_COUNTER    10000

extern XAxiDma AxiDma0;

void AXI_DMA_Init(XAxiDma *AxiDma, uint32_t DeviceId);
void AXI_DMA_TxInt_Init(XAxiDma *AxiDma, uint16_t TxIntrId, Xil_InterruptHandler Handler);
void AXI_DMA_RxInt_Init(XAxiDma *AxiDma, uint16_t RxIntrId, Xil_InterruptHandler Handler);

#endif /* _AXI_DMA_H_ */
 

AXI_DMA.c

#include "AXI_DMA.h"

XAxiDma AxiDma0;


/*****************************************************************************
* @brief    初始化AXI DMA
* @param    AxiDmaPtr    指向DMA实例的指针
* @param    DeviceId    AXI DMA的设备ID
* @Usage    AXI_DMA_Init(&AxiDma0, XPAR_AXIDMA_0_DEVICE_ID);
******************************************************************************/
void AXI_DMA_Init(XAxiDma *AxiDma, uint32_t DeviceId)
{
    XAxiDma_Config *Config;
    Config = XAxiDma_LookupConfig(DeviceId);
    XAxiDma_CfgInitialize(AxiDma, Config);

    //禁用所有中断
    XAxiDma_IntrDisable(AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
    XAxiDma_IntrDisable(AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DEVICE_TO_DMA);
}

/*****************************************************************************
* @brief    初始化DMA的Tx中断
* @param    AxiDmaPtr    指向DMA实例的指针
* @param    TxIntrId    TX通道中断ID
* @param    Handler        Tx通道中断处理函数
* @Usage    AXI_DMA_TxInt_Init(&AxiDma, TX_INTR_ID, TxIntrHandler);
******************************************************************************/
void AXI_DMA_TxInt_Init(XAxiDma *AxiDma, uint16_t TxIntrId, Xil_InterruptHandler Handler)
{
    //连接中断服务函数
    Set_ScuGic_Link(TxIntrId, 0xA0, Rising_Edge_Sensitive, Handler, (void *)AxiDma);

    //使能DMATx中断
    XAxiDma_IntrEnable(AxiDma, XAXIDMA_IRQ_ALL_MASK, XAXIDMA_DMA_TO_DEVICE);
}

/*****************************************************************************
* @brief    初始化DMA的Rx中断
* @param    AxiDmaPtr    指向DMA实例的指针
* @param    RxIntrId    Rx通道中断ID
* @param    Handler        Rx通道中断处理函数
* @Usage    AXI_DMA_RxInt_Init(&AxiDma, RX_INTR_ID, RxIntrHandler);
******************************************************************************/
void AXI_DMA_RxInt_Init(XAxiDma *AxiDma, uint16_t RxIntrId, Xil_InterruptHandler Handler)
{
    //连接中断服务函数
    Set_ScuGic_Link(RxIntrId, 0xA0, Rising_Edge_Sensitive, Handler, (void *)AxiDma);

    //使能DMARx中断
    XAxiDma_IntrEnable(AxiDma, XAXIDMA_IRQ_ALL_MASK,XAXIDMA_DEVICE_TO_DMA);
}
 

ISR.h

#ifndef ACZ702_LIB_ISR_H_
#define ACZ702_LIB_ISR_H_


#include "AXI_DMA.h"

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

void TxIntrHandler(void *Callback);
void RxIntrHandler(void *Callback);

void ScuTimer_IRQ_Handler(void *CallBackRef);

#endif /* ACZ702_LIB_ISR_H_ */
 

ISR.c

#include "ISR.h"
#include "SCU_TIMER.h"
//中断处理标志
volatile int TxDone;
volatile int RxDone;
volatile int Error;

/*****************************************************************************
* @brief    DMA TX中断处理函数。
*            从硬件获取中断状态,对其进行确认:
*            如果发生任何错误,它将重置硬件。
*            如果中断完成,则将TxDone标志置为1
*
* @param    Callback是指向DMA引擎的TX通道的指针。
******************************************************************************/
void TxIntrHandler(void *Callback)
{
    uint32_t IrqStatus;
    int TimeOut;
    XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

    //读取挂起的中断,获取被声明的中断的位掩码
    IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE);

    //确认挂起的中断
    XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE);

    //如果没有中断被断言,直接返回
    if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK))
    {
        return;
    }

    //如果断言中断错误,则拉高错误标志位,复位硬件以从错误中恢复,然后直接返回
    if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK))
    {
        Error = 1;//将错误标志置为1

        //复位DMA通道
        XAxiDma_Reset(AxiDmaInst);

        //装载循环计数器复位的超时次数
        TimeOut = RESET_TIMEOUT_COUNTER;

        //等待DMA复位完成,若复位超时则也会跳出等待,超时次数由开头用户自定义
        while (TimeOut)
        {
            if (XAxiDma_ResetIsDone(AxiDmaInst))//如果复位完成,则break跳出while循环
            {
                break;
            }

            TimeOut --;//超时次数减一,达到0时,while循环也会跳出
        }

        return;
    }

    //如果中断完成,则将TxDone标志置为1
    if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK))
    {
        TxDone = 1;//将发送完成标志置为1
    }
}

/*****************************************************************************
* @brief    DMA RX中断处理函数
*             它从硬件获取中断状态,对其进行确认:
*             如果发生任何错误,它将重置硬件。
*             否则,如果中断完成,则将RxDone标志置为1。
*
* @param    Callback是指向DMA引擎的RX通道的指针。
******************************************************************************/
void RxIntrHandler(void *Callback)
{
    uint32_t IrqStatus;
    int TimeOut;
    XAxiDma *AxiDmaInst = (XAxiDma *)Callback;

    //读取挂起的中断
    IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DEVICE_TO_DMA);

    //确认挂起的中断
    XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DEVICE_TO_DMA);

    //如果没有中断被断言,直接返回
    if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK))
    {
        return;
    }

    /*
     * 如果断言中断错误,则将错误标志置1,
     * 复位硬件以从错误中恢复,然后直接返回
     */
    if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK))
    {
        Error = 1;//将错误标志置为1

        XAxiDma_Reset(AxiDmaInst);

        TimeOut = RESET_TIMEOUT_COUNTER;

        //等待DMA复位完成,若复位超时也会跳出等待,超时次数由开头用户自定义
        while (TimeOut)
        {
            if(XAxiDma_ResetIsDone(AxiDmaInst))//如果复位完成,则break跳出while循环
            {
                break;
            }

            TimeOut -= 1;//超时次数减一,达到0时,while循环也会跳出
        }
        return;
    }

    //如果中断完成,则将RxDone标志置为1
    if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK))
    {
        RxDone = 1;//将接收完成标志置为1
    }
}


/**
  *****************************************************
  * @brief    私有定时器中断处理程序
  * @tag    本函数用来处理私有定时器中断,在内部加入用户程序即可
  *****************************************************
**/
void ScuTimer_IRQ_Handler(void *CallBackRef)
{
    /* ↓↓↓用户处理↓↓↓ */

  //本次没用到,略略略
    /* ↑↑↑结束处理↑↑↑ */
    XScuTimer_ClearInterruptStatus(&ScuTimer);
}
 

main.c

/*****************************************************************************
*    实验流程:
*    1.初始化AXI DMA
*    2.设置Tx和Rx中断
*    3.提交传输
*    4.等待传输完成
*    5.检查传输状态并校验数据
*    6.打印测试结果
******************************************************************************/

#include <stdio.h>

//#include "xil_types.h"
#include "sleep.h"
#include "xparameters.h"
#include "xil_exception.h"
#include "xscugic.h"
#include "xscutimer.h"

#include "ISR.h"
#include "SCU_GIC.h"
#include "SCU_TIMER.h"
#include "AXI_DMA.h"


#define RX_INTR_ID        XPAR_FABRIC_AXIDMA_0_S2MM_INTROUT_VEC_ID    //RX的中断号
#define TX_INTR_ID        XPAR_FABRIC_AXIDMA_0_MM2S_INTROUT_VEC_ID    //TX的中断号
#define TX_BUFFER_BASE    0x1200000                //TX缓冲区的基地址
#define RX_BUFFER_BASE    0x1400000                //RX缓冲区的基地址

int main(void)
{
    int Index;                    //计数变量
    uint16_t Error_Cnt = 0;        //错误计数
    uint8_t *TxBufferPtr;        //传输数据的指针
    uint8_t *RxBufferPtr;        //接收数据的指针

    //将指针TxBufferPtr指向TX_BUFFER_BASE
    TxBufferPtr = (uint8_t *)TX_BUFFER_BASE;
    //将指针RxBufferPtr指向RX_BUFFER_BASE
    RxBufferPtr = (uint8_t *)RX_BUFFER_BASE;

    //初始化通用中断控制器
    ScuGic_Init();

    //初始化AXI DMA
    AXI_DMA_Init(&AxiDma0, XPAR_AXIDMA_0_DEVICE_ID);

    //设置中断服务函数
    AXI_DMA_TxInt_Init(&AxiDma0, TX_INTR_ID, TxIntrHandler);
    AXI_DMA_RxInt_Init(&AxiDma0, RX_INTR_ID, RxIntrHandler);

    //在开始传输测试之前清除中断状态标志
    TxDone = 0;
    RxDone = 0;
    Error = 0;

    //给TxBufferPtr赋值为0~255
    for(Index = 0; Index < 256; Index ++) {
        TxBufferPtr[Index] = Index;
    }

    //在DMA传输之前刷新Buffer
    Xil_DCacheFlushRange((UINTPTR)TxBufferPtr, 256);


    //开启数据传输
    XAxiDma_SimpleTransfer(&AxiDma0,(UINTPTR) TxBufferPtr, 256, XAXIDMA_DMA_TO_DEVICE);

    XAxiDma_SimpleTransfer(&AxiDma0,(UINTPTR) RxBufferPtr, 256, XAXIDMA_DEVICE_TO_DMA);

    //等待TX完成、或者RX完成、或者传输错误,否则一直等待
    while (!TxDone && !RxDone && !Error) {
        /* 等待 */
    }

    //如果发生错误,则打印失败提示
    if (Error) {
        printf("DMA Transfer Failed!\n");
    } else {
        //测试完成,检查数据
        for(Index = 0; Index < 256; Index++)
        {
            if(RxBufferPtr[Index] != Index) {
                Error_Cnt++;
            }
        }
        if(Error_Cnt > 0)
            printf("Test Failed!\n");
        else
            printf("Test Successfully!\n");
    }

    return 0;
}
 

#include "SCU_GIC.h"  #include "SCU_TIMER.h"之前写过啦

4. 板级验证

//下次尝试一下别的

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐