51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)

写在前面

单片机的定时器和中断可以说是贯穿了各个模块的始终,这一个概念可以说是最最最重要的了,每一个模块的调用很多都需要在中断里面,包括后面的I2C,Uart等等
如有错误,欢迎大佬指出

单片机原理图

51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第1张图片

矩阵键盘

原理图
51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第2张图片使用:独立键盘
独立键盘模块是很简单的一部分,只需要了解按下开关之后,串口会被拉低到0即可判断。
简单的一个独立键盘代码,供参考
这里面还有一个可以学习的地方就是防止抖动
当人不去触碰按键的时候,按键会有可能因为各种原因产生抖动,从到使得串口读取到的电压会在0/1之间来回跳动,这也就使得程序没有使用的意义,这时候就需要防抖动来达到稳定电压的作用,下面代码中的防抖动的代码是一个比较常用的代码。

/*功能:按下开发板左下角S2按键数码管值+1,最大到9,按下S3按下,值减一,最小减到0*/
#include 
#include 

#define uint unsigned int
#define uchar unsigned char

sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选
sbit key_s2 = P3^0;//独立按键S2
sbit key_s3 = P3^1;//独立按键S3
uchar num;//数码管显示的值

//共阴数码管段选表0-9
uchar  code tabel[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};


//毫秒级延时函数定义
void delay(uint z)
{
	uint x,y;
	for(x = z; x > 0; x--)
		for(y = 114; y > 0 ; y--); 		
} 

void main()//main函数自身会循环
{
	WE = 1;//打开位选锁存器
	P0 = 0XFE; //1111 1110
	WE = 0;//锁存位选数据
	
	while(1)
	{
		if(key_s2 == 0)//判断S2是否被按下
		{
			delay(20);//按键消抖
			if(key_s2 == 0)
			{
				if(num != 9)//如果值不等于9则+1,功能把值限定为小于9
				num++;
				while(!key_s2);//松手检测
			}	
		}
		if(key_s3 == 0)//判断S3是否被按下
		{
			delay(20);//按键消抖
			if(key_s3 == 0)
			{
				if(num > 0)	//如果大于0则执行减一
					num--;
				while(!key_s3);//松手检测
			}	
		}
		//松手之后刷新显示
		DU = 1;//打开段选锁存器
		P0 = tabel[num];//
		DU = 0;//锁存段选数据
	}	
}

使用:矩阵键盘
和数码管一样,矩阵键盘如果采用独立键盘的方式,那么需要太多太多的串口,这会导致浪费和成本上升,所以就采用了上面接线的方法来实现8个串口控制16个按键的方法
(在单片机的一个公众号上面看到,5个串口可以控制25个按键…)
51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第3张图片
对于8个串口,我们只需要先进行列扫描,即先输出00001111,再输出11110000,然后再使用一个 | 运算符,就可以判断出按键的具体位置在哪里了。
模块代码
这里采用的是状态机法来扫描矩阵键盘

/*pbdata.h*/
#ifndef __PBDATA_H__
#define __PBDATA_H__
#define uchar unsigned char
#define uint unsigned int

#include 
#include "KeyBoard.h"
/*    矩阵键盘扫描    */
						//矩阵键盘的数据口
sbit S1 = P3^0;			//四个独立键盘模块	
sbit S2 = P3^1;
sbit S3 = P3^2;
sbit S4 = P3^3;
#endif
/*KeyBoard.h*/
#ifndef __KeyBoard_H__
#define __KeyBoard_H__

#define key P3			//矩阵键盘的数据口
#define no_key  0xff    //无按键按下
#define key_state0  0   //状态0,此时无按键按下
#define key_state1  1  	//状态1,此时处于确定按键是否按下
#define key_state2  2  	//状态2,此时判断按键是否释放

uchar Keyscan();

#endif
#include "pbdata.h"

