我是靠谱客的博主 细腻金针菇,最近开发中收集的这篇文章主要介绍关于单片机按键状态检测方法的一些体会-----按键消抖,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

开发板:普中A7开发板
核心板:51和STM32F103C8T6

关于按键状态检测要注意的就一个消抖问题,消抖方式需要高效简单。这里需要搞清楚一点,按键状态检测用的是轮询方式还是中断方式

在这里需要感谢一下金沙滩的51单片机视频课程老师宋老师。因为其中一种按键消抖方法就是学自宋老师课程,需要详细查看的话可以看《手把手教你51单片机.pdf》文档。

我知道的软件消抖(轮询方式)有两种方式:
1、在绝大多数情况下,我们是用软件即程序来实现消抖的。最简单的消抖原理,就是当检测到按键状态变化后,先等待一个 10ms 左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。实际应用中,这种做法局限性大(实时性差)。
2、第二种就是学自宋老师的启用一个定时中断,每 2ms 进一次中断,扫描一次按键状态并且存储起来,连续扫描 8 次后,看看这连续 8 次的按键状态是否是一致的。8 次按键的时间大概是 16ms,这 16ms 内如果按键状态一直保持一致,那就可以确定现在按键处于稳定的阶段,而非处于抖动的阶段。(这里需要注意的是这个16ms的时间不是固定的,2ms进一次中断检测一个位,一个字节8位,所以用一个字节来判断按键状态就是需要16ms,如果用4位来判断那么就是8ms,具体可以查看下面代码)如下图所示。
在这里插入图片描述

在这里需要提一下的是,在STM32F103C8T6核心板上做实验的时候,使用第二种按键消抖方式(用GPIO外部中断来实现,而不是用时间片轮询来实现)始终没调试出来,按键操作之后容易卡死,也不知道卡死在哪,代码就不贴出来了。但是网上查了一下资料,有人是通过定时器延迟到了给flag标志来进行消抖的,比如:如果定时没到的话flag = 0,在主程序中检测按键就用if(flag)来判断是否经过了延时消抖,定时到了就给flag,那么主程序中的检测按键代码就执行。下面看一下按键的常识性知识:

1、通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,如图所示。
图1
2、按键稳定闭合时间长短是由操作人员决定的,通常都会在 100ms 以上,刻意快速按的话能达到 40-50ms 左右,很难再低了。抖动时间是由按键的机械特性决定的,一般都会在 10ms以内,为了确保程序对按键的一次闭合或者一次断开只响应一次,必须进行按键的消抖处理。

下面代码中第1种方式的软件消抖是用的STM32平台的,挑取的是按键扫描一个函数;第2种方式的软件消抖是51平台的,为了便于理解,把整个工程代码都贴出来了,结合上下文容易理解。
/********************************方法1 ********************************/

/**************************************************************************************
 * 函数功能:扫描矩阵按键,期间用延时函数做按键消抖;保存按键状态以及按键键值
 * 参    数:
 * 返 回 值:
 * 注意事项:一定要在函数最后重新设置按键为全部监听状态,否则只能监听到最后一排的按键中断
 **************************************************************************************/
void KeyScan(void)
{
	u8 i, j;
	u16 tmp = GPIO_ReadOutputData(GPIOB);

	for(j=0; j<4; j++)
	{
		delayms(10);
		tmp |= (0xF<<8);
		tmp &= ~(1<<(8+j));
		GPIO_Write(GPIOB, tmp);
		for(i=0; i<4; i++)
		{
			if(GPIO_ReadInputDataBit(GPIOB, 1<<(12+i)) == 0x00)
			{
				key_value = key_map[j][i];
				key_state = PUSH;
				return;
			}	
		}	
	}

	key_state = POP;
	KeyAllListening();		/* 一定要重新设置按键监听状态 */

	return;
}

/********************************方法2 *********************************/

/**************************************************************************************
 *实验现象:根据按键键值做加法器,所按数字在数码管上显示,加法所得值也在数码管显示
 *接    线:P0.0 -> RC, P0.1 -> SC, P0.2 -> SE; J29 -> JP3; J6 -> J27; P1.0 -> A_138,
 			P1.1 -> B_138, P1.2 -> C_138;
 *注意事项:1、先弄明白矩阵按键和动态数码管显示的原理,也就是说实现该加法器功能的时候
 *			矩阵按键的状态和动态数码管的显示所需要的条件由谁生产,又由谁消费。所以这里
 *			中断函数的功能有2个:第1个是生产按键状态、第二个是消费数码管显示,也就是根
 *			据主程序给的数码管显示位数和数值进行刷新显示。
 *			2、业务逻辑要调试清楚,			
 **************************************************************************************/
