51单片机入门——矩阵按键

article/2025/1/17 13:49:41

文章目录

  • 1.按键
    • 1.1.独立按键
    • 1.2.矩阵按键
  • 2.按键的扫描与抖动
    • 2.1.独立按键的扫描
    • 2.2.按键抖动与消抖
    • 2.3.矩阵按键的扫描
  • 3.简易计算器

1.按键

1.1.独立按键

常用的按键电路有两种形式,独立式按键和矩阵式按键,独立式按键比较简单,它们各自与独立的输入线相连接。在这里插入图片描述
4 条输入线接到单片机的 IO 口上,当按键 K1 按下时,+5V 通过电阻 R1 然后再通过按键 K1 最终进入 GND 形成一条通路,那么这条线路的全部电压都加到了 R1 这个电阻上,KeyIn1 这个引脚就是个低电平。当松开按键后,线路断开,就不会有电流通过,那么 KeyIn1和+5V 就应该是等电位,是一个高电平。我们就可以通过 KeyIn1 这个 IO 口的高低电平来判断是否有按键按下。

这个电路中按键的原理我们清楚了,但是实际上在我们的单片机 IO 口内部,也有一个上拉电阻的存在。我们的按键是接到了 P2 口上,P2 口上电默认是准双向 IO 口,我们来简单了解一下这个准双向 IO 口的电路。在这里插入图片描述
方框内的电路都是指单片机内部部分,方框外的就是我们外接的上拉电阻和按键。

这个地方大家要注意一下,就是当我们要读取外部按键信号的时候,单片机必须先给该引脚写“1”,也就是高电平,这样我们才能正确读取到外部按键信号,我们来分析一下缘由。

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

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

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

从上面的分析就可以得出一个结论,这种具有上拉的准双向 IO 口,如果要正常读取外部信号的状态,必须首先得保证自己内部输出的是 1,如果内部输出 0,则无论外部信号是 1 还是 0,这个引脚读进来的都是 0。

1.2.矩阵按键

在某一个系统设计中,如果需要使用很多的按键时,做成独立按键会大量占用 IO 口,
因此我们引入了矩阵按键的设计。如下图所示,使用 8 个 IO 口来实现了 16 个按键。
在这里插入图片描述
如果独立按键理解了,矩阵按键也不难理解,那么我们一起来分析一下。图 8-8 中,一共有 4 组按键,我们只看其中一组。大家认真看一下,如果 out1 输出一个低电平,KeyOut1 就相当于是 GND,是否相当于 4 个独立按键呢。当然这时候 out2、out3、out4 都必须输出高电平,它们都输出高电平才能保证与它们相连的三路按键不会对这一路产生干扰。

2.按键的扫描与抖动

2.1.独立按键的扫描

原理搞清楚了,那么下面我们就先编写一个独立按键的程序,把最基本的功能验证一下。
原理图如下:
在这里插入图片描述

