【单片机笔记】状态机效率地按键扫描、识别短按、长按及松手检测方法

按键是人机交互最简单也是最廉价的方式之一,要实现一个或者多个按键的有效扫描并处理,这里附上我修改过的代码:

实现的代码主要包含有四个部分:

第一部分:按键的初始化部分

void Key_Configuration(void)
{
	return;
}

这里需要根据所使用的IC来做不同的配置方式,我使用的是51内核,在初始化的过程I/O口默认做了准双向若上拉处理,按键低电平有效,所以就没有处理直接跳出去。

第二部分:按键的电平读取

//只读取初次按键电平状态,在状态机中进一步处理
static u8 Key_Read(void)
{
    if(!READ_KEY1)  
		return KEY1_PRES;
    if(!READ_KEY2)  
		return KEY2_PRES;       
    if(!READ_KEY3)  
		return KEY3_PRES;
    if(!READ_KEY4)  
		return KEY4_PRES;
    if(!READ_KEY5)  
		return KEY5_PRES;
	
	return KEY_NONE;
}

根据使用的具体环境及功能,这里每次读取电平只读取一个有效的电平并且有优先级,由代码可以看出优先级的顺序为:KEY1>KEY2>KEY3>KEY4>KEY5。当然需要使用多少个按键根据项目的需求来定,理论支持多少个独立按键都是可以的。

第三部分:状态机的按键判定部分

//状态机
static u8 Key_Scan(void)
{
	static u8 state = 0; //按键初始化状态
	static u8 KEY_LAST=0,KEY_NOW=0; //记录两次电平的状态
	u8 KEY_VALUE=0;

	KEY_NOW = Key_Read();//读按键值
   
	switch(state)
	{
		case 0:
		{
			if(KEY_NOW != KEY_LAST)	state = 1; //有按键按下
		}break;
		case 1: 
		{
			if(KEY_NOW == KEY_LAST)	state = 2; //消斗之后按键有效
			else state = 0; //认为误触
		}break; 
		case 2: //消斗之后
		{
			if(KEY_NOW == KEY_LAST) //还是按下的状态 
			{
			  	state = 3;
			}
			else//松开了,短按
			{
				state = 0; 
				
				KEY_VALUE = KEY_LAST|KEY_SHORT;  //返回键值短按	
			}
		}break;
		
		case 3: //判断长按短按
		{
			if(KEY_NOW == KEY_LAST) 
			{
			    static u8 cnt = 0;
				if(cnt++ > 120) //1200ms
				{
					cnt = 0; 
					state = 4;
					KEY_VALUE = KEY_LAST|KEY_LONG; //返回键值长按
				}			  
			}
			else
			{
				state = 0;
				KEY_VALUE = KEY_LAST|KEY_SHORT; //返回键值短按			
			}
		}break;
		case 4://长按松手检测
		{
			if(KEY_NOW != KEY_LAST) 
				state = 0;
		}break;
	}//switch
	
	KEY_LAST = KEY_NOW; //更新
	return KEY_VALUE;
}

这部分也是整个的核心代码部分,首先定义了三个静态变量,按键的状态state,当前读取的键值KEY_NOW,上一次的键值KEY_LAST,以及返回的判定后的有效键值KEY_VALUE。接下来一步步研究:

state初始值为0,进入switch和case,每次进入case判断的时间间隔是由第四部分来确定的,这里给出我选用的是10ms

判定0,只要上一次记录的键值和本次读取到的键值不想等,则进入1。这里还有另一个作用,那就是短按的松手检测。这也是没有else的原因,具体如何实现请看状态4。

判定1,上一次和这一次的键值相等,注意case 0后KEY_LAST已经被更新,也就是说10ms后这次读取到的键值还等于上一次的键值,这里我们认为是有效的按下,而并非误触和干扰造成的,这种情况下进入2。否则就返回到0。

判定2,这里是从1过来的,也就是此时的按键键值是有效的,这里还是来判断上次更新的KEY_LAST及这次读取到的新的键值,如果不想等,证明手已经松开(单一按键的情况)。这样就识别成了短按,state重新回到0并返回短按的键值量,在0。如果按键还没有被释放那就有长按的趋势了,进入3。

判定3,这里也很简单,判定键值有没有释放,每次进来就开始计次,10ms进入一次,这里计120次也就是需要1200ms的时间达到条件并且把返回的键值赋值成长按,同时进入4,反之没有达到时间就识别成短按并重新进入0。

判定4,这里代码的作用主要是作为长按的松手检测,道理也很简单,按键没有释放,那肯定历史键值和当前的键值相等并且不为0,等按键释放的后,读取的键值肯定为0,这就跳出了状态4。

第四部分:实体函数及被调用函数

