接觸過的按鍵驅動中,大部分是使用延時函數消抖,或者是在掃描到按鍵狀態之後,直接就在按鍵掃描程序內部對事件進行處理。一直在思考一個不使用延時函數同時兼容性和移植性較強的按鍵驅動程序。之前用了半天時間總算是寫出來這個驅動程序,還不是很成熟,不足之處多多指正。
在講解之前,先說明一下按鍵,短按,長按,單擊,多擊分別是如何區分的。這裏非常感謝張俊老師編著的《匠人手機》這本書中關於按鍵的講解,給了我很大的啓發。
相關詳細的說明,請參閱《匠人筆記》中180頁開始的手記13—按鍵漫談。
在本次的驅動程序,需要一個定時器,6個字節的靜態變量的資源開銷。其中靜態變量爲一個無符號16位變量:key_longtime(用於長按按鍵時計時),兩個無符號8位變量:key_time(按鍵動作結束計時)、C (狀態標誌),還有一個按鍵標誌結構體;按鍵掃描程序的流程如下:
圖1 按鍵掃描程序流程
這個按鍵掃描程序我是放在定時爲1ms的定時器中斷服務函數中執行。代碼如下:
key.h文件
#ifndef _KEY_H
#define _KEY_H
#include "stm8l15x.h"
#include "bsp.h"
typedef enum{
NoClick = (u8)0x01, //無按鍵的按鍵號
SingleClick = (u8)0x02, //單擊
DoubleClick = (u8)0x04, //雙擊
ThreeClick = (u8)0x08, //三擊
SingleLongClick = (u8)0x03 //長按
}KEY_NUM_FLAG;
typedef struct{
bool state; //按鍵狀態
u8 num; //按鍵號
}KEY_NUM_STATE;
extern volatile KEY_NUM_STATE KEY_Flag;
#define KEY_Init() GPIO_Init(GPIOE,GPIO_Pin_7,GPIO_Mode_In_PU_No_IT)//按鍵I/O初始化
#define KEY GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_7) //按鍵狀態獲取
void KEYFlag_Init(void);
void KEY_Scan(void);
#endif
//按鍵狀態獲取
void KEYFlag_Init(void);
void KEY_Scan(void);
#endif
key.c文件
#include "key.h"
#define LONGMULTIHIT 0 //0,不使用長按多擊功能 1,使用(長按需要鬆手纔會有反應)
#define MAXKEYNUM ThreeClick //按鍵號的範圍
//這個數據是將按鍵掃描放在定時爲1ms的中斷中的數據,中斷時間不同需要修改
#define LONGCLICKTIME 500 //長按按鍵計時
#define STOPCLICKTIME 200 //結束按鍵動作計時
/*
* KEY_Flag.num (按鍵號)
* 0000 0010 短單擊
* 0000 0100 短雙擊
* 0000 1000 短三擊
* 0001 0000 短四擊
* . .
* . .
* . .
*
*
* 0000 0011 長單擊
* 0000 0111 長雙擊
* . .
* . .
* . .
*
*
* KEY_Flag.state 爲TRUE 確認狀態,區分單擊與多擊時使用
*
* 使用按鍵號結束一定要清按鍵號(設置爲NoClick)
*
*/
volatile KEY_NUM_STATE KEY_Flag;
/* 按鍵初始化,程序開始前執行,也可以用來清除按鍵號 */
void KEYFlag_Init(void)
{
KEY_Flag.num = NoClick;
KEY_Flag.state = FALSE;
}
/************************************************
函數名稱 : KEY_Scan
功 能 : 按鍵掃描,在定時爲1ms的中斷中
參 數 : 無
返 回 值 : 無
作 者 : zkx
*************************************************/
void KEY_Scan(void)
{
static u16 key_longtime = 0; //長按計時
static u8 key_time = 0; //結束動作計時
static u8 C = 255; //按鍵狀態標誌
if(C == 255) //鬆手檢測
{
if(KEY == 0) //按鍵按下
{
key_longtime ++; //長按計時
if(key_longtime>LONGCLICKTIME) //確認按鍵長按
{
C = 1;
}
key_time = 0;
}
else if(key_longtime>50) //按鍵按下又鬆開,短按,大於50ms消抖
{
C = 0; //按鍵短按
key_time = 0;
}
#if LONGMULTIHIT
else if(KEY_Flag.num != NoClick) //按鍵動作結束後,無任何操作
{
key_time++;
}
}
#else
}
if(KEY_Flag.num != NoClick) //按鍵動作結束後,無任何操作
{
key_time++;
}
#endif
if(C!=255)
{
if((C!=254)&&(KEY_Flag.num<=ThreeClick)) //未對按鍵值操作,按鍵使用範圍
{
KEY_Flag.num = (KEY_Flag.num<<1)+C;
key_longtime = 0;
C = 254;
}
if(KEY)
{
C = 255;
}
}
if(key_time>STOPCLICKTIME) //無動作時間,確認當前狀態
{
KEY_Flag.state = TRUE;
}
}
使用方法:按鍵掃描程序KEY_Scan()在1ms的中斷中調用;在需要使用按鍵的時候,直接讀取按鍵號,這裏分爲兩種情況。
1、不需要區分多擊,只區分單擊和長按:在需要使用按鍵的地方直接判斷KEY_Flag.num的值不爲NoClick,然後判斷按鍵的狀態(單擊或長按)。最後再初始化按鍵號,例如:
if(KEY_Flag.num != NoClick)
{
if(KEY_Flag.num == SingleLongClick)
{
}
else if(KEY_Flag.num == SingleClick)
{
}
KEYFlag_Init();
}
2、需要區分多擊:在需要使用按鍵的地方首先判斷KEY_Flag.state是否爲TRUE,爲TRUE再判斷KEY_Flag.num的值,從而判斷按鍵的狀態。使用結束需執行按鍵號初始化(KEYFlag_Init()),例如:
if(KEY_Flag.state)
{
if(KEY_Flag.num == SingleLongClick)
{
}
else if(KEY_Flag.num == DoubleClick)
{
}
KEYFlag_Init();
}