#include<reg52.h>sbit LED1 = P2^0 ;
sbit LED2 = P2^1 ;
sbit LED3 = P2^2 ;
sbit LED4 = P2^3 ;
sbit KEY1 = P3^0 ;
sbit KEY2 = P3^1 ;
sbit KEY3 = P3^2 ;
sbit KEY4 = P3^3 ;void main()
{P2 = 0 ; while(1){LED1 = ~KEY1 ;  //按下时为 0,对应的 LED 点亮LED2 = ~KEY2 ;LED3 = ~KEY3 ;LED4 = ~KEY4 ;}
}

当按键按下时,对应按键的输入引脚是 0,对应小灯控制信号也是 0,于是灯就亮了,这说明上述关于按键检测的理论都是可实现的。

绝大多数情况下,按键是不会一直按住的,所以我们通常检测按键的动作并不是检测一个固定的电平值,而是检测电平值的变化,即按键在按下和弹起这两种状态之间的变化,只要发生了这种变化就说明现在按键产生动作了。

程序上,我们可以把每次扫描到的按键状态都保存起来,当一次按键状态扫描进来的时候,与前一次的状态做比较,如果发现这两次按键状态不一致,就说明按键产生动作了。当上一次的状态是未按下而现在是按下,此时按键的动作就是“按下”;当上一次的状态是按下而现在是未按下,此时按键的动作就是“弹起”。显然,每次按键动作都会包含一次“按下”和一次“弹起”,我们可以任选其一来执行程序,或者两个都用,以执行不同的程序也是可以的。下面就用程序来实现这个功能,程序只取按键 4 为例。

我们先建立如下的原理图:
在这里插入图片描述

#include<reg52.h>sbit KEY1 = P3^0 ;
sbit KEY2 = P3^1 ;
sbit KEY3 = P3^2 ;
sbit KEY4 = P3^3 ;unsigned char code LedChar[] = { //共阳数码管显示字符转换表0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};void main()
{bit backup = 1; //定义一个位变量,保存前一次扫描的按键值unsigned char cnt = 0; //定义一个计数变量,记录按键按下的次数P0 = ~LedChar[cnt]; //显示按键次数初值while (1){if (KEY4 != backup) //当前值与前次值不相等说明此时按键有动作{if (backup == 0) //如果前次值为 0,则说明当前是由 0 变 1,即按键弹起{cnt++; //按键次数+1if (cnt >= 10){ //只用 1 个数码管显示,所以加到 10 就清零重新开始cnt = 0;}P0 = ~LedChar[cnt]; //计数值显示到数码管上}backup = KEY4; //更新备份为当前值,以备进行下次比较}}
}

先来介绍出现在程序中的一个新知识点,就是变量类型——bit,这个在标准 C 语言里边是没有的。51 单片机有一种特殊的变量类型就是 bit 型。比如 unsigned char 型是定义了一个无符号的 8 位的数据,它占用一个字节(Byte)的内存,而 bit 型是 1 位数据,只占用 1 个位(bit)的内存,用法和标准 C 中其他的基本数据类型是一致的。它的优点就是节省内存空间,8 个bit 型变量才相当于 1 个 char 型变量所占用的空间。虽然它只有 0 和 1 两个值,但也已经可以表示很多东西了,比如:按键的按下和弹起、LED 灯的亮和灭、三极管的导通与关断等等,联想一下已经学过的内容,它是不是能用最小的内存代价来完成很多工作呢?

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

2.2.按键抖动与消抖

虽然这个问题在《51单片机入门——数码管》的后半部分有讲到过,在此还是在说一下。

通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动。如下图:在这里插入图片描述
按键稳定闭合时间长短是由操作人员决定的,通常都会在 100ms 以上,刻意快速按的话能达到 40-50ms 左右,很难再低了。抖动时间是由按键的机械特性决定的,一般都会在 10ms以内,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。当检测到按键状态变化时,不是立即去响应动作,而是先等待闭合或断开稳定后再进行处理。按键消抖可分为硬件消抖和软件消抖。

硬件消抖就是在按键上并联一个电容,如下图所示:在这里插入图片描述

利用电容的充放电特性来对抖动过程中产生的电压毛刺进行平滑处理,从而实现消抖。但实际应用中,这种方式的效果往往不是很好,而且还增加了成本和电路复杂度,所以实际中使用的并不多(基本用于单片机的复位按键)。

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

#include<reg52.h>sbit KEY1 = P3^0 ;
sbit KEY2 = P3^1 ;
sbit KEY3 = P3^2 ;
sbit KEY4 = P3^3 ;unsigned char code LedChar[] = { //共阳数码管显示字符转换表0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};void delay();void main()
{bit backup = 1; //定义一个位变量,保存前一次扫描的按键值unsigned char cnt = 0; //定义一个计数变量,记录按键按下的次数P0 = ~LedChar[cnt]; //显示按键次数初值while (1){if (KEY4 != backup) //当前值与前次值不相等说明此时按键有动作{delay(10); // 延时大约10msif (backup == 0) //如果前次值为 0,则说明当前是由 0 变 1,即按键弹起{cnt++; //按键次数+1if (cnt >= 10){ //只用 1 个数码管显示,所以加到 10 就清零重新开始cnt = 0;}P0 = ~LedChar[cnt]; //计数值显示到数码管上}backup = KEY4; //更新备份为当前值,以备进行下次比较}}
}void delay(unsigned char n){unsigned char i j;for(i = 0 ; i < 123 ; i ++){for(j = 0 ; j < 123 ; j ++) ;}}

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

在此我给出两种解决办法:

1.添加一个变量

void key(){if(key0 == 0 && a == 0){a = 1;}if(a == 1 && key0 == 1){a = 0;............. // 需要执行的代码}}

2.我们启用一个定时中断,每 2ms 进一次中断,扫描一次按键状态并且存储起来,连续扫描 8 次后,看看这连续 8 次的按键状态是否是一致的。

8 次按键的时间大概是 16ms,这 16ms 内如果按键状态一直保持一致,那就可以确定现在按
键处于稳定的阶段,而非处于抖动的阶段,如下图:
在这里插入图片描述
假如左边时间是起始 0 时刻,每经过 2ms 左移一次,每移动一次,判断当前连续的 8 次按键状态是不是全 1 或者全 0,如果是全 1 则判定为弹起,如果是全 0 则判定为按下,如果0 和 1 交错,就认为是抖动,不做任何判定。

#include<reg52.h>sbit KEY1 = P3^0 ;
sbit KEY2 = P3^1 ;
sbit KEY3 = P3^2 ;
sbit KEY4 = P3^3 ;unsigned char code LedChar[] = { //共阳数码管显示字符转换表0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};void main()
{bit backup = 1; //定义一个位变量,保存前一次扫描的按键值unsigned char cnt = 0; //定义一个计数变量,记录按键按下的次数P0 = ~LedChar[cnt]; //显示按键次数初值TMOD = 0x01; //设置 T0 为模式 1TH0 = 0xF8; //为 T0 赋初值 0xF8CD,定时 2msTL0 = 0xCD;ET0 = 1; //使能 T0 中断EA = 1; //使能总中断while (1){if (KeySta != backup) //当前值与前次值不相等说明此时按键有动作{if (backup == 0) //如果前次值为 0,则说明当前是由 0 变 1,即按键弹起{cnt++; //按键次数+1if (cnt >= 10){ //只用 1 个数码管显示,所以加到 10 就清零重新开始cnt = 0;}P0 = ~LedChar[cnt]; //计数值显示到数码管上}backup = KeySta; //更新备份为当前值,以备进行下次比较}}
}/* T0 中断服务函数,用于按键状态的扫描并消抖 */
void InterruptTimer0() interrupt 1
{static unsigned char keybuf = 0xFF; //扫描缓冲区,保存一段时间内的扫描值TH0 = 0xF8; //重新加载初值TL0 = 0xCD;keybuf = (keybuf<<1) | KEY4; //缓冲区左移一位,并将当前扫描值移入最低位if (keybuf == 0x00){ //连续 8 次扫描值都为 0,即 16ms 内都只检测到按下状态时,可认为按键已按下KeySta = 0;}else if (keybuf == 0xFF){ //连续 8 次扫描值都为 1,即 16ms 内都只检测到弹起状态时,可认为按键已弹起KeySta = 1;}else{} //其它情况则说明按键状态尚未稳定,则不对 KeySta 变量值进行更新
}

这个算法是我们在实际工程中经常使用按键所总结的一个比较好的方法,介绍给大家,今后都可以用这种方法消抖了。当然,按键消抖也还有其它的方法,程序实现更是多种多样,大家也可以再多考虑下其它的算法,拓展下思路。

2.3.矩阵按键的扫描

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

前边讲过,按键按下通常都会保持 100ms 以上,如果在按键扫描中断中,我们每次让矩阵按键的一个 out 输出低电平,其它三个输出高电平,判断当前所有 Int 的状态,下次中断时再让下一个out 输出低电平,其它三个输出高电平,再次判断所有 Int,通过快速的中断不停的循环进行判断,就可以最终确定哪个按键按下了,这个原理是不是跟数码管动态扫描有点类似?数码管我们在动态赋值,而按键这里我们在动态读取状态。至于扫描间隔时间和消抖时间,因为现在有 4 个 out 输出,要中断 4 次才能完成一次全部按键的扫描,显然再采用 2ms 中断判断 8 次扫描值的方式时间就太长(248=64ms),那么我们就改用 1ms 中断判断 4 次采样值,这样消抖时间还 16ms(144)。下面就用程序实现出来,程序循环扫描板子上的 K1~K16 这 16 个矩阵按键,分离出按键动作并在按键按下时把当前按键的编号显示在一位数码管上(用 0~F 表示,显示值=按键编号 -1)。
在这里插入图片描述

#include<reg52.h>typedef unsigned char uchar ;
typedef unsigned int uint ;
typedef unsigned long ulong ;sbit out1 = P2^7 ;
sbit out2 = P2^6 ;
sbit out3 = P2^5 ;
sbit out4 = P2^4 ;
sbit int1 = P2^3 ;
sbit int2 = P2^2 ;
sbit int3 = P2^1 ;
sbit int4 = P2^0 ;uchar ledChar[] = {//共阳极数码管0~F0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};uchar keySta[4][4] = {//矩阵按键的当前状态 1为高电平 ,0为低电平{1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} 	
};void KeyDriver() ;void main()
{EA = 1 ;TMOD = 0x01 ;TH0 = 0xfc ;TL0 = 0x67 ;ET0 = 1 ;TR0 = 1 ;P0 = ~ledChar[0] ;while(1){KeyDriver() ;	}
}/* 按键驱动函数,检测按键动作,调度相应动作函数*/
void KeyDriver()
{uchar i , j ;static char backup[4][4] = {{1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1}} ;for (i = 0 ; i < 4 ; i ++){for (j = 0 ; j < 4 ; j ++){if (keySta[i][j] != backup[i][j]){if (backup[i][j] != 0){P0 = ~ledChar[i*4+j]; //将编号显示到数码管}backup[i][j] = keySta[i][j] ;}}}
}/* 按键扫描函数 */
void KeyScan()
{uchar i ;static uchar keyout = 0 ;static uchar keybuf[4][4] = {{0xff , 0xff , 0xff , 0xff} , {0xff , 0xff , 0xff , 0xff} , {0xff , 0xff , 0xff , 0xff} , {0xff , 0xff , 0xff , 0xff} } ;keybuf[keyout][0] = (keybuf[keyout][0] << 1) | int1 ;keybuf[keyout][1] = (keybuf[keyout][1] << 1) | int2 ;keybuf[keyout][2] = (keybuf[keyout][2] << 1) | int3 ;keybuf[keyout][3] = (keybuf[keyout][3] << 1) | int4 ; for (i = 0 ; i < 4 ; i ++){if ((keybuf[keyout][i] & 0x0f) == 0x00){keySta[keyout][i] = 0 ;}else if ((keybuf[keyout][i] & 0x0f) == 0x0f){keySta[keyout][i] = 1 ;}}keyout ++ ;keyout = keyout & 0x03 ;switch(keyout){case 0: out4 = 1 ; out1 = 0 ; break ;case 1: out1 = 1 ; out2 = 0 ; break ;case 2: out2 = 1 ; out3 = 0 ; break ;case 3: out3 = 1 ; out4 = 0 ; break ;default: break ;}
}/* T0 中断服务函数,用于按键状态的扫描并消抖 */
void InterruptTimer0() interrupt 1
{TH0 = 0xfc ;TL0 = 0x67 ;KeyScan() ;
}

这个程序完成了矩阵按键的扫描、消抖、动作分离的全部内容,希望大家认真研究一下,彻底掌握矩阵按键的原理和应用方法。在程序中还有两点值得说明一下。

首先,可能你已经发现了,中断函数中扫描 Int 输入和切换 out 输出的顺序与前面提到的顺序不同,程序中我首先对所有的 int 输入做了扫描、消抖,然后才切换到了下一次的 out 输出,也就是说我们中断每次扫描的实际是上一次输出选择的那行按键,这是为什么呢?因为任何信号从输出到稳定都需要一个时间,有时它足够快而有时却不够快,这取决于具体的电路设计,我们这里的输入输出顺序的颠倒就是为了让输出信号有足够的时间(一次中断间隔)来稳定,并有足够的时间来完成它对输入的影响,当你的按键电路中还有硬件电容消抖时,这样处理就是绝对必要的了,虽然这样使得程序理解起来有点绕,但它的适应性是最好的,换个说法就是,这段程序足够“健壮”,足以应对各种恶劣情况。

其次,是一点小小的编程技巧。注意看 keyout = keyout & 0x03;这一行,在这里我是要让keyout 在 0~3 之间变化,加到 4 就自动归零,按照常规你可以用前面讲过的 if 语句轻松实现,但是你现在看一下这样程序是不是同样可以做到这一点呢?因为 0、1、2、3 这四个数值正好占用 2 个二进制的位,所以我们把一个字节的高 6 位一直清零的话,这个字节的值自然就是一种到 4 归零的效果了。看一下,这样一句代码比 if 语句要更为简洁吧,而效果完全一样。

3.简易计算器

学到这里,我们已经掌握了一种显示设备和一种输入设备的使用,那么是不是可以来做点综合性的实验了。好吧,那我们就来做一个简易的加法计算器,用程序实现从板子上标有0~9 数字的按键输入相应数字,该数字要实时显示到数码管上,用标有向上箭头的按键代替加号,用标有向左箭头的按键代替减号,用标有向下箭头的按键代替乘号,用标有向右箭头的按键代替除号。虽然这远不是一个完善的计算器程序,但作为初学者也足够你研究一阵子了。

首先,本程序相对于之前的例程要复杂得多,需要完成的工作也多得多,所以我们把各个子功能都做成独立的函数,以使程序便于编写和维护。大家分析程序的时候就从主函数和中断函数入手,随着程序的流程进行就可以了。大家可以体会体会划分函数的好处,想想如果还是只有主函数和中断函数来实现的话程序会是什么样子。

其次,大家可以看到我们再把矩阵按键扫描分离出动作以后,并没有直接使用行列数所组成的数值作为分支判断执行动作的依据,而是把抽象的行列数转换为了一种叫做标准键盘键码(就是电脑键盘的编码)的数据,然后用得到的这个数据作为下一步分支判断执行动作的依据,为什么多此一举呢?有两层含义:第一,尽量让自己设计的东西(包括硬件和软件)向已有的行业规范或标准看齐,这样有助于别人理解认可你的设计,也有助于你的设计与别人的设计相对接,毕竟标准就是为此而生的嘛。第二,有助于程序的层次化而方便维护与移植,比如我们现在用的按键是 44 的,但如果后续又增加了一行成了 45 的,那么由行列数组成的编号可能就变了,我们就要在程序的各个分支中查找修改,稍不留神就会出错,而采用这种转换后,我们则只需要维护 keyCodeMap 这样一个数组表格就行了,看上去就像是把程序的底层驱动与应用层的功能实现函数分离开了,应用层不用关心底层的实现细节,底层改变后也无需在应用层中做相应修改,两层程序之间是一种标准化的接口。这就是程序的层次化,而层次化是构建复杂系统的必备条件,那么现在就先通过简单的示例来学习一下吧。
在这里插入图片描述

#include<reg52.h>typedef unsigned char uchar ;
typedef unsigned int uint ;
typedef unsigned long ulong ;sbit out1 = P2^0 ;
sbit out2 = P2^1 ;
sbit out3 = P2^2 ;
sbit out4 = P2^3 ;
sbit int1 = P2^4 ;
sbit int2 = P2^5 ;
sbit int3 = P2^6 ;
sbit int4 = P2^7 ;uchar ledChar[] = {//共阳极数码管0~F0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};uchar ledBuff[8] = {// 数码管显示缓冲区0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 
} ;uchar keySta[4][4] = {//矩阵按键的当前状态 1为高电平 ,0为低电平{1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} 	
};uchar code keyCodeMap[4][4] = {// 矩阵按键编号到标准键盘键码的映射表{0x31 , 0x32 , 0x33 , 0x26} , // 数字键1、数字键2、数字键3、向上键{0x34 , 0x35 , 0x36 , 0x25} , // 数字键4、数字键5、数字键6、向左键{0x37 , 0x38 , 0x39 , 0x28} , // 数字键7、数字键8、数字键9、向下键{0x30 , 0x1b , 0x0d , 0x27}	  // 数字键0、ESC键、回车键、向右键
} ;void KeyDriver() ;void main()
{EA = 1 ;TMOD = 0x01 ;TH0 = 0xfc ;TL0 = 0x67 ;ET0 = 1 ;TR0 = 1 ;ledBuff[0] = ~ledChar[0] ;while(1){KeyDriver() ;	}
}/* 将一个无符号长整型数字显示数码管上,num-待显示数字 */
void ShowNumber(ulong num)
{signed char i ;uchar buf[8] ;for (i = 0 ; i < 8 ; i ++){buf[i] = num % 10 ;num = num / 10 ;}for (i = 7 ; i >= 1 ; i --){if(buf[i] == 0){ledBuff[i] = 0x00 ;}else break ;}for ( ; i >= 0 ; i --){ledBuff[i] = ~ledChar[buf[i]] ;}
}/* 按键动作函数,根据键码执行相应的操作 , keyCode-按键键码 */
void KeyAction(uchar keyCode)
{static ulong result = 0 ; //用于保存运算结果static ulong addend = 0 ; //用于保存输入的加数static ulong subtract = 0 ; // 用于保存输入的减数static ulong multiplication = 1 ; //用于保存输入的乘数static ulong division = 1 ; // 用于保存输入的除数static uchar suspend = 0 ; // 用于判断使用哪种法则if ((keyCode >= 0x30) && (keyCode <= 0x39)){result = (result * 10) + (keyCode - 0x30) ;ShowNumber(result) ;}else if (keyCode == 0x26) //加法运算{addend = result ;result = 0 ;suspend = 1 ;ShowNumber(addend) ;}else if (keyCode == 0x25) //减法运算{subtract = result ;result = 0 ;suspend = 2 ;ShowNumber(subtract) ;	}else if (keyCode == 0x28) //乘法运算{multiplication = result ;result = 0 ;suspend = 3 ;ShowNumber(multiplication) ;	}else if (keyCode == 0x27) //除法运算{division = result ;result = 0 ;suspend = 4 ;ShowNumber(division) ;}else if (keyCode == 0x0d){if (suspend == 1){result = addend + result ;addend = 0 ;	}else if (suspend == 2){if (subtract > result){result = subtract - result ;	}else {result =result - subtract ;	}}else if (suspend == 3){result = multiplication * result ;	}else if (suspend == 4){result = division / result ;	}ShowNumber(result) ;}else if (keyCode == 0x1b){result = 0 ; addend = 0 ; subtract = 0 ; multiplication = 1 ;division = 1 ;ShowNumber(result) ;}
}/* 按键驱动函数,检测按键动作,调度相应动作函数*/
void KeyDriver()
{uchar i , j ;static char backup[4][4] = {{1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1} , {1 , 1 , 1 , 1}} ;for (i = 0 ; i < 4 ; i ++){for (j = 0 ; j < 4 ; j ++){if (keySta[i][j] != backup[i][j]){if (backup[i][j] != 0){KeyAction(keyCodeMap[i][j]) ;}backup[i][j] = keySta[i][j] ;}}}
}/* 按键扫描函数 */
void KeyScan()
{uchar i ;static uchar keyout = 0 ;static uchar keybuf[4][4] = {{0xff , 0xff , 0xff , 0xff} , {0xff , 0xff , 0xff , 0xff} , {0xff , 0xff , 0xff , 0xff} , {0xff , 0xff , 0xff , 0xff} } ;keybuf[keyout][0] = (keybuf[keyout][0] << 1) | int1 ;keybuf[keyout][1] = (keybuf[keyout][1] << 1) | int2 ;keybuf[keyout][2] = (keybuf[keyout][2] << 1) | int3 ;keybuf[keyout][3] = (keybuf[keyout][3] << 1) | int4 ; for (i = 0 ; i < 4 ; i ++){if ((keybuf[keyout][i] & 0x0f) == 0x00){keySta[keyout][i] = 0 ;}else if ((keybuf[keyout][i] & 0x0f) == 0x0f){keySta[keyout][i] = 1 ;}}keyout ++ ;keyout = keyout & 0x03 ;switch(keyout){case 0: out4 = 1 ; out1 = 0 ; break ;case 1: out1 = 1 ; out2 = 0 ; break ;case 2: out2 = 1 ; out3 = 0 ; break ;case 3: out3 = 1 ; out4 = 0 ; break ;default: break ;}
}/* 数码管扫描函数 */
void LedScan()
{static uchar i = 0 ;switch (i){case 0: P3 = 0x7f ; i ++ ; P0 = ledBuff[0] ; break ;case 1: P3 = 0xbf ; i ++ ; P0 = ledBuff[1] ; break ;case 2: P3 = 0xdf ; i ++ ; P0 = ledBuff[2] ; break ;case 3: P3 = 0xef ; i ++ ; P0 = ledBuff[3] ; break ;case 4: P3 = 0xf7 ; i ++ ; P0 = ledBuff[4] ; break ;case 5: P3 = 0xfb ; i ++ ; P0 = ledBuff[5] ; break ;case 6: P3 = 0xfd ; i ++ ; P0 = ledBuff[6] ; break ;case 7: P3 = 0xfe ; i = 0 ; P0 = ledBuff[7] ; break ;default: break ;}
}void InterruptTimer0() interrupt 1
{TH0 = 0xfc ;TL0 = 0x67 ;KeyScan() ;LedScan() ;
}

http://chatgpt.dhexx.cn/article/UJvb1pSK.shtml

相关文章

mac怎么给移动硬盘分区

我们经常用Windows系统给移动硬盘分区&#xff0c;对于Windows系统的操作我们是非常熟悉的&#xff0c;但是现在很多人都在使用苹果电脑&#xff0c;那么如何用Mac给硬盘分区呢&#xff1f;这个可是需要技术的。如果我们不会用Mac系统给移动硬盘分区&#xff0c;当我们在Mac上插…

ntfs格式的移动硬盘如何在mac电脑写入?

随着照片视频等技术的进步&#xff0c;这类文件越发清晰的同时&#xff0c;占用内存也越来越大&#xff0c;人们逐渐将其中不常用到的存入移动硬盘中&#xff0c;以此减少电脑空间占用。目前国内大多数移动硬盘均为Windows自研的NTFS格式&#xff0c;对于这类NTFS格式的移动硬盘…

mac怎么删除硬盘里面的东西?为什么苹果电脑无法删除移动硬盘文件?

mac怎么删除硬盘里面的东西&#xff1f;由于移动硬盘的文件系统是NTFS格式的&#xff0c;而这种格式与Mac电脑是不兼容的&#xff0c;Mac电脑没有权限对移动硬盘上的数据进行操作&#xff0c;Mac上不能把移动硬盘的数据删除了&#xff0c;那么&#xff0c;有没有什么操作方法&a…

苹果电脑怎么用移动硬盘ntfs?快速读取和编辑Mac外置移动硬盘

苹果电脑怎么用移动硬盘ntfs&#xff1f;如果你对电脑比较熟悉的话&#xff0c;可能知道NTFS磁盘格式。该格式专门为Windows系统设计的&#xff0c;也称为Windows NT文件系统。从Windows系统迁移到Mac可能是一项相当困难的任务&#xff0c;因为NTFS格式的硬盘在Mac上不能正常工…

如何在Mac上给移动硬盘加密

随着人们的安全意识越来越强&#xff0c;手机加密、Wi-Fi加密&#xff0c;甚至平时工作的U盘、移动硬盘也要加密。在Mac上对移动硬盘加密并不是很难&#xff0c;通过磁盘工具对硬盘进行格式化处理&#xff0c;并重新选择磁盘格式便可实现加密。下面就让我们来看具体的操作吧&am…

mac无法在移动硬盘上新建文件夹 mac如何在移动硬盘新建文件夹

明明是新买的硬盘&#xff0c;为什么把移动硬盘插入Mac后&#xff0c;Mac无法在移动硬盘上新建文件夹呢&#xff1f;相信很多Mac用户都曾遇到过Mac无法在移动硬盘上新建文件夹的问题。为什么会出现这样的问题呢&#xff1f;用户应该怎么解决呢&#xff1f;本文就来为大家介绍ma…

Mac无法写入移动硬盘 这些软件帮你解决

疫情期间因工作需要&#xff0c;从淘宝上买了一个移动硬盘&#xff0c;将公司的文件拷贝到这个移动硬盘上&#xff0c;并没有发现什么问题&#xff0c;一拿回到家里就懵圈了&#xff0c;突然发现里面的文件都无法进行修改或剪切粘贴。 一番百度之后&#xff0c;我找到了原因&a…

Mac如何拷贝文件到移动硬盘

haha~ 看到这个标题不要以为博主这是傻了: “切, 往移动硬盘拷文件谁不会啊~”, “又在故弄玄虚, 就这也写博客~” … 确实, 我要把这个写成博客了. 因为今天要拷贝MacOS Sierra安装包到另外一台电脑上, 安装包有4.7个G, U盘果断不行, 于是换上移动硬盘, 连上电脑, 将安装包拖入…

Mac下如何把iphone资料备份到移动硬盘

一般的备份方法 首先新系统的iphone备份已经转到了访达 连接iphone后在访达左边栏会看到你的手机 在终端输入下面这段命令行 ln -s /Volumes/Data/ios_backup &#xff5e;/Library/Application\ Support/MobileSync/Backup /Volumes/Data/ios_backup 这行是你想要指定的目…

怎样将iphone照片通过MAC导入移动硬盘?

iMazing是一款第三方的苹果iOS设备管理软件&#xff0c;大家使用数据线或Wi-Fi将苹果设备与电脑进行连接以后&#xff0c;就可以用它进行音乐传输、照片传输和数据备份等操作。 它支持Windows系统和Mac系统&#xff0c;下面通过一篇教程&#xff0c;教大家如何通过Mac系统的iM…

mac怎么用ntfs硬盘 NTFS移动硬盘怎么在mac上使用

品牌型号:MacBook Air 系统:macOS 10.13 软件版本:Tuxera Ntfs for mac 2020 初次接触到Mac电脑的用户&#xff0c;会发现自己的移动硬盘或U盘连接到电脑后仅有只读权限&#xff0c;并不能对其写入数据。这和移动硬盘格式有着密切的关系&#xff0c;NTFS格式在Mac电脑上并不…

mac不识别移动硬盘导致无法拷贝资源

背景 硬盘插入到Mac电脑上之后&#xff0c;mac不识别移动硬盘导致无法拷贝资源。 移动硬盘在Mac上无法被识别的原因可能有很多&#xff0c;多数情况下&#xff0c;是硬盘的格式与Mac电脑不兼容。 文件系统格式不兼容 macOS使用的文件系统是HFS或APFS&#xff0c;如果移动硬盘是…

Mac不能复制拷贝写入文件到移动硬盘/U盘解决办法

1.有的小伙伴把移动硬盘或 U 盘接入到 Mac 电脑上&#xff0c;当把文件拷贝到移动硬盘时&#xff0c;会发现不能复制文件到移动硬盘。这里因为移动硬盘或 U 盘是使用 Windows 系统下的 NTFS 分区格式&#xff0c;而 Mac 系统原生是不支持这种格式的&#xff0c;也就是为什么不能…

windows移动硬盘接到mac上使用(加载ntfs移动硬盘)

也就是将NTFS格式的移动硬盘&#xff0c;连接到mac上进行存储。 目录 步骤如下&#xff1a;1、接入硬盘&#xff0c;查看Device Node2、推出硬盘&#xff0c;一般桌面就有对应图标&#xff0c;右键推出就行3、建个目录&#xff0c;然后把硬盘挂上去就行4、解挂载硬盘 步骤如下…

ntfs硬盘如何在mac上读写移动硬盘文件?

在日常的工作中&#xff0c;总是避免不了跨平台的传输文件、文件共享等&#xff0c;例如一些用户使用Mac电脑修图或者剪辑视频之后需要拷贝到Windows电脑上查看。对于需要同时使用Mac和Windows的用户来说&#xff0c;系统之间不兼容是很大的阻碍&#xff0c;尤其是使用NTFS移动…

Mac 移动硬盘没有推出,再插上不显示移动硬盘解决办法

1.写在前面 你是否遇到移动硬盘读不出来的情况呢&#xff1f; 我们或多或少都有外接存储设备&#xff0c;一般电脑买的存储内存小&#xff0c;或者有拷贝资料需要的时候&#xff0c;我们都需要外接一个移动硬盘。 而作为 iOS 开发&#xff0c;使用的是 Mac 电脑&#xff0c;在…

如何让移动硬盘在Mac和Windows上通用使用

刚入手了一块新的移动硬盘&#xff0c;Mac电脑插上却发现一片空白无法使用&#xff0c;这是什么情况呢&#xff1f; 原来一般一块新的大容量移动硬盘刚入手时&#xff0c;默认是NTFS格式&#xff0c;这是Windows的一种特有硬盘格式&#xff0c;但是Mac上只能读取不能写入。 Ma…

如何在苹果电脑复制文件到移动硬盘或U盘 MAC系统读写NTFS

现在用苹果电脑的用户越来越多&#xff0c;在使用的过程中我们免不了会使用一些硬盘设备来存储文件或者是数据&#xff0c;然而绝大多数的移动硬盘都是在windows系统下创建的ntfs格式。但ntfs格式在苹果Mac OS X 10.3系统后才对NTFS格式只支持读写&#xff0c;不能写入。 就是…

Mac下将文件复制到移动硬盘

现象分析&#xff1a; 如果你在使用Mac系统时&#xff0c;发现Mac系统和移动硬盘之间无法拷贝数据&#xff0c;很有可能你的移动硬盘是NTFS格式的&#xff0c;因为目前苹果系统的硬盘格式暂时不兼容这样的格式拷贝&#xff0c;只能从NTFS格式拷贝到Mac&#xff0c;而不能从Mac…

苹果电脑的文件怎样拷贝入移动硬盘里

这两天有点烦&#xff0c;新购Mac的喜悦心情全没了&#xff0c;恨不得把它人道毁灭——电脑里的文件竟然不能拷贝到移动硬盘上&#xff0c;这是什么烂系统。 后来用Mac里的磁盘工具把移动硬盘格式化后&#xff0c;Mac上的文件能拷贝到移动硬盘里了。 可是把移动硬盘接回PC上&a…