单片机基础:独立按键,多按键实现单击,双击,长按,多击功能

前言

很多人学单片机的时候马马虎虎,许多例程都是知其然不知其所以然。这样其实是害了自己,拿来就用自然很方便,但如果不懂其中的原理,只要功能要求稍微变一点,估计你就蒙圈了。毕竟技巧是基于扎实的基础之上的!

一、明确需求
    

用户基本操作定义:
    1。短按操作:按键按下,按下时间<1s,属于一次短按操作
    2。长按操作:按键按下,按下时间>1s,属于一次长按操作

在正常0.5s内无按键操作为启始按键扫描条件下,扫描按键将产生以下3种按键事件:
    1。长按事件:任何1次出现的长按操作都属于长按事件
    2。单击事件:1次短按操作后,间隔0.5内没有短按操作
    3。双击事件:2次短按操作间隔时间<0.5s,则2次短按操作为1次双击事件,且2次短按都取消

特别操作情况定义:
    1。短按操作和长按操作间隔<0.5s,以及,长按操作和短按操作间隔<0.5s,均不产生双击事件
    2。连续n次(n为奇数)短按操作,且间隔均<0.5s,产生(n-1)/2次双击事件+1次单击事件
    3。连续n次(n为偶数)短按操作,且间隔均<0.5s,产生n/2次双击事件

对按键操作者的建议:     
    由于按键的多功能性质,建议操作者每次在单击/长按/双击按键事件发生后,隔0.5s后再进行下一次的按键操作。因为在特别操作情况下,程序是保证按定义进行判断和处理的,主要是怕操作者自己记不清楚导致操作失误。

对软件设计者的要求:
    1。应该全面进行分析,给出严格定义和判断条件,如上所示。如果自己都不清楚,你的设计出的系统就不稳定,不可靠。
    2。在1的基础上,编写出符合要求的程序,并进行全面测试。

二,实现过程

1.首先在key.h定义声明结构体,及其所需的函数

typedef struct
{
	uint8_t KeyIO;//按键IO
    uint8_t KeyIOFilterCnt;//滤波次数
	uint8_t KeyPressNum;//按键次数
    uint8_t KeyPressTime;//按键按下时间
	uint8_t KeyPressOverTime;//超时时间
    uint8_t KeyPressFlag;//按键按下标志位
    uint8_t keyEffectNum;//按键次数
    uint8_t KeyLongPressTime;//长按时间
    uint8_t KeyShortPressTime;//短按时间
} KEY_MELEMENT;

//--------------------------flag--------------------------------
#define G_F(VALX,VALY)    ((VALX)&(VALY))        //GET_FLAG
#define C_F(VALX,VALY)    ((VALX)&=(~(VALY)))    //CLEAR_FLAG
#define S_F(VALX,VALY)    ((VALX)|=(VALY))       //SET_FLAG

//---------------------KeyPressFlag-----------------------------
#define   KeyPress       0x01
#define   KeyShotPress   0x02
#define   KeyLongPress   0x04
#define   TwoKeyPress    0x08

//------------------------TwoKeyFlag---------------------------
#define  TwoKeyAllPress      0x01
#define  TwoKeyPressEffect   0x01

//------------------------KEYIO-------------------------
  //根据自己的单片机型号去声明按键IO
#define KEY1IO    ((gpio_porta_read()>>5)&0x01)    //PA5
#define KEY2IO    ((gpio_porta_read()>>6)&0x01)    //PA6
		
//声明函数
void Function_KeyInit(void);//初始化
void Function_OneKeyRead(KEY_MELEMENT * Key);//扫描单个按键
void Function_TwoKeyRead(KEY_MELEMENT *KeyI , KEY_MELEMENT *KeyII);//扫描两个按键
void Function_KeyHandle(void);//按键处理

2.在Key.c中实现函数功能

2.1初始化按键IO

#include "Key.h"

//有多少个按键就声明多少个结构体
KEY_MELEMENT Key1IO;
KEY_MELEMENT Key2IO;

/*******************************************************
 * 
 * 按键端口初始化
 * 
 *******************************************************/
