【51单片机】8-按键

1.按键相关知识

在按键未被按下之前,电路中默认为高电平【1】;

按键被按下后,电路中默认为低电平【0】

1.按键工作原理

1.内部机械结构

内部是没有电路的,电路在引脚上,看着4个引脚,实际上里面两个引脚相互连接,只有按下的时候,1,2才会连接起来

2.电路连接与原理图中图标

按下按键,内部引脚连接再一起;

松开按键,内部引脚断开

3.按键电路接法

4.上拉电阻

在与VCC相连接的位置有一个上拉电阻,所以,使得我们在与VCC和mcu和按键相连接的电路中是保持高电平【如果未按下按钮】

上拉是为了让引脚默认为高电平【因为如果为低电平,则会与VCC形成电压差,就会造成电流】,但是上拉的力量扛不住接地,所以按键没有按下时上拉的力量保证了IO引脚输入为1,而按下后绝对为0【接地是默认无穷大】

5.按下和弹起的区别

区别就是接不接地的问题,也就是引脚输入为1还是为0的问题。

6.按键存在的意义

按键这个设备对CPU来说是一共输入设备,输入的是人的工作。CPU提高监控按键连接的IO引脚的电平输入是1还是0就知道外部有没有人按下这个按键。相当于人通过按按键给CPU输入一个信号。这个信号可以被CPU监测到从而指导CPU去做一定的工作

2.CPU如何处理按键

1.轮询式

所谓的轮询式就是CPU不断的隔很小时间去查看有没有按键被按下,如果按下就处理按键,如果没有按下就过一会再来查看(按键什么时候被按下CPU是无法预知的)

2.中断式

3.按键分类

1.独立按键

2.矩阵键盘

2.独立按键编程

1.原理图和接线分析

(1)8个独立按键接法一样:都是一端接GND,另外一端接插座上

(2)接线:插座接到P1端口,接完之后P1端口8个IO分别对应8个按键(P1.0对应K1,p1.1对应K2,....P1.7对应K8)

(3)为了应用LED点亮或者熄灭来指示按键是否按下,还要给LED接线。P0端口接LED,P1端口接按键。

2.先监测1个按键(用LED作为指示)

(1)使用轮询法,来处理独立按键K1,单片机再循环中每隔很短的时间就监测K1对应的P1.0引脚的输入电平是1还是0,如果是1则表示按键没有按下,延时等待下一次检验;如果是0表示按键引脚按下,点亮一颗LED作为指示。

(2)为了用LED点亮或者熄灭来指示按键是否按下,还要给LED接线,P0端接LED。

#include<reg51.h>

//当前我们要处理的是K1,对应的P1.0这个IO口
//操控的LED是LED1,对应的是P0.0

sbit Key1=P1^0;
sbit LED1=P0^0;



void delay(){
	unsigned char i,j;
	for(i=0;i<100;i++){
		for(j=0;j<200;j++);
	}
	
}

int main(){
	
	while(1){
		
		//再C语言中把一个引脚定义未一个变量key1
		//然后给key1变量赋值就相当于向这个IO引脚输出
		//直接使用(读)这个key1变量,就相当于从这个IO引脚输入
		if(Key1==1){
			//表示此时为高电平,此时未按下按键
			//这里我们要对应原理图和你所插的单片机进行分析,应该是【0灭还是0亮】
			LED1=0;//led此时熄灭
		}else{
			//已经按下按键,Key1==0
			LED1=1;
		}
		
	}
	
	
}

3.监测8个独立按键

(1)如果要监测的按键数量少,可以用位监测。如果多可以直接用端口字节变量来监测。

(2)独立按键之间多个按键之间是彼此独立的,所以允许多个按键同时按下,而且不会互相影响(矩阵按键只能一次按下一个按键,不能同时按下多个)4.按键去抖

3.键值检测和显示

1.何为键值

(1)一般产品按键很多,对于整个程序来说一般都是把按键进行编码,给每一个按键一个对应的编码值,就叫做按键的键值

