1)实验平台:正点原子MPSoC开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html
在“EMIO按键控制LED实验”中,我们通过EMIO实现了PS端与PL端的交互,而PS与PL最主要的连接方式则是一组AXI接口。AXI互联接口作为MPSOC PS和PL之间的桥梁,能够使两者协同工作,进而形成一个完整的、高度集成的系统。
本章我们将在PL端调用AXI GPIO IP核,并通过AXI4-Lite接口实现PS与PL中AXI GPIO模块的通信。本章包括以下几个部分:
55.1简介
5.2实验任务
5.3硬件设计
5.4软件设计
5.5下载验证
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存储器映射(memo
ry-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[0-7]为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[0:0]是由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[0:0]”连接起来。如下图所示:
图 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_ID XPAR_AXI_GPIO_0_DEVICE_ID //PL端 AXI GPIO器件 ID
14 #define GPIO_INT_ID XPAR_FABRIC_GPIO_0_VEC_ID //PL端 AXI GPIO中断 ID
15
16 #define MIO_LED 38 //PS_LED1 连接到 MIO38
17 #define KEY_CHANNEL 1 //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 {
35 printf("AXI GPIO INTERRUPT TEST!\n");
36
37 //初始化各器件驱动
38 instance_init();
39
40 //配置PS GPIO
41 XGpioPs_SetDirectionPin(&gpiops_inst, MIO_LED, 1); //设置 PS GPIO 为输出
42 XGpioPs_SetOutputEnablePin(&gpiops_inst, MIO_LED ,1); //使能 PS GPIO 输出
43 XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value); //点亮LED
44
45 //配置PL AXI GPIO
46 XGpio_SetDataDirection(&axi_gpio_inst, KEY_CHANNEL, 1); //设置PL AXI GPIO 通道1为输入
47 XGpio_InterruptEnable(&axi_gpio_inst, KEY_MASK); //使能通道1中断
48 XGpio_InterruptGlobalEnable(&axi_gpio_inst); //使能AXI GPIO全局中断
49
50 //设置中断优先级和触发类型(高电平触发)
51 XScuGic_SetPriorityTriggerType(&scugic_inst, GPIO_INT_ID, 0xA0, 0x1);
52 //关联中断ID和中断处理函数
53 XScuGic_Connect(&scugic_inst, GPIO_INT_ID, axi_gpio_handler, &axi_gpio_inst);
54 //使能AXI GPIO中断
55 XScuGic_Enable(&scugic_inst, GPIO_INT_ID);
56
57 //设置并打开中断异常处理功能
58 Xil_ExceptionInit();
59 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
60 (Xil_ExceptionHandler)XScuGic_InterruptHandler, &scugic_inst);
61 Xil_ExceptionEnable();
62
63 while(1);
64
65 return 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 //初始化中断控制器驱动
72 scugic_cfg_ptr = XScuGic_LookupConfig(SCUGIC_ID);
73 XScuGic_CfgInitialize(&scugic_inst, scugic_cfg_ptr, scugic_cfg_ptr->CpuBaseAddress);
74
75 //初始化PS端 GPIO驱动
76 gpiops_cfg_ptr = XGpioPs_LookupConfig(GPIOPS_ID);
77 XGpioPs_CfgInitialize(&gpiops_inst, gpiops_cfg_ptr, gpiops_cfg_ptr->BaseAddr);
78
79 //初始化PL端 AXI GPIO驱动
80 XGpio_Initialize(&axi_gpio_inst, AXI_GPIO_ID);
81 }
82
83 //PL端 AXI GPIO 中断服务(处理)函数
84 void axi_gpio_handler(void *CallbackRef)
85 {
86 int key_value = 1;
87 XGpio *GpioPtr = (XGpio *)CallbackRef;
88
89 print("Interrupt Detected!\n");
90 XGpio_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!”信息,说明本次实验下载验证成功。