独立按键中双击,短按,长按一次实现,支持多按键(注意学习思路)

本文详细解析了STM32G431RBT6板子上使用hal库实现的按键识别逻辑,涉及短按、双击、长按的判断机制,以及10ms定时器配合中断消除按键抖动的方法,同时讨论了代码中的优化和局限性。
摘要由CSDN通过智能技术生成

目录

原理简介

主要分析图(按下时低电平,松开是高电平)

代码

总结


原理简介

用0.5秒进行判断,0.5s内按一下算短按,0.5s内按两下算双击,0.5s以上按一下算长按。是不是一看觉得很简单,先让我们认真分析一下。

首先:你按下0.5s以内,然后松开,这个时候时间还在0.5s以内,那你改对他放入那一类呢?答案是这个时候还不能判断,你无法判断它剩下的时间能否再按一次(总时间未超过0.5s),所以你对一次按键的判断肯定得超过0.5s,所以这个代码的弊端一就出来了。

到这里,有人肯定会说,这还不是照样简单,0.5s按一下我就算短按,0.5s按两下我就算双击,可是还是会有一个问题,我第一次按下抬起在0.5s以内,但我第二次按下了,我还没松开,0.5s已经过了,那这种算啥呢?所以我们这个时候得把第一次算短按中去,剩下的这个第二次按下需要重新判断。

最后聪明的小伙伴会问,那这个时候应没坑了吧?那我也说多余的坑我也不知道。当然这里面的长按是不需要啥顾虑的,你按下的时间超过了0.5s,那肯定是长按了,还有就是双击的判读,0.5s内你就是按了两次,同时最后松开了,那就是双击。这个时候比较牛逼的人就要说了,博主你是不是不行啊,像我手速快,我0.5s按三下,但然我还没回答,更加牛逼的人肯定要说了,0.5s按三下算什么,我手速快的飞起,0.5s四下,0.5s四下算什么,0.5s一万下····

不得不说他们确实牛逼,博主手速没你们这么快。所以我们得引入循环这个概览,10ms定时器加中断,既可以消抖动,还能循环往复,那三次怎么算你,首先前两次肯定算双击,后面一次 保留进行0.5s的重新观察,那0.5s按四次就是两次双击,一万次的博主表示,太快了不好。

主要分析图(按下时低电平,松开是高电平)

代码

我这里用了hal库,板子stm32g431Rbt6,我这里用四个按键,随意弄了一个结构体,但其实不管是啥都一样的,无非就是对一个按键进行不断读取,然后判断状态。

uint16_t count;
struct key
{
	bool single_flag;    //单次按下
	bool double_flag;    //双击
	bool long_flag;       //长按
	uint16_t double_time;    //按下后松开的时间
	bool double_timeEN;        //已经有一次按下松开的标志位
	uint16_t age;                //按下的时间
	uint16_t age_old;            //需要保留的第一次按下时间
	bool press;                   //松开的标准位
}keys[5];                            //懒得减一

uint8_t key_read(void){    //判断按键的返回值
	uint8_t key_val=0;
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==0)
		key_val=1;
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==0)
		key_val=2;
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==0)
		key_val=3;
	if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==0)
		key_val=4;
	return key_val;
}

