实验三 按键操作—外部中断

一.实验目标

  1. 通过外部中断检测普通按键KEY1和KEY2是否按下,并通过LED1和LED2指示-每按一次,LED状态发生翻转;

  2. 通过本实验掌握STM32的中断基本概念,掌握对外部中断配置基本方法。

二.知识储备及设计思路

嵌套向量中断控制器简介

嵌套向量中断控制器 NVIC 包含以下特性:

● STM32F407xx 具有 82 个可屏蔽中断通道

● 16 个可编程优先级(使用了 4 位中断优先级)

● 低延迟异常和中断处理

● 电源管理控制

● 系统控制寄存器的实现

STM32F4外部中断简介

STM32F4的每个IO都可以作为外部中断的中断输入口,外部中断/事件控制器包含多达 23
个用于产生事件/中断请求的边沿检测器。每根输入线都可
单独进行配置,以选择类型(中断或事件)和相应的触发事件(上升沿触发、下降沿触发或边沿触发)。每根输入线还可单独屏蔽。挂起寄存器用于保持中断请求的状态线。

外部中断/事件控制器框图如图:

要产生中断,必须先配置好并使能中断线。根据需要的边沿检测设置 2
个触发寄存器,同时在
中断屏蔽寄存器的相应位写“1”使能中断请求。当外部中断线上出现选定信号沿时,便会产生中断请求,对应的挂起位也会置
1。在挂起寄存器的对应位写“1”,将清除该中断请求。

要产生事件,必须先配置好并使能事件线。根据需要的边沿检测设置 2
个触发寄存器,同时
在事件屏蔽寄存器的相应位写“1”允许事件请求。当事件线上出现选定信号沿时,便会产生事件脉冲,对应的挂起位不会置
1。

通过在软件中对软件中断/事件寄存器写“1”,也可以产生中断/事件请求。

STM32F407的23个外部中断为:

EXTI线0~15:对应外部IO口的输入中断。

EXTI线16:连接到PVD输出。

EXTI线17:连接到RTC闹钟事件。

EXTI线18:连接到USB OTG FS唤醒事件。

EXTI线19:连接到以太网唤醒事件。

EXTI线20:连接到USB OTG HS(在FS中配置)唤醒事件。

EXTI线21:连接到RTC入侵和时间戳事件。

EXTI线22:连接到RTC唤醒事件。

从上面可以看出,STM32F4供IO口使用的中断线只有16个,但是STM32F4的IO口却远远不止16个,那么STM32F4是怎么把16个中断线和IO口一一对应起来的呢?于是STM32就这样设计,GPIO的引脚GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G,H,I)分别对应中断线0~15。这样每个中断线对应了最多9个IO口,以线0为例:它对应了GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0,GPIOH.0,GPIOI.0。而中断线每次只能连接到1个IO口上,这样就需要通过配置来决定对应的中断线配置到哪个GPIO上了。下面我们看看GPIO跟中断线的映射关系图:

中断寄存器简介

当使用外部中断时,需要配置寄存器,在这里我们介绍部分EXIT寄存器:EXTI_IMR,EXTI_EMR
,EXTI_RTSR,EXTI_FTSR,EXTI_SWIER,EXTI_PR。

EXTI_IMR中断屏蔽寄存器用于开放/屏蔽来自 x
线的中断请求,寄存器如图:

EXTI_RTSR上升沿触发选择寄存器用于禁止/允许来自 x 线的上升沿触发,寄存器如图:

EXTI_FTSR下降沿触发选择寄存器用于禁止/允许来自 x 线的上升沿触发,寄存器如图:

EXTI_PR挂起寄存器,当外部发生了边沿事件,该位自动被置“1”,存寄存器如图:

配置EXIT

要使用外部中断,需要使能相应的寄存器,我们使用HAL库配置的时候,底层寄存器操作被封装成函数,我们只是需要调用相应的函数。

使用HAL库配置外部中断的一般步骤。HAL中外部中断相关配置函数和定义在文件stm32f4xx_hal_exti.h和stm32f4xx_hal_exti.c文件中。

1.I/O口作为中断输入,所以我们要使能相应的I/O口时钟;设置IO口模式,触发条件,开启SYSCFG时钟,设置IO口与中断线的映射关系。

SYSCFG 外部中断配置寄存器 2
(SYSCFG_EXTICR2),开启SYSCFG时钟和外部中断配置寄存器关系入图:

这部分代码在函数MX_GPIO_Init中实现:

__HAL_RCC_GPIOG_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

例如:我们这里初始化的是PG6,调用该函数后中断线6会自动连接到PG6。