void Function_KeyInit(void)
{
	//====================按键引脚初始化==================
     //根据自己的单片机型号初始化按键IO
	system_set_port_mux(GPIO_PORT_A, GPIO_BIT_5, PORTA5_FUNC_A5);
	gpio_set_dir(GPIO_PORT_A, (GPIO_BIT_5), GPIO_DIR_IN);
	system_set_port_pull(GPIO_PA5, false);

	system_set_port_mux(GPIO_PORT_A, GPIO_BIT_6, PORTA6_FUNC_A6); 
	gpio_set_dir(GPIO_PORT_A, (GPIO_BIT_6), GPIO_DIR_IN);
	system_set_port_pull(GPIO_PA6, false);

	//====================按键结构体初始化==================
	Key1IO.KeyIOFilterCnt = 0;
	Key1IO.KeyPressNum = 0;
	Key1IO.KeyPressTime = 0;
	Key1IO.KeyPressOverTime = 0;
	Key1IO.KeyPressFlag = 0;
	Key1IO.keyEffectNum = 0;
     //长按,短按时间可根据自己的需求进行调节
	Key1IO.KeyLongPressTime = 60; //时间MS = KeyLongPressTime * 扫描函数周期
	Key1IO.KeyShortPressTime = 20;//时间MS = KeyShortPressTime * 扫描函数周期

	Key2IO.KeyIOFilterCnt = 0;
	Key2IO.KeyPressNum = 0;
	Key2IO.KeyPressTime = 0;
	Key2IO.KeyPressOverTime = 0;
	Key2IO.KeyPressFlag = 0;
	Key2IO.keyEffectNum = 0;
	Key2IO.KeyLongPressTime = 50;
	Key2IO.KeyShortPressTime = 20;
}


2.2按键扫描函数

2.2.1单按键扫描函数
/******************************************************* 
KeyIO IO口寄存器
KeyPressNum   为按键次数。   KeyPressNum = 0xFF为长按生效
KeyPressFlag  bit0=1按键按下  bit1=0按键松开
  *******************************************************/
void Function_OneKeyRead(KEY_MELEMENT * Key)//扫描单个按键
{
    Key->keyEffectNum = 0;
                              
      if(Key->KeyIO==0)//低电平生效  //if(Key->KeyIO==1)//高电平生效
		{
			if(Key->KeyIOFilterCnt<2)
			{
               Key->KeyIOFilterCnt++;
			}
			else
			{
				 Key->KeyPressOverTime = 0;
				 S_F(Key->KeyPressFlag,KeyPress);

                if(G_F(Key->KeyPressFlag,TwoKeyPress)==0)
				 {
						if(G_F(Key->KeyPressFlag,KeyLongPress)==0)
						{
								Key->KeyPressTime++;
								if(Key->KeyPressTime > Key->KeyLongPressTime)
								{
										Key->KeyPressTime = 0;
										S_F(Key->KeyPressFlag,KeyLongPress);
										C_F(Key->KeyPressFlag,KeyShotPress);
										Key->KeyPressNum = 0;
										Key->keyEffectNum = 0xff;
								}
								else
								{
										S_F(Key->KeyPressFlag,KeyShotPress);
								}
						}
						else
						{
								Key->KeyPressTime = 0;
								C_F(Key->KeyPressFlag,KeyShotPress);
						} 
				 }
				 else
				 {
					     Key->KeyPressTime = 0;
					     C_F(Key->KeyPressFlag,KeyShotPress);
						 C_F(Key->KeyPressFlag,KeyLongPress);
						 Key->keyEffectNum = 0;
						 Key->KeyPressNum = 0;
				 }
			}
		}
		else
		{
			if(Key->KeyIOFilterCnt>0)
			{
               Key->KeyIOFilterCnt--;
			}
			else
			{
				 Key->KeyPressTime = 0;
				 C_F(Key->KeyPressFlag,KeyLongPress);
				 C_F(Key->KeyPressFlag,KeyPress);

				 if(G_F(Key->KeyPressFlag,KeyShotPress)!=0)
				 {
					  C_F(Key->KeyPressFlag,KeyShotPress);

					  Key->KeyPressNum++;
					  Key->KeyPressOverTime = 0;
				 }
				 else
				 {
					   if(Key->KeyPressNum!=0)
						 {
							  if(++Key->KeyPressOverTime > Key->KeyShortPressTime)
								{
									 Key->KeyPressOverTime = 0;
									 Key->keyEffectNum = Key->KeyPressNum;
									 Key->KeyPressNum = 0;
								}
						 }
				 }
			}
		}
}

