【STM32】状态机实现定时器按键消抖,处理多种点击事件

目录

一、简单介绍

二、模块与接线

三、cubemx配置

四、驱动编写

状态图

按键类型定义

参数初始化/复位

按键扫描

串口重定向

主函数

五、效果展示

六、驱动附录

key.c

key.h

一、简单介绍

        众所周知,普通的机械按键会产生抖动,可以采取硬件上加电容来滤波,也可以考虑用软件来消抖。这里笔者分享一种基于状态机的按键消抖策略,可以实现单击双击三击长按事件的读取。按键时间也可以自己设置。

        这种方法需要消耗掉定时器资源,还有额外的RAM支出。

二、模块与接线

笔者使用STM32单片机来实现这一过程,具体型号为STM32F103CBT6,和常见的最小核心板引脚是一样的,只是容量大一些。

外部按键选择的是51单片机的独立按键,原理图如下

按下按键后,相应的引脚电平就为低。将P30,P31,P32,P33分别连接至单片机的PA1,PA2,PA3,PA4

三、cubemx配置

GPIO口开启对应的按键为输入模式,配置上拉

串口

时钟为72MHz

定时器设置为10ms触发一次

四、驱动编写

状态图

将一个按键从按下前到按下再到松手分成四个状态:无操作、按下、按压、弹起

对应的状态图如下,分别是四个圆形

按键类型定义

如图矩形框内描述,最终键值的确定需要标志位和计数值,因此一个按键结构体应该这样定义

typedef struct 
{
	GPIO_TypeDef * GPIO_Port;		//按键端口
	uint16_t GPIO_Pin;				//按键PIN
	KeyActionType key;				//按键类型
	uint16_t hold_cnt;				//按压计数器
	uint16_t high_cnt;				//高电平计数器
	uint8_t press_flag;				//按压标志
	uint8_t release_flag;			//松手标志
	ButtonActionType buttonAction;	//按键键值
}buttonType;

该工程需要配置的只有一个主函数文件,外加笔者编写的key.c和key.h还有串口重定向的部分 

参数初始化/复位

void Key_ParaInit(buttonType* button)
{
	button->high_cnt = 0;
	button->hold_cnt = 0;
	button->press_flag = 0;
	button->release_flag = 0;
}

按键扫描

代码如下,基本实现了状态图

void Key_Scan(buttonType* button)
{
	switch(button->key)
	{
		case KEY_NULL:
		{
			/* if falling edge captured */
			if(HAL_GPIO_ReadPin(button->GPIO_Port,button->GPIO_Pin) == 0)
			{
				button->key = KEY_DOWN;
			}
			else if(HAL_GPIO_ReadPin(button->GPIO_Port,button->GPIO_Pin) == 1)
			{
				button->key = KEY_NULL;
			}
					
			/* if button is released ,high_time_count++ */
			if(button->release_flag == 1)
			{
				button->high_cnt++;
			}			

			/**********************judge***********************/
			/* if high_time_count is longer than LONG_PRESS_TIME, consider BUTTON_LONG_PRESS */
			if(button->hold_cnt > LONG_PRESS_TIME)
			{
				button->buttonAction = BUTTON_LONG_PRESS;
				Key_ParaInit(button);
			}
			/* if high_time_count is shorter than LONG_PRESS_TIME,but longer than CLICK_MAX_TIME consider INVALID */
			else if(button->hold_cnt < LONG_PRESS_TIME && button->hold_cnt > CLICK_MAX_TIME)
			{
				Key_ParaInit(button);
			}

			/* 
				only the latest press time is in range of [CLICK_MIN_TIME,CLICK_MAX_TIME] can be regarded valid
				if high level time > JUDGE_TIME also means that over the JUDGE_TIME and still dont have button pushed
				we can check the flag value to get button state now
			*/
			else if((button->high_cnt > JUDGE_TIME)&&(button->hold_cnt > CLICK_MIN_TIME && button->hold_cnt < CLICK_MAX_TIME))
			{
				if(button->press_flag ==1)
				{
					button->buttonAction = BUTTON_SINGLE;
				}
				else if(button->press_flag == 2)
				{
					button->buttonAction = BUTTON_DOUBLE;
				}
				else if(button->press_flag == 3)
				{
					button->buttonAction = BUTTON_TRIPLE;
				}
				
				Key_ParaInit(button);
			}
			break;
		}
		
		case KEY_DOWN:
		{
			button->key = KEY_PRESS;
			
			/* as long as falling edge occurring,press_flag++ */
			button->press_flag++;
			
			button->release_flag = 0; 			/* means that the button has been pressed */

			button->hold_cnt = 0;				/* reset hold time count */
			break;
		}
		
		case KEY_PRESS:
		{
			/* when button was kept pressed, hold count++ */
			if(HAL_GPIO_ReadPin(button->GPIO_Port,button->GPIO_Pin) == 0)
			{
				button->key = KEY_PRESS;
				button->hold_cnt++;
			}
			/* when button was released, change state */
			else if(HAL_GPIO_ReadPin(button->GPIO_Port,button->GPIO_Pin) == 1)
			{
				button->key = KEY_UP;
			}
			break;
		}
		
		case KEY_UP:
		{
			button->key = KEY_NULL;
			
			button->release_flag = 1;			/* means that the button is released */

			button->high_cnt = 0;				/* reset hold time count */

			/* if press time is longer than 1s then press_flag-- */
			if(button->hold_cnt > 100)
			{
				button->press_flag--;
			}
			break;
		}
		default:
			break;
	}
}

