STM32CubeMX学习笔记(5)--外部中断应用

348 阅读4分钟

1.开发环境

  1. 主控:STM32F401RCT6开发板
  2. 预期功能:使用板载按键,控制板载LED亮灭

外部中断的详细知识参考:

  1. juejin.cn/post/709927…
  2. blog.csdn.net/weixin_4625…

image.png

image.png

2.外部中断基础知识

STM32的每个GPIO都可以配置为外部中断。
中断触发电平变化有三种:下降沿、上升沿、高低电平
image.png

  • 下降沿触发:高电平变为低电平
  • 上升沿触发:低电平变为高电平
  • 高低电平触发:按下时触发一次,松开时再次触发中断。

触发方式两种:中断触发、事件触发

image.png

外部中断引脚的标志位: image.png

3.STM32CubeMX配置

配置板载按键PA0为外部中断输入,下降沿触发中断。 image.png

image.png

配置板载LED,PC13

image.png

4.编写业务代码

中断回调函数示意图:

image.png 当产生外部中断事件时,程序会跳转到中断处理函数:

void EXTI0_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI0_IRQn 0 */

  /* USER CODE END EXTI0_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(KEY_user_Pin);
  /* USER CODE BEGIN EXTI0_IRQn 1 */

  /* USER CODE END EXTI0_IRQn 1 */
}

但是中断处理函数中只简单标注了中断来源,然后退出中断,回到主函数。
回到主函数后,并不是继续执行之前中止的程序,而是自动调用中断回调函数,中断事件的处理都放在回调函数里。
可以在stm32f4xx_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)
{
  /* EXTI line interrupt detected */
  if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
  {
    __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)
{
  /* 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
   */
}
  1. 中断处理函数:清除中断标志位,调用中断回调函数。
  2. 中断回调弱函数:弱函数可以重新进行定义,在里面写中断处理的业务代码。

接下来我们就在 stm32f4xx_it.c 这个文件的最下面重定义中断回调函数HAL_GPIO_EXTI_Callback()
在中断回调函数里,不可以使用系统自带的延时函数HAL_Delay(),可以使用自己写的延时函数。
程序已验证可用!!!

在中断里使用HAL_Delay()延时卡死的原因:因为延时利用MCU中的滴答定时器中断来计时,按键的中断优先级比滴答定时器的优先级高,导致外部中断使用延时函数时,滴答定时器的中断无法执行,最终卡死在延时函数中。
解决方法1:可以给外部中断的优先级降级!

image.png
中断优先级设置的越小,优先级越高!修改中断优先级如下所示:

image.png

以下为不更改优先级的示例程序,已测试可以正常使用。

// 自定义延时函数
void delay_us(uint32_t us)
{
  uint32_t delay = (HAL_RCC_GetHCLKFreq() / 25000000 * us); // 数值与选用的晶振有关,25MHz无源晶振
  while (delay--)
    ; // 循环delay次,达到1us延时
}

// 按键检测函数
uint8_t KEY_1(void)
{
  uint8_t a = 0; // 按键未按下
  if (HAL_GPIO_ReadPin(KEY_user_GPIO_Port, KEY_user_Pin) == GPIO_PIN_RESET)
  {
    delay_us(20000); // 延时消抖

    // 再次检测按键是否按下
    if (HAL_GPIO_ReadPin(KEY_user_GPIO_Port, KEY_user_Pin) == GPIO_PIN_RESET)
    {
      a = 1;
    }
  }

  while (HAL_GPIO_ReadPin(KEY_user_GPIO_Port, KEY_user_Pin) == GPIO_PIN_RESET)
    ;              // 等待按键松开
  delay_us(20000); // 延时消抖,避免按键松开时的抖动
  return a;
}

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  UNUSED(GPIO_Pin); // 防报错的定义,当传进来的GPIO端口号没有做任何处理的时候,编译器也不会报出警告。

  if (GPIO_Pin == KEY_user_Pin) // 判断产生中断的端口
  {
    HAL_NVIC_DisableIRQ(EXTI0_IRQn); // 中断号,按键处理期间禁止再次触发中断,起到按键消抖的作用

    if (KEY_1())
    {
      HAL_GPIO_TogglePin(LED_user_GPIO_Port, LED_user_Pin);
    }

    HAL_NVIC_EnableIRQ(EXTI0_IRQn); // 按键处理完成后,开启中断允许
  }
}

补充:关于Time base: System tick timer的优先级设置问题

image.png 系统计时器,这个不需要很高的优先级,一般设置为最低就行。
注意:如果这个中断优先级与外部中断的优先级一样或更低,当在外部中断函数中使用延时函数,延时函数会被卡死。

20231224补充:使用一个全局变量,代替按键扫描

功能:按下按键点亮LED,再次按下按键熄灭LED

uint8_t flag = 1;

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    UNUSED(GPIO_Pin);
    if (GPIO_Pin == KEY_user_Pin)
    {
        HAL_NVIC_DisableIRQ(EXTI1_IRQn); // 在处理按钮时禁用中断
        // 切换 flag 变量
        flag = !flag;

        if (flag == 1)
        {
            HAL_GPIO_WritePin(LED_user_GPIO_Port, LED_user_Pin, GPIO_PIN_SET); // Turn on LED
        }
        else
        {
            HAL_GPIO_WritePin(LED_user_GPIO_Port, LED_user_Pin, GPIO_PIN_RESET); // Turn off LED
        }

        HAL_NVIC_EnableIRQ(EXTI1_IRQn); // 处理按钮后启用中断
    }
}