单片机处理按键长按的三种解决方法

作者:水瓶星人
座右铭:记录每一分痛点,分享每一点收获

突然看到之前写的一篇博客,发现其按键处理程序和最近使用到的一个按键处理程序不大一样,拿过来综合比较一下。

首先,我们在判断按键按下时,最简单的处理方法是直接比较按键对应引口的高低电平。

比如 按键按下点亮LED,我们可以直接写

//假设button1代表按键1,led低电平点亮
if(button1==0)  
{
	led=0;	//亮
}
else if(button1 == 1) 
{
	led=1;	//灭
}

但是,如果使用这种方法去处理时钟 时分秒 的加减设置,一旦按下由于程序运行速度极快,我们按一下,相应的检测程序实际上已经执行了很多遍了,在实际使用中就是,只按一次 时/分/秒数却增加好多。
比如代码:

while(1)
{
	//button123对应三个按键	shi:时 fen:分 miao:秒
	if(button1==0)	shi++; break;
	if(button2==0)	fen++; break;
	if(button3==0)	miao++; break;
}

解决方法一

在最开始的时候我的处理是在按键按下时,我让程序进入死循环,等待按键松开后再执行相应的时分秒加减。
比如代码:

if(button2 == 0)
{
	switch(button_num1)
	{
		case 0:	break;
		case 1:	delay_us(10); while(~button2); num3++;  break; 		//等待按键释放	时加一
		case 2:	delay_us(10); while(~button2); num2++;	break;		//等待按键释放	分加一
		case 3:	delay_us(10); while(~button2); num1++;	break;		//等待按键释放	秒加一
		default: break;	
	}
	if(num3 == 24) num3 = 0;	//超出归零
	if(num2 == 60) num2 = 0;
	if(num1 == 60) num1 = 0;	
}

这种方法的优点是:简单粗暴,易于理解。
但是缺点也很明显:就是我在按下按键后如果我不松开按键,那么程序将一直卡在死循环里无法去做其他的事,对应明显表现就是液晶屏此时不显示数值了或者显示数值缺失。

注:这种方法也是我在做单片机显示时间时看到使用最多的一种方法,但是我并不推荐


解决方法二

这个时候我们就需要对按键进行长按判断处理,让程序知道我 按下——松开 才是完成了 一次 按键按下动作。而不是让程序忙等。

接下来我假设我要用KEY1和KEY2去修改时间的秒数,当KEY1按下时秒数加一,KEY2按下时秒数减一,我们可以这么处理

//按键扫描函数,返回值确定按键是否按下
u8 KEY_Scan()
{	 
	static u8 key_up=1;//按键按松开标志	  
	if(key_up&&(KEY1==0||KEY2==0))
	{
		delay_ms(10);//去抖动 
		key_up=0;	//进入一次按键就将按键按下标志置0,防止重复进入
		if(KEY1 == 0)
		{
			return '1';	//KEY1按下
		}
		else if(KEY2 == 0)
		{
			return '2';	//KEY2按下
		}
	}else if(KEY1==1&&KEY2==1) key_up=1; 	//当无按键按下或着按键松开后,按键标志位 重新置1,为下次按键按下做准备	    
 	return '0';// 无按键按下
}

//设置秒数
void Miao_Setting()
{
	u8 key;	//定义一个接收变量
	key=KEY_Scan();	//key等于 ‘0’/‘1’/‘2’
	switch(key)
	{
		case '0': break;	//没有按下直接跳出
		case '1': miao++; break;
		case '2': miao--; break;
	}
}

通过这个函数,我们就可以实现每按一下,对应修改数值 加一/减一。

这个函数实现起来比较简单,但是我们也可以看到,如果我们要判断好多按键按下,就比如说要判断8个按键其中某个按键是否按下,那么我们就需要在 if 条件判断中加入8个按键状态判断,判断语句也将由两条变为8条,代码就显得很长。

解决方法三

现在我们有一种更加高大上的方法来做这项处理。
先花几分钟看下代码:

/***********************************************************
*函数名		:KeyScan
*功能		: 按键扫描,返回按下的按键对应的位,并防止连按
*返回值		:返回按键按下的位置
*参数		:无
************************************************************/
char KeyScan() 
{
	static unsigned char klast = 0;	  //记录上一次的按键值
	unsigned char trg = 0,key = 0;		  //trg:得到的返回值,返回值中8位只有一位为1,为1的位代表按下的位,其余位为零
	key = P2 & 0x0F;				  //将按下的位转换为0 没按下的位依然为1
	key ^= 0x0f;					  //异或之后没按下的位转换为1,按下的位转换为0
	trg = key & (key ^ klast);		  //这句是最关键的一句,需要自己理解
	klast = key;
	return trg;	  						//最终返回值是按下哪个按键,返回值对应就是1,没按对应的就是0  eg:按下button3 则返回0x01 0000 0100
}

如果没有理解也没关系,我们先举个例子
假设:P2是51单片机的P2口,对应有 P0_0——P2_7 8个引口,现在我们把低4个引口接入按键,高4个引口作为其他用途不得改变,检测低四位中哪个被按下
我画了一张表,取了六种情况,得到六种情况的返回值。
如下:
在这里插入图片描述
通过这张表我们也可以看出,

  • 按键都没按下时,返回值的低四位都为0
  • 低四位某个按键被按下时,其相应位返回值为1,其余位为0
  • 某个按键长按时,其对应位返回值变回0
  • 在上一次的按键未松开时按住另一个按键,未松开的按键对应返回值为0,新按下的按键对应返回值为1
  • 同时按住多个按键,新按下的按键对应位返回值为1,已经按过的按键对应位返回值为0

结论:此函数能够指出新按下的按键位置,以1标明按下的是哪一位,并支持多按键触发


我们在使用的时候只需要比较对应按键位的值是否为1即可,为1则表示此按键按下,为0表示按键未按下或未松开。

使用举例:

void button_setting()
{
	if((button_flag & 0x0f) == 0x01 )		//此button_flag即trg  判断最低位是否被按下
	{
		button_num1++;	//按下就加一	
	}
}

效果对比(注意看我按键按下的动作)
方案一:
在这里插入图片描述
方案二、三
在这里插入图片描述

总结:
方案三 优于 方案二 优于 方案一
在以后的开发中应该尽量使用更少的代码去实现相应的功能,在单片机开发中更是如此。

一一一一一一一一一2021年6月10日更新一一一一一一一一一
找到一篇针对第三种方法更加生动形象的解释,收获良多,帖子地址:点击进入
第三种方法的简单写法

unsigned char Trg;
unsigned char Cont;
void KeyRead( void )
{
    unsigned char ReadData = PINB^0xff;   // 1
    Trg = ReadData & (ReadData ^ Cont);      // 2
    Cont = ReadData;                                // 3
}

Trg(triger) 代表的是触发,Cont(continue)代表的是连续按下。

一一一一一一一一一2023年10月24日更新一一一一一一一一一
针对第三种方法最近找到一篇相关专利介绍,专利号:CN202110678156.5

在专利介绍中针对那段代码有一些专业的描述,这里我复制粘贴一下。

原文代码

Read_Data = (GPIO_Read&X)^X
Trag = Read_Data&(Read_Data^Count)
Rel = Read_Data^Trag^Count
Count = Read_Data

描述:GPIO_Read表示所述目标数据,X为预设值且X的第1bit至第Nbit均为1,Trag的第1bit至第Nbit依次表示第1按键至第N按键各自的触发变量值,Rel的第1bit至第Nbit依次表示第1按键至第N按键各自的释放变量值,Count的第1bit至第Nbit依次表示第1按键至第N按键各自的长按变量值,Read_Data为预设的中间变量;&表示与操作,∧表示异或操作;
(PS:这里的释放变量值就像klast一样,记录上一回按下的按键)

代码转换一下就是

char KeyScan() 
{
	static unsigned char klast = 0;
	unsigned char trg = 0,key = 0;
	key = (P2 & 0x0F)^0x0f
	trg = key & (key ^ klast);
	Rel = key^Trag^klast
	klast = key;
	return trg;
}

感兴趣的可以下载那篇专利看一下,这里附上下载链接:点击进入 提取码:crnf

希望本文能对你有所帮助 谢谢!

  • 36
    点赞
  • 123
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值