九、按键
1、按键相关知识
a、按键工作原理
(1)内部机械结构:按键外部表现为一个长方体,上部有一个圆柱体的按钮,下部有四个引脚。在内部四个引脚中有
两个为一组是相互连接的,可等效为1、2两组电路。在内部,2组电路在下,1组电路在上,同时1组电路也是一个压片弹
簧,按键按下的时候1、2组电路接通,松开时,1、2组电路断开。
(2)电路连接与原理图中图标:在电路连接和原理图中,按键是由两个引脚上面有一个悬空的“帽子”,其变现形式
和我们对按键内部结构的认识基本相同。
(3)按键电路接法、上拉电阻:按键一端连在单片机引脚上,一端接地,同时单片机引脚还外接了一个电阻,电阻一
端连单片机引脚,一端接电源VCC。因此该电阻十分重要,这个电阻使得当我们按键不按下的时候该引脚是保持高电平的。
这个电阻被称为上拉电阻。注意:上拉是为了让引脚保持高电平,但是上拉的力量扛不住接地的力量的,只要一接地电平
就会变成低电平。所以按键没有按下的时候上拉的力量保证了IO引脚输入为1,而按下后由于接地IO引脚的输入为0。
(4)按下和弹起的区别:区别就是IO引脚接不接地的问题。按下按键的时候,单片机引脚接地,从高电平变为低电平。
并且造按键按下期间都是低电平;按键弹起的时候,单片机和GND连接断开,VCC使该引脚从低电平变为高电平。并且按键
不按下该引脚一直是高电平。也就是引脚输入是1还是0的问题了。
(5)按键这个设备的意义:按键对于CPU来说是一个输入设备,输入的是人的操作。CPU通过检测按键连接的IO引脚
的电平输入是1还是0就知道外部有没有人按下这个按键。相当于人通过按按键给CPU输入一个信号,这个信号可以被CPU监
测到从而指导CPU去做一定的工作。
b、CPU如何处理按键
(1)轮询式:所谓轮询式就是CPU不断的间隔很小的时间间隔去查看有没有按键被按下,如果按下就处理按键,如果
没有按下就过一会儿再来查看。(按键什么时候被按下CPU是无法预知的)
(2)中断式:何为中断?中断就是CPU在执行程序的时候,去处理突发事件,CPU要停止原程序的处理,处理完突发
事件继续执行原程序。中断分为硬件中断和软件中断。
c、按键电路接法分类
(1)独立按键:独立按键均为一端接地,另一端接到单片机的引脚上。
(2)矩阵键盘:16个按键以4*4的正方形排列,这些按键每一行的左端引脚接在一起和一个单片机引脚接在一起,每
一列的按键的右端引脚接在一起和一个单片机的引脚相连。这样16个按键只需要8个引脚就可以检测其全部的输出,如果以
独立按键的方式连接则需要16个引脚才能全部检测。
2、独立按键编程
a、原理图与接线分析
(1)8个独立按键接法一样,一端接地,一端接到单片机的IO引脚上。
(2)接线:8PIN杜邦线连接单片机IO端口和8个独立按键的插针排座。例如:P1.0对应K1、P1.1对应K2......
P1.7对应K8。
(3)为了用LED来指示按键是否按下,需要把LED的插针排座有杜邦线和单片机IO端口相连接。
b、先检测一个独立按键(LED指示)
(1)使用轮询法来处理独立按键K1,单片机在循环中没隔很短的时间就检测K1对应的P1.0引脚的输入电平是1还是0。
如果是1则表示按键还没有按下,熄灭LED为指示。延时等待下一次检验;如果是0表示按键已经按下了,点亮一颗LED为指
示。
(2)当前要处理的是K1,对应P1.0IO端口,操控的LED是LED1,对应P0.0
(3)代码:
#include <REGX51.H>
sbit key1 = P1^0;
sbit led1 = P0^0;
void main(void)
{
while (1)
{
//C语言把一个IO引脚定义成一个变量key1
//然后key1变量赋值就相当于是向这个IO引脚输出
//直接使用(读)这个key1变量,就相当于从这个IO引脚输入
if (key != 1)
{
//按键按下
led1 = 0; //点亮
}else
{
//按键没有按下
led1 = 1; //熄灭
}
//注:在这个版本开发板中LED的阳极一起接了VCC,负极接单片机引脚。因此,led1为1时灭
//led1为0时亮。
}
}
c、扩展为检测8个独立按键
(1)如果检测的按键数量少,可以用位来监测。如果端口多则可以直接用端口字节来监测。
(2)独立按键多个按键之间是相互独立的,所以允许多个按键同时按下而不会影响。(矩阵按键就只能一次按一个按
键,不能多个同时按下)
(3)代码:
#include <REGX51.H>
#define led P0
#define key P1
void main(void)
{
while (1)
{
if (key != 0xff)
{
led = key; //LED是1灭0亮可以直接赋值,如果是1亮0灭则需要取反再赋值
}else
{
led = 0xff;
}
}
}
3、键值检测与显示
a、何为键值
(1)一般的产品中按键是比较多的,对于整个程序来说一般都是把按键进行编码,给每个按键一个对应的编码值,就
叫做按键的键值。
(2)在正式比较庞大的程序中,按键的检测部分和处理部分都是隔开的。这两部分有利于格子部分的程序编写,这两
部分用键值来连接。按键监测部分负责监测键值,一旦发生一个按键事件就产生一个键值,然后将键值部分传递给按键部分
处理。
b、加入数码管显示键值
(1)整个程序分为两部分:第一部分做按键监测并且发出键值;第二部分接收键值并将其显示在数码管上。
(2)代码:
(1)
#include <REGX51.H>
#define key P1
#define smg P0
//数码管段码表
unsigned char duan[16]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x98,0x88,0x83,0xc6,0xa1,0x86,0x8e};
//函数声明
unsigned char keynumber();
void display(unsigned char num);
void main(void)
{
unsigned char num;
while(1)
{
//接收键值并用数码管显示
num = keynumber();
if(num != 0)
{
display(num);
}
else
{
display(0xff);
}
}
}
//监测按键并发出键值
unsigned char keynumber()
{
unsigned char keynumber;
if(key != 0xff)
{
switch ((~key))
{
case 0x01:
keynumber = 1;
break;
case 0x02:
keynumber = 2;
break;
case 0x04:
keynumber = 3;
break;
case 0x08:
keynumber = 4;
break;
case 0x10:
keynumber = 5;
break;
case 0x20:
keynumber = 6;
break;
case 0x40:
keynumber = 7;
break;
case 0x80:
keynumber = 8;
break;
default:
break;
}
}
else
{
keynumber = 0;
}
return keynumber;
}
//数码管显示函数
void display(unsigned char num)
{
if(num != 0xff)
{
smg = duan[num];
}else
{
smg = 0xff;
}
}
(2)#include <REGX51.H>
#define key P1
#define smg P0
unsigned char duan[16]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x98,0x88,0x83,0xc6,0xa1,0x86,0x8e};
//unsigned char keynumber();
void display(unsigned char num);
void main(void)
{
// unsigned char num;
unsigned char keynumber, i;
while(1)
{
if(key != 0xff)
{
for(i=0; i<8; i++)
{
if((P1 & (0x01 << i)) == 0x00)
{
keynumber = i+1;
}
}
}
else
{
keynumber = 0;
}
display(keynumber);
}
}
void display(unsigned char num)
{
if(num != 0)
{
smg = duan[num];
}
else
{
smg = 0xff;
}
}
两种代码效果相同,第二种方法更简洁,并且可以检测到所有的按键信息,第一种按下一个按键不松
手的时候,再按其他按键不会有反应,第二种可以检测到按下了其他按键并显示
4、按键的消抖
a、案例:按键按一次数码管显示数字加1
代码:
#include <REGX51.H>
#define smg P0
sbit key1 = P1^0;
unsigned char duan[16]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x98,0x88,0x83,0xc6,0xa1,0x86,0x8e};
unsigned char keynumber = 0;
void Adddisplay(void);
void delay(unsigned int t);
void main(void)
{
while(1)
{
if (key1 == 0)
{
Adddisplay();
}
}
}
void Adddisplay(void)
{
keynumber ++;
if (keynumber > 15)
{
keynumber = 0;
}
smg = duan[keynumber];
delay(20);
}
void delay(unsigned int t)
{
while(t--)
{
unsigned char i = 2;
unsigned char j = 239;
do
{
while(j--);
}while(i--);
}
}
实验结论:该代码可以是实现功能,但是经常会导致按一下会加好几次。经过研究和资料研究认为这个问
题有可能是按键抖动导致的。
b、什么是抖动?
(1)按键按下和弹起的电平变化图:按键按下的时候电平有高电平变为电平;弹起的时候有低电平变高
电平。在理论图中按下的瞬间有高电平变低电平;弹起也是瞬间由低电平变为高电平。但是在实际实验中发
现电平变化不是瞬间完成的,其需要有一个时间来达到稳定的高电平或者低电平。在这个时间中,电平会起
伏不定,这也是导致在实验中按一下加好几次的原因所在。
(2)抖动如何产生:通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点
的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合
和断开的瞬间伴随了一连串的抖动。
(3)抖动的危害:在抖动的时间范围内引脚的电平变化是不定的,如果程序在这一段时间范围内去判断
引脚的电平从而判断有无按键,则有很大可能会发生误判
c、如何消抖
(1)硬件消抖,在硬件设计上想办法降低抖动,这是一种主动消抖。
(2)软件消抖,既然在硬件上不可能完全消抖,软件设计上就要想办法绕开抖动造成的影响,这是一种
被动(逃避式)的消抖。
5、完整按键消抖
a、一次完整的按键事件
(1)按键事件就是按键操作过程中的不同状态切换
(2)一个完整的按键事件包括:按下事件(电平由高变低的变化)、弹起事件(电平由低到高的变化)
(3)一般都认为发生了一次完整的按键事件才算是用户操作了一次按键,程序才会去处理按键,所以一
次完整的按键事件中程序只会去处理一次按键。
b、改良版按键增加数码管显示
代码:
#include <REGX51.H>
#define smg P0
sbit key1 = P1^0;
unsigned char duan[16]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x98,0x88,0x83,0xc6,0xa1,0x86,0x8e};
unsigned char num = 0;
unsigned char flag = 0;
void display(void);
void delay(unsigned int t);
void keyshut(void);
void main(void)
{
while(1)
{
keyshut();
}
}
void display(void)
{
num++;
if (num > 15)
{
num = 0;
}
smg = duan[num];
}
void keyshut(void)
{
if (key1 == 0)
{
//发现了一次低电平,有可能是按键按下,也有可能是抖动,软件消抖
delay(10);
if (key1 == 0)
{
//进过10ms的延时还是低电平说明按键真的被按下了,不是抖动
//这里是一次按下事件
if (flag == 0)
{
flag = 1;
if (flag > 0 && flag == 1)
{
flag ++;
display();
}
}
}
}
else
{
flag = 0;
}
}
void delay(unsigned int t)
{
while(t--)
{
unsigned char i = 2;
unsigned char j = 239;
do
{
while(j--);
}while(i--);
}
}
实验结论:在按键按下去就会显示的数字+1,之后不会连续相加。按键弹起再次按下才会+1,实现了预
期的目标。
6、中断的引入
a、任务:独立数码管循环显示0~F,同时按键控制LED亮灭。
(1)逐步分析能否实现,实践证明可以实现功能,但是按键监测控制LED非常不灵敏。
(2)逐步认识到单片机只有一个“主线任务”的特点
(3)多任务时如何及时响应?使用中断来对于多任务的及时响应
‘
b、中断的思路
(1)“主线任务”为常规任务,默认运行
(2)中断发生后CPU暂停主线任务转去处理中断任务,完成后回来接着执行主线任务
c、中断的意义
(1)中断处理能力让CPU可以权利处理主线任务而不用担心会错过中断任务(举例:看视频和收快递)
(2)中断式和轮询式更适合处理异步事件,效率更高。
(3)中断中处理事件的特点是:事先无法预料、处理时间短、响应要求急
7、使用单片机外部中断来处理按键
a、外部中断INT0和INT1
(1)何为外部中断?中断源来自于单片机外部就叫做外部中断,51支持4个外部中断源。分别对应4个引
脚。每一个外部中断都对应一个特定的单片机IO引脚(譬如INT0对应P3.2,这个是单片机设计的时候定好
的,是无法改变的)。我们软件只需要对P3.2做一些相关设置,P3.2就可以响应外部的中断事件。当硬件产
生了一个外部中断时CPU就会收到一个中断信号,从而转去执行外部中断对应的处理程序(这个处理程序需要
我们软件去编写提供)。
(2)外部中断对应那些引脚?详细情况在数据手册上都有。
b、参考数据手册中示例写代码
(1)编程使用INT0和INT1处理按键
代码:
#include <REGX51.H>
#define smg P0
sbit led1 = P2^0;
sbit led2 = P2^1;
unsigned char duan[16]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x98,0x88,0x83,0xc6,0xa1,0x86,0x8e};
void display(void);
void delay(unsigned int t);
void main(void)
{
//开启外部中断0和1
IT0 = 1;
IT1 = 1;
EX0 = 1;
EX1 = 1;
EA = 1;
while(1)
{
//任务一:数码管循环显示
display();
}
}
void display(void)
{
unsigned int i;
for(i=0; i<16; i++)
{
smg = duan[i];
delay(150);
}
}
void delay(unsigned int t)
{
while(t--)
{
unsigned char i = 2;
unsigned char j = 239;
do
{
while(j--);
}while(i--);
}
}
void led9() interrupt 0
{
led1 = ~led1;
}
void led10() interrupt 2
{
led2 = ~led2;
}
(2)程序解释
IT0这一位用来设置中断的触发模式:下降沿触发(Falling)或者低电平触发(lowlevel)
EX0这一位是INT0的开关。如果EX0=0则外部中断在单片机内部被关闭,此时CPU无法收到INT0的
中断信息所以不会处理INT0;如果需要使用INT0就一定要设置为1。
EA是全局的中断开关。EA如果关掉则整个CPU就不能响应中断,所有中断都关掉了。光EA打开也不
一定可以响应中断,需要具体的中断开关打开才行。
c、总结
(1)
8、矩阵键盘的原理
a、矩阵键盘的原理图分析
(1)横向和纵向分割
(2)按键两端分别接不同的IO引脚
(3)按键的物理作用不变:按下接通电路,弹起断开电路
b、矩阵键盘的工作过程
(1)先送(IO引脚输出)0x0f
(2)若有按键按下则接收到就不是0x0f,从接收到的数据(IO引脚输入)判断哪一行按下了
(3)再送(IO引脚输出)0xf0
(4)从收到的数据(IO引脚输入)判断哪一行按下了
(5)综合两次得到的行和列的位置就可以计算出键值
c、矩阵键盘的特点
(1)优点:节省单片机IO引脚
(2)缺点:不能同时按下多个按键
9、矩阵键盘编程原理
a、实验研究按键按下的规律(LED显示辅助)
(1)实验一
代码:
#include <stdio.h>
#define key P1
#define led P0
void main(void)
{
//第一回合第一步
key = 0x0f; //从IO口输出,写IO口
if (key != 0x0f) //从IO口输入,读IO口
{
//读出不是0x0f说明有按键被按下
//第一回合第二步:读出端口从读出的值来判断是哪一行
led = key;
}
}
现象:
按下S1从0000 1111变为0000 1110可以推出bit0对应第一行
按下S2从0000 1111变为0000 1101可以推出bit1对应第二行
以此类推bit2~3分别对应第三行和第四行。
(2)实验二
代码:
#include <stdio.h>
#define key P1
#define led P0
void main(void)
{
//第一回合第一步
key = 0x0f; //从IO口输出,写IO口
if (key != 0x0f) //从IO口输入,读IO口
{
//读出不是0x0f说明有按键被按下
//第一回合第二步:读出端口从读出的值来判断是哪一行
//led = key;
//第二回合第一步
key = 0xf0;
if (key != 0xf0)
{
led = key;
}
}
}
现象:
按下S1从1111 0000变为0111 0000可以推出bit4对应第一列
按下S2从1111 0000变为1011 0000可以推出bit5对应第二列
以此类推bit6~7分别对应第三列和第四列。
b、编写键值检验函数
代码:
unsigned char keynumber(void)
{
unsigned char keynumber = 0;
P1 = 0xf0;
if (P1 != 0xf0)
{
delay(10);
if(P1 != 0xf0)
{
switch(P1)
{
case 0xe0: keynumber = 1; break;
case 0xd0: keynumber = 2; break;
case 0xb0: keynumber = 3; break;
case 0x70: keynumber = 4; break;
default: break;
}
P1 = 0x0f;
switch(P1)
{
case 0x0e: keynumber += 0; break;
case 0x0d: keynumber += 4; break;
case 0x0b: keynumber += 8; break;
case 0x07: keynumber += 12; break;
default: break;
}
}
}
return keynumber;
}
c、独立数码管显示键值
代码:
#include <REGX51.H>
#define smg P0
sbit led1 = P2^0;
sbit led2 = P2^1;
unsigned char duan[16]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x98,0x88,0x83,0xc6,0xa1,0x86,0x8e};
void display(unsigned char no);
void delay(unsigned int t);
unsigned char keynumber(void);
void main(void)
{
unsigned char num = 0;
while(1)
{
num = keynumber();
if(num != 0)
{
display(num);
}
}
}
void display(unsigned char no)
{
smg = duan[no-1];
}
unsigned char keynumber(void)
{
unsigned char keynumber = 0;
P1 = 0xf0;
if (P1 != 0xf0)
{
delay(10);
if(P1 != 0xf0)
{
switch(P1)
{
case 0xe0: keynumber = 1; break;
case 0xd0: keynumber = 2; break;
case 0xb0: keynumber = 3; break;
case 0x70: keynumber = 4; break;
default: break;
}
P1 = 0x0f;
switch(P1)
{
case 0x0e: keynumber += 0; break;
case 0x0d: keynumber += 4; break;
case 0x0b: keynumber += 8; break;
case 0x07: keynumber += 12; break;
default: break;
}
}
}
return keynumber;
}
void delay(unsigned int t)
{
while(t--)
{
unsigned char i = 2;
unsigned char j = 239;
do
{
while(j--);
}while(i--);
}
}