里面涉及到的宏定义和枚举,都在头文件内给出

/*
double click:
```___________``````````````___________```````````` 
     min< <max    <judge      min< <max     >judge
	
single click:
``````___________`````````
       min< <max  >judge
	   
*/

#define LONG_PRESS_TIME 	     100
#define CLICK_MIN_TIME 		5	/* if key press_cnt time less than this -> invalid click */
#define CLICK_MAX_TIME 		20	/* if key press_cnt time more than this -> invalid click */
#define JUDGE_TIME 			20	/* double click time space */


typedef enum
{
	KEY_NULL,
	KEY_DOWN,
	KEY_PRESS,
	KEY_UP,
}KeyActionType;

typedef enum
{
	BUTTON_NULL,
	BUTTON_SINGLE,
	BUTTON_DOUBLE,
	BUTTON_TRIPLE,
	BUTTON_LONG_PRESS,
}ButtonActionType;

串口重定向

打开usart.c

添加如下代码

int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}

int fgetc(FILE *f)
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
  return ch;
}

主函数

在主循环内去读取键值,用定时器来周期扫描按键

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "tim.h"
#include "key.h"
/* 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 */

/* 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();
  MX_TIM2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	Key_Config();					//配置按键
	HAL_TIM_Base_Start_IT(&htim2); 	//开定时器
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  Key_Debug();
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses 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_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim == &htim2)
	{
		Key_Scan(button);
		Key_Scan(button+1);
		Key_Scan(button+2);
		Key_Scan(button+3);
	}
}
/* 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 */
  __disable_irq();
  while (1)
  {
  }
  /* 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,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

五、效果展示

按下按键,串口打印相应的按键号和键值

六、驱动附录

key.c

#include "key.h"
#include "stdio.h"

buttonType button[4];

void Key_Config()
{
	button[0].GPIO_Port = K1_GPIO_Port;
	button[0].GPIO_Pin = K1_Pin;

	button[1].GPIO_Port = K2_GPIO_Port;
	button[1].GPIO_Pin = K2_Pin;

	button[2].GPIO_Port = K3_GPIO_Port;
	button[2].GPIO_Pin = K3_Pin;

	button[3].GPIO_Port = K4_GPIO_Port;
	button[3].GPIO_Pin = K4_Pin;
}

void Key_ParaInit(buttonType* button)
{
	button->high_cnt = 0;
	button->hold_cnt = 0;
	button->press_flag = 0;
	button->release_flag = 0;
}

void Key_Scan(buttonType* button)
{
	switch(button->key)
	{
		case KEY_NULL:
		{
			/* if falling edge captured */
			if(HAL_GPIO_ReadPin(button->GPIO_Port,button->GPIO_Pin) == 0)
			{
				button->key = KEY_DOWN;
			}
			else if(HAL_GPIO_ReadPin(button->GPIO_Port,button->GPIO_Pin) == 1)
			{
				button->key = KEY_NULL;
			}
					
			/* if button is released ,high_time_count++ */
			if(button->release_flag == 1)
			{
				button->high_cnt++;
			}			

			/**********************judge***********************/
			/* if high_time_count is longer than LONG_PRESS_TIME, consider BUTTON_LONG_PRESS */
			if(button->hold_cnt > LONG_PRESS_TIME)
			{
				button->buttonAction = BUTTON_LONG_PRESS;
				Key_ParaInit(button);
			}
			/* if high_time_count is shorter than LONG_PRESS_TIME,but longer than CLICK_MAX_TIME consider INVALID */
			else if(button->hold_cnt < LONG_PRESS_TIME && button->hold_cnt > CLICK_MAX_TIME)
			{
				Key_ParaInit(button);
			}

			/* 
				only the latest press time is in range of [CLICK_MIN_TIME,CLICK_MAX_TIME] can be regarded valid
				if high level time > JUDGE_TIME also means that over the JUDGE_TIME and still dont have button pushed
				we can check the flag value to get button state now
			*/
			else if((button->high_cnt > JUDGE_TIME)&&(button->hold_cnt > CLICK_MIN_TIME && button->hold_cnt < CLICK_MAX_TIME))
			{
				if(button->press_flag ==1)
				{
					button->buttonAction = BUTTON_SINGLE;
				}
				else if(button->press_flag == 2)
				{
					button->buttonAction = BUTTON_DOUBLE;
				}
				else if(button->press_flag == 3)
				{
					button->buttonAction = BUTTON_TRIPLE;
				}
				
				Key_ParaInit(button);
			}
			break;
		}
		
		case KEY_DOWN:
		{
			button->key = KEY_PRESS;
			
			/* as long as falling edge occurring,press_flag++ */
			button->press_flag++;
			
			button->release_flag = 0; 			/* means that the button has been pressed */

			button->hold_cnt = 0;				/* reset hold time count */
			break;
		}
		
		case KEY_PRESS:
		{
			/* when button was kept pressed, hold count++ */
			if(HAL_GPIO_ReadPin(button->GPIO_Port,button->GPIO_Pin) == 0)
			{
				button->key = KEY_PRESS;
				button->hold_cnt++;
			}
			/* when button was released, change state */
			else if(HAL_GPIO_ReadPin(button->GPIO_Port,button->GPIO_Pin) == 1)
			{
				button->key = KEY_UP;
			}
			break;
		}
		
		case KEY_UP:
		{
			button->key = KEY_NULL;
			
			button->release_flag = 1;			/* means that the button is released */

			button->high_cnt = 0;				/* reset hold time count */

			/* if press time is longer than 1s then press_flag-- */
			if(button->hold_cnt > 100)
			{
				button->press_flag--;
			}
			break;
		}
		default:
			break;
	}
}



