【自学51单片机】8 -- 按键的学习,单片机最小系统

1、单片机最小系统

  • 电源
    单片机中常见5v和3.3v的单片机,“5v”和“3.3v”分别只是他们正常工作的典型值,5v和3.3v单片机也是以他们正常工作典型值命名的,他们正常工作电压的标准范围要根据单片机手册查询,
  • 晶振
    晶振分无源晶振和有源晶振。实物图和原理图见下图。
    实物图
    原理图
    无源晶振: 依靠单片机内部振荡电路才能工作,接到单片机两个振荡引脚即可,晶体两个引脚无区别,电压无要求,两侧通常有电容,手册有要求根据手册选电容,手册无要求一般选20pf。
    有源晶振: 无需依靠单片机内部振荡电路,只需外部供电达到电压要求,即可产生振荡频率,接到单片机晶振输入引脚即可接受到晶振频率,单片机晶振输出引脚无需连接。
    两者区别:无源晶振信号质量和精度比有源晶振差,价格比有源晶振便宜。
  • 复位电路
    KST-51开发板复位电路如下图

电路图
STC89C52单片机:高电平复位,低电平正常工作。
上电复位:一上电,给电容充电,此时电容相当于导线,RST高电平,电容充电越多,电路电流越来越小直到电容开路电流为0,RST电压越来越小直到低电平(0v),这就是上电复位,复位时间达到要求即可复位。
手动(按键)复位:按键未按前,RST为低电平,按键按下,RST为高电平,松开按键,电源给电容充电后,电容开路,RST为低电平。18欧姆电阻作用:为抑制按键按下后,电容产生的电磁干扰。

2、独立按键和准双向IO口

  用户和单片机交流信息依赖于输入设备和输出设备,前边LED小灯部分都是输出设备,现在来学习输入设备-----按键。

  • 按键:按键电路分独立式按键矩阵式按键两种。下面说明独立式按键,见下图原理图。

独立按键原理图
说明:四条KeyIn编号输入线连接单片机IO口,当按键k1按下,KeyIn1引脚为低电平,当按键k1松开,KeyIn1引脚为高电平,KeyIn编号IO口的电平情况由按键的状态所决定。

  • 准双向IO口:在KST51-开发板中,按键接到P2中KeyIn编号IO口上,这些IO口上电默认是准双向IO口,下面来学习准双向IO口电路,如下图8-7。
    准双向IO口

说明:这种IO口,有输出端和输入端,要正常读取外部信号状态,必须要保证自己内部输出的是1。当内部输出为1时,经过非门后输出为0,三极管不导通,按键松开,内部输入为高电平,按键按下,内部输入为低电平。而当内部输出为0时,通过分析,按键是什么状态,内部输入都为低电平。

3、矩阵按键

 矩阵按键相对于独立按键而言可以减少IO口的使用,原理图见下图。
矩阵按键原理图
说明:如果keyout1输出为低电平,相当于GND,而keyout2、keyout3、keyout4输出为高电平的时候,K1、K2、K3、K4相当于独立按键。

4、矩阵按键扫描程序

//矩阵按键的扫描
#include<reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit keyin1 = P2^4;
sbit keyin2 = P2^5;
sbit keyin3 = P2^6;
sbit keyin4 = P2^7;
sbit keyout1 = P2^3;
sbit keyout2 = P2^2;
sbit keyout3 = P2^1;
sbit keyout4 = P2^0;

unsigned char code LedChar[] = { //数码管显示字符转换表
	0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
	0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};

unsigned char keysta[4][4]= { //全部矩阵按键的当前状态
	{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}//数组不能定义成bit型
};

