单片机按键扫描实现短按_长按_重复_组合键功能详解

在单片机项目中,按键操作通常是产品与用户交互必不可少功能,按键又有短按、长按、重复、组合键等操作,本文介绍了一种按键扫描的实现方法,能够实现短按、长按、重复以为组合键的功能。

短按:即按下按键马上松开,如图1,按下的 时间tl小于允许的最大时间t_max,则认为是短按
长按:即按下按键3秒后再松开,如图2,按下的 时间tl大于时间t_lmax
重复:即一直按住按键不放,重复该按键,如图3,按下的时间达到t_rep,产生一次按键,达到时间r1又产生一次按键,以此重复。
我们可以通过计算按下的时间长短来确定是短按还是长按,在松开按键时,产生相应的键值。
在这里插入图片描述
扫描操作流程图
在这里插入图片描述

头文件

#ifndef __KEY_H
#define	__KEY_H

#include "stm8l15x.h"
#include "stm8l15x_gpio.h"
#include "stm8l15x_exti.h"

/*--------按键标记 ------*/
#define KEY_NO	0x00	//无按键
#define KEY_S1	0x01	//按键1
#define KEY_S2	0x02	//按键2
#define KEY_S3	0x04
#define KEY_S4	0x08
#define KEY_REP_FLAG	0x40	//重复按下
#define KEY_LONG_FLAG	0x80	//长按3秒标记

//键值
typedef enum _key{
	KEY_OFF				= KEY_NO,	//没有任何键按下
	KEY_RIGHT			= KEY_S2,  //向右键
	KEY_DN				= KEY_S3,	//向下键
	KEY_UP				= KEY_S1,	//向上键
	KEY_LEFT			= KEY_S4,  //向左键
	KEY_L_UP			=(KEY_LONG_FLAG|KEY_UP), //左键+上键
	KEY_L_DN			=(KEY_LONG_FLAG|KEY_DN),
	KEY_L_RIGHT			=(KEY_LONG_FLAG|KEY_RIGHT),
	KEY_L_LEFT			=(KEY_LONG_FLAG|KEY_LEFT),
	KEY_R_UP			=(KEY_REP_FLAG|KEY_UP),
	KEY_R_DN			=(KEY_REP_FLAG|KEY_DN),
	KEY_R_RIGHT			=(KEY_REP_FLAG|KEY_RIGHT),
	KEY_R_LEFT			=(KEY_REP_FLAG|KEY_LEFT),
	KEY_L_UP_DN			=(KEY_LONG_FLAG|KEY_UP|KEY_DN), //+ - 长按
	KEY_L_LEFT_RIGHT	=(KEY_LONG_FLAG|KEY_RIGHT|KEY_LEFT), //OK ESC 长按
	KEY_L_LEFT_DN		=(KEY_LONG_FLAG|KEY_DN|KEY_LEFT), //DN ESC 长按
}KeyValue_ENUM;

/*----------按键计时-----------*/
#define KEY_DOWN_CNT	50  //按下时长
#define KEY_SPEED		40	//重复按下重载时间
#define KEY_REP_3S		2	//重复速度

//*-----------IO-----------*/
#define KEY1_PORT	GPIOA
#define KEY1_PIN	GPIO_Pin_2

#define KEY2_PORT	GPIOA
#define KEY2_PIN	GPIO_Pin_3

#define KEY3_PORT	GPIOC
#define KEY3_PIN	GPIO_Pin_0

#define KEY4_PORT	GPIOC
#define KEY4_PIN	GPIO_Pin_1

/*-----------读取按键-----------*/
#define Key1_ReadSta()		((BitStatus)(KEY1_PORT->IDR&KEY1_PIN))
#define Key2_ReadSta()		((BitStatus)(KEY2_PORT->IDR&KEY2_PIN))
#define Key3_ReadSta()		((BitStatus)(KEY3_PORT->IDR&KEY3_PIN))
#define Key4_ReadSta()		((BitStatus)(KEY4_PORT->IDR&KEY4_PIN))

