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

畅学单片机 2019-12-03 15:03

独立按键

通常的按键分为独立式按键和矩阵式按键两种,独立式按键比较简单,并且与独立的输入线相连接,如图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;

}

}

为了方便大家更好的学习,您还可以关注畅学电子和EDA的公众号,每天推送相关知识,希望能对你的学习有所帮助!


畅学单片机 以单片机为核心,带你全面了解和单片机相关的知识技巧,经验心得。关注我们,一起来学习吧!
评论
  • 前言近年来,随着汽车工业的快速发展,尤其是新能源汽车与智能汽车领域的崛起,汽车安全标准和认证要求日益严格,应用范围愈加广泛。ISO 26262和ISO 21448作为两个重要的汽车安全标准,它们在“系统安全”中扮演的角色各自不同,但又有一定交集。在智能网联汽车的高级辅助驾驶系统(ADAS)应用中,理解这两个标准的区别及其相互关系,对于保障车辆的安全性至关重要。ISO 26262:汽车功能安全的基石如图2.1所示,ISO 26262对“功能安全”的定义解释为:不存在由于电子/电气系统失效引起的危害
    广电计量 2025-01-02 17:18 171浏览
  • 影像质量应用于多个不同领域,无论是在娱乐、医疗或工业应用中,高质量的影像都是决策的关键基础。清晰的影像不仅能提升观看体验,还能保证关键细节的准确传达,例如:在医学影像中,它对诊断结果有着直接的影响!不仅如此,影像质量还影响了:▶ 压缩技术▶ 存储需求▶ 传输效率随着技术进步,影像质量的标准不断提高,对于研究与开发领域,理解并提升影像质量已成为不可忽视的重要课题。在图像处理的过程中,硬件与软件除了各自扮演着不可或缺的基础角色,有效地协作能够确保图像处理过程既高效又具有优异的质量。软硬件各扮演了什么
    百佳泰测试实验室 2025-01-03 10:39 73浏览
  • Matter加持:新世代串流装置如何改变智能家居体验?随着现在智能家庭快速成长,串流装置(Streaming Device,以下简称Streaming Device)除了提供更卓越的影音体验,越来越多厂商开始推出支持Matter标准的串流产品,使其能作为智能家庭中枢,连结多种智能家电。消费者可以透过Matter的功能执行多样化功能,例如:开关灯、控制窗帘、对讲机开门,以及操作所有支持Matter的智能家电。此外,再搭配语音遥控器与语音助理,打造出一个更加智能、便捷的居家生活。支持Matter协议
    百佳泰测试实验室 2025-01-03 10:29 88浏览
  • 光耦合器,也称为光隔离器,是一种利用光在两个隔离电路之间传输电信号的组件。在医疗领域,确保患者安全和设备可靠性至关重要。在众多有助于医疗设备安全性和效率的组件中,光耦合器起着至关重要的作用。这些紧凑型设备经常被忽视,但对于隔离高压和防止敏感医疗设备中的电气危害却是必不可少的。本文深入探讨了光耦合器的功能、其在医疗应用中的重要性以及其实际使用示例。什么是光耦合器?它通常由以下部分组成:LED(发光二极管):将电信号转换为光。光电探测器(例如光电晶体管):检测光并将其转换回电信号。这种布置确保输入和
    腾恩科技-彭工 2025-01-03 16:27 102浏览
  • 在快速发展的能源领域,发电厂是发电的支柱,效率和安全性至关重要。在这种背景下,国产数字隔离器已成为现代化和优化发电厂运营的重要组成部分。本文探讨了这些设备在提高性能方面的重要性,同时展示了中国在生产可靠且具有成本效益的数字隔离器方面的进步。什么是数字隔离器?数字隔离器充当屏障,在电气上将系统的不同部分隔离开来,同时允许无缝数据传输。在发电厂中,它们保护敏感的控制电路免受高压尖峰的影响,确保准确的信号处理,并在恶劣条件下保持系统完整性。中国国产数字隔离器经历了重大创新,在许多方面达到甚至超过了全球
    克里雅半导体科技 2025-01-03 16:10 71浏览
  • 从无到有:智能手机的早期探索无线电话装置的诞生:1902 年,美国人内森・斯塔布菲尔德在肯塔基州制成了第一个无线电话装置,这是人类对 “手机” 技术最早的探索。第一部移动手机问世:1938 年,美国贝尔实验室为美国军方制成了世界上第一部 “移动” 手机。民用手机的出现:1973 年 4 月 3 日,摩托罗拉工程师马丁・库珀在纽约曼哈顿街头手持世界上第一台民用手机摩托罗拉 DynaTAC 8000X 的原型机,给竞争对手 AT&T 公司的朋友打了一个电话。这款手机重 2 磅,通话时间仅能支持半小时
    Jeffreyzhang123 2025-01-02 16:41 147浏览
  • 【工程师故事】+半年的经历依然忧伤,带着焦虑和绝望  对于一个企业来说,赚钱才是第一位的,对于一个人来说,赚钱也是第一位的。因为企业要活下去,因为个人也要活下去。企业打不了倒闭。个人还是要吃饭的。企业倒闭了,打不了从头再来。个人失业了,面对的不仅是房贷车贷和教育,还有找工作的焦虑。企业说,一个公司倒闭了,说明不了什么,这是正常的一个现象。个人说,一个中年男人失业了,面对的压力太大了,焦虑会摧毁你的一切。企业说,是个公司倒闭了,也不是什么大的问题,只不过是这些公司经营有问题吧。
    curton 2025-01-02 23:08 196浏览
  • 车身域是指负责管理和控制汽车车身相关功能的一个功能域,在汽车域控系统中起着至关重要的作用。它涵盖了车门、车窗、车灯、雨刮器等各种与车身相关的功能模块。与汽车电子电气架构升级相一致,车身域发展亦可以划分为三个阶段,功能集成愈加丰富:第一阶段为分布式架构:对应BCM车身控制模块,包含灯光、雨刮、门窗等传统车身控制功能。第二阶段为域集中架构:对应BDC/CEM域控制器,在BCM基础上集成网关、PEPS等。第三阶段为SOA理念下的中央集中架构:VIU/ZCU区域控制器,在BDC/CEM基础上集成VCU、
    北汇信息 2025-01-03 16:01 113浏览
  • 在科技飞速发展的今天,机器人已经逐渐深入到我们生活和工作的各个领域。从工业生产线上不知疲倦的机械臂,到探索未知环境的智能探测机器人,再到贴心陪伴的家用服务机器人,它们的身影无处不在。而在这些机器人的背后,C 语言作为一种强大且高效的编程语言,发挥着至关重要的作用。C 语言为何适合机器人编程C 语言诞生于 20 世纪 70 年代,凭借其简洁高效、可移植性强以及对硬件的直接操控能力,成为机器人编程领域的宠儿。机器人的运行环境往往对资源有着严格的限制,需要程序占用较少的内存和运行空间。C 语言具有出色
    Jeffreyzhang123 2025-01-02 16:26 133浏览
  • 国际标准IPC 标准:IPC-A-600:规定了印刷电路板制造过程中的质量要求和验收标准,涵盖材料、外观、尺寸、焊接、表面处理等方面。IPC-2221/2222:IPC-2221 提供了用于设计印刷电路板的一般原则和要求,IPC-2222 则针对高可靠性电子产品的设计提供了进一步的指导。IPC-6012:详细定义了刚性基板和柔性基板的要求,包括材料、工艺、尺寸、层次结构、特征等。IPC-4101:定义了印刷电路板的基板材料的物理和电气特性。IPC-7351:提供了元件封装的设计规范,包括封装尺寸
    Jeffreyzhang123 2025-01-02 16:50 162浏览
  • 本文继续介绍Linux系统查看硬件配置及常用调试命令,方便开发者快速了解开发板硬件信息及进行相关调试。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。查看系统版本信息查看操作系统版本信息root@ido:/# cat /etc/*releaseDISTRIB_ID=UbuntuDISTRIB_RELEASE=20.04DISTRIB_CODENAME=focalDIS
    Industio_触觉智能 2025-01-03 11:37 82浏览
  • 在测试XTS时会遇到修改产品属性、SElinux权限、等一些内容,修改源码再编译很费时。今天为大家介绍一个便捷的方法,让OpenHarmony通过挂载镜像来修改镜像内容!触觉智能Purple Pi OH鸿蒙开发板演示。搭载了瑞芯微RK3566四核处理器,树莓派卡片电脑设计,支持开源鸿蒙OpenHarmony3.2-5.0系统,适合鸿蒙开发入门学习。挂载镜像首先,将要修改内容的镜像传入虚拟机当中,并创建一个要挂载镜像的文件夹,如下图:之后通过挂载命令将system.img镜像挂载到sys
    Industio_触觉智能 2025-01-03 11:39 75浏览
  • 自动化已成为现代制造业的基石,而驱动隔离器作为关键组件,在提升效率、精度和可靠性方面起到了不可或缺的作用。随着工业技术不断革新,驱动隔离器正助力自动化生产设备适应新兴趋势,并推动行业未来的发展。本文将探讨自动化的核心趋势及驱动隔离器在其中的重要角色。自动化领域的新兴趋势智能工厂的崛起智能工厂已成为自动化生产的新标杆。通过结合物联网(IoT)、人工智能(AI)和机器学习(ML),智能工厂实现了实时监控和动态决策。驱动隔离器在其中至关重要,它确保了传感器、执行器和控制单元之间的信号完整性,同时提供高
    腾恩科技-彭工 2025-01-03 16:28 118浏览
  • 物联网(IoT)的快速发展彻底改变了从智能家居到工业自动化等各个行业。由于物联网系统需要高效、可靠且紧凑的组件来处理众多传感器、执行器和通信设备,国产固态继电器(SSR)已成为满足中国这些需求的关键解决方案。本文探讨了国产SSR如何满足物联网应用的需求,重点介绍了它们的优势、技术能力以及在现实场景中的应用。了解物联网中的固态继电器固态继电器是一种电子开关设备,它使用半导体而不是机械触点来控制负载。与传统的机械继电器不同,固态继电器具有以下优势:快速切换:确保精确快速的响应,这对于实时物联网系统至
    克里雅半导体科技 2025-01-03 16:11 113浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