天天看點

基于狀态機的按鍵檢測

基于狀态機的按鍵檢測

一般在學單片機的時候,最基礎的一個内容就是學習按鍵的輸入掃描,最簡單的方式當然是讀IO電平然後再加上一段延時做消抖。今天要分享的是我自己寫的一個基于狀态機檢測的按鍵掃描程式,使用狀态機可以根據按鍵按下時長的不同狀态實作短按單擊、長按連擊、長時間長按高速連擊(我叫超級連擊)的區分。

今天我所說的按鍵掃描程式全部是基于這種5向開關來做的,如果是矩陣按鍵掃描的話得做一些相應的修改

基于狀态機的按鍵檢測

原理圖如下,一共6個引腳。一個公共端上拉,其他5個引腳接單片機IO口就可以了。

基于狀态機的按鍵檢測

首先是最簡單的按鍵輸入方式,哪個按鍵按下就傳回哪個按鍵的值,沒有按下的時候就傳回空格鍵,這裡是純粹的按鍵掃描,一般還要加入一個20ms到150ms之間的延遲函數來做按鍵消抖:

uint8_t GetKeyInpt(){

    uint8_t Key_x;

    if(Key_Up) Key_x = 'U';
    else if(Key_Down) Key_x = 'D';
    else if(Key_Left) Key_x = 'L';
    else if(Key_Rignt) Key_x = 'R';
    else if(Key_Center) Key_x = 'C';
    else Key_x = ' ';

    return Key_x;
}
           

因為一個比較大延遲的加入,這樣的按鍵檢測也是缺點非常明顯的,無論你有沒有按下按鍵都會有一個固定的100ms左右的延遲,它大大的降低了整個程式的執行的速度。之前做智能車的時候有一次學弟跟我說他的舵機感覺不管怎麼調總是反應特别慢,最後檢測才發現他的按鍵檢測和舵機控制是放在main函數裡的,而罪魁禍首就是按鍵檢測裡的Delay();

而基于狀态機做的按鍵檢測在沒有按鍵按下的時候完全不需要做延遲,幾乎不花時間,實測我的按鍵檢測程式在48M主頻的KEAZ128上之花了13us。

在說程式之前先說說什麼是狀态機,這是百度百科上的說法:

基于狀态機的按鍵檢測

簡單的了解就是通過不停的檢測系統輸入的狀态,然後根據不同的條件讓系統處于不同的狀态,然後得到不同的輸出。

對于按鍵的輸入,我的按鍵掃描狀态轉移圖可以由下表示:

基于狀态機的按鍵檢測

這隻是一個按鍵的輸入掃描,如果多個按鍵怎麼辦?其實也很簡單弄一個數組然後用循環不就可以啦!

具體實作的代碼如下:

/////////////////////////////////////////////  按鍵輸入檢測   ////////////////////////////////////////////////////////////////
//功能:5向開關檢測按鍵
//傳回值 : 'U' 'D' 'L' 'R' 'C' 分别代表5向按鍵的 上、下、左、右、中 5個方向上的某個被按下了 ,沒有任何按鍵按下傳回 ' '
//
//     注意:
//         1、需要在頭檔案中配置按鍵的引腳,以及按下/松開按鍵時的電平狀态
//         2、按鍵輸入分為不按,短按單擊,長按連擊,超級連擊四種模式。可以在頭檔案中把短按配置成按下時傳回按鍵值或松開的時候傳回按鍵值
//            短按的時候在 按下/松開 的時候傳回一次按鍵值(需要頭檔案配置),短按超過一段時間之後,自動進入連擊和超級連擊模式(進入的時間
//            以及連擊的速度可以在頭檔案中配置),連擊模式下持續傳回按鍵值
//         3、5個按鍵是有優先級的,分别為 : 上 > 下 > 左 > 右 > 中
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
uint8_t GetKeyInpt(){

    //這個數組表示按鍵的狀态,0表示已經松開,1表示短按,2表示長按進入連擊模式,3表示超級連擊(連擊速度超快)
    static uint8_t key_state[] =   { ,  ,  ,  ,  };
    //                   對應的按鍵 'U' 'D' 'L' 'R' 'C'

    uint8_t key_input[] = {}; //按鍵目前的輸入狀态
#if Key_Trigger_Rising_Edge == 1
    static uint8_t key_input_1[] = {}; //上一次按鍵的輸入
#endif
    static uint8_t key_con[] = {}; //按鍵計數,控制按下的持續時間  
    static uint16_t key_con_s[] = {}; //進入超級連擊的計數

    static uint8_t Key_x[] = {'U','D','L','R','C'}; //傳回值
    uint8_t i = ;

    //擷取目前鍵盤狀态
    key_input[] = Key_Up;
    key_input[] = Key_Down;
    key_input[] = Key_Left;
    key_input[] = Key_Right;
    key_input[] = Key_Center;

    for(i = ;i<;i++){

        if(key_state[i]== && key_input[i] == Key_Press){//某個按鍵的之前狀态是松開的,然後又被按下了,這是短按模式

            KEY_Delay(); //消抖
            key_state[i] = ; //按鍵狀态改為按下
#if Key_Trigger_falling_Edge == 1
            KEY_Delay(); 
            return Key_x[i]; //傳回按鍵值,在這傳回相當于是下降沿觸發(短按按下的時候傳回值)
#elif Key_Trigger_Rising_Edge == 1
            return ' '; //上升沿在這不傳回按鍵值
#endif
        }
        else if(key_state[i] ==  && key_input[i] == Key_Press){ //上次某個按鍵被按下了,這次還是按下的,檢測長按模式

            key_con[i]++;
            KEY_Delay(Delay_Tim);

            if(key_con[i]>=Time_Multiple_Hit){   //設定計數時間
                key_state[i] = ; //該按鍵狀态改為長按連擊模式
                key_con[i] = ;   //清空計數值
#if Key_Trigger_Rising_Edge == 1
                return Key_x[i];  //上升沿模式下,在進入連擊模式之前先傳回一次單擊的按鍵值
#endif
            }
        }
        else if(key_state[i] ==  && key_input[i] == Key_Press){ //如果還一直按着這個按鍵,進入長按模式

            key_con[i]++;
            key_con_s[i]++; //超級連擊計數
            KEY_Delay(Delay_Tim);
            if(key_con[i]>=Speed_Multiple_Hit){   //設定計數時間
                key_con[i] = ;   //清空計數值
                return Key_x[i]; //傳回按鍵值
            }
            if(key_con_s[i] >= Time_Super_Multiple_Hit){

                key_con[i] = ;   //清空計數值
                key_con_s[i] = ;   //清空計數值
                key_state[i] = ; //進入超級連擊模式
                return Key_x[i]; //傳回按鍵值
            }
        }
        else if(key_state[i] ==  && key_input[i] == Key_Press){ //處于超級連擊狀态還按着按鍵

            key_con_s[i]++;
            KEY_Delay(Delay_Tim);
            if(key_con_s[i] >= Super_Speed_Multiple_Hit){

                key_con_s[i] = ; //清計數值
                return Key_x[i]; //傳回按鍵值
            }
        }
        else if(key_state[i] ==  && key_input[i] == Key_Release){ //某按鍵上次狀态是長按,但是這次沒有按鍵按下

            key_state[i] = ; //狀态改為沒按下
            key_con[i] = ; //檢測長按計數清0
        }
#if Key_Trigger_Rising_Edge == 1
        //這一次檢測某按鍵是松開的,但是上一次是按下的,而且按鍵的狀态是短按的時候,傳回按鍵值(這個if相當于上升沿,短按松開的時候傳回按鍵值)
        else if((key_input[i] == Key_Release && key_input_1[i] == Key_Press) && (key_state[i] == )){

            key_state[i] = ;
            KEY_Delay(); //消抖
            return Key_x[i]; //傳回按鍵值
        }
#endif
        else if(key_input[i] == Key_Release){ //某按鍵沒有按下

            key_state[i] = ; //狀态改為沒按下
            key_con[i] = ; //檢測長按連擊計數清0
            key_con_s[i] = ; //檢測超級連擊計數清0
        } 
#if Key_Trigger_Rising_Edge == 1
        key_input_1[i] = key_input[i]; //記錄按鍵
#endif
    }
    return ' '; //沒有任何按鍵按下時傳回空格' '
}
           