void key_scan(void)
{
	uint8_t key_sta=key_read();
	if(key_sta!=0)        //有按键按下
	{
		keys[key_sta].age++;    //按下时间记录
		if(keys[key_sta].age>1){    //第一次不管,进行消抖
			keys[key_sta].press=1;    //按下标志位
			if(keys[key_sta].double_timeEN==1&&(keys[key_sta].age+keys[key_sta].age_old+keys[key_sta].double_time)>=50){    //0.5s去除前一次,保留后一次
				keys[key_sta].double_timeEN=0;
				keys[key_sta].double_time=0;
				keys[key_sta].single_flag=1;
				keys[key_sta].age_old=0;
			}
		}
	}else        //松开
	{
		for(int i=0;i<5;i++)
		{
			if(keys[i].age>50)    //直接长按
			{
				keys[i].long_flag=1;
			}
			if(keys[i].double_timeEN==1&&keys[i].press==1&&(keys[i].age+keys[i].age_old+keys[i].double_time)<50)    //符合双击
			{
				keys[i].double_flag=1;
				keys[i].press=0;
				keys[i].double_timeEN=0;
				keys[i].age_old=0;
			}
			
			if(keys[i].press==1&&keys[i].long_flag!=1)    //第一次按下松开
			{
				keys[i].double_timeEN=1;        //记录松开时间,打开标志位
				keys[i].age_old=keys[i].age;    //保存第一次按下时间
			}
			if(keys[i].double_timeEN==1)
			{
				keys[i].double_time++;        //记录松开时间
			}
			
			if(keys[i].double_timeEN==1&&(keys[i].double_time+keys[i].age)>50)    //符合单击
			{
				keys[i].single_flag=1;
				keys[i].double_timeEN=0;
				keys[i].double_time=0;
			}
			keys[i].age=0;    //松开进行数据清理
			keys[i].press=0;
		}
	}
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){    //10ms定时器中断
	if(htim->Instance==TIM6){
		key_scan();
	}
}

void key_prc1(){        //对具体进行处理,同时记得清零
	if(keys[1].single_flag){
		count++;
		keys[1].single_flag=0;        //单击清零
	}
	if(keys[1].long_flag){
		count+=2;
		keys[1].long_flag=0;
	}
	if(keys[1].double_flag){
		count+=3;
		keys[1].double_flag=0;
	}
}

//定时器初始化我就不写了,还有一下其他基础配置之类的也不写了
int main(void)
{
  while (1)
  {
		key_prc1();
  }

}

总结

聪明的人是不是发现第二个弊端了,if(keys[key_sta].age>1)竟然第一次进来20ms,博主时间这么长的吗?其实也可以弄成10ms,只不过又得加变量了,需要设置key_old=为之前出入的值,然后key_up,在key_down,有点麻烦了,算了,是我太懒了。

