【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

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在STM32开发中,使用定时器可以方便地实现按键单击双击按功能。具体实现方式如下: 1. 按键单击: 当按键被按下时,启动定时器计时,若在规定的时间内松开键,则视为单击操作。可以设置定时器1为中断模式,启动定时器后等待中断响应即可。 2. 按键双击: 按键双击功能需要在单击结束后一定时间内再次单击才能触发,一般设置为200ms-300ms。实现方式与单击类似,只需在单击结束后启动定时器再次等待中断响应即可。 3. 按键按: 按键按功能一般指按住按键不动一定时间后触发,可以设置一个按时间阈值,一般为1s-2s。启动定时器开始计时,若在规定时间内键状态一直为高电平,则视为按操作。 总之,在STM32的开发中,使用定时器可以方便地实现按键单击双击按功能,只需设置好相应的时间参数和中断响应即可。 ### 回答2: 在STM32中使用定时器实现按键单击双击按的实现方法比较简单。 按键单击可以通过检测按键是否按下并保持一段时间来实现。在STM32中,可以通过读取GPIO口的状态来检测按键是否按下,并使用定时器来延时判断按键是否被松开。当按键被按下并且被松开时,即可判断为单击事件。 按键双击可以通过在单击事件的基础上增加一个短暂的时间间隔来实现。当第一次按下按键时,开启定时器计数并在计数完毕后进行单击判断,当第二次按下按键时,再次开启定时器计数并在计数完毕后进行单击判断,如果两次单击之间的时间间隔较短,则可以判断为双击事件。 按键按可以通过读取GPIO口状态并在一段时间内判断按键是否一直为按下状态来实现。当按键被按下时,开启定时器计数,并在计数完成后判断GPIO口是否一直为按下状态,如果是,则判断为事件。 总之,在STM32中使用定时器实现按键单击双击按的方法非常简单,只需要结合GPIO口状态来进行相应的判断即可。 ### 回答3: STM32系列的微处理器内置了多种定时器功能,可用于实现按键的单击双击按等功能。下面是使用定时器实现按键的单击双击按的具体方法: 1、按键单击:当按键按下时,开启一个短时间的定时器,在定时器时间内检测按键是否释放,如果释放则判断为单击事件。 2、按键双击:当检测到按键第一次单击事件时,开启一个短时间的定时器,在定时器时间内检测是否有第二次单击事件,如果有,则判断为双击事件。 3、按键按:当按键按下一段时间后,开启一个时间定时器,在定时器时间内不断检测按键是否释放,如果一直按下不放,则判断为事件。 需要注意的是,定时器的时间要适当设置,避免误判或判断不准确。此外,还需要进行防抖处理,避免按键抖动导致误判。 在STM32中,定时器的具体使用可以参考官方提供的开发文档和代码示例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值