51单片机_按键检测

51单片机_按键检测

一、独立按键介绍

轻触按键相当于是一种电子开关

按下时开关接通,松开时开关断开,实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开

image-20260109110408710

image-20260109110503289

由于机械点的弹性作用,按键开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开。因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定,一般为5ms到10ms。

按键稳定闭合时间的长短由操作人员的按键动作决定,一般为零点几秒至数秒。按键抖动会引起按键被误读多次。为了确保CPU对按键的依次闭合仅作一次处理,必须及时进行消抖。

按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更加简单,通常采用软件消抖。

软件消抖一般来说一个简单的按键消抖就是先读取按键的状态,如果得到按键按下之后,延时10ms,再次读取按键状态,如果按键还是按下状态,那么说明按键已经按下。其中的延时10ms就是软件消抖处理。

常用的软件去抖动方法:

  1. 先设置IO口为高电平,由于开发板IO有上拉电阻,默认IO为高电平;
  2. 读取IO口电平确认是否有按键按下;
  3. 如果有IO电平为低电平后,延时几个毫秒;
  4. 再读取该IO电平,如果仍为低电平,说明按键按下;
  5. 执行相应按键的程序;

二、独立按键检测原理

独立按键电路构成是由各个按键的一个管脚连接在一起接地,按键其它引脚分别接到单片机IO口。

单片机的IO口既可作为输出也可作为输入使用,当检测按键时用的是它的输入功能,独立按键的一端接地,另一端与单片机的某个IO口相连,开始时先给IO口赋一高电平,然后让单片机不断检测该IO口是否变为低电平,当按键闭合时,相当于该IO口通过按键与地相连,变成低电平,程序一旦检测到IO口变为低电平则说明按键被按下,然后执行相应的指令。

image-20260109110908433

三、独立按键应用实践

1)实现按键控制LED功能

#include <REGX52.H>
#include "Delay.h"

/**
 * 主函数
 */
void main()
{
	while(1)
	{
		if(P3_1 == 0)  			  // 如果K1按键按下,则为低电平
		{
			Delay(20);			  // 延时消抖
			while(P3_1 == 0);	  // 松手检测
			Delay(20);			  // 延时消抖
		
			P2_0 = ~P2_0;		  // LED1 取反
		}
	}
}

这段代码依旧存在不灵敏的情况,下面是改进:

#include <REGX52.H>
#include "Delay.h"

void main()
{
    while(1)
    {
        if(P3_1 == 0)              // 检测按键是否按下
        {
            Delay(10);             // 延时消抖
            if(P3_1 == 0)          // 再次确认按键确实按下
            {
                P2_0 = ~P2_0;      // 执行LED取反
                
                // 等待按键释放,避免连按
                while(P3_1 == 0);  // 等待按键松开
            }
        }
    }
}

2)按键控制LED灯二进制显示

#include <REGX52.H>
#include "Delay.h"

void main()
{
	unsigned char LEDNum=0;
	while(1)
	{
		if(P3_1==0)					//如果K1按键按下
		{
			Delay(10);				//延时消抖
			if(P3_1==0)
			{
			  LEDNum++;				//变量自增
			  P2=~LEDNum;			//变量取反输出给LED
				while(P3_1==0);	    //松手检测
			}
		}
	}
}

//  实现原理:
//  +000  LEDNum 0000 0000  	取反 —>  1111 1111
//  +001  LEDNum 0000 0001  	取反 —>  1111 1110
//  +002  LEDNum 0000 0010  	取反 —>  1111 1101
//  +003  LEDNum 0000 0011  	取反 —>  1111 1100
//  ···
//  +255  LEDNum 1111 1111  	取反 —>  0000 0000
//  +256  溢出 -> LEDNum 0000 0000  重新开始计数

3)按键实现LED左右移动

#include <REGX52.H>
#include "Delay.h"

unsigned char LEDNum;

void main()
{
	P2=~0x01;					//上电默认LED1点亮 0000 0001 取反 -> 1111 1110
	while(1)
	{
		if(P3_0==0)			    //如果K1按键按下,右移
		{ 
			Delay(20);
			while(P3_0==0);
			Delay(20);
			
			LEDNum++;			 //LEDNum自增
			if(LEDNum>=8)		 //限制LEDNum自增范围
				LEDNum=0;
			P2=~(0x01<<LEDNum);	 //LED的第LEDNum位点亮
		}
		if(P3_1==0)				 //如果K2按键按下,左移
		{
			Delay(20);
			while(P3_1==0);
			Delay(20);
			
			if(LEDNum==0)		 //LEDNum减到0后变为7
				LEDNum=7;
			else				 //LEDNum未减到0,自减
				LEDNum--;
			P2=~(0x01<<LEDNum);  //LED的第LEDNum位点亮
		}
	}
}

P2=~(0x01<<LEDNum); 实现原理如下:

LEDNum 0x01 << LEDNum (二进制) 取反后 (二进制) 效果说明
0 0000 0001 1111 1110 LED0亮,其他灭
1 0000 0010 1111 1101 LED1亮,其他灭
2 0000 0100 1111 1011 LED2亮,其他灭
3 0000 1000 1111 0111 LED3亮,其他灭
4 0001 0000 1110 1111 LED4亮,其他灭
5 0010 0000 1101 1111 LED5亮,其他灭
6 0100 0000 1011 1111 LED6亮,其他灭
7 1000 0000 0111 1111 LED7亮,其他灭

四、矩阵按键

独立键盘与单片机连接时,每一个按键都需要单片机的一个I/O口。若某单片机系统需要较多按键,如果用独立按键便会占用过多的I/O口资源。

当用到多个按键时,为了减少I/O口引脚,引入了矩阵按键。比如4*4矩阵键盘。

对于4*4矩阵键盘,开发板上通常将16个按键排成4行4列。第一行将每个按键的一端连接在一起构成行线,第一列将每个按键的另一端连接在一起构成列线,这样便一共有4行4列共8根线。将这8根线连接到单片机的8个I/O口上,通过程序扫描键盘可以检测16个键。

无论是独立键盘还是矩阵键盘,单片机检测其是否被按下的依据都是一样的,即检测与该键对应的I/O口是否为低电平。独立键盘有一端固定为低电平而矩阵键盘两端都与单片机I/O口相连,在检测时需编程通过单片机I/O口送出低电平。检测方法最常用的是行列扫描和线翻转法。

  • 行列扫描法检测时,先送一列为低电平,其余几列全为高电平,此时确定列数;然后立即轮流检测一次各行是否有低电平,若检测到某一行为低电平,此时确定了行数,便可以确认当前被按下的键是哪一行哪一列的。用同样方法轮流送各列一次低电平,再轮流检测一次各行是否变为低电平,这样即可检测完所有的按键,当有按键被按下时便可判断出按下的键是哪一个键。
  • 线翻转法检测时,就是使所有行线为低电平时,检测所有列线是否有低电平,如果有,就记录列线值;然后再翻转,使所有列线都为低电平,检测所有行线的值。由于有按键按下,行线的值也会有变化,记录行线的值,从而就可以检测到全部按键。

矩阵按键也需要按键消抖。

image-20260109120836392

image-20260109121508035

image-20260109123822715

以第一列代码作为示例,简单介绍下 逐列扫描 的检测原理:

image-20260109124004422

  • P1=0xFF; 把所有按键端口置为高电平,也就是关闭所有按键
  • P1_3=0; 将第一列按键置为低电平,其他按键依旧为高电平(关闭状态)
  • 使用 If判断 检测行按键,如果某一行为低电平,就返回 KeyNumber 编号

其他3列按键的检测原理同上

五、矩阵按键应用实践

1)实现LCD1602显示按键编号

main.c

#include <REGX52.H>
#include "Delay.h"		  //包含Delay头文件
#include "LCD1602.h"	  //包含LCD1602头文件
#include "MatrixKey.h"	  //包含矩阵键盘头文件

unsigned char KeyNum;

void main()
{
	LCD_Init();							          //LCD初始化
	LCD_ShowString(1,1,"MatrixKey:");	          //LCD显示字符串
	while(1)
	{
		KeyNum=MatrixKey();				          //获取矩阵键盘键码
		if(KeyNum)						          //如果有按键按下
		{
			LCD_ShowNum(2,1,KeyNum,2);	          //LCD显示键码
		}
	}
}

Delay.c


void Delay(unsigned int xms)
{
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
	}
}

Delay.h

#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);

#endif

LCD1602.c

#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

LCD1602.h

#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

MatrixKey.c

#include <REGX52.H>
#include "Delay.h"

/**
  * @brief  矩阵键盘读取按键键码
  * @param  无
  * @retval KeyNumber 按下按键的键码值
			如果按键按下不放,程序会停留在此函数,松手的一瞬间,返回按键键码,没有按键按下时,返回0
  */
unsigned char MatrixKey()
{
	unsigned char KeyNumber=0;
	
	P1=0xFF;  // 1111 1111 全部置高电平,没有按键按下
	P1_3=0;   // 检测第一列
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}
	
	P1=0xFF;  // 1111 1111 全部置高电平,没有按键按下
	P1_2=0;   // 检测第二列
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}
	
	P1=0xFF;  // 1111 1111 全部置高电平,没有按键按下
	P1_1=0;   // 检测第三列
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}
	
	P1=0xFF;  // 1111 1111 全部置高电平,没有按键按下
	P1_0=0;   // 检测第四列
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}
	if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}
	if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}
	if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}
	
	return KeyNumber;
}

MatrixKey.h

#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__

unsigned char MatrixKey();

#endif

2)使用矩阵键盘实现密码锁功能

只需要修改main.c ,其余代码同上

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"

unsigned char KeyNum;
unsigned int Password,Count;

void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"Password:");
	while(1)
	{
		KeyNum=MatrixKey();
		if(KeyNum)
		{
			if(KeyNum<=10)	                      //如果S1~S10按键按下,输入密码
			{                                    
				if(Count<4)	                       //如果输入次数小于4
				{                                  
					Password*=10;				   //密码左移一位
					Password+=KeyNum%10;		   //获取一位密码
					Count++;	                   //计次加一
				}
				LCD_ShowNum(2,1,Password,4);	   //更新显示
			}                                    
			if(KeyNum==11)	                       //如果S11按键按下,确认
			{                                    
				if(Password==2345)	               //如果密码等于正确密码
				{                                  
					LCD_ShowString(1,14,"OK ");	   //显示OK
					Password=0;		               //密码清零
					Count=0;		               //计次清零
					LCD_ShowNum(2,1,Password,4);   //更新显示
				}
				else				               //否则
				{
					LCD_ShowString(1,14,"ERR");	   //显示ERR
					Password=0;		               //密码清零
					Count=0;		               //计次清零
					LCD_ShowNum(2,1,Password,4);   //更新显示
				}
			}
			if(KeyNum==12)	                       //如果S12按键按下,取消
			{
				Password=0;		                   //密码清零
				Count=0;		                   //计次清零
				LCD_ShowNum(2,1,Password,4);	   //更新显示
			}
		}
	}
}

Password*=10; //密码左移一位

Password+=KeyNum%10; //获取一位密码

图表:按键序列 5 → 6 → 3 → 8 的处理过程

按键 运算前 Password Password × 10 KeyNum % 10 运算后 Password 解释
5 0 0 × 10 = 0 5 % 10 = 5 5 第一个数字直接存入
6 5 5 × 10 = 50 6 % 10 = 6 56 5移到十位,6放在个位
3 56 56 × 10 = 560 3 % 10 = 3 563 56变为560,3放在个位
8 563 563 × 10 = 5630 8 % 10 = 8 5638 563变为5630,8放在个位
posted @ 2026-01-09 14:49  Q&25  阅读(138)  评论(0)    收藏  举报