void Key_Debug()
{
	
	for(uint8_t i=0;i<4;i++)
	{
		switch(button[i].buttonAction)
		{
			
			case BUTTON_SINGLE:
			{
				button[i].buttonAction = BUTTON_NULL;
				printf("%d->",i);
				printf("BUTTON_SINGLE\r\n");
				break;
			}
			case BUTTON_LONG_PRESS:
			{
				button[i].buttonAction = BUTTON_NULL;
				printf("%d->",i);
				printf("BUTTON_LONG_PRESS\r\n");
				break;
			}
			case BUTTON_DOUBLE:
			{
				button[i].buttonAction = BUTTON_NULL;
				printf("%d->",i);
				printf("BUTTON_DOUBLE\r\n");
				break;
			}
			case BUTTON_TRIPLE:
			{
				button[i].buttonAction = BUTTON_NULL;
				printf("%d->",i);
				printf("BUTTON_TRIPLE\r\n");
				break;
			}
			case BUTTON_NULL:
			{
				button[i].buttonAction = BUTTON_NULL;
				break;
			}
			default:
			{
				break;
			}
		}
	}
	
}

key.h

#ifndef KEY_H
#define KEY_H

#include "tim.h"
#include "main.h"
/*
double click:
```___________``````````````___________```````````` 
     min< <max    <judge      min< <max     >judge
	
single click:
``````___________`````````
       min< <max  >judge
	   
*/

#define LONG_PRESS_TIME 	     100
#define CLICK_MIN_TIME 		5	/* if key press_cnt time less than this -> invalid click */
#define CLICK_MAX_TIME 		20	/* if key press_cnt time more than this -> invalid click */
#define JUDGE_TIME 			20	/* double click time space */


typedef enum
{
	KEY_NULL,
	KEY_DOWN,
	KEY_PRESS,
	KEY_UP,
}KeyActionType;

typedef enum
{
	BUTTON_NULL,
	BUTTON_SINGLE,
	BUTTON_DOUBLE,
	BUTTON_TRIPLE,
	BUTTON_LONG_PRESS,
}ButtonActionType;

typedef struct 
{
	GPIO_TypeDef * GPIO_Port;		//按键端口
	uint16_t GPIO_Pin;				//按键PIN
	KeyActionType key;				//按键类型
	uint16_t hold_cnt;				//按压计数器
	uint16_t high_cnt;				//高电平计数器
	uint8_t press_flag;				//按压标志
	uint8_t release_flag;			//松手标志
	ButtonActionType buttonAction;	//按键键值
}buttonType;

extern buttonType button[4];

void Key_Scan(buttonType*);
void Key_Debug();
void Key_Config();

#endif

作者:田甲

物联沃分享整理
物联沃-IOTWORD物联网 » 【STM32】状态机实现定时器按键消抖,处理多种点击事件

发表回复