(2)再正式的比较庞大的程序中,按键的检测部分和处理部分都是隔开的。这两部分隔开有利于各自部分的程序编写,两部分之间用键值来连接。按键监测部分赋值监测按键,一旦发送一个按键事件就产生一个键值,然后将键值传递给按键处理部分。

2.加入数码管显示键值

(1)整个程序分为2部分:第一步部分做按键检测,另一部分负责将接收到

(2)将muc中的P0与数码管连接,mcu中的P1与独立按键连接

注意点:

注意点1:显示问题

//当前我们要处理的是K1,对应的P1.0这个IO口
//操控的LED是LED1,对应的是P0.0

sbit Key1=P1^0;
sbit LED1=P0^0;

sbit Key2=P1^1;
sbit LED2=P0^1;

sbit Key3=P1^2;
sbit LED3=P0^2;


		if(Key1==1){
			//表示此时为高电平,此时未按下按键
			//LED1=0;//led此时熄灭
		}else{
			//已经按下按键,Key1==0
			LED1=1;
			keynum=1;
		}

(1)我们将P0.0作为数码管的端口,此时我们按下【按键】,则数码管上显示“1”,当我们松开,则按键处于断电状态,此时进入if语句中,将LED1=0,因为LED1对应的是P0.0端口,此时我们就意味着修改了P0.0这个端口的数值。

所以我们应该将if语句中的LED1=0注释掉。

简单写法
		/**
			简单写法
		*/
		
		if(Key1==0){//此时“按键”已经被按下
			keynum=1;
		}
		if(Key2==0){
			keynum=2;
		}
		if(Key3==0){
			keynum=3;
		}
		
读取整个端口的数值
		/**
			读取整个端口
			如果使用端口读取,则直接使用P1【因为P1是连接着独立按键】
		*/
		//高电平【1】表示未按下按键,【0】表示被按下按键
		//1111 1110
		if(P1==0xfe){//此时“按键”已经被按下
			keynum=1;
		}
		//1111 1101
		if(P1==0xfd){
			keynum=2;
		}
		//1111 1011
		if(P1==0xfb){
			keynum=3;
		}

注意点2:多个按钮按下问题

		使用整个IO端口读入--漏洞:此时无法同时按下多个按钮
		
		if(P1==0xfe){//1111 1110
			//表示最右边的按键被按下
			keynum=1;
		}
		
		if(P1==0xbf){//1011 1111
			//表示最右边的按键被按下
			keynum=7;
		}
		
		if(P1==0x7f){//0111 1111
			//表示最右边的按键被按下
			keynum=81;
		}

如果我们按照上面这样写,则无法同时按下多个按钮。

//独立数码管的段码表
unsigned char val[16]={0xc0,0xf9,0xA4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};


//*****************************************
//函数声明
void display(unsigned char num);
void delay();

//***********************************************************
//该函数将num送到独立数码管中进行显示
void display(unsigned char num){
	
	//原来P0端口我们接着LED(因为我们原来在测试按钮,此时将起接到独立数码管中)
	P0=val[num];
}


void delay(){
	unsigned char i,j;
	for(i=0;i<100;i++){
		for(j=0;j<200;j++);
	}
	
}
//*********************************************************
int main(){
	
	//默认为0
	//如果按键未被按下,则数码管默认输入“0”
	unsigned char keynum=0;
		
	unsigned char i=0;
		for(i=0;i<8;i++){//因为我们P1IO端口有8个IO口,所以需要循环8次
			//P1 &(0x1<<i))==0:这里判断是否为0
			//我们通过P1与0x1位移的每一位进行比较,&【同1则1,其余为0】
			//如果我们p1中有一位刚好与【0x1《《i】&出结果为0,则表示p1中的数值为0,则表示为低电平
			//则表示此按键已经被按下
			if( (P1 &(0x1<<i))==0  ){
					keynum=i+1;//i是从0开始
			}
		}
		
		
		
		//在这里去处理按键
		//处理方法就是把整个按键发出到独立数码管去显示
		display(keynum);
		
	}
	
	
}

4.按键的消抖

1.案例:按键按一次数码管显示数字+1

