【单片机】07按键

独立按键控制流水灯

我们先讲解一下上次布置给大家的双向流水灯作业。该实验的现象很明确就是将我们已经做过的循环左移流水效果和循环右移流水效果重合起来,双向流水代码如下:

#include <reg51.h>
#include <intrins.h>
void main()
{
	unsigned char x,y;
	unsigned int i;
	x=0xFE;         
	y=0x7F;         
	while(1)
	{
		P1=x & y;            
		for(i=0;i<=10000;i++);  //延时
		x=_crol_(x, 1);     //循环左移1位
		y=_cror_(y, 1);     //循环右移1位
	}
}

​ 本节实验内容是:利用开发板上的S1、S2、S3、S4按键来控制流水灯效果,若按下S1,8个LED呈现从左向右单向流水,若按下S2,8个LED呈现从右向左单向流水,若按下S3,8个LED呈现呈现双向流水效果,若按下S4,关闭8个LED。本实验的关键是单片机如何判断哪个按键按下?我们直接介绍过I/O接口,称为输入/输出接口,顾名思义它既有输出功能,也有输入功能,之前我们做的实验都体现了它的输出功能,我们现在介绍一下它
的输入功能。
​ 51单片机的4个I/O口具有输入和输出输出功能,但是在同一时刻只能有一个功能被使用,所以用之前要把引脚设定为你需要的功能。作输出功能时,不用管它,直接用就行了,例如我们上面的实验,都是直接使用,不做任何设置;通常作输入功能使用时,要先将对应的寄存器位置为1,但单片机上电后,默认的是置1的,只要你不是先做出其它功能用,再做输入,就不用置1了。但为保险起见,多置一次1,没有关系。至于为什么要这样设置,就必须知道I/O接口电路原理,其实这些原理我们并不需要去深入理解,不需要把内部的工作原理及电路都完全弄懂,我们是要用单片机不是设计单片机芯片的,所以我们只需要会用就行了。
​ 将对应I/O接口设置为输入功能后,I/O引脚上的输入电平若为高电平,对应寄存器的对应位则为1,否则为0,也就是CPU可以通过读取I/O寄存器的值,判断对应I/O引脚输入的高电平还是低电平。回到我们这次实验上来,我们知道做单片机实验要从硬件和软件两个方面去考虑。本实验硬件上需要8个LED,4个按键和一个单片机,8个LED灯如何接到单片机上我们在之前的实验中都已经掌握,下面我们分析4个按键怎么和单片机连接,我们首先分析一下按键电路图,如图7.1所示,本实验只用了S1、S2、S3、S4,我们就先只分析这四个按键。从图中可以看出这四个按键一端都接到了J9最下面的插针上,我们用一根导线将这个插针接到开发板的GND上,另一端分别接到了J9上面的四个插针上,我们可以将这四个插针接到单片机I/O引脚上,由于P1口已用于控制LED灯,我们可以选用P0、P2、P3口,开发板上P3口插针刚好在J9插针的右两列,为了方便接线,我们选用P3口,用跳线帽将四个按键的另一端插针分别接到P3插针的P30、P31、P32、P33上,本实验全部的接线如图7.2所示。
image-20230329123240848
image-20230329123409024

接下来我们以S1为例来分析硬件电平的变化,当我们不按S1时,S1处于断开状态,与S1右端相连的P3口的第0脚的电平为I/O默认的高电平,与之相应的P0寄存器的第0位就为1,当按下S1,S1左右短接通,左端我们接到了GND上,那右端电平也变为低电平,与右端相连,P30也变为低电平,与之相应的P0寄存器的第0位就为0。这样我们得出一个结论:单片机的P3寄存器的第0位若为1表示按键没按下,若为0表示按键按下。也就是软件上我要判断按键是按下还是没按下,我们可以用C语言中的判断语句判断P3寄存器的第0位为0还是1就行了。
下面大家自行完成本实验的软件部分。(结合大家完成的情况作出讲解)
讲解本实验的代码:

