单片机C语言实现独立按键检测与矩阵键盘操作

       所有的电子产品几乎到涉及到按键操作。所以微控制器是如何识别一个按键是否被按下,按下后又该如何做出反应,又如何防止按键抖动呢?更深入一点,微控制器又是如何识别矩阵键盘的?本文将详细阐述如何用C语言实现独立按键的检测和矩阵键盘操作。

完成本文所需硬件:基于C51系列单片机的开发板(本文是基于STC12C5A60S2处理器的一款开发板),带中文版windows操作系统的电脑。

完成本文所需软件:KEIL系列平台(本文选取Keil uVision4), STC烧写软件-ISP-V6.82E 。


一、独立按键检测

       这里我要实现用按键K1去控制发光二极管LD4。同时为了试验按键过程中与其他事件的冲突性,引入两个事件即LD1与LD8分别以不同频率闪烁。先上程序吧。另外利用keil软件新建工程和文件部分这里就略过了,总之所有的代码都放在main文件里执行。

代码:

-----------------------------------------------------------------------------------------------

#include <reg51.h>
sbit K1=P2^4;       //定义按键K1的检测口
sbit LD4=P1^3;    //定义控制LD4的输出口
sbit LD1=P1^0;   //定义控制LD1的输出口
sbit LD8=P1^7;   //定义控制LD8的输出口

void LD1_flash()  //LD1闪烁
{
	static unsigned int a; //定义a为静态局部变量
	a = a + 1;
	if(a == 20000) //当a达到20000次CPU计数次数
	{
		a=0;  //a归零
		LD1 =! LD1; //LD1取反,如果先前是灭,则取反后亮,反之。
	}
}

void LD8_flash()//LD8 flash
{
	static unsigned int b;
	b++;
	if(b==40000)
	{
		b=0;
		LD8 =! LD8;
	}
}

void delay(unsigned int x) 
{
	while(x)
	{
		x = x - 1;
	}
}

void Key1()
{
	static char st;
	if(K1==0) //按键K1是否被按下
	{
		if(st==0) //按键K1是否是被刚刚按下,假设st为1,则按键还处于被按下的状态,则不用执行if里面语句。
		{
			delay(5000); //延时防抖动
			if(K1==0) //查看按键K1是否还处于被按下状态
			{
				LD4=!LD4;
				st=1;  //等待放手
			}
				
		}
	}
	else
	{
		st=0;
	}
}

void main() //C语言执行入口
{
	while(1)
	{
		LD1_flash(); //ld1 flash 
		LD8_flash(); //ld8 flash
		Key1();
	}
}

要理解上面的代码,我们需要结合C语言的一些特性来分析。

1、static unsigned int a,为什么不把变量a与b定义成局部变量,却定义成静态局部变量?

     我们知道局部变量在函数执行完时候其值归零,而static关键字修饰的局部变量在函数结束时,其值会保留到下一次该函数被调用。

2、为什么a和b没被定义成全局变量?

    全局变量是指该变量可被任何函数使用,而局部变量只有定义该变量的函数可以使用。局部变量的优点是安全,但缺点是函数结束其值随之归零。与之相对,全局变量则不安全。

3、为什么a和b并未赋初始值?

    这是因为startup.A51程序执行时使其赋值为0。C语言执行是从函数开始的,但是真正的程序运行是从汇编语言开始,即startup.A51文件。因为C语言无法访问寄存器如R0, 所以只能由汇编程序来执行。下面截取一段startup.A51的程序进行分析:

IF IDATALEN <> 0
                MOV     R0,#IDATALEN - 1
                CLR     A
IDATALOOP:      MOV     @R0,A
                DJNZ    R0,IDATALOOP
ENDIF

    这段程序会将内存区域清零,其中A代表累加器ACC,即内存中224号地址。

     另外解释下赋初值的情况,例如unsigned int a = 8, 我们知道RAM在断电后数据丢失,因此单片机就利用ROM来保存这个初始值。在下次上电时,进入C语言之前,汇编程序将ROM中的a调到RAM中。这样也就保证了a的初值。