我们设计代码后,如果一直按着加键,则数值会一直加。如果我们一个一个的按,则显示结果不是我们想要的,因为CPU判断的速度和我们手动去按的速度不一样。

2.什么是抖动

(1)按键按下和弹起时的电平变化图

(2)抖动如何产生【物理产生的,无法消除的】

(3)抖动的危害:在抖动时间范围内引脚的电平变化是不定的,如果程序在这一段时间范围内去判断引脚的电平从而来判断有无按键,则有很大可能性会误判。

3.如何消抖

1.硬件消抖

在硬件设计上想办法降低抖动,这是一种主动消抖。【使用电容器--》滤波,电容器可以阻碍电容变化-----》吸走高频】

2.软件消抖

既然在硬件上不能完全消除抖动,软件设计上想办法绕开抖动造成的影响,这是一种被动的(逃避式)的消抖。---》通过延时,来判断是真低电平还是高电平。

while(1){
		
		if(key1==0){
			//发现一次低电平,有可能是按键按下,也可能是抖动,软件消除
			delay10ms();
			if(key1==0){
				//10ms后还是低电平,则说明是真的按下,不是抖动
				AddDisplay();
			}
            delay10ms();
		}

5.完整的按键检测

1.一次完整的按键事件

(1)按键事件就是按键操作过程中不同状态的切换

(2)一个完整的事件包括:按下事件(电平由高变低),弹起事件(由低到高)

(3)一般都认为发生了一次完整的按键事件才算的用户操作了一次按键,程序才会去处理按键,所以在一次完整的按键事件中程序只能去处理一次按键。

2.改良版按键增加数码管显示

1.按下按钮数值不变,当松开按键时,数值+1

        unsigned char flag=0;//默认为0

        while(1){
            if(Key1==0){
            //发现一次低电平,有可能是按键按下,也可能是抖动
                delay10ms();
                if(Key1==0){
                    //10ms后还是低电平,说明是真的按键按下,不是抖动
                    //这里说明发现了一个按下事件
                        flag=1;
                }
            }else{//Key1==1【表示未按下按键,或者正在弹起】
                //弹起也要消抖
                delay10ms();
                if(Key1==1){
                    //说明弹起
                    if(flag==1){//判断此时是不是按下产生的弹起,因为上面我们设置了按下flag=1
                        AddDisplay();//等按下后才弹起的数值+1
                        //记得将flag置为0,才可能进入下一次循环
                        flag=0;
                    }
                }
            }
        }

/******************************全局变量定义*************************/
unsigned char dnum = 0;//默认从0开始加
/**************************************************************/
int main(){
	//这里默认为0,用来表示此时按键的状态
	unsigned char flag=0;
		
		while(1){
			
			if(Key1==0){
			//发现一次低电平,有可能是按键按下,也可能是抖动
				delay10ms();
				if(Key1==0){
					//10ms后还是低电平,说明是真的按键按下,不是抖动
					//这里说明发现了一个按下事件
					if(flag<2){
						flag=1;
					}
				}
			}else{//Key1==1【表示未按下按键,或者正在弹起】
				//弹起也要消抖
				delay10ms();
				if(Key1==1){
					//说明弹起
					if(flag==1){//判断此时是不是按下产生的弹起,因为上面我们设置了按下flag=1
						AddDisplay();//等按下后才弹起的数值+1
						//记得将flag置为0,才可能进入下一次循环
						flag=0;
					}
				}
			}
		}
			
		delay();
}
// 该函数将num数字送到独立数码管去显示
void AddDisplay(void)
{

	dnum = dnum + 1;
	if (dnum > 15)
	{
		dnum = 0;
	}

	P0 = val[dnum];
}

// 延时函数
void delay(void)
{
	unsigned char i, j;

	for (i=0; i<100; i++)
		for (j=0; j<200; j++);
}

2.按下按键时数值+1,松开按钮数值不变

//这里默认为0,用来表示此时按键的状态
    unsigned char flag=0;
        
        while(1){
            if(Key1==0){
            //发现一次低电平,有可能是按键按下,也可能是抖动
                delay10ms();
                if(Key1==0){
                    //10ms后还是低电平,说明是真的按键按下,不是抖动
                    //这里说明发现了一个按下事件
                        if(flag==0){
                            AddDisplay();
                            flag=1;
                        }

                }
            }else{//Key1==1【表示未按下按键,或者正在弹起】
                //弹起也要消抖
                delay10ms();
                if(Key1==1){
                    //说明弹起
                    if(flag==1){//判断此时是不是按下产生的弹起,因为上面我们设置了按下flag=1
                        //记得将flag置为0,才可能进入下一次循环
                        flag=0;
                    }

                }
            }
        }

int main(){
	//这里默认为0,用来表示此时按键的状态
	unsigned char flag=0;
		
		while(1){
			
			if(Key1==0){
			//发现一次低电平,有可能是按键按下,也可能是抖动
				delay10ms();
				if(Key1==0){
					//10ms后还是低电平,说明是真的按键按下,不是抖动
					//这里说明发现了一个按下事件
						if(flag==0){
							AddDisplay();
							flag=1;
						}
				}
			}else{//Key1==1【表示未按下按键,或者正在弹起】
				//弹起也要消抖
				delay10ms();
				if(Key1==1){
					//说明弹起
					if(flag==1){//判断此时是不是按下产生的弹起,因为上面我们设置了按下flag=1
						//记得将flag置为0,才可能进入下一次循环
						flag=0;
					}
				}
			}
		}
			
		delay();
}
// 该函数将num数字送到独立数码管去显示
void AddDisplay(void)
{

	dnum = dnum + 1;
	if (dnum > 15)
	{
		dnum = 0;
	}

	P0 = val[dnum];
}

6.中断

1.案例引入

独立数码管循环显示0-F,同时按键控制LED亮灭

#include<reg51.h>


/**

	中断测试:
		
*/

// 当前要处理的是K1,对应P1.0IO口,操控的LED是LED1,对应P0.0

/*********************变量定义************************************/
sbit key1 = P1^0;
sbit key2 = P1^1;
sbit key8 = P1^7;

//因为P0端口被数码管占用,所以我们LED只能使用P2端口
//这里为什么从7开始??因为P2端口的7在上面
sbit led1 = P2^7;
sbit led2 = P2^6;




// 独立数码管的段码表
unsigned char val[16] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e};



