在嵌入式系統中,一定要遵循能不用阻塞延時就不用阻塞延時的規則,通常在裸機系統中,很多人處理按鍵還是用非常老的延時消抖方法,這樣的程序不僅可移植性非常差,而且程序效率也非常的低,在多任務系統中,按鍵狀態機是用得比較多的方法,不過狀態機的邏輯和程序復雜度都挺高的,今天就給大家介紹一種類似於數電里面學的真值表方法來處理按鍵,按鍵只有兩種電平狀態0/1(模擬的除外),這兩種狀態的組合邏輯就有四種,分別為00,01,10,11,如果我們約定未按下為0,按下為1,可以形成以下“真值表”;
未按下 | 按下 | 狀態 |
0 | 0 | 釋放 |
0 | 1 | 按下 |
1 | 0 | 彈起 |
1 | 1 | 任然按下 |
根據真值表我們可以在程序中把按鍵的這兩個電平狀態記為上一次狀態和當前狀態,那么代碼就很簡單了,這里以STM32為例,其它平台稍微實現下底層就行了。
1 #ifndef __KEY_H
2 #define __KEY_H
3
4 #include "stm32f1xx_hal.h"
5 #include "stdbool.h"
6
7 #define KEY1_GPIO_PORT GPIOA
8 #define KEY1_GPIO_PIN GPIO_PIN_0
9 #define KEY1_ON GPIO_PIN_SET /*按鍵按下的有效電平*/
10
11 #define KEY2_GPIO_PORT GPIOC
12 #define KEY2_GPIO_PIN GPIO_PIN_13
13 #define KEY2_ON GPIO_PIN_RESET
14
15 /*按鍵編號枚舉*/
16 typedef enum { 17 KEY1, 18 KEY2, 19 KEY_NUM_CNT,/*按鍵數量統計*/ 20 }keyNumType_e; 21 22 /*按鍵狀態枚舉值*/ 23 typedef enum { 24 KEY_PRESS, /*按下*/ 25 KEY_PRESSING, /*任然按下*/ 26 KEY_RELEASE, /*彈起*/ 27 KEY_NONE, /*未按下*/ 28 }keyState_t; 29 30 /*按鍵參數相關結構體*/ 31 typedef struct _keyPara_t{ 32 keyState_t keyState; 33 bool keyLastStatus; 34 bool keyThisStatus; 35 }keyPara_t; 36 37 /*按鍵 GPIO 初始化*/ 38 void KEY_GPIO_InitCfg(void); 39 /*掃描按鍵(按一定周期調用)*/ 40 void KeyScan(void); 41 /*獲取按鍵狀態*/ 42 keyState_t GetKeyState(keyNumType_e keyNum); 43 44 #endif/*__KEY_H*/
1 #include "./KEY/key.h"
2
3 /*定義一個按鍵參數結構體數組*/
4 keyPara_t aKeyPara[KEY_NUM_CNT];
5
6 /*按鍵 GPIO 初始化*/
7 void KEY_GPIO_InitCfg(void) 8 { 9 __HAL_RCC_GPIOA_CLK_ENABLE(); 10 __HAL_RCC_GPIOC_CLK_ENABLE(); 11 12 GPIO_InitTypeDef GPIO_InitStruct; 13 14 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; 15 16 GPIO_InitStruct.Pull = GPIO_PULLDOWN; 17 GPIO_InitStruct.Pin = KEY1_GPIO_PIN; 18 HAL_GPIO_Init(KEY1_GPIO_PORT,&GPIO_InitStruct); 19 20 GPIO_InitStruct.Pull = GPIO_PULLUP; 21 GPIO_InitStruct.Pin = KEY2_GPIO_PIN; 22 HAL_GPIO_Init(KEY2_GPIO_PORT,&GPIO_InitStruct); 23 } 24 25 /*按鍵端口*/ 26 GPIO_TypeDef *aKEY_Gpio_Port[] = {KEY1_GPIO_PORT, 27 KEY2_GPIO_PORT}; 28 /*按鍵引腳*/ 29 uint16_t aKEY_Gpio_Pin[] = {KEY1_GPIO_PIN, 30 KEY2_GPIO_PIN}; 31 /*按鍵有效狀態*/ 32 GPIO_PinState aKEY_On_State[] = {KEY1_ON, 33 KEY2_ON}; 34 35 /*獲取按鍵 I/O 電平*/ 36 bool CbGetKeyIoStatus(keyNumType_e keyNum) 37 { 38 if(HAL_GPIO_ReadPin(aKEY_Gpio_Port[keyNum],aKEY_Gpio_Pin[keyNum]) == aKEY_On_State[keyNum]) 39 { 40 return true; 41 } 42 return false; 43 } 44 45 /*檢查按鍵是否有效*/ 46 bool CheckKeyIfValid(bool (*pCbGetKeyIoStatus)(keyNumType_e ),keyNumType_e keyNum) 47 { 48 return pCbGetKeyIoStatus(keyNum); 49 } 50 51 /*掃描按鍵(按一定周期調用)*/ 52 void KeyScan(void) 53 { 54 for(uint32_t i = 0;i<sizeof(aKeyPara) / sizeof(keyPara_t);i++) 55 { 56 aKeyPara[i].keyThisStatus = CheckKeyIfValid(CbGetKeyIoStatus,(keyNumType_e)i); 57 58 if ( (aKeyPara[i].keyThisStatus) && (!(aKeyPara[i].keyLastStatus)) )//10 59 { 60 aKeyPara[i].keyState = KEY_PRESS; 61 } 62 else if ( (!(aKeyPara[i].keyThisStatus)) && aKeyPara[i].keyLastStatus )//01 63 { 64 aKeyPara[i].keyState = KEY_RELEASE; 65 } 66 else if( aKeyPara[i].keyThisStatus && aKeyPara[i].keyLastStatus )//11 67 { 68 aKeyPara[i].keyState = KEY_PRESSING; 69 } 70 else if( (!(aKeyPara[i].keyThisStatus)) && (!(aKeyPara[i].keyLastStatus)) )//00 71 { 72 aKeyPara[i].keyState = KEY_NONE; 73 } 74 75 aKeyPara[i].keyLastStatus = aKeyPara[i].keyThisStatus; 76 } 77 } 78 79 /*獲取按鍵狀態*/ 80 keyState_t GetKeyState(keyNumType_e keyNum) 81 { 82 return aKeyPara[keyNum].keyState; 83 } 84 85 /*用戶函數*/ 86 void UserFun(void) 87 { 88 if(GetKeyState(KEY1) == KEY_PRESS) 89 { 90 /*do something*/ 91 } 92 if(GetKeyState(KEY1) == KEY_RELEASE) 93 { 94 /*do something*/ 95 } 96 if(GetKeyState(KEY1) == KEY_PRESSING) 97 { 98 /*do something*/ 99 } 100 101 if(GetKeyState(KEY2) == KEY_PRESS) 102 { 103 /*do something*/ 104 } 105 if(GetKeyState(KEY2) == KEY_RELEASE) 106 { 107 /*do something*/ 108 } 109 if(GetKeyState(KEY2) == KEY_PRESSING) 110 { 111 /*do something*/ 112 } 113 }
核心代碼就在keyScan();函數中,簡單幾句就可以實現,主要是原理懂了就行,代碼僅自己用,並沒有做過多的封裝和降低耦合度,第一次寫文章,文筆限制,如果讀者有不懂的地方還請留言,歡迎吐槽。