這裡面用了一些條件編譯,實作了按鍵單擊是下降沿觸發(按鍵按下的時間就傳回按鍵值)還是上升沿觸發(按鍵松開的時候才傳回按鍵值),還有按鍵從單擊轉換到連擊所需的時間、連擊的速度、進入高速連擊的時間、進入高速連擊的速度都是用宏在頭檔案中定義的,友善做相應的修改。

關于按鍵輸入的IO口,輸入時候觸發電平是高電平還是低電平也可以通過宏定義修改。

頭檔案鍵裡相關的宏定義如下:

/////////////////////////////////////  按鍵I/O相關配置   ////////////////////////////////////////////////////////////////////
#define Key_Up           gpio_get(E0)
#define Key_Down         gpio_get(E3)
#define Key_Left         gpio_get(E2)
#define Key_Right        gpio_get(I3)
#define Key_Center       gpio_get(I2)


#define KeyUPin          E0
#define KeyDPin          E3
#define KeyLPin          E2
#define KeyRPin          I3
#define KeyCPin          I2

#define Hight            1       //高電平
#define Low              0       //低電平

//配置按鍵按下的時候和松開的時候的電平狀态
#define Key_Press        Hight   //按鍵按下時的電平狀态
#define Key_Release      Low     //按鍵松開是的電平狀态

/////////////////////////////////////  按鍵檢測相關配置   ////////////////////////////////////////////////////////////////////

//按鍵短按的時候是下降沿觸發還是上升沿觸發觸發方式同時隻能存在一個,兩個都存在的時候上升沿優先級比較高,下降沿無效
//注意:兩個觸發方式都為0的時候短按會失效
#define Key_Trigger_Rising_Edge     0  //上升沿觸發:按鍵短按松開的時候才傳回按鍵值
#define Key_Trigger_falling_Edge    1  //下降沿觸發:按鍵短按按下的時候就傳回按鍵值

//按鍵短按多久進入連擊模式的時間,這個時間大概 = Delay_Tim * Time_Multiple_Hit,最大為255(用的uint8_t型變量來計數)
#define Time_Multiple_Hit  8

//進入連擊模式後連擊的速度,每次連擊之間的間隔為 Speed_Multiple_Hit * Delay_Tim   ms
#define Speed_Multiple_Hit  5

//在長按連擊的模式下,一直長按進入超級連擊模式的時間,時間大概 = 10ms * Time_Super_Multiple_Hit
#define Time_Super_Multiple_Hit  65

//進入超級連擊模式後連擊的速度,每次連擊之間的間隔為 Super_Speed_Multiple_Hit * Delay_Tim   ms  如果速度和長按連擊一樣,相當于沒有超級連擊
#define Super_Speed_Multiple_Hit 2

//替換延遲函數
#define KEY_Delay  systick_delay_ms
#define Delay_Tim  5
           

繼續閱讀