/*********************************** 函数声明 ***********************/
void delay(void);
void delay10ms(void);

// 延时函数
void delay(void)
{
	unsigned char i, j;

	for (i=0; i<200; i++)
		for (j=0; j<250; j++);
}

void delay10ms(void)   //误差 0us
{
    unsigned char a,b,c;
    for(c=5;c>0;c--)
        for(b=4;b>0;b--)
            for(a=248;a>0;a--);
}

int main(){
	
	unsigned char i=0;
	while(1){
		//【任务1】:在数码管上1循环显示0-F
		for(i=0;i<16;i++){
			//这里P0端口被数码管使用
			P0=val[i];
			//时间变化太快看不到数值
			delay();
		}
		
		//【任务2】:按键监测和led控制
		if(Key1==0){
			led1=1;
		}else{
			led1=0;
		}
		
		if(Key2==0){
			led2=1;
		}else{
			led2=0;
		}
	}
	
}

(1)分析是否实现,实践证明可以实现功能,但是按键监测控制LED这边非常不灵敏

(2)逐步认识到单片机只有一个”主线任务“的特点

(3)多任务时如何及时响应?

2.中断的思路

(1)”主线任务“为常规任务,默认运行

(2)中断发生后CPU暂替主线任务转去处理中断任务,完成后再回来接着执行主线任务。

3.中断的意义

(1)中断处理内容让CPU可以全力处理主线任务而不用担心会错过中断任务(看电影和收快递)

(2)中断式比轮询式更加合适处理异步事件,效率更高。

(3)中断任务的特点:处理时间短,响应要求急,无法事先预料

7.使用单片机外部中断处理按键

1.外部中断INTO和INT1

int0【表示”外部中断0】

