基于定时器扫描的状态机按键,实现单击、双击、长按、短长按、超长按的按键识别

痴心易碎 提交于 2019-12-12 03:35:31

基于定时器扫描的状态机按键,实现单击、双击、长按、短长按、超长按的按键识别

说明

本按键例程是基于10ms的定时器扫描,实现了单击、双击、长按、短长按、超长按的按键识别。希望对你也有帮助。

使用说明:

  1. 传入一个读取按键管脚电平的函数指针
    该函数为返回bool类型,调用参数void
    指针赋值给read_key
    本例程按键管脚电平低为按下
    若电平高为按下,则将返回值取反
    注意自己需要提前做好GPIO初始化,设定管脚为输入等
    参考如下:
 bool read_key1(void)  // 读按键1管脚电平
{
		return HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin);
}
bool read_key1(void)  // 读按键1管脚电平
{
		return nrf_gpio_pin_read(KEY1_PIN);
}
  1. 传入一个按键回调事件的函数指针
    该函数为返回void类型,调用参数void
    指针赋值给single_click_callback、long_press_callback、double_click_callback、short_long_press_callback、long_long_press_callback
    参考如下:
void main_key_release_handle(void)  // 主按键短按事件处理
{
	  printf("Main key is pressed!\r\n");
}
  1. 创建一个10ms的定时器,这里就不介绍了
  2. 在定时器初始化之前,进行按键初始化my_key_init()
  3. 在定时器中断里进行按键扫描my_key_scan()

代码

my_key.c文件

#include <string.h>

#include "my_key.h"
#include "my_peripheral.h"  // 我的函数指针的头文件,添加自己的函数指针
#include "my_process.h"  // 我的函数指针的头文件,添加自己的函数指针

static my_key_param_t key_param[KEY_NUMBER] = {0};  // 按键参数结构体数组

void my_key_init(void)  // 按键初始化
{
	  key_param[0].read_key = read_key1;  // 读取按键管脚电平的函数
	  key_param[0].key_is_press = false;
	  key_param[0].count = 0;
	  key_param[0].state = KEY_STATE_IDLE;
	  key_param[0].event = KEY_EVENT_IDLE;
	  key_param[0].single_click_callback = main_key_release_handle;  // 按键回调事件的函数
	  key_param[0].long_press_callback = main_key_long_press_handle;  // 按键回调事件的函数
	  key_param[0].double_click_callback = NULL;
	  key_param[0].short_long_press_callback = NULL;
	  key_param[0].long_long_press_callback = NULL;
	
	  key_param[1].read_key = read_key2;  // 读取按键管脚电平的函数
	  key_param[1].key_is_press = false;
	  key_param[1].count = 0;
	  key_param[1].state = KEY_STATE_IDLE;
	  key_param[1].event = KEY_EVENT_IDLE;
	  key_param[1].single_click_callback = func_key_release_handle;  // 按键回调事件的函数
	  key_param[1].long_press_callback = func_key_long_press_handle;  // 按键回调事件的函数
	  key_param[1].double_click_callback = NULL;
	  key_param[1].short_long_press_callback = NULL;
	  key_param[1].long_long_press_callback = NULL;
}
	