#include <reg51.h>
#include <intrins.h>
void main()
{
	unsigned char x,y,r,z,key=0x0f;
	unsigned int i;
	x=0xFE;         //做流水初始化
	y=0x7F;         //右流水初始化
	z=0xfe;         //双向流水初始化
	r=0x7f;
	while(1)
	{
		if((P3 & 0x0f) !=0x0f)
		{
			key=P3 & 0x0f;
		}
		if(key==0x0e)      //判断按下的是否为S1
		{
			P1=z;
			for(i=0;i<=10000;i++);       //延时
			z=_crol_(z,1);      //循环左移一位
		}
		else if (key==0x0d)          //判断按下的是否为S2
		{
			P1=r;
			for(i=0;i<=10000;i++);       //延时
			r=_cror_(r,1);      //循环右移一位
		}
		else if (key==0x0b)          //判断按下的是否为S3
		{
			P1=x & y;            
			for(i=0;i<=10000;i++);  //延时
			x=_crol_(x,1);     
			y=_cror_(y,1);     
		}
		else
			P1=0XFF;
	}
}

key7接地
image-20230329161534917

key0,key1,key2,key3是1。key7接地是0。只有按下按钮的时候,才会改变key0,key1,key2,key3,即P30,P31,P32,P33(会变成0)

image-20230329162550615

一个按钮控制流水灯

使用开发板上的一个按键S1,控制LED灯的流水效果。代码如下:

#include <reg51.h>
#include <intrins.h>
sbit S1=P3^0;
void main()
{
	unsigned char x,y,r,z,times=4;
	int i;
	x=0xFE;         //做流水初始化
	y=0x7F;         //右流水初始化
	z=0xfe;         //双向流水初始化
	r=0x7f;
	while(1)
	{  
		if(S1==0)
		{
			times++;
			if(times==5)
				times=1;
		}

		if(times==1)      //判断是否为第一次按下
		{
			P1=z;
			for(i=0;i<=10000;i++);       //延时
			z=_crol_(z,1);      //循环左移一位
		}
		else if (times==2)          ///判断是否为第二次按下
		{
			P1=r;
			for(i=0;i<=10000;i++);       //延时
			r=_cror_(r,1);      //循环右移一位
		}
		else if (times==3)          ///判断是否为第三次按下
		{
			P1=x & y;            
			for(i=0;i<=10000;i++);  //延时
			x=_crol_(x,1);     
			y=_cror_(y,1);     
		}
		else if(times==4)
		{	P1=0XFF;	}
	}
}

这段代码看上去很合理,定义一个记录按键按下次数的变量times,times四个取值1、2、3和4,分别对应于向右流水、向左流水、双向流水和关闭状态。这段代码里给times赋了一个初值4,表示LED在开始时为熄灭状态。在主循环的开始CPU先判断按键有没有按下,也就是判断S1是否等于0,若按下就将times加1,然后再判断times的值决定灯的显示效果。这段代码看似没问题,现在大家将这段代码下载到开发板上,大家看看运行现象怎么样。
我们来一起分析一下实验现象。程序运行开始,LED处于熄灭状态,当我们按下S1,LED灯理论上应该是向右流水,但我们发现不一定是这个现象,有时是向右流水,有时是双向流水,有时看不出什么现象。这是为什么呢?因为当我们按下S1,CPU在主循环里第一次判断到S1按下,times累加1,然后根据times值执行一种灯的流水效果,CPU在执行一次灯的流水效果语句时间很短,执行完,又循环上去判断S1,这时我们的手还没来得及放开,CPU又判断了一次S1按下,times又一次累加1,然后下面就执行另一种流水效果,所以就出现了按下一次S1,CPU判断了很多次,造成流水效果错位的现象。要想解决这个问题,我们就需要在给times累加的条件改为CPU首次判断S1按下,如果下次判断S1还是按下的状态,则S1不累加,直到按键抬起再次按下的首次判断times才再累加。那我们怎么判断是首次按下?首次按下包含两个条件:一是首次,二是按下。按下的条件我们只要判断S1是否为0,那怎么知道是首次呢?我们可以定义一个标记变量flag,当没有按键按下,flag赋为0,当我们判断出S1按下,并且flag为0的情况下,那就表示是首次按下,只要是按下,flag就置为1,表示这次按键已经处理过,在flag为1的情况下再次判断按键按下,就不做任何处理。下面大家根据上面的讲解自行改善本实验的软件部分。(30分钟学生实践,教师巡视)
讲解本实验的代码:

