单片机如何识别按键长按和短按?
单片机工程师在面试的过程中,经常会碰到一些相同的问题,笔者总结了十个提问率较高的问题,供大家参考。现在我们来分析单片机工程师常遇面试问题之二:单片机如何区别按键长按和短按?
图(1)连接线路图
首先,我们来看一下按键和单片机的线路连接图(图1),KEY1直接连接到单片机IO口,单片机IO口设置为上拉输入状态,注意如果IO口内部无上拉电阻,那么还需外部增加上拉电阻,这一点在设计单片机线路时需要特别注意,不然将会拖慢项目进度。我们先来分析一下单片机是如何识别按键按下的,如下图所示,当按键未按下时,单片机IO口和地不相连接,因为我们设置了内部上拉,所以现在读取该IO口状态为高电平,也就是逻辑1,当按键按下时,单片机IO和地相连接,那么此时读取该IO口状态为低电平,也就是逻辑0,按键按下和未按下的等效示意图如图(2)。所以当我们读取IO口状态为“1”时,代表按键未按下,当我们读取IO口状态为“0”时,代表按键按下。
图(2)等效电路图
在实际使用过程中,会因为按键的抖动而产生杂波,抖动波形请参考图(3),所以我们在识别按键状态的时候还需要做一些消除抖动的处理。硬件方面我们可以在按键上并联一个104的电容到地来消除部分杂波,如图(1)中的C22就是这个作用。软件方面也需要做一些消抖的算法,在抖动的这个过程中,读取IO状态是不确定的,我们要想办法把这这个抖动的过程给过滤掉。常用的一种算法是当连续检测到100ms IO口为低电平的时候(可以设定每10ms检测一次),就认为此时已经有按键按下,事实上这种算法是非常有效的
图(3)抖动波形
如果文章对你有帮助,请关注作者,后面作者将为你推送更有价值的文章,谢谢!
附上参考程序,大家如有疑问,请把问题发到评论区,我们一起讨论。各位大佬如有更好的识别方法,也可发到评论区分享给大家,谢谢~~
参考程序:
//注:flag_10ms每10ms会被置1,这个动作一般用定时器来完成
if(flag_10ms)
{
flag_10ms = 0;
if(!KEY1)
{
count++;
if(count == 200) //长按超过2s
{
; //此处认为按键长按,可设置相关标志位
}
If(count >= 200)
{
count = 250;
}
}
else
{
if(count >= 10 && count <= 200)//按键时间在100ms至2s之间
{
; //此处认为按键为短按,可设置相关标志位
}
count = 0;
}
}
短按,长按,按键释放,三种模式的按键扫描程序(软件消抖动)
先来说一下这三种模式的意思:
1. 短按模式: 单击按键时,返回一次有效按键值;长按时也只返回一次有效按键值。这样可以有效地排除因不小心长按带来的返回多次有效按键,进而执行多次按键处理程序。
2. 长按模式: 单击按键时,返回一次有效按键;长按时,返回多次有效按键值。这样可以很快的调节某个较大的参数,比如时间的时分秒参数。
3. 按键释放模式 :这个模式与短按模式是相对的。短按模式只要按键按下去,立即返回有效键值,进而试行按键处理程序;二按键释放模式,却是要等到按下键,释放之后,才会返回有效键值,进而执行按键处理程序。
接下来说一下扫描程序:
1)采用的是轮询的方式(非中断)
2)消抖动的方式:多次扫描,来确定按键值。下面的程序的是设定了5次。主要是根据扫描周期来确定,次数的多少。
注:
扫描周期:从进入按键扫描程序开始,直到到下一次进入按键扫描程序时 结束,之间所用的时间。
下面是整个按键扫描程序的源码,可以读一读,语句都很简单,而且每一句都有注释,一步一步看下去,应该能明白。
如果不明白,可以留言谈论。
以下是 KeyScan.c 文件的内容,
//======================================================
//KeyScan.c
//======================================================
//注意:该宏定义,定义在keyscan.h文件中
//#define KEYDEBOUNCE 0x05 //消抖动,按键扫描次数。如果连续5次都是扫描的都是相同键值,则认为是有效键值,否则是误触发
unsigned int g_uiCurrKey; //当前按键值
unsigned int g_uiLastKey; //上次按键值
unsigned int g_uiKeyScanCount; //按键扫描计数,作用:消抖动
unsigned int g_uiPreKeyValue; // 上一次的有效按键值
unsigned int g_uiKeyDown; //键被按下,返回的键值。 作用:单次按键,单次返回有效键值;按住不放,也只返回被按下的一个键值
unsigned int g_uiKeyRelease; //键被释放后,返回的键值。 作用:只有按下的按键被释放后,才返回按下的键值
unsigned int g_uiKeyContinue; //键连续按键,重复返回的键值。 作用:只要按住不放,就会重复地返回相同键值
//P0口的低八位作为按键
//没有按键时,返回的是0xff,
void Int_Key_Scan(void)
{
static unsigned short LastReadKey; //上次从IO口读取的键值 ,注意是静态变量
unsigned short CurrReadKey; //当前从IO口读取的键值
CurrReadKey = P0 & 0x00ff; //获取当前的键值
if(CurrReadKey == LastReadKey) //如果当前读取的键值与上次从IO口读取的键值相同
{
if(g_uiKeyScanCount >= KEYDEBOUNCE) //首先判断是否大于等于debounce的设定值(即是,是否大于等于设定的采样次数)
{
//按住不放,多次响应
g_uiCurrKey = CurrReadKey; //如果是,将当前的读取值判定为有效按键值(如果是,在采样周期中,都是这个值,则判定为有效按键值)
g_uiKeyContinue = g_uiCurrKey ; //长按,多次响应 按键值
//按住不放只响应一次
if(g_uiPreKeyValue == g_uiCurrKey)
{
g_uiKeyDown = 0xff; //没有键值
}
else
{
g_uiKeyDown = g_uiCurrKey; //如果不同,按键有效,(就是第一次有效值时)
}
//按键释放时,按键值才有效
if(g_uiCurrKey == 0xff) //当有效按键值从非0到0的状态时(即是,从有按键到无按键,表示已经释放了),表示之前按键已经释放了
{
g_uiKeyRelease = g_uiPreKeyValue;
}
g_uiLastKey = g_uiCurrKey; //记录上次有效按键值
}
else //如果否,则debounce加一(如果否,则继续采样键值)
{
g_uiKeyScanCount++;
}
}
else //如果当前读取的键值与上次从IO口读取的键值不同,说明按键已经变化
{
g_uiKeyDown = 0xff; //放开按键后第一次进入扫描程序,清零g_uiKeyDown.作用:消除一个BUG(你猜BUG是什么?)
g_uiKeyScanCount = 0; //清零之前的按键的debounce计数
LastReadKey = CurrReadKey; //将当前读取的键值记录为上次读取的按键值
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263以下是KeyScan.h文件内容
//======================================================
//KeyScan.h
//======================================================
//宏定义
#define KEYDEBOUNCE 0x05 //消抖动,按键扫描次数。如果连续5次都是扫描的都是相同键值,则认为是有效键值,否则是误触发
//声明变量
extern unsigned int g_uiCurrKey; //当前按键值
extern unsigned int g_uiLastKey; //上次按键值
extern unsigned int g_uiKeyScanCount; //按键扫描计数,作用:消抖动
extern unsigned int g_uiPreKeyValue; //上一次的有效按键值
extern unsigned int g_uiKeyDown; //键被按下,返回的键值。 作用:单次按键,单次返回有效键值;按住不放,也只返回被按下的一个键值
extern unsigned int g_uiKeyRelease; //键被释放后,返回的键值。 作用:只有按下的按键被释放后,才返回按下的键值
extern unsigned int g_uiKeyContinue; //键连续按键,重复返回的键值。 作用:只要按住不放,就会重复地返回相同键值
//函数声明
void Int_Key_Scan(void);
1234567891011121314151617181920使用注意:
1.作为按键使用的相应IO口,必须设置为输入模式(如果是51单片机的话,无需关心)
2.按键的硬件连接必须是一端接GND,一端接IO口。
下面介绍一下程序的使用方法:
这里以51单片机的 按键点亮和熄灭LED灯作为例子。
硬件:
1)按键使用单片机的P0端口
2)LED灯使用P1.0的IO口,低电平点亮
返回的按键值:
没有键按下, 返回键值是0xFF
如果P0.0按下,返回键值是0xFE
如果P0.1按下,返回键值是0xFD
如果P0.2按下,返回键值是0xFB
如果P0.3按下,返回键值是0xF7
…
如果P0.7按下,返回键值是0x7F
下面是例子的参考源码:
//======================================================
//main.c
//======================================================
#include "reg51.h"
#include "KeyScan.h"
sbit LED = P1.0; //定义LEDIO口
char time_10ms_ok; // 10ms 定时标志
void init_timer0(void)
{
//定时器的初始化
TMOD = 0x01; //选择定时器的工作模式:定时器0,方式1
TH0 = (65535 - 10000)/256; //定时器的初值
TL0 = (65535 - 10000)%256;
EA = 1; //开打总中断使能
ET0 = 1; //打开定时器0 的使能
TR0 = 1; //打开定时器0 ,开始工作
}
void main(void)
{
P0 = 0xff;
LED = 0; //点亮LED
init_timer0(); //初始化定时器 定时10ms
while(1)
{
if(time_10ms_ok) //这里表示10ms扫描一次
{
time_10ms_ok = 0; //清除10ms定时标志
Int_Key_Scan(); //按键扫描程序
}
//第一种:KeyDown的使用
//单按时和长按时,都只返回一次有效键值(无需等到按键释放,就可以返回有效键值)
switch(g_uiKeyDown)
{
case 0xFE:
//P0.0按键程序
LED = !LED;
break;
case 0xFD:
//P0.1按键程序
//...
break;
case 0xFB:
//P0.2按键程序
//...
break;
case 0xF7:
break;
case 0xEF:
break;
case 0xDF:
break;
case 0xBF:
break;
case 0x7F:
break;
case 0xFF:
//没有按键程序
//...
break;
}
//第二种:KeyRelease的使用
//只有当按键释放之后,才返回一次有效键值,即是按键释放后,才执行相应的函数
switch(g_uiKeyRelease)
{
case 0xFE:
//P0.0按键程序
LED = !LED;
break;
case 0xFD:
//P0.1按键程序
//...
break;
case 0xFB:
//P0.2按键程序
//...
break;
case 0xF7:
break;
case 0xEF:
break;
case 0xDF:
break;
case 0xBF:
break;
case 0xEF:
break;
case 0xFF:
//没有按键程序
//...
break;
}
//第三种:KeyContinue的使用
//1)单次按键(非长按),返回一次有效值。
//2)长按,返回多次相同有效值
switch(g_uiKeyContinue)
{
case 0xFE:
//P0.0按键程序
LED = !LED;
break;
case 0xFD:
//P0.1按键程序
//...
break;
case 0xFB:
//P0.2按键程序
//...
break;
case 0xF7:
break;
case 0xEF:
break;
case 0xDF:
break;
case 0xBF:
break;
case 0xEF:
break;
case 0xFF:
//没有按键程序
//...
break;
}
}
}
void timer0(void) interrupt 1 //用的是定时器0, 这个“interrupt 1”中的“1”代表1号中断即是定时器0中断。如果是“0”就是外部中断0;“2“=外部中断1;”3“定时器1中断;”4“=串行口中断
{
TH0 = (65535 - 10000)/256;
TL0 = (65535 - 10000)%256; //定时器0的方式1,得在中断程序中重复初值。
time_10ms_ok = 1; //定时10MS 的标志
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166Pillar Peng
2015.5.25 - 18:23
log:
感谢网友“Shiow1984”的提醒,有漏掉和不足的地方,我已经修改。
之前写的文章意在按键程序的思路,就没有将定时扫描程序添加进去,怕影响按键程序的理解,若要稳定地运用于程序中,就要使用定时扫描了,这样按键扫描的时间就可以确定,调节好扫描次数后,几乎就没有什么误触了。
我也在上面的程序中添加了定时扫面程序。
相关问答
单片机 一个 按键 实现短按,中 长按 ,更 长按 ,也就是三种功能,程序怎么写?在0.5S的时候做一次判断,如果弹起了则表示短按,没弹起,再经过2.5S再判断次,再经过2s再判断是否弹起,基本能实现在0.5S的时候做一次判断,如果弹起了则表示短按...
用一个轻触开关实现 单片机 短按开机, 长按 关机?这个主要是电路的设计,你做一个电子开关,按键按下时开关可以打开(短按是对人而言的,实际上时间也有几十几百毫秒,对单片机来说时间很长了),即系统有电,单...
单片机 怎么从矩阵键盘中提取一个 按键 来实现“按一下键切换一个LED灯亮”?可以啊,可以根据按键时间的长短,和高低电平来实现。比如,长按1s为高电平是一个功能,长按2s是另一个功能。步骤为:先读取某个io口的值,若为高电平(if按...可...
单片机 的独立键盘有什么用?很多的嵌入式或者单片机系统中,一般都需要向嵌入式或者单片机系统输入信息的外设,以及嵌入式或者单片机向外输出信息的外设我们常见的显示屏,数码管等就是嵌...
长按 3秒开关和短按1下开关有什么区别?长按开关进行开关机是利用了电容充电的原理,开关按下之后,电路接通,并对一个电容充电,3秒之后,电容的电压值达到一个设定值,单片机对其进行判断,然后输出...
能否说明独立式键盘的特点及适用场合,是如何用软件法消除 按键 的?朋友们好,我是电子及工控技术,我来回答这个问题。键盘作为一种输入设备,它是人机交换的重要入口。通过键盘的机械按键的断开与闭合可以向单片机输入各种命令和...
为什么自己设计不出来 单片机 程序?该怎么做?设计编写单片机程序是一个渐进的过程不可一蹴而就,毕竟单片机程序是与硬件有密切关系的,我们一般称为单片机程序叫底层硬件驱动程序。编写程序不但要对软件要熟...
想做一个温湿度控制器,可以液晶显示温湿度,可以通过键盘设定温湿度,超过设定会声光报警,电路原理图有什么建议?报警部分如下图所示。4、键盘部分:由于整体电路不需要很多按键,所以采用2*4矩阵键盘的方案。单片机只需要检测哪个按键有了低电平,即可确定哪个按键被按...
lcd1602间接连法?虽然LCD1602的显示屏幕与显示字符都较小,实用性并不强,但是在一般的教学实验中,它仍不失为一个常用的输出显示设备。LCD1602与单片机连接的线路共有11条,其...
向 单片机 高手求助,怎样用C语言编写歌曲程序和歌曲代码?需要用那些工具怎么样编写的要求详细解答。谢谢?//绝对调试通过,AVR-GCC,mega16,带数码显示,单键开关多功能控制#include{key_state=7;key_return=3;}elsekey_state=5; ...