【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.mzph.cn/pingmian/12205.shtml

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

相关文章

注意力机制篇 | YOLOv8改进之在C2f模块引入反向残差注意力模块iRMB | CVPR 2023

前言:Hello大家好,我是小哥谈。反向残差注意力模块iRMB是一种用于图像分类和目标检测的深度学习模块。它结合了反向残差和注意力机制的优点,能够有效地提高模型的性能。在iRMB中,反向残差指的是将原始的残差块进行反转,即将卷积操作和批量归一化操作放在了后面。这样做的好…

软件工程期末复习(6)需求分析的任务

需求分析 需求分析的任务 “建造一个软件系统的最困难的部分是决定要建造什么……没有别的工作在做错时会如此影响最终系统&#xff0c;没有别的工作比以后矫正更困难。” —— Fred Brooks 需求难以建立的原因&#x…

矩阵相关运算1

矩阵运算是线性代数中的一个核心部分&#xff0c;它包含了许多不同类型的操作&#xff0c;可以应用于各种科学和工程问题中。 矩阵加法和减法 矩阵加法和减法需要两个矩阵具有相同的维度。操作是逐元素进行的&#xff1a; CAB or CA−B其中 A,B 和 C 是矩阵&#xff0c;且 C…

7nm项目之模块实现——02 Placeopt分析

一、Log需要看什么 1.log最后的error 注意&#xff1a;warnning暂时可以不用过于关注&#xff0c;如果特别的warning出现问题&#xff0c;在其他方面也会体现 2.run time 在大型项目实际开发中&#xff0c;周期一般较长&#xff0c;可能几天过这几周&#xff0c;所以这就需要…

探讨 cs2019 c++ 的STL 库中的模板 conjunction 与 disjunction

&#xff08;1&#xff09;在 STL 库源码中这俩模板经常出现&#xff0c;用来给源码编译中的条件选择&#xff0c;模板的版本选择等提供依据。先给出其定义&#xff1a; 以及&#xff1a; 可以得出结论&#xff1a; conj 是为了查找逻辑布尔型模板参数中的第一个 false &#x…

vs2019中__cplusplus一直显示199711

vs2019中__cplusplus一直显示199711&#xff0c;如何修改&#xff1f; 打开属性->C/C->命令行&#xff0c;其他选项&#xff0c;输入&#xff1a;/Zc:__cplusplus

aws s3

列出关键点 创建s3 设置s3策略&#xff0c;所有人访问 { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor1", "Effect": "Allow", …

C#窗体程序设计笔记:如何调出控件工具箱,并设置控件的属性

文章目录 调出控件工具箱设置控件属性 调出控件工具箱 使用Visual Studio打开C#解决方案后&#xff0c;初始界面如下图所示&#xff1a; 接着&#xff0c;在上方的菜单栏依次选择“视图”“工具箱”&#xff0c;即可打开工具箱&#xff0c;如下图所示&#xff1a; 设置控件属…

Android开发,日志级别

5个日志级别 Verbose (VERBOSE): 这是最低的日志级别&#xff0c;用于输出最为详尽的信息&#xff0c;包括开发和调试过程中的各种细节。在Log类中对应的方法是Log.v()。Debug (DEBUG): 此级别用于输出调试信息&#xff0c;帮助开发者理解程序运行流程或状态。通过Log.d()方法…

产品品牌CRUD

文章目录 1.renren-generator生成CRUD1.数据库表设计1.数据表设计2.分析 2.代码生成器生成crud1.查看generator.properties&#xff08;不需要修改&#xff09;2.修改application.yml 连接的数据库修改为云数据库3.启动renren-generator模块4.浏览器访问 http://localhost:81/5…

解决使用Vue.js前端与Flask后端API交互时跨源资源共享问题

我在使用flask以及Vue做一个项目时遇到了Vue前端与Flask后端API交互的问题就是前端获取不到后端返回的数据&#xff0c;报错&#xff1a; 上网查说是跨域问题&#xff0c;于是找了一些解决办法&#xff0c;就是可以通过设置响应头的 Access-Control-Allow-Origin 字段来允许所有…

易货模式:引领交易新潮流,实现资源高效利用

随着全球经济的繁荣和科技的日新月异&#xff0c;传统的交易模式正面临革新。在追求高效、便捷与环保的当下&#xff0c;易货模式作为一种新兴的交易方式&#xff0c;逐渐崭露头角&#xff0c;受到越来越多人的青睐。 易货模式&#xff0c;简而言之&#xff0c;就是通过平台或在…

知乎广告推广投放流程以及价格?

知乎作为一个拥有庞大高质量用户群体的知识分享平台&#xff0c;成为了众多品牌不可忽视的广告投放渠道。知乎不仅汇聚了各行各业的专业人士&#xff0c;其独特的社区氛围也为品牌提供了精准触达目标受众的机会。知乎广告推广的投放流程、费用标准&#xff0c;云衔科技提供的专…

刷代码随想录有感(65):回溯算法——组合问题

题干&#xff1a; 代码&#xff1a; class Solution { public:vector<vector<int>> res;vector<int> tmp;void backtracking(int n, int k, int start){if(tmp.size() k){res.push_back(tmp);return;}for(int i start; i < n; i){tmp.push_back(i);bac…

全栈开发之路——前端篇(9)插槽、常用api和全局api

全栈开发一条龙——前端篇 第一篇&#xff1a;框架确定、ide设置与项目创建 第二篇&#xff1a;介绍项目文件意义、组件结构与导入以及setup的引入。 第三篇&#xff1a;setup语法&#xff0c;设置响应式数据。 第四篇&#xff1a;数据绑定、计算属性和watch监视 第五篇 : 组件…

Transformers中加载预训练模型的过程剖析(一)

使用HuggingFace的Transformers库加载预训练模型来处理下游深度学习任务很是方便,然而加载预训练模型的方法多种多样且过程比较隐蔽,这在一定程度上会给人带来困惑。因此,本篇文章主要讲一下使用不同方法加载本地预训练模型的区别、加载预训练模型及其配置的过程,藉此做个记…

计算机组成原理(超详解!!) 第八节 总线系统

1.总线的概念和结构形态 1.总线&#xff08;BUS&#xff09;的基本概念 是构成计算机系统的互联机构&#xff0c;是多个系统功能部件&#xff08;运算器、控制器、存储器、输入/输出设备&#xff09;之间进行数据传送的公共通路。 由传输信息的电路和管理信息传输的协议组成…

【数据结构】栈和队列专题

前言 上篇博客我们讨论了栈和队列的有关结构&#xff0c;本篇博客我们继续来讨论有关栈和队列习题 这些题算是经典了 &#x1f493; 个人主页&#xff1a;小张同学zkf ⏩ 文章专栏&#xff1a;数据结构 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44d…

Linux内核发送网络数据

前言 我们开始今天对 Linux 内核⽹络发送过程的深度剖析。还是按照我们之前的传统&#xff0c;先从⼀段代码作为切⼊。 上述代码中&#xff0c;调⽤ send 之后内核是怎么样把数据包发送出去的。本⽂基于Linux 3.10&#xff0c;⽹卡驱动采⽤Intel的igb举例。 基础框架 我们看…