void main()
{
	unsigned char i, j;
	unsigned char backup[4][4] = {//按键值备份,保存前一次的值
	{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}
	};

	EA = 1;//使能总中断
	ENLED = 0;//选择数码管DS0显示
	ADDR3 = 1;
	ADDR2 = 0;
	ADDR1 = 0;
	ADDR0 = 0;
	TMOD  = 0x10; //设置T1为模式1
	TH1 = 0xFC;	//为T1赋值,定时1ms
	TL1 = 0x67;
	TR1 = 1; //开启T1
	ET1 = 1; //使能T1中断
	P0 = LedChar[0]; //默认数码管显示0

	while(1)
	{ 	
		 for(i = 0; i < 4; i++)	//循环检测4*4矩阵按键
		 {
		 	for(j = 0; j < 4; j++)
			{
				if(keysta[i][j] != backup[i][j]) //检测按键动作
				{
					if(backup[i][j] == 0)  //前一次值为0,说明当前按键弹起
					{						 
						P0 = LedChar[4*i+j];//将编号显示到数码管
					}
					backup[i][j] = keysta[i][j];//更新前一次备份值
				}
			}
		 }
	}
}


void Interrupt() interrupt 3
{
	unsigned char i; 
	static unsigned char keyout = 0;   //矩阵按键扫描输出索引
	static unsigned char keybuf[4][4] = {	//矩阵按键扫描缓冲区
	{0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF},
	{0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF}
	};

	TH1 = 0xFC;	//重新为T1赋值
	TL1 = 0x67;
	//将一行的4个按键值移入缓冲区
	keybuf[keyout][0] =	(keybuf[keyout][0] << 1) | keyin1;
	keybuf[keyout][1] =	(keybuf[keyout][1] << 1) | keyin2;
	keybuf[keyout][2] =	(keybuf[keyout][2] << 1) | keyin3;
	keybuf[keyout][3] =	(keybuf[keyout][3] << 1) | keyin4;
	//消抖后更新按键状态
	for(i = 0; i < 4; i++)	  //每行四个按键,所以循环四次
	{
		if((keybuf[keyout][i] & 0x0F) == 0x00)
		{	  //连续扫描4次值为0,即4*4ms内都是按下状态时,可认为按键已稳定按下
			keysta[keyout][i] = 0;
		}else if((keybuf[keyout][i] & 0x0F) == 0x0F)
		{	  //连续扫描4次值为1,即4*4ms内都是弹起状态时,可认为按键已稳定弹起
			keysta[keyout][i] = 1;
		}
	}
	//执行一下次的扫描输出
	keyout++;	//输出索引递增
	keyout = keyout & 0x03;//索引值加到4即归零
	switch(keyout)	 //根据索引,释放当前输出引脚,拉低下次的输出引脚
	{
		case 0:	keyout4 = 1; keyout1 = 0; break;
		case 1:	keyout1 = 1; keyout2 = 0; break;
		case 2:	keyout2 = 1; keyout3 = 0; break;
		case 3:	keyout3 = 1; keyout4 = 0; break;
		default:break;
	}
}

原理:根据上面所讲的内容和下图单片机原理图的分析,程序原理很好理解,程序利用动态扫描扫描4行按键,每隔一秒扫描一行按键,通过检测不同位置按键输入引脚的电平变化,来改变数码管的值,程序中keyin输入和keyout输出颠倒是为了让输出信号有足够时间来稳定。
单片机电路

5、按键抖动

 按键抖动:按键闭合时间由操作人员控制决定,通常会在100ms以上,刻意快速的按,也会在40-50ms左右,因为按键有机械弹性,按下和弹起瞬间都会有一连串的抖动,如下图8-10,这种抖动直接影响了数码管值的变化,上面程序运用了软件消抖解决该问题,每个按键每隔4ms检测一次按键状态,每个按键检测4次,每个按键检测4*4ms,若4次状态相同则可确定按键的状态。这就将按键的抖动消去了。
按键

6、综合小程序-简易加减法计算器

//支持“向上键”加,“向下键”减, 回车计算结果,但不支持连续加减操作
#include<reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit keyin1 = P2^4;
sbit keyin2 = P2^5;
sbit keyin3 = P2^6;
sbit keyin4 = P2^7;
sbit keyout1 = P2^3;
sbit keyout2 = P2^2;
sbit keyout3 = P2^1;
sbit keyout4 = P2^0;

