关于按键
按钮相当于一个电子开关。按下时开关打开,松开时开关断开。实现原理是通过触摸按钮内的金属弹片来打开和断开。
最基本的是用死循环来轮询判断按键动作。
例如,如果按下某个按钮,监控LED就会亮。
对于机械开关,当机械接触断开或关闭时,由于机械接触的弹性,开关不会立即稳定连接,断开时不会突然断开,因此在开关关闭和断开时会伴随一系列抖动:
可通过延迟消抖。
上拉电阻
:
通过电阻和电源将不确定信号(高或低电平)VCC连接,固定在高电平。
:
通过电阻与地面连接不确定信号(高或低电平)GND连接,固定在低电平。
当上拉电阻与下拉电阻共同作用时,表现为接地端的低电平状态。
查看原理图
独立按键:
代码实现
此处,我将JP5引脚接到P1端口,将LED灯接到P0端口。
1.按一个按钮,照亮相应的对应LED灯。
/** *@file key.c *@author Timi *@date 2022.07.21 */ #include <reg51.h> #define uchar unsigned char void KeyLightLed(); ///函数入口 void main(void) { KeyLightLed(); } /** *@brief *@param[in] *@param[out] *@return */ void KeyLightLed() { uchar portData[] = {0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F}; while(1) { int i = 0; for (i; i < 8; i ) { if(P1 == portData[i]) { P0 = ~portData[i]; } } } }
2.点击按钮控制其中一个LED灯,再次点击按钮,然后熄灭,再次按下并点亮,以便循环来回。也就是说,每个按钮都会改变相应的LED灯的状态。
/** *@file key.c *@author Timi *@date 2022.07.21 */ #include <reg51.h> #define uchar unsigned char sbit P00 = P0^0; sbit P01 = P0^1; sbit P02 = P0^2; sbit P03 = P0^3; sbit P04 = P0^4; sbit P05 = P0^5; sbit P06 = P0^6; sbit P07 = P0^7; sbit P10 = P1^0; sbit P11 = P1^1; sbit P12 = P1^2; sbit P13 = P1^3; sbit P14 = P1^4; sbit P15 = P1^5; sbit P16 = P1^6; sbit P17 = P1^7; void KeyLightLed(); ///函数入口 void main(void) { KeyLightLed(); } /** *@brief *@param[in] *@param[out] *@return */ void KeyLightLed() { P0 = 0x0; while(1) { if(P10 == 0) { P00 = ~P00; } if(P11 == 0) { P01 = ~P01; } if(P12 == 0) { P02 = ~P02; } if(P13 == 0) { P03 = ~P03; } if(P14 == 0) { P04 = ~P04; } if(P15 == 0) { P05 = ~P05; } if(P16 == 0) { P06 = ~P06; } if(P17 == 0) { P07 = ~P07; } } }
关于这种方法的灵敏度。
当按数量少的时候,其实并不明显,如果按键数量过多,就会导致按键时,CPU其他部分的代码仍在执行中,因此检测按钮不及时。
上面提到的按钮在按下和弹起的过程中会抖动,这会导致一些问题。例如,按下和弹起按钮时,按钮会闪烁。更明显的是,例如,按钮控制数字管数量依次增加。如果不抖动,按下时可能会多次检测到低电平,因为抖动,所以显然只按下一个按钮,但数字不规则增加,例如,有时增加1,有时超过1。
所以常常都需要消抖,消抖有两方面,一方面是硬件消抖(比如利用电容器来使抖动变得更平滑),另一方面是软件消抖,即在检测到一次某种电平时,不急着进行某种操作,而是延时一段时间,因为这种电平有可能是抖动期间引起的,并不稳定。
通常,抖动的延迟时间在5-20毫秒之间,一般选择10毫秒。示例代码如下:
/** *@file key.c *@author Timi *@date 2022.07.21 */ #include <reg51.h> #define uchar unsigned char sbit P00 = P0^0; sbit P10 = P1^0; void KeyLightLed(void); void Delay10ms(void); ///函数入口 void main(void) { KeyLightLed(); } /** *@brief *@param[in] *@param[out] *@return */ void KeyLightLed(void) { P0 = 0x0; while(1) { if(P10 == 0) { ///不着先点亮LED,延时10ms后再判断,如果状态仍然相同,点亮 Delay10ms(); if (P10 == 0) { P00 = 1; } } else { ///不着先点亮LED,延时10ms后再判断,如果状态仍然相同,点亮 Delay10ms(); if (P10 == 1) { P00 = 0; } } } } /** *@brief *@param[in] *@param[out] *@return */ void Delay10ms(void) //误差 0us { uchar a, b, c; for(c=5; c > 0; c--) { for(b=4;b>0;b--) { for(a=248;a>0;a--); } } }
中断的引入
任务:独立数字管循环显示0-F,同时,按键控制LED亮灭。
这两个功能都需要实现CPU执行死循环怎么办?最简单的就是把这两个程序放在同一个死循环里。这个问题可以在一定程度上解决。
但也有一个问题,即按键检测可能不及时,导致按键不敏感。
为什么呢?因为只有在执行完t时间过后,来t2.按钮检测代码只能在时间内执行,所以如果按钮一直在等待检测,它仍然可以工作;如果你快速按下按钮,很有可能t一次,然后就检测不到了。
这两个任务需要宏观并行执行。t1时间远大于t2的时间,t1可以是秒级,t几乎是微秒级,差距还是比较大的。
p>此时,数码管循环显示就可以看做是主线任务。“主线任务”为常规任务,默认运行。可以将类似按键检测的这种任务绑定到中断。中断发生后CPU暂停主线任务转去处理中断任务,完成后再回来接着执行主线任务。由于中断处理程序所耗费的时间较短,所以几乎不会对主线任务产生影响。
中断流程:
中断处理能力让CPU可以全力处理主线任务而不用担心会错过中断任务(举例:看电影和收快递)
中断式比轮询式更适合处理异步事件,效率更高。
中断中处理的事件的特点是:无法预料、处理时间短、响应要求急。
中断能力是CPU本身设计时支持的,并不是编程制造出来的 程序员只要负责2件事即可:主程序中初始化中断、定义中断处理程序。 当中断条件发生时,硬件会自动检测到并且通知CPU,CPU会自动去执行中断处理程序,这一切都是CPU设计时定下的,不需要编程干预。
51单片机的中断
内容较多,具体内容阅读数据手册,中断系统相关章节。
外部中断0使用示例如下:
我们这里就用INT0来进行简单的按键处理。
在电路连接上,需要将按键的一端连接到中断引脚上,INT0的引脚为P3.2
如果中断没有打开并使能,那么P3.2就是一个普通的GPIO。
这里我有个疑惑,那就是,INT0只有一个引脚,那么不就只有1个按键能触发中断了?这也太少了吧。
暂且不管,后面再说。~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
中断代码实现
实现功能如下:按键K1触发外部中断0,然后就执行相应的代码,点亮LED。
/** *@file key.c *@author Timi *@date 2022.07.21 */ #include <reg51.h> #define uchar unsigned char sbit P00 = P0^0; sbit P10 = P1^0; void KeyLightLed(void); void Delay10ms(void); //函数入口 void main(void) { IT0 = 1; //选择下降沿触发 EX0 = 1; //使能INT0 EA = 1; //打开中断开关 while(1); } /** *@brief *@param[in] *@param[out] *@return */ void KeyLightLed(void) interrupt 0 { P0 = 0xAA; }
矩阵键盘
按键作为最基本的输入设备,有独立按键和矩阵按键两种。
和LED类似的理念,独立按键就是两个引脚都有单独的线连接。矩阵按键则是将许多按键的同一端连接在一起,然后通过扫描来检测哪个按键被按下。
独立按键与单片机连接时, 每一个按键都需要单片机的一个 I/O 口, 若某单片机系统需较多按键, 用独立按键便会占用过多的 I/O 口资源。 单片机系统中 I/O 口资源往往比较宝贵, 当用到多个按键时为了减少 I/O 口引脚, 引入了矩阵按键,一般叫做矩阵键盘。
可以看到是将16个按键排成4行4列,前面的四行分别连接io口的每一行,后面的四行分别连接io口的每一列,这样就实现了每个io口都连接四个按键,同样通过这样的方式也可以实现3X3,5X5等这样的布局。
那么在检测的时候又是如何实现的呢,这种按键的检测一般是通过扫描来实现的。
具体原理解析如下:
首先,假定p10-p13接地,p14-p17接电源,则可以通过检测p14-p17是否有电平被拉低,因为扫描的速度够快,所以能检测到按键按下,但这种情况只能确定有哪一行被按下了,并不能定位到具体的按键。
要想定位到具体的按键,那么就需要用到二维检测。
首先,让p10置低电平,p11-p13置高电平,同时检测p14-p17是否有电平被拉低,假如此时检测到p14有电平变化,则肯定是s1按键被按下了。同理,可以检测到任意一个按键是否被按下。
矩阵键盘: 优点:省单片机IO 缺点:不能同时按下多个按键
数码管扫描(输出扫描)
原理:显示第1位→显示第2位→显示第3位→……,然后快速循环这个过程,最终实现所有数码管同时显示的效果;
矩阵键盘扫描(输入扫描)
原理:读取第1行(列)→读取第2行(列) →读取第3行(列) → ……,然后快速循环这个过程,最终实现所有按键同时检测的效果。
查看原理图
代码实现
将JP4连接到P1端口,P1端口全部为高电平。
/** *@file key.c *@author Timi *@date 2022.07.21 */ #include <reg51.h> #define uchar unsigned char sbit P10 = P1^0; sbit P11 = P1^1; sbit P12 = P1^2; sbit P13 = P1^3; sbit P14 = P1^4; sbit P15 = P1^5; sbit P16 = P1^6; sbit P17 = P1^7; void KeyLightLed(void); void FeelKey(); //函数入口 void main(void) { KeyLightLed(); } /** *@brief *@param[in] *@param[out] *@return */ void KeyLightLed(void) { while(1) { P1 = 0xFE; //经测试,最后一列是P1.7控制的 FeelKey(); P1 = 0xFD; //经测试,最后一列是P1.7控制的 FeelKey(); P1 = 0xFB; //经测试,最后一列是P1.7控制的 FeelKey(); P1 = 0xF7; //经测试,最后一列是P1.7控制的 FeelKey(); } } /** *@brief *@param[in] *@param[out] *@return */ void FeelKey() { if(P14 == 0) { P0 = 0x01; } if(P15 == 0) { P0 = 0x02; } if(P16 == 0) { P0 = 0x04; } if(P17 == 0) { P0 = 0x08; } }