欧克,如有不足,希望指正

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在51单片机独立按键的驱动可以采用轮询方式或者断方式。下面分别介绍三种按键的驱动方式。 ### 单击按键 #### 轮询方式 在轮询方式,通过不断查询按键的状态来判断是否按下。具体实现如下: ```c // 定义按键IO口和状态 sbit key = P1^0; bit key_state = 0; void main() { while(1) { // 检测按键是否按下 if(key == 0) { // 消抖处理 delay_ms(10); if(key == 0) { key_state = 1; } } else { key_state = 0; } // 判断按键状态,执行相应的操作 if(key_state == 1) { // 执行单击操作 // ... } } } ``` #### 断方式 在断方式,通过外部断触发来判断按键是否按下。具体实现如下: ```c // 定义按键IO口和状态 sbit key = P1^0; bit key_state = 0; // 断服务函数 void key_interrupt() interrupt 0 { // 消抖处理 delay_ms(10); if(key == 0) { key_state = 1; } } void main() { // 配置外部断0 IT0 = 1; EX0 = 1; EA = 1; while(1) { // 判断按键状态,执行相应的操作 if(key_state == 1) { // 执行单击操作 // ... } } } ``` ### 双击按键 双击按键实现可以通过轮询方式或者定时器方式实现。 #### 轮询方式 在轮询方式,需要记录上一次按键的时间,并通过计算两次按键时间的间隔来判断是否为双击。具体实现如下: ```c // 定义按键IO口和状态 sbit key = P1^0; bit key_state = 0; unsigned int last_key_time = 0; void main() { while(1) { // 检测按键是否按下 if(key == 0) { // 消抖处理 delay_ms(10); if(key == 0) { key_state = 1; } } else { key_state = 0; } // 判断按键状态,执行相应的操作 if(key_state == 1) { if((unsigned int)(get_sys_time() - last_key_time) < 500) { // 执行双击操作 // ... last_key_time = 0; } else { last_key_time = get_sys_time(); } } } } ``` #### 定时器方式 在定时器方式,需要配置定时器,并记录上一次按键的时间。每次检测到按键按下时,通过计算两次按键时间的间隔来判断是否为双击。具体实现如下: ```c // 定义按键IO口和状态 sbit key = P1^0; bit key_state = 0; unsigned int last_key_time = 0; // 定时器0断服务函数 void timer0_interrupt() interrupt 1 { // 定时1ms TH0 = (65536 - 1000) / 256; TL0 = (65536 - 1000) % 256; } void main() { // 配置定时器0 TMOD = 0x01; TH0 = (65536 - 1000) / 256; TL0 = (65536 - 1000) % 256; ET0 = 1; TR0 = 1; EA = 1; while(1) { // 检测按键是否按下 if(key == 0) { // 消抖处理 delay_ms(10); if(key == 0) { key_state = 1; } } else { key_state = 0; } // 判断按键状态,执行相应的操作 if(key_state == 1) { if((unsigned int)(get_sys_time() - last_key_time) < 500) { // 执行双击操作 // ... last_key_time = 0; } else { last_key_time = get_sys_time(); } } } } ``` ### 按键 按键实现可以通过轮询方式、断方式或者定时器方式实现。 #### 轮询方式 在轮询方式,需要记录按键按下的时间,并通过判断按键持续时间的来判断是否为按。具体实现如下: ```c // 定义按键IO口和状态 sbit key = P1^0; bit key_state = 0; unsigned int key_down_time = 0; void main() { while(1) { // 检测按键是否按下 if(key == 0) { // 消抖处理 delay_ms(10); if(key == 0) { key_state = 1; key_down_time = get_sys_time(); } } else { if(key_state == 1) { if((unsigned int)(get_sys_time() - key_down_time) > 1000) { // 执行按操作 // ... key_state = 0; } else { // 执行单击操作 // ... key_state = 0; } } } } } ``` #### 断方式 在断方式,需要配置外部断,并记录按键按下的时间。每次触发断时,通过判断按键持续时间的来判断是否为按。具体实现如下: ```c // 定义按键IO口和状态 sbit key = P1^0; bit key_state = 0; unsigned int key_down_time = 0; // 断服务函数 void key_interrupt() interrupt 0 { // 消抖处理 delay_ms(10); if(key == 0) { key_down_time = get_sys_time(); } else { if((unsigned int)(get_sys_time() - key_down_time) > 1000) { // 执行按操作 // ... } else { // 执行单击操作 // ... } } } void main() { // 配置外部断0 IT0 = 1; EX0 = 1; EA = 1; while(1) { } } ``` #### 定时器方式 在定时器方式,需要配置定时器,并记录按键按下的时间。每次检测到按键按下时,通过判断按键持续时间的来判断是否为按。具体实现如下: ```c // 定义按键IO口和状态 sbit key = P1^0; bit key_state = 0; unsigned int key_down_time = 0; // 定时器0断服务函数 void timer0_interrupt() interrupt 1 { // 定时1ms TH0 = (65536 - 1000) / 256; TL0 = (65536 - 1000) % 256; // 检测按键是否按下 if(key == 0) { // 消抖处理 delay_ms(10); if(key == 0) { key_state = 1; key_down_time = get_sys_time(); } } else { if(key_state == 1) { if((unsigned int)(get_sys_time() - key_down_time) > 1000) { // 执行按操作 // ... key_state = 0; } else { // 执行单击操作 // ... key_state = 0; } } } } void main() { // 配置定时器0 TMOD = 0x01; TH0 = (65536 - 1000) / 256; TL0 = (65536 - 1000) % 256; ET0 = 1; TR0 = 1; EA = 1; while(1) { } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值