1、外部中断是什么?
通俗的来说,单片机的外部中断(External Interrupts, EXTI)是 MCU 中很重要的功能之一,它允许单片机对外部事件做出快速响应。
当连接到某个GPIO引脚的中断线检测到预设的电平变化或边沿变化时,就会产生一个中断请求,使CPU暂停当前任务,转而去执行相应的中断服务程序。这样,单片机就能够及时地处理外部事件,提高了系统的实时性和灵活性。
STM32 的外部中断/事件控制器(EXTI) 由20个产生事件/中断请求的边沿检测器组成,每个输入线可以独立地配置输入类型(脉冲或挂起)和对应的触发事件(上升沿或下降沿或者双边沿都触发)。
STM32的外部中断/事件控制器框图如下,实现外部事件触发的逻辑电路示意如图中的红色标线位置。
STM32F103 提供了多个外部中断线(EXTI),这些中断线可以连接到不同的GPIO引脚,也就是所有的IO中只要能作为输入/输出功能使用的都支持外部中断触发功能。
通用 I/O 端口以 EXTIn 中断控制的方式连接到16个外部中断/事件线上,分别为 EXTI0 ... EXTI15,如下:
配置使用外部中断的大致操作流程为:想要产生外部中断,必须先配置好并使能中断线,根据需要的边沿检测(上升沿/下降沿)设置触发寄存器,同时在中断屏蔽寄存器的相应位写‘1’允许中断请求。当外部中断线上发生了期待的边沿时,将产生一个中断请求,对应的挂起位也随之被置‘1’,通过挂起寄存器的对应位写‘1’,将清除该中断请求。
2、STM32CUBEMX 配置外部中断
本文使用实验板上的按键作为外部中断的演示,板载按键的连接原理图如下:
使用 WK_UP 和 KEY0 两个按键分别实现外部中断的下降沿和上升沿的触发外部中断配置。
对应连接的IO口如下:
STM32CUBEMX 的配置如下:
(1)打开 cubemx 选择 Pinout & Configuration,选择 PA0 和 PE4 配置为外部中断功能。
(2)选择 Pinout & Configuration 中的 GPIO 配置外部中断的属性。
(3)中断的触发方式选择。
WK_UP 连接 PA0 引脚,按键按下该引脚是高电平,所以可以把这个引脚默认为下拉,上升沿方式触发中断。
KEY0 连接 PE4 引脚,按键按下该引脚是低电平,所以可以把这个引脚默认为上拉,下降沿方式触发中断。
(4)选择 Pinout & Configuration 中的 NVIC 使能外部中断,并设置抢占优先级和子优先级。
(5)点 GENERATE CODE 生成代码。用 keil 打开代码,在 void MX_GPIO_Init(void) 函数中能找到相关的配置代码:
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = KEY_0_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(KEY_0_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = KEY_WK_UP_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(KEY_WK_UP_GPIO_Port, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
HAL_NVIC_SetPriority(EXTI4_IRQn, 3, 0);
HAL_NVIC_EnableIRQ(EXTI4_IRQn);
}
(6)中断函数。
外部中断的函数入口在 stm32f1xx_it.c 中,如下:
/**
* @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(KEY_WK_UP_Pin);
/* USER CODE BEGIN EXTI0_IRQn 1 */
/* USER CODE END EXTI0_IRQn 1 */
}
/**
* @brief This function handles EXTI line4 interrupt.
*/
void EXTI4_IRQHandler(void)
{
/* USER CODE BEGIN EXTI4_IRQn 0 */
/* USER CODE END EXTI4_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(KEY_0_Pin);
/* USER CODE BEGIN EXTI4_IRQn 1 */
/* USER CODE END EXTI4_IRQn 1 */
}
进入中断函数以后,会再调用 HAL_GPIO_EXTI_IRQHandler();
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}
这部分代码主要用于判断中断标志位是否发生,并且要清理发生中断的标志位,防止一直进入中断调用。
最后再调用 HAL_GPIO_EXTI_Callback(); 处理中断事务。这个函数 HAL 库中定义了一个 weak 型的函数,这个需要自己定义一个函数用于执行外部中断事务,如果不定义则会使用库中已经定义的 weak 型的回调函数。
比如我这里在代码中其他地方重新定义这个中断回调函数,当中断被检测到的时候,翻转板子上的LED灯,每个按键对应一颗LED,如下:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
if(GPIO_Pin == KEY_WK_UP_Pin)
{
HAL_GPIO_TogglePin(LED1_PORT_GPIO_Port, LED1_PORT_Pin);
}
if(GPIO_Pin == KEY_0_Pin)
{
HAL_GPIO_TogglePin(LED0_PORT_GPIO_Port, LED0_PORT_Pin);
}
}