独立按键和矩阵按键

独立按键和矩阵按键
独立按键和矩阵按键

第八章独立按键和矩阵按键

我们和单片机之间进行信息交互,主要包含两大类,输入设备和输出设备。前边讲的LED小灯、数码管、点阵都是输出设备,这节课我们学习一下最常用的输入设备——按键。在本节课的学习过程中我们还会穿插介绍一点硬件设计的基础知识。

8.1 单片机最小系统电路解析

8.1.1 电源

我们在学习过程中,很多指标都是直接用的概念指标,比如我们说+5V代表1,GND代表0等等这些。但在实际电路中是没有这么精准的,那这些指标允许范围是什么呢?随着我们所学的内容不断增多,大家要慢慢培养一种阅读手册的能力。

比如我们使用STC89C52RC单片机的时候,我们找到他的手册的11页,第二个选项,工作电压:5.5V-3.4V(5V单片机),这个地方就说明我们这个单片机正常的工作电压是个范围值,只要电源VCC在5.5V到3.4V之间都可以正常工作,电压超过5.5V是绝对不允许的,会烧坏单片机,电压如果低于3.4V,单片机不会损坏,但是也不能正常工作。而在这个范围内,最典型、最常用的电压值就是5V,这就是后面括号里“5V单片机”这个名称的由来。除此之外,还有一种常用的工作电压范围是2.7V-3.6V、典型值是3.3V的单片机,也就是所谓的“3.3V单片机”了。日后随着大家接触的东西慢慢增多,对这点会有更深刻的理解。

现在我们再顺便多了解一点,大家打开74HC138的数据手册,会发现74HC138手册的第二页也有一个表格,上边写了74HC138的工作电压范围,最小值是4.75V,额定值是5V,最大值是5.25V,可以得知它的工作电压范围是4.75V-5.25V。这个地方讲这些目的是让大家清楚的了解,我们获取器件工作参数的一个最重要,也是最权威的途径,就是通过器件的数据手册。

8.1.2 晶振

晶振通常分为无源晶振和有源晶振两种类型,无源晶振一般称之为crystal(晶体),而有源晶振则叫做oscillator(振荡器)。

有源晶振是一个完整的谐振振荡器,他是利用石英晶体的压电效应来起振,所以有源晶振需要供电,当我们把有源晶振电路做好后,不需要外接电路,它就可以主动产生振荡频率,并且可以提供高精度的频率基准,信号质量比无源信号好。

而无源晶振自身无法振荡起来,它需要芯片内部的振荡电路一起工作才能振荡,它允许不同的电压,但是信号质量和精度较有源晶振差一些。相对价格来说,无源晶振要比有源晶振价格便宜很多。无源晶振两侧通常都会有两个电容,一般其容值都选在10pF~40pF之间,如果手册中有具体电容大小的要求则要根据要求来选电容,如果手册没有要求,我们用20pF 就是比较好的选择,这是一个长久以来的经验值,具有极其普遍的适用性。

我们来认识下比较常用的两种晶振的样貌,如图8-1和图8-2所示。

图8-1 27Mhz有源晶

振图8-2 11.0592M无源晶振

有源晶振通常有4个引脚,VCC,GND,晶振输出引脚和一个没有用到的悬空引脚。无源晶振有2个或3个引脚,如果是3个引脚的话,中间引脚是晶振的外壳,使用时要接到GND,两侧的引脚就是晶体的2个引出脚了,这两个引脚作用是等同的,就像是电阻的2个引脚一样,没有正负之分。对于无源晶振,就是用我们的单片机上的两个晶振引脚接上去即可,而有源晶振,只接到单片机的晶振的输入引脚上,输出引脚上不需要接,如图8-3和图8-4所示。关于晶振的更多资料可参考:https://www.360docs.net/doc/7014006102.html,/dianzi/300.html上面有更深层的原理剖析与详细的分类.

图8-3 无源晶振接法图8-4 有源晶振接法

8.1.3 复位电路

我们先来分析一下我们的复位电路,如图8-5所示。

图8-5 单片机复位电路

当这个电路处于稳态时,电容起到隔离直流的作用,隔离了+5V,而左侧的复位按键是弹起状态,下边部分电路就没有电压差的产生,所以按键和电容C11以下部分的电位都是和GND相等的,也就是0V电压。我们这个单片机是高电平复位,低电平正常工作,所以正常工作的电压是0V电压,完全OK,没有问题。

我们再来分析从没有电到上电的瞬间,电容C11上方是5V电压,下方是0V电压,根据我们初中所学的知识,这个时候电容C11要进行充电,正离子从上往下充电,负电子从GND往上充电,这个时候电容对电路来说相当于一根导线,全部电压都加在了R31这个电阻上,那么RST端口位置是+5V电压,随着电容充电越来越多,即将充满的时候,电流会越来越小,那RST端口上的电压值等于电流乘以R31的阻值,也就会越来越小,一直到电容完全充满后,线路上不再有电流,这个时候RST和GND的电位就相等了也就是0V了。

从这个过程上来看,我们加上这个电路,单片机系统上电后,RST引脚会先保持一小段时间的高电平而后变成低电平,这个过程就是上电复位的过程。那这个“一小段时间”到

底是多少才合适呢?每种单片机不完全一样,51单片机手册里写的是持续时间不少于2个机器周期的时间。复位电压值,每种单片机不完全一样,我们按照通常值0.7Vcc作为复位电压值,复位时间的计算过程比较复杂,我这里只给大家一个结论,时间t=1.2RC,我们用的R是4700,C是0.0000001,那计算得知t是564us,远远大于2个机器周期(2us),在电路设计的时候一般留够余量就行。

按键复位(即手动复位)有2个过程,按下按键之前,RST的电压值是0V,当按下按键后电路导通,同时电容也会在瞬间进行放电,RST电压值变化为4700Vcc/(4700+18),会处于高电平复位状态。当松开按键后就和上电复位类似了,先是电容充电,后电流逐渐减小直到RST电压变0V的过程。我们按下按键的时间通常都会有上百毫秒,这个时间足够复位了。按下按键的瞬间,电容两端的5V电压(注意不是电源的5V和GND之间)会被直接接通,此刻会有一个瞬间的大电流冲击,会在局部范围内产生电磁干扰,为了抑制这个大电流所引起的干扰,我们这里在电容放电回路中串入一个18欧的电阻来限流。

如果有的同学已经开始DIY设计自己的电路板的时候,那单片机最小系统的设计现在已经有了足够的理论依据了,可以考虑尝试了。如在制作过程有有问题可到:单片机论坛https://www.360docs.net/doc/7014006102.html,/bbs/求助作者会不定期回复的,基础比较薄弱的同学先不要着急,继续跟着往下学,把课程都学完了再动手操作也不迟,磨刀不误砍柴工。

8.2 函数的调用

随着我们编程的程序量的增多,如果把所有的语句都写到main函数中,一方面程序会写的比较乱,另外一个方面,当我们一个功能需要多次执行的时候,我们就得不断重复写语句,这个时候,就引入了函数调用的概念。

一个程序一般由若干个子程序模块组成,一个模块实现一个特定的功能,在C语言中,这个模块就用函数来表示。一个C程序一般由一个主函数和若干个其他函数构成。主函数可以调用其他函数,其他函数也可以相互调用,但其它函数不能调用主函数。在我们的51单片机程序中,还有中断服务函数,是当相应的中断到来后自动调用执行的,不需要也不能由其他函数调用。

函数调用的一般形式是:

函数名(实参列表)

函数名就是需要调用的函数的名称,实参列表就是根据实际调用函数要传递给被调用函数的参数列表,不需要传递参数的只加括号就可以,传递多个参数时要用逗号隔开。在这里我以上节课的点阵I?U的纵向移动的程序改动一下,大家先了解一下基本的函数调用。另外,大家不要偷懒,一定把这个程序抄下来做一下实验加深一下自己的印象。

#include

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

unsigned char code graph[] = {

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,

0xC3,0xE7,0xE7,0xE7,0xE7,0xE7,0xC3,0xFF,

0x99,0x00,0x00,0x00,0x81,0xC3,0xE7,0xFF,

0x99,0x99,0x99,0x99,0x99,0x81,0xC3,0xFF,

0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF

};

unsigned char index = 0; //图片刷新索引

void refresh(); //函数声明

void main()

{

P0 = 0xFF; //P0口初始化

ADDR3 = 0; //选择LED点阵

ENLED = 0; //LED显示总使能

TMOD = 0x01; //设置定时器0为模式1

TH0 = 0xFC; //定时器初值,定时1ms

TL0 = 0x67;

TR0 = 1; //打开定时器0

ET0 = 1; //使能定时器0中断

EA = 1; //打开总中断开关

while(1);

}

void refresh()