void KeyDriver();//按键驱动函数
void KeyAction(unsigned char keycodeMap);//按键动作函数
void ShowNumber(signed long num);//显示函数
void Keyscan();//按键扫描函数
void LedScan();//数码管扫描函数

unsigned char code LedChar[] = { //数码管显示字符转换表
	0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
	0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};

unsigned char  Ledbuff[6] = {  //数码管显示缓冲区
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};

unsigned char 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键、  回车键、 向右键
};

unsigned char keysta[4][4]= { //全部矩阵按键的当前状态
	{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}
};


void main()
{
	ENLED = 0; //使能U3
	ADDR3 = 1;
	EA = 1; //使能总中断
	TMOD = 0x01;//设置T0为模式1
	TH0 = 0xFC;//为T0赋值,定时1ms
	TL0 = 0x67;
	ET0 = 1; //使能T0中断
	TR0 = 1; //开启T0
	Ledbuff[0] = LedChar[0]; //上电显示0

	while(1)
	{
	 	KeyDriver();//调用按键驱动函数
	};
}

//按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用
void KeyDriver()
{
	unsigned char i, j;
	static unsigned 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++)		   //循环检测4*4的矩阵按键
	{
		 for(j = 0; j < 4; j++)
		 {
		 	if(backup[i][j] != keysta[i][j])   //检测按键动作
			{
				if(backup[i][j] != 0)	//按键按下时执行动作
				{
					 KeyAction(KeyCodeMap[i][j]);  //调用按键动作函数
				}
				backup[i][j] = keysta[i][j];   //刷新前一次的备份值
			}	
		 }
	}
}

//按键动作函数,根据键码执行相应的操作,keycode-按键按码
void KeyAction(unsigned char keycodeMap)
{
    static signed char oprt = 0; //用于保存加减运算符
	static signed long addend = 0;//用于保存输入的减数
	static signed long result = 0;//用于保存运算结果
	if((keycodeMap >= 0x30) && (keycodeMap <= 0x39))//输入0~9的数字
	{
		addend = (addend * 10) + (keycodeMap - 0x30);//整体十进制左移,新数字进入个位
		ShowNumber(addend);	  //运算结果显示到数码管
	}
	else if(keycodeMap == 0x26)  //向上键用作加号,执行连法运算
	{
		oprt = 0;  //设置运算符变量
		result = addend;  //运算数存到结果中,准备进行加减
		addend = 0;	   //清零运算数,准备接收下一个运算数
		ShowNumber(result);	 //刷新数码管显示
	} 
	else if(keycodeMap == 0x28)  //向下键用作减号,执行减法运算
	{
		oprt = 1;//设置运算符变
		result = addend;//运算数存到结果中,准备进行加减 
		addend = 0;	//清零运算数,准备接收下一个运算数
		ShowNumber(result);	//刷新数码管显示
	} 
	else if(keycodeMap == 0x0D)	//回车键执行加减法运算)
	{
		if(oprt == 0)  //执行加法运算
		{
			result += addend;
		}
		else	 //执行减法运算
		{
			result -= addend;
		}
		addend = 0;
		ShowNumber(result);	//运算结果显示到数码管
	}
	else if(keycodeMap == 0x1B)//ESC键,清零结果
	{
		 result = 0;
		 addend = 0;
		 ShowNumber(addend); //清零后的减数显示到数码管
	}
}

//将一个无符号长整形的数字显示到数码管上,num为待显示数字
void ShowNumber(signed long num)
{
	unsigned char sign = 0;
	unsigned char buf[6];
	signed char i;
	if(num < 0)	//首先提取并暂存符号位
	{
		sign = 0;
		num = -num;
	}
	else{
		sign = 1;
	}
	for(i = 0; i < 6; i++)	//把长整型数转换为6位十进制的数组
	{
		buf[i] = num % 10;
		num = num / 10; 	
	}
	for(i = 5; i >= 1; i--)	//从最高位起,遇到0转换为空格,遇到非0则退出循环
	{
		if(buf[i] == 0)
		{
			Ledbuff[i] = 0xFF;
		}
		else
		{
			break;
		}
	}
	if(sign == 0)    //负数时,需在最前面添加负号
	{
		if(i < 5)   //当有效位数小于6位时添加负号,否则显示结果将是错的
		{
			Ledbuff[i+1] = 0xBF;
		}
	}					
	for(; i >= 0; i--) //剩余低位都如实转换为数码管显示字符
	{
		Ledbuff[i] = LedChar[buf[i]];
	}
}

