三行代码按键消抖 独立按键 矩阵按键 长按 短按 双击

本文围绕单片机按键展开,介绍了按键抖动、稳定闭合时间等基本概念,以及查询、定时、中断三种扫描方式。阐述了普通软件延时、标志位软件延时等常见消抖算法,重点解析了三行代码消抖算法,还提及独立按键和矩阵键盘的扫描、消抖及长按、双击、复合按键的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

九层妖塔 起于垒土

在这里插入图片描述


直接跳转到三行代码




改编自国信长天蓝桥杯官方蓝皮书例程,按照自己的习惯进行了补充和修改


链接
1、独立按键抖动产生原因;普通软件延时消抖;标志位软件延时消抖;标志位定时器延时消抖
2、矩阵键盘的识别与编码;查询扫描;定时扫描;中断扫描;使用定时器状态机的方法进行按键的扫描识别
3、定时器三行代码 矩阵键盘长短按的识别



一、基本理论

0、按键的常见名词:

 ①按键抖动

 当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动。抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。

在这里插入图片描述
尝试用示波器的触发捕获按键抖动波形,只捕获到了比较明显的松手抖动。
在这里插入图片描述
下图是串口返回的IO口内部数学电平(不知道是采样频率的问题还是按键的问题,并没有观察到方波)
在这里插入图片描述

 ②按键稳定闭合时间

按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为100ms 以上至数秒。

在这里插入图片描述

完整的按键识别分为两部分:按键的扫描按键的消抖

1、按键的扫描:

 按键的扫描就是通过程序判断与按键相连的IO口的电平状态。通过相关IO口的电平状态来判断按键的抬起和按下。
①查询扫描
  把按键扫描子程序和其他子程序并列在一起,单片机循环分时运行各个子程序。当按键按下并且单片机查询到的时候立即响应键盘输入操作。
②定时扫描
  定时器,灵活多样,方法较多。
③中断扫描
  依赖于硬件(如四路与门等),利用外部中断。

2、按键的消抖:

 由于上文所说的 按键抖动 ,需要对IO口电平进行消抖处理。消抖分为硬件消抖和软件消抖。常见的硬件消抖有 使用电容进行延时消抖 、S触发器进行硬件去抖等。
 在此旨在介绍全面的软件消抖算法。



二、常见的按键消抖算法

1、普通软件延时

 ●消抖:软件延时。CPU在按键按下抖动时进行10ms的软件延时消抖,在数百ms的稳定期以及松手抖动时停滞在松手检测while(!KEY)中,占用CPU资源过多;也就是在整个按键检测期间内,没有再执行其他指令。
 ●松手检查:while(!KEY)等待按键释放。 等按键确认释放后再去执行相应代码。防止重入。
 ●按键检测时是借助while(1)死循环进行反复检测。
 ●若是采用普通软件延时消抖10ms的软件延时和松手检测都会占用CPU资源,对其它同样借助while(1)死循环进行反复运行的模块代码产生了挤占影响,比如动态数码管。
 ●稳定期取决于是否松手,若不松手,则数码管将一直保持进入按键检测前最后一次对数码管的操作。

最低级的方法,没有实用性。
 ●流程图:
在这里插入图片描述
 ●示例代码

	while(1)
	{
		if(key==0)
		{
			Delay10ms();
			if(key==0)
			{
              //按键按下执行相关功能代码                   
			  while(!key);
			}
		}
	}

2、标志位软件延时

 ●消抖:软件延时
 ●通过引入按下标志位,省去了while(!KEY)的松手检测,将按下时稳定期的数百ms释放出来,只占用了按下和松手时的共20ms的消抖时间。

标志位的使用使算法具有了一定的实用性,优点是不借助硬件资源
 ●波形图:
在这里插入图片描述


3、标志位定时器延时

 ●消抖:定时器延时。使用定时器进行延时消抖。
 ●使用标志位代替松手检测。