#include <reg52.h>
#include <intrins.h>

#define BIT_16_LOAD		0x01
#define BIT_8_AUTOLOAD	0x00
#define ON				0x01
#define OFF				0x00
#define PUSH			0x00
#define POP				0x01


typedef unsigned char	uchar;
typedef unsigned int	uint;
typedef unsigned long	ulong;

sbit RC = P0^0;
sbit SC = P0^1;
sbit SE = P0^2;
sbit A_138 = P1^0;
sbit B_138 = P1^1;
sbit C_138 = P1^2;

sbit KEY_H1 = P3^0;
sbit KEY_H2 = P3^1;
sbit KEY_H3 = P3^2;
sbit KEY_H4 = P3^3;
sbit KEY_L1 = P3^4;
sbit KEY_L2 = P3^5;
sbit KEY_L3 = P3^6;
sbit KEY_L4 = P3^7;

/* 数码管字模 (0->9) */
unsigned char code byte_buff[] = {0xFC, 0x60, 0xDA, 0xF2, 0x66, 0xB6, 0xBE, 0xE0, 0xFE, 0xF6};
/* 138译码器真值表 (最右边->最左边) */
unsigned char code ctl_138[] = {0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0};
uchar cur_state[4][4] = {    
	{1, 1, 1, 1}, {1, 1, 1, 1}, 
	{1, 1, 1, 1}, {1, 1, 1, 1},
	};
uchar bak_state[4][4] = {
	{1, 1, 1, 1}, {1, 1, 1, 1}, 
	{1, 1, 1, 1}, {1, 1, 1, 1},
	};
uchar code key_map[4][4] = {
	{0x31, 0x32, 0x33, 0x25},//数字键 1、数字键 2、数字键 3、向上键
	{0x34, 0x35, 0x36, 0x26},//数字键 4、数字键 5、数字键 6、向左键
	{0x37, 0x38, 0x39, 0x27},//数字键 7、数字键 8、数字键 9、向下键
	{0x30, 0x1B, 0x0D, 0x28},//数字键 0、 ESC 键、 回车键、 向右键
	};
uchar key_tmp[8] = {0,0,0,0,0,0,0,0,};	
uchar val_tmp[8] = {0,0,0,0,0,0,0,0,};
uchar tube_pos;	
ulong display_val = 0;
uchar num_138;
	
void KeyDriver(uchar key_value);	
ulong SetNumber(uchar *array, uchar num);
void SelectDisplay(ulong value);
void ScanKey();
void ScanDisplay(uchar num);
void ConfigTimer0(uint ms);
void InitTimer0(uint ms);
void CleanDisplay();
void Display_138(unsigned char num);
void SendByte(uchar byte);


void main()
{
	uchar i, j;
	
	/* 定时2ms */
	InitTimer0(2);
	
	CleanDisplay();
	SelectDisplay(0);
		
	while(1)
	{
		for(j=0; j<4; j++)
		{
			for(i=0; i<4; i++)
			{
				if(cur_state[j][i] != bak_state[j][i])
				{
					if(cur_state[j][i] == PUSH)
					{
						KeyDriver(key_map[j][i]);
					}
					
					bak_state[j][i] = cur_state[j][i];
				}
			}
		}		
	}

	return;
} 


