STM32 的外部中断(External Interrupt)是一种由外部硬件信号触发的中断机制,用于响应外部事件(如按键按下、外部传感器信号、信号变化等)。STM32 提供了灵活的中断管理机制,通过使用外部中断,可以有效地处理外部硬件信号的变化,而无需持续轮询。
1. 外部中断的基本概念
外部中断通常由外部硬件(例如按钮、传感器、开关、信号源等)触发。STM32 的外部中断可以由以下方式触发:
- 上升沿触发(例如按键按下的信号变化)
- 下降沿触发(例如按键释放的信号变化)
- 电平触发(高电平或低电平)
2. STM32 中的外部中断管理
STM32 使用 外部中断控制器(EXTI) 来管理外部中断。每个外部引脚(如 GPIO 引脚)都可以与 EXTI 线相连,STM32 提供了多个中断通道(取决于具体的 STM32 系列),包括16个GPIO_PIN,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒等。
3. STM32 的 EXTI 控制器
STM32 的 EXTI 控制器可以连接多个 GPIO 引脚,每个引脚可以配置成外部中断线。在 STM32F1 系列中,EXTI 控制器支持最多 16 条中断线,其他系列的 STM32 可能支持更多的中断线。
3.1. 中断线路与引脚的对应关系
- 每个外部引脚(例如,GPIOA、GPIOB、GPIOC 等)可以连接到对应的 EXTI 线。
- EXTI 线通过外部引脚的输入信号触发中断。例如,GPIOA 的 0 引脚可以触发 EXTI0 中断,GPIOB 的 1 引脚可以触发 EXTI1 中断,以此类推。
4. 外部中断的配置过程
在 STM32 中配置外部中断的流程包括以下几个步骤:
4.1. GPIO 配置
首先,需要对目标引脚进行配置,确保它作为输入引脚并且可以触发外部中断。例如,配置 GPIO 为输入模式,启用上拉或下拉电阻(根据需要)。
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 假设我们使用的是 GPIOA 的 0 引脚
__HAL_RCC_GPIOA_CLK_ENABLE(); // 启用 GPIOA 时钟
// 配置 GPIOA0 为输入,并启用上拉电阻
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; // 上升沿触发中断
GPIO_InitStruct.Pull = GPIO_PULLUP; // 启用上拉电阻
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
4.2. EXTI 配置
配置引脚对应的 EXTI 线。STM32 提供了 HAL_NVIC_SetPriority()
和 HAL_NVIC_EnableIRQ()
来配置中断的优先级,并使能相应的中断线。
// 设置外部中断优先级,并使能 EXTI0 中断
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); // 优先级 0
HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 使能 EXTI0 中断
4.3. 中断处理函数
每当外部中断触发时,STM32 会自动跳转到与之对应的中断服务程序(ISR)。例如,EXTI0 中断会触发 EXTI0_IRQHandler()
函数。这个函数会调用 HAL_GPIO_EXTI_IRQHandler()
来处理具体的中断事件。
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); // 调用 HAL 库的中断处理函数
}
在 HAL_GPIO_EXTI_IRQHandler()
中,调用了 HAL_GPIO_EXTI_Callback()
回调函数,允许用户在其中编写自己的中断处理代码。
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_0) // 如果是 GPIOA0 引脚触发的中断
{
// 执行按键按下的处理逻辑
}
}
4.4. 中断处理总结
当外部中断发生时,STM32的中断控制器会调用中断向量表中与该中断相关的中断服务程序(ISR)。
例如,如果你配置了 PA0 为外部中断,并且发生了中断,STM32的硬件会自动触发 EXTI0_IRQHandler()
,而 EXTI0_IRQHandler()
会调用 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0)
。
然后,HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0)
会调用 HAL_GPIO_EXTI_Callback()
,并将 GPIO_PIN_0
作为参数传递给回调函数。这时,用户的回调函数 HAL_GPIO_EXTI_Callback()
会根据 GPIO_Pin
来执行特定的操作。
5. EXTI 中断的触发方式
STM32 的外部中断可以通过以下几种方式进行触发:
- 上升沿触发(Rising edge):当信号从低电平跳变到高电平时触发中断。
- 下降沿触发(Falling edge):当信号从高电平跳变到低电平时触发中断。
- 双沿触发(Both edges):当信号在两个边沿(上升沿和下降沿)时都会触发中断。
- 电平触发(Level trigger):当信号处于高电平或低电平时,持续触发中断,直到电平变化。
在 STM32 中配置引脚的方式决定了触发方式。例如,使用 GPIO_MODE_IT_RISING
配置上升沿触发,GPIO_MODE_IT_FALLING
配置下降沿触发。
6. 外部中断的中断服务程序(ISR)
STM32 中断处理程序通过中断向量表来触发。当外部中断发生时,STM32 会跳转到对应的中断服务程序(ISR)。对于 EXTI0 中断,ISR 为 EXTI0_IRQHandler()
。在该函数中,用户可以使用 HAL_GPIO_EXTI_IRQHandler()
来调用 HAL 库中的中断处理程序。
7. 外部中断的优先级
STM32 允许用户设置中断的优先级。每个中断都可以被分配一个优先级,优先级越低的中断会先执行。用户可以使用 HAL_NVIC_SetPriority()
函数来设置中断优先级,并使用 HAL_NVIC_EnableIRQ()
来使能中断。
8. 外部中断的中断嵌套
STM32 支持中断嵌套,即在处理中断时,低优先级的中断可以被高优先级的中断打断。可以通过配置 NVIC(嵌套向量中断控制器)来控制中断的优先级。
9. 总结
STM32 外部中断的基本流程包括:
- 配置 GPIO 引脚为输入模式,并设置中断触发条件。
- 启用外部中断的时钟和中断线路。
- 配置中断优先级和中断服务程序。
- 在中断回调函数中编写自己的业务逻辑。
通过外部中断,STM32 可以高效地响应外部事件,而无需轮询,减少了处理器的负担。
10. 中断函数封装示例
如果我们希望让代码能顺利移植到其他型号的STM单片机,我们可以对配置中断的模块进行封装,接下来以一个按键(KEY1)进行举例
10.1. 新建.c / .h文件
为了增强代码的易读性,我们对于每个模块应当在头文件中申明函数,在.c文件中实现其功能。我们可以在工程文件中新建一个文件夹(KEY1-Drive),里面新建好key1_driver.c和key1_driver.h,并在Keil uVision5中新建一个组(GROUP),用来管理这个文件夹。
10.2. 编辑头文件
在key1.h
中
- 定义了按键
KEY1
相关的宏,包括 GPIO 端口、引脚、时钟使能函数等。 KEY1_Init()
函数用于按键初始化,配置 PA1 为上升沿触发中断。KEY1_IRQHandler()
是外部中断服务程序,用于处理中断。HAL_GPIO_EXTI_Callback()
是 HAL 库提供的回调函数,用于按键中断处理。
#ifndef __KEY1_H__
#define __KEY1_H__
#include "stm32f1xx_hal.h" // 适配 STM32F1 库,若使用其他系列的 STM32,修改为对应的 HAL 库
// 按键 KEY1 的引脚和端口定义
#define KEY1_GROUP GPIOA // 按键连接的端口
#define KEY1_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() // 启用 GPIOA 时钟
#define KEY1_PIN GPIO_PIN_1 // 按键连接的引脚
// 外部中断处理函数原型
void KEY1_Init(void); // 按键初始化
define KEY1_IRQHandler EXTI1_IRQHandler // 外部中断服务函数
#endif /* __KEY1_H__ */
10.3. 编辑.c文件
在.c文件中,我们应当完成以下流程
KEY1_Init()
:初始化 GPIO 和中断,设置 PA1 为输入引脚,并启用上拉电阻。该引脚通过上升沿触发外部中断。EXTI1_IRQHandler()
:中断处理程序。该函数由 STM32 自动调用,负责触发 HAL 库的中断处理函数HAL_GPIO_EXTI_IRQHandler()
。HAL_GPIO_EXTI_Callback()
:由HAL_GPIO_EXTI_IRQHandler()
调用。在此函数中,你可以编写中断触发时的业务逻辑。
#include "key1.h"
// 按键初始化函数
void KEY1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 启用 GPIOA 时钟
KEY1_CLK_ENABLE();
// 配置 PA1 为输入模式,上升沿触发中断
GPIO_InitStruct.Pin = KEY1_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; // 上升沿触发
GPIO_InitStruct.Pull = GPIO_PULLUP; // 启用上拉电阻
HAL_GPIO_Init(KEY1_GROUP, &GPIO_InitStruct);
// 配置中断优先级并使能 EXTI1 中断
HAL_NVIC_SetPriority(EXTI1_IRQn, 0, 0); // 优先级 0
HAL_NVIC_EnableIRQ(EXTI1_IRQn); // 使能 EXTI1 中断
}
// 外部中断服务程序 (ISR)
void EXTI1_IRQHandler(void)
{
// 调用 HAL 库的中断处理函数
HAL_GPIO_EXTI_IRQHandler(KEY1_PIN);
}
// 按键中断回调函数 (用户可根据需求编写处理逻辑)
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == KEY1_PIN) // 判断是否是 KEY1 引脚触发的中断
{
// 在此处编写按键按下时的处理逻辑,例如:
// 可以设置一个标志位,或者调用其他函数
// 例如:Toggle_LED(); // 假设有一个LED切换函数
}
}
10.4. 在mian函数中进行调用
在主程序中调用 KEY1_Init()
初始化按键:
int main(void)
{
HAL_Init(); // 初始化 HAL 库
KEY1_Init(); // 初始化按键
while (1)
{
// 主循环中的其他任务
}
}
当按下 KEY1 按键并触发上升沿时,会调用 HAL_GPIO_EXTI_Callback()
回调函数,可以在其中添加按键事件的处理逻辑。