MCU按键输入外部中断详解及HAL_GPIO_EXTI_IRQHandler()实现方法指南
目录
一、 硬件板及设计目的
二、建立工程
1.配置GPIO
2.配置时钟源和Debug
3.配置系统时钟
4.配置NVIC
三、代码编写
四、修改HAL_GPIO_EXTI_IRQHandler()
一、 硬件板及设计目的
本文使用的硬件板是ST的开发板NUCLEO-G474RE,板上MCU型号为STM32G474RET6。并按照资源提示设计制造了扩展IO板,有需要此扩展板的留言联系我。
本例设计目的及其功能和操作流程如下。
用户标签 |
|
引脚名称 |
引脚功能 |
GPIO模式 |
默认电平 |
上拉或下拉 |
LED1 |
|
PB11 |
GPIO_Output |
推挽输出 |
High |
上拉 |
LED2 |
|
PB12 |
GPIO_Output |
推挽输出 |
High |
上拉 |
KeyRight |
K1 |
PA0 |
EXTI0 |
输入 |
|
上拉 |
KeyDown |
K2 |
PA1 |
EXTI1 |
输入 |
|
上拉 |
KeyLeft |
K3 |
PA6 |
EXTI[9:5] |
输入 |
|
上拉 |
KeyUp |
K5 |
PA7 |
EXTI[9:5] |
输入 |
|
上拉 |
二、建立工程
1.配置GPIO
2.配置时钟源和Debug
打开System Core中的RCC,高速时钟(HSE)选择Crystal/ eramic Resonator,使用片外时钟晶体作为HSE的时钟源。在SYS中将Debug设置Serial Wire。
3.配置系统时钟
将系统时钟(SYSCLK)频率配置为170 MHz。
4.配置NVIC
配置Time base的抢占式优先级为0;配置EXTI0、EXTI1的抢占式优先级为1;配置EXTI[9:5]的抢占式优先级为2;这样处理后,当优先级1的中断执行期间,触发优先级为2的中断时不会及时响应,直到优先级为1的中断执行完毕,才去执行优先级为2的中断。
三、代码编写
为实现设计目的,只需要在main.c的程序里添加外部中断的回调函数就可以。
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == KeyUp_Pin) //PA7=KeyUp, 使两个LED输出翻转
{
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);
HAL_Delay(500); //软件消除按键抖动的影响
}
else if(GPIO_Pin == KeyRight_Pin) //PA0=KeyRight, 使LED2 输出翻转
{
HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin);
HAL_Delay(500); //软件消除按键抖动的影响,观察优先级的作用
}
else if (GPIO_Pin == KeyDown_Pin) //PA1=KeyDown,产生EXTI0 软中断
{
__HAL_GPIO_EXTI_GENERATE_SWIT(GPIO_PIN_0); //产生EXTI0 软中断
HAL_Delay(500); //这个延时也是必要的,否则由于按键抖动,会两次触发
}
else if (GPIO_Pin == KeyLeft_Pin) //PA6=KeyLeft, 使LED1输出翻转
{
HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
HAL_Delay(1000); //软件消除按键抖动的影响,观察优先级的作用
}
}
/* USER CODE END 4 */
四、修改HAL_GPIO_EXTI_IRQHandler()
完成回调函数的代码后,下载到开发板上进行测试时,按键按下后的响应并不如预期(预期的现象是每按下一个按键,翻转对应的LED,再次按下按键,再翻转)。例如按下 Keyup键后,两个LED会出现无规律的现象:亮灭两次、或不亮、或亮了后又熄灭,这不是按键抖动影响的。这是由ISR中调用的外部中断通用处理函数HAL_GPIO_EXTI_IRQHandler()的代码引起的,这个函数的代码如下:
/**
* @brief Handle EXTI interrupt request.
* @param GPIO_Pin Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
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);
}
}
这个函数在检测到中断挂起标志后,先清除中断挂起标志,然后再执行回调函数。一般的中断通用处理函数都是这样的处理流程,是为了硬件能及时响应下一次中断。但是对于检测按键输入的外部中断,这是有问题的,因为清除中断挂起标志后,按键的抖动就会触发下一次中断,并将中断挂起标志置位。虽然在回调函数里使用了延时,但是回调函数退出后,NVIC检测到中断挂起标志被置位,就会再执行一次回调函数。
所以,对于外部中断方式的按键输入检测,需要修改一下HAL_GPIO_EXTI_IRQHandler() 的代码,将清除中断挂起标志位的功能放在后面,即修改为如下的代码,这样修改后的程序运行就实现了设计想要达到的目的了。
/**
* @brief Handle EXTI interrupt request.
* @param GPIO_Pin Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
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_Callback(GPIO_Pin);
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
}
}
需要注意的是,函数HAL_GPIO_EXTI_IRQHandler()是文件stm32g4xx_hal_gpio.c中的,这是HAL驱动的原始文件,这个函数里并没有代码沙箱,也不是弱函数,不可以重写。修改这个函数的代码后,在CubeMX 重新生成代码时,这个函数的代码又会变成原来的样子。所以,一定要记得再次改回去。
作者:wenchm