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

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

短按:即按下按键马上松开,如图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;
			//.....
			}	
		}
	}
}

以下是一个基于状态机的STM32按键短按组合按键的示例代码: ```c /* 定义按键状态机的不同状态 */ typedef enum { KEY_IDLE = 0, // 按键空闲状态 KEY_SHORT, // 按键短按状态 KEY_LONG, // 按键按状态 KEY_COMB // 按键组合按状态 } KeyState_t; /* 定义按键结构体 */ typedef struct { GPIO_TypeDef* port; // 按键所在的GPIO端口 uint16_t pin; // 按键所在的GPIO引脚 uint32_t shortPressMs; // 短按时间阈值(单位:毫秒) uint32_t longPressMs; // 按时间阈值(单位:毫秒) uint32_t combPressMs; // 组合按时间阈值(单位:毫秒) KeyState_t state; // 当前按键状态 uint32_t pressTimeMs; // 按键按下的时间点(单位:毫秒) } Key_t; /* 定义按键结构体数组 */ Key_t keys[] = { {GPIOA, GPIO_PIN_0, 50, 1000, 5000, KEY_IDLE, 0}, {GPIOB, GPIO_PIN_12, 50, 1000, 5000, KEY_IDLE, 0}, {GPIOC, GPIO_PIN_1, 50, 1000, 5000, KEY_IDLE, 0}, // ... 其他按键 }; /* 定义按键扫描函数 */ void KeyScan(void) { static uint32_t lastTick = 0; uint32_t tick = HAL_GetTick(); if (tick - lastTick < 10) { // 每10毫秒扫描一次按键 return; } lastTick = tick; for (int i = 0; i < sizeof(keys) / sizeof(keys[0]); i++) { Key_t* key = &keys[i]; GPIO_PinState pinState = HAL_GPIO_ReadPin(key->port, key->pin); switch (key->state) { case KEY_IDLE: if (pinState == GPIO_PIN_RESET) { key->state = KEY_SHORT; key->pressTimeMs = tick; } break; case KEY_SHORT: if (pinState == GPIO_PIN_SET) { if (tick - key->pressTimeMs < key->shortPressMs) { key->state = KEY_IDLE; } else if (tick - key->pressTimeMs < key->longPressMs) { key->state = KEY_SHORT; } else if (tick - key->pressTimeMs < key->combPressMs) { key->state = KEY_LONG; } else { key->state = KEY_COMB; } } break; case KEY_LONG: case KEY_COMB: if (pinState == GPIO_PIN_SET) { key->state = KEY_IDLE; } break; } } } ``` 在上述代码中,首先定义了一个按键状态机的不同状态,然后定义了一个按键结构体,该结构体包含了按键所在的GPIO端口和引脚、短按时间阈值、按时间阈值、组合按时间阈值、当前按键状态以及按键按下的时间点等信息。接着定义了一个按键结构体数组,其中包含了所有需要扫描的按键。 在按键扫描函数中,首先判断是否到达了扫描的时间间隔,如果没有则直接返回。然后依次扫描每个按键,根据当前按键状态和GPIO引脚的电平状态进行状态转移。具体来说,当按键处于空闲状态时,如果检测到GPIO引脚的电平为低电平,则将按键状态转移为短按状态,并记录按键按下的时间点;当按键处于短按状态时,如果检测到GPIO引脚的电平为高电平,则根据按键按下的时间点和短按时间阈值、按时间阈值、组合按时间阈值进行状态转移;当按键处于按状态或组合按状态时,如果检测到GPIO引脚的电平为高电平,则将按键状态转移为空闲状态。最后,按键扫描函数结束。 需要注意的是,上述代码中使用了HAL库中的函数进行GPIO操作和时间戳获取,需要根据实际情况进行修改。此外,还需要在主函数中周期性地调用按键扫描函数,例如使用定时器中断进行调用。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值