static void KEY1_ShortHander(void)
{

}
static void KEY1_LongHander(void)
{

}
static void KEY2_ShortHander(void)
{

}
static void KEY2_LongHander(void)
{

}
static void KEY3_ShortHander(void)
{

}
static void KEY3_LongHander(void)
{

}
static void KEY4_ShortHander(void)
{

}
static void KEY4_LongHander(void)
{

}
static void KEY5_ShortHander(void)
{

}
static void KEY5_LongHander(void)
{

}


void Key_Hander(void)					//按键处理函数
{
	u8 KEY_NUM=0;
	static u32 LAST=0;
	if(Systick_ms-LAST<10)	return;
	LAST = Systick_ms;	
	
	KEY_NUM = Key_Scan();  //按键扫描值
	if(KEY_NUM == KEY_NONE) return;
	
	//有按键按下
	if(KEY_NUM & KEY_SHORT) //短按
	{    
		if(KEY_NUM & KEY1_PRES)//KEY1_PRES
		{				
			KEY1_ShortHander();
		}
		else if(KEY_NUM & KEY2_PRES)//KEY2_PRES
		{
			KEY2_ShortHander();
		}		
		else if(KEY_NUM & KEY3_PRES)//KEY3_PRES
		{
			KEY3_ShortHander();
		}
		else if(KEY_NUM & KEY4_PRES)//KEY4_PRES
		{
			KEY4_ShortHander();
		}
		else if(KEY_NUM & KEY5_PRES)//KEY5_PRES
		{
			KEY5_ShortHander();
		}
	}
	else if(KEY_NUM & KEY_LONG) //长按 
	{
		if(KEY_NUM & KEY1_PRES)//KEY1_PRES
		{
			KEY1_LongHander();
		}
		else if(KEY_NUM & KEY2_PRES)//KEY2_PRES
		{
			KEY2_LongHander();
		}		
		else if(KEY_NUM & KEY3_PRES)//KEY3_PRES
		{
			KEY3_LongHander();
		}
		else if(KEY_NUM & KEY4_PRES)//KEY4_PRES
		{
			KEY4_LongHander();
		}
		else if(KEY_NUM & KEY5_PRES)//KEY5_PRES
		{
			KEY5_LongHander();
		}
	}	
}

这部分代码是比较清晰的,通过前面3部分的分析,在这里调用前3部分的得到键值的结果,然后判定结果做相应的函数和功能处理,这里给的直接是函数体,用户可以直接在函数体里面添加响应的代码。但更有效的方式应该是把执行函数换成标记变量,这样程序执行会更加有条理并且不错误的占用时间片。

最后附上头文件:

#ifndef __FY_KEY_H
#define __FY_KEY_H

#include "fy_includes.h"

#define READ_KEY1	P30
#define READ_KEY2	P14
#define READ_KEY3	P13
#define READ_KEY4	P10
//#define READ_KEY5	P17

#define KEY1_PRES 0x01
#define KEY2_PRES 0x02
#define KEY3_PRES 0x04
#define KEY4_PRES 0x08
#define KEY5_PRES 0x10

#define KEY_SHORT  0x40
#define KEY_LONG   0x80
#define KEY_NONE      0

#define HOT_KEY_PRES	KEY1_PRES
#define PWR_KEY_PRES	KEY2_PRES
#define H2_KEY_PRES	KEY3_PRES
#define MP3_KEY_PRES	KEY4_PRES

void Key_Configuration(void);
void Key_Hander(void);

#endif

/*********************************************END OF FILE**********************************************/

 By Urien 2018年3月19日 10:47:36
