STM32
直播中

贾桂林

8年用户 1583经验值
私信 关注
[问答]

如何使用STM32的外部中断功能读取按键输入

如何使用STM32的外部中断功能读取按键输入?
如何使用软件消抖来消除按键输入产生的电压的抖动?

回帖(1)

董超

2021-11-17 15:42:57
  STM32 CubeMX学习:6. 按键的外部中断
    1 基础知识
  1.1 按键原理图
  在探索者 STM32F4开发板上的按键 KEY0 连接在 PE4 上、KEY1 连接在 PE3 上、KEY2 连接在 PE2 上、KEY_UP连接在 PA0 上。如图所示:
  
  在这块开发板上,KEY0、KEY1 和 KEY2 是低电平有效的,而 KEY_UP 是高电平有效的,并且外部都没有上下拉电阻,所以,我们需要在 STM32F4 内部设置上下拉。
  1.2 按键软件消抖
  考虑到真实的情况,由于按键的机械结构具有弹性,按下时开关不会立刻接通,断开时也不会立刻断开,这就导致按键的输入信号在按下和断开时都会存在抖动,如果不先将抖动问题进行处理,则读取的按键信号可能会出现错误。
  为了消除这一问题,我们可以通过软件消抖或者硬件消抖两种方式来实现,我们在这里主要采用软件滤波的实现方法。软件滤波的思想其实非常简单,大家很容易就明白了。抖动的产生在按键按下和松开的两个边沿时刻,也叫下降沿(电平从高到低)和上升沿(电平从低到高)时刻,所以我们只需要在边沿时进行延时,等到按键输入已经稳定再进行信号读取即可,是不是很简单呢?
  一般采用软件消抖时,会进行20ms的延时,示波器采集按键波形如图所示。
  
  1.3 外部中断
  单片机的外部中断通常是由GPIO的电平跳变引起的中断。在STM32中,每一个GPIO都可以作为外部中断的触发源,外部中断一共有16条线,对应着GPIO的0-15引脚,每一条外部中断都可以与任意一组的对应引脚相连,但不能重复使用。
  例如,外部中断Line0可以和PA0,PB0,PC0等任意一条0号引脚相连,但如果已经和PA0相连,就不能同时和PB0,PC0其他引脚相连。大家在规划中断的时候要格外小心呦。
  外部中断支持GPIO的三种电平跳变的模式,如下所示:
  上升沿中断:当GPIO的电平从低电平跳变成高电平时,引发外部中断。
  下降沿中断:当GPIO的电平从高电平跳变成低电平时,引发外部中断。
  上升沿和下降沿中断:当GPIO的电平从低电平跳变成高电平和从高电平跳变成低电平时,都能引发外部中断。
  2 程序的学习
  2.1 按键的外部中断在CubeMX里的配置
  STM32的GPIO提供外部中断功能,当GPIO检测到电压跳变时,就会发出中断触发信号给STM32,使程序进入外部中断服务函数。
  (今天的程序,我们可以在LED灯的基础之上完成)
  将PA0号引脚设置为按键的输入引脚,将其设置为外部中断模式。
  
  接着点开GPIO标签页,对引脚进行如下设置,将GPIO模式设置为升降沿触发的外部中断,上下拉电阻设置为下拉电阻,最后设置用户标签为WK_UP。
  
  在NVIC标签页下,可以看到外部中断已经开启。
  
  点击“GENERATE CODE”生成代码
  2.2 HAL_GPIO_ReadPin函数介绍
  HAL库提供了读取引脚上的电平的函数HAL_GPIO_ReadPin。该函数说明如下:
  GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) 此函数的返回参数为GPIO_PinState,如果是高电平则返回GPIO_PIN_SET(对应为1),如果是低电平则返回GPIO_PIN_RESET(对应为0);
  该函数的作用在于返回引脚电平;
  该函数具有两个参数:
  (1)GPIOx——对应GPIO总线,其中x可以是A…I。例如,PH10,则输入GPIOH
  (2)GPIO_Pin——对应引脚数。可以是0-15。例如,PH10,则输入GPIO_PIN_10
  2.3 中断回调函数介绍
  每当产生外部中断时,程序首先会进入外部中断服务函数。在stm32f4xx_it.c中,可以找到函数EXTI0_IRQHandler,它通过调用函数HAL_GPIO_EXTI_IRQHandler对中断类型进行判断,并对涉及中断的寄存器进行处理,在处理完成后,它将调用中断回调函数HAL_GPIO_EXTI_Callback,在中断回调函数中编写在此次中断中需要执行的功能。
  2.4 程序中的前后台
  在本次实验中,发现主循环和中断回调函数中都有代码。这是一个非常典型的以前后台模式组织的工程。但是,什么是前后台模式呢?我们可以想象一下一个餐厅的运作模式,餐厅往往分为前台的叫餐员和后台的大厨,前台只有在来了客人,或者后台做好了一道菜时才会工作,而后厨则一直在忙着做菜,只有前台来了新的单子或者已经有菜做好了才会停下一会手中的活。
  在单片机中,中断就是前台,而循环就是后台,中断只在中断源产生时才会进行相应的处理,而循环则一直保持工作,只有被中断打断时才会暂停。前后台程序的异同可以参见下表:
  编写前后台程序时,需要注意尽量避免在前台程序中执行过长或者过于耗时的代码,让前台程序能够尽快执行完毕,以保证其能够实时响应突发的事件,比较繁杂和耗时的任务一般放在后台程序中处理。
  前后台模式可以帮助我们提高单片机的时间利用率,从而组织起比较复杂的工程。
  2.5 程序流程
  我们今天的程序中前后台任务各自承担的任务为:
  前台程序——记录按键翻转的状态rising_falling_flag
  后台程序——执行处理工作,根据记录的翻转状态进行按键状态的判断
  在主循环中,首先通过边沿检测标志 rising_falling_flag 来判断按键是处于按下还是松开的边沿,如果是下降的边沿(rising_falling_flag == GPIO_PIN_RESET)则将LED灯熄灭,如果是如果是上升的边沿(rising_falling_flag == GPIO_PIN_SET)则将LED灯点亮。为了防止误触发,通过边沿检测的判断之后,程序还会再对电平进行一次读取,确认下降沿后跟随的是低电平或者上升沿后跟随的是高电平,如果不是则不切换LED状态。
  在中断回调函数中,利用HAL_GPIO_ReadPin对rising_falling_flag进行赋值,从而判断触发中断的是上升沿还是下降沿。
  使用exit_flag来实现主循环和中断回调函数之间的互斥,保证中断处理函数中的功能(判断上升/下降沿)只在主循环完成判断之后进行,或者主循环的判断只在中断处理函数运行(即检测到了一次上升沿或者下降沿)之后再进行。
  最终main.c文件如下所示
  /* USER CODE BEGIN Header */
  /**
  ******************************************************************************
  * @file : main.c
  * @brief : Main program body
  ******************************************************************************
  * @attention
  *
  * 《h2》《center》© Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.《/center》《/h2》
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the “License”; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  * opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
  /* USER CODE END Header */
  /* Includes ------------------------------------------------------------------*/
  #include “main.h”
  #include “gpio.h”
  /* Private includes ----------------------------------------------------------*/
  /* USER CODE BEGIN Includes */
  /* USER CODE END Includes */
  /* Private typedef -----------------------------------------------------------*/
  /* USER CODE BEGIN PTD */
  /* USER CODE END PTD */
  /* Private define ------------------------------------------------------------*/
  /* USER CODE BEGIN PD */
  /* USER CODE END PD */
  /* Private macro -------------------------------------------------------------*/
  /* USER CODE BEGIN PM */
  /* USER CODE END PM */
  /* Private variables ---------------------------------------------------------*/
  /* USER CODE BEGIN PV */
  /* USER CODE END PV */
  /* Private function prototypes -----------------------------------------------*/
  void SystemClock_Config(void);
  /* USER CODE BEGIN PFP */
  /* USER CODE END PFP */
  /* Private user code ---------------------------------------------------------*/
  /* USER CODE BEGIN 0 */
  //backgroud program
  //后台程序
  uint8_t exit_flag = 0;
  uint8_t rising_falling_flag;
  /**
  * @brief exit callback function
  * @param[in] GPIO_Pin:gpio pin
  * @retval none
  */
  /**
  * @brief 外部中断回调
  * @param[in] GPIO_Pin:引脚号
  * @retval none
  */
  void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
  {
  if(GPIO_Pin == WK_UP_Pin)
  {
  if(exit_flag == 0)
  {
  exit_flag = 1;
  rising_falling_flag = HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin);
  }
  }
  }
  /* USER CODE END 0 */
  /**
  * @brief The application entry point.
  * @retval int
  */
  int main(void)
  {
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */
  /* MCU Configuration--------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* USER CODE BEGIN Init */
  /* USER CODE END Init */
  /* Configure the system clock */
  SystemClock_Config();
  /* USER CODE BEGIN SysInit */
  /* USER CODE END SysInit */
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */
  HAL_GPIO_WritePin( LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET );
  /* USER CODE END 2 */
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */
  /* USER CODE BEGIN 3 */
  //foreground program
  //前台程序
  if(exit_flag == 1)
  {
  exit_flag = 2;
  if(rising_falling_flag == GPIO_PIN_RESET)
  {
  //debouce
  //消抖
  HAL_Delay(20);
  if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin) == GPIO_PIN_RESET)
  {
  HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
  exit_flag = 0;
  }
  else
  {
  exit_flag = 0;
  }
  }
  else if(rising_falling_flag == GPIO_PIN_SET)
  {
  //debouce
  //消抖
  HAL_Delay(20);
  if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port, WK_UP_Pin) == GPIO_PIN_SET)
  {
  HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
  exit_flag = 0;
  }
  else
  {
  exit_flag = 0;
  }
  }
  }
  }
  /* USER CODE END 3 */
  }
  /**
  * @brief System Clock Configuration
  * @retval None
  */
  void SystemClock_Config(void)
  {
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 6;
  RCC_OscInitStruct.PLL.PLLN = 168;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
  Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
  Error_Handler();
  }
  }
  /* USER CODE BEGIN 4 */
  /* USER CODE END 4 */
  /**
  * @brief This function is executed in case of error occurrence.
  * @retval None
  */
  void Error_Handler(void)
  {
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  /* USER CODE END Error_Handler_Debug */
  }
  #ifdef USE_FULL_ASSERT
  /**
  * @brief Reports the name of the source file and the source line number
  * where the assert_param error has occurred.
  * @param file: pointer to the source file name
  * @param line: assert_param error line source number
  * @retval None
  */
  void assert_failed(uint8_t *file, uint32_t line)
  {
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
  tex: printf(“Wrong parameters value: file %s on line %drn”, file, line) */
  /* USER CODE END 6 */
  }
  #endif /* USE_FULL_ASSERT */
  /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
  3 运行效果
  一开始LED0为点亮状态,当按键按下时,红灯熄灭;当按键松开时,红灯点亮
  代码我已经放到了我的GitHub仓库,如有需要可以下载使用:
  总结
  按键也是一种人机交互的操作,同时也可以设置成STM32的输入模式,可以用于设置某项功能的开启等作用。外部中断也是STM32中重要的中断类型,常常用于传感器的数据处理,例如陀螺仪,加速度计,磁力计的数据准备中断,通知stm32已经有新的传感器数据产生。
举报

更多回帖

发帖
×
20
完善资料,
赚取积分