一段比較好的按鍵實現程式碼
之前的一個專案按鍵比較多,面板上面有按鍵,遙控器,處理的稍微複雜一點,MCU使用的是STM8S005K6.關於按鍵部分的處理,現在拿處理來和大家分享一下,說的不對的地方還請各位大俠請教,大家共同進步。按鍵通常分有IO口按鍵(BUTTON),AD按鍵(通過AD取樣電壓),IR(遙控器)按按鍵功能分:有短按鍵,長按鍵,連續按鍵。打個比方,遙控電視機,按一下音量鍵,音量增加1,這個就是短按鍵。按住音量鍵不放,音量連續加,這個就是連續按鍵。按住一個按鍵5s,系統會復位,這個是長按鍵。怎麼去處理這些不同的按鍵了,下面我們就細細來說
1,IO口按鍵,就是我們比較常見的一個IO接一個按鍵,或者是一個矩陣鍵盤。很多新人的處理方法可能是取樣延時的方法,當年我也是這樣的,如下
- if(GETIO==low)
- {
- delay_10ms()
- if(GETIO==low)
- {
- //得到按鍵值
- }
- }
這種方法雖然簡單,但是有很大弊端。
首先 Delay 浪費很多時間,影響系統。
第二,無法判斷長短按鍵,連續按鍵。
第三,如果這個按鍵是開關機按鍵系統在低功耗狀態下,需要中斷喚醒,這種方法比較容易出問題,如STM8S系列的 halt 模式。
所以我們一般在產品開發的過程中,採用掃描的方法,就是每隔10ms 去檢測IO的狀態,看是否有按鍵,然後去抖動,判斷按鍵功能。
參考程式碼如下,這段程式碼是之前在一個論壇看到的比我自己寫的更加優秀,所以拿出來和大家分享一下,也順便感謝一下作者。
這段程式碼,容易修改,可以根據自己的時間需要,進行長短按鍵,連續按鍵,還有組合按鍵的判斷。
- /* 按鍵濾波時間50ms, 單位10ms
- 只有連續檢測到50ms狀態不變才認為有效,包括彈起和按下兩種事件
- */
- #define BUTTON_FILTER_TIME 5
- #define BUTTON_LONG_TIME 300 /* 持續1秒,認為長按事件 */
- /*
- 每個按鍵對應1個全域性的結構體變數。
- 其成員變數是實現濾波和多種按鍵狀態所必須的
- */
- typedef struct
- {
- /* 下面是一個函式指標,指向判斷按鍵手否按下的函式 */
- unsigned char (*IsKeyDownFunc)(void); /* 按鍵按下的判斷函式,1表示按下 */
- unsigned char Count; /* 濾波器計數器 */
- unsigned char FilterTime; /* 濾波時間(最大255,表示2550ms) */
- unsigned short LongCount; /* 長按計數器 */
- unsigned short LongTime; /* 按鍵按下持續時間, 0表示不檢測長按 */
- unsigned char State; /* 按鍵當前狀態(按下還是彈起) */
- unsigned char KeyCodeUp; /* 按鍵彈起的鍵值程式碼, 0表示不檢測按鍵彈起 */
- unsigned char KeyCodeDown; /* 按鍵按下的鍵值程式碼, 0表示不檢測按鍵按下 */
- unsigned char KeyCodeLong; /* 按鍵長按的鍵值程式碼, 0表示不檢測長按 */
- unsigned char RepeatSpeed; /* 連續按鍵週期 */
- unsigned char RepeatCount; /* 連續按鍵計數器 */
- }BUTTON_T;
- typedef enum
- {
- KEY_NONE = 0, /* 0 表示按鍵事件 */
- KEY_DOWN_Power, /* 按鍵鍵按下 */
- KEY_UP_Power, /* 按鍵鍵彈起 */
- KEY_LONG_Power, /* 按鍵鍵長按 */
- KEY_DOWN_Power_TAMPER /* 組合鍵,Power鍵和WAKEUP鍵同時按下 */
- }KEY_ENUM;
- BUTTON_T s_Powerkey;
- //是否有按鍵按下介面函式
- 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; /* 判斷按鍵按下的函式 */
- s_Powerkey.FilterTime = BUTTON_FILTER_TIME; /* 按鍵濾波時間 */
- s_Powerkey.LongTime = BUTTON_LONG_TIME; /* 長按時間 */
- s_Powerkey.Count = s_Powerkey.FilterTime / 2; /* 計數器設定為濾波時間的一半 */
- s_Powerkey.State = 0; /* 按鍵預設狀態,0為未按下 */
- s_Powerkey.KeyCodeDown = KEY_DOWN_Power; /* 按鍵按下的鍵值程式碼 */
- s_Powerkey.KeyCodeUp =KEY_UP_Power; /* 按鍵彈起的鍵值程式碼 */
- s_Powerkey.KeyCodeLong = KEY_LONG_Power; /* 按鍵被持續按下的鍵值程式碼 */
- s_Powerkey.RepeatSpeed = 0; /* 按鍵連發的速度,0表示不支援連發 */
- s_Powerkey.RepeatCount = 0; /* 連發計數器 */
- }
- void Panakey_Init(void)
- {
- PanakeyHard_Init(); /* 初始化按鍵變數 */
- PanakeyVar_Init(); /* 初始化按鍵硬體 */
- }
複製程式碼
*********************************************************************************************************
* 函 數 名: bsp_DetectButton
* 功能說明: 檢測一個按鍵。非阻塞狀態,必須被週期性的呼叫。
* 形 參:按鍵結構變數指標
* 返 回 值: 無
*********************************************************************************************************
- */
- void Button_Detect(BUTTON_T *_pBtn)
- {
- if (_pBtn->IsKeyDownFunc())
- {
- if (_pBtn->Count < _pBtn->FilterTime)
- {
- _pBtn->Count = _pBtn->FilterTime;
- }
- else if(_pBtn->Count < 2 * _pBtn->FilterTime)
- {
- _pBtn->Count++;
- }
- else
- {
- if (_pBtn->State == 0)
- {
- _pBtn->State = 1;
- /* 傳送按鈕按下的訊息 */
- if (_pBtn->KeyCodeDown > 0)
- {
- /* 鍵值放入按鍵FIFO */
- Pannelkey_Put(_pBtn->KeyCodeDown);// 記錄按鍵按下標誌,等待釋放
- }
- }
- if (_pBtn->LongTime > 0)
- {
- if (_pBtn->LongCount < _pBtn->LongTime)
- {
- /* 傳送按鈕持續按下的訊息 */
- if (++_pBtn->LongCount == _pBtn->LongTime)
- {
- /* 鍵值放入按鍵FIFO */
- Pannelkey_Put(_pBtn->KeyCodeLong);
- }
- }
- else
- {
- if (_pBtn->RepeatSpeed > 0)
- {
- if (++_pBtn->RepeatCount >= _pBtn->RepeatSpeed)
- {
- _pBtn->RepeatCount = 0;
- /* 常按鍵後,每隔10ms傳送1個按鍵 */
- Pannelkey_Put(_pBtn->KeyCodeDown);
- }
- }
- }
- }
- }
- }
- else
- {
- if(_pBtn->Count > _pBtn->FilterTime)
- {
- _pBtn->Count = _pBtn->FilterTime;
- }
- else if(_pBtn->Count != 0)
- {
- _pBtn->Count--;
- }
- else
- {
- if (_pBtn->State == 1)
- {
- _pBtn->State = 0;
- /* 傳送按鈕彈起的訊息 */
- if (_pBtn->KeyCodeUp > 0) /*按鍵釋放*/
- {
- /* 鍵值放入按鍵FIFO */
- Pannelkey_Put(_pBtn->KeyCodeUp);
- }
- }
- }
- _pBtn->LongCount = 0;
- _pBtn->RepeatCount = 0;
- }
- }
- //功能說明: 檢測所有按鍵。10MS 呼叫一次
- void Pannelkey_Polling(void)
- {
- Button_Detect(&s_Powerkey); /* USER 鍵 */
- }
- void Pannelkey_Put(void)
- {
- // 定義一個佇列 放入按鍵值
- }
複製程式碼
2,遙控器按鍵,遙控器解碼的一般就有兩種 脈寬調製和脈衝調製,這裡就不細講解碼的過程了。這裡詳細解碼之後,如何處理遙控器按鍵
實現遙控器按鍵的長短按功能和連續按鍵功能。
程式碼裁剪過,大家根據實際需要改動
其實AD按鍵,通過AD取樣獲得按鍵值之後,可以採取如下面的一樣方法處理,提一個函式介面即可
- typedef struct
- {
- unsigned char count;//
- unsigned char LongkeyFlag;/*是否長按鍵,1代表是*/
- unsigned char PreKeyValue;/*按鍵值的一個備份,用於釋放按鍵值*/
- }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個介面函式,如下。
- unsigned char get_irkey(void);
- unsigned char ISSeriesKey(unsigned char temp);//按鍵是否需要做連續按鍵
- unsigned char changeSeriesKey(unsigned char temp);//轉換連續按鍵值
- unsigned char ISLongKey(unsigned char temp);//按鍵是否需要做連續按鍵
- unsigned char changeToLongKey(unsigned char temp);//轉換連續按鍵值
- 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)) //處理連續按鍵
- {
- KeyValue = changeSeriesKey ( KeyValueTemp );
- ScanKeyDef.PreKeyValue = KEY_NONE;
- }
- else if ( ScanKeyDef.count < SHORT_PRESS_TIME_IR )
- {
- ScanKeyDef.PreKeyValue = KeyValueTemp;
- }
- else
- {
- ScanKeyDef.PreKeyValue = KEY_NONE; // 無效按鍵
- }
- }
- else if ( ScanKeyDef.count == LONG_PRESS_TIME_IR )
- {
- if (ISLongKey(KeyValueTemp))
- {
- {
- ScanKeyDef.LongkeyFlag = 1;
- KeyValue = changeToLongKey ( KeyValueTemp );
- }
- }
- ScanKeyDef.PreKeyValue = KEY_NONE;
- }
- else if (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);
- }
通過我的修改支援了AD按鍵
只需在一個任務裡週期呼叫void key_Polling(uint16_t adVal)即可,最終的按鍵處理是在handkeyCode裡
freeRTOS中的呼叫示例如下:
uint16_t keyAdc = 0;
void keyScanTask(void const * argument)
{
/* USER CODE BEGIN keyScanTask */
keyVar_Init();
/* Infinite loop */
for(;;)
{
keyAdc = Get_Adc();
key_Polling(keyAdc);
osDelay(10);
}
/* USER CODE END keyScanTask */
}
電路如此:
按鍵程式碼如下:
#include "FreeRTOS.h"
#include "task.h"
#include "myAdKey.h"
/* 按鍵濾波時間50ms, 單位10ms
只有連續檢測到50ms狀態不變才認為有效,包括彈起和按下兩種事件
*/
#define KEY_FILTER_TIME 5
/*定義多長時間為長按,單位為秒,精度為 portTICK_PERIOD_MS 毫秒*/
#define KEY_LONG_TIME 2.0
/*
每個按鍵對應1個全域性的結構體變數。
其成員變數是實現濾波和多種按鍵狀態所必須的
*/
typedef struct
{
unsigned char Count; /* 濾波器計數器 */
unsigned char FilterTime; /* 濾波時間(最大255,表示2550ms) */
unsigned char State; /* 按鍵當前狀態(按下還是彈起) */
unsigned char keyCode;
TickType_t keyDownTime;
TickType_t keyUpTime;
} BUTTON_T;
typedef enum
{
KEY_NONE = 0, /* 0 表示按鍵事件 */
KEY_A, /* 按鍵鍵按下 */
KEY_B, /* 按鍵鍵彈起 */
KEY_CONNECT, /* 按鍵鍵長按 */
KEY_DISCONNECT
} KEY_ENUM;
BUTTON_T key_A, key_B, connect, disconnect ;
volatile char handIsDisconnected = 0, keyAIsShortPushed = 0, keyAIsLongPushed = 0, keyBIsShortPushed = 0, keyBIsLongPushed = 0, handIsConnected = 0;
void keyVar_Init(void)
{
key_A.FilterTime = KEY_FILTER_TIME; /* 按鍵濾波時間 */
key_A.Count = key_A.FilterTime / 2; /* 計數器設定為濾波時間的一半 */
key_A.State = 0; /* 按鍵預設狀態,0為未按下 */
key_A.keyCode = KEY_A;
///////////////////////////////////
key_B.FilterTime = KEY_FILTER_TIME; /* 按鍵濾波時間 */
key_B.Count = key_B.FilterTime / 2; /* 計數器設定為濾波時間的一半 */
key_B.State = 0; /* 按鍵預設狀態,0為未按下 */
key_B.keyCode = KEY_B;
//////////////////////////////////
connect.FilterTime = 100; /* 接入檢測濾波時間 100x10ms=1S */
connect.Count = connect.FilterTime / 2; /* 計數器設定為濾波時間的一半 */
connect.State = 0; /* 按鍵預設狀態,0為未按下 */
connect.keyCode = KEY_CONNECT;
//////////////////////////////////
disconnect.FilterTime = 100; /* 斷開檢測濾波時間 100x10ms=1S */
disconnect.Count = disconnect.FilterTime / 2; /* 計數器設定為濾波時間的一半 */
disconnect.State = 0; /* 按鍵預設狀態,0為未按下 */
disconnect.keyCode = KEY_DISCONNECT;
}
void handkeyCode(BUTTON_T *_pBtn)
{
TickType_t timeInterval;
timeInterval = _pBtn->keyUpTime - _pBtn->keyDownTime;
switch(_pBtn->keyCode)
{
case KEY_A:
{
if(timeInterval > (TickType_t)(KEY_LONG_TIME * configTICK_RATE_HZ )) // 按鍵A長按
{
keyAIsLongPushed = 1;
}
else //按鍵A短按
{
keyAIsShortPushed = 1;
}
break;
}
case KEY_B:
{
if(timeInterval > (TickType_t)(KEY_LONG_TIME * configTICK_RATE_HZ )) // 按鍵B長按
{
keyBIsLongPushed = 1;
}
else //按鍵B短按
{
keyBIsShortPushed = 1;
}
break;
}
case KEY_CONNECT:
{
handIsConnected = 1;
break;
}
case KEY_DISCONNECT:
{
handIsDisconnected = 1;
break;
}
}
}
void keyDownDetect(BUTTON_T *_pBtn)
{
if (_pBtn->Count < _pBtn->FilterTime) // 2 5
{
_pBtn->Count = _pBtn->FilterTime;
}
else if(_pBtn->Count < 2 * _pBtn->FilterTime)
{
_pBtn->Count++;
}
else
{
if (_pBtn->State == 0)
{
_pBtn->State = 1;
/* 傳送按鈕按下的訊息 */
_pBtn->keyDownTime = xTaskGetTickCount();
}
}
}
void keyUpDetect(BUTTON_T *_pBtn)
{
if(_pBtn->Count > _pBtn->FilterTime)
{
_pBtn->Count = _pBtn->FilterTime;
}
else if(_pBtn->Count != 0)
{
_pBtn->Count--;
}
else
{
if (_pBtn->State == 1)
{
_pBtn->State = 0;
/* 傳送按鈕彈起的訊息 */
_pBtn->keyUpTime = xTaskGetTickCount();
handkeyCode(_pBtn);
}
}
}
/*
非阻塞狀態,必須被週期性的呼叫。
*/
void key_Polling(uint16_t adVal)
{
if(adVal > 3584 && adVal < 4095) // 探頭斷開
{
keyDownDetect(&connect);
keyUpDetect(&disconnect);
}
else if(adVal > 2560 && adVal < 3584) // 接入,注意這裡既包括手柄第一次插入的情況又包括A鍵或B鍵由按下到釋放的情況
{
keyUpDetect(&connect);
keyDownDetect(&disconnect);
keyUpDetect(&key_A);
keyUpDetect(&key_B);
}
else if(adVal > 1536 && adVal < 2560) // 按鍵A按下
{
keyDownDetect(&key_A);
}
else if(adVal > 512 && adVal < 1536) //按鍵B按下
{
keyDownDetect(&key_B);
}
}