【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;				//按键PINKeyActionType 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 validif high level time > JUDGE_TIME also means that over the JUDGE_TIME and still dont have button pushedwe 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     >judgesingle 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 validif high level time > JUDGE_TIME also means that over the JUDGE_TIME and still dont have button pushedwe 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     >judgesingle 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;				//按键PINKeyActionType 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/700958.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript的综合案例

案例要求&#xff1a; 实现一个表单验证 1.当输入框失去焦点时&#xff0c;验证输入的内容是否符合要求 2.当点击注册按钮时&#xff0c;判断所有输入框的内容是否都符合要求&#xff0c;如果不符合要求阻止表单提交 简单的页面实现 <!DOCTYPE html> <html lang&…

【Node.js】事件循环

Node.js 中的事件循环是基于单线程的异步非阻塞模型。它是 Node.js 的核心机制&#xff0c;用于处理非阻塞的 I/O 操作和异步事件。 1. Node.js 事件循环介绍 Node.js 的事件循环是一个 Event Loop&#xff0c;通过异步回调函数的方式实现非阻塞的处理。事件循环会在主线程上…

港股大反攻结束了吗?

‘港股长线见顶了吗&#xff1f;今天开盘就是最高点&#xff0c;然后一路跳水&#xff0c;市场又是一片恐慌。到底是健康的技术性回调&#xff0c;还是市场已经见顶&#xff1f; 港股此轮“大反攻”中&#xff0c;科网股表现十分亮眼。今日港股盘后&#xff0c;阿里巴巴、腾讯…

【Shell】Shell编程之函数

目录 1.Shell函数定义 2.Shell函数的作用 3.函数返回值 4.函数传参 5.函数变量的作用范围 案例 1.Shell函数定义 格式1 function 函数名 { 命令序列 } 格式2 函数名() { 命令序列 } 2.Shell函数的作用 使用函数可以避免代码重复 使用函数可以将大的工程分割为若…

DeepSort / Sort 区别

推荐两篇博文,详细介绍了deepsort的流程及代码大致讲解: https://blog.csdn.net/qq_48764574/article/details/138816891 https://zhuanlan.zhihu.com/p/196622890 DeepSort与Sort区别: 1、Sort 算法利用卡尔曼滤波算法预测检测框在下一帧的状态,将该状态与下一帧的检测结…

什么是工具? 从语言模型视角的综述

24年3月CMU和上海交大的论文“What Are Tools Anyway? A Survey from the Language Model Perspective”。 到底什么是工具&#xff1f; 接下来&#xff0c;工具在哪里以及如何帮助语言模型&#xff1f; 在综述中&#xff0c;对语言模型使用的外部程序工具进行了统一定义&…

【RAG 论文】UPR:使用 LLM 来做检索后的 re-rank

论文&#xff1a;Improving Passage Retrieval with Zero-Shot Question Generation ⭐⭐⭐⭐ EMNLP 2022, arXiv:2204.07496 Code: github.com/DevSinghSachan/unsupervised-passage-reranking 论文&#xff1a;Open-source Large Language Models are Strong Zero-shot Query…

智慧公厕系统:改变“上厕所”体验的科技革新

公共厕所是城市建设中不可或缺的基础设施&#xff0c;然而&#xff0c;由于较为落后的管理模式&#xff0c;会常常存在着管理不到位、脏乱差的问题。为了改善公厕的使用体验&#xff0c;智慧公厕系统应运而生&#xff0c;并逐渐成为智慧城市建设的重要组成部分。本文将以智慧公…

“打工搬砖记”中首页的功能实现(一)

文章目录 打工搬砖记秒薪的计算文字弹出动画根据时间数字变化小结 打工搬砖记 先来一个小程序首页预览图&#xff0c;首页较为复杂的也就是“秒薪”以及弹出文字的动画。 已上线小程序“打工人搬砖记”&#xff0c;进行预览观看。 秒薪的计算 秒薪计算公式&#xff1a;秒薪 …

特产销售|基于Springboot+vue的藏区特产销售平台(源码+数据库+文档)​

目录 基于Springbootvue的藏区特产销售平台 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道…

Golang SDK安装

windows环境安装 1.链接: 下载地址 2.安装SDK 检查环境变量&#xff1a; 3.开启go modules,命令行执行一下命令&#xff1a; go env -w GO111MODULEon4.设置国内代理&#xff0c;命令行执行一下命令&#xff1a; go env -w GOPROXYhttps://proxy.golang.com.cn,https:/…

如何使用恢复模式修复Mac启动问题?这里提供详细步骤

如果你的Mac无法启动,不要惊慌,Mac有一个隐藏的恢复模式,你可以使用它来诊断和修复任何问题,或者在需要时完全重新安装macOS。以下是如何使用它。 如何在Mac上启动到恢复模式 你需要做的第一件事是启动到恢复模式。尽管操作说明会因你使用的Mac电脑而异,但幸运的是,启动…