目录
1,独立按键
2,矩阵按键
(注意:文章中的代码仅供参考学习,实际使用时要根据需要修改)
1,独立按键
按键管脚两端距离长的表示默认是导通状态,距离短的默认是断开状态, 如果按键按下,初始导通状态变为断开,初始断开状态变为导通。
由于机械点的弹性作用,按键开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开,因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定的,一般为5ms 到10ms。按键稳定闭合时间的长短则由操作人员的按键动作决定的,一般为零点几秒至数秒。按键抖动会引起按键被误读多次。为了确保CPU 对按键的一次闭合仅作一次处理,必须进行消抖。
按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更加简单,通常采用软件消抖。我们开发板也是采用软件消抖,一般来说一个简单的按键消抖就是先读取按键的状态,如果得到按键按下之后,延时10ms,再次读取按键的状态,如果按键还是按下状态,那么说明按键已经按下。
单片机常用的软件去抖动方法:
1,先设置IO 口为高电平(由于开发板IO 都有上拉电阻,所以默认IO 为高电平)。
2,读取IO 口电平确认是否有按键按下。
3,如有IO 电平为低电平后,延时几个毫秒。
4,再读取该IO 电平,如果仍然为低电平,说明按键按下。
5,执行按键控制程序。
独立按键电路构成是由各个按键的一个管脚连接在一起接地,按键其他引脚分别接到单片机IO 口。
我们知道单片机的IO 口既可作为输出也可作为输入使用,当检测按键时用的是它的输入功能,独立按键的一端接地, 另一端与单片机的I/O 口相连,开始时先给该IO 口赋一高电平,然后让单片机不断地检测该I/O 口是否变为低电平,当按键闭合时,即相当于该I/O 口通过按键与地相连,变成低电平,程序一旦检测到I/O 口变为低电平则说明按键被按下,然后执行相应的指令。
CPU如何处理按键
(1)轮询式。所谓轮询式就是CPU不断的隔很小时间去查看有没有按键被按下,如果按下就处理按键,如果没按下就过一会再来查看。(按键什么时候被按下CPU是无法预知的)
(2)中断式
轮询式处理按键代码:
#include <reg51.h>
// 当前要处理的是K1,对应P1.0IO口,操控的LED是LED1,对应P0.0
/*********************变量定义************************************/
sbit key1 = P1^0;
sbit key2 = P1^1;
sbit key8 = P1^7;
// 独立数码管的段码表
unsigned char val[16] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e};
/*********************************** 函数声明 ***********************/
void AddDisplay(void);
void delay(void);
void delay10ms(void);
/******************************全局变量定义*************************/
unsigned char dnum = 0;
void main(void)
{
unsigned char flag = 0; // 默认状态等于0
while (1)
{
if (key1 == 0)
{
// 发现1次低电平,有可能是按键按下,也有可能是抖动,软件消抖
delay10ms();
if (key1 == 0)
{
// 10ms后还是低电平,说明真的是按键按下了,不是抖动
// 这里说明发现了一个按下事件
//flag = 1;
if (flag == 0)
{
AddDisplay();
flag = 1;
}
}
}
else
{
// 电平 == 1
delay10ms();
if (key1 == 1)
{
// 说明弹起了
if (flag == 1)
{
//AddDisplay();
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++);
}
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--);
}
中断式处理按键源代码:
#include <reg51.h>
// 当前要处理的是K1,对应P1.0IO口,操控的LED是LED1,对应P0.0
/*********************变量定义************************************/
sbit key1 = P1^0;
sbit key2 = P1^1;
sbit key4 = P3^4;
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 delay10ms(void);
/*************中断处理程序*************/
void Eint0Isr() interrupt 0
{
// 任务2:按键监测及led控制
// 进入中断后LED状态转换。原来亮则灭,原来灭就亮
if (led1 == 1)
led1 = 0;
else
led1 = 1;
}
void main(void)
{
unsigned char i = 0;
// 中断初始化
IT0 = 1;
EX0 = 1;
EA = 1;
// 主线任务
while (1)
{
// 任务1:数码管显示
for (i=0; i<16; i++)
{
P0 = val[i];
delay10ms();
}
}
}
// 延时函数
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--);
}
上述代码中主线任务是使用数码显示0-F,利用中断功能去检测按键是否按下,当检测到按键按下时中断主线任务去执行中断程序点亮或熄灭LED灯。
中断的思路:
(1)“主线任务”为常规任务,默认运行;
(2)中断发生后CPU暂停主线任务转去处理中断任务,完成后再回来接着执行主线任务;
中断的意义:
(1)中断处理能力让CPU可以全力处理主线任务而不用担心会错过中断任务(举例:看电影和收快递);
(2)中断式比轮询式更适合处理异步事件,效率更高;
(3)中断中处理的事件的特点是:无法预料、处理时间短、响应要求急。
中断任务的处理时间要远远小于主线程序的处理时间才能通过中断来处理。
IT0:这一位用来设置中断的触发模式:下降沿触发(Falling)或者低电平触发(low level);
EX0:这一位是INT0的开关。如果EX0等于0则外部中断在单片机内部被关闭,此时CPU无法收到INT0的中断信息所以不会处理INT0,如果需要使用INT0就一定要设置为1。
EA:是全局的中断开关。EA如果关掉则整个CPU不能响应中断,所有中断都被关了。光EA打开也不一定能响应中断,还得具体的中断开关打开才行。
总结:
(1)中断能力是CPU本身设计时支持的,并不是编程制造出来的;
(2)程序员只要负责2件事即可:主程序中初始化中断、定义中断处理程序;
(3)当中断条件发生时,硬件会自动检测到并且通知CPU,CPU会自动去执行中断处理程序,这一切都是CPU设计时定下的,不需要编程干预。
2,矩阵按键
电路中的ARRAY_H1表示矩阵键盘第1 行,ARRAY_L1 表示矩阵键盘第1 列。
需要注意的是,按键两端如果分别接单片机的两个端口,一个端口置高电平,一个端口置低电平,按下按键时会把高电平的端口拉低,端口置低可以认为是在单片机内部将这个端口接地。
矩阵按键实验程序,按下矩阵按键显示对应的0-F:
第一种方法:
/********************************************************************
******************
实验名称:矩阵按键实验
接线说明:
实验现象:下载程序后,按下“矩阵按键”模块中S1-S16 键,对应静态数码管显示0-F
注意事项:
*********************************************************************
******************/
#include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
#define KEY_MATRIX_PORT P1 //使用宏定义矩阵按键控制口
#define SMG_A_DP_PORT P0 //使用宏定义数码管段码口
//共阴极数码管显示0~F 的段码数据
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
/********************************************************************
***********
* 函数名: delay_10us
* 函数功能: 延时函数,ten_us=1 时,大约延时10us
* 输入: ten_us
* 输出: 无
*********************************************************************
**********/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
/********************************************************************
***********
* 函数名: key_matrix_ranks_scan
* 函数功能: 使用行列式扫描方法,检测矩阵按键是否按下,按下则返回对应键值
* 输入: 无
* 输出: key_value:1-16,对应S1-S16 键,0:按键未按下
*********************************************************************
**********/
u8 key_matrix_ranks_scan(void)
{
u8 key_value=0;
KEY_MATRIX_PORT=0xf7;//给第一列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xf7)//判断第一列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第一列按键按下后的键值
{
case 0x77: key_value=1;break;
case 0xb7: key_value=5;break;
case 0xd7: key_value=9;break;
case 0xe7: key_value=13;break;
}
}
while(KEY_MATRIX_PORT!=0xf7);//等待按键松开
KEY_MATRIX_PORT=0xfb;//给第二列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xfb)//判断第二列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第二列按键按下后的键值
{
case 0x7b: key_value=2;break;
case 0xbb: key_value=6;break;
case 0xdb: key_value=10;break;
case 0xeb: key_value=14;break;
}
}
while(KEY_MATRIX_PORT!=0xfb);//等待按键松开
KEY_MATRIX_PORT=0xfd;//给第三列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xfd)//判断第三列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第三列按键按下后的键值
{
case 0x7d: key_value=3;break;
case 0xbd: key_value=7;break;
case 0xdd: key_value=11;break;
case 0xed: key_value=15;break;
}
}
while(KEY_MATRIX_PORT!=0xfd);//等待按键松开
KEY_MATRIX_PORT=0xfe;//给第四列赋值0,其余全为1
if(KEY_MATRIX_PORT!=0xfe)//判断第四列按键是否按下
{
delay_10us(1000);//消抖
switch(KEY_MATRIX_PORT)//保存第四列按键按下后的键值
{
case 0x7e: key_value=4;break;
case 0xbe: key_value=8;break;
case 0xde: key_value=12;break;
case 0xee: key_value=16;break;
}
}
while(KEY_MATRIX_PORT!=0xfe);//等待按键松开
return key_value;
}
void main()
{
u8 key=0;
while(1)
{
key=key_matrix_ranks_scan();
if(key!=0)
SMG_A_DP_PORT=~gsmg_code[key-1];//得到的按键值减1 换算成数组下标对应0-F 段码
}
}
第二种方法:
#include <reg51.h>
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
#define KEY_MATRIX_PORT P1 //使用宏定义矩阵按键控制口
#define SMG_A_DP_PORT P0 //使用宏定义数码管段码口
//共阳极数码管显示0~F 的段码数据
u8 gsmg_code[16]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
u8 key_matrix_flip_scan(void)
{
static u8 key_value=0;
KEY_MATRIX_PORT=0xf0;//给所有行赋值0,列全为1
if(KEY_MATRIX_PORT != 0xf0)
{
delay_10us(1000);
if(KEY_MATRIX_PORT != 0xf0)
{
switch(KEY_MATRIX_PORT)
{
case 0xe0: key_value = 1;break;
case 0xd0: key_value = 2;break;
case 0xb0: key_value = 3;break;
case 0x70: key_value = 4;break;
}
}
KEY_MATRIX_PORT=0x0f;//给所有行赋值1,列全为0
if(KEY_MATRIX_PORT != 0x0f)
{
delay_10us(1000);
if(KEY_MATRIX_PORT != 0x0f)
{
switch(KEY_MATRIX_PORT)
{
case 0x0e: key_value = key_value;break; //代表第一行的按键
case 0x0d: key_value = key_value+4;break; //代表第二行的按键
case 0x0b: key_value = key_value+8;break; //代表第三行的按键
case 0x07: key_value = key_value+12;break; //代表第四行的按键
}
}
while(KEY_MATRIX_PORT!=0x0f);//等待按键松开
}
else
{
key_value = 0;
}
}
return key_value;
}
void main()
{
u8 key = 0;
while(1)
{
key = key_matrix_flip_scan();
if (key != 0)
{
SMG_A_DP_PORT = gsmg_code[key-1];
}
}
}