2.2.2双按键长按扫描函数
 

void Function_TwoKeyRead(KEY_MELEMENT *KeyI , KEY_MELEMENT *KeyII)
{
	  static uint8_t TwoKeyPressCnt=0;

	  C_F(TwoKeyFlag,TwoKeyPressEffect);

	  if((G_F(KeyI->KeyPressFlag,KeyPress)!=0)&&(G_F(KeyII->KeyPressFlag,KeyPress)!=0))//按下
		{
		   S_F(KeyI->KeyPressFlag,TwoKeyPress);
			 S_F(KeyII->KeyPressFlag,TwoKeyPress);

			 if(G_F(TwoKeyFlag,TwoKeyAllPress)==0)
			 {
				  if(TwoKeyPressCnt<187)//长按的时间
					{
							TwoKeyPressCnt++;
					}
					else
					{
							TwoKeyPressCnt = 0;
							S_F(TwoKeyFlag,TwoKeyAllPress);
							S_F(TwoKeyFlag,TwoKeyPressEffect);
					}
			 }
		}
		else
		{
			  TwoKeyPressCnt = 0;

				if((G_F(KeyI->KeyPressFlag,KeyPress)==0)&&(G_F(KeyII->KeyPressFlag,KeyPress)==0))
				{
					  C_F(TwoKeyFlag,TwoKeyAllPress);
					  C_F(KeyI->KeyPressFlag,TwoKeyPress);
			      C_F(KeyII->KeyPressFlag,TwoKeyPress);
				}
		}
}

3.按键处理

/********************************************
*
*按键处理
*
*********************************************/
void Function_KeyHandle(void)
{   
	//key1键处理
			if(Key1IO.keyEffectNum==Long_Key)
			{

			}
			else if(Key1IO.keyEffectNum==Once_Key)
			{
	
			}
			else if(Key1IO.keyEffectNum==Double_Key)
			{
				
			}//需要实现多击功能,自行拓展
   
   //key2键处理
			if(Key2IO.keyEffectNum==Long_Key)
			{

			}
			else if(Key2IO.keyEffectNum==Once_Key)
			{
	
			}
			else if(Key2IO.keyEffectNum==Double_Key)
			{
				
			}//需要实现多击功能,自行拓展
      
   //双按键长按处理   
		if(G_F(TwoKeyFlag,TwoKeyAllPress)!=0)
		{
		    
	      
	    }
}

4.实际运用

void Main(void)
{
	if(TimeData.InterruptFlag_1ms != 0)
	{
		TimeData.InterruptFlag_1ms = 0;//1MS中断标志位

		TimeData.InterruptCnt_1ms++;//时间轮询

		switch(TimeData.InterruptCnt_1ms & 0x07)8MS
		{
			case 0x00:
				break;
			
			case 0x02:
				break;
			
			case 0x04:
				break;
			
			case 0x06:
				break;
			
			default:
				break;
		}
		
		switch(TimeData.InterruptCnt_1ms & 0x0f)//16MS
		{
			case 0x01:
			    Key1IO.KeyIO = KEY1IO;
				Key2IO.KeyIO = KEY2IO;

				Function_OneKeyRead(&Key1IO);				
				Function_OneKeyRead(&Key2IO);

				Function_TwoKeyRead(&Key1IO,&Key2IO);
				
				break;
			
			case 0x05:
			     Function_KeyHandle();
				break;
			
			case 0x09:
				break;
			
			case 0x0d:
				break;
			
			default:
				break;
		}
		
		switch(TimeData.InterruptCnt_1ms & 0x1f)//32ms
		{
			case 0x03:
				break;
			
			case 0x07:
				break;
			
			case 0x0b:

				break;
			
			case 0x0f:
				break;
			
			case 0x13:
				break;
			
			case 0x17:
				break;
			
			case 0x1b:
				break;
			
			case 0x1f:
				break;
			default:
				break;
		}
	}	
}

作者:gin果果

物联沃分享整理
物联沃-IOTWORD物联网 » 单片机基础:独立按键,多按键实现单击,双击,长按,多击功能

发表回复