void KeyDriver(uchar key_value)
{
	static ulong tmp_value = 0;
	static ulong add_value = 0;
	
	/* 按第9个按键数字的时候,数码管显示最右边的0 */
	if(tube_pos > 7)
	{
		tube_pos = 0;
		num_138 = 0;		/* 只显示最右边一个数码管 */
		SelectDisplay(0);	/* 显示数字为0 */
		return;
	}

	/* 数字0-9的话就按输入顺序显示在数码管上 */
	if(key_value >= 0x30 && key_value <= 0x39)
	{
		CleanDisplay();
		key_tmp[tube_pos] = key_value - 0x30;		/* 计算键值是数字几 */
		tmp_value = SetNumber(key_tmp, tube_pos);	/* 按了几次按键,生成对应的数值 */
		display_val = tmp_value;
		SelectDisplay(display_val);
		tube_pos++;
	}	
	else if(key_value == 0x25)		/* 向上键设置为'+'号,数码管显示原来的数字 */
	{
		if(tmp_value != 0x25 && tmp_value != 0x0D && tmp_value != 0x26 && tmp_value != 0x27 && tmp_value != 0x28)
		{
			add_value += tmp_value;
			if(add_value > 999999999)
			{
				add_value = 0;
			}
			SelectDisplay(display_val);
			tmp_value = key_value;
			tube_pos = 0;
		}		
	}
	else if(key_value == 0x0D)		/* 回车键结束输入并计算值,数码管显示计算值 */
	{
		if(tmp_value != 0x25 && tmp_value != 0x0D && tmp_value != 0x26 && tmp_value != 0x27 && tmp_value != 0x28)
		{
			add_value += tmp_value;
			display_val = add_value;
			SelectDisplay(display_val);	
			tube_pos = 0;
			tmp_value = key_value;
			add_value = 0;
		}
	}
	else if(key_value == 0x1B)
	{
		CleanDisplay();
		SelectDisplay(0);
		tube_pos = 0;
		tmp_value = 0;
		add_value = 0;
	}
	else if(key_value == 0x26)
	{
		if(tmp_value != 0x25 && tmp_value != 0x0D && tmp_value != 0x26 && tmp_value != 0x27 && tmp_value != 0x28)
		{
			tmp_value = key_value;
			SelectDisplay(display_val);		
		}
	}
	else if(key_value == 0x27)
	{
		if(tmp_value != 0x25 && tmp_value != 0x0D && tmp_value != 0x26 && tmp_value != 0x27 && tmp_value != 0x28)
		{
			tmp_value = key_value;
			SelectDisplay(display_val);		
		}
	}
	else if(key_value == 0x28)
	{
		if(tmp_value != 0x25 && tmp_value != 0x0D && tmp_value != 0x26 && tmp_value != 0x27 && tmp_value != 0x28)
		{
			tmp_value = key_value;
			SelectDisplay(display_val);		
		}
	}

	return;
}

/*实现数码管数字显示 */
void SelectDisplay(ulong value)
{
	uchar i;
	
	val_tmp[0] = byte_buff[value%10];
	val_tmp[1] = byte_buff[(value/10)%10];
	val_tmp[2] = byte_buff[(value/100)%10];
	val_tmp[3] = byte_buff[(value/1000)%10];
	val_tmp[4] = byte_buff[(value/10000)%10];
	val_tmp[5] = byte_buff[(value/100000)%10];
	val_tmp[6] = byte_buff[(value/1000000)%10];
	val_tmp[7] = byte_buff[(value/10000000)%10];

	for(i=0; i<8; i++)
	{
		if(val_tmp[7-i] != byte_buff[0])		/* 判断需要显示几个数码管 */
		{
			num_138 = 7 - i;
			break;
		}
	}	
	
	return;
}

/* 根据保存的数组来计算按键输入的值是多少 */
ulong SetNumber(uchar *array, uchar num)
{
	ulong tmp = 0;
	
	if(num > 7)
		return 1;
	
	switch(num)
	{
		case 0:
			tmp = (ulong)array[0];
			break;
		case 1:
			tmp = (ulong)array[0]*10 + (ulong)array[1];
			break;
		case 2:
			tmp = (ulong)array[0]*100 + (ulong)array[1]*10 + (ulong)array[2];
			break;
		case 3:
			tmp = (ulong)array[0]*1000 + (ulong)array[1]*100 + (ulong)array[2]*10 + (ulong)array[3];
			break;
		case 4:
			tmp = (ulong)array[0]*10000 + (ulong)array[1]*1000 + (ulong)array[2]*100 + (ulong)array[3]*10 + (ulong)array[4];
			break;
		case 5:
			tmp = (ulong)array[0]*100000 + (ulong)array[1]*10000 + (ulong)array[2]*1000 + (ulong)array[3]*100 + (ulong)array[4]*10 + (ulong)array[5];				
			break;
		case 6:
			tmp = (ulong)array[0]*1000000 + (ulong)array[1]*100000 + (ulong)array[2]*10000 + (ulong)array[3]*1000 + (ulong)array[4]*100 + (ulong)array[5]*10 + (ulong)array[6];	
			break;
		case 7:
			tmp = (ulong)array[0]*10000000 + (ulong)array[1]*1000000 + (ulong)array[2]*100000 + (ulong)array[3]*10000 + (ulong)array[4]*1000 + (ulong)array[5]*100 + (ulong)array[6]*10 + (ulong)array[7];	
			break;
		default:
			break;		
	}
	
	return tmp;
}

/* 中断处理函数 */
void InterruptTimer0() interrupt 1
{
	ConfigTimer0(2);
	
	/* 刷新按键 */
 	ScanKey();
	
	/* 刷新数码管 */
	ScanDisplay(num_138);
}

