正点原子 发表于 2023-3-29 14:28:29

《ATK-DFPGL22G之FPGA开发指南_V1.0》第五章AXI GPIO按键控制LED实验

本帖最后由 正点原子 于 2023-3-29 14:28 编辑

1)实验平台:正点原子 DFZU2EG_4EV MPSoC开发板
2)购买链接:https://item.taobao.com/item.htm?&id=692368045899
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-340252-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子FPGA交流群:994244016




第五章AXI GPIO按键控制LED实验
在“EMIO按键控制LED实验”中,我们通过EMIO实现了PS端与PL端的交互,而PS与PL最主要的连接方式则是一组AXI接口。AXI互联接口作为MPSOC PS和PL之间的桥梁,能够使两者协同工作,进而形成一个完整的、高度集成的系统。
本章我们将在PL端调用AXI GPIO IP核,并通过AXI4-Lite接口实现PS与PL中AXI GPIO模块的通信。本章包括以下几个部分:
5.1简介
5.2实验任务
5.3硬件设计
5.4软件设计
5.5下载验证



5.1简介
AXI GPIO IP核为AXI接口提供了一个通用的输入/输出接口。与PS端的GPIO不同,AXI GPIO是一个软核(Soft IP),即MPSOC芯片在出厂时并不存在这样的一个硬件电路,而是由用户通过配置PL端的逻辑资源来实现的一个功能模块。而PS端的GPIO是一个硬核(Hard IP),它是一个生产时在硅片中实现的功能电路。
AXI GPIO可以配置成单通道或者双通道,每个通道的位宽可以单独设置。另外通过打开或者关闭三态缓冲器,AXI GPIO的端口还可以被动态地配置成输入或者输出接口。其顶层模块的框图如下所示:
图 5.1.1 AXI GPIO框图
从图 5.1.1中可以看到,模块的左侧实现了一个32位的AXI4-Lite从接口,用于主机访问AXI GPIO内部各通道的寄存器。当右侧接口输入的信号发生变化时,模块还能向主机产生中断信号。不过只有在配置IP核时选择“使能中断”,才会启用模块的中断控制功能。
5.2实验任务
本章的实验任务是通过调用AXI GPIO IP核,使用中断机制,实现开发板上PL端按键控制PS端LED的功能。
5.3硬件设计
根据实验任务我们可以画出本次实验的系统框图,如下图所示:
图 5.3.1 系统框图
在图 5.3.1中,PS端的M_AXI_HPM作为主端口,与PL端的AXI GPIO IP核以AXI4-Lite总线相连接。其中,AXI互联IP(AXI Interconnect)用于连接AXI存储器映射(memory-mapped)的主器件和从器件。通用中断控制器(GIC)用于管理来自PS或者PL的中断,并把这些中断发送到CPU。
首先创建Vivado工程,工程名为“axi_gpio”,然后创建Block Design设计(design_1.bd)并添加Zynq UltraScale+ MPSOC模块。接下来按照《“Hello World”实验》中的步骤2-7、2-8分别配置PS的UART和DDR控制器。需要特别注意的是,我们在《“Hello World”实验》的步骤2-10中,移除了PS中与PL端交互的接口信号,这些接口在我们本次实验中需要予以保留。
图 5.3.2 PS中断配置界面
由于本次实验用到了PL端的中断,因此在Zynq UltraScale+ MPSOC处理系统的配置界面左侧点击“PS_PL Configuration”标签以配置中断。然后在右侧的界面中依次展开General -> Interrupts -> PL to PS,设置IRQ0为1,如图 5.3.2所示。
另外我们还要用到PS端的LED,因此需要在I/O Configuration界面勾选“GPIO1 MIO”,并设置Bank0,Bank2电压为1.8V,如下图所示:
图 5.3.3 MIO配置
最后点击右下角的“OK”,本次实验MPSOC处理系统就配置完成了。配置完成后其接口如下图所示:
图 5.3.4 MPSOC模块接口
相比于前面的几个实验,在图 5.3.4中多了一些接口信号,他们是PS使用AXI接口与PL端进行通信时所需要的信号:
M_AXI_HPM0_LPD是通用(General Purpose)AXI接口,它包含了一组信号。首字母M表示PS作为主机(Master),PL中的外设作为从机(Slave)。而左侧的maxihpm0_lpd_aclk是这个接口的全局时钟信号,它是一个输入信号,M_AXI_HPM0_LPD接口的所有信号都是在这个全局时钟的上升沿采样的。
pl_clk0是PS输出的时钟信号,它将作为PL中外设模块的时钟源。在配置MPSOC的时候,该时钟默认为100MHz。
pl_resetn0是由PS输出到PL的全局复位信号,低电平有效。
pl_ps_irq0是由PL输出到PS的中断信号。
接下来我们要在Block Design中添加PL端的AXI GPIO IP核,在Diagram窗口空白位置右击,然后选择“Add IP”。在弹出的IP目录中搜索“AXI GPIO”,最后双击搜索结果中的“AXI GPIO”将其添加到设计中,如下图所示:
图 5.3.5 添加AXI GPIO IP核
添加完成后Diagram窗口如下图所示:
图 5.3.6 添加AXI GPIO完成
双击AXI GPIO IP核,打开其配置界面如下图所示:
图 5.3.7 AXI GPIO配置
在上图中,我们需要修改红色方框所标注的两个位置。首先设置GPIO接口的位宽“GPIO Width”,最大可以支持32位。这里我们只需要连接一个按键,因此将其设置为1。另外我们还需要使能其中断功能,所以需要勾选“Enable Interrupt”。
我们也可以通过勾选图中的“All Inputs”或者“All Outputs”将GPIO指定为输入或者输出接口。这两个选项默认是没有勾选的,这样我们可以在程序中将其动态地配置成输入或者输出接口。大家需要注意箭头1所指示的参数 “Default Tri State Value”,它配置GPIO默认情况下的输入输出模式,当其为0xFFFFFFFF时,表明GPIO所有的位默认为输入模式。
另外勾选箭头2所指示的选项可以使能GPIO通道2,GPIO 2的配置与GPIO完全相同。该选项默认没有勾选,即该IP工作在单通道模式下。
AXI GPIO IP核配置完成后点击右下角的“OK”,回到Diagram界面之后会发现AXI GPIO IP核多了一个中断接口“ip2intc_irpt”。我们将鼠标指针放在该接口上,待其变成一支铅笔的样式后,按住左键将其与Zynq UltraScale+ MPSOC的中断接口“pl_ps_irq0”连接起来。如下图所示:
图 5.3.8 连接中断
然后将GPIO引脚引出。鼠标指针放到GPIO接口上,待其变成一只铅笔的样式后,右击选择Make External,如图图5.3.9所示。
图5.3.9 将GPIO引脚引出
修改AXI GPIO IP核引出的GPIO端口的名称。点击引出的GPIO_0端口,在左侧外部端口属性一栏中将其名称修改为“AXI_GPIO_KEY”,如下图所示:
图5.3.10 修改端口名称
修改端口后的Diagram窗口如下图所示:
图5.3.11 Diagram窗口
接下来点击上图中箭头所指示的Run Connection Automation,在弹出的对话框左侧确认勾选All Automation,下面列出了会自动连接的模块及其接口,点击“OK”,工具会自动连接AXI GPIO IP核的S_AXI接口,如下图所示:
图 5.3.12 自动连接
连接完成后,在Diagram窗口空白处右击,然后选择“Regenerate Layout”对设计进行重新布局,布局后的界面如下图所示:
图 5.3.13 重新布局后的设计界面
从上图中可以看到,在执行了自动连接之后,工具自动添加了两个IP核,分别是AXI互联(AXI Interconnect)和处理器系统复位(Processor System Reseet)。
AXI Interconnect IP核用于将一个(或多个)AXI存储器映射的主器件连接到一个(或多个)存储器映射的从器件。在这里我们解释一下这个术语——互联(Interconnect):互联实际上是一个开关,它管理并指挥所连接的AXI接口之间的通信。图5.3.13中橙色高亮的两组信号线表明,在这个设计中,AXI互联实现了由主器件(Zynq UltraScale+ MPSOC)到从器件(AXI GPIO)一对一的连接。它也可以实现一对多、多对一以及多对多的AXI接口连接。
Processor System Reseet IP核为整个处理器系统提供复位信号。它会处理输入端的各种复位条件,并在输出端产生相应的复位信号。在本次实验中,Processor System Reseet接收Zynq UltraScale+ MPSOC输出的异步复位信号pl_resetn0,然后产生一个同步到PL时钟源pl_clk0的复位信号peripheral_aresetn,用于复位PL端的各外设模块。如下图所示:
图 5.3.14 Processor System Reset
最后我们再来看一下设计中的时钟信号,如下图所示:
图 5.3.15 时钟信号
从图 5.3.15中可以看到PL端所有外设模块的时钟接口都连接到了Zynq UltraScale+ MPSOC输出的时钟信号pl_clk0上。需要注意的是,该时钟同样连接到了PS端maxihpm0_pld_aclk端口,作为M_AXI_HPM0_LPD接口的全局时钟信号。
到这里我们的Block Design就设计完成了,在Diagram窗口空白处右击,然后选择“Validate Design”验证设计,如下图所示:
图 5.3.16 验证设计
验证完成后弹出对话框提示“Validation Successful”表明设计无误,点击“OK”确认。最后按快捷键“Ctrl + S”保存设计。
接下来在Source窗口中右键点击Design Source设计文件“design_1.bd”,然后依次执行“Generate Output Products”和“Create HDL Wrapper”。
在左侧Flow Navigator导航栏中找到RTL ANALYSIS,点击该选项中的“Open Elaborated Design”。然后在菜单栏中点击 Layout,在下拉列表中选择I/O Planning以打开I/O Ports窗口。我们将在 I/O Ports 窗口中对AXI GPIO引出的接口AXI_GPIO_KEY进行管脚分配,如下图所示:
图5.3.17 管脚分配
在图5.3.17中,我们将AXI_GPIO_KEY分配到AD11引脚上,该引脚最终与开发板上的按键PL_KEY1相连接。管脚分配完成后按快捷键Ctrl+S保存管脚约束,在弹出的窗口中输入引脚约束文件名,然后点击“OK”,如下图所示:
图5.3.18 管脚约束文件命名
最后在左侧Flow Navigator导航栏中找到PROGRAM AND DEBUG,点击该选项中的“Generate Bitstream”,在弹出的窗口中点击“OK”,对设计进行综合、实现、并生成Bitstream文件。
Bitstream文件生成后,会弹出Bitstream Generation Completed对话框,这里直接点击取消。
在菜单栏中选择 File > Export > Export hardware导出硬件,并在弹出的对话框中,勾选“Include bitstream”,如下图所示:
图5.3.19 导出Hardware
新建vitis文件夹,将产生的xsa文件放入其中。
然后在菜单栏选择Tools > Launch Vitis,启动Vitis开发环境。在弹出的对话框中,将路径指定到新建的vitis文件夹下,点击Launch启动Vitis。
到这里我们的硬件设计部分已经结束,接下来的软件设计部分需要在Vitis软件中进行。
5.4软件设计
在Vitis软件中新建一个空的应用工程,应用工程名为“axi_gpio”。然后为应用工程新建一个源文件“main.c”,我们在新建的main.c文件中输入本次实验的代码。代码的主体部分如下所示:
1#include "stdio.h"
2#include "xparameters.h"
3#include "xgpiops.h"
4#include "xgpio.h"
5#include "xscugic.h"
6#include "xil_exception.h"
7#include "xil_printf.h"
8#include "sleep.h"
9
10 //宏定义
11 #define SCUGIC_ID    XPAR_SCUGIC_0_DEVICE_ID      //中断控制器ID
12 #define GPIOPS_ID    XPAR_XGPIOPS_0_DEVICE_ID   //PS端GPIO器件ID
13 #define AXI_GPIO_IDXPAR_AXI_GPIO_0_DEVICE_ID    //PL端AXI GPIO器件ID
14 #define GPIO_INT_IDXPAR_FABRIC_GPIO_0_VEC_ID    //PL端AXI GPIO中断ID
15
16 #define MIO_LED      38                           //PS_LED1 连接到MIO38
17 #define KEY_CHANNEL1                            //PL按键使用 AXI GPIO通道1
18 #define KEY_MASK   XGPIO_IR_CH1_MASK            //通道1的位定义
19
20 //函数声明
21 void instance_init();                           //初始化器件驱动
22 void axi_gpio_handler(void *CallbackRef);         //中断服务函数
23
24 //全局变量
25 XScuGic            scugic_inst;                   //中断控制器    驱动实例
26 XScuGic_Config*scugic_cfg_ptr;                //中断控制器    配置信息
27 XGpioPs            gpiops_inst;                   //PS端GPIO 驱动实例
28 XGpioPs_Config*gpiops_cfg_ptr;                //PS端GPIO 配置信息
29 XGpio            axi_gpio_inst;               //PL端AXI GPIO 驱动实例
30
31 int led_value = 1;                              //LED显示状态
32
33 int main()
34 {
35printf("AXI GPIO INTERRUPT TEST!\n");
36
37//初始化各器件驱动
38instance_init();
39
40//配置PS GPIO
41XGpioPs_SetDirectionPin(&gpiops_inst, MIO_LED, 1);       //设置 PS GPIO 为输出
42XGpioPs_SetOutputEnablePin(&gpiops_inst, MIO_LED ,1);    //使能 PS GPIO 输出
43XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value);      //点亮LED
44
45//配置PL AXI GPIO
46XGpio_SetDataDirection(&axi_gpio_inst, KEY_CHANNEL, 1);//设置PL AXI GPIO 通道1为输入
47XGpio_InterruptEnable(&axi_gpio_inst, KEY_MASK);         //使能通道1中断
48XGpio_InterruptGlobalEnable(&axi_gpio_inst);             //使能AXI GPIO全局中断
49
50//设置中断优先级和触发类型(高电平触发)
51XScuGic_SetPriorityTriggerType(&scugic_inst, GPIO_INT_ID, 0xA0, 0x1);
52//关联中断ID和中断处理函数
53XScuGic_Connect(&scugic_inst, GPIO_INT_ID, axi_gpio_handler, &axi_gpio_inst);
54//使能AXI GPIO中断
55XScuGic_Enable(&scugic_inst, GPIO_INT_ID);
56
57//设置并打开中断异常处理功能
58Xil_ExceptionInit();
59Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
60       (Xil_ExceptionHandler)XScuGic_InterruptHandler, &scugic_inst);
61Xil_ExceptionEnable();
62
63while(1);
64
65return 0;
66 }
在主函数中,首先调用自己编写的instance_init()函数对各器件驱动进行初始化。然后分别配置PS端GPIO、PL端的AXI GPIO,如程序第40至48行所示。在配置PL端AXI GPIO时,我们需要使能其中断功能,包括AXI GPIO通道1的中断和全局中断。
接下来在程序第50至55行配置GIC。每一个中断源都有自己唯一的标识——中断号(ID),具体的数值可以在头文件xparameters.h中查看。其中由PL产生的共享外设中断(SPI)共16个,中断ID分别为121到128,以及136到143。我们在程序第14行定义了一个宏GPIO_INT_ID,用于标识AXI GPIO的中断ID,它的值为121。
配置GIC首先需要设置中断ID所代表的中断源的优先级和触发类型。中断优先级共分为32个等级,0代表最高优先级,0xF8(10进制数248)代表最低优先级,各优先级之间的步进值为8。也就是说,支持的优先级分别为0、8、16、32……、248。中断触发类型分为高电平敏感类型和上升沿敏感类型。AXI GPIO在检测到输入接口的信号发生改变时,会产生一个电平类型的中断请求,高有效,因此将中断源AXI GPIO的触发类型设置为高电平敏感类型。
然后还需要将中断ID与其中断服务函数关联起来。中断服务函数axi_gpio_handler()是需要我们自己编写的,用于响应和处理AXI GPIO中断的函数。除此之外,还要调用函数XScuGic_Enable(&scugic_inst, GPIO_INT_ID)来使能中断ID所对应的中断源。
最后我们需要初始化并设置ARM处理器的异常处理功能,如程序第57至61行所示。ARM处理器支持7种异常情况:复位、未定义指令、软件中断、指令预取中止、数据中止、中断请求(IRQ)和快速中断请求(FIQ)。每种异常也都有自己的ID标识,其中XIL_EXCEPTION_ID_INT用于标识中断请求(IRQ)异常。我们通过调用函数Xil_ExceptionRegisterHandler( XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler,&scugic_inst )来给IRQ异常注册处理程序,它会将中断控制器GIC的中断处理程序与ARM处理器中的硬件中断处理逻辑连接起来。另外还要通过Xil_ExceptionEnable( )函数使能IRQ异常。
我们在程序中,除了main( )函数之外,另外还编写了两个函数:instance_init( )和axi_gpio_handler( void *CallbackRef )。代码如下所示:
68 //初始化各器件驱动
69 void instance_init()
70 {
71//初始化中断控制器驱动
72scugic_cfg_ptr = XScuGic_LookupConfig(SCUGIC_ID);
73XScuGic_CfgInitialize(&scugic_inst, scugic_cfg_ptr, scugic_cfg_ptr->CpuBaseAddress);
74
75//初始化PS端GPIO驱动
76gpiops_cfg_ptr = XGpioPs_LookupConfig(GPIOPS_ID);
77XGpioPs_CfgInitialize(&gpiops_inst, gpiops_cfg_ptr, gpiops_cfg_ptr->BaseAddr);
78
79//初始化PL端AXI GPIO驱动
80XGpio_Initialize(&axi_gpio_inst, AXI_GPIO_ID);
81 }
82
83 //PL端AXI GPIO 中断服务(处理)函数
84 void axi_gpio_handler(void *CallbackRef)
85 {
86int key_value = 1;
87XGpio *GpioPtr = (XGpio *)CallbackRef;
88
89print("Interrupt Detected!\n");
90XGpio_InterruptDisable(GpioPtr, KEY_MASK);            //关闭 AXI GPIO 中断使能
91   key_value = XGpio_DiscreteRead(GpioPtr, KEY_CHANNEL);   //读取按键数据
92   if(key_value == 0){                                     //判断按键按下
93      led_value = ~led_value;
94      XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value); //改变LED显示状态
95   }
96   sleep(1);                                             //延时1s 按键消抖
97   XGpio_InterruptClear(GpioPtr, KEY_MASK);                //清除中断
98   XGpio_InterruptEnable(GpioPtr, KEY_MASK);               //使能AXI GPIO中断
99 }
其中instance_init( )函数用于初始化设计中所使用的各个器件的驱动,包括PS端的GIC和GPIO,及PL端的AXI GPIO,如程序第68至81行所示。其中通用中断控制器(GIC)是PS中用于集中管理中断信号的资源,如果我们在程序中使用到了中断,就需要对其进行初始化及配置。
而axi_gpio_handler( void *CallbackRef )是中断服务(处理)函数,当CPU检测到AXI GPIO产生的中断后,需要执行该函数。如程序第83至99行所示,在中断服务函数中,我们先通过XGpio_DiscreteRead ( GpioPtr, KEY_CHANNEL )函数读取AXI GPIO通道1所连接的PL端按键的状态;当判断到按键按下时,通过XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value)函数修改PS端LED的显示状态。
对于电平敏感类型的中断,在中断服务函数响应了中断之后,需要将中断源的中断清除。如程序第97行所示,我们通过XGpio_InterruptClear( GpioPtr, KEY_MASK )函数清除AXI GPIO通道1的中断状态寄存器。
程序设计完成后,按快捷键Ctrl+S保存main.c文件,然后编译工程。编译完成后控制台(Console)中会出现提示信息“Build Finished”,同时在应用工程的Binaries目录下可以看到生成的elf文件。
软件设计部分到这里就完成了。
5.5下载验证
首先我们将下载器与开发板上的JTAG接口连接,下载器另外一端与电脑连接。然后使用USB连接线将USB UART(PS_PORT)接口与电脑连接,然后连接开发板的电源,给开发板上电。
打开Vitis Terminal终端,设置并连接串口。然后下载本次实验的程序,下载完成后,在下方的Terminal中可以看到应用程序打印的信息“AXI GPIO INTERRUPT TEST!”,同时开发板上的PS端LED1(红色)点亮。
实验结果如下图所示:
图 5.5.1 下载验证
我们按下开发板上的PL端按键PL_KEY1然后释放,可以看到PS_LED1熄灭。同时Terminal窗口中先后打印出“Interrupt Detected!”信息,说明检测到AXI GPIO产生的中断,如下图所示:
图 5.5.2 串口终端中打印的信息
我们每次按下按键PL_KEY1,都可以PS_LED1的显示状态发生改变,同时串口终端打印“Interrupt Detected!”信息,说明本次实验下载验证成功。
页: [1]
查看完整版本: 《ATK-DFPGL22G之FPGA开发指南_V1.0》第五章AXI GPIO按键控制LED实验