/*--------函数-------*/
void KEY_Init(void);
KeyValue_ENUM KeyScan(void);

#endif /* __KEY_H */

代码

/************************************************************
Copyright (C), 2013-2020
@FileName: KEY.C
@Author  : 糊读虫 QQ:570525287
@Version : 2.0
@Date    : 2019-8-12
@Description: 按键扫描
@Function List:
@History    : 
<author> <time> <version > <desc>

***********************************************************/
#include "key.h"

//IO初始化
void KEY_Init(void)
{
	GPIO_Init(KEY1_PORT,KEY1_PIN,GPIO_Mode_In_PU_No_IT); //上拉无中断
	GPIO_Init(KEY2_PORT,KEY2_PIN,GPIO_Mode_In_PU_No_IT); //上拉无中断
	GPIO_Init(KEY3_PORT,KEY3_PIN,GPIO_Mode_In_PU_No_IT); //上拉无中断
	GPIO_Init(KEY4_PORT,KEY4_PIN,GPIO_Mode_In_PU_No_IT); //上拉无中断
}

//按键扫描
KeyValue_ENUM KeyScan(void)
{
	uint8_t io_value;
	static uint8_t key_Press;  // 这个是要返回的键值
	static uint8_t key_Old = 0x00;
	static uint8_t DownCnt = 0; //按下计数
	static struct _sta{
		uint8_t up:		1;  //弹起
		uint8_t dn:		1;	//按下
		uint8_t rep:	1;
	}Status;  //按键状态标记
	uint8_t ret;  
	
	io_value = 0x00;
	ret = KEY_OFF;

	//获取当前按下的按键
	if (!Key1_ReadSta())io_value |= KEY_S1;
	if (!Key2_ReadSta())io_value |= KEY_S2;
	if (!Key3_ReadSta())io_value |= KEY_S3;
	if (!Key4_ReadSta())io_value |= KEY_S4;

	if (io_value) //如果按键被按下
	{
		if (key_Old == io_value) //判断与上次扫描到的是否为同一个按键
		{
			DownCnt++; //计数

			if(DownCnt < KEY_DOWN_CNT)//当前计数小于长按的时间
			{
				if (Status.rep == 0) // 不是重复
				{
					Status.dn = 1;
					key_Press = io_value; //记下按键
				}				
			}

			if (DownCnt >= KEY_DOWN_CNT) //当前计数大于长按的时间
			{
				Status.rep = 1;  //标记重复

				if(CntLongPress++ == KEY_REP_3S)//按下超过3秒
				{
					ret = io_value | KEY_LONG_FLAG; //加长按标记
				}
				else
				{
					ret = io_value | KEY_REP_FLAG; //重复
				}
				
				DownCnt = KEY_SPEED;	//重复起始值				
			}
		}
		key_Old = io_value;	 //记录键值
	}
	else  //按键松开
	{
		if (Status.dn && !Status.rep) //返回松开前的键值
		{
			ret = key_Press;
		}

		//清除标记
		Status.dn = 0;
		Status.rep = 0;
		DownCnt = 0;
		key_Old = KEY_NO;
	}

	return (KeyValue_ENUM)ret;  //返回键值
}
//--------------END OF FILE---------------

在主函数中每隔20ms扫描一次

#include "key.h"

void main()
{
	//....
	KEY_Init();
	while(1)
	{
		if(delay_20ms == 1)
		{
			KeyValue_ENUM key;
			delay_20ms = 0;
			key = KeyScan();
			
			switch(key)
			{
			case KEY_RIGHT:
				//处理按键
				break;
			case KEY_UP:
				//处理按键
				break;
			//.....
			}	
		}
	}
}

### 回答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(); } } 在实际开发中,我们可以根据需要对上述代码进行修改和优化,以满足不同的应用场景。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值