[Detailed ~Key State Machine ~Function Plus] 2. Realize the functions of single-click, double-click, and long-press

For the explanation of the key state machine, see: [Detailed ~ Key state machine] 1. Realize the function of short press and long press

In this article, on the basis of long press and short press, the double-click function is added.

1. Problem description

Use one button to achieve long-press, single-click, and double-click operations. Use the state machine idea to improve efficiency.

  • Peripherals: io port, timer

2. Definition of single click, double click and long press

  • Long press event : any event longer than 1 second to press and release
  • Single- click event : the time of pressing is less than 1 second and there is no re-pressing operation within 500ms after the release
  • Double-click event : The time interval between two short presses is less than 500ms, and the two short-press operations are combined into one double-click event.

Instructions for special cases:
1. The time interval between short press and long press is less than 500ms, and it responds to a single click and long press event, but does not respond to a double-click event
. 2. 2n short presses in a row, and the time interval is less than 500ms, the response is n double -click events.
3. Press 2n+1 times in a row, and the time interval is less than 500ms, and there is no operation within 500ms of the last time.
Note:
It is recommended to operate after 500ms after each event.

3. Explanation of code changes

3.1 Macro Definition

Added, the time interval to wait for the second press, 500ms.

/**************************************************************************************************** 
*                             长按、单击、双击定义
* 长按事件:任何大于 KEY_LONG_PRESS_TIME 
* 单击事件:按下时间不超过 KEY_LONG_PRESS_TIME 且 释放后 KEY_WAIT_DOUBLE_TIME 内无再次按下的操作
* 双击事件:俩次短按时间间隔小于KEY_WAIT_DOUBLE_TIME,俩次短按操作合并为一次双击事件。
* 特殊说明:
*          1.短按和长按时间间隔小于 KEY_WAIT_DOUBLE_TIME,响应一次单击和长按事件,不响应双击事件
*          2.连续2n次短按,且时间间隔小于 KEY_WAIT_DOUBLE_TIME,响应为n次双击
*          3.连续2n+1次短按,且时间间隔小于 KEY_WAIT_DOUBLE_TIME,且最后一次KEY_WAIT_DOUBLE_TIME内无操作,
*				响应为n次双击 和 一次单击事件
****************************************************************************************************/
#define KEY_LONG_PRESS_TIME    50 // 20ms*50 = 1s
#define KEY_WAIT_DOUBLE_TIME   25 // 20ms*25 = 500ms
#define KEY_PRESSED_LEVEL      0  // 按键按下是电平为低

3.2 Structure, enumeration

Added double click event

// 按键事件
typedef enum _KEY_EventList_TypeDef 
{
    
    
	KEY_Event_Null 		   = 0x00,  // 无事件
	KEY_Event_SingleClick  = 0x01,  // 单击
	KEY_Event_DoubleClick  = 0x02,  // 双击
	KEY_Event_LongPress    = 0x04   // 长按
}KEY_EventList_TypeDef;

Added the status of some buttons:
To achieve double-click,
KEY_Status_WaiteAgain: Waiting for the status of the second press
KEY_Status_SecondPress: Confirm that there is a second press

// 按键状态
typedef enum _KEY_StatusList_TypeDef 
{
    
    
	KEY_Status_Idle = 0				, // 空闲
	KEY_Status_Debounce   		    , // 消抖
	KEY_Status_ConfirmPress		    , // 确认按下	
	KEY_Status_ConfirmPressLong		, // 确认长按着	
	KEY_Status_WaiteAgain		    , // 等待再次按下
	KEY_Status_SecondPress          , // 第二次按下
}KEY_StatusList_TypeDef;

other unchanged

// 按键动作,按下、释放
typedef enum
{
    
     
	KEY_Action_Press = 0,
	KEY_Action_Release
}KEY_Action_TypeDef;

// 按键引脚的电平
typedef enum
{
    
     
	KKEY_PinLevel_Low = 0,
	KEY_PinLevel_High
}KEY_PinLevel_TypeDef;


// 按键配置结构体
typedef struct _KEY_Configure_TypeDef 
{
    
    
	uint16_t                        KEY_Count;        //按键长按计数
	KEY_Action_TypeDef             KEY_Action;        //按键动作,按下1,抬起0
	KEY_StatusList_TypeDef         KEY_Status;        //按键状态
	KEY_EventList_TypeDef          KEY_Event;          //按键事件
	KEY_PinLevel_TypeDef          (*KEY_ReadPin_Fcn)(void);    //读IO电平函数
}KEY_Configure_TypeDef;

3.3 Key global variables

KeyCfg saves key configuration information.

