单片机独立按键与矩阵按键原理图

畅学单片机 2021-03-01 00:00

独立按键

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

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

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

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

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

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

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

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

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

矩阵按键

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

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

我们先用一个简单的程序来实现这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任何一个按键或者多个按键的时候,我们对应赋值的小灯就会点亮,松开按键的时候,小灯就熄灭。

从这里可以看出来,其实独立按键本身就是矩阵按键中的一种情况而已。

13.3按键消抖

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

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

#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操作。理论是如此,大家可以多按几次,是不是会发生这样一种现象:有的时候我明明只按了一下按键,但数字却加了不止1,而是2或者更多?但是我们的程序并没有任何逻辑上的错误,这是怎么回事呢?于是我们就得来说说按键抖动和消抖了。

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

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

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

在绝大多数情况下,我们是用软件即程序来实现消抖的。最简单的消抖原理,就是当检测到按键状态变化后,先等待一个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内如果按键状态一直保持一种状态,那就可以确定现在按键是稳定的阶段,并非处于抖动的阶段,如图13-7图13-7 按键连续判断

假如左边时间是起始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

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灯的亮和灭、三极管的导通与关断、开关的闭合与断开,联想一下已经学过的内容,它是不是能用最小的内存代价来完成很多工作呢?

13.4矩阵按键

我们讲了独立按键,大家已经简单认识了矩阵按键是什么样子了。矩阵按键相当于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

