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

写在前面

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

单片机原理图

在这里插入图片描述

矩阵键盘

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

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

#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个按键…)
在这里插入图片描述
对于8个串口,我们只需要先进行列扫描,即先输出00001111,再输出11110000,然后再使用一个 | 运算符,就可以判断出按键的具体位置在哪里了。
模块代码
这里采用的是状态机法来扫描矩阵键盘

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

#include <reg52.h>
#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子系列是由以下几种中断的
在这里插入图片描述每个中断又有不同的优先级,也就是当两个中断同时响应的时候,这个优先级会决定你先去执行哪一个中断
在这里插入图片描述使用中断有三个步骤

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

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
这些是大致的流程,来一个简单的代码

#include <reg52.h>
#include <intrins.h>
#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前面已经写了
在这里插入图片描述在这里插入图片描述在这里插入图片描述注:定时器最高定时时间为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 <reg52.h>
#include <intrins.h>

#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); //数码管显示函数	
} 
  • 14
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是一个简单的 51 单片机 C 语言矩阵键盘扫描、LED 显示器显示、简易计算程序代码示例: ``` #include <reg51.h> // 定义按键扫描函数 unsigned char keyScan() { unsigned char keyVal = 0xFF; // 初始化为无键按下状态 unsigned char i, j; for (i = 0; i < 4; i++) // 行扫描 { P1 = ~(1 << i); // P1 按位取反,使第 i 行输出低电平 for (j = 0; j < 4; j++) // 列检测 { if ((P1 & (1 << (j + 4))) == 0) // 判断第 j 列是否为低电平,即是否有键按下 { keyVal = i * 4 + j; // 计算键值 while ((P1 & (1 << (j + 4))) == 0); // 等待键松开 break; } } if (keyVal != 0xFF) // 如果有键按下,跳出循环 break; } return keyVal; // 返回键值,如果没有键按下,返回 0xFF } // 定义数码管显示函数 void ledDisplay(unsigned char num) { unsigned char ledCode[] = { // 数码管编码表 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; P2 = ledCode[num]; // 根据数字显示相应的编码 } void main() { unsigned char keyVal, num1 = 0, num2 = 0, op = 0, result = 0; while (1) { keyVal = keyScan(); // 扫描键盘,获取键值 if (keyVal != 0xFF) // 如果有键按下 { if (keyVal <= 9) // 如果按下的是数字键 { if (op == 0) // 如果还没有选择运算符,把数字存入 num1 num1 = keyVal; else // 如果已经选择了运算符,把数字存入 num2 num2 = keyVal; ledDisplay(keyVal); // 显示按下的数字 } else if (keyVal == 10) // 如果按下的是加号键 { op = 1; // 选择加运算 ledDisplay('+'); // 显示加号 } else if (keyVal == 11) // 如果按下的是减号键 { op = 2; // 选择减运算 ledDisplay('-'); // 显示减号 } else if (keyVal == 12) // 如果按下的是乘号键 { op = 3; // 选择乘运算 ledDisplay('*'); // 显示乘号 } else if (keyVal == 13) // 如果按下的是除号键 { op = 4; // 选择除运算 ledDisplay('/'); // 显示除号 } else if (keyVal == 14) // 如果按下的是等号键 { switch (op) // 根据选择的运算符进行计算 { case 1: // 加 result = num1 + num2; break; case 2: // 减 result = num1 - num2; break; case 3: // 乘 result = num1 * num2; break; case 4: // 除 result = num1 / num2; break; default: break; } ledDisplay(result); // 显示计算结果 num1 = result; // 把计算结果存入 num1,以便进行连续计算 num2 = 0; // 清零 num2 op = 0; // 清空运算符 } else if (keyVal == 15) // 如果按下的是清零键 { num1 = 0; // 清零 num1 num2 = 0; // 清零 num2 op = 0; // 清空运算符 ledDisplay(0); // 显示 0 } } } } ``` 这段代码实现了一个简单的计算器,可以通过矩阵键盘输入数字和选择运算符,然后在 LED 数码管上显示计算结果。在代码中,`keyScan()` 函数通过行扫描和列检测来实现键盘扫描,`ledDisplay()` 函数根据数码管编码表来显示数字和运算符,主函数则根据键值进行相应的处理,包括存储数字、选择运算符、进行计算、清零等操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暮尘依旧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值