/*矩阵键盘扫描,返回值按键扫描得到的值可以更改*/
/*在定时中断中调用此函数*/
//通过返回值来确定是哪一个按键
uchar Keyscan()
{
	uchar key_state;        //状态指示
	uchar key_value;		//键值返回
	uchar key_temp;
 	uchar key1,key2;
 
 	key=0xf0;
	key1=key;			   
 	key1=key&0xf0;  		//确定哪一行的按键按下
 	key=0x0f;
	key2=key;
 	key2=key&0x0f;  		//确定哪一列的按键按下
	key_temp=key1|key2;  	//确定按键位置
 
 	switch(key_state)      	//检测当前状态
 	{
  		case key_state0:							//之前无按键被按下
	   		if(key_temp!=no_key)					//说明有按键按下或者抖动
	   		{
	    		key_state=key_state1;				//转换为状态1,然后去判断是否真的按下
	   		}
	   		break;


  		case key_state1:							//状态1,说明之前已经有按键按下或者抖动
	   		if(key_temp==no_key)					//全为高电平,说明是抖动
	   		{
	    		key_state=key_state0;				//返回到状态1,
	   		}
	   		else									//确实有按键被按下
	   		{
	    		switch(key_temp)                    //当确定按键按下后,列举所有的按键情况
	    		{
				     case 0xee: key_value=0;break;
				     case 0xde: key_value=1;break;
				     case 0xbe: key_value=2;break;
				     case 0x7e: key_value=3;break;
				     case 0xed: key_value=4;break;
				     case 0xdd: key_value=5;break;
				     case 0xbd: key_value=6;break;
				     case 0x7d: key_value=7;break;
				     case 0xeb: key_value=8;break;
				     case 0xdb: key_value=9;break;
				     case 0xbb: key_value=10;break;
				     case 0x7b: key_value=11;break;
				     case 0xe7: key_value=12;break;
				     case 0xd7: key_value=13;break;
				     case 0xb7: key_value=14;break;
				     case 0x77: key_value=15;break;
		    	}
	    		key_state=key_state2;				//跳到状态2,进而判断是否被释放
			}
	   		break; 

  		case key_state2:							//状态2,判断是否被释放
	   		if(key_temp==no_key)					//释放,转回到状态0
	   		{
	   			key_state=key_state0;
	   		}
	   		break;    
 	}
	return key_value;
}

中断

首先需要明确什么是中断
在这里插入图片描述51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第4张图片而51子系列是由以下几种中断的
51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第5张图片每个中断又有不同的优先级,也就是当两个中断同时响应的时候,这个优先级会决定你先去执行哪一个中断
51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第6张图片使用中断有三个步骤

让单片机允许中断
配置中断的方式
编写中断的函数

51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第7张图片51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第8张图片
51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第9张图片
这些是大致的流程,来一个简单的代码

#include 
#include 
#define uchar unsigned char
#define uint unsigned int

sbit WE = P2^7;
sbit DU = P2^6;
sbit key_s2 = P3^0;
sbit flag = P3^3;
void delay(uint z)
{
	uint x,y;
	for(x = z; x > 0; x--)
		for(y = 114; y > 0 ; y--); 		
} 
//外部中断1初始化
void int1Init()
{
	EA = 1;	//开总中断
	EX1 = 1;//开外部中断1
	IT1 = 1;//外部中断1下降沿触发	
}

void main()//main函数自身会循环
{
	int1Init();//外部中断1初始化	
	while(1)
	{
		if(key_s2 == 0)//判断S2是否被按下
		{
			delay(20);//按键消抖
			if(key_s2 == 0)
			{
				flag = 1;
				delay(5);
				flag = 0;//产生下降沿
				while(!key_s2);//松手检测
			}	
		}

	}	
}

//外部中断1中断服务程序
void int1() interrupt 2
{
	P1 = ~P1;
}  	 

定时器(计数器)中断

使用方式:
首先需要先了解TCON和TMOD两个控制器,TCON前面已经写了
51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第10张图片51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第11张图片51单片机学习总结(二)矩阵键盘扫描(状态机法),定时器和中断(内含模块化代码)_第12张图片注:定时器最高定时时间为65535*1.085us
而如何才能达到实现计自己的那么多时间了
按照以下代码

