这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 分享一种检测按键状态的方法

共1条 1/1 1 跳转至

分享一种检测按键状态的方法

高工
2025-05-31 14:37:42   被打赏 20 分(兑奖)     打赏

最近在参加论坛的活动,需要外部中断按键实现一些功能,而ST的开发板上面只有一个按键,这里和大家分享一种外键按键:按下,抬起和常按的三种不同状态的处理方法。

初学按键检测时候,一般就是读取IO口的状态,然后使用系统的延时函数,再次判断IO口的状态,当两次状态一致时候,认为是有效触发,然后程序去执行按键处理函数。这种方法的局限性只能是实现一种按键状态的检测:比如是按键按下的状态,放到主函数中,由于延时函数的存在,很容易导致程序在这里死等,造成程序阻塞,造成其他模块运行不正常,如有交互式的模块急需处理或者是正在处理,很容易导致程序的其他模块运行不正常。

下面和大家分享一下,之前用到的一种检测按键的方法:

主要是依赖于STM32的基本定时器完成按键的按下、抬起和常按的功能;

优点:软件去抖的时间可调,长按时间可调,基本没有延时,无阻塞,可实现稳定触发;

而且,代码移植方便,只需要修改下底层的读取按键状态函数就可以,配置IO口为输入模式即可。

按键代码如下所示:

/**************************************************************************************
KeyBoard.c    按键处理部分 
zhaoruicong
2025年5月31日
**************************************************************************************/

#include "define.h"
#include "global.h" 
#include "keyboard.h" 
#include "stdio.h"
#include "string.h"
#include "stdint.h"
void DealKey(uint8_t KeyValue);
static BUTTON_T s_KEY1;		/* KEY1 	键 */
static KEY_FIFO_T s_Key;		/* 按键FIFO变量,结构体 */
/*
	定义12个函数,判断按键是否按下,返回值1 表示按下,0表示未按下
*/

static uint8_t IsKey1(void) 	{if (HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_13)  == GPIO_PIN_SET) return 0; return 1;}
																	  
/*******************************************************************************
	函数名: InitButtonVar
	输  入:
	输  出:
	功能说明:初始化所有的按键变量,这个函数需要在systic中断启动钱调用1次
*/
void InitButtonVar(void)
{
	/* 对按键FIFO读写指针清零 */
	s_Key.Read = 0;
	s_Key.Write = 0;

	/* 初始化Key1按键变量 */
	s_KEY1.IsKeyDownFunc = IsKey1;	/* 判断按键按下的函数 */
	s_KEY1.FilterTime = BUTTON_FILTER_TIME;	/* 按键滤波时间 */
	s_KEY1.LongTime = BUTTON_LONG_TIME;		/* 长按时间 */
	s_KEY1.Count = s_KEY1.FilterTime / 2;	/* 计数器设置为滤波时间的一半 */
	s_KEY1.State = 0;						/* 按键缺省状态,0为未按下 */
	s_KEY1.KeyCodeDown = KEY1_DOWN;		/* 按键按下的键值代码 */
	s_KEY1.KeyCodeUp = KEY1_UP;			/* 按键弹起的键值代码 */
	s_KEY1.KeyCodeLong = KEY1_LONG_state;		/* 按键被持续按下的键值代码 */ 

}

/*******************************************************************************
	函数名: PutKey
	输  入: 键值
	输  出:
	功能说明:将1个键值压入按键FIFO缓冲区
*/
void PutKey(uint8_t _KeyCode)
{
	s_Key.Buf[s_Key.Write] = _KeyCode;

	if (++s_Key.Write  >= KEY_FIFO_SIZE)
	{
		s_Key.Write = 0;
	}
}

/*******************************************************************************
	函数名: GetKey
	输  入:
	输  出: 返回键值, KEY_NONE ( = 0) 表示无键按下
	功能说明:从按键FIFO取1个键值
*/
uint8_t GetKey(void)
{
	uint8_t ret;

	if (s_Key.Read == s_Key.Write)
	{
		return KEY_NONE;
	}
	else
	{
		ret = s_Key.Buf[s_Key.Read];

		if (++s_Key.Read >= KEY_FIFO_SIZE)
		{
			s_Key.Read = 0;
		}
		return ret;
	}
}

/*******************************************************************************
	函数名:DetectButton
	输  入: 按键结构变量指针
	输  出:
	功能说明:检测指定的按键
*/
static void DetectButton(BUTTON_T *_pBtn)
{
	/* 如果没有初始化按键函数,则报错
	if (_pBtn->IsKeyDownFunc == 0)
	{
		printf("Fault : DetectButton(), _pBtn->IsKeyDownFunc undefine");
	}
	*/

	if (_pBtn->IsKeyDownFunc())
	{
		if (_pBtn->Count < _pBtn->FilterTime)
		{
			_pBtn->Count = _pBtn->FilterTime;
		}
		else if(_pBtn->Count < 2 * _pBtn->FilterTime)
		{
			_pBtn->Count++;
		}
		else
		{
			if (_pBtn->State == 0)
			{
				_pBtn->State = 1;

				/* 发送按钮按下的消息 */
				if (_pBtn->KeyCodeDown > 0)
				{
					/* 键值放入按键FIFO */
					PutKey(_pBtn->KeyCodeDown);
				}
			}

			if (_pBtn->LongTime > 0)
			{
				if (_pBtn->LongCount < _pBtn->LongTime)
				{
					/* 发送按钮持续按下的消息 */
					if (++_pBtn->LongCount == _pBtn->LongTime)
					{
						/* 键值放入按键FIFO */
						PutKey(_pBtn->KeyCodeLong);
					}
				}
			}
		}
	}
	else
	{
		if(_pBtn->Count > _pBtn->FilterTime)
		{
			_pBtn->Count = _pBtn->FilterTime;
		}
		else if(_pBtn->Count != 0)
		{
			_pBtn->Count--;
		}
		else
		{
			if (_pBtn->State == 1)
			{
				_pBtn->State = 0;

				/* 发送按钮弹起的消息 */
				if (_pBtn->KeyCodeUp > 0)
				{
					/* 键值放入按键FIFO */
					PutKey(_pBtn->KeyCodeUp);
				}
			}
		}

		_pBtn->LongCount = 0;
	}
}