/**************************************************************************************************** 
*                             全局变量
****************************************************************************************************/
KEY_Configure_TypeDef KeyCfg={
    
    		
		0,						//按键长按计数
		KEY_Action_Release,		//虚拟当前IO电平,按下1,抬起0
		KEY_Status_Idle,        //按键状态
		KEY_Event_Null,         //按键事件
		KEY_ReadPin             //读IO电平函数
};

3.4 Function Definition

Based on STM32F103C8 microcontroller.
Use the pin as PA0, the circuit diagram is as follows: The
insert image description here
code is as follows

/**************************************************************************************************** 
*                             函数定义
****************************************************************************************************/
// 按键读取按键的电平函数,更具实际情况修改
static KEY_PinLevel_TypeDef KEY_ReadPin(void) //按键读取函数
{
    
    
  return (KEY_PinLevel_TypeDef) GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0);
}
// 获取按键是按下还是释放,保存到结构体
static void KEY_GetAction_PressOrRelease(void) // 根据实际按下按钮的电平去把它换算成虚拟的结果
{
    
    
	if(KeyCfg.KEY_ReadPin_Fcn() == KEY_PRESSED_LEVEL)
	{
    
    
		KeyCfg.KEY_Action = KEY_Action_Press;
	}
	else
	{
    
    
		KeyCfg.KEY_Action =  KEY_Action_Release;
	}
}

//按键初始化函数
void KEY_Init(void) //IO初始化
{
    
     
 	GPIO_InitTypeDef GPIO_InitStructure;
 
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能PORTA时钟
	//初始化 WK_UP-->GPIOA.0	  
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0
}
key function

The function of reading the button state machine, the idea is similar to the hand-drawn diagram of my previous blog . Just a few more states.
Main idea:

First read the action of the button, and then match the state of the pin in the switch case. In the case, use if to judge the button action or the duration of the press, and assign values ​​to the state and event.

Reference Code:

/**************************************************************************************************** 
*                             读取按键状态机
****************************************************************************************************/
void KEY_ReadStateMachine(void)
{
    
    
    KEY_GetAction_PressOrRelease();
	
	switch(KeyCfg.KEY_Status)
	{
    
    
		//状态:没有按键按下
		case KEY_Status_Idle:
			if(KeyCfg.KEY_Action == KEY_Action_Press)
			{
    
    
				KeyCfg.KEY_Status = KEY_Status_Debounce;
				KeyCfg.KEY_Event = KEY_Event_Null;
			}
			else
			{
    
    
				KeyCfg.KEY_Status = KEY_Status_Idle;
				KeyCfg.KEY_Event = KEY_Event_Null;
			}
			break;
			
		//状态:消抖
		case KEY_Status_Debounce:
			if(KeyCfg.KEY_Action == KEY_Action_Press)
			{
    
    
				KeyCfg.KEY_Status = KEY_Status_ConfirmPress;
				KeyCfg.KEY_Event = KEY_Event_Null;
			}
			else
			{
    
    
				KeyCfg.KEY_Status = KEY_Status_Idle;
				KeyCfg.KEY_Event = KEY_Event_Null;
			}
			break;	


		//状态:继续按下
		case KEY_Status_ConfirmPress:
			if( (KeyCfg.KEY_Action == KEY_Action_Press) && ( KeyCfg.KEY_Count >= KEY_LONG_PRESS_TIME))
			{
    
    
				KeyCfg.KEY_Status = KEY_Status_ConfirmPressLong;
				KeyCfg.KEY_Event = KEY_Event_Null;
				KeyCfg.KEY_Count = 0;
			}
			else if( (KeyCfg.KEY_Action == KEY_Action_Press) && (KeyCfg.KEY_Count < KEY_LONG_PRESS_TIME))
			{
    
    
				KeyCfg.KEY_Count++;
				KeyCfg.KEY_Status = KEY_Status_ConfirmPress;
				KeyCfg.KEY_Event = KEY_Event_Null;
			}
			else
			{
    
    
				KeyCfg.KEY_Count = 0;
				KeyCfg.KEY_Status = KEY_Status_WaiteAgain;// 按短了后释放
				KeyCfg.KEY_Event = KEY_Event_Null;

			}
			break;	
			
		//状态:一直长按着
		case KEY_Status_ConfirmPressLong:
			if(KeyCfg.KEY_Action == KEY_Action_Press) 
			{
    
       // 一直等待其放开
				KeyCfg.KEY_Status = KEY_Status_ConfirmPressLong;
				KeyCfg.KEY_Event = KEY_Event_Null;
				KeyCfg.KEY_Count = 0;
			}
			else
			{
    
    
				KeyCfg.KEY_Status = KEY_Status_Idle;
				KeyCfg.KEY_Event = KEY_Event_LongPress;
				KeyCfg.KEY_Count = 0;
			}
			break;	
			
		//状态:等待是否再次按下
		case KEY_Status_WaiteAgain:
			if((KeyCfg.KEY_Action != KEY_Action_Press) && ( KeyCfg.KEY_Count >= KEY_WAIT_DOUBLE_TIME))
			{
    
       // 第一次短按,且释放时间大于KEY_WAIT_DOUBLE_TIME
				KeyCfg.KEY_Count = 0;
				KeyCfg.KEY_Status = KEY_Status_Idle;  
				KeyCfg.KEY_Event = KEY_Event_SingleClick;// 响应单击
				
			}
			else if((KeyCfg.KEY_Action != KEY_Action_Press) && ( KeyCfg.KEY_Count < KEY_WAIT_DOUBLE_TIME))
			{
    
    // 第一次短按,且释放时间还没到KEY_WAIT_DOUBLE_TIME
				KeyCfg.KEY_Count ++;
				KeyCfg.KEY_Status = KEY_Status_WaiteAgain;// 继续等待
				KeyCfg.KEY_Event = KEY_Event_Null;
				
			}
			else // 第一次短按,且还没到KEY_WAIT_DOUBLE_TIME 第二次被按下
			{
    
    
				KeyCfg.KEY_Count = 0;
				KeyCfg.KEY_Status = KEY_Status_SecondPress;// 第二次按下
				KeyCfg.KEY_Event = KEY_Event_Null;
			}
			break;		
		case KEY_Status_SecondPress:
			if( (KeyCfg.KEY_Action == KEY_Action_Press) && ( KeyCfg.KEY_Count >= KEY_LONG_PRESS_TIME))
			{
    
    
				KeyCfg.KEY_Status = KEY_Status_ConfirmPressLong;// 第二次按的时间大于 KEY_LONG_PRESS_TIME
				KeyCfg.KEY_Event = KEY_Event_SingleClick; // 先响应单击
				KeyCfg.KEY_Count = 0;
			}
			else if( (KeyCfg.KEY_Action == KEY_Action_Press) && ( KeyCfg.KEY_Count < KEY_LONG_PRESS_TIME))
			{
    
    
                KeyCfg.KEY_Count ++;
				KeyCfg.KEY_Status = KEY_Status_SecondPress;
				KeyCfg.KEY_Event = KEY_Event_Null;
			}
            else 
            {
    
    // 第二次按下后在 KEY_LONG_PRESS_TIME内释放
                KeyCfg.KEY_Count = 0;
				KeyCfg.KEY_Status = KEY_Status_Idle;
				KeyCfg.KEY_Event = KEY_Event_DoubleClick; // 响应双击
            }
			break;	
		default:
			break;
	}

}