#include <reg51.h>
#include <intrins.h>
sbit S1=P3^0;
void main()
{
	unsigned char x,y,r,z,times=4,flag=0;
	int i;
	x=0xFE;         //做流水初始化
	y=0x7F;         //右流水初始化
	z=0xfe;         //双向流水初始化
	r=0x7f;
	while(1)
	{  
		if(S1==0)
		{
			if(flag==0)   //判断是否为首次按下
			{
				flag=1;   //只要是按下,flag就置为1
				times++;
				if(times==5)
					times=1;
			}
		}
		else
			flag=0;      //若没有按键按下,将flag清为0
		if(times==1)      //判断是否第一次S1按下
		{
			P1=z;
			for(i=0;i<=10000;i++);       //延时
			z=_crol_(z,1);      //循环左移一位
		}
		else if (times==2)        
		//判断是否第二次S1按下
		{
			P1=r;
			for(i=0;i<=10000;i++);       //延时
			r=_cror_(r,1);      //循环右移一位
		}
		else if (times==3)          //判断是否第三次S1按下
		{
			P1=x & y;            
			for(i=0;i<=10000;i++);  //延时
			x= _crol_(x,1);     
			y=_cror_(y,1);     
		}
		else if(times==4)
			P1=0XFF;
	}
}

矩阵按钮

上次课要求大家课后学习书上关于矩阵按键识别部分的知识点,这次课我们重点介绍矩阵按键应用方法。
image-20230329181828407

大家回忆一下我们做过的4个按键控制流水灯效果,共使用了4个I/O脚,我们称这四个按键是独立按键,如果我们需要16个按键,都使用独立按键方法,需要16个I/O脚,这样太浪费I/O资源了,为了节约I/O脚,可以将16个按键按四行、四列排列。图9.1为开发板上按键原理图,该按键模块有四行和四列,组成了16个按键,每一列按键的左边端口接在一起,称为列线,每一行按键的右边端口接在一起,称为行线,本实验我们将四个行线从上到下(也就是第一行到第行)分别接到P3口的第0脚到第3脚,四个列线从上到下(也就是第四列到第一列)分别接到P3口的第4脚到第7脚,P3口的第4脚到第7脚,那单片机如何判断哪个按键按下,可以采用按键扫描法,具体步骤如图9.2所示。
1、给第一列线也就是P3.7引脚低电平,其他列线及行线引脚输出高电平,也就是给P3输出11111110B扫描值,如果第一列有按键按下,四个行线肯定有一个跟P3.7连上,也就是判断四个行线P3.0到P3.3是否有低电平,若没有转第2步,若有表示第一列有按键按下,再进一步判断P3.0到P3.3具体是哪个引脚为低电平,得出是哪个按键按下,若P3.0是低电平,说明是S1按下,也就是若是S1按下,P3口电平的状态用二进制表示就是01111110,十六进制就是0x7e,我们称0x7e为S1的键值。以此类推S2的键值为0xbe,S3的键值为0xde,S4的键值为0xee。得出S1到S4任意一个键值后结束扫描,无需转第2步;
2、给第二列线也就是P3.6引脚低电平,其他列线及行线引脚输出高电平,也就是给P3输出11111101B扫描值,如果第二列有按键按下,四个行线肯定有一个跟P3.6连上,也就是判断四个行线P3.0到P3.3是否有低电平,若没有转第3步,若有表示第二列有按键按下,再进一步判断P3.0到P3.3具体是哪个引脚为低电平,得出是哪个按键按下,若P3.0是低电平,说明是S4按下,照1中的方法,得出S4到S8任意一个键值后结束扫描,无需转第2步;
3、给第三列线也就是P3.5引脚低电平,其他列线及行线引脚输出高电平,也就是给P3输出11111011B扫描值,如果第三列有按键按下,四个行线肯定有一个跟P3.5连上,也就是判断四个行线P3.0到P3.3是否有低电平,若没有转第4步,若有表示第三列有按键按下,再进一步判断P3.0到P3.3具体是哪个引脚为低电平,得出是哪个按键按下,若P3.0是低电平,说明是S9按下,按照1中的方法,得出S9到S12任意一个键值后结束扫描,无需转第2步;
4、给第四列线也就是P3.4引脚低电平,其他列线及行线引脚输出高电平,也就是给P3输出11110111B扫描值,如果第四列有按键按下,四个行线肯定有一个跟P3.4连上,也就是判断四个行线P3.0到P3.3是否有低电平,若没有表示没有按键按下,结束扫描,若有表示第四列有按键按下,再进一步判断P3.0到P3.3具体是哪个引脚为低电平,得出是哪个按键按下,若P3.0是低电平,说明是S9按下,按照1中的方法,得出S9到S12任意一个键值后结束扫描,无需转第2步;
按上面的方法,可以写出16个按键的键值,如表9.1所示。
image-20230329210059106
image-20230329210125021
明白了矩阵按键扫描和键值的概念后,我们再了解按键定义值的概念。按键的定义值是指按键在应用中的代表的功能,例如按下S1,希望单片机将它作为数字1处理,那S1的定义值就是1,按下S2,希望单片机将它作为乘号“*”处理,那S1的定义值就是“*”,也就是定义值是程序员按具体的应用分配的,同一个按键它的键值是唯一不变的,但定义值是可以变化的。下面我们来编程实现上课让大家思考的实验。该实验要求从S1到S16的顺序,将按键定义为1到16,例如S1的定义值为1,S16的键值为16,按下任意键,将该键的定义值以二进制的形式显示在8个LED上,灯亮代表1,灯灭代表0。要实现该实验我们要从两个方面考虑:硬件设计和软件设计。
1、硬件设计
硬件上将矩阵按键的行和列线按行1到行4、列4到列1的顺序分别接到P3口的第0脚到第7脚,8个LED灯分别按顺序接到P1口的8个引脚上。
2、软件设计
1)数据结构
软件上首先我们要定义一个键值和定义值的查询表,可以用数组实现,数组定义的格式为:
unsigned char code key_table[]={key1键值,key1定义值,……,key16键值,key16定义值};
这样如果我们获取到键值后可以查该键值,查到键值后,它的下一个数据即为该键值对应的定义值。
注意:该定义的数组的语句中unsigned char后面为什么加了个code?在这里我们先简单介绍一下程序存储器。程序存储器顾名思义是存放程序的存储器,但它不但可以放代码,还可以存放在程序运行过程中一直不变的数据,例如这里的键值和定义值这些数据在程序运行过程中一直不会发生变化,这些数据要是定义在数据存储器RAM区,比较浪费有限的RAM资源。那用C51怎么实现在程序存储器中怎么存放数据呢?下面的C51语句就是实现在程序存储器中存放数据100,该数据占用一个存储单元,存储单元的名称为x:
unsigned char code x=100;
上面语句的特点是,在数据类型后加“code”,表示将x分配在程序存储器中,并且要给x初始化数据,初始化的数据是随着代码一起通过下载器写入到程序存储器中,在程序运行过程中,不能改变x的值,例如下面的赋值语句是错误的:x=56;因为x是程序存储器中的存储单元,不支持在线写入数据。
2)按键扫描函数
按键扫描函数可以用一个4次循环来实现,具体流程如图9.3所示。