### 回答1: 在STM32单片机中,通过状态机实现按键按与短按是一种常见的做法。状态机是一种计算机程序设计思想,它根据输入和内部状态的变化来确定输出行为。 在按键按与短按的实现中,我们需要考虑按键的两种不同状态:按下和弹起。具体实现步骤如下: 1. 定义按键状态 首先需要定义按键的状态变量,可以用枚举类型或宏定义,例如: typedef enum{ KEY_IDLE, KEY_SHORT_PRESS, KEY_LONG_PRESS }KEY_STATE; 2. 初始化按键状态 在初始化时,将按键状态初始化为KEY_IDLE,表示按键处于空闲状态,即未被按下或弹起。 3. 检测按键状态 在每次中断中检测按键状态,如果按键按下,则将状态变量设置为KEY_SHORT_PRESS,如果按键一直被按下,则将状态变量设置为KEY_LONG_PRESS。当按键被弹起时,将状态变量重新设置为KEY_IDLE。 4. 处理按键事件 根据按键状态变量的不同值来执行相应的操作。例如,当按键状态变量为KEY_SHORT_PRESS时,执行短按操作;当按键状态变量为KEY_LONG_PRESS时,执行按操作。 通过这种方法,我们可以实现按键按与短按的功能。需要注意的是,由于按键抖动等原因,需要对按键输入信号进行去抖处理。这里可以利用定时器来实现按键去抖。同时,还需要设置适当的按时间阈值,来确定按的时间范围。 ### 回答2: STM32单片机是一种广泛应用于电子工程的微控制器。其中实现按键功能主要是通过状态机实现按和短按的功能。 所谓状态机,就是将状态进行分类,并以此为基础对I/O接口进行判断和控制。实现按键状态机,需要通过以下三个基本状态:Idle(空闲)、Press(按下)和Release(抬起)。 当用户按下按键时,状态转变为Press,此时需要启动一定的计时器来计算按键的持续时间,如果按键持续时间小于一定的时间阈值,就可以判断这个按键短按;反之,如果按键持续时间于一定的时间阈值,就可以判断这个按键按。 为了实现按键的状态转换,还需要一些状态标志来协助实现,比如:按键是否按下标志,按键按下后计数器,按键短按的时间阈值,按键按的时间阈值等。 举个例子,如果我们要实现实现PB8引脚的按键状态机按键功能,可以采取下面的步骤: 定义状态变量state、按键按下计数器count、按键按下标志flag、按键短按时间阈值shortTime、按键按时间阈值longTime。 初始化所有状态变量,使其达到初始状态。 在主循环中监测按键是否被按下,并更新状态变量。 若按键按下,将flag设为true,计数器count清零,并进入Press状态。 若按键抬起,将flag设为false,计算按键按下持续时间,根据时间阈值,判断是按还是短按,并根据不同结果,实现不同的响应。 以上仅仅是一个简化的按键状态机的实现过程。实际进行状态机编程需要充分考虑各种情况的差异,以避免状态机失控的情况发生,同时对状态机的各种标志进行准确的复位。 ### 回答3: stm32单片机按键状态机实现按与短按 在实际开发中,我们经常需要对按键进行扫描,并根据按键的不同状态进行相应的处理。一种常用的做法就是使用按键状态机。通过按键状态机,我们可以简单明了地实现按键短按按功能。 按键状态机的实现步骤如下: 1. 定义按键状态枚举类型 在程序中定义按键状态的枚举类型,包括三种状态:按下释放按。 2. 定义按键状态结构体 在程序中定义按键状态结构体,包括按键状态、按键计时器和按计时器等。 3. 编写按键状态机函数 按键状态机函数主要包括按键扫描按键状态判断两个部分。按键扫描是以一定的时间间隔去扫描按键状态,根据当前按键状态和保存的按键状态来判断按键处于短按按还是释放状态。 4. 调用按键状态机函数 将按键状态机函数放在主循环中调用,即可完成按键状态的检测和处理。 下面是一份简单的示例代码: //按键状态枚举类型 typedef enum { KEY_STATE_UP = 0, KEY_STATE_DOWN, KEY_STATE_LONG }key_state_e; //按键状态结构体 typedef struct { key_state_e state; //按键状态 uint8_t timer_cnt; //按键计时器 uint8_t long_timer_cnt; //按计时器 uint8_t scan_interval; //按键扫描间隔 }key_status_t; //按键状态机函数 void key_state_machine(void) { static key_status_t key_status = {KEY_STATE_UP, 0, 0, 5}; uint8_t key_value = 0; //按键扫描 if(key_status.timer_cnt >= key_status.scan_interval) { key_value = get_key_value(); switch(key_status.state) { case KEY_STATE_UP: if(key_value == 0) //按键按下 { key_status.state = KEY_STATE_DOWN; key_status.timer_cnt = 0; } break; case KEY_STATE_DOWN: if(key_value == 0) //按计时 { key_status.timer_cnt = 0; key_status.long_timer_cnt ++; if(key_status.long_timer_cnt >= 200) //按200ms { key_status.long_timer_cnt = 200; key_status.state = KEY_STATE_LONG; //按状态 key_long_click_callback(); //按回调函数 } } else //短按处理 { key_status.timer_cnt = 0; key_status.long_timer_cnt = 0; key_status.state = KEY_STATE_UP; //回到按键状态 key_short_click_callback(); //短按回调函数 } break; case KEY_STATE_LONG: if(key_value != 0) //回到按键状态 { key_status.timer_cnt = 0; key_status.long_timer_cnt = 0; key_status.state = KEY_STATE_UP; } break; default: break; } key_status.timer_cnt = 0; //清零计时器 } else { key_status.timer_cnt ++; //计时器加一 } } //主函数 int main(void) { //初始化系统和按键 system_init(); key_init(); while(1) { //按键状态机处理 key_state_machine(); } } 在实际开发中,我们可以根据需要对上述代码进行修改和优化,以满足不同的应用场景。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值