3.5 Timer interrupt and main function

timer function

Initialize a timer. The interruption time is 20ms.
Tim3 is used here.
In the timer interrupt, you can directly call the KEY_ReadStateMachine() function to save the read event to the KeyCfg.KEY_Event variable.

extern KEY_Configure_TypeDef KeyCfg;
//定时器3中断服务程序
void TIM3_IRQHandler(void)   //TIM3中断
{
    
    

	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
	{
    
    
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志 
            KEY_ReadStateMachine();  //调用状态机
			
			if(KeyCfg.KEY_Event == KEY_Event_SingleClick)
			{
    
    
				printf("单击\r\n");//事件处理
			}
			if(KeyCfg.KEY_Event == KEY_Event_LongPress)
			{
    
    
				printf("长按\r\n");//事件处理
			}
		}
}
main function

The main function here is very concise. Just initialize.
In practical applications, the code for event processing is not recommended to be placed in the timer.

int main(void)
{
    
    	

	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	uart_init(115200); // 用于查看输出
	TIM3_Int_Init(200-1,7200-1); //调用定时器使得20ms产生一个中断
	//按键初始化函数
	KEY_Init();
	
	while(1);

}     

4. Experimental verification

Download the program to the microcontroller and connect the serial port.
insert image description here
7 quick keystrokes. Produces 3 double clicks, one single click.
insert image description here
After a single click, click again after an interval of 600ms, and then double-click quickly.
insert image description here

After one click, press and hold for 2s within 500ms and then release.
insert image description here

5. Summary

The printed information matches the actual.
Therefore, the code is feasible.

Postscript:
The printf in KEY_ReadStateMachine is used for printing and viewing. In order to make it easier to read, I wrote some unnecessary assignment operations. It can be commented out in practical application to improve efficiency.

Double-click to achieve, three-click, four-click is not difficult. . . If you are interested, you can try it.
No one is perfect, the code must have deficiencies, welcome to communicate, we will maintain it together.

Thank you for reading, it is not easy to share code words.
If it helps, please don't be stingy. Like and comment on favorites to let more people see useful content.

Guess you like

Origin blog.csdn.net/qq_44078824/article/details/123757354