{

static unsigned char j = 0;

P0 = 0xFF; //LED点阵动态刷新

switch (j)

{

case 0: ADDR0=0; ADDR1=0; ADDR2=0; break; case 1: ADDR0=1; ADDR1=0; ADDR2=0; break; case 2: ADDR0=0; ADDR1=1; ADDR2=0; break; case 3: ADDR0=1; ADDR1=1; ADDR2=0; break; case 4: ADDR0=0; ADDR1=0; ADDR2=1; break; case 5: ADDR0=1; ADDR1=0; ADDR2=1; break; case 6: ADDR0=0; ADDR1=1; ADDR2=1; break; case 7: ADDR0=1; ADDR1=1; ADDR2=1; break; default: break;

}

P0 = graph[index+j];

j++;

if (j >= 8)

{

j = 0;

}

}

void InterruptTimer0() interrupt 1

{

static unsigned char tmr = 0;

TH0 = 0xFC; //溢出后进入中断重新赋值

TL0 = 0x67;

refresh(); //函数调用

tmr++; //图片刷新频率控制

if (tmr >= 250) //每隔250ms刷新一帧

{

tmr = 0;

index++;

if (index >= 32)

{

index = 0;

}

}

}

这个程序是对函数的简单调用,但是有以下三个细节需要大家注意一下:

1、函数调用的时候,不需要加函数类型。在中断函数内调用刷新函数的时候我们只写了refresh(); 而没有加void。

2、调用函数与被调用函数的位置关系,C语言规定:函数在被调用之前,必须先被定时或声明。意思就是说:在一个文件中,一个函数应该先定义,然后才能被调用,也就是调用函数应位于被调用函数的下方。但是作为一种通常的编程规范,我们推荐main函数写在最前面(因为它起到提纲挈领的作用),其后再定义各个子函数,而中断函数则写在文件的最后。这时候,我们就在文件开头,所有函数定义之前,开辟一块区域,叫做函数声明区,用来把被调用的子函数声明一下,如此,该函数就可以被随意调用了。如上述例程所示。

3、函数声明的时候必须加函数类型,函数的形式参数,最后加上一个分号表示结束。这点请尤其注意,因为函数定义时最后是不能有分号的,初学者很容易因粗心大意搞错,导致程序编译不过。

4、函数自身的类型、声明的类型以及调用的类型必须一致。我们这个例子里refresh 函数的类型是void。

8.3 函数的形式参数和实际参数

上一个程序在进行函数调用的时候,我们不需要任何参数传递,所以函数定义和调用时refresh()括号里是空的,但是更多的时候我们调用函数,主调函数和被调用函数之间是要有参数传递关系的。在调用一个有参数的函数时,函数名后边括号里中的参数叫做实际参数,简称实参。而被调用的函数在进行定义的时候,括号里的参数就叫做形式参数,简称形参,我们找个简单程序例子做说明。

unsigned char add(unsigned char x, unsigned char y);

void main()

{

unsigned char a = 1;

unsigned char b = 2;

unsigned char c = 0;

c = add(a, b); //调用时,a和b就是实参,把函数的返回值赋给c

//运算完后,c的值就是3

while(1);

}

unsigned char add(unsigned char x, unsigned char y) //x和y就是形参

{

unsigned char z = 0;

z = x + y;

return z; //返回值z的类型就是函数add的类型

}

这个演示程序虽然很简单,但是形参和实参以及函数返回值等全部内容都囊括在内了。主调函数main和被调函数add之间的数据通过形参和实参发生了传递关系,而函数运算完了也把值传递给了变量c,函数只要不是void类型的函数,都会有返回值,返回值类型就是函数的类型。关于形参和实参,还有以下几点需要注意。

1、函数定义中指定的形参,在未发生函数调用时不占内存,只有函数调用时,函数add 中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放,这个前边讲过了,形参是局部变量。

2、实参可以是常量,也可以是简单或者复杂的表达式,但是要求他们必须有确定的值,在调用发生时将实参的值传递给形参。

如上边这个程序也可以写成: c = add(1, a+b);

3、形参必须要指定数据类型,和定义变量一样。

4、实参和形参的数据类型应该相同或者赋值兼容。和变量赋值一样,当形参和实参出现不同类型时,则按照不同类型数值的赋值规则进行转换。

5、主调函数在调用函数之前,应对被调函数做原型声明。

6、实参向形参的数据传递是单向传递,不能有形参再回传给实参。也就是说,实参值传递给形参后,调用结束,形参单元被释放,而实参单元仍保留并且维持原值。

8.4 独立按键

通常的按键分为独立式按键和矩阵式按键两种,独立式按键比较简单,并且与独立的输入线相连接,如图8-6所示

图8-6 独立式按键电路图

4条输入线接到单片机的IO口上,当按键K1按下时,+5V通过电阻R1然后再通过按键K1最终进入GND形成一条通路,那么这条线路的全部电压都加到了R1这个电阻上,KeyIn1这个引脚就是个低电平。当松开按键后,线路断开,就不会有电流通过,那么KeyIn1和+5V就应该是等电位,是一个高电平。我们就可以通过KeyIn1这个IO口的高低电平来判断是否有按键按下。

这个电路中按键的原理我们清楚了,但是实际上在我们的单片机IO口内部,也有一个上拉电阻的存在。我们的按键是接到了P2口上,P2口上电默认是准双向IO口,我们来简单了解一下这个准双向IO口的电路,如图8-7所示。

图8-7 准双向IO口结构图

首先说明一点,就是我们现在绝大多数单片机的IO口都是使用MOS管而非三极管,但用在这里的MOS管其原理和三极管是一样的,因此在这里我用三极管替代它来进行原理讲解,把前面讲过的三极管的知识搬过来,一切都是适用的,有助于理解。

图8-7方框内的电路都是指单片机内部部分,方框外的就是我们外接的上拉电阻。这个地方大家要注意一下,就是当我们要读取外部按键信号的时候,首先单片机必须得给个‘1’,也就是高电平,这样我们才能正常的读取外部的按键信号,我们来分析一下缘由。

当内部输出是高电平,经过一个反向器变成低电平,NPN三极管不会导通,那么单片机IO口从内部来看,由于上拉电阻R的存在,所以是一个高电平。当外部没有按键按下将电平拉低的话,VCC也是+5V,他们之间虽然有2个电阻,但是没有压差,就不会有电流,线上所有的位置都是高电平,这个时候我们就可以正常读取到按键的状态了。

当内部输出是个低电平,经过一个反相器变成高电平,NPN三极管导通,那么单片机的内部IO口就是个低电平,这个时候,外部虽然也有上拉电阻的存在,但是两个电阻是并联关系,不管按键是否按下,单片机的IO口上输入到单片机内部的状态都是低电平,我们就无法正常读取到按键的状态了。

这个和水流其实很类似的。内部和外部,只要有一边是低电位,那么电流就会顺流而下,由于只有上拉电阻,下边没有电阻分压,直接到GND上了,所以不管另外一边是高还是低,那电位肯定就是低电位了。

这里得到一个结论,这种具有上拉的准双向IO口,如果要正常读取外部信号的状态,必须首先得保证自己输出的电平是‘1’,如果输出‘0’,则无论外部信号是高是低,这个引脚读进来的都是低。

8.5 矩阵按键

8.5.1 矩阵按键和独立按键的关系

我们在使用按键的时候有这样一种使用经验,当需要多个按键的时候,如果做成独立按键会大量占用IO口,因此我们引入了矩阵按键,如图8-8所示,使用了8个IO口来实现16个按键。

图8-8 矩阵按键

其实独立按键理解了,矩阵按键也简单,我们来分析一下。图8-8中,一共有4组按键,我们只看其中一组,如图8-9所示。大家认真看一下,当KeyOut1输出一个低电平,KeyOut2、KeyOut3、KeyOut4这三个输出高电平时,是否相当于4个独立按键呢。

图8-9 矩阵按键变独立按键我们先用一个简单的程序来实现这4个独立按键的使用。#include

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

sbit LED9 = P0^7;

sbit LED8 = P0^6;

sbit LED7 = P0^5;

sbit LED6 = P0^4;

sbit KEY1 = P2^4;

sbit KEY2 = P2^5;

sbit KEY3 = P2^6;

sbit KEY4 = P2^7;

void main(void)

{

//选择独立LED进行显示

P0 = 0xFF; //初始化P0

ADDR0 = 0;

ADDR1 = 1;

ADDR2 = 1;

ADDR3 = 1;

ENLED = 0;

P2 = 0xF7; //选中第一行按键以进行扫描

while(1)

{

//将按键扫描引脚的值传递到LED上

LED9 = KEY1; //按下时为0,对应的LED点亮

LED8 = KEY2;

LED7 = KEY3;

LED6 = KEY4;

}

}

这个程序可以实现当按下K1、K2、K3或者K4任何一个按键或者多个按键的时候,我们对应赋值的小灯就会点亮,松开按键的时候,小灯就熄灭。这里提醒一句,原理图K1到K4是竖着画的,但是走线布局的时候是横向排布的,注意一下。

