单片机-STM32部分:8、外部中断
飞书文档https://x509p6c8to.feishu.cn/wiki/ENFswNTSGiblehkIMtfc9dYinqh
创建工程
按工程创建章节步骤,把工程创建好,并配置外部时钟源,SWD调试模式,时钟72MHz。
设置引脚模式
找到需要配置的按键,例如PC2,将KEY所在的引脚PC2配置为GPIO_EXIT模式。因为在第2脚,所以是EXIT2,又叫做中断线2。
EXIT模式 = external interrupt trigger mode = 外部中断触发模式
EXTI2中的2指的是中断线2,STM32有20个EXTI线,可以产生不同的中断事件
外部中断控制器框图
图中的20代表在控制器内部类似的信号线路有 20 个,这与 EXTI 总共有 20 个中断/事件线是吻合的。 |
配置GPIO
打开GPIO设置,选中外部中断引脚,这里是PC2。
GPIO mode |
开启外部中断
在NVIC(嵌套向量中断控制器)中,勾选EXIT Line2 interrupt使能PC2中断。
然后配置工程名称,输出MDK工程,并且设置只复制必要的库文件,每个模块初始化代码生成单独的.c/.h文件,最后生成工程。
打开工程后,我们可以看到以下文件生成了对应的代码,那如何添加中断触发后的执行代码呢?
"gpio.c"
void MX_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOD_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/*Configure GPIO pin : PtPin */GPIO_InitStruct.Pin = KEY2_Pin;GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;GPIO_InitStruct.Pull = GPIO_NOPULL;HAL_GPIO_Init(KEY2_GPIO_Port, &GPIO_InitStruct);/* EXTI interrupt init*/HAL_NVIC_SetPriority(EXTI2_IRQn, 0, 0);HAL_NVIC_EnableIRQ(EXTI2_IRQn);
}"stm32f1xx_it.c"
/*** @brief This function handles EXTI line2 interrupt.*/
void EXTI2_IRQHandler(void)
{HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
}"stm32f1xx_hal_gpio.c"
/*** @brief This function handles EXTI interrupt request.* @param GPIO_Pin: Specifies the pins connected EXTI line* @retval None*/
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u){__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);HAL_GPIO_EXTI_Callback(GPIO_Pin);}
}/*** @brief EXTI line detection callbacks.* @param GPIO_Pin: Specifies the pins connected EXTI line* @retval None*/
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{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*/
}函数名称前面加上__weak 修饰符,我们一般称这个函数为“弱函数”。加上了__weak 修饰符的函数,用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak 声明的函数,并且编译器不会报错。所以我们可以在别的地方定义一个相同名字的函数,而不必也尽量不要修改之前的函数。
所以,我们可以在main.c中重写这个函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ if(GPIO_Pin == KEY2_Pin) //判断中断来自哪个IO{//do something}
}
同理,我们可以在执行代码中,添加LED的翻转操作,例如,HAL_GPIO_TogglePin(xxx, xxx); 这样,就能更直观看到是否有中断产生了。
如果发现按下按键后,有时灯的状态会发生改变,有时又不会发生改变,这是正常现象,因为机械按键在断开与闭合时,在电路中会产生一些抖动,这些抖动可能会重复触发外部中断,看似只按了一次按键,然而触发了多次中断,致使LED发生了多次翻转。
此时可以通过去抖解决,去抖方式有很多
1、中断中直接加延时去抖,不允许,可能会其它中断被延迟或错过。
2、可以设置开启上升下降沿中断,计算上升沿与下降沿之间的时差,如果太短则滤除
3、中断触发后,由定时器中断计算并消抖
想要编写高效的代码,应该尽可能减少延时函数的使用。
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){if(GPIO_Pin == KEY2_Pin){//中断内如果一定要用HAL_Delay//需要设置System tick中断的抢占优先级比改外部中断高,否则会出现卡死HAL_Delay(10);if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == 0){HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);}}
}
/* USER CODE END 4 *//* USER CODE BEGIN 4 */
#define DEBOUNCE_TIME 50 // 去抖时间,单位为ms
uint32_t falling_tick = 0;
uint32_t rising_tick = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ if(GPIO_Pin == KEY2_Pin) //判断中断来自哪一条中断线{if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == 0){//触发中断后,如果读取到IO电平是低,则是下降沿falling_tick = HAL_GetTick();}else if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == 1 && falling_tick != 0){//触发中断后,如果读取到IO电平是高,则是上升沿rising_tick = HAL_GetTick();if (rising_tick - falling_tick > DEBOUNCE_TIME){// 按键按下需要执行的代码HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);}falling_tick = rising_tick = 0;}}
}
/* USER CODE END 4 */
多个中断同时触发,会执行那个?
依赖中断优先级的设置,简单介绍一下NVIC(嵌套向量中断控制器)。NVIC就是控制中断响应的。主要由三个参数,一个是中断使能,一个是抢占优先级(主优先级),还有一个就是响应优先级(子优先级),优先级数值越小,优先级别越高。
Enabled:中断使能 |
子优先级和子优先级相同的情况下,根据中断向量表确定。中断向量表到单片机数据手册,或者Cubemx配置表中查看。