MCU實(shí)戰(zhàn)經(jīng)驗(yàn):多種的按鍵處理
按鍵通常有:IO口按鍵(BUTTON),AD按鍵(通過AD采樣電壓),IR(遙控器)
按按鍵功能分:有短按鍵,長按鍵,連續(xù)按鍵。打個(gè)比方,遙控電視機(jī),按一下音量鍵,音量增加1,這個(gè)就是短按鍵。按住音量鍵不放,音量連續(xù)加,這個(gè)就是連續(xù)按鍵。按住一個(gè)按鍵5s,系統(tǒng)會復(fù)位,這個(gè)是長按鍵。
1、IO口按鍵,就是我們比較常見的一個(gè)IO接一個(gè)按鍵,或者是一個(gè)矩陣鍵盤。很多新人的處理方法可能是采樣延時(shí)的方法,當(dāng)年我也是這樣的,如下
if(GETIO==low)
{
delay_10ms()
if(GETIO==low)
{
//得到按鍵值
}
}
這種方法雖然簡單,但是有很大弊端。首先 Delay浪費(fèi)很多時(shí)間,影響系統(tǒng)。第二,無法判斷長短按鍵,連續(xù)按鍵。第三,如果這個(gè)按鍵是開關(guān)機(jī)按鍵系統(tǒng)在低功耗狀態(tài)下,需要中斷喚醒,這種方法比較容易出問題,如STM8S系列的 halt 模式。
所以我們一般在產(chǎn)品開發(fā)的過程中,采用掃描的方法,就是每隔10ms 去檢測IO的狀態(tài),看是否有按鍵,然后去抖動,判斷按鍵功能。參考代碼如下,這段代碼是之前在一個(gè)論壇看到的比我自己寫的更加優(yōu)秀,所以拿出來和大家分享一下,也順便感謝一下作者。這段代碼,容易修改,可以根據(jù)自己的時(shí)間需要,進(jìn)行長短按鍵,連續(xù)按鍵,還有組合按鍵的判斷。
/* 按鍵濾波時(shí)間50ms, 單位10ms
*只有連續(xù)檢測到50ms狀態(tài)不變才認(rèn)為有效,包括彈起和按下兩種事件
*/
#define BUTTON_FILTER_TIME 5
#define BUTTON_LONG_TIME 300 /* 持續(xù)1秒,認(rèn)為長按事件 */
/*
每個(gè)按鍵對應(yīng)1個(gè)全局的結(jié)構(gòu)體變量。
其成員變量是實(shí)現(xiàn)濾波和多種按鍵狀態(tài)所必須的
*/
typedefstruct
{
/* 下面是一個(gè)函數(shù)指針,指向判斷按鍵手否按下的函數(shù) */
unsigned char (*IsKeyDownFunc)(void); /* 按鍵按下的判斷函數(shù),1表示按下 */
unsigned char Count; /* 濾波器計(jì)數(shù)器 */
unsigned char FilterTime; /* 濾波時(shí)間(最大255,表示2550ms) */
unsigned short LongCount; /* 長按計(jì)數(shù)器 */
unsigned short LongTime; /* 按鍵按下持續(xù)時(shí)間, 0表示不檢測長按 */
unsigned char State; /* 按鍵當(dāng)前狀態(tài)(按下還是彈起) */
unsigned char KeyCodeUp; /* 按鍵彈起的鍵值代碼, 0表示不檢測按鍵彈起 */
unsigned char KeyCodeDown; /* 按鍵按下的鍵值代碼, 0表示不檢測按鍵按下 */
unsigned char KeyCodeLong; /* 按鍵長按的鍵值代碼, 0表示不檢測長按 */
unsigned char RepeatSpeed; /* 連續(xù)按鍵周期 */
unsigned char RepeatCount; /* 連續(xù)按鍵計(jì)數(shù)器 */
}BUTTON_T;
typedefenum
{
KEY_NONE = 0, /* 0 表示按鍵事件 */
KEY_DOWN_Power, /* 按鍵鍵按下 */
KEY_UP_Power, /* 按鍵鍵彈起 */
KEY_LONG_Power, /* 按鍵鍵長按 */
KEY_DOWN_Power_TAMPER /* 組合鍵,Power鍵和WAKEUP鍵同時(shí)按下 */
}KEY_ENUM;
BUTTON_T s_Powerkey;
//是否有按鍵按下接口函數(shù)
unsigned char IsKeyDownUser(void)
{if (0==GPIO_ReadInputPin(POWER_KEY_PORT, POWER_KEY_PIN) ) return 1;return 0;}
void PanakeyHard_Init(void)
{
GPIO_Init (POWER_KEY_PORT, POWER_KEY_PIN, GPIO_MODE_IN_FL_NO_IT);//power key
}
void PanakeyVar_Init(void)
{
/* 初始化USER按鍵變量,支持按下、彈起、長按 */
s_Powerkey.IsKeyDownFunc = IsKeyDownUser; /* 判斷按鍵按下的函數(shù) */
s_Powerkey.FilterTime = BUTTON_FILTER_TIME; /* 按鍵濾波時(shí)間 */
s_Powerkey.LongTime = BUTTON_LONG_TIME; /* 長按時(shí)間 */
s_Powerkey.Count = s_Powerkey.FilterTime / 2; /* 計(jì)數(shù)器設(shè)置為濾波時(shí)間的一半 */
s_Powerkey.State = 0; /* 按鍵缺省狀態(tài),0為未按下 */
s_Powerkey.KeyCodeDown = KEY_DOWN_Power; /* 按鍵按下的鍵值代碼 */
s_Powerkey.KeyCodeUp =KEY_UP_Power; /* 按鍵彈起的鍵值代碼 */
s_Powerkey.KeyCodeLong = KEY_LONG_Power; /* 按鍵被持續(xù)按下的鍵值代碼 */
s_Powerkey.RepeatSpeed = 0; /* 按鍵連發(fā)的速度,0表示不支持連發(fā) */
s_Powerkey.RepeatCount = 0; /* 連發(fā)計(jì)數(shù)器 */
}
void Panakey_Init(void)
{
PanakeyHard_Init(); /* 初始化按鍵變量 */
PanakeyVar_Init(); /* 初始化按鍵硬件 */
}
/*
*********************************************************************************************************
* 函 數(shù) 名: bsp_DetectButton
* 功能說明: 檢測一個(gè)按鍵。非阻塞狀態(tài),必須被周期性的調(diào)用。
* 形 參:按鍵結(jié)構(gòu)變量指針
* 返 回 值: 無
*********************************************************************************************************
*/
void Button_Detect(BUTTON_T *_pBtn)
{
if (_pBtn->IsKeyDownFunc())
{
if (_pBtn->Count < _pBtn->FilterTime)
{
_pBtn->Count = _pBtn->FilterTime;
}
elseif(_pBtn->Count < 2 * _pBtn->FilterTime)
{
_pBtn->Count++;
}
else
{
if (_pBtn->State == 0)
{
_pBtn->State = 1;
/* 發(fā)送按鈕按下的消息 */
if (_pBtn->KeyCodeDown > 0)
{
/* 鍵值放入按鍵FIFO */
Pannelkey_Put(_pBtn->KeyCodeDown);// 記錄按鍵按下標(biāo)志,等待釋放
}
}
if (_pBtn->LongTime > 0)
{
if (_pBtn->LongCount < _pBtn->LongTime)
{
/* 發(fā)送按鈕持續(xù)按下的消息 */
if (++_pBtn->LongCount == _pBtn->LongTime)
{
/* 鍵值放入按鍵FIFO */
Pannelkey_Put(_pBtn->KeyCodeLong);
}
}
else
{
if (_pBtn->RepeatSpeed > 0)
{
if (++_pBtn->RepeatCount >= _pBtn->RepeatSpeed)
{
_pBtn->RepeatCount = 0;
/* 常按鍵后,每隔10ms發(fā)送1個(gè)按鍵 */
Pannelkey_Put(_pBtn->KeyCodeDown);
}
}
}
}
}
}
else
{
if(_pBtn->Count > _pBtn->FilterTime)
{
_pBtn->Count = _pBtn->FilterTime;
}
elseif(_pBtn->Count != 0)
{
_pBtn->Count--;
}
else
{
if (_pBtn->State == 1)
{
_pBtn->State = 0;
/* 發(fā)送按鈕彈起的消息 */
if (_pBtn->KeyCodeUp > 0) /*按鍵釋放*/
{
/* 鍵值放入按鍵FIFO */
Pannelkey_Put(_pBtn->KeyCodeUp);
}
}
}
_pBtn->LongCount = 0;
_pBtn->RepeatCount = 0;
}
}
//功能說明: 檢測所有按鍵。10MS 調(diào)用一次
void Pannelkey_Polling(void)
{
Button_Detect(&s_Powerkey); /* USER 鍵 */
}
void Pannelkey_Put(void)
{
// 定義一個(gè)隊(duì)列 放入按鍵值
}
2、遙控器按鍵,遙控器解碼的一般就有兩種:脈寬調(diào)制和脈沖調(diào)制,這里就不細(xì)講解碼的過程了。這里詳細(xì)解碼之后,如何處理遙控器按鍵實(shí)現(xiàn)遙控器按鍵的長短按功能和連續(xù)按鍵功能。代碼裁剪過,大家根據(jù)實(shí)際需要改動。其實(shí)AD按鍵,通過AD采樣獲得按鍵值之后,可以采取如下面的一樣方法處理,提一個(gè)函數(shù)接口即可
typedefstruct
{
unsigned char count;//
unsigned char LongkeyFlag;/*是否長按鍵,1代表是*/
unsigned char PreKeyValue;/*按鍵值的一個(gè)備份,用于釋放按鍵值*/
}ScanKeyDef;
#define SHORT_PRESS_TIME_IR 16 // 10ms
#define SERIES_PRESS_TIME_IR 10
#define LONG_PRESS_TIME_IR 22
#define KEY_RELEASE_TIME_OUT_IR 12 // 10ms
//提供5個(gè)接口函數(shù),如下。
unsigned char get_irkey(void);
unsigned char ISSeriesKey(unsigned char temp);//按鍵是否需要做連續(xù)按鍵
unsigned char changeSeriesKey(unsigned char temp);//轉(zhuǎn)換連續(xù)按鍵值
unsigned char ISLongKey(unsigned char temp);//按鍵是否需要做連續(xù)按鍵
unsigned char changeToLongKey(unsigned char temp);//轉(zhuǎn)換連續(xù)按鍵值
unsigned char KeyScan(void)
{
unsigned char KeyValue = KEY_NONE,
KeyValueTemp = KEY_NONE;
static unsigned char KeyReleaseTimeCount =0;
KeyValueTemp = get_irkey();
if(KeyValueTemp != KEY_NONE)
{
ScanKeyDef.count++;
KeyReleaseTimeCount =0;
if(ScanKeyDef.count < LONG_PRESS_TIME_IR )
{
ScanKeyDef.LongkeyFlag = 0;
if((ScanKeyDef.count == SERIES_PRESS_TIME_IR) && ISSeriesKey(KeyValueTemp)) //處理連續(xù)按鍵
{
KeyValue = changeSeriesKey ( KeyValueTemp );
ScanKeyDef.PreKeyValue = KEY_NONE;
}
elseif ( ScanKeyDef.count < SHORT_PRESS_TIME_IR )
{
ScanKeyDef.PreKeyValue = KeyValueTemp;
}
else
{
ScanKeyDef.PreKeyValue = KEY_NONE; // 無效按鍵
}
}
elseif ( ScanKeyDef.count == LONG_PRESS_TIME_IR )
{
if (ISLongKey(KeyValueTemp))
{
{
ScanKeyDef.LongkeyFlag = 1;
KeyValue = changeToLongKey ( KeyValueTemp );
}
}
ScanKeyDef.PreKeyValue = KEY_NONE;
}
elseif (ScanKeyDef.count > LONG_PRESS_TIME_IR )
{
ScanKeyDef.PreKeyValue = KEY_NONE; //無效按鍵
}
}
else//release & no press
{
KeyReleaseTimeCount ++;
if(KeyReleaseTimeCount >= KEY_RELEASE_TIME_OUT_IR)
{
if ( ScanKeyDef.PreKeyValue != KEY_NONE ) //釋放按鍵值
{
if ( ScanKeyDef.LongkeyFlag == 0 )
{
KeyValue =ScanKeyDef.PreKeyValue ;
}
}
ScanKeyDef.count = 0;
ScanKeyDef.LongkeyFlag = 0;
ScanKeyDef.PreKeyValue = KEY_NONE;
}
}
return(KeyValue);
}
評論