void InterruptTimer0() interrupt 1
{
	TH0 = 0xFC;//为重新T0赋值
	TL0 = 0x67;

	LedScan();	//调用数码管显示扫描函数
	Keyscan();	//调用按键扫描函数
}

void Keyscan()//按键扫描函数
{
	unsigned char i;
	static unsigned char keyout = 0;//矩阵按键扫描输出索引
	static unsigned char keybuf[4][4] = {  //矩阵按键扫描缓冲区
		{0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF},
		{0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF}
	};

	//将一行的4个按键值移入缓冲区
	keybuf[keyout][0] = (keybuf[keyout][0] << 1) | keyin1;
	keybuf[keyout][1] = (keybuf[keyout][1] << 1) | keyin2;
	keybuf[keyout][2] = (keybuf[keyout][2] << 1) | keyin3;
	keybuf[keyout][3] = (keybuf[keyout][3] << 1) | keyin4;
	//消抖后更新按键状态
	for(i = 0; i < 4; i++)	  //每行4个按键,所以循环四次
	{
		if((keybuf[keyout][i] & 0x0F) == 0x00)
		{  //连续4次扫描值为0,即4*4ms内都是按下状态,可认为按键以稳定地按下
			keysta[keyout][i] = 0;
		}
		else if((keybuf[keyout][i] & 0x0F) == 0x0F)
		{	 //连续4次扫描值为1,即4*4ms内都是弹起状态,可认为按键以稳定地弹起
			keysta[keyout][i] = 1;
		}
	}
	//执行下一次的扫描输出
	keyout++;	//输出索引递增
	keyout = keyout & 0x03;	 //索引值加到4后归零
	switch(keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚
	{
		case 0:keyout4 = 1; keyout1 = 0; break;
		case 1:keyout1 = 1; keyout2 = 0; break;
		case 2:keyout2 = 1; keyout3 = 0; break;
		case 3:keyout3 = 1; keyout4 = 0; break;
		default:break;
	}
}

//数码管动态扫描刷新函数,须需在定时中断中调用
void LedScan()
{
	static unsigned char i = 0;	   //动态扫描的索引
	P0 = 0xFF; //显示消隐
	switch(i)
	{
		case 0:	ADDR2 = 0; ADDR1 = 0; ADDR0 = 0; i++; P0 = Ledbuff[0];break;
		case 1:	ADDR2 = 0; ADDR1 = 0; ADDR0 = 1; i++; P0 = Ledbuff[1];break;
		case 2: ADDR2 = 0; ADDR1 = 1; ADDR0 = 0; i++; P0 = Ledbuff[2];break;
		case 3:	ADDR2 = 0; ADDR1 = 1; ADDR0 = 1; i++; P0 = Ledbuff[3];break;
		case 4:	ADDR2 = 1; ADDR1 = 0; ADDR0 = 0; i++; P0 = Ledbuff[4];break;
		case 5:	ADDR2 = 1; ADDR1 = 0; ADDR0 = 1; i=0; P0 = Ledbuff[5];break;
		default:break;
	}
}

说明:程序功能上只能进行一次加或一次减,该程序分为几个模块,目的是为了让程序层次化。

7、收获

 本章内容相比前几章较多,每天陆陆续续学一点,花了比较久的时间学这章,书本上例题的程序有点难以理解,看了几遍才看理解。但还是有所收获,掌握了如何按键消抖和按键的动态扫描。也加深了对程序层次化的理解。奥里给奥里给!!
表情包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值