void my_key_scan(void)  // 按键扫描
{
		for (uint8_t i = 0; i < KEY_NUMBER; i++)  // 轮询按键
		{
			if (key_param[i].read_key == NULL) continue;

			key_param[i].key_is_press = !key_param[i].read_key();  // 获取按键状态
			
			// 扫描按键状态
			switch (key_param[i].state)  // 进入状态机流程
			{
				case KEY_STATE_IDLE:  // 按键空闲状态
					if (key_param[i].key_is_press)  // 若按键被按下
					{
						key_param[i].count = 0;  // 计数清零
						key_param[i].state = KEY_STATE_FIRST_PRESS;  // 跳转到按键第一次按下状态
					}
					else
					{
						key_param[i].count = 0;  // 计数清零
						key_param[i].state = KEY_STATE_IDLE;  // 跳转到按键空闲状态
						key_param[i].event = KEY_EVENT_IDLE;  // 按键空闲事件
					}
					break;

				case KEY_STATE_FIRST_PRESS:  // 按键第一次按下状态
					if (key_param[i].key_is_press)  // 若按键被按下
					{
						if (++key_param[i].count >= KEY_SHORT_PRESS_COUNT_NUMBER)  // 若第一次按下的时间计数超过KEY_SHORT_PRESS_COUNT_NUMBER
						{
							key_param[i].count = 0;  // 计数清零
							key_param[i].state = KEY_STATE_FIRST_PRESS_VALID;  // 跳转到按键第一次按下有效状态
						}
					}
					else 
					{
						// 按下时间不够,不处理,按键消抖
						key_param[i].count = 0;  // 计数清零
						key_param[i].state = KEY_STATE_IDLE;  // 跳转到按键空闲状态
						key_param[i].event = KEY_EVENT_IDLE;  // 按键空闲事件
					}
					break;
					
				case KEY_STATE_FIRST_PRESS_VALID:	// 按键第一次按下有效状态
					if (key_param[i].key_is_press)  // 若按键被按下
					{
						if (++key_param[i].count >= KEY_LONG_PRESS_COUNT_NUMBER - KEY_SHORT_PRESS_COUNT_NUMBER)  // 若第一次按下的时间计数超过KEY_LONG_PRESS_COUNT_NUMBER
						{
							key_param[i].count = 0;  // 计数清零
							key_param[i].state = KEY_STATE_LONG_PRESS;  // 跳转到按键长按状态
							key_param[i].event = KEY_EVENT_LONG_PRESS;  // 按键长按事件
						}
					}
					else 
					{
						key_param[i].count = 0;  // 计数清零
						key_param[i].state = KEY_STATE_FIRST_RELEASE;  // 跳转到按键第一次释放状态
					}
					break;
					
				case KEY_STATE_FIRST_RELEASE:  // 按键第一次释放状态
					if (!key_param[i].key_is_press)  // 若按键释放
					{
						if (++key_param[i].count >= KEY_DOUBLE_CLICK_INTERVAL_COUNT_NUMBER)  // 若超过KEY_DOUBLE_CLICK_INTERVAL_COUNT_NUMBER计数时间,按键没有第二次被按下
						{
							key_param[i].count = 0;  // 计数清零
							key_param[i].state = KEY_STATE_IDLE;  // 跳转到按键空闲状态
							key_param[i].event = KEY_EVENT_SINGLE_CLICK;  // 按键单击事件
						}
					}
					else if (key_param[i].count >= KEY_SHORT_PRESS_COUNT_NUMBER && key_param[i].count < KEY_DOUBLE_CLICK_INTERVAL_COUNT_NUMBER)  // 若在间隔时间内,获取了第二次按键按下
					{
						key_param[i].count = 0;  // 计数清零
						key_param[i].state = KEY_STATE_SECOND_PRESS;  // 跳转到按键第二次按下状态
					}
					else if (key_param[i].count < KEY_SHORT_PRESS_COUNT_NUMBER) // 若在KEY_SHORT_PRESS_COUNT_NUMBER计数时间内,获取了第二次按键按下,那么此次动作忽略,做消抖处理
					{
						key_param[i].count = 0;  // 计数清零
						key_param[i].state = KEY_STATE_IDLE;  // 跳转到按键空闲状态
						key_param[i].event = KEY_EVENT_SINGLE_CLICK;  // 按键单击事件
					}
					break;
					
				case KEY_STATE_SECOND_PRESS:  // 按键第二次按下状态
					if (key_param[i].key_is_press)  // 若按键被按下
					{
						if (++key_param[i].count >= KEY_SHORT_PRESS_COUNT_NUMBER)  // 若第一次按下的时间计数超过KEY_SHORT_PRESS_COUNT_NUMBER
						{
							key_param[i].count = 0;  // 计数清零
							key_param[i].state = KEY_STATE_SECOND_PRESS_VALID;  // 跳转到按键第二次按下有效状态
						}
					}
					else 
					{
						// 按下时间不够,不处理,按键消抖
						key_param[i].count = 0;  // 计数清零
						key_param[i].state = KEY_STATE_IDLE;  // 跳转到按键空闲状态
						key_param[i].event = KEY_EVENT_SINGLE_CLICK;  // 按键单击事件
					}
					break;
					
				case KEY_STATE_SECOND_PRESS_VALID:  // 按键第二次按下有效状态
					if (key_param[i].key_is_press)  // 若按键被按下
					{
						if (++key_param[i].count >= KEY_LONG_PRESS_COUNT_NUMBER - KEY_SHORT_PRESS_COUNT_NUMBER)  // 若第二次按下的时间计数超过KEY_LONG_PRESS_COUNT_NUMBER
						{
							key_param[i].count = 0;  // 计数清零
							key_param[i].state = KEY_STATE_SHORT_LONG_PRESS;  // 跳转到按键短长按状态
							key_param[i].event = KEY_EVENT_SHORT_LONG_PRESS;	// 按键短长按事件
						}
					}
					else 
					{
						key_param[i].count = 0;  // 计数清零
						key_param[i].state = KEY_STATE_IDLE;  // 跳转到按键空闲状态
						key_param[i].event = KEY_EVENT_DOUBLE_CLICK;	// 按键双击事件
					}
					break;
					
				case KEY_STATE_SECOND_RELEASE:  // 按键第二次释放状态
					// Do nothing!
					break;
				
				case KEY_STATE_LONG_PRESS:
					if (key_param[i].key_is_press)  // 若按键被按下
					{
						if (++key_param[i].count >= KEY_LONG_LONG_PRESS_COUNT_NUMBER - KEY_LONG_PRESS_COUNT_NUMBER)  // 若长按下的时间计数超过KEY_LONG_LONG_PRESS_COUNT_NUMBER
						{
							key_param[i].count = 0;  // 计数清零
							key_param[i].state = KEY_STATE_LONG_LONG_PRESS;  // 跳转到按键超长按状态
							key_param[i].event = KEY_EVENT_LONG_LONG_PRESS;  // 按键超长按事件
						}
					}
					else 
					{
						key_param[i].count = 0;  // 计数清零
						key_param[i].state = KEY_STATE_IDLE;  // 跳转到按键空闲状态
					}
					break;
					
				case KEY_STATE_SHORT_LONG_PRESS:
					if (key_param[i].key_is_press)  // 若按键被按下
					{
						// 短长按后,不再做处理,等待按键释放
						// Do nothing!
					}
					else 
					{
						key_param[i].count = 0;  // 计数清零
						key_param[i].state = KEY_STATE_IDLE;  // 跳转到按键空闲状态
					}
					break;
					
				case KEY_STATE_LONG_LONG_PRESS:
					if (key_param[i].key_is_press)  // 若按键被按下
					{
						// 超长按后,不再做处理,等待按键释放
						// Do nothing!	
					}
					else 
					{
						key_param[i].count = 0;  // 计数清零
						key_param[i].state = KEY_STATE_IDLE;  // 跳转到按键空闲状态
					}
					break;
			}
			
			// 扫描按键事件
			switch (key_param[i].event)  // 进入状态机流程
			{
				case KEY_EVENT_IDLE:  
					// Do nothing!
					break;
				
				case KEY_EVENT_SINGLE_CLICK: 
					if (key_param[i].single_click_callback != NULL) key_param[i].single_click_callback();
					break;
				
				case KEY_EVENT_LONG_PRESS: 
					if (key_param[i].long_press_callback != NULL) key_param[i].long_press_callback();
					break;
				
				case KEY_EVENT_DOUBLE_CLICK:  
					if (key_param[i].double_click_callback != NULL) key_param[i].double_click_callback();
					break;

				case KEY_EVENT_SHORT_LONG_PRESS:  
					if (key_param[i].short_long_press_callback != NULL) key_param[i].short_long_press_callback();
					break;

				case KEY_EVENT_LONG_LONG_PRESS: 
					if (key_param[i].long_long_press_callback != NULL) key_param[i].long_long_press_callback();
					break;
			}
			
			key_param[i].event = KEY_EVENT_IDLE;  // 清除按键事件
		}
}