/*******************************************************************************
	函数名:KeyPro
	输  入: 
	输  出:
	功能说明:检测所有的按键,这个函数要被systic的中断服务程序调用
*/
void KeyPro(void)
{
	DetectButton(&s_KEY1);	/* KEY1 键 */
}

/*
功能函数:键盘扫描
*/
void ScanKey(void)
{  
	ucKeyValue = GetKey();
	if(ucKeyValue == 0)         return;	
	switch(ucKeyValue)
	{
		case KEY1_DOWN: ucKeyValue = 1;
										printf(" 按键按下 \r\n");
			break;
		
		case KEY1_UP: ucKeyValue = 2;
										printf(" 按键弹起 \r\n");
			break;	

		case KEY1_LONG_state: ucKeyValue =3;
										printf(" 按键常按 \r\n");
			break;		
	
		default : ucKeyValue = 0 ;
			break;	
		
	}
	//根据键值,进入不同的按键处理函数,按键弹起的时候不会响应蜂鸣器操作
	if(ucKeyValue)
	{

		DealKey(ucKeyValue);
	}
	
	ucKeyValue  = 0 ;
}

/*****************************************************************************
键盘处理函数
*****************************************************************************/
void DealKey(uint8_t KeyValue)
{
	if(!KeyValue)	return;

	switch(KeyValue)
    {
				case 1:      
				 break ;
						
        default:break;   
    }
    KeyValue = 0; 	
}
/**
  * @brief  按键初始化

  * @param  无  
  * @retval 无
  */
void Key_Init(void)
{
	InitButtonVar();//按键初始化
}

keyboard.h如下所示:

#ifndef __keyBoard_H
#define	__keyBoard_H

#include "define.h"
#include "global.h" 

//#include "global_def.h"

/* 按键滤波时间50ms, 单位10ms
 只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件
*/
#define BUTTON_FILTER_TIME 	20  //  单位 1ms
#define BUTTON_LONG_TIME 	2000		/* 持续1秒,认为长按事件 */

/*
	每个按键对应1个全局的结构体变量。
	其成员变量是实现滤波和多种按键状态所必须的
*/
typedef struct
{
	/* 下面是一个函数指针,指向判断按键手否按下的函数 */
	uint8_t (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1表示按下 */

	uint8_t Count;			  /* 滤波器计数器 */
	uint8_t FilterTime;		/* 滤波时间(最大255,表示2550ms) */
	uint16_t LongCount;		/* 长按计数器 */
	uint16_t LongTime;		/* 按键按下持续时间, 0表示不检测长按 */
	uint8_t  State;			  /* 按键当前状态(按下还是弹起) */
	uint8_t KeyCodeUp;		/* 按键弹起的键值代码, 0表示不检测按键弹起 */
	uint8_t KeyCodeDown;	/* 按键按下的键值代码, 0表示不检测按键按下 */
	uint8_t KeyCodeLong;	/* 按键长按的键值代码, 0表示不检测长按 */
}BUTTON_T;

/* 定义键值代码
	推荐使用enum, 不用#define,原因:
	(1) 便于新增键值,方便调整顺序,使代码看起来舒服点
	(2)	编译器可帮我们避免键值重复。
*/
typedef enum
{
	KEY_NONE = 0,			/* 0 表示按键事件 */

	/* 为了演示,需要检测USER键弹起事件和长按事件 */
	KEY1_DOWN,			/* KEY1键按下 */
	KEY1_UP,			/* KEY1键弹起 */
	
	KEY1_LONG_state,  //KEY1键 常按


}KEY_ENUM;

/*
	按键FIFO用到变量
*/
#define KEY_FIFO_SIZE	30
typedef struct
{
	uint8_t Buf[KEY_FIFO_SIZE];		/* 键值缓冲区 */
	uint8_t Read;	/* 缓冲区读指针 */
	uint8_t Write;	/* 缓冲区写指针 */
}KEY_FIFO_T;



void InitButtonVar(void);
void PutKey(uint8_t _KeyCode);
uint8_t GetKey(void);
void KeyPro(void);

extern void ScanKey(void);

extern void DealKey(uint8_t KeyValue);

void Key_Init(void);


#endif

注意:在定时器中,实现对按键的扫描:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */

  /* USER CODE BEGIN Callback 1 */
  if (htim->Instance == TIM10) {
			Time10point++;

				KeyPro();
				if(Time10point >=1000)
				{
					Time10point = 0 ;
					HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);  
				}
			}
}

在主程序中,实现按键的处理:

  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

		ScanKey();
  }

验证图如下所示:

11111111.png

使用方法:

1:按下是最基本的触发方式,弊端就是当常按按键时,按键按下的状态也会触发。

2:可以keyboard中设置去抖时间,和常按的时间,需要下定时器的中断时间即可。

3:需要在定时器中执行扫描按键函数,然后再主程序中执行键值处理函数。

4:亲测代码功能正常,可实现几种不同的按键状态。





关键词: 按键检测    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]