从这里可以看出来,其实独立按键本身就是矩阵按键中的一种情况而已,那这样看来我们板子上就有4组每组4个独立共16个独立按键。

8.5.2 按键消抖

绝大多数情况下,我们按按键是不能一直按住的,所以我们通常是判断按键从按下到弹起两种状态发生变化了,就认为是有按键按下。

程序上,我们可以把每次按键状态都存储起来,当下一次按键状态读进来的时候,与当前按键状态做比较,如果发现这两次按键状态不一致,就说明按键发生动作了,当上一次的状态是未按下、现在是按下,此时的按键动作就是“按下”;当上一次的状态是按下、现在是未按下,此时的按键动作就是“弹起”。显然,每次按键动作都会包含一次“按下”动作和一次“弹起”动作,我们可以任选一个动作来执行程序,或者两个都用以执行不同的程序也是可以的。下面还是用程序来直观的看一下。

#include

sbit KEY1 = P2^4;

sbit KEY2 = P2^5;

sbit KEY3 = P2^6;

sbit KEY4 = P2^7;

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

unsigned char code LedChar[] = {

0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,

0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e

}; //数码管真值表

void main(void)

{

bit backup = 1; //按键值备份,保存前一次的扫描值

unsigned char counter = 0; //计数器记录按键按下的次数

//选择最右边的数码管进行显示

P0 = LedChar[counter];

ADDR0 = 0;

ADDR1 = 0;

ADDR2 = 0;

ADDR3 = 1;

ENLED = 0;

//选中第一行按键以进行扫描

P2 = 0xF7;

while(1)

{

if (KEY4 != backup) //只取KEY4为例,当前值与前一次值不相等时,说明按键有动作

{

if (backup == 0) //如果前一次的值为0,则说明当前状态是由0变为1,即按键弹起

{

counter++; //计数器+1

if (counter >= 10)

{ //只用1个数码管显示,所以记到10就清零重新开始

counter = 0;

}

P0 = LedChar[counter]; //计数值显示到数码管上

}

backup = KEY4; //更新备份为当前值,以备进行下次比较

}

}

}

在这个程序中,我们以K4为例,按一次按键,就会产生“按下”和“弹起”两个动态的动作,我们选择在“弹起”时对数码管进行加1操作。理论是如此,大家可以在板子上用K4按键做做实验试试,多按几次,是不是会发生这样一种现象:有的时候我明明只按了一下按键,但数字却加了不止1,而是2或者更多?但是我们的程序并没有任何逻辑上的错误,这是怎么回事呢?于是我们就得来说说按键抖动和消抖了。

通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如图8-10所示。

图8-10 按键抖动状态图

按键稳定闭合时间长短是由操作人员决定的,通常都会在100ms以上,刻意快速按的话能达到40-50ms左右,很难再低了。抖动时间是由按键的机械特性决定的,一般是都会在10ms以下,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。当检测到按键状态变化时,不是立即去响应动作,而是先等待闭合或断开稳定后再进行处理。按键消抖可分为硬件消抖和软件消抖。

硬件消抖就是在按键上并联一个电容,如图8-11所示,利用电容的充放电特性来对抖动过程中产生的电压毛刺进行平滑处理,从而实现消抖。但实际应用中,这种方式的效果往往不是很好,而且还增加了成本和电路复杂度。所以实际中使用的并不多。

图8-11 电容消抖

在绝大多数情况下,我们是用软件即程序来实现消抖的。最简单的消抖原理,就是当检测到按键状态变化后,先等待一个10ms左右的延时瞬间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就刻意确认按键已经稳定的动作了。将上边的程序稍加改动,如下所示。

#include

sbit KEY1 = P2^4;

sbit KEY2 = P2^5;

sbit KEY3 = P2^6;

sbit KEY4 = P2^7;

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

unsigned char code LedChar[] = {

0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,

0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e

}; //数码管真值表

void delay(void); //延时函数声明

void main(void)

{

bit keybuf = 1; //按键值暂存,临时保存按键的扫描值

bit backup = 1; //按键值备份,保存前一次的扫描值

unsigned char counter = 0; //计数器记录按键按下的次数

//选择最右边的数码管进行显示

P0 = LedChar[counter];

ADDR0 = 0;

ADDR1 = 0;

ADDR2 = 0;

ADDR3 = 1;

ENLED = 0;

//选中第一行按键以进行扫描

P2 = 0xF7;

while(1)

{

keybuf = KEY4; //只取KEY4为例,把当前扫描值暂存

if (keybuf != backup) //当前值与前一次值不相等说明此时按键有动作

{

delay(); //延时大约10ms

if (keybuf == KEY4) //判断扫描值有没有发生改变,即按键抖动

{

if (backup == 0) //如果前一次的值为0,则说明当前状态是由0变为1,即按键弹起

{

counter++; //计数器+1

if (counter >= 10)

{ //只用1个数码管显示,所以记到10就清零重新开始

counter = 0;

}

P0 = LedChar[counter]; //计数值显示到数码管上

}

backup = keybuf; //更新备份为当前值,以备进行下次比较

}

}

}

}

void delay(void)

{

unsigned int i = 1000;

while (i--); //通过debug的KEIL软件延时方式计算得出大概是10ms

}

大家把这个程序下载到板子上再进行试验试试,按一下按键而数字加了多次的问题是不是就这样解决了?把问题解决掉的感觉是不是很爽呢?

这个程序用了一个简单的算法实现了按键的消抖。作为这种很简单的演示程序,我们可以这样来写,但是实际工程开发的时候,我们的程序量很大,各种状态值也很多,我们while(1)的这个主循环要不停的扫描各种状态值是否有发生变化的,如果程序中间加了这种delay延时操作后,很可能某一事件发生了,但是我们程序还在进行delay延时操作中,当这个事件发生完了,我们还在delay操作中,当我们delay完事再去检查的时候,已经晚了,已经检测不到那个事件了。为了避免这种情况的发生,我们要尽量缩短while(1)循环一次所用的事件,而需要进行长时间延时的操作,必须想其它的办法来处理。

那么我们如何处理这种延时问题呢?其实除了这种简单的延时,我们还有更优异的方法来处理按键抖动问题。举个例子:我们启用一个定时中断,每2ms进一次中断,扫描一次按键状态并且存储起来,连续扫描8次后,看看这连续8次的按键状态是否是一致的。8次按键的时间大概是16ms,这16ms内如果按键状态一直保持一种状态,那就可以确定现在按键是稳定的阶段,并非处于抖动的阶段,如图8-12

图8-12 按键连续判断

假如左边时间是起始0时刻,每经过2ms左移一次,每移动一次,判断当前连续的8次按键状态是不是全1或者全0,如果是全1则判定为弹起,如果是全0则判定为按下,如果0和1交错,就认为是抖动,不做任何判定。想一下,这样是不是比简单的延时更加可靠?

利用这种方法,就可以避免通过直接延时按键消抖占用CPU时间,而是转化成了一种按键状态判定而非按键过程判断,我们只对当前按键的连续16ms的8次状态进行判断,而不再关心它在这16ms内都做了什么事情,我们来看看这个程序怎么写。

#include

sbit KEY1 = P2^4;

sbit KEY2 = P2^5;

sbit KEY3 = P2^6;

sbit KEY4 = P2^7;

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

unsigned char code LedChar[] = {

0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,

0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e

};

bit KeySta = 1; //当前按键状态

void main(void)

{

bit backup = 1; //按键值备份,保存前一次的值

unsigned char counter = 0; //计数器记录按键按下的次数

//选择最右边的数码管进行显示

P0 = LedChar[counter];

ADDR0 = 0;

ADDR1 = 0;

ADDR2 = 0;

ADDR3 = 1;

ENLED = 0;

//选中第一行按键以进行扫描

P2 = 0xF7;

//配置T0工作在模式1,定时2ms

TMOD = 0x01;

TH0 = 0xF8;

TL0 = 0xCD;

TR0 = 1;

ET0 = 1;

EA = 1;

while(1)

{

if (KeySta != backup) //当前值与前一次值不相等说明此时按键有动作

{

if (backup == 0) //如果前一次的值为0,则说明当前状态是由0变为1,即按键弹起

{

counter++; //计数器+1

if (counter >= 10)

{ //只用1个数码管显示,所以记到10就清零重新开始

counter = 0;

}

P0 = LedChar[counter]; //计数值显示到数码管上

}

backup = KeySta; //更新备份为当前值,以备进行下次比较

}

}

}

void InterruptTimer0() interrupt 1