my_key.h文件

#ifndef __MY_KEY_H__
#define __MY_KEY_H__

#include <stdbool.h>
#include <stdint.h>

// 在10ms的定时器中进行计数
#define KEY_SHORT_PRESS_COUNT_NUMBER				5	 // 短按时间
#define KEY_DOUBLE_CLICK_INTERVAL_COUNT_NUMBER		15  // 双击间隔
#define KEY_LONG_PRESS_COUNT_NUMBER					200	 // 长按时间
#define KEY_LONG_LONG_PRESS_COUNT_NUMBER			800	 // 超长按时间

typedef bool (* my_read_key_t)(void);
typedef void (* my_key_callback_t)(void);

#define KEY_NUMBER		2

// 按键状态
typedef enum
{
      KEY_STATE_IDLE,					// 按键空闲状态
      KEY_STATE_FIRST_PRESS,			// 按键第一次按下状态
      KEY_STATE_FIRST_PRESS_VALID,	    // 按键第一次按下有效状态
	  KEY_STATE_FIRST_RELEASE,			// 按键第一次释放状态
      KEY_STATE_SECOND_PRESS,			// 按键第二次按下状态
      KEY_STATE_SECOND_PRESS_VALID,	    // 按键第二次按下有效状态
	  KEY_STATE_SECOND_RELEASE,			// 按键第二次释放状态
	  KEY_STATE_LONG_PRESS,				// 按键长按状态
	  KEY_STATE_SHORT_LONG_PRESS,		// 按键短长按状态
	  KEY_STATE_LONG_LONG_PRESS,		// 按键超长按状态
} my_key_state_t;

// 按键事件
typedef enum
{
      KEY_EVENT_IDLE,					// 按键空闲事件
      KEY_EVENT_SINGLE_CLICK,			// 按键单击事件
      KEY_EVENT_DOUBLE_CLICK,			// 按键双击事件
      KEY_EVENT_LONG_PRESS,				// 按键长按事件
	  KEY_EVENT_SHORT_LONG_PRESS,		// 按键短长按事件
	  KEY_EVENT_LONG_LONG_PRESS,		// 按键超长按事件
} my_key_event_t;

// 按键参数
typedef struct
{
	  my_read_key_t read_key;  // 读按键管脚状态
	  bool key_is_press;  // 按键是否被按下
	  uint32_t count;  // 按键持续时间定时器计数值
	  my_key_state_t state;  // 按键状态
	  my_key_event_t event;  // 按键事件
	  my_key_callback_t single_click_callback;  // 按键单击回调函数
	  my_key_callback_t long_press_callback;  // 按键长按回调函数
	  my_key_callback_t double_click_callback;  // 按键双击回调函数
	  my_key_callback_t short_long_press_callback;  // 按键短长按回调函数
	  my_key_callback_t long_long_press_callback;  // 按键超长按回调函数
} my_key_param_t;


void my_key_init(void);  // 按键初始化,必须在定时器初始化之前!!!
void my_key_scan(void);  // 按键扫描,必须在10ms定时器中断里进行
	

#endif  // __MY_KEY_H__

原创文章,转载请注明,谢谢。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!