文章目录
- 一、STM32F4 外部中断简介
- 二、硬件设计
- 三、软件设计
- 四、实验现象
- 五、STM32CubeMX 配置外部中断
本章我们将介绍如何将
STM32F429
的
IO
口作为外部中断输入。
一、STM32F4 外部中断简介
这里首先介绍STM32F4 IO
口中断的一些基础概念。STM32F4
的每个 IO
都可以作为外部中断的中断输入口,这点也是 STM32F4
的强大之处。STM32F429
的中断控制器支持 22
个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F429
的 22
个外部中断为:
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
跟中断线的映射关系图:
GPIO
和中断线映射关系是在寄存器SYSCFG_EXTICR1~ SYSCFG_EXTICR4
中配置的。所以我们要配置外部中断,还需要打开 SYSCFG
时钟。
接下来我们来看看使用 HAL
库配置外部中断的一般步骤。HAL
中外部中断相关配置函数和定义在文件 stm32f4xx_hal_exti.h
和 stm32f4xx_hal_exti.c
文件中。
-
使能 IO 口时钟。
首先,我们要使用IO
口作为中断输入,所以我们要使能相应的IO
口时钟。 -
设置 IO 口模式,触发条件,开启 SYSCFG 时钟,设置 IO 口与中断线的映射关系。
在函数HAL_GPIO_Init
中一次性完成。例如我们要设置PA0
链接中断线 0,并且为上升沿触发,代码为:GPIO_InitTypeDef GPIO_Initure; GPIO_Initure.Pin=GPIO_PIN_0; //PA0 GPIO_Initure.Mode=GPIO_MODE_IT_RISING; //外部中断,上升沿触发 GPIO_Initure.Pull=GPIO_PULLDOWN; //默认下拉 HAL_GPIO_Init(GPIOA,&GPIO_Initure);
当我们调用
HAL_GPIO_Init
设置IO
的Mode
值为GPIO_MODE_IT_RISING
(外部中断上升 沿 触 发 ),GPIO_MODE_IT_FALLING
(外部中断下降沿触发)或者GPIO_MODE_IT_RISING_FALLING
(外部中断双边沿触发)的时候,该函数内部会通过判断
Mode
的值来开启SYSCFG
时钟,并且设置IO
口和中断线的映射关系。
因为我们这里初始化的是PA0
,调用该函数后中断线 0 会自动连接到PA0
。如果某个时间,我们又同样的方式初始化了PB0
,那么PA0
与中断线的链接将被清除,而直接链接PB0
到中断线 0。 -
配置中断优先级(NVIC),并使能中断。
我们设置好中断线和GPIO
映射关系,然后又设置好了中断的触发模式等初始化参数。既然是外部中断,涉及到中断我们当然还要设置NVIC
中断优先级。设置中断线 0 的中断优先级并使能外部中断 0 的方法为:HAL_NVIC_SetPriority(EXTI0_IRQn,2,1); //抢占优先级为 2,子优先级为 1 HAL_NVIC_EnableIRQ(EXTI0_IRQn); //使能中断线 0
-
编写中断服务函数。
我们配置完中断优先级之后,接着要做的就是编写中断服务函数。中断服务函数的名字是在HAL
库中事先有定义的。这里需要说明一下,STM32F4
的IO
口外部中断函数只有 7 个,分别为:void EXTI0_IRQHandler(); void EXTI1_IRQHandler(); void EXTI2_IRQHandler(); void EXTI3_IRQHandler(); void EXTI4_IRQHandler(); void EXTI9_5_IRQHandler(); void EXTI15_10_IRQHandler();
中断线
0-4
每个中断线对应一个中断函数,中断线5-9
共用中断函数EXTI9_5_IRQHandler
,中断线10-15
共用中断函数EXTI15_10_IRQHandler
。一般情况下,我们可以把中断控制逻辑直接编写在中断服务函数中,但是HAL
库把中断处理过程进行了简单封装。 -
编写中断处理回调函数 HAL_GPIO_EXTI_Callback
HAL
库为了用户使用方便,它提供了一个中断通用入口函数HAL_GPIO_EXTI_IRQHandler
,在该函数内部直接调用回调函数HAL_GPIO_EXTI_Callback
。我们可以看看HAL_GPIO_EXTI_IRQHandler
函数定义:
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
口编写相应的中断服务控制逻辑。
下面总结一下配置IO
口外部中断的一般步骤:
- 使能
IO
口时钟。 - 调用函数
HAL_GPIO_Init
设置IO
口模式,触发条件,使能SYSCFG
时钟以及设置IO
口与中断线的映射关系。 - 配置中断优先级(
NVIC
),并使能中断。 - 在中断服务函数中调用外部中断共用入口函数
HAL_GPIO_EXTI_IRQHandler
。 - 编写外部中断回调函数
HAL_GPIO_EXTI_Callback
。
通过以上几个步骤的设置,我们就可以正常使用外部中断了。
二、硬件设计
本实验用到的硬件资源有:
- 指示灯
DS0
、DS1
。 - 4 个按键:
KEY0
、KEY1
、KEY2
、和KEY_UP
。
在阿波罗 STM32
开发板上的按键 KEY0
连接在 PH3
上、KEY1
连接在 PH2
上、KEY2
连接在 PC13
上、KEY_UP
连接在 PA0
上。如图所示:
这里需要注意的是:KEY0
、KEY1
和 KEY2
是低电平有效的(按下是低电平),而 KEY_UP
是高电平有效的(按下是高电平),并且外部都没有上下拉电阻,所以,需要在 STM32F429
内部设置上下拉。输入模式配置如下:
PH3
:上拉输入PH2
:上拉输入PC13
:上拉输入PA0
:下拉输入
三、软件设计
我们直接复制“按键输入实验”的工程模板,将复制过来的模板文件夹重新命名为“4-外部中断实验”。在HARDWARE->EXTI
文件夹下面新建exti.c
文件以及头文件 exti.h
。
打开exti.c
文件,代码如下:
#include "exti.h"
#include "delay.h"
#include "led.h"
#include "key.h"
//外部中断初始化
void EXTI_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOA_CLK_ENABLE(); //开启GPIOA时钟
__HAL_RCC_GPIOC_CLK_ENABLE(); //开启GPIOC时钟
__HAL_RCC_GPIOH_CLK_ENABLE(); //开启GPIOH时钟
GPIO_Initure.Pin=GPIO_PIN_0; //PA0
GPIO_Initure.Mode=GPIO_MODE_IT_RISING; //上升沿触发
GPIO_Initure.Pull=GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_13; //PC13
GPIO_Initure.Mode=GPIO_MODE_IT_FALLING; //下降沿触发
GPIO_Initure.Pull=GPIO_PULLUP;
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_2|GPIO_PIN_3; //PH2,3
HAL_GPIO_Init(GPIOH,&GPIO_Initure);
//中断线0-PA0
HAL_NVIC_SetPriority(EXTI0_IRQn,2,0); //抢占优先级为2,子优先级为0
HAL_NVIC_EnableIRQ(EXTI0_IRQn); //使能中断线0
//中断线2-PH2
HAL_NVIC_SetPriority(EXTI2_IRQn,2,1); //抢占优先级为2,子优先级为1
HAL_NVIC_EnableIRQ(EXTI2_IRQn); //使能中断线2
//中断线3-PH3
HAL_NVIC_SetPriority(EXTI3_IRQn,2,2); //抢占优先级为2,子优先级为2
HAL_NVIC_EnableIRQ(EXTI3_IRQn); //使能中断线2
//中断线13-PC13
HAL_NVIC_SetPriority(EXTI15_10_IRQn,2,3); //抢占优先级为2,子优先级为3
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); //使能中断线13
}
//中断服务函数
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);//调用中断处理公用函数
}
void EXTI2_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);//调用中断处理公用函数
}
void EXTI3_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);//调用中断处理公用函数
}
void EXTI15_10_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);//调用中断处理公用函数
}
//中断服务程序中需要做的事情
//在HAL库中所有的外部中断服务函数都会调用此函数
//GPIO_Pin:中断引脚号
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
delay_ms(100); //消抖
switch(GPIO_Pin)
{
case GPIO_PIN_0:
if(WK_UP==1)
{
LED1=!LED1;//控制LED0,LED1互斥点亮
LED0=!LED1;
}
break;
case GPIO_PIN_2:
if(KEY1==0) //LED1翻转
{
LED1=!LED1;
}
break;
case GPIO_PIN_3:
if(KEY0==0) //同时控制LED0,LED1翻转
{
LED0=!LED0;
LED1=!LED1;
}
break;
case GPIO_PIN_13:
if(KEY2==0)
{
LED0=!LED0;//控制LED0翻转
}
break;
}
}
其头文件exti.h
为
#ifndef _EXTI_H
#define _EXTI_H
#include "sys.h"
void EXTI_Init(void);
#endif
exti.c
文件总共包含 6 个函数。外部中断初始化函数 void EXTIX_Init
用来配置 IO
口外部中断相关步骤并使能中断,另一个函数HAL_GPIO_EXTI_Callback
是外部中断共用回调函数,用来处理所有外部中断真正的控制逻辑。其他 4 个都是中断服务函数。
void EXTI0_IRQHandler(void)
是外部中断 0 的服务函数,负责KEY_UP
按键的中断检测;void EXTI2_IRQHandler(void)
是外部中断 2 的服务函数,负责KEY2
按键的中断检测;void EXTI3_IRQHandler(void)
是外部中断 3 的服务函数,负责KEY1
按键的中断检测;void EXTI4_IRQHandler(void)
是外部中断 4 的服务函数,负责KEY0
按键的中断检测;
下面我们分别介绍这几个函数。
首先是外部中断初始化函数 void EXTIX_Init(void)
,该函数内部主要做了两件事情。首先是调用 IO
口初始化函数 HAL_GPIO_Init
来初始化 IO
口,其次是设置中断优先级并使能中断线。接下来我们看看外部中断服务函数,一共 4 个。所有的中断服务函数内部都只调用了同样一个函数 HAL_GPIO_EXTI_IRQHandler
,该函数是外部中断共用入口函数,函数内部会进行中断标志位清零, 并且调用中断处理共用回调函数HAL_GPIO_EXTI_Callback
。
最后是外部中断回调函数 HAL_GPIO_EXTI_Callback
,该函数用来编写真正的外部中断控制逻辑。该函数有一个入口参数就是 IO
口序号。所以我们在该函数内部,一般通过判断 IO
口序号值来确定中断是来自哪个 IO
口,也就是哪个中断线,然后编写相应的控制逻辑。所以在该函数内部,我们通过 switch
语句判断 IO
口来源。
主函数main
函数代码如下:
int main(void)
{
HAL_Init(); //初始化 HAL 库
Stm32_Clock_Init(360,25,2,8); //设置时钟,180Mhz
delay_init(180); //初始化延时函数
uart_init(115200); //初始化 USART
LED_Init(); //初始化 LED
EXTI_Init(); //外部中断初始化
while(1)
{
printf("OK\r\n"); //打印 OK 提示程序运行
delay_ms(1000); //每隔 1s 打印一次
}
}
在 while
死循环中不停的打印字符串到串口。当有某个外部按键按下之后,会触发中断服务函数做出相应的反应。
四、实验现象
使用 USB 线将开发板和电脑连接成功后(电脑能识别开发板上 CH340 串口),把编译后产生的.hex 文件烧入到芯片内。在串口调试助手里面可以看到如图所示:
此时,按下 KEY0
、KEY1
、KEY2
和KEY_UP
可以观察到 DS0
、DS1
跟着按键的变化而变化。
五、STM32CubeMX 配置外部中断
对于外部中断的配置,首先在 MCU
引脚配置图界面选择相应的 GPIO
设置其模式为外部中断模式。
然后我们打开依次点击 Configuration->GPIO
进入 GPIO
详细配置界面 Pin Configuration
,界面会列出 4 个 IO
口的配置信息。
从上图界面可以看出,当我们配置 IO
口作为外部中断触发引脚之后,其详细配置界面便只有三个选项。第一个选项 GPIO mode
用来设置外部中断触发方法,上升沿触发还是下降沿触发还是双边沿触发。第二个选项 GPIO Pull-up/Pull-down
用来设置是默认上拉还是下拉。这里除了设置 PA0
为上升沿触发默认下拉外,其他 IO
口都设置为下降沿触发默认上拉。
配置好 IO
口信息之后,接下来就需要配置 NVIC
中断优先级设置。依次点击Configuration->NVIC
,进入 NVIC
配置界面。在界面可以看到有四个外部中断线可配置,这是因为我们前面开启了四个 IO
口的外部中断(对应 4 个外部中断线)。依次配置四个中断线的 NVIC 即可,配置完成后如下图所示:
&esmp;最后生成工程,在 main.c
中生成的函数 MX_GPIO_Init
和我们实验工程中 exti.c
文件中的函数 EXTI_Init
内容一致,在stm32f4xx_it.c
中生成了 4 个中断服务函数,和 exti.c
文件中的中断服务函数内容一致。当然,回调函数 HAL_GPIO_EXTI_Callback
的内容软件是无法自动生成的,需要我们自己编写。
/** Configure pins as
* Analog
* Input
* Output
* EVENT_OUT
* EXTI
*/
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin : PC13 */
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/*Configure GPIO pin : PA0 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/*Configure GPIO pins : PH2 PH3 */
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
HAL_NVIC_SetPriority(EXTI2_IRQn, 2, 1);
HAL_NVIC_EnableIRQ(EXTI2_IRQn);
HAL_NVIC_SetPriority(EXTI3_IRQn, 2, 2);
HAL_NVIC_EnableIRQ(EXTI3_IRQn);
HAL_NVIC_SetPriority(EXTI15_10_IRQn, 2, 3);
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}
/******************************************************************************/
/* STM32F4xx Peripheral Interrupt Handlers */
/* Add here the Interrupt Handlers for the used peripherals. */
/* For the available peripheral interrupt handler names, */
/* please refer to the startup file (startup_stm32f4xx.s). */
/******************************************************************************/
/**
* @brief This function handles EXTI line0 interrupt.
*/
void EXTI0_IRQHandler(void)
{
/* USER CODE BEGIN EXTI0_IRQn 0 */
/* USER CODE END EXTI0_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
/* USER CODE BEGIN EXTI0_IRQn 1 */
/* USER CODE END EXTI0_IRQn 1 */
}
/**
* @brief This function handles EXTI line2 interrupt.
*/
void EXTI2_IRQHandler(void)
{
/* USER CODE BEGIN EXTI2_IRQn 0 */
/* USER CODE END EXTI2_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
/* USER CODE BEGIN EXTI2_IRQn 1 */
/* USER CODE END EXTI2_IRQn 1 */
}
/**
* @brief This function handles EXTI line3 interrupt.
*/
void EXTI3_IRQHandler(void)
{
/* USER CODE BEGIN EXTI3_IRQn 0 */
/* USER CODE END EXTI3_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);
/* USER CODE BEGIN EXTI3_IRQn 1 */
/* USER CODE END EXTI3_IRQn 1 */
}
/**
* @brief This function handles EXTI line[15:10] interrupts.
*/
void EXTI15_10_IRQHandler(void)
{
/* USER CODE BEGIN EXTI15_10_IRQn 0 */
/* USER CODE END EXTI15_10_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
/* USER CODE BEGIN EXTI15_10_IRQn 1 */
/* USER CODE END EXTI15_10_IRQn 1 */
}