1)实验平台:正点原子MPSoC开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html
第四章GPIO之MIO按键中断实验
中断是一种当满足要求的突发事件发生时通知处理器进行处理的信号。中断可以由硬件处理单元和外部设备产生,也可以由软件本身产生。对硬件来说,中断信号是一个由某个处理单元产生的异步信号,用来引起处理器的注意。对软件来说,中断还是一种异步事件,用来通知处理器需要改变代码的执行,不过,轮询所产生的中断的过程是同步的。
本章我们将学习GPIO中MIO(包括EMIO)接口中断信号的使用。本章包括以下几个部分:
44.1简介
4.2实验任务
4.3硬件设计
4.4软件设计
4.5下载验证
4.1简介
当处理器收到中断,它会停下当前正在做的任务,然后跳转到需要处理的地方去。这和轮询的方式是相反的,轮询是由软件同步获取设备的状态。在中断方式中,不需要由处理器不断地轮询设备的I/O端口来查看是否需要处理,设备本身会中断处理器。中断(主要是硬件中断)可以进一步被分类为以下几种类型:
• 可屏蔽中断(Maskable Interrupts,IRQ)—可通过在中断屏蔽寄存器中设定位掩码来关闭。触发可屏蔽中断的事件源不总是重要的。程序设计人员需要决定该事件是否应该导致程序跳到所需处理的地方去。使用可屏蔽中断的设备包括定时器、比较器和ADC。
• 不可屏蔽中断(Non-Maskable Interrupts,NMI)—无法通过在中断屏蔽寄存器中设定位掩码来关闭。这些是不可忽视的中断。NMI的事件包括上电、外部重启(用实际的按钮)和严重的设备失效。
• 处理器间中断(Inter-Processor Interrupts,IPI)—在多处理器系统中,一个处理器可能需要中断另一个处理器的操作。在这种情况下,就会产生一个IPI,以便于处理器间通信或同步。
在ARM的SOC系统中,会有多个外设,均有可能会产生中断发送给ARM CPU,等待CPU处理。因此就需要有一个中断控制器来作为中间的桥接,收集SOC的所有中断信号,然后仲裁选择合适的中断,再发送给CPU,等待CPU处理。这中间的桥接器件,就是ARM公司推出的GIC(general interrupt controller,通用中断控制器)。
当对应的中断源有效时,GIC根据该中断源的配置,决定是否将该中断信号,发送给CPU。如果有多个中断源有效,那么GIC还会进行仲裁,选择最高优先级中断,发送给CPU。
当CPU接受到GIC发送的中断,通过读取GIC的寄存器,就可以知道,中断的来源来自于哪里,从而可以做相应的处理。
当CPU处理完中断之后,会告诉GIC,其实就是访问GIC的寄存器,该中断处理完毕。GIC接受到该信息后,就将该中断源取消,避免又重新发送该中断给CPU以及允许中断抢占。
Zynq Ultrascale+ MPSOC包含两个中断控制器(GIC),分别是符合GICv2架构规范的Arm GIC-400通用中断控制器APU GIC和符合GICv1架构规范的Arm PL-390通用中断控制器(PL390)。中断控制器框图如下图所示:
图 4.1.1 中断控制器框图
上图中GIC-400是APU中断控制器,其功能划分如下图所示:
图4.1.2 APU中断控制器
APU中断控制器被分为两部分,第一部分是分发器(Distributor),用来登记传送进来的中断并对它们进行排序,然后将中断送到正确的目标CPU。distributor对中断提供以下的功能:
–全局中断使能
–每个中断的使能
–中断的优先级
–中断的分组
–中断的目的core
–中断触发方式
–对于SGI中断,传输中断到指定的core
–每个中断的状态管理
–提供软件,可以修改中断的pending状态
中断控制器第二部分(CPU Interface)和每个CPU的中断线连接,用来触发相关Cortex-A53的中断。
cpu interface提供了一下的功能:
–将中断请求发送给cpu
–对中断进行认可(acknowledging an interrupt)
–中断完成识别(indicating completion of an interrupt)
–设置中断优先级屏蔽
–定义中断抢占策略
–决定当前处于pending状态最高优先级中断
每个Cortex-A53有四个中断线作为输入,分别是普通优先级中断nIRQ,高优先级(或快速)中断nFIQ,普通优先级虚拟中断nVIRQ,高优先级(或快速)虚拟中断nFIQ。
首先我们来看通用中断控制器。通用中断控制器是一个用于集中管理从PS和PL发送到CPU的中断,启用、禁用、屏蔽和优先化中断源的处理中心,将具有最高优先级的中断源分配给各个CPU之前集中所有中断源,并在CPU接口接受下一个中断时以编程方式将它们发送到选定的CPU。GIC寄存器通过CPU私有总线访问寄存器,以避免临时阻塞或互连中的瓶颈,从而实现快速读/写响应。GIC确保针对多个CPU的中断一次只能由一个CPU占用。所有中断源都由唯一的中断ID号标识,对应有它自己的可配置优先级和目标CPU列表。
APU中断控制器处理三种类型中断:16个软件产生的中断(SGI),7个私有外设中断(PPI),92个共享外设中断(SPI)。接下来我们依次来看软件生成中断、CPU私有外设中断和共享外设中断。
每个CPU都可以使用软件生成的中断来中断自身、另一个CPU或同时中断两个CPU。有16个软件生成中断,具体见表 4.1.1。向软件产生的中断寄存器(GICD_SGIR)写入SGI中断编号并指定目标CPU(或两个CPU),就产生了一个SGI。该写操作通过CPU自己的专用(私有)总线进行。每个CPU都有自己的一组SGI寄存器,用于生成16个软件生成的中断中的一个或多个。所有的SGI都是边沿触发的,且其敏感性类型是固定的,不能修改。
表 4.1.1 软件生成中断
表 4.1.2 私有外设中断 (PPI)
信号 来自PL的IRQ信号
共享外设中断(SPI)是一种可以被分配器路由到任意指定处理器的外设中断。这些送到GIC的线性中断源来自各种中断源。
了解了软件生成中断SGI、CPU私有外设中断PPI和共享外设中断SPI后,我们来看下中断优先级定序。
所有的中断请求,无论是PPI、SGI还是SPI,都分配了一个唯一的ID编号,以用于中断控制器的仲裁。中断分派(配)器保存每个CPU的中断挂起列表,并从中选择优先级最高的中断,然后把它发送到CPU接口。如果具有相同优先级的两个中断同时到达,具有最低中断 ID的会首先被发送。
每个 CPU 都存在着优先级定序逻辑,所以对最高优先级中断的选择是每个CPU各自进行的。中断分配器具有中断、处理器和活跃信息的中央列表,并负责触发CPU的软件中断。为了给每个处理器提供单独的副本,SGI和PPI分派器寄存器是分组的。硬件确保针对多个CPU的中断同一时间只能被一个CPU获取。
在发送挂起的最高优先级的中断给CPU接口后,中断分配器会从该CPU收到中断已被确认的消息,这样它就可以改变对应的中断的状态。只有确认中断的CPU才能结束该中断。
RPU中断控制器框图如下图所示,这里不作详细介绍。
图4.1.3 RPU中断控制器框图
以上我们大概了解了Zynq Ultrascale+ MPSOC的中断。下面我们来看作为GPIO的MIO的中断。上章我们介绍了GPIO通道的下半部分,现在我们来看其上半部分,也就是关于中断的部分,如下图所示:
图4.1.4 GPIO通道
左边有7个寄存器,说明如下:
INT_MASK:这个寄存器是只读的,显示哪些位当前被屏蔽,哪些位未被屏蔽/启用。
INT_EN:向该寄存器的任何位写入1,可以启用/解除中断信号的掩码。从该寄存器读取将返回一个不可预测的值。
INT_DIS:向该寄存器的任何位写入1都会屏蔽该中断信号。从该寄存器读取会返回不可预测的值。
INT_STAT:该寄存器显示是否发生了中断事件。将1写入该寄存器中的某个位可清除该位的中断状态。将0写入该寄存器中的某个位将被忽略。
INT_TYPE:该寄存器控制中断是边沿敏感还是电平敏感。
INT_POLARITY:该寄存器控制中断是低电平有效还是高电平有效(或下降沿敏感或上升沿敏感)。
INT _ANY:如果INT_TYPE设置为边沿敏感,则该寄存器在上升沿和下降沿都会启用中断事件。如果INT_TYPE设置为电平敏感,则忽略该寄存器。
从INT_TYPE、INT_POLARITY和INT _ANY寄存器我们可以看到中断触发方式可以是上升沿,下降沿,边沿,低电平或高电平。
从图4.1.4中我们可以看到,INT_TYPE、INT_POLARITY和INT _ANY控制监视GPIO输入信号的中断检测逻辑。如果检测到中断,中断检测逻辑将GPIO的INT_STAT状态设置为真。如果中断未屏蔽,则中断传输到一个或电路(图中未画出)。该或电路将四个BANK中所有GPIO的所有中断组合成一个输出(IRQ ID#48)到中断控制器。如果中断被禁止(屏蔽),则INT_STAT状态将保持直到被清除,但它不会传输到中断控制器,除非稍后写入INT_EN以禁用屏蔽。由于所有GPIO共享相同的中断,因此软件必须同时考虑INT_MASK和INT_STAT以确定哪个GPIO导致中断。
通过向INT_EN和INT_DIS寄存器写入1来控制中断屏蔽状态。向INT_EN寄存器写入1将禁用屏蔽,允许活动中断传输到中断控制器。将1写入INT_DIS寄存器可启用屏蔽。可以使用INT_MASK寄存器读取中断屏蔽的状态。
如果GPIO中断是边沿触发的,则INT状态由检测逻辑锁存。通过向INT_STAT寄存器写入1来清除INT锁存器。对于电平触发的中断,必须清零GPIO中断输入源,以清除中断信号。或者,软件可以使用INT_DIS寄存器屏蔽该输入。
可以通过读取INT_STAT和INT_MASK寄存器来推断进入中断控制器的中断信号的状态。如果INT_STAT = 1且INT_MASK = 0,则该中断信号有效。
4.2实验任务
本章的实验任务是使用PS的MIO按键中断控制LED的亮灭。
4.3硬件设计
图 4.3.1 按键原理图
PS端的按键没有按下时,对应的IO端口为高电平;当按键按下时,对应的IO端口变为低电平。
根据实验任务我们可以画出本次实验的系统框图,如下图所示:
图 4.3.2 系统框图
从图我们可以看到,我们选用的是KEY按键是PS_KEY1,LED是开发板上的PS_LED1。
本章的硬件设计部分与《第二章 MIO控制LED》实验完全相同。为了方便管理工程,我们不直接在原工程上开始实验,而是将其另存为gpio_mio_interrupt实验工程。
step1:创建Vivado工程
1-1 我们打开《第二章 MIO控制LED》实验的Vivado工程,打开后选择菜单栏的File-> Project->Save As…,如下图所示:
图 4.3.3 选择另存为
1-2 在弹出的另存为界面中可以输入新的工程名或更改保存位置,此处我们输入新的工程名“gpio_mio_interrupt”,工程位置保持默认,如下图所示:
图 4.3.4 工程另存为
然后点击“OK”按钮。
工程另存为之后,因硬件设计无需改变,所以以下的
step2:使用IP Integrator创建Processing System
step3:生成顶层HDL
就不再需要了,直接进入step4。
step4:硬件设计导出Hardware
4-1 依次点击File -> Export -> Export Hardware,在弹出的窗口中点击“OK”,新建vitis文件夹,将生成的xsa文件放到里面,如下图所示:
图 4.3.5 更改xsa文件路径
4-2 硬件导出完成后,在菜单栏选择Tools > Launch Vitis,启动Vitis开发环境。在弹出的对话框中,将路径指定到新建的vitis文件夹下,点击Launch启动Vitis。
到这里我们的硬件设计部分已经结束,接下来的软件设计部分需要在Vitis软件中进行。
4.4软件设计
在硬件设计的最后,我们打开了Vitis开发环境,下面我们开始第五步——创建应用工程。
step5:在Vitis中创建应用工程
5-1在菜单栏依次点击“File->New->Application Project”,新建一个Vitis应用工程。在弹出的对话框中,输入工程名“gpio_emio”,其他默认,点击“Next”,如下图所示:
图 4.4.1 创建工程
5-2 打开“Create a new platform hardware(XSA)”标签页,点击“+”添加我们自己的硬件平台,如下图所示:
图 4.4.2 添加硬件平台文件
5-3 打开我们导出的硬件平台文件,然后点击“Next”。
图 4.4.3 选择要添加的xsa文件
5-4 在弹出的页面中保持默认设置,然后点击“Next”。
图 4.4.4 保持默认设置
5-5 因为本章将自行创建工程文件,所以选择空工程模版“Empty Application”,然后点击“Finish”按钮,如下图所示:
图 4.4.5 选择空工程模板
创建完工程后的页面如下图所示:
图 4.4.6 工程创建完成
5-6新建源文件。首先我们在gpio_mio_interrupt/src目录上右键,选择New-> File,如下图所示:
图 4.4.7 新建源文件
在弹出的对话框中File name一栏输入文件名“main.c”,然后点击“Finish”。
图 4.4.8 输入文件名
5-7 输入源代码。我们在新建的main.c文件中输入以下代码:
1 #include "xparameters.h"
2 #include "xgpiops.h"
3 #include "xscugic.h"
4 #include "xil_exception.h"
5 #include "xplatform_info.h"
6 #include <xil_printf.h>
7 #include "sleep.h"
8
9 /************************** Constant Definitions *****************************/
10
11 //以下常量映射到xparameters.h文件
12 #define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID //PS端GPIO器件ID
13 #define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID //通用中断控制器ID
14 #define GPIO_INTERRUPT_ID XPAR_XGPIOPS_0_INTR //PS端GPIO中断ID
15
16 //定义使用到的MIO引脚号
17 #define KEY 40 //PS_KEY1 连接到 MIO40
18 #define LED 38 //PS_LED1 连接到 MIO38
19 #define LED2 39 //PS_LED2 连接到 MIO39
20
21 /************************** Function Prototypes ******************************/
22
23 static void intr_handler(void *callback_ref);
24 int setup_interrupt_system(XScuGic *gic_ins_ptr, XGpioPs *gpio, u16 GpioIntrId);
25
26 /**************************Global Variable Definitions ***********************/
27
28 XGpioPs gpio; //PS端GPIO驱动实例
29 XScuGic intc; //通用中断控制器驱动实例
30 u32 key_press; //KEY按键按下的标志
31 u32 key_val; //用于控制LED的键值
32
33 /************************** Function Definitions *****************************/
34
35 int main(void)
36 {
37 int status;
38 XGpioPs_Config *ConfigPtr; //PS 端GPIO配置信息
39
40 xil_printf("Gpio interrupt test \r\n");
41
42 //根据器件ID查找配置信息
43 ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
44 if (ConfigPtr == NULL) {
45 return XST_FAILURE;
46 }
47 //初始化Gpio driver
48 XGpioPs_CfgInitialize(&gpio, ConfigPtr, ConfigPtr->BaseAddr);
49
50 //设置KEY所连接的MIO引脚的方向为输入
51 XGpioPs_SetDirectionPin(&gpio, KEY, 0);
52
53 //设置LED所连接的MIO引脚的方向为输出并使能输出
54 XGpioPs_SetDirectionPin(&gpio, LED, 1);
55 XGpioPs_SetOutputEnablePin(&gpio, LED, 1);
56 XGpioPs_WritePin(&gpio, LED, 0x0);
57
58 //设置LED2连接的MIO引脚方向为输出并使能输出
59 XGpioPs_SetDirectionPin(&gpio, LED2, 1);
60 XGpioPs_SetOutputEnablePin(&gpio, LED2, 1);
61 XGpioPs_WritePin(&gpio, LED2, 0x0); //PS_LED2默认关闭;
62
63 //建立中断,出现错误则打印信息并退出
64 status = setup_interrupt_system(&intc, &gpio, GPIO_INTERRUPT_ID);
65 if (status != XST_SUCCESS) {
66 xil_printf("Setup interrupt system failed\r\n");
67 return XST_FAILURE;
68 }
69
70 //中断触发时,key_press为TURE,延时一段时间后判断按键是否按下,是则反转LED
71 while (1) {
72 if (key_press) {
73 usleep(20000);
74 if (XGpioPs_ReadPin(&gpio, KEY) == 0) {
75 key_val = ~key_val;
76 XGpioPs_WritePin(&gpio, LED, key_val);
77 }
78 key_press = FALSE;
79 XGpioPs_IntrClearPin(&gpio, KEY); //清除按键KEY中断
80 XGpioPs_IntrEnablePin(&gpio, KEY); //使能按键KEY中断
81 }
82 }
83 return XST_SUCCESS;
84 }
85
86 //中断处理函数
87 // @param CallBackRef是指向上层回调引用的指针
88 static void intr_handler(void *callback_ref)
89 {
90 XGpioPs *gpio = (XGpioPs *) callback_ref;
91
92 //读取KEY按键引脚的中断状态,判断是否发生中断
93 if (XGpioPs_IntrGetStatusPin(gpio, KEY)){
94 key_press = TRUE;
95 XGpioPs_IntrDisablePin(gpio, KEY); //屏蔽按键KEY中断
96 }
97 }
98
99 //建立中断系统,使能KEY按键的下降沿中断
100 // @param GicInstancePtr是一个指向XScuGic驱动实例的指针
101 // @param gpio是一个指向连接到中断的GPIO组件实例的指针
102 // @param GpioIntrId是Gpio中断ID
103 // @return 如果成功返回XST_SUCCESS, 否则返回XST_FAILURE
104 int setup_interrupt_system(XScuGic *gic_ins_ptr, XGpioPs *gpio, u16 GpioIntrId)
105 {
106 int status;
107 XScuGic_Config *IntcConfig; //中断控制器配置信息
108
109 //查找中断控制器配置信息并初始化中断控制器驱动
110 IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
111 if (NULL == IntcConfig) {
112 return XST_FAILURE;
113 }
114
115 status = XScuGic_CfgInitialize(gic_ins_ptr, IntcConfig,
116 IntcConfig->CpuBaseAddress);
117 if (status != XST_SUCCESS) {
118 return XST_FAILURE;
119 }
120
121 //设置并使能中断异常
122 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
123 (Xil_ExceptionHandler) XScuGic_InterruptHandler, gic_ins_ptr);
124 Xil_ExceptionEnable();
125
126 //为中断设置中断处理函数
127 status = XScuGic_Connect(gic_ins_ptr, GpioIntrId,
128 (Xil_ExceptionHandler) intr_handler, (void *) gpio);
129 if (status != XST_SUCCESS) {
130 return status;
131 }
132
133 //使能来自于Gpio器件的中断
134 XScuGic_Enable(gic_ins_ptr, GpioIntrId);
135
136 //设置KEY按键的中断类型为下降沿中断
137 XGpioPs_SetIntrTypePin(gpio, KEY, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
138
139 //使能按键KEY中断
140 XGpioPs_IntrEnablePin(gpio, KEY);
141
142 return XST_SUCCESS;
143 }
在代码的第17至19行,我们指定了PS端的按键,LED和LED2所连接的MIO引脚编号,其中LED2没有用到,在后面的程序中设置其固定输出低电平。这些编号可以从开发板的原理图中查到。
代码第64行我们调用了建立中断函数。代码104行至143行完成了按键中断的的建立。程序首先对中断控制器进行初始化,随后设置并打开中断异常处理的功能。接下来为按键中断设置中断处理函数,通过XScuGic_Connect 函数进行设置。XScuGic_Connect函数建立中断源ID与识别中断时要运行的关联处理程序之间的连接,这里设置的按键中断处理函数为intr_handler。XGpioPs_SetIntrTypePin函数用于设置单个PS GPIO引脚的中断类型。进入其函数定义处,我们可以看到如下类型:
图 4.4.9 GPIO中断类型
总共有五种中断类型,分别是上升沿、下降沿、双边沿、高电平和低电平。此处我们使用的下降沿中断。
设置完中断类型后,就使能按键KEY中断。
成功建立中断后,就等待中断触发,当CPU检测到KEY引脚下降沿时,触发中断。程序进入代码第88行的中断处理函数。该函数的主要作用是读取KEY按键引脚的中断状态,判断是否是按键发生中断,是则将key_press设置为TURE,并屏蔽按键KEY中断。
此时代码72行的if语句执行。先用usleep函数延时20ms进行消抖。延时完成后读取KEY按键的状态,如果为0代表按下,则反转LED的显示状态,然后清除按键KEY中断位并使能按键KEY中断。等待下次中断触发。
5-8编译工程。保存main.c文件,右键点击应用工程gpio_mio_interrupt,在弹出的菜单中选择Build Project,如下图所示:
图 4.4.10 编译工程
工程编译结束后,在Binaries下成功生成gpio_emio.elf文件。
至此,硬件和软件设计部分均已完成。
4.5下载验证
完成了硬件设计和软件设计后,我们就可以进行板级验证了,也就是设计流程的最后一步。在进行板级验证之前,我们先将开发板上的JTAG与电脑连接,然后使用USB连接线将USB UART(PS_PORT)接口与电脑连接,然后连接开发板的电源,给开发板上电。
图 4.5.1 MPSOC开发板实物图
step6:板级验证
6-1打开串口助手或具有串口功能的软件。串口助手是上位机中用于辅助串口调试的小工具,可以选择安装使用开发板随附资料中“6_软件资料/1_软件/ XCOM V2.0”的文件夹中提供的串口助手,也可从网上下载或选择自己常用的串口调试工具。这里我们使用Vitis软件自带的串口终端。
按照《Hello World》实验中的步骤,打开Vitis中的Terminal窗口,如下图所示:
图 4.5.2 打开Vitis串口终端
我们进入该窗口后,点击上图箭头所指的图标,在弹出的窗口中对串口进行设置,选择串口“Serial Terminal”,COM口依据自己的电脑设置,波特率为“115200”,数据位为8位,停止位为1位,如下图所示:
图 4.5.3 设置UART串口
点击“OK”,串口终端成功连接,如下图所示:
图 4.5.4 串口连接成功
6-2下载程序。右键选择gpio_mio_interrupt工程,在弹出的菜单中选择Run as-> Launch on Hardware,如下图所示:
图 4.5.5 下载程序
软件程序下载完成后,在下方的Terminal中可以看到应用程序打印的信息“Gpio interrupt test”,如下图所示:
图 4.5.6 串口终端中打印的信息
我们按下开发板PS端的用户按键PS_KEY1,可以看到对应的PS端的 PS_LED1灯在每次按键按下时亮灭的状态反转。说明我们通过MIO按键中断控制PS端LED的实验在MPSOC开发板上面下载验证成功。实验结果如下图所示:
图 4.5.7 按下PS_KEY1时开发板运行结果