#include <reg51.h>
#include <intrins.h>
unsigned char key_scan(); //申明按键扫描函数
unsigned char code  key_table[]={0x7e,1,//  key1的键值和定义值 0111 1110  
								0x7d,2,  //key2的键值和定义值  0111 1101
								0x7b,3,  //key3的键值和定义值
								0x77,4, //key4的键值和定义值
								0xbe,5,//  key5的键值和定义值
								0xbd,6,  //key6的键值和定义值
								0xbb,7, //key7的键值和定义值
								0xb7,8, //key8的键值和定义值
								0xde,9,//key9的键值和定义值
								0xdd,10,  //key10的键值和定义值
								0xdb,11, //key11的键值和定义值
								0xd7,12, //key12的键值和定义值
								0xee,13,//key13的键值和定义值
								0xed,14,// key14的键值和定义值
								0xeb,15,//key15的键值和定义值
								0xe7,16, };//key16的键值和定义值

void main()
{
	unsigned char key=0x0f,key_num=0xff;
	int i;
	while(1)
	{  
		key=key_scan();
		if(key!=0xff)
		{
			for(i=0;i<32;i=i+2)
			{
				if(key_table[i]==key)
				{
					key_num=key_table[i+1];
					break;
				}
			}
		}
		P1=key_num;		
	}
}



unsigned char key_scan() //按键扫描函数
{
	unsigned char key_value=0xff,n,i;
	n=0xfe;   //1111 1110
	for(i=1;i<=4;i++)
	{
		P3=n;     //赋扫描值
		if(P3!=n)//扫描当前行是否有按键按下
		{
			key_value=P3;//获取扫描值
			break;
		}
		n=(n<<1)|(n>>7);//将扫描值循环左移一位,为下一行扫描做准备
	}
	return key_value; //返回键值
}

显示的是1 2 3 4 5…16

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值