文前链接中有解释和程序,写的不太成熟,与状态机类似

4、状态机

 也是一种利用标志位定时器延时按键算法,但比较成熟,逻辑严谨。

状态机进行按键的扫描识别

bug最少,算法成熟,但程序量比较大,不太方便移植。


5、三行代码

目前已知最好的按键算法,将按键的识别与扫描完全分开,便于移植,拓展性好。



三行代码

完整的按键程序可以包括:按键的扫描按键的消抖 两部分。
三行代码 属于 按键的消抖 部分的算法,与是独立按键还是矩阵键盘无关。
三行代码 其实就是一种时域滤波算法–消抖滤波法。

1、按键的扫描

按键的扫描 采用定时器进行扫描。10ms扫描一次IO口电平状态,即10ms读取一次键值。通过Key_Read()函数读取。

unsigned char Key_Read(void)
{
  Key_Value = P01; //任意一个IO口的电平值保存在变量中
}

①独立按键的扫描

基于蓝桥杯单片机开发板

uchar Key4_Read(void)    //独立按键扫描函数,读取键值
{
    uchar Key_temp;
	uchar Key_Value;
	
	P3 |= 0x0f;
	Key_temp = P3&0x0f;
	
	switch(Key_temp)
	{
	    case 0x0e : Key_Value = 7; break;  //S7
		case 0x0d : Key_Value = 6; break;  //S6
		case 0x0b : Key_Value = 5; break;  //S5
		case 0x07 : Key_Value = 4; break;  //S4
		default: Key_Value = 0;
	}
	
	return Key_Value;
}

Notes:
 ●程序中P3 |= 0x0f; Key_temp = P3&0x0f;两行属于按键扫描的识别部分
 ●switch属于按键扫描的编码部分

 ●对程序中P3 |= 0x0f; Key_temp = P3&0x0f;两行的解释:
  ○P3 |= 0x0f;P3低四位即独立按键接口拉高置为1;Key_temp = P3&0x0f;读取P3低四位I/O口的电平。
  ○重点是读取I/O口外部电平状态前,需要先将I/O口置为1。 STC15的芯片手册中有介绍:STC15F2系列单片机I/O口上电默认为准双向口,在324页有相关的介绍和使用说明。截去一段关于读取外部状态的使用说明:
在这里插入图片描述

再来看国信长天给的电子版例程:
在这里插入图片描述
emmm……似乎不太严谨…………

①矩阵键盘的扫描

基于蓝桥杯单片机开发板

uchar Key16_Read(void)    //矩阵按键扫描函数,读取键值
{
    uint  Key_temp;
	uchar Key_Value;
	
	
	//按键扫描的识别部分
	P44=0; P42=1; P35=1; P34=1; P3|=0X0F; //第一列拉低,其余全为高
	Key_temp = P3;                        //读取P3低四位I/O状态
	
	P44=1; P42=0; P35=1; P34=1; P3|=0X0F;  //第二列拉低,其余全为高
	Key_temp = (Key_temp<<4) | (P3&0X0F);  //读取P3低四位I/O状态
	
	P44=1; P42=1; P35=0; P34=1; P3|=0X0F; //第三列拉低,其余全为高
	Key_temp = (Key_temp<<4) | (P3&0X0F); //读取P3低四位I/O状态
	
	P44=1; P42=1; P35=1; P34=0; P3|=0X0F; //第四列拉低,其余全为高
	Key_temp = (Key_temp<<4) | (P3&0X0F);  //读取P3低四位I/O状态
	
	//有按键按下Key_temp相应位为0	
	
	
	//按键扫描的编码部分
	switch(~Key_temp)   
	{
		case 0X8000: Key_Value = 4; break;   //S4
		case 0X4000: Key_Value = 5; break;   //S5
		case 0X2000: Key_Value = 6; break;   //S6
		case 0X1000: Key_Value = 7; break;   //S7
		
		case 0X0800: Key_Value = 8; break;   //S8
		case 0X0400: Key_Value = 9; break;   //S9
		case 0X0200: Key_Value = 10; break;   //S10
		case 0X0100: Key_Value = 11; break;   //S11
		
		case 0X0080: Key_Value = 12; break;   //S12
		case 0X0040: Key_Value = 13; break;   //S13
		case 0X0020: Key_Value = 14; break;   //S14
		case 0X0010: Key_Value = 15; break;   //S15
		
		case 0X0008: Key_Value = 16; break;   //S16
		case 0X0004: Key_Value = 17; break;   //S17
		case 0X0002: Key_Value = 18; break;   //S18
		case 0X0001: Key_Value = 19; break;   //S19
		
		default: Key_Value = 0;  //无按键按下
	}
	return Key_Value;
	
}