2.
配置中断优先级(NVIC),并使能中断。我们设置好中断线和GPIO映射关系,然后又设置好了中断的触发模式等初始化参数。既然是外部中断,涉及到中断我们当然还要设置NVIC中断优先级。

中断优先级我们全部配置成4bit抢占优先级,这个在cube软件中配置,生成的代码如下:

HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0); HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

3. 编写中断服务函数

配置完中断优先级之后,接着要做的就是编写中断服务函数。中断服务函数的名字是在HAL库中事先有定义的。这里需要说明一下,STM32F4的IO口外部中断函数只有7个。使用cube生成的代码,中断服务函数为EXTI9_5_IRQHandler,这个函数在stm32fxx_ic.c中生成:

void EXTI9_5_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6); HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_7); }

HAL库为了用户使用方便,它提供了一个中断通用入口函数HAL_GPIO_EXTI_IRQHandler,在该函数内部直接调用回调函数HAL_GPIO_EXTI_Callback。

void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) { if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); HAL_GPIO_EXTI_Callback(GPIO_Pin); } }

该函数实现的作用非常简单,就是清除中断标志位,然后调用回调函数HAL_GPIO_EXTI_Callback()实现控制逻辑。所以我们编写中断控制逻辑将跟串口实验类似,在中断服务函数中直接调用外部中断共用处理函数HAL_GPIO_EXTI_IRQHandler,然后在回调函数HAL_GPIO_EXTI_Callback中通过判断中断是来自哪个IO口编写相应的中断服务控制逻辑。

三.引脚说明与硬件连接

图3.2.1(a) 普通按键KEY1,KEY2硬件连接图

图3.2.1(b) 普通按键KEY1,KEY2的管脚分配图

如图3.2.1(a)、(b)、(c)、(d)可知,普通按键KEY1、KEY2分别连接至GPIO PG6和PG7管脚,
LED1和LED2分别连接至GPIO
PF6和PF7管脚。当某一按键按下时,PG6(或PG7)产生中断,控制LED1(或LED2)发生一次翻转。

四.程序设计

在主函数main中,除了初始换函数后,在while循环中判断按键标志位是否为“1”,标志位为高的话,延时10ms左右,再次判断按键IO口状态,如果保持一致,翻转一次LED状态。代码如下:

int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); while (1) { if (key1Interrupt == 1){ // 响应PG6中断,将标志清零 key1Interrupt = 0; // 延迟10ms,按键消抖处理 HAL_Delay(10); // 再次判断PG6是否能为低电平,如果是,说明按键KEY1被按下 if (HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_6) == GPIO_PIN_RESET){ // 翻转LED1的状态 HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_6); } }else if (key2Interrupt == 1){ // 响应PG7中断,将标志清零 key2Interrupt = 0; // 延迟10ms,按键消抖处理 HAL_Delay(10); // 再次判断PG7是否能为低电平,如果是,说明按键KEY2被按下 if (HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_7) == GPIO_PIN_RESET){ // 翻转LED2的状态 HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_7); } } }

在回调函数HAL_GPIO_EXTI_Callback中,判断中断来自哪个管脚,然后置1相应的按键标志位。

// 按键中断回调函数,在中断环境中执行 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ if (GPIO_Pin == GPIO_PIN_6){ // 判断中断来自于PG6管脚 key1Interrupt = 1; __HAL_GPIO_EXTI_CLEAR_FLAG(GPIO_PIN_6); }else if (GPIO_Pin == GPIO_PIN_7){ // 判断中断来自于PG7管脚 key2Interrupt = 1; __HAL_GPIO_EXTI_CLEAR_FLAG(GPIO_PIN_7); } }

接下来,将介绍使用CUBE生成的初始化函数MX_GPIO_Init,函数的功能用于使能时钟,配置中断/GPIO。

void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOI_CLK_ENABLE(); __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOH_CLK_ENABLE(); __HAL_RCC_GPIOG_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOF, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET); /*Configure GPIO pins : PF6 PF7 */ GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOF, &GPIO_InitStruct); /*Configure GPIO pins : PG6 PG7 */ GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOG, &GPIO_InitStruct); /* EXTI interrupt init*/ HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0); //中断设置优先级 HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);//中断使能 }

五.实验结果

将编译好的代码下载到实验板中,按下复位键。

按下普通按键KEY1后,LED1点亮;再按KEY1后,LED1点灭。

按下普通按键KEY2后,LED2点亮;再按KEY2后,LED2点灭。

六.思考与拓展

  1. 按键所用的开关为机械弹性开关,由于弹性作用,按键开关闭合或断开瞬间会伴随一连串的抖动,因此要进行按键消抖。请尝试其他软件方式按键消抖处理。

七.STM32CubeMx配置GPIO

在cube中将按键输入IO口配置为外部中断模式。