int1【表示“外部中断1”】

1.何为外部中断

中断源来自于单片机外部就叫外部中断,51支持4个外部中断。分别对应4个引脚。每一个外部中断都对应一个特定的单片机IO引脚(比如INT0对应P3.2,这个是单片机再设计时就设定好的,是无法改变的)。

我们软件只需要对P3.2做一些相关配置,P3.2 就可以响应外部的中断事件。当硬件产生了一个外部中断时CPU就会收到一个中断信号。从而转去执行外部中断对应的处理程序(这个处理程序也是我们软件需要去编写提供的)。

2.外部中断对应

外部中断对应哪一个引脚??数据手册上有。

2.参考数据手册中“中断处理程序”示例代码写程序

1.编程使用INT0处理按键

【IT0】:这一位用来设置中断触发模式:下降沿触发【1】(Falling)或者低电平【0】触发(Low level)【我们一般初始化为1,因为按键事件只有一个下降沿】

【EX0】:外部中断0的开关。如果EX0=0则外部中断再单片机内部被关闭,此时CPU无法收到INT0的中断信息所以不会处理INT0;如果需要使用INT0就一定要设置为1.

【EA】:全局的中断开关。EA如果关掉,则整个CPU不能响应中断,如果EA打开也不一定可以响应中断,还得具体的中断开关打开才行。

2.程序解释

//2.中断处理程序
//interrupt:关键字
//Isr:中断处理程序
void Enit0Isr() interrupt 0
{
	//进入中断后LED状态转换,原来亮则灭,原来灭则亮
	if(led1==1){
		led1=0;
	}else{
		led1=1;
	}
}


int main(){
	
	unsigned char i=0;
	
	
	//1.中断处理的初始化
	IT0=1;
	EX0=1;
	EA=1;
	
	
	//2.主线任务
	while(1){
		//在数码管上1循环显示0-F
		for(i=0;i<16;i++){
			//这里P0端口被数码管使用
			P0=val[i];
			//时间变化太快看不到数值
			delay();
		}
		
		
	}
	
}

3.总结

(1)中断能力的CPU本身设计时支持的,并不是编程制造出来的

(2)程序员只要负责2件事情即可:主程序中初始化中断,定义中断处理程序

(3)当中断条件发生时,硬件会自动检测到并且通知CPI,CPU会自动去执行中断处理程序,这一切都是CPU设计时定下来的,不需要编程干预。

8.矩阵键盘的原理

1.原理分析

(1)横向和纵向分组(因为有一端是连接再一起的)

(2)按键两端分别接不同的IO引脚

(3)按键的物理作用不变:接下接通电路,弹起断开电路

2.工作原理

主要步骤:分别给0x0f和0xf0

(1)先送(IO引脚输出)0x0f

(2)如果有按键接收到的不是0x0f,从收到的数据(IO引脚输入)判断哪一行按下了

(3)再送(IO引脚输出)0xf0

(4)从收到的数据(IO引脚出入)判断哪一列按下了

(5)综合2次得到的行和列的位置,计算出键值

3.特点

(1)优点:节省单片机的IO口

(2)缺点:不能同时按下多个按键【可能会产生多个焦点,无法确定按下是哪一个按键】

9.矩阵键盘编程实战

1.实验研究按键按下的规律(LED显示辅助)

#include<reg51.h>

//P0:接LED
#define LED P0



//P3:矩阵键盘
#define KEY P3


void main(){
	
	//【第一回合第一步】输出
	KEY=0x0f;		//从IO口输出,写IO口
	//因为KEY是接IO空,所以中间KEY可能会变
	if(KEY!=0x0f){		//从IO口输入,读IO口
		//读出的不是0x0f说明有按键被按下
		//【第一回合第2步】:读出端口从读出值来判断是哪一行
		LED=KEY;//直接读出到LED中
	 }
		//【第二回合第1步】
		KEY=0xf0;
		if(KEY!=0xf0){
			LED=KEY;
		}
}

文章来源地址https://uudwc.com/A/4rnby

2.编写键值检验函数