4、LD1与LD8的闪烁为什么不适用delay函数?

     如果使用delay函数,那么单片机在执行到LD1_flash函数时候,只能停滞在delay的这个时间段内,浪费时钟资源。而引入a和b后,程序进入LD1_flash函数时候,只需判断a的值,然后再加一或者清零,几乎不占用CPU时间。

5、key1函数中st的作用?

     首先,st可以判断程序在每次被调用的前后按键状态,这样,可以保证键被按住的时候,发光二极管不会闪烁。另外,也可以避免在操作K1键的时候影响LD1和LD8的运行。另外如果不想在key1函数中使用delay函数,可以采用如下代码:

void Key1()
{
	static unsigned int a,b;
	if(K1==0)
	{
		if(a==0) //检查按键是否被刚刚按下
		{
			b++;
			if(b >= 1000) //看看按键时间是否维持了1000次CPU执行周期
			{
				LD4=!LD4;
				//...
				a=1;
			}
		}
	}
	else
	{
		a=0;
		b=0;
	}
}


6、如何将不同的函数独立到不同的文件?

    为了保证代码的可维护性,如果要将不同的代码区块分配到不同文件,可以使用extern,它的作用是实现不同文件间的函数调用。例如:

    本例中可将main函数精简成:

#include <reg51.h>

extern void delay(unsigned int x);
extern void LD1_flash();
extern void LD8_flash();
extern void Key1();

void main()
{
	while(1)
	{
		LD1_flash(); //ld1 flash 
		LD8_flash(); //ld8 flash
		Key1();
	}
}

二、矩阵键盘



上图中,P1口初始值从高位到低位为11111110,当2和3键被按下,此时P1.0口连通P1.5和P1.6口,即P1口状态变为10011110。由此我们可以用P1口的高四位来判断有没有键被按下,如果没有那么其值为1111,也就是0x0F。那如果被按下了,具体是哪个键呢?这时候我们可以建立一个矩阵表,然后通过每次读取P1口的状态,与矩阵表相对应,便可以知道哪个键被按下。这个矩阵表见下(选用了P0口代替P1口):

原理就解释到这吧,直接上程序:

-----------------------------------------------------------------------------------------------------------------

#include "reg51.h"

void delay(unsigned int x)
{
	while(x)
	{
		x=x-1;
	}
}

void key_exc(unsigned char k)//unsigned 表示最高位不当做符号用
{
	switch(k)
	{
		case 0xee: P1=~P1; break; //1键按下
		case 0xed:  break;        //2键按下
		case 0xeb:  break;
		case 0xe7:  break;		
		
		case 0xde:  break;
		case 0xdd:  break;
		case 0xdb:  break;
		case 0xd7:  break;
		
		case 0xbe:  break;
		case 0xbd:  break;
		case 0xbb:  break;
		case 0xb7:  break;
		
		case 0x7e:  break;
		case 0x7d:  break;
		case 0x7b:  break;
		case 0x77:  P1=~P1; break;
	}
}
void key_scan()
{
	char i;
	char t; //用来存放读取P0口的状态
	
//	if((P0>>4) != 15)
//	{
//		return; //直接跳出函数
//	}
	
	for(i=0;i<4;i++) //for循环执行,首先i赋值,再判断i是否小于4,如果是的话,开始执行下面的程序,程序执行完加1,再判断i是否小于4,这样一直到结束
	{
		P0 = ~(1<<i); //扫描第i行。注意1<<i是一个数,这个数中只有一个1,这个1正好在第i位上。而~则将其取反。
		t=P0; //将P0的值赋给t
		if((t>>4)!=0x0F) //本行是否有按钮被按下
		{
			delay(5000);
			if(P0==t) //如果相等则表示延时前后的按键状态是一致的
			{
				key_exc(t);//将局部变量传给函数key-exc,并分析按下的按钮
				while((P0>>4)!=15)//等待放手
				{
					
				}
			}
		}
	}
}
void main()
{
	while(1)
	{
		key_scan();
	}
}
-----------------------------------------------------------------------------------------------------------------------------------

利用上面的程序,可以判断哪个按钮被按下。例如当K1按下时候,P1口的发光二极管都被点亮。好了就讲到这里吧,如果有什么疑问,请留言,我会及时答复!

展开阅读全文

没有更多推荐了,返回首页