/* 配置并启动T0,ms-T0 定时时间 */
void ConfigTimer0(unsigned int ms)
{
	unsigned long tmp; 					//临时变量
	tmp = 11059200 / 12;		 		//定时器计数频率
	tmp = (tmp * ms) / 1000; 			//计算所需的计数值
	tmp = 65536 - tmp; 					//计算定时器重载值
	tmp = tmp + 18;						//补偿中断响应延时造成的误差
	T0RH = (unsigned char)(tmp>>8); 	//定时器重载值拆分为高低字节
	T0RL = (unsigned char)tmp;
	TMOD &= 0xF0; 						//清零T0 的控制位
	TMOD |= 0x01;						//配置T0 为模式1 16位计数器
	TH0 = T0RH; 						//加载T0 重载值
	TL0 = T0RL;
	ET0 = 1; 							//打开定时器0中断
	TR0 = 1; 							//打开定时器0
}

定时/计数器
启动定时器/计数器 通过TCON控制器
设置定时器/计数器工作模式 通过TMOD控制器
查询定时器/计数器是否溢出 读取TCON的TF位

下面是一个简单编程实例,通过中断来实现显示数字,通过按键来加一或者减一

#include 
#include 

#define uint unsigned int
#define uchar unsigned char

sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选
sbit key_s2 = P3^0;//独立按键S2
sbit key_s3 = P3^1;//独立按键S3
uchar num;//数码管显示的值
uchar mSec, Sec;//毫秒和秒储存变量

//共阴数码管段选表0-9
uchar code SMGduan[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};
//数码管位选码
uchar code SMGwei[] = {0xfe, 0xfd, 0xfb};

/*====================================
函数	: delay(uint z)
参数	:z 延时毫秒设定,取值范围0-65535
返回值	:无
描述	:12T/Fosc11.0592M毫秒级延时
====================================*/
void delay(uint z)
{
	uint x,y;
	for(x = z; x > 0; x--)
		for(y = 114; y > 0 ; y--); 		
} 

/*====================================
函数	:display(uchar i)
参数	:i 显示数值,取值范围0-255
返回值	:无
描述	:三位共阴数码管动态显示
====================================*/
void display(uchar i)
{
	static uchar wei; 		
	P0 = 0XFF;//清除断码
	WE = 1;//打开位选锁存器
	P0 = SMGwei[wei];
	WE = 0;//锁存位选数据
	switch(wei)
	{
		case 0: DU = 1; P0 = SMGduan[i / 100]; DU = 0; break;
		case 1: DU = 1; P0 = SMGduan[i % 100 / 10]; DU = 0; break;	
		case 2: DU = 1; P0 = SMGduan[i % 10]; DU = 0; break;		
	}
	wei++;
	if(wei == 3)
		wei = 0;
}
/* 配置并启动T0,ms-T0 定时时间 */
void ConfigTimer0(unsigned int ms)
{
	unsigned long tmp; 					//临时变量
	tmp = 11059200 / 12;		 		//定时器计数频率
	tmp = (tmp * ms) / 1000; 			//计算所需的计数值
	tmp = 65536 - tmp; 					//计算定时器重载值
	tmp = tmp + 18;						//补偿中断响应延时造成的误差
	T0RH = (unsigned char)(tmp>>8); 	//定时器重载值拆分为高低字节
	T0RL = (unsigned char)tmp;
	TMOD &= 0xF0; 						//清零T0 的控制位
	TMOD |= 0x01;						//配置T0 为模式1 16位计数器
	TH0 = T0RH; 						//加载T0 重载值
	TL0 = T0RL;
	ET0 = 1; 							//打开定时器0中断
	TR0 = 1; 							//打开定时器0
}
void main()//main函数自身会循环
{	
	ConfigTimer0(5);//定时器0初始化
	while(1)
	{
		if(key_s2 == 0)//判断S2是否被按下
		{
			delay(20);//按键消抖
			if(key_s2 == 0)
			{
				if(num != 120)
				num++;
				while(!key_s2);//松手检测
			}	
		}
		if(key_s3 == 0)//判断S3是否被按下
		{
			delay(20);//按键消抖
			if(key_s3 == 0)
			{
				if(num > 0)
					num--;
				while(!key_s3);//松手检测
			}	
		}
	}	
} 

//定时器0中断函数
void timer0() interrupt 1
{
	TH0 = 0xED;
	TL0 = 0xFF; //定时5ms
	display(num); //数码管显示函数	
} 

你可能感兴趣的:(单片机学习)