1.按键相关知识
1.1、按键工作原理
(1)内部机械结构
(2)电路连接与原理图中图标
按键图标
(3)按键电路接法、上拉电阻。上拉是为了让引脚默认是高电平,但是上拉的力量扛不住接地,所以按键没法按下时上拉的力量保证了IO引脚输入为1,而按下后绝对为0.
(4)按下和弹起的区别就是接地不接地的问题,也就是引脚输入为1还是0的问题。
(5)按键这个设备对我们的意义:按键对于我们CPU来说是一个输入设备,输入的是人的操作。CPU通过监测按键连接的IO引脚的电平输入是1还是0就知道外部有没有人按下这个按键。相当于人通过按键给CPU输入了一个信号,这个信号可以被CPu监测到从而指导CPU去做一定的工作。
1.2、CPU如何处理按键
(1)轮询式:所谓轮询式就是CPU不断的隔很小一段时间去查看有没有按键被按下,如果按下就处理按键,如果没按下就过一会再来查看。(按键什么时候被按下CPU是无法预知的)。
(2)中断式
1.3、按键电路接法分类
(1)独立按键
(2)矩阵按键
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。
2.2、先监测1个按键(用LED作为指示)
(1)使用轮询法来处理独立按键K1,单片机在循环中每隔很短的时间就监测K1对应的P1.0引脚的输入电平是1还是0。如果是1则表示按键没有按下,熄灭LED作为指示。延时等待下一次检验;如果是0表示按键已经按下了,点亮一颗LED作为指示。
#include <reg51.h>
sbit key1 = P1^0;
sbit led1 = P0^0;
void main(void)
{
while (1)
{
// C语言中把一个IO引脚定义成一个变量key1
// 然后给key1变量赋值就相当于是向这个IO引脚输出
// 直接使用(读)这个key1变量,就相当于从这个IO引脚输入
if (key1 == 1)
{
// 没有按键的时候
// led1 = 0; // led1熄灭
}
else
{
// 有按键的时候
led1 = 1; // led1点亮
}
}
}
2.3、扩展为监测8个独立按键
(1)如果要监测的按键数量少,可以用位监测(就像上面一样)。如果多则可以直接用端口字节变量(0xfe)来监测。
(2)独立按键多个按键之间是彼此独立的,所以允许多个按键同时按下而不会影响。(矩阵按键就只能一次按下一个按键,不能多个同时按下)。
3.键值监测与显示
3.1、何为键值?
(1)一般的产品中按键都有很多,对于整个程序来说一般都是把按键进行编码,给每个按键一个对应的编码值,就叫做按键的键值。
(2)在正式的比较庞大的程序中,按键的监测部分和处理部分都是隔开的。这两部分隔开有利于各自部分的程序编写,两部分之间用键值来连接。按键监测部分负责监测按键,一旦发生一个按键事件就产生一个键值,然后将键值传递给按键处理部分。
3.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 display(unsigned char num);
void delay(void);
void main(void)
{
unsigned char keynum = 0;
while (1)
{
// C语言中把一个IO引脚定义成一个变量key1
// 然后给key1变量赋值就相当于是向这个IO引脚输出
// 直接使用(读)这个key1变量,就相当于从这个IO引脚输入
unsigned char i = 0;
for (i=0; i<8; i++)
{
if ((P1 & (0x1<<i)) == 0)
{
keynum = i + 1;
}
}
/*
// 11111110
if (P1 == 0xfe)
{
keynum = 1;
}
if (P1 == 0xfd)
{
keynum = 2;
}
if (P1 == 0xfb)
{
keynum = 3;
}
if (P1 == 0xf7)
{
keynum = 4;
}
if (P1 == 0xef)
{
keynum = 5;
}
if (P1 == 0xdf)
{
keynum = 6;
}
if (P1 == 0xbf)
{
keynum = 7;
}
if (P1 == 0x7f) // 7e
{
keynum = 8;
}
*/
// 在这里去处理按键
// 处理方法就是把这个按键发出到独立数码管去显示
display(keynum);
}
}
// 该函数将num数字送到独立数码管去显示
void display(unsigned char num)
{
P0 = val[num];
}
// 延时函数
void delay(void)
{
unsigned char i, j;
for (i=0; i<100; i++)
for (j=0; j<100; j++);
}
4.消抖
4.1、案例:按键按一次数码管显示数字加1
4.2、什么是抖动?
(1)按键按下和弹起时的电平变化图
(2)抖动如何产生
(3)抖动的危害:在抖动时间范围内引脚的电平变化是不定的,如果程序在这一段范围内去判断引脚的电平从而来判断有无按键,则有很大可能性会误判。
4.3、如何消抖
(1)硬件消抖,在硬件设计上想办法降低抖动,这是一种主动消抖。加电容,可以让电平抖动变成光滑曲线。
(2)软件消抖,既然在硬件上不可能完全消除抖动,软件设计上就要想办法绕开抖动造成的影响,这是一种被动(逃避式)的消抖。使用delay10ms函数,从接收到一个低电平信号开始,延时10ms后再判断是否还是低电平,如果是,则说明是真的有按键按下了。
#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 keynum = 0;
while (1)
{
// C语言中把一个IO引脚定义成一个变量key1
// 然后给key1变量赋值就相当于是向这个IO引脚输出
// 直接使用(读)这个key1变量,就相当于从这个IO引脚输入
// if (key1 == 0)
// {
// AddDisplay();
// }
if (key1 == 0)
{
// 发现1次低电平,有可能是按键按下,也有可能是抖动,软件消抖
delay10ms();
if (key1 == 0)
{
// 10ms后还是低电平,说明真的是按键按下了,不是抖动
AddDisplay();
}
}
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<200; 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--);
}
5.完整的按键监测
5.1、一次完整的按键事件
(1)按键事件就是按键操作过程的不同状态切换
(2)一个完整的按键事件包括:按下事件(由高变低)、弹起事件(由低变高)
(3)一般都认为发生了一次完整的按键事件才算是用户操作了一次按键,程序才会去处理按键,所以在一次完整的按键事件中程序只会去处理一次按键。
5.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--);
}
6.中断的引入
6.1、任务:独立数码管循环显示0-F,同时按键控制LED亮灭
(1)分析能否实现,实践证明可以实现功能,但是按键监测控制LED这边非常不灵敏。
(2)逐步认识到单片机只有一个“主线任务”的特点。
(3)多任务如何及时响应?
6.2、中断的思路
(1)“主线任务”为常规任务,默认运行
(2)中断发生后CPU暂停主线任务转去处理中断任务,完成后再回来接着执行主线任务。
6.3、中断的意义
(1)中断处理能力让CPU可以全力处理主线任务而不用担心会错过中断任务(举例:看电影和收快递)
(2)中断式比轮询式更适合处理异步事件,效率更高。
(3)中断处理的事件的特点是:处理时间短、响应要求急、不可预见。
7.使用单片机外部中断来处理按键
7.1、外部中断INT0和INT1
(1)何为外部中断。中断源来自单片机外部就叫外部中断,51单片机支持4个外部中断。分别对应4个引脚。每一个外部中断都对应一个特定的单片机IO引脚(比如INT0对应P3.2,这个是单片机在设计时候定好的,是无法改变的)。我们软件只需要对P3.2做一些相关配置,P3.2就可以响应外部的中断事件。当硬件产生了一个外部中断时CPU就会收到一个中断信号,从而转去执行外部中断对应的处理程序(这个处理程序也是我们软件需要去编写提供的)。
(2)外部中断对应哪个引脚?
数据手册上有。
7.2、参考数据手册中示例代码写程序
(1)编程中使用INT0处理按键
(2)程序解释
IT0这一位用来设置中断的触发模式:下降沿触发(Falling)或者低电平触发(Low level)
EX0这一位是INT0的开关。如果EX0等于0则外部中断在单片机内部被关闭,此时CPU无法收到INT0的中断信息所以不会处理INT0;如果需要使用INT0就一定要设置爱为1.
EA是全局的中断开关。EA如果关掉则整个CPU不能响应中断,所有中断都被关了。光EA打开也不一定能响应中断,还得具体的中断开关打开才行。
7.3、总结
(1)中断能力是CPU本身设计时支持的,并不是编程制造出来的。
(2)程序员只要负责2件事即可:主程序中初始化中断、定义中断处理程序。
(3)当中断条件发生时,硬件会自动检测到并且通知CPU,CPU会自动去执行中断处理程序,这一切都是CPU设计时定下的,不需要编程干预。
8.矩阵键盘
8.1、矩阵键盘的原理图分析
矩阵键盘(1)横向和纵向分割
(2)按键两端分别接不通的IO引脚
(3)按键的物理作用不变:按下接通电路,弹起断开电路
8.2、矩阵键盘的工作过程
(1)先送(IO引脚输出)0x0f
(2)若有按键收到的不是0x0f,从收到的数据(IO引脚输入)判断哪一行按下了。
(3)再送(IO引脚输出)0xf0
(4)从收到的数据(IO引脚输入)判断哪一列按下了
(5)综合两次得到的行和列位置,计算出键值。
注意:CPU的IO发送,接收也是CPU的IO。下面的8号引脚改为1号引脚,因为教学视频和购买的开发板不一样。
矩阵键盘工作原理
8.3、矩阵键盘的特点
(1)优点:省单片机IO
(2)缺点:不能同时按下多个按键
9.矩阵键盘编程实战
(1)实验研究按键按下的规律(LED显示辅助)
(2)编写键值检验函数
(3)独立数码管显示键值
#include <reg51.h>
// P0端口接LED
// P0端口接数码管
// P3端口接矩阵键盘
#define LED P0
#define KEY P3
#define DIG P0
unsigned char GetKey(void);
void delay10ms(void);
// 独立数码管的段码表
unsigned char val[16] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e};
void main(void)
{
unsigned char key = 0;
while (1)
{
key = GetKey();
if (key != 0)
{
DIG = val[key];
}
}
}
unsigned char GetKey(void)
{
unsigned char hang = 0, lie = 0;
unsigned char keyvalue = 0;
// 第1回合第1步
KEY = 0x0f; // 从IO口输出,写IO口
if (KEY != 0x0f) // 从IO口输入,读IO口
{
// 读出的不是0x0f说明有按键被按下
// 第1回合第2步:读出端口从读出值来判断是哪一行
delay10ms();
// 第一回合中算出行号
switch (KEY)
{
case 0x0e: hang = 1; break;
case 0x0d: hang = 2; break;
case 0x0b: hang = 3; break;
case 0x07: hang = 4; break;
default: break;
}
// 第2回合第1步
KEY = 0xf0;
if (KEY != 0xf0)
{
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去计算键值即可
keyvalue = (hang - 1) * 4 + lie;
return keyvalue;
}
}
return 0;
}
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--);
}
网友评论