Notes:
 ●依旧是因为准双向口读取I/O口外部电平状态前,需要先将I/O口置为1,所以将国信长天蓝色指导书上的 按键扫描的识别部分改成了上边的样子。

指导书上的程序
在这里插入图片描述

  按键的扫描已经介绍完了,按键扫描函数Key_Read()放在定时器中进行扫描,10ms执行一次,即10ms读取一次I/O状态,进行一次键值编码。
  可以知道按键扫描函数Key_Read()10ms一次读取到的键值是 瞬态的,只能反映按键此刻的状态,无法反映按下按键抬起按键的稳定过程。


2、按键的消抖

直接上代码(三行代码):

uchar Key_Temp,Key_Down,Key_Up;
static uchar Key_Old = 0;

Key_Temp = Key_Read();
Key_Down = Key_Temp & (Key_Old ^ Key_Temp);   //按下为按键值,其它为0
Key_Old = Key_Temp;
	

Notes:
 ●三行代码第一行:Key_Temp = Key_Read(); 读取10ms更新一次的I/O电平状态,并存储在变量Key_Temp,可以理解为临时按键值
 ●三行代码第三行:Key_Old = Key_Temp;Key_Old为静态局部变量,离开函数,值仍保留:数据存储在静态存储区,在程序整个运行期间都不释放,且只能在该函数中调用。将这次读取到的临时按键值Key_Temp更新到Key_Old中,作为下一次的旧的按键值;概括说Key_TempKey_Old为相差10ms的临时按键值。
 ●三行代码第二行:Key_Down = Key_Temp & (Key_Old ^ Key_Temp);两个位操作:按位与,按位异或。
  ○首先Key_Old ^ Key_Temp位操作针对二进制,二进制与十进制一 一对应。按位异或:相同为0,不同为1。由下图可知:Key_OldKey_Temp可能出现的情况:(假设按下的是按键4)
  Key_Old=0, Key_Temp=0 未按下 。Key_Old ^ Key_Temp=0
  Key_Old=0, Key_Temp=4 按下过程中。Key_Old ^ Key_Temp=0100=4
  Key_Old=4, Key_Temp=4 按下稳定期间。 Key_Old ^ Key_Temp=0000
  Key_Old=4, Key_Temp=0 抬起过程中。 Key_Old ^ Key_Temp=0100=4
再次说明由于是用定时器扫描,Key_OldKey_Temp的按键值相差10ms,不可能出现Key_Old=4, Key_Temp=6 两个按键值的情况。

  ○Key_Old ^ Key_Temp的运算结果再&Key_Temp

Key_OldKey_Temp对应的按键过程Key_Old ^ Key_TempKey_Down
00未按下00
04按下过程中0100 (4)4
44按下稳定期间00
40抬起过程中0100 (4)0

所以由上表可知:三行代码第二行Key_Down = Key_Temp & (Key_Old ^ Key_Temp); 最后的运算结果Key_Down只有在按键按下的过程中为按键值,持续时间大约10ms。

 ●可以在原有三行代码的基础上再增加一行,来判断按键抬起的过程。