unsigned char GetKey(void){
	
	//定义行列
	unsigned char hang=0,lie=0;
	//显示当前按下的按键的键值
	unsigned char keyvalue=0;
	
	
	//【第一回合第一步】输出
	KEY=0x0f;		//从IO口输出,写IO口
	
	//因为KEY是接IO空,所以中间KEY可能会变
	if(KEY!=0x0f){		//从IO口输入,读IO口
		//读出的不是0x0f说明有按键被按下
		//【第一回合第2步】:读出端口从读出值来判断是哪一行
		
		//1.算出行号
		switch(KEY){
			case 0xfe:hang=1;	break;
			case 0xfd:hang=2;	break;
			case 0xfb:hang=3;	break;
			case 0xf7:hang=4;	break;
			default:break;
		}
		
	
	 }
		//【第二回合第1步】
		KEY=0xf0;
		if(KEY!=0xf0){
			
			//1.算出列号
			switch(KEY){
				case 0xef:lie=1;	break;
				case 0xdf:lie=2;	break;
				case 0xbf:lie=3;	break;
				case 0x7f:lie=4;	break;
				default:break;
			}
		
				//经过2个回合后hang和lie都知道了,然后根据hang和lie计算出键值
				//(行-1)*4+列=键值
				keyvalue=(hang-1)*4+lie;
				return keyvalue;
			
			}
}

3.独立数码管显示键值

// 独立数码管的段码表
unsigned char val[16] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82,
0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e};
//************************************************
//函数声明
unsigned char GetKey(void);

void main(){
	
	unsigned char key=0;
	while(1){
		key=3;
		//key=GetKey();
		if(key!=0){
			DIG=val[key];
		}
	}	
}

4.总结

#include<reg51.h>


//P0:接数码管
#define DIG P0


//P3:矩阵键盘
#define KEY P3


// 独立数码管的段码表
unsigned char val[16] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82,
0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e};
//************************************************
//函数声明
unsigned char GetKey(void);
void delay10ms(void);

void delay10ms(void)   //误差 0us
{
    unsigned char a,b,c;
    for(c=5;c>0;c--)
        for(b=4;b>0;b--)
            for(a=248;a>0;a--);
}

void main(){
	
	unsigned char key=0;
	while(1){
		key=3;
		//key=GetKey();
		if(key!=0){
			DIG=val[key];
		}
	}	
}

unsigned char GetKey(void){
	
	//定义行列
	unsigned char hang=0,lie=0;
	//显示当前按下的按键的键值
	unsigned char keyvalue=0;
	
	
	//【第一回合第一步】输出
	KEY=0x0f;		//从IO口输出,写IO口
	
	//因为KEY是接IO空,所以中间KEY可能会变
	if(KEY!=0x0f){		//从IO口输入,读IO口
		//读出的不是0x0f说明有按键被按下
		//【第一回合第2步】:读出端口从读出值来判断是哪一行
		
		//因为按键会出现显示效果不太符合,所以这里需要消抖
		delay10ms();
		
		//1.算出行号
		switch(KEY){
			case 0x0e:hang=1;	break;
			case 0x0d:hang=2;	break;
			case 0x0b:hang=3;	break;
			case 0x07:hang=4;	break;
			default:break;
		}
		
	
	 }
		//【第二回合第1步】
		KEY=0xf0;
		if(KEY!=0xf0){
			
			delay10ms();
			
			//1.算出列号
			switch(KEY){
				case 0xe0:lie=1;	break;
				case 0xd0:lie=2;	break;
				case 0xb0:lie=3;	break;
				case 0x70:lie=4;	break;
				default:break;
			}
		
				//经过2个回合后hang和lie都知道了,然后根据hang和lie计算出键值
				//(行-1)*4+列=键值
				keyvalue=(hang-1)*4+lie;
				return keyvalue;
			
			}
}

原文地址:https://blog.csdn.net/m0_63077733/article/details/133241785

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

上一篇 2023年10月12日 12:50
【数据结构】堆,堆的实现,堆排序,TOP-K问题
下一篇 2023年10月12日 13:50