{

for (j=0; j

{

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

{

if (backup[j] == 0) //判断按键弹起

{

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

}

backup[j] = KeySta[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]

keybuf[keyout][1] = (keybuf[keyout][1]

keybuf[keyout][2] = (keybuf[keyout][2]

keybuf[keyout][3] = (keybuf[keyout][3]

//消抖后更新按键状态

for (i=0; i

{

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

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

KeySta[keyout] = 0;

}

else if ((keybuf[keyout] & 0x0F) == 0x0F)

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

KeySta[keyout] = 1;

}

}

//执行下一次的扫描输出

keyout++;

keyout &= 0x03; //用跟0x03做“与”的方式,实现加到4即归零,是不是很巧妙,学会它吧

switch (keyout)

{

case 0:

KEY_OUT_4 = 1;

KEY_OUT_1 = 0;

break;

case 1:

KEY_OUT_1 = 1;

KEY_OUT_2 = 0;

break;

case 2:

KEY_OUT_2 = 1;

KEY_OUT_3 = 0;

break;

case 3:

KEY_OUT_3 = 1;

KEY_OUT_4 = 0;

break;

default:

break;

}

}

这个程序是一个比较简单的按键程序,但是大家要把按键消抖和矩阵按键检测机制充分理解透彻,这块内容今后就是你的一个技术积累了。

13.5按键、数码管简单加法运算

这一小节内容只有一个程序,使用我们的矩阵按键实现计算器中简单的整数加法运算,这是我们第一次做一个算的上的综合性程序,实现了按键和数码管以及C语言灵活运用的一个例程。作为初学者针对这种程序的学习方式是,先从头到尾读一到三遍,边读边理解,然后边抄边理解,彻底理解透彻后,自己尝试独立写出来。完全采用记忆模式来学习这种例程,一两个例程你感觉不到什么提高,当这种例程背过上百八十个的时候,厚积薄发的感觉就会体现出来了。同时,在抄读的过程中注意学习我们程序的编程规范,尽量规整一些。

#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

}; //数码管真值表

const unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到PC标准键盘键码的映射表

{ '1', '2', '3', 0x26 }, //数字键1、数字键2、数字键3、向上键

{ '4', '5', '6', 0x25 }, //数字键4、数字键5、数字键6、向左键

{ '7', '8', '9', 0x28 }, //数字键7、数字键8、数字键9、向下键

{ '0', 0x1B, 0x0D, 0x27 } //数字键0、ESC键、 回车键、 向右键

};

unsigned char KeySta[4][4] = { //全部矩阵按键的当前状态

{1, 1, 1, 1},

{1, 1, 1, 1},

{1, 1, 1, 1},

{1, 1, 1, 1}

}; //由于数组不能定义成bit型,这里定义成unsigned char型

unsigned char LedBuf[6] = { //数码管动态扫描显示缓冲区

0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF

};

void DisplayNum(unsigned long num);

void KeyAction(unsigned char keycode);

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;

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

{

for (j=0; j

{

if (backup[j] != KeySta[j])

{

if (backup[j] == 0) //按键弹起时执行动作

{

KeyAction(KeyCodeMap[j]);

}

backup[j] = KeySta[j];

}

}

}

}

}

void KeyAction(unsigned char keycode)

{

static unsigned long result = 0; //用于保存运算结果

static unsigned long addend = 0; //用于保存输入的加数

if ((keycode>='0') && (keycode

{

addend = (addend*10) + (keycode-'0'); //原数据扩大10倍,由新输入的数字填充其个位

DisplayNum(addend); //运算结果显示到数码管

}

else if (keycode == 0x26) //向上键用作加号,执行加法或连加运算

{

result += addend; //进行加法运算

addend = 0;

DisplayNum(result); //运算结果显示到数码管

}

else if (keycode == 0x0D) //回车键,执行加法运算(实际效果与加号并无区别)

{

result += addend; //进行加法运算

addend = 0;

DisplayNum(result); //运算结果显示到数码管

}

else if (keycode == 0x1B) //Esc键,清零结果

{

addend = 0;

result = 0;

DisplayNum(addend); //清零后的加数显示到数码管

}

}

void DisplayNum(unsigned long num)

{

signed char i;

unsigned char buf[6];

for (i=0; i

{

buf = num % 10;

num /= 10;

}

for (i=5; i>=1; i--) //从最高位起,遇到0即转换为空格,遇到非0即退出

{

if (buf == 0)

{

LedBuf = 0xFF;

}

else

{

break;

}

}

for ( ; i>=0; i--) //剩余低位都如实转换为数字

{

LedBuf = LedChar[buf];

}

}

void InterruptTimer0() interrupt 1

{

unsigned char i;

static unsigned char ledcnt = 0; //数码管扫描计数器

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]

keybuf[keyout][1] = (keybuf[keyout][1]

keybuf[keyout][2] = (keybuf[keyout][2]

keybuf[keyout][3] = (keybuf[keyout][3]

//消抖后更新按键状态

for (i=0; i

{

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

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

KeySta[keyout] = 0;

}

else if ((keybuf[keyout] & 0x0F) == 0x0F)

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

KeySta[keyout] = 1;

}

}

//执行下一次的扫描输出

keyout++;

keyout &= 0x03;

switch (keyout)

{

case 0:

KEY_OUT_4 = 1;

KEY_OUT_1 = 0;

break;

case 1:

KEY_OUT_1 = 1;

KEY_OUT_2 = 0;

break;

case 2:

KEY_OUT_2 = 1;

KEY_OUT_3 = 0;

break;

case 3:

KEY_OUT_3 = 1;

KEY_OUT_4 = 0;

break;

default:

break;

}

//执行数码管动态扫描显示

P0 = 0xFF;

switch (ledcnt)

{

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;

default: break;

}

P0 = LedBuf[ledcnt];

ledcnt++;

if (ledcnt >= 6)

{

ledcnt = 0;

}

}

畅学单片机 以单片机为核心,带你全面了解和单片机相关的知识技巧,经验心得。关注我们,一起来学习吧!
评论
  • 采购与分销是企业运营中至关重要的环节,直接影响到企业的成本控制、客户满意度和市场竞争力。以下从多个方面介绍如何优化采购与分销:采购环节优化供应商管理供应商评估与选择:建立一套全面、科学的供应商评估体系,除了考虑价格因素,还要综合评估供应商的产品质量、交货期、信誉、研发能力、售后服务等。通过多维度评估,选择那些能够提供优质产品和服务,且与企业战略目标相契合的供应商。建立长期合作关系:与优质供应商建立长期稳定的合作关系,这种合作模式可以带来诸多好处。双方可以在信任的基础上进行深度沟通与协作,共同开展
    Jeffreyzhang123 2024-12-27 17:43 161浏览
  • 在当今这个科技飞速发展的时代,物联网(IoT)已经不再是一个陌生的概念,它正以一种前所未有的速度改变着我们的生活和工作方式,像一股无形的力量,将世界紧密地连接在一起,引领我们步入一个全新的智能时代。物联网是什么简单来说,物联网就是通过感知设备、网络传输、数据处理等技术手段,实现物与物、人与物之间的互联互通和智能化管理。想象一下,你的家里所有的电器都能 “听懂” 你的指令,根据你的习惯自动调节;工厂里的设备能够实时监测自身状态,提前预警故障;城市的交通系统可以根据实时路况自动优化信号灯,减少拥堵…
    Jeffreyzhang123 2024-12-27 17:18 131浏览
  • 在科技飞速发展的今天,医疗电子作为一个融合了医学与电子技术的交叉领域,正以前所未有的速度改变着我们的医疗模式和健康生活。它宛如一颗璀璨的明珠,在医疗领域绽放出耀眼的光芒,为人类的健康福祉带来了诸多惊喜与变革。医疗电子的神奇应用医疗电子的应用范围极为广泛,深入到医疗的各个环节。在诊断方面,各种先进的医学成像设备堪称医生的 “火眼金睛”。X 光、CT、MRI 等成像技术,能够清晰地呈现人体内部的结构和病变情况,帮助医生准确地发现疾病。以 CT 为例,它通过对人体进行断层扫描,能够提供比传统 X 光更
    Jeffreyzhang123 2024-12-27 15:46 151浏览
  • 在当今科技飞速发展的时代,工业电子作为现代制造业的中流砥柱,正以前所未有的速度推动着各个行业的变革与进步。从汽车制造到航空航天,从智能家居到工业自动化,工业电子的身影无处不在,为我们的生活和生产带来了巨大的改变。工业电子的崛起与发展工业电子的发展历程可谓是一部波澜壮阔的科技进化史。追溯到上世纪中叶,电子技术开始逐渐应用于工业领域,最初主要是简单的电子控制装置,用于提高生产过程的自动化程度。随着半导体技术、计算机技术和通信技术的不断突破,工业电子迎来了爆发式的增长。集成电路的发明使得电子设备的体积
    Jeffreyzhang123 2024-12-27 15:40 154浏览
  • 在科技飞速发展的今天,汽车不再仅仅是一种交通工具,更是一个融合了先进技术的移动智能空间。汽车电子作为汽车产业与电子技术深度融合的产物,正以前所未有的速度推动着汽车行业的变革,为我们带来更加智能、安全、舒适的出行体验。汽车电子的发展历程汽车电子的发展可以追溯到上世纪中叶。早期,汽车电子主要应用于发动机点火系统和简单的电子仪表,功能相对单一。随着半导体技术的不断进步,集成电路被广泛应用于汽车领域,使得汽车电子系统的性能得到了显著提升。从电子燃油喷射系统到防抱死制动系统(ABS),从安全气囊到车载导航
    Jeffreyzhang123 2024-12-27 11:53 149浏览
  • 在当今这个数字化的时代,电子设备无处不在,从我们手中的智能手机、随身携带的笔记本电脑,到复杂的工业控制系统、先进的医疗设备,它们的正常运行都离不开一个关键的 “幕后英雄”—— 印刷电路板(Printed Circuit Board,简称 PCB)。PCB 作为电子设备中不可或缺的重要部件,默默地承载着电子元件之间的连接与信号传输,是整个电子世界的基石。揭开 PCB 的神秘面纱PCB,简单来说,就是一块由绝缘材料制成的板子,上面通过印刷、蚀刻等工艺形成了导电线路和焊盘,用于固定和连接各种电子元件。
    Jeffreyzhang123 2024-12-27 17:21 146浏览
  • 引言工程师作为推动科技进步和社会发展的核心力量,在各个领域发挥着关键作用。为深入了解工程师的职场现状,本次调研涵盖了不同行业、不同经验水平的工程师群体,通过问卷调查、访谈等方式,收集了大量一手数据,旨在全面呈现工程师的职场生态。1. 工程师群体基本信息行业分布:调研结果显示,工程师群体广泛分布于多个行业,其中制造业占比最高,达到 90%,其次是信息技术、电子通信、能源等行业。不同行业的工程师在工作内容、技术要求和职业发展路径上存在一定差异。年龄与经验:工程师群体以中青年为主,30 - 45 岁年
    Jeffreyzhang123 2024-12-27 17:39 184浏览
  • 从教师的角度来看,麻省理工学院开除因学术造假的学生,这一决定是合理且必要的。首先,学术诚信是学术研究的基石。在学术界,真实性和原创性是至关重要的。学术造假不仅破坏了学术研究的公正性和准确性,还损害了学术领域的整体声誉。因此,对于任何形式的学术不端行为,包括伪造数据、抄袭等,学校都应采取严厉措施,以维护学术诚信。其次,学校对学生具有管理权,包括对学生的处分权。按照相关规定,学校有权对违纪学生进行警告、严重警告、记过、留校察看、勒令退学、开除学籍等处分。开除学籍是一种严厉的处分,通常适用于严重违反学
    curton 2024-12-28 21:49 103浏览
  • 在当今竞争激烈的商业世界中,供应链管理已成为企业生存与发展的核心竞争力之一。它就像一条无形的纽带,将供应商、制造商、分销商、零售商直至最终消费者紧密相连,确保产品和服务能够高效、顺畅地流转。今天,就让我们一同深入探索供应链管理的奥秘。供应链管理是什么简单来说,供应链管理是对从原材料采购、生产制造、产品配送直至销售给最终用户这一整个过程中,涉及的物流、信息流和资金流进行计划、协调、控制和优化的管理活动。它不仅仅是对各个环节的简单串联,更是一种通过整合资源、优化流程,实现整体效益最大化的管理理念和方
    Jeffreyzhang123 2024-12-27 17:27 144浏览
  • 一、前言 回首2024,对于我而言,是充满挑战与收获的一年。在这一年里,我积极参与了论坛的众多活动,不仅拓宽了我的认知边界(有些东西不是你做不到,而是你想不到),还让我在实践中收获了宝贵的经验和。同时,多种多样的论坛活动让我们全方面的接受新东西,连接新知识,多种类型的的活动交织了你我的2024。在这里说一说对过去一年的活动经历,进行一次年终总结,并谈谈我的收获和感受,以及对2025年的展望。二、活动足迹(一)快速体验:机智云Gokit2.0开发板初体验 机智云Gokit2.0开发板的体验活动让大
    无言的朝圣 2024-12-27 14:50 105浏览
  • 一、引言无人机,作为近年来迅速崛起的新兴技术产物,正以前所未有的速度改变着众多行业的运作模式,从民用领域的航拍、物流,到工业领域的测绘、巡检,再到军事领域的侦察、打击等,无人机的身影无处不在。为了深入了解无人机的现状,本次调研综合了市场数据、行业报告、用户反馈等多方面信息,全面剖析无人机的发展态势。二、市场规模与增长趋势随着技术的不断进步和成本的逐渐降低,无人机市场呈现出爆发式增长。近年来,全球无人机市场规模持续扩大,预计在未来几年内仍将保持较高的增长率。从应用领域来看,消费级无人机市场依然占据
    Jeffreyzhang123 2024-12-27 17:29 237浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