{

static unsigned char keybuf = 0xFF; //按键扫描缓冲区,保存一段时间内的扫描值

TH0 = 0xF8; //溢出后进入中断重新赋值

TL0 = 0xCD;

keybuf = (keybuf << 1) | KEY4; //只取KEY4为例,缓冲区左移一位,并将当前扫描值移入最低位

if (keybuf == 0x00)

{ //当连续8次扫描值都为0,即16ms内都只检测到按下状态时,可认为按键已按下 KeySta = 0; //按键状态值为按下

}

else if (keybuf == 0xFF)

{ //当连续8次扫描值都为1,即16ms内都只检测到弹起状态时,可认为按键已弹起

KeySta = 1; //按键状态值为弹起

}

else

{} //其它情况下则说明按键状态尚未稳定,则不对KeySta变量值进行更新

}

这个算法是我们在工程中经常使用按键所总结的一个比较好的方法,介绍给大家,今后都可以用这种方法消抖了。当然,按键消抖也还有其它的方法,程序实现更是多种多样,大家也可以再多考虑下其它的算法,拓展下思路。这个程序有一个新知识点,就是bit类型的变量,这个在标准C语言里边是没有的。51单片机有一种特殊的变量类型就是bit型,比如unsigned char型是定义了一个无符号的8位的数据,它占用一个字节(Byte)的内存,而bit型是1位数据,只占用1个位(bit)的内存,用法和标准C中其他的基本数据类型是一致的。它的优点就是节省内存空间,8个bit型变量才相当于1个char型变量所占用的空间。虽然它只有0和1两个值,但也已经可以表示很多东西了,比如:按键的按下和弹起、LED灯的亮和灭、三极管的导通与关断、开关的闭合与断开,联想一下已经学过的内容,它是不是能用最小的内存代价来完成很多工作呢?上面是c语言版的,汇编语言的键盘解说可参考:https://www.360docs.net/doc/7014006102.html,/mcuteach/227.html里面也讲得比较详细.

8.5.3 矩阵按键

我们讲独立按键的时候,大家已经简单认识了矩阵按键是什么样子了。矩阵按键相当于4组每组各4个独立按键,一共是16个按键。那我们如何区分这些按键呢?想一下我们生活所在的地球,要想确定我们所在的位置,就要借助经纬线,而矩阵按键就是通过行线和列线来确定哪个按键被按下。在程序中我们是如何进行的呢?

前边讲过,我们的按键按下通常都会保持100ms以上的,那我们程序上就每次快速的让矩阵按键的KeyOut其中一个输出低电平,其他三个输出高电平,判断当前列的按键的状态,下次再让另外一个KeyOut输出低电平,另外三个高电平,再次判断列,通过程序快速执行不断的循环判断,就可以最终确定有哪个按键按下,这个是不是和我们动态刷新数码管有点类似?数码管我们在动态赋值,而按键这里我们在动态读取状态。消抖方式依然采取检测连续状态的方式,只是我们现在连续检测4次就可以了。看下我们的程序,这个程序是按下我们的16个按键K1~K16,对应在最右边的数码管显示0~F,大家学一下矩阵按键的基本用法和矩阵按键消抖的方法。

#include

sbit KEY_IN_1 = P2^4; //矩阵按键的扫描输入引脚1

sbit KEY_IN_2 = P2^5; //矩阵按键的扫描输入引脚2

sbit KEY_IN_3 = P2^6; //矩阵按键的扫描输入引脚3

sbit KEY_IN_4 = P2^7; //矩阵按键的扫描输入引脚4

sbit KEY_OUT_1 = P2^3; //矩阵按键的扫描输出引脚1

sbit KEY_OUT_2 = P2^2; //矩阵按键的扫描输出引脚2

sbit KEY_OUT_3 = P2^1; //矩阵按键的扫描输出引脚3

sbit KEY_OUT_4 = P2^0; //矩阵按键的扫描输出引脚4

sbit ADDR0 = P1^0;

sbit ADDR1 = P1^1;

sbit ADDR2 = P1^2;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

unsigned char code LedChar[] = {

0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,

0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e

}; //数码管真值表

unsigned char KeySta[4][4] = { //全部矩阵按键的当前状态,默认都未按下 {1, 1, 1, 1}, //bit类型不能定义数组,因此定义成unsigned char

{1, 1, 1, 1},

{1, 1, 1, 1},

{1, 1, 1, 1}

};

void main(void)

{

unsigned char i, j;

unsigned char backup[4][4] = { //按键值备份,保存前一次的值

{1, 1, 1, 1},

{1, 1, 1, 1},

{1, 1, 1, 1},

{1, 1, 1, 1}

};

//选择最右边的数码管进行显示

P0 = 0xFF;

ADDR0 = 0;

ADDR1 = 0;

ADDR2 = 0;

ADDR3 = 1;

ENLED = 0;

//配置T0工作在模式1,定时1ms

TMOD = 0x01;

TH0 = 0xFC;

TL0 = 0x67;

TR0 = 1;

ET0 = 1;

EA = 1;

while(1)

{

//检索按键状态的变化

for (i=0; i<4; i++) //i作为行循环变量

{

for (j=0; j<4; j++) //j作为列循环变量

{

if (backup[i][j] != KeySta[i][j]) //判断按键动作

{

if (backup[i][j] == 0) //判断按键按下

{

P0 = LedChar[i*4+j]; //执行按键动作

}

backup[i][j] = KeySta[i][j]; //更新前一次的值

}

}

}

}

}

void InterruptTimer0() interrupt 1