/*扫描按键并生成状态 */
void ScanKey()
{
	static uchar tmp_buf[4][4] = {
		{0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF}, {0xFF,0xFF,0xFF,0xFF},  
	};
	static uchar pos = 0;
	uchar i;
	
	/* 检测按键状态 */
	switch(pos)
	{
		case 0:
			KEY_H1 = 0; KEY_H2 = 1; KEY_H3 = 1; KEY_H4 = 1;
			KEY_L1 = 1; KEY_L2 = 1; KEY_L3 = 1; KEY_L4 = 1;
			break;
		case 1:
			KEY_H1 = 1; KEY_H2 = 0; KEY_H3 = 1; KEY_H4 = 1;
			KEY_L1 = 1; KEY_L2 = 1; KEY_L3 = 1; KEY_L4 = 1;
			break;
		case 2:
			KEY_H1 = 1; KEY_H2 = 1; KEY_H3 = 0; KEY_H4 = 1;
			KEY_L1 = 1; KEY_L2 = 1; KEY_L3 = 1; KEY_L4 = 1;
			break;
		case 3:
			KEY_H1 = 1; KEY_H2 = 1; KEY_H3 = 1; KEY_H4 = 0;
			KEY_L1 = 1; KEY_L2 = 1; KEY_L3 = 1; KEY_L4 = 1;
			break;			
		default:
			break;		
	}
	
	/* 巧妙利用定时器来隔断时间检测按键状态并保存值 */
	tmp_buf[pos][0] = (tmp_buf[pos][0] << 1) | KEY_L1;
	tmp_buf[pos][1] = (tmp_buf[pos][1] << 1) | KEY_L2;
	tmp_buf[pos][2] = (tmp_buf[pos][2] << 1) | KEY_L3;
	tmp_buf[pos][3] = (tmp_buf[pos][3] << 1) | KEY_L4;
	
	for(i=0; i<4; i++)
	{
		if((tmp_buf[pos][i] & 0x0F) == 0x00)
		{
			cur_state[pos][i] = PUSH;
		}
		else if((tmp_buf[pos][i] & 0x0F) == 0x0F)
		{
			cur_state[pos][i] = POP;
		}
	}
	
	/* 自加超过4就从0开始 */
	pos++;
	pos = pos & 0x03; 

	return;
}

/* 根据数值和对应的显示位数进行刷新数码管显示 */
void ScanDisplay(uchar num)
{
	static uchar tmp_num = 0;
	
	if(tmp_num > num)
	{
		tmp_num = 0;
	}
	
	switch(tmp_num)
	{
		case 0:
			Display_138(0);	break;
		case 1:
			Display_138(1);	break;	
		case 2:
			Display_138(2);	break;
		case 3:
			Display_138(3);	break;	
		case 4:
			Display_138(4);	break;
		case 5:
			Display_138(5);	break;	
		case 6:
			Display_138(6);	break;
		case 7:
			Display_138(7);	break;	
		default:
			break;					
	}
	
	tmp_num++; 
}

/* 根据函数参数配置定时时间 */
void ConfigTimer0(uint ms)
{
	uint tmp = 0;
	tmp = 65536 - (12/12)*ms*1000;
	
	TH0 = (tmp >> 8) & 0xFF;
	TL0 = tmp & 0xFF;

	return;
}

/* 初始化定时器中断模式 */
void InitTimer0(uint ms)
{
	TMOD = BIT_16_LOAD;
	ConfigTimer0(ms);
	TR0 = ON;
	ET0 = ON;
	EA = ON;
}

/*清屏函数 */
void CleanDisplay()
{
	num_138 = 0;
	SendByte(0);

	return;
}

/* 选择显示数码管并发送显示数据到数码管 */
void Display_138(unsigned char num)
{
	SendByte(0);		/* 必须清屏,不然显示有虚影 */
	A_138 = (ctl_138[num] >> 0) & 0x1;
	B_138 = (ctl_138[num] >> 1) & 0x1;
	C_138 = (ctl_138[num] >> 2) & 0x1;
	SendByte(val_tmp[num]);
}


/* 74HC595发送数据 */
void SendByte(uchar byte)
{
	uchar i;
	
	RC = 1;
	SC = 1;
	
	for(i=0; i<8; i++)
	{
		SC = 0;
		SE = (byte >> i) & 0x01;
		SC = 1;	
	}
	
	RC = 0;
	_nop_();
	_nop_();
	RC = 1;

	return;
}

最后

以上就是细腻金针菇为你收集整理的关于单片机按键状态检测方法的一些体会-----按键消抖的全部内容,希望文章能够帮你解决关于单片机按键状态检测方法的一些体会-----按键消抖所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(72)

评论列表共有 0 条评论

立即
投稿
返回
顶部