Key_Up  = ~Key_Temp & (Key_Old ^ Key_Temp);  //松手为抬起前的按键值,其他为0 

Key_DownKey_Up识别的分别是按键按下和抬起时的下降沿和上升沿

在这里插入图片描述


3、相关变量值的变化

 由以上分析可知:

按键过程Key_DownKey_Up
未按下00
按下过程中相应的按键值0
按下稳定期间00
抬起过程中0抬起前的按键值

 所以可以将Key_DownKey_Up理解为临时值,只在按下或抬起过程中不为0,又 按键的扫描 Key_Read() 采用定时器进行扫描。10ms扫描一次,数据10ms更新一次。临时值Key_DownKey_Up为局部变量,生存期只在子函数中,只能在按键处理函数Key_Proc()内部使用。


4、按键处理函数

①独立按键的处理函数

//-----------------------------------------独立按键处理函数---------------//
void Key4_Proc(void)     //独立按键处理函数
{
    uchar Key_Temp,Key_Down,Key_Up;
    static uchar Key_Old = 0;
	
	if(uc_Key_flag) return;  //10ms
	uc_Key_flag = 1;
	
	Key_Temp = Key4_Read();
	Key_Down = Key_Temp & (Key_Old ^ Key_Temp);   //按下为按键值,其它为0
	Key_Up  = ~Key_Temp & (Key_Old ^ Key_Temp);  //松手为抬起前的按键值,其他为0 
	Key_Old = Key_Temp;
	
		
	if(Key_Down)
	{
		uc_Key_state = 'd';
        uc_Key_Value = Key_Down;
	}
 
	if(Key_Up)
	{
		uc_Key_state = 'U';
	}
}

②矩阵键盘的处理函数

void Key16_Proc(void)     //矩阵按键处理函数	
{
    uchar Key_Value,Key_Down,Key_Up;
    static uchar Key_Old = 0;
	
	if(uc_Key_flag) return;  //10ms
	uc_Key_flag = 1;
	
	Key_Value = Key16_Read();
	Key_Down = Key_Value & (Key_Old ^ Key_Value);   //按下为按键值,其它为0
	Key_Up  = ~Key_Value & (Key_Old ^ Key_Value);  //松手为抬起前的按键值,其他为0 
	Key_Old = Key_Value;

	if(Key_Down)
	{
	  uc_Key_state = 'd';
	  uc_Key_Value = Key_Down;

	}
 
	if(Key_Up)
	{
	  uc_Key_state = 'U';
      uc_Key_Value = Key_Up;
	}
}

Notes:
 ●观察独立按键/矩阵键盘的处理函数可知,定义了两个全局变量uc_Key_Valueuc_Key_state来分别向全局传递按键值和按键状态。
 ●uc_Key_Valueuc_Key_state对比临时值Key_DownKey_Up,可以理解为状态值,10ms刷新一次,可以在全局中传递。


5、长按

长按一

 顾名思义,长按 区别于 短按 在于时间,又因为STC15没有实时时钟,只能通过定时器来搭建全局时间。用定时器1作为1ms定时器,用作程序内的ms计时。

创建全局变量ul_ms,用作ms计时。

unsigned long ul_ms;

在定时器1的中断服务函数或者自定义的查询服务函数中:

ul_ms++;  //ms计时

按键长按短按的实现只需在按键处理函数中增加代码:

//-----------------------------------------独立按键处理函数---------------//
void Key4_Proc(void)     //独立按键处理函数
{
    uchar Key_Temp,Key_Down,Key_Up;
    static uchar Key_Old = 0;
	static ulong ul_Key_Time_Down = 0;
	static ulong ul_Key_Time_Up = 0;
	
	if(uc_Key_flag) return;  //10ms
	uc_Key_flag = 1;
	
	Key_Temp = Key4_Read();
	Key_Down = Key_Temp & (Key_Old ^ Key_Temp);   //按下为按键值,其它为0
	Key_Up  = ~Key_Temp & (Key_Old ^ Key_Temp);  //松手为抬起前的按键值,其他为0 
	Key_Old = Key_Temp;
	
		
	if(Key_Down)
	{				
		uc_Key_state = 'd';
		
		ul_Key_Time_Down = ul_ms;  //记录按键按下时的时刻
		
	}
 
	if(Key_Up)
	{
		uc_Key_state = 'U';

		ul_Key_Time_Up = ul_ms;  //记录按键抬起时的时刻
		
		switch(Key_Up)
		{
			case 7 : uc_Key_Value = 7; break;
			case 6 : uc_Key_Value = 6; break;
			case 5 : uc_Key_Value = 5; break;
			case 4 : uc_Key_Value = 4; break;
		}
		
		//长按判断  
		if(ul_Key_Time_Up - ul_Key_Time_Down >1000)
		{
		  uc_Key_Value = uc_Key_Value +10;
		}
	}

}

Notes:
 ●有长按的要求,按键的功能最好放在按键抬起时刻。即在按键抬起时刻相应事件再响应。

长按二

  static ulong ul_Key4_Presstime = 0;   //计时按键按下状态的时间
  static bit Key4_Down_flag = 0;        //按按下标志位
  static bit Key4_Up_Enable = 0;        //抬起触发使能标志位

	if(Key_Down) //下降沿检测
	{	
		switch(Key_Down)
		{
		  case 4:
			{
			  ul_Key4_Presstime = ul_time;  //在按键按下的时刻 开始记录时间
				Key4_Down_flag = 1;   //按下标志位 置1
				Key4_Up_Enable = 1;   //使能抬起标志位
			}break;
			…………
	   }
	 }

	if(Key_Up)  //上升沿检测
	{
		switch(Key_Up)
		{
		  case 4:
			{
				if(Key4_Up_Enable == 1)  //抬起标志位已经使能
				{
					Key4_Down_flag = 0;  //清除按下标志位
					Key4_Up_Enable = 0;  //清除抬起标志位
					
					uc_Key_Value = 40;  //短按功能
				}
		    }
    }  


	if( (( ul_time - ul_Key4_Presstime)>1000) && (Key4_Down_flag == 1) && (Key_Old == 4) && (ul_Key4_Presstime != 0))
	{
	    ul_Key4_Presstime = ul_time;
		//ul_Key4_Presstime = 0;
		Key4_Up_Enable = 0;
		
	    uc_Key_Value += 1;
	}

6、双击

//-----------------------------------------独立按键处理函数---------------//
void Key4_Proc(void)     //独立按键处理函数
{
    uchar Key_Temp,Key_Down,Key_Up;
    static uchar Key_Old = 0;
	
	static ulong ul_Key_Time_Down_First = 0;  //记录按键 第一次 按下时的时刻
	static ulong ul_Key_Time_Down_Second = 0; //记录按键 第二次 按下时的时刻
	static uchar Key_First = 0;   //记录按键 第一次 按键值
    static uchar Key_Second = 0;  //记录按键 第二次 按键值
	
	if(uc_Key_flag) return;  //10ms
	uc_Key_flag = 1;
	
	Key_Temp = Key4_Read();
	Key_Down = Key_Temp & (Key_Old ^ Key_Temp);   //按下为按键值,其它为0
	Key_Up  = ~Key_Temp & (Key_Old ^ Key_Temp);  //松手为抬起前的按键值,其他为0 
	Key_Old = Key_Temp;
	
		
	if(Key_Down)
	{				
		uc_Key_state = 'd';
		
		if( (Key_First == 0) && (Key_Second == 0) )  //未有按键按下
		{
		  ul_Key_Time_Down_First = ul_ms;  //记录按键 第一次 按下时的时刻
		  Key_First = Key_Down;  //记录按键 第一次 按键值
			
		}
		else if( (Key_First != 0) && (Key_Second == 0) )  //已有一次按键按下,此次是第二次
		{
		  ul_Key_Time_Down_Second = ul_ms;  //记录按键 第二次 按下时的时刻
		  Key_Second = Key_Down;  //记录按键 第二次 按键值
				
		}
		
	}
 
	if(Key_Up)
	{
		uc_Key_state = 'U';
	}
   
	
	//双击、单击的判断
	 if( (Key_First != 0) && (ul_ms - ul_Key_Time_Down_First >500) )  //有第一次按键按下,并且时间>500ms
		{
		  if(Key_Second == 0)    //第二次没有按下, 单击
			{
			 		switch(Key_First)
							{
								case 7 : uc_Key_Value = 7; break;
								case 6 : uc_Key_Value = 6; break;
								case 5 : uc_Key_Value = 5; break;
								case 4 : uc_Key_Value = 4; break;
							}
				 
							Key_First = 0;Key_Second = 0;  //清零
			}
			else if(Key_Second == Key_First)  //第二次有按下,且和第一次相等 ,双击
			{
              uc_Key_Value = Key_Second +10;
			  Key_First = 0;Key_Second = 0; //清零
			}
			else if( (Key_Second != 0) && (Key_Second != Key_First) )  //第二次有按下,但和第一次不相等 ,错误
			{
			    uc_Key_Value = 'E';
				Key_First = 0;Key_Second = 0; //清零
			}
			
		}
	
}

Notes:
 ●按键双击的判断周期设为500ms,即按键500ms响应一次,判断是单击、双击还是错误。


7、复合按键

 类似于Ctrl+CCtrl+V 在长按一个的情况下,再单击另一个。
复合按键 是在按键扫描函数中进行修改。

uchar Key4_Read(void)    //独立按键扫描函数,读取键值
{
    uchar Key_temp;
	uchar Key_Value;
	
	P3 |= 0x0f;
	Key_temp = P3&0x0f;
	
	switch(Key_temp)
	{
	    case 0x0e : Key_Value = 7; break;  //S7
		case 0x0d : Key_Value = 6; break;  //S6
		case 0x0b : Key_Value = 5; break;  //S5
		case 0x07 : Key_Value = 4; break;  //S4
		
		 //S4的复合按键部分
	    case 0x06 : Key_Value = 47; break;  //S4 + S7
		case 0x05 : Key_Value = 46; break;  //S4 + S6
		case 0x03 : Key_Value = 45; break;  //S4 + S5
		
		default: Key_Value = 0;
	}
	
	return Key_Value;
}

  
  
  
  
彩 蛋

<think>好的,我现在需要帮助用户理解蓝桥杯单片机比赛中按键三行代码实现及其原理。首先,我应该回顾一下按键的基本知识。机械按键在按下和释放时会产生动,导致电平不稳定,通常在10ms左右。如果不处理,单片机可能会误判多次按键操作。 用户提到的三行代码可能是一种简洁的实现方式,常见的方法是通过延时和状态检测来动。例如,先读取当前按键状态,延时一段时间后再次读取,比较两次状态是否一致,从而判断是否为有效按键。 接下来,我需要确认这三行代码的具体内容。可能类似于: 1. 读取当前按键状态:key_current = P3_0; 2. 延时:delay_ms(10); 3. 再次读取并确认:if (key_current == P3_0) { ... } 但用户提到的是三行代码,可能更精简,比如连续读取两次并结合判断。例如: if (P3_0 == 0) { delay_ms(10); if (P3_0 == 0) { ... } } 不过这可能超过三行。另一种可能是使用位操作或状态变量来简化代码。例如,通过移位寄存器记录按键状态,每次读取后更新,并通过位运算判断是否稳定。 需要查阅蓝桥杯相关资料,确认常见的三行代码实现方式。例如,有的资料提到用三行代码实现按键检测和,可能结合了状态机和逻辑。例如: static uint8_t key_state = 0; key_state = (key_state << 1) | (P3_0 & 0x01); if (key_state == 0x7F) { ... } 这里通过移位操作检测连续的低电平,当连续8次(假设)检测到低电平时认为按键有效,但具体参数可能不同。 用户提供的引用[1]提到了按键三行代码,可能需要结合该引用内容。引用中的描述可能包括:读按键、点灯三个步骤。例如: 1. 读取按键状态并取反:key = ~P3; 2. 延时:delay_ms(10); 3. 再次读取并确认:if ((~P3 & 0x0F) == key) { ... } 不过具体实现可能因硬件连接而异。例如,按键连接到P3口的某些引脚,需要屏蔽其他位。 另外,蓝桥杯比赛中可能使用特定的开发板,如CT107D,按键连接在P3口的P30-P33,所以代码可能涉及这些引脚。例如: if ((P3 & 0x0F) != 0x0F) { // 检测是否有按键按下 delay_ms(10); if ((P3 & 0x0F) != 0x0F) { // 再次确认 // 处理按键 } } 这可能被简化为三行代码,例如通过函数封装或宏定义。 总结来说,三行代码的关键在于两次读取按键状态并比较,中间加入延时。例如: 1. 检测按键是否按下(初次读取) 2. 延时 3. 再次检测并确认状态一致 因此,代码可能如下: if (P3_0 == 0) { // 第一行:检测按键是否按下 delay_ms(10); // 第二行:延时 if (P3_0 == 0) { // 第三行:再次确认 // 执行操作 } } 但这里的核心逻辑确实由这三行完成。可能用户提到的“三行代码”指的是的关键部分,省略了其他处理步骤。 需要进一步解释每行代码的作用,并结合原理图说明按键的连接方式。例如,按键按下时,对应的引脚会被拉低,因此检测低电平。延时通常为10ms,避开动期。 此外,需要提到可能的状态机方法或其他优化方式,但用户问题主要关注三行实现,所以重点放在简洁的延时检测方法。 最后,生成相关问题时,需要考虑按键的其他方法、参数调整、硬件设计影响等。</think>### 单片机蓝桥杯按键三行代码实现及原理 #### 按键原理 机械按键在按下或释放时,由于金属触点弹性会导致电平在短时间内(约$10ms$)发生多次动(如图)。若不处理,单片机会误判为多次按键操作[^1]。的核心是通过 **延时检测** 或 **状态滤波** 确认按键稳定。 #### 三行代码实现 **典型场景**:蓝桥杯开发板(如CT107D)按键连接在$P3$口(例如$P3_0$~$P3_3$),按下时引脚电平被拉低。 **代码示例**: ```c if ((P3 & 0x0F) != 0x0F) { // 1.初次检测是否有按键按下(低电平) delay_ms(10); // 2.延时10ms避开动期 if ((P3 & 0x0F) != 0x0F) // 3.再次确认按键状态稳定 // 执行按键操作(如点灯) } ``` **逐行解析**: 1. **首次检测**:`(P3 & 0x0F) != 0x0F` 表示$P3$口的低4位(按键引脚)有至少一个为低电平(按键按下)。 2. **延时**:`delay_ms(10)` 跳过动期(约$10ms$),等待电平稳定。 3. **二次确认**:再次检测电平是否仍为低,排除动干扰。 #### 关键点 - **硬件原理**:按键按下时引脚接地(低电平),松开时为高电平[^1]。 - **时间**:$10ms$是经验值,可根据实际按键特性调整。 - **代码优化**:通过位运算直接操作端口,提高效率。 ```c // 示例:按下P3_0点亮LED(假设LED连接P0_0) if ((P3 & 0x01) == 0) { // 检测P3_0是否按下 delay_ms(10); // 延时 if ((P3 & 0x01) == 0) P0_0 = ~P0_0; // 翻转LED状态 } ```
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

#法外狂徒张三

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

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

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

打赏作者

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

抵扣说明:

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

余额充值