{

unsigned char i;

static unsigned char keyout = 0; //矩阵按键扫描输出计数器

static unsigned char keybuf[4][4] = { //按键扫描缓冲区,保存一段时间内的扫描值 {0xFF, 0xFF, 0xFF, 0xFF},

{0xFF, 0xFF, 0xFF, 0xFF},

{0xFF, 0xFF, 0xFF, 0xFF},

{0xFF, 0xFF, 0xFF, 0xFF}

};

TH0 = 0xFC; //溢出后进入中断重新赋值

TL0 = 0x67;

//将一行的4个按键值移入缓冲区

keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;

keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;

keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;

keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;

//消抖后更新按键状态

for (i=0; i<4; i++) //每行4个按键,所以循环4次

{

if ((keybuf[keyout][i] & 0x0F) == 0x00)

{ //连续4次扫描值为0,即16ms(4*4ms)内都只检测到按下状态时,可认为按键已按下

矩阵按键识别技术

矩阵按键识别技术 矩阵按键部份由16个轻触按键按照4行4列排列,连接到JP50端口。将行线所接的单片机的I/O口作为输出端,而列线所接的I/O口则作为输入。这样,当按键没有按下时,所有的输出端都是高电平,代表无键按下。行线输出是低电平,一旦有键按下,则输入线就会被拉低,这样,通过读入输入线的状态就可得知是否有键按下了。 相关原理: 程序运行照片:

接线方法: 1、用一条8PIN数据排线,把矩阵按键部份的JP50,接到CPU部份的P1口JP44. 2、接8位数码管的数据线。将数码管部份的数据口 JP5接到CPU部份的P0口JP51. 3、接8位数码管的显示位线。将数码管部份的显示位口 JP8接到CPU部份的P2口JP52. ;本程序实现扫描按键显示功能. ;分别按16个键盘显示分别显示数字123A456B789C*0#D ;键盘口P1,数码管显示第二位p21, 数码管段位p0口 确定矩阵式键盘上何键被按下,介绍一种“行扫描法”。行扫描法又称为逐行(或列)扫描查询法,是一种最常用的按键识别方法. 程序流程图:

8031单片机的P1口用作键盘I/O口,键盘的列线接到P1口的低4位,键盘的行线接到P1口的高4位。列线P1.0-P1.3设置为输入线,行线P1.4-P.17设置为输出线。4根行线和4根列线形成16个相交点。 1、检测当前是否有键被按下。检测的方法是P1.4-P1.7输出全“0”,读取 P1.0-P1.3的状态,若P1.0-P1.3为全“1”,则无键闭合,否则有键闭合。 2、去除键抖动。当检测到有键按下后,延时一段时间再做下一步的检测判断。 3、若有键被按下,应识别出是哪一个键闭合。方法是对键盘的行线进行扫描。P1.4-P1.7按下述4种组合依次输出: 在每组行输出时读取P1.0-P1.3,若全为“1”,则表示为“0”这一行没有键闭合,否则有键闭合。由此得到闭合键的行值和列值,然后可采用计算法或查表法将闭合键的行值和列值转换成所定义的键值。 4、为了保证键每闭合一次CPU仅作一次处理,必须去除键释放时的抖动。 汇编语言参考程序: org 0000h ajmp main org 0080h main: mov dptr,#tab ;将表头放入DPTR lcall key ;调用键盘扫描程序 movc a,@a+dptr ;查表后将键值送入ACC mov p0,a ;将Acc值送入P0口

汇编矩阵键盘程序

方法一、 ORG 0000H LJMP MAIN ORG 0100H MAIN: MOV P1,#0F0H //P1口设初值F0,矩阵按键高四位置1,低四位置0, JNB P1.4,Y0 //用JNB检测按键端口,P1.4口低电平跳转 Y0 JNB P1.5,Y1 JNB P1.6,Y2 JNB P1.7,Y3 SJMP MAIN Y0: MOV 30H,#00H MOV P1,#0EFH JNB P1.4,X0 MOV P1,#0DFH JNB P1.4,X1 MOV P1,#0BFH JNB P1.4,X2 MOV P1,#07FH JNB P1.4,X3 Y1: MOV 30H,#01H MOV P1,#0EFH JNB P1.0,X0 MOV P1,#0DFH JNB P1.1,X1 MOV P1,#0BFH JNB P1.2,X2 MOV P1,#7FH JNB P1.3,X3 Y2: MOV 30H,#02H MOV P1,#0EFH JNB P1.0,X0 MOV P1,#0DFH JNB P1.1,X1 MOV P1,#0BFH JNB P1.2,X2 MOV P1,#7FH JNB P1.3,X3 Y3: MOV 30H,#03H MOV P1,#0EFH

MOV P1,#0DFH JNB P1.1,X1 MOV P1,#0BFH JNB P1.2,X2 MOV P1,#7FH JNB P1.3,X3 X0: MOV 31H,#00H ACALL DELAY MOV P1,#0F0H LJMP JISUAN X1: MOV 31H,#01H ACALL DELAY MOV P1,#0F0H LJMP JISUAN X2: MOV 31H,#02H ACALL DELAY MOV P1,#0F0H LJMP JISUAN X3: MOV 31H,#03H ACALL DELAY MOV P1,#0F0H LJMP JISUAN JISUAN: MOV A,31H MOV B,#04H MUL AB ADD A,30H MOV DPTR,#TAB MOVC A,@A+DPTR MOV P0,A CC: MOV A,P1 ANL A,#0F0H XRL A,#0F0H JNZ CC LCALL MAIN DELAY: MOV R4,#0C5H D1: MOV R5,#43H D0: MOV R6,#10H

单片机矩阵键盘设计方案

1、设计原理 (1)如图14.2所示,用单片机的并行口P3连接4×4矩阵键盘,并以单片机的P3.0-P3.3各管脚作输入线,以单片机的P3.4-P3.7各管脚作输出线,在数码管上显示每个按键“0-F”的序号。 (2)键盘中对应按键的序号排列如图14.1所示。 2、参考电路 图14.2 4×4矩阵式键盘识别电路原理图 3、电路硬件说明 (1)在“单片机系统”区域中,把单片机的P3.0-P3.7端口通过8联拨动拨码开关JP3连接到“4×4行列式键盘”区域中的M1-M4,N1-N4端口上。 (2)在“单片机系统”区域中,把单片机的P0.0-P0.7端口连接到“静态数码显示模块”区域中的任何一个a-h端口上;要求:P0.0对应着a,P0.1对应着b,……,P0.7对应着h。 4、程序设计内容 (1)4×4矩阵键盘识别处理。 (2)每个按键都有它的行值和列值,行值和列值的组合就是识别这个按键的编码。矩阵的行线和列线分别通过两并行接口和CPU通信。键盘的一端(列线)通过电阻接VCC,而接地是通过程序输出数字“0”实现的。键盘处理程序的任务是:确定有无键按下,判断哪一个键按下,键的功能是什么?还要消除按键在闭合或断开时的抖动。两个并行口中,一个输出扫描码,使按键逐行动态接地;另一个并行口输入按键状态,由行扫描值和回馈信号共同形成键编码而识别按键,通过软件查表,查出该键的功能。 5、程序流程图(如图14.3所示) 6、汇编源程序 ;;;;;;;;;;定义单元;;;;;;;;;; COUNT EQU 30H ;;;;;;;;;;入口地址;;;;;;;;;;

ORG 0000H LJMP START ORG 0003H RETI ORG 000BH RETI ORG 0013H RETI ORG 001BH RETI ORG 0023H RETI ORG 002BH RETI ;;;;;;;;;;主程序入口;;;;;;;;;; ORG 0100H START: LCALL CHUSHIHUA LCALL PANDUAN LCALL XIANSHI LJMP START ;;;;;;;;;;初始化程序;;;;;;;;;;

4乘4矩阵键盘输入数码管显示四位数

综合课程设计三相步进电机控制器电路的设计 学生姓名__________

指导教师_________ 课程设计任务书 一、设计说明 步进电机是工业过程控制及仪表控制中的主控元件之一,作为执行元件其特点为能够快速起启停、精度高且能直接接收数字量,由于这些特点使其在定位场合得到了广泛的应用。 设计一个三相步进电机控制器,使其能够控制步进电机的工作状态,如步进电机正、反转,步进电机的工作方式等。 用键盘设定步进电机的工作频率,工作方式,并用数码管显示设定值,可以通过按键来更换显示内容。用示波器观测三相的输出波形,并用数码管显示电路的工作状态。 二、技术指标 步进电机的工作频率为:<10kHz 三、设计要求 1.进行方案论证,提出一个合理的设计方案并进行理论设计; 2.对所设计的方案部分进行调试; 3.在选择器件时,应考虑成本。 4.设计测量调试电路。 四、实验要求 1.根据技术指标制定实验方案;验证所设计的电路。 2.进行实验数据处理和分析。 五、推荐参考资料 1?谢自美?电子线路设计?实验?测试.[M]武汉:华中理工大学出版社,2000 年 2. 阎石. 数字电子技术基础. [M] 北京:高等教育出版社,2006年 3. 童诗白、华成英.模拟电子技术基础. [M] 北京:高等教育出版社,2006年 4..付家才. 电子实验与实践. [M] 北京:高等教育出版社,2004年 5.沙占友、李学芝著.中外数字万用表电路原理与维修技术. [M] 北京:人民 邮电出版社,1993年

六、按照要求撰写课程设计报告成绩评定表

一、概述 本次毕设的题目是:三相步进电机控制电路的设计。本次毕设使用80C51单片机作为主控芯片,利用ULN2003A集成电路作为三相步进电机的驱动电路,采用单极性驱动方式,使三相步进电机能在(1)三相单三拍,(2)三相双三拍, (3)三相六拍三种工作方式下正常工作;能实现的功能有:启动/停止控制、方向控制;速度控制;用LED数码管显示工作方式。键盘输入工作频率。本次课程设计采用80C51单片机作为主控芯片,程序采用C语言来编写,驱动电路采用ULN2003A集成电路,显示采用 7SEG-MPX4-CC卩四位共阴数码管,P0接段码,并用8只1K欧左右电阻上拉。P2的4位10 口接位选码。正转,数码管显示1。反转,数码管显示2.不转,数码管显示0.采用Proteus软件进行仿真。在Keil uVsuon3编程环境下编程和编译生成HEX文件,导入到 80C51单片机,实现对各个模块的控制,实现我们所需要的功能。 本次课程是对毕业设计的基础设计,即实现4x4键盘输入,数码管显示输入数字的设计。 二、方案论证 1步进电机驱动方案选择 方案1 :使用功率三极管等电子器件搭建成功率驱动电路来驱动电机的运行。这种方案的驱动电路的优点是使用电子器件联接,电路比较简单,但容易受 干扰,信号不够稳定,缺点是器件较大而不便电路的集成,使用时很不方便,联接时容易出错误。 方案2:使用专门的电机驱动芯片ULN2003A来驱动电机运行。驱动芯片的优点是便于电路的集成,且驱动电路简单,驱动信号很稳定,不易受外界环境的干扰,因而设计的三相步进电机控制系统性能更好。 通过对两种方案的比较,我选择方案2使用ULN2003A S机驱动芯片来作为驱动。 2数码管显示方案选择 方案1:把所需要显示的数据通过专用的七段显示译码器(例如7448)的转换输出给LED显示屏。优点是输出比较简单,可以简化程序,但增加了芯片的费用,电路也比较复杂。 方案2:通过程序把所要的数据转化为七段显示的数据,直接通过单片机接 口来显示,其优点是简化了电路,但增加了软件编写的负担。 通过对两种方案进行比较,我选择通过软件编写来输出显示信号,即单片机直接和显示器相连。 3控制状态的读取 方案1:把按键接到单片机的中断口,若有按键按下,单片机接收到中断信 号,再通过软件编写的中断程序来执行中断,优点是接线简单,简化了电路,但软件编写较为复杂,不易掌握。

第六讲 独立按键和矩阵键盘 第七讲 数码管要点

第六讲独立按键和矩阵键盘 按键是什么东西,我想这个就不必由我向各位阐述了。嗯,如你所见,按键种类繁多,功能有简有繁,极大的充斥着我们的生活。但是无论如何,所有的按键其实都有一个原型,来源于同一种原理,所有的按键无论多复杂,多华丽,都是从这样一个原型发展而成的。好比你就算长的再帅,你也是只猩猩变来的,呵呵。我们平日所见到的绝大部分的按键,其实都可以归类为一种,叫“接触式按键”。下图为一个典型的接触式按键(又称轻触开关)。 需要特别说明的是,这里说的“接触”,是指机械层面上的接触,而不是感光或者某些特殊涂层(比如触摸屏)一类的接触。所以,按键的工作特性其实是一种机械特性,下文会详细说明。 , 如上图,请对照图一想象,1、2、3、4 分别对应按键的四个引脚,其中蓝色的线表示按键未被按下之时的状态,我成为初始状态,它是不导通的;而绿色

的线是却永久导通的。各位明白了么,其实是两个相同的结构连在一起了。我们只要将需要按键开关作用的线路分别接在1、3 和2、4 的任意取一组合,概括起来就是(1,2)、(1,4)、(3,2)、(3,4)四种组合,都可以起到我们预期的开关作用。 相信以上说明使大家对按键的工作原理有了个比较清晰的认识了,现在来说说一个小知识。先看下图(图4): 首先说明的是,上图的连法是不允许的,因为当按键按下之后,电源和地短接,会将导线直接烧毁。但是此处用作特例,假设导线不会烧毁。现在来提出一个问题,当按键按下以后,请问如果这时用万用表测量导线上任何一处的电压,得到的结果是VCC 还是GND 的电压? 答案是:GND,即表示测出的电压为0V。为什么呢,因为导线上,对于两端的电平是一种类似于程序语言逻辑运算里面的“与”,即对于导线两端:有零即为零,只有全为一是才为一。理解了这点,按键的工作前提就有了。 键盘分为编码键盘和非编码键盘。键盘上闭合键的识别由专用的硬件编码器实现,并产生键编码号或键值的称为编码键盘,如计算机键盘。而靠软件编程来识别的键盘称为非编码键盘,在单片机组成的各种系统中,用的较多的是非编码键盘。非编码键盘又分为独立键盘和行列式键盘(常说的矩阵键盘)。在这一讲中我们介绍一下单片机中键盘使用。 单片机的IO口既可作为输出也可作为输入使用,当检测按键时用的是它的输入功能,我们把按键的一端接地,另一端与单片机的某个I/O口相连,开始时先给该IO口赋一高电平,然后让单片机不断地检测该I/O口是杏变为低电平,当按键闭合时,即相当于该I/O口通过按键与地相连,变成低电平,程序一旦检测到I/O口变为低电平则说明按键被按下,然后执行相应的指令。 我们先来说一下,按键常常遇到的问题—抖动问题。

数码管显示4×4键盘矩阵按键

9数码管显示4×4键盘矩阵按键 #include #define uchar unsigned char #define uint unsigned int sbit BEEP = P3^7; uchar code DSY_CODE[]= { 0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0x00 }; uchar Pre_KeyNO = 16,KeyNO = 16; void DelayMS(uint ms) { uchar t; while(ms--) { for(t=0;t<120;t++); } }

void Keys_Scan() { uchar Tmp; P1 = 0x0f; DelayMS(1); Tmp = P1 ^ 0x0f; switch(Tmp) { case 1: KeyNO = 0; break; case 2: KeyNO = 1; break; case 4: KeyNO = 2; break; case 8: KeyNO = 3; break; default: KeyNO = 16; } P1 = 0xf0; DelayMS(1); Tmp = P1 >> 4 ^ 0x0f; switch(Tmp) { case 1: KeyNO += 0; break; case 2: KeyNO += 4; break; case 4: KeyNO += 8; break; case 8: KeyNO += 12; } } void Beep() { uchar i; for(i=0;i<100;i++) { DelayMS(1); BEEP = ~BEEP; } BEEP = 1; } void main() { P0 = 0x00; while(1) { P1 = 0xf0; if(P1 != 0xf0)

经典的矩阵键盘扫描程序

键盘是单片机常用输入设备,在按键数量较多时,为了节省I/O口等单片机资源,一般采取扫描的方式来识别到底是哪一个键被按下。即通过确定被按下的键处在哪一行哪一列来确定该键的位置,获取键值以启动相应的功能程序。 4*4矩阵键盘的结构如图1(实物参考见万用板矩阵键盘制作技巧)。在本例中,矩阵键盘的四列依次接到单片机的P1.0~P1.3,四行依次接到单片机的P1.4~P1.7;同时,将列线上拉,通过10K电阻接电源。 查找哪个按键被按下的方法为:一个一个地查找。 先第一行输出0,检查列线是否非全高; 否则第二行输出0,检查列线是否非全高; 否则第三行输出0,检查列线是否非全高; 如果某行输出0时,查到列线非全高,则该行有按键按下; 根据第几行线输出0与第几列线读入为0,即可判断在具体什么位置的按键按下。 下面是具体程序:

void Check_Key(void) { unsigned char row,col,tmp1,tmp2; tmp1 = 0x10; //tmp1用来设置P1口的输出,取反后使 P1.4~P1.7中有一个为0 for(row=0;row<4;row++) // 行检测 { P1 = 0x0f; // 先将p1.4~P1.7置高 P1 =~tmp1; // 使P1.4~p1.7中有一个为0 tmp1*=2; // tmp1左移一位 if ((P1 & 0x0f) < 0x0f) // 检测P1.0~P1.3中是否有一位为0,只要有,则说明此行有键按下,进入列检测 { tmp2 = 0x01; // tmp2用于检测出哪一列为0 for(col =0;col<4;col++) // 列检测 { if((P1 & tmp2)==0x00) // 该列如果为低电平则可以判定为该列 { key_val =key_Map[ row*4 +col ]; // 获取键值,识别按键;key_Map为按键的定义表 return; // 退出循环 } tmp2*=2; // tmp2左移一位 } } } } //结束 这是一种比较经典的矩阵键盘识别方法,实现起来较为简单,程序短小精炼。

矩阵键盘设计实验报告

南京林业大学 实验报告 基于AT89C51 单片机4x4矩阵键盘接口电路设计 课程机电一体化设计基础 院系机械电子工程学院 班级 学号 姓名 指导老师杨雨图 2013年9月26日

一、实验目的 1、掌握键盘接口的基本特点,了解独立键盘和矩阵键盘的应用方法。 2、掌握键盘接口的硬件设计方法,软件程序设计和贴士排错能力。 3、掌握利用Keil51软件对程序进行编译。 4、用Proteus软件绘制“矩阵键盘扫描”电路,并用测试程序进行仿真。 5、会根据实际功能,正确选择单片机功能接线,编制正确程序。对实验结果 能做出分析和解释,能写出符合规格的实验报告。 二、实验要求 通过实训,学生应达到以下几方面的要求: 素质要求 1.以积极认真的态度对待本次实训,遵章守纪、团结协作。 2.善于发现数字电路中存在的问题、分析问题、解决问题,努力培养独立 工作能力。 能力要求 1.模拟电路的理论知识 2.脉冲与数字电路的理念知识 3.通过模拟、数字电路实验有一定的动手能力 4.能熟练的编写8951单片机汇编程序 5.能够熟练的运用仿真软件进行仿真 三、实验工具 1、软件:Proteus软件、keil51。 2、硬件:PC机,串口线,并口线,单片机开发板 四、实验内容 1、掌握并理解“矩阵键盘扫描”的原理及制作,了解各元器件的参数及格 元器件的作用。 2、用keil51测试软件编写AT89C51单片机汇编程序 3、用Proteus软件绘制“矩阵键盘扫描”电路原理图。 4、运用仿真软件对电路进行仿真。 五.实验基本步骤 1、用Proteus绘制“矩阵键盘扫描”电路原理图。 2、编写程序使数码管显示当前闭合按键的键值。 3、利用Proteus软件的仿真功能对其进行仿真测试,观察数码管的显示状 态和按键开关的对应关系。 4、用keil51软件编写程序,并生成HEX文件。 5、根据绘制“矩阵键盘扫描”电路原理图,搭建相关硬件电路。 6、用通用编程器或ISP下载HEX程序到MCU。 7、检查验证结果。

LED数码管显示矩阵键盘按键的设计

任务九设计说明2 一、电路原理及仿真图: 二、程序设计: #include #define uchar unsigned char uchar display[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0 x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0 x40}; uchar key; void get(){ uchar a; P1=0x0f; //按下按钮// a=P1^0x0f; switch(a) //确定行// { case 1:key=0;break; case 2:key=4;break; case 4:key=8;break; case 8:key=12;break; case 0:key=16;} P1=0xf0; a=P1^0xf0; switch(a) //确定列//{ case 16:key=key+3;break; case 32:key=key+2;break; case 64:key=key+1;break;

case 128:key=key+0;}} void main(){ P0=display[16]; get(); P0=display[key]; } 程序完成两个功能,首先扫描键盘,检测是否有按键按下并计算键值。 然后如果有按键按下则驱动数码管显示相应键值,否则显示”-“符号。 三、设计说明 如电路原理图所示,图中矩阵键盘和P3端口连接,共阳极数码管的段选端和单片机的P0口连接,位选直接接到高电平,使得数码管始终处于选通状态。系统启动后,单片机逐行扫描键盘,当没有按键按下时,驱动数码管显示“-”符号,当检测到有按键按下时,单片机将相应键值对应的数码编码送至P0端口,驱动数码管以十六进制方式显示被按下的按键的键值。四、遇到的问题 首先遇到的问题是系统启动后数码管没有任何显示,仔细查看仿真现象后发现P0口始终为高阻状态,于是怀疑是数码管极性错误。再检查数码管型号后发现果然使用了共阴极数码管,于是换成共阳极数码管后终于有了显示。其次是希望键值从键盘的左下角起始,即左下角键值为0。但由于对键盘的扫描方向理解的不是很透彻,导致调试了很多次,键值排列顺序都不尽人意。不过最终还是达到了设计要求。

EDA矩阵键盘

实验十五矩阵键盘接口电路的设计 一、实验目的 1、了解普通4×4键盘扫描的原理。 2、进一步加深七段码管显示过程的理解。 3、了解对输入/输出端口的定义方法。 一、实验原理 实现键盘有两种方案:一是采用现有的一些芯片实现键盘扫描;再就是用软件实现键盘扫描。作为一个嵌入系统设计人员,总是会关心产品成本。目前有很多芯片可以用来实现键盘扫描,但是键盘扫描的软件实现方法有助于缩减一个系统的重复开发成本,且只需要很少的CPU 开销。嵌入式控制器的功能能强,可能充分利用这一资源,这里就介绍一下软键盘的实现方案。 图15-1 简单键盘电路 通常在一个键盘中使用了一个瞬时接触开关,并且用如图15-1 所示的简单电路,微处理器可以容易地检测到闭合。当开关打开时,通过处理器的I/O 口的一个上拉电阻提供逻辑1;当开关闭合时,处理器的/IO 口的输入将被拉低得到逻辑0。可遗憾的是,开关并不完善,因为当它们被按下或者被释放时,并不能够产生一个明确的1 或者0。尽管触点可能看起来稳定而且很快地闭合,但与微处理器快速的运行速度相比,这种动作是比较慢的。当触点闭合时,其弹起就像一个球。弹起效果将产生如图15-2 所示的好几个脉冲。弹起的持续时间通常将维持在5ms~30ms 之间。如果需要多个键,则可以将每个开关连接到微处理器上它自己的输入端口。然而,当开关的数目增加时,这种方法将很快使用完所有的输入端口。

图15-2 按键抖动 键盘上阵列这些开关最有效的方法(当需要5 个以上的键时)就形成了一个如图15-3 所示的二维矩阵。当行和列的数目一样多时,也就是方型的矩阵,将产生一个最优化的布列方式(I/O 端被连接的时候)。一个瞬时接触开关(按钮)放置在每一行与线一列的交叉点。矩阵所需的键的数目显然根据应用程序而不同。每一行由一个输出端口的一位驱动,而每一列由一个电阻器上拉且供给输入端口一位。 图15-3 矩阵键盘 键盘扫描的实现过程如下:对于4×4键盘,通常连接为4行、4列,因此要识别按键,只需要知道是哪一行和哪一列即可,为了完成这一识别过程,我们的思想是,首先固定输出4行为高电平,然后输出4列为低电平,在读入输出的4行的值,通常高电平会被低电平拉低,如果读入的4行均为高电平,那么肯定没有按键按下,否则,如果读入的4行有一位为低电平,那么对应的该行肯定有一个按键按下,这样便可以获取到按键的行值。同理,获取列值也是如此,先输出4列为高电平,然后在输出4行为低电平,再读入列值,如果其中有哪一位为低电平,那么肯定对应的那一列有按键按下。 获取到行值和列值以后,组合成一个8位的数据,根据实现不同的编码在对每个按键进行匹配,找到键值后在7段码管显示。 二、实验内容 本实验要求完成的任务是通过编程实现对4X4矩阵键盘按下键的键值的读取,并在数码管上完成一定功能(如移动等)的显示。按键盘的定义,按下“*”键则在数码管是显示“E”键值。按下“#”键在数码管上显示“F”键值。其它的键则按键盘上的标识进行显示。 在此实验中数码管与FPGA的连接电路和管脚连接在以前的实验中都做了详细说

矩阵键盘控制12864显示最经典程序

#include //这个程序的功能:用4*4的矩阵键盘(接P3口)按键盘k1——k16中的任何一个键ki #include //12864液晶上显示数字i-1 (液晶数据口接P0) #define uint unsigned int//键盘扫描的思想是将行设置为低,列设置为高,来读取P3口的值,就能知道是哪个按键按下了 #define uchar unsigned char #define LCDdata P0 sbit E = P2^7; sbit RW = P2^6; sbit RS = P2^5; void init(); void delayms(uint x); void displaykey(); void write_com(uchar com);//写命令 void write_data(uchar date);//写数据 uchar temp; //--------------主函数----------------- void main() { init();// P3=0xfe;//P3=0xfd;//P3=0xfb;//P3=0xf7; while(1) { displaykey(); } } //-------------液晶初始化---------------- void init() { write_com(0x01); write_com(0x02); write_com(0x06); write_com(0x0e); } //------------毫秒延时--------------- void delayms(uint x) { uchar i; while(x--) {

矩阵键盘程序c程序,51单片机.

/*编译环境:Keil 7.50A c51 */ /*******************************************************/ /*********************************包含头文件********************************/ #include /*********************************数码管表格********************************/ unsigned char table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x 8E}; /**************************************************************************** 函数功能:延时子程序 入口参数: 出口参数: ****************************************************************************/ void delay(void) { unsigned char i,j; for(i=0;i<20;i++) for(j=0;j<250;j++); } /**************************************************************************** 函数功能:LED显示子程序 入口参数:i 出口参数: ****************************************************************************/ void display(unsigned char i) { P2=0xfe; P0=table[i]; } /**************************************************************************** 函数功能:键盘扫描子程序 入口参数: 出口参数: ****************************************************************************/ void keyscan(void) { unsigned char n; //扫描第一行 P1=0xfe;

单片机独立按键和矩阵按键

单片机按键(独立按键和矩阵按键) 独立按键 常用的按键电路有两种形式,独立式按键和矩阵式按键,独立式按键比较简单,它们各自与独立的输入线相连接,如图8-6 所示。 图8-6 独立式按键原理图 4 条输入线接到单片机的IO 口上,当按键K1 按下时,+5V 通过电阻R1 然后再通过按键K1 最终进入GND 形成一条通路,那么这条线路的全部电压都加到了R1 这个电阻上,KeyIn1 这个引脚就是个低电平。当松开按键后,线路断开,就不会有电流通过,那么KeyIn1和+5V 就应该是等电位,是一个高电平。我们就可以通过KeyIn1 这个IO 口的高低电平来判断是否有按键按下。 这个电路中按键的原理我们清楚了,但是实际上单片机IO 口内部,也有一个上拉电阻的存在。我们的按键是接到了P2 口上,P2 口上电默认是准双向IO 口,我们来简单了解一下这个准双向IO 口的电路,如图8-7 所示。

图8-7 准双向IO 口结构图 首先说明一点,就是我们现在绝大多数单片机的IO 口都是使用MOS 管而非三极管,但用在这里的MOS 管其原理和三极管是一样的,因此在这里我用三极管替代它来进行原理讲解,把前面讲过的三极管的知识搬过来,一切都是适用的,有助于理解。 图8-7 方框内的电路都是指单片机内部部分,方框外的就是我们外接的上拉电阻和按键。这个地方大家要注意一下,就是当我们要读取外部按键信号的时候,单片机必须先给该引脚写“1”,也就是高电平,这样我们才能正确读取到外部按键信号,我们来分析一下缘由。 当内部输出是高电平,经过一个反向器变成低电平,NPN 三极管不会导通,那么单片机IO 口从内部来看,由于上拉电阻R 的存在,所以是一个高电平。当外部没有按键按下将电平拉低的话,VCC 也是+5V,它们之间虽然有2 个电阻,但是没有压差,就不会有电流,线上所有的位置都是高电平,这个时候我们就可以正常读取到按键的状态了。 当内部输出是个低电平,经过一个反相器变成高电平,NPN 三极管导通,那么

单片机实验报告——矩阵键盘数码管显示

单片机实验报告 信息处理实验 实验二矩阵键盘 专业:电气工程及其自动化 指导老师:高哲 组员:明洪开张鸿伟张谦赵智奇 学号:152703117 \152703115\152703118\152703114室温:18 ℃日期:2017 年10 月25日

矩阵键盘 一、实验内容 1、编写程序,做到在键盘上每按一个键(0-F)用数码管将该建对应的名字显示出来。按其它键没有结果。 二、实验目的 1、学习独立式按键的查询识别方法。 2、非编码矩阵键盘的行反转法识别方法。 3、掌握键盘接口的基本特点,了解独立键盘和矩阵键盘的应用方法。 4、掌握键盘接口的硬件设计方法,软件程序设计和贴士排错能力。 5、掌握利用Keil51软件对程序进行编译。 6、会根据实际功能,正确选择单片机功能接线,编制正确程序。对实验结果 能做出分析和解释,能写出符合规格的实验报告。 三、实验原理 1、MCS51系列单片机的P0~P3口作为输入端口使用时必须先向端口写入“1”。 2、用查询方式检测按键时,要加入延时(通常采用软件延时10~20mS)以消除抖动。 3、识别键的闭合,通常采用行扫描法和行反转法。行扫描法是使键盘上某一行线为低电平,而其余行接高电平,然

后读取列值,如读列值中某位为低电平,表明有键按下,否则扫描下一行,直到扫完所有行。 行反转法识别闭合键时,要将行线接一并行口,先让它工作在输出方式,将列线也接到一个并行口,先让它工作于输入方式,程序使CPU通过输出端口在各行线上全部送低电平,然后读入列线值,如此时有某键被按下,则必定会使某一列线值为0。然后,程序对两个并行端口进行方式设置,使行线工作于输入方式,列线工作于输出方式,并将刚才读得的列线值从列线所接的并行端口输出,再读取行线上输入值,那么,在闭合键所在行线上的值必定为0。这样,当一个键被接下时,必定可以读得一对唯一的行线值和列线值。 由于51单片机的并口能够动态地改变输入输出方式,因此,矩阵键盘采用行反转法识别最为简便。 行反转法识别按键的过程是:首先,将4个行线作为输出,将其全部置0,4个列线作为输入,将其全部置1,也就是向P1口写入0xF0;假如此时没有人按键,从P1口读出的值应仍为0xF0;假如此时1、4、7、0四个键中有一个键被按下,则P1.6被拉低,从P1口读出的值为0xB0;为了确定是这四个键中哪一个被按下,可将刚才从P1口读出的数的低四位置1后再写入P1口,即将0xBF写入P1口,使P1.6为低,其余均为高,若此时被按下的键是“4”,则P1.1被拉低,从P1口读出的值为0xBE;这样,当只有一个键被按下时,每一个键只有唯一的反转码,事先为12个键的反转码建一个表,通过查表就可知道是哪个键被按下了。

单片机按键矩阵识别(含程序、原理图)

按键矩阵识别技术实验说明 如图2所示,把P1端口的8条I/O口分成4条列线4条行线交叉但不接触构成4×4键盘阵列,16个按键放置交叉位置,这样在单片机复杂系统需要较多按键时,这种接法可以节省单片机的硬件资源。 1.结合给出的电路原理图试分析4*4键盘矩阵识别原理,及LED动态扫描原理。(6分) 2.根据分析的键盘矩阵识别原理设计程序实现一下功能:当按下某个按键时在2个七段数码管上显示该按键的编号(注意考虑同时按下多个按键时程序处理过程)、按下某个按键使其弹起时对于消抖情况程序的处理。(9分)

2.0相关原理图如下:

3.0实验说明 本试验给了1-8键判断方法。按1-8键中任意键,则数码管显示该键编号。 想想怎样实现1-16个键的判断显示? 参考程序见程序范例。 /************************************************************************ ****************** *描述: 按键距阵识别技术 *编写: 秦立春 *版本信息: V1.0 2008年4月20日 *说明: sp1,sp2,SP3跳线向右; ************************************************************************* *****************/ #include #define uchar unsigned char #define uint unsigned int #define ON 0 #define OFF 1 uchar bdata OUT; sbit JDQ=OUT^0; sbit HF =OUT^1; sbit BZ =OUT^2; sbit AA =OUT^3; sbit BB =OUT^4; sbit CC =OUT^5; sbit DD =OUT^6; sbit X0=P2^0; sbit X1=P2^1; sbit X2=P2^2; sbit X3=P2^3; sbit Y0=P2^4; sbit Y1=P2^5; sbit Y2=P2^6; sbit Y3=P2^7; sbit RS=P1^7; sbit RW=P3^4; sbit E =P3^5;

矩阵键盘显示系统

1 4×4矩阵式键盘识别显示系统概述 矩阵式键盘模式以N个端口连接控制N*N个按键,实时在LED数码管上显示按键信息。显示按键信息,既降低了成本,又提高了精确度,省下了很多的I/O 端口为他用,相反,独立式按键虽编程简单,但占用I/O口资源较多,不适合在按键较多的场合应用。并且在实际应用中经常要用到输入数字、字母、符号等操作功能,如电子密码锁、电话机键盘、计算器按键等,至少都需要12到16个按键,在这种情况下如果用独立式按键的话,显然太浪费I/O端口资源,为了解决这一问题,我们使用矩阵式键盘。 矩阵式键盘又称行列键盘,它是用N条I/O线作为行线,N条I/O线作为列线组成的键盘。在行线和列线的每个交叉点上设置一个按键。这样键盘上按键的个数就为N×N个。这种行列式键盘结构能有效地提高单片机系统中I/O口的利用率。 最常见的键盘布局如图1.1所示。一般由16个按键组成,在单片机中正好可以用一个P口实现16个按键功能,这也是在单片机系统中最常用的形式,本设计就采用这个键盘模式。 图1.1 键盘布局

2系统主要硬件电路设计 2.1单片机控制系统原理 图2.1 单片机控制系统原理框图 2.2单片机主机系统电路 AT89C52单片机是51系列单片机的一个成员,是52单片机的简化版。内部自带2K字节可编程FLASH存储器的低电压、高性能COMS八位微处理器,与Intel MCS-52系列单片机的指令和输出管脚相兼容。由于将多功能八位CPU和闪速存储器结合在单个芯片中,因此,AT89C52构成的单片机系统是具有结构最简单、造价最低廉、效率最高的微控制系统,省去了外部的RAM、ROM和接口器件,减少了硬件开销,节省了成本,提高了系统的性价比。 图2.2 单片机主机系统图

51单片机矩阵键盘程序

/*风清云扬*/ # include #define uchar unsigned char #define uint unsigned int void delay(uint i) { uchar x,j; for(j=0;j

} else if(temp0==0x0b) { switch (temp1) { case 0xe0: num=12;break; case 0xd0: num=11;break; case 0xb0: num=10;break; case 0x70: num=9;break; default:num=0;break; } } else if(temp0==0x07) { switch (temp1) { case 0xe0: num=16;break; case 0xd0: num=15;break; case 0xb0: num=14;break; case 0x70: num=13;break; default:num=0;break; } } } } return num; } void main() { char num; while(1) { num=key_scan(); P2=num/10; P3=num%10; } }

单片机矩阵键盘

单片机 4*4 矩阵键盘 在单片机按键使用过程中,当键盘中按键数量较多时为了减少端口的占用通常将按键排列成矩阵形式如下图所示,在矩阵式键盘中每条水平线和垂直线在交叉处不直接连通而是通过一个按键加以连接,到底这样做是出意何种目的呢?大家看下面电路图,单片机的整一个8位端口可以构成4*4=16 个矩阵式按键,相比独立式按键接法多出了一倍,而且线数越多区别就越明显,假如再多加一条线就可以构成20个按键的键盘,但是独立式按键接法只能多出1个按键。由此可见,在需要的按键数量比较多时,采用矩阵法来连接键盘是非常合理的,矩阵式结构的键盘显然比独立式键盘复杂一些,单片机对其进行识别也要复杂一些。确定矩阵式键盘上任何一个键被按下通常采用行扫描法。行扫描法又称为逐行查询法它是一种最常用的多按键识别方法。因此,我们就以行扫描法为例介绍矩阵式键盘的工作原理。 首先,不断循环地给低四位独立的低电平,然后判断键盘中有无键按下。将低位中其中一列线(P1.0~P1.3中其中一列)置低电平然后检测行线的状态(高4位,即P1.4~P1.7,由于线与关系,只要与低电平列线接通,即跳变成低电平),只要有一行的电平为低就延时一段时间以消除抖动,然后再次判断,假如依然为低电平,则表示键盘中真的有键被按下而且闭合的键位于低电平的4个按键之中任其一,若所有行线均为高电平则表示键盘中无键按下。再其次,判断闭合键所在的具体位置。在确认有键按下后,即可进入确定具体闭合键的过程。其方法是: 依次将列线置为低电平,即在置某一根列线为低电平时,其它列线为高电平。同时再逐行检测各行线的电平状态;若某行为低,则该行线与置为低电平的列线交叉处的按键就是闭合的按键。下面图5-5是4*4矩阵式按键接法的软件算法操作流程。 图5-4(4*4矩阵式按键的接法) 下面程序按照上述算法流程去编写的,其电路如图5-6,只是在图5-5的基础上多加了P0端口的8只LED灯。从键盘中检测到一个键值,然后将这个值写到LED数码管上显示。

相关文档
最新文档