1. 程式人生 > >【按鍵】[獨立按鍵] - 3:三擊 以及 N擊

【按鍵】[獨立按鍵] - 3:三擊 以及 N擊

三、 [三擊]程式

1.三擊介紹

  • 三擊判定:在完成第一次【單擊】之後,在一定的時間間隔內(本程式使用的是300ms),接著完成第二次【單擊】,時間間隔重新計時,然後又在這個時間間隔內,完成第三次【單擊】,及判定為【三擊】。
    注:【單擊】是包括按下和釋放按鍵的過程,判定方式沿用上文所說的,如果忘了,可以參考上文。
  • 三擊響應時序圖
    這裡寫圖片描述
    注:
    T1:是單擊判定的時間,範圍:30ms < T1 < 3000ms。
    T2:是判定雙擊的時間間隔,是個定值 300ms。這個時間間隔的計算,是從第一次【單擊】釋放後開始計時,直到第二次【單擊】釋放按鍵後結束。在完成第一次【單擊】後(釋放按鍵後開始計時),在這個時間間隔內,如果再一次完成【單擊】
    T3:是判定三擊的時間間隔,雙擊之後,在這個時間內,再一次有【單擊】,擊判定為【三擊】。其實質與T2一樣。

2.按鍵程式結果

按鍵程式結構可以分為三個部分:
  • 第一部分:判斷有無單擊按鍵;
  • 第二部分:判斷雙擊,即是在預設的時間間隔內,有無第二次【單擊】;
  • 第三部分:判斷三擊,即是在【雙擊】後,在預設的時間間隔內,有無再一次的【單擊】。

    3.三擊程式的原始碼以及註釋

    程式的註釋寫的很詳細,應該是可以讀懂的,如果有疑問可以留言討論。
    以下是key.c 的原始碼:

// =========================== key.c ======================
#include "reg51.h"

#define KEY_INPUT           P1.0    //  按鍵IO
#define KEY_STATE_0 0 // 按鍵狀態 #define KEY_STATE_1 1 #define KEY_STATE_2 2 #define KEY_STATE_3 3 #define SINGLE_KEY_TIME 3 // SINGLE_KEY_TIME*10MS = 30MS 判定單擊的時間長度,軟體消抖 #define KEY_INTERVAL 30 // KEY_INTERVAL*10MS = 300MS 判定雙擊的時間間隔 #define LONG_KEY_TIME 300 // LONG_KEY_TIME*10MS = 3S 判定長按的時間長度
#define N_KEY 0 // no click #define S_KEY 1 // single click #define D_KEY 2 // double click #define T_KEY 3 // Triple click #define L_KEY 10 // long press // ----------------------------------- key_driver -------------------------- unsigned char key_driver(void) { static unsigned char key_state = 0; static unsigned int key_time = 0; unsigned char key_press, key_return; key_return = N_KEY; // 清除 返回按鍵值 key_press = key_input; // 讀取當前鍵值 switch (key_state) { case KEY_STATE_0: // 按鍵狀態0:判斷有無按鍵按下 if (!key_press) // 有按鍵按下 { key_time = 0; // 清零時間間隔計數 key_state = KEY_STATE_1; // 然後進入 按鍵狀態1 } break; case KEY_STATE_1: // 按鍵狀態1:軟體消抖(確定按鍵是否有效,而不是誤觸)。按鍵有效的定義:按鍵持續按下超過設定的消抖時間。 if (!key_press) { key_time++; // 一次10ms if(key_time>=SINGLE_KEY_TIME) // 消抖時間為:SINGLE_KEY_TIME*10ms = 30ms; { key_state = KEY_STATE_2; // 如果按鍵時間超過 消抖時間,即判定為按下的按鍵有效。按鍵有效包括兩種:單擊或者長按,進入 按鍵狀態2, 繼續判定到底是那種有效按鍵 } } else key_state = KEY_STATE_0; // 如果按鍵時間沒有超過,判定為誤觸,按鍵無效,返回 按鍵狀態0,繼續等待按鍵 break; case KEY_STATE_2: // 按鍵狀態2:判定按鍵有效的種類:是單擊,還是長按 if(key_press) // 如果按鍵在 設定的長按時間 內釋放,則判定為單擊 { key_return = S_KEY; // 返回 有效按鍵值:單擊 key_state = KEY_STATE_0; // 返回 按鍵狀態0,繼續等待按鍵 } else { key_time++; if(key_time >= LONG_KEY_TIME) // 如果按鍵時間超過 設定的長按時間(LONG_KEY_TIME*10ms=300*10ms=3000ms), 則判定為 長按 { key_return = L_KEY; // 返回 有效鍵值值:長按 key_state = KEY_STATE_3; // 去狀態3,等待按鍵釋放 } } break; case KEY_STATE_3: // 等待按鍵釋放 if (key_press) { key_state = KEY_STATE_0; // 按鍵釋放後,進入 按鍵狀態0 ,進行下一次按鍵的判定 } break; default: // 特殊情況:key_state是其他值得情況,清零key_state。這種情況一般出現在 沒有初始化key_state,第一次執行這個函式的時候 key_state = KEY_STATE_0; break; } return key_return; // 返回 按鍵值 } // ----------------------------------- key_read -------------------------------- unsigned char key_read(void) { static unsigned char key_state1=0, key_time1=0; unsigned char key_return,key_temp; key_return = N_KEY; // 清零 返回按鍵值 key_temp = key_driver(); // 讀取鍵值 switch(key_state1) { case KEY_STATE_0: // 按鍵狀態0:等待有效按鍵(通過 key_driver 返回的有效按鍵值) if (key_temp == S_KEY) // 如果是[單擊],不馬上返回單擊按鍵值,先進入 按鍵狀態1,判斷是否有[雙擊]的可能 { key_time1 = 0; // 清零計時 key_state1 = KEY_STATE_1; } else // 如果不是[單擊],直接返回按鍵值。這裡的按鍵值可能是:[長按],[無效按鍵] { key_return = key_temp; // 返回 按鍵值 } break; case KEY_STATE_1: // 按鍵狀態1:判定是否有[雙擊] if (key_temp == S_KEY) // 有[單擊]後,如果在 設定的時間間隔(KEY_INTERVAL*10ms=30*10ms=300ms) 內,再次有[單擊],則為[雙擊],但是不馬上返回 有效按鍵值為[雙擊],先進入 按鍵狀態2,判斷是否有[三擊] { key_time1 = 0; // 清零 時間間隔 key_state1 = KEY_STATE_2; // 改變 按鍵狀態值 } else // 有[單擊]後,如果在 設定的時間間隔(KEY_INTERVAL*10ms=30*10ms=300ms)內,沒有[單擊]出現,則判定為 [單擊] { key_time1++; // 計數 時間間隔 if(key_time1 >= KEY_INTERVAL) // 超過 時間間隔 { key_return = S_KEY; // 返回 有效按鍵:[單擊] key_state1 = KEY_STATE_0; // 返回 按鍵狀態0,等待新的有效按鍵 } } break; case KEY_STATE_2: // 按鍵狀態2:判定是否有[三擊] if (key_temp == S_KEY) // 有[雙擊]後,如果在 設定的時間間隔(KEY_INTERVAL*10ms=30*10ms=300ms) 內,再次有[單擊],則為[三擊],由於這裡只擴充套件到[三擊],所以馬上返回 有效按鍵值為[三擊] { key_return = T_KEY; // 返回 有效按鍵:[三擊] key_state1 = KEY_STATE_0; // 返回 按鍵狀態0,等待新的有效按鍵 } else // 有[雙擊]後,如果在 設定的時間間隔(KEY_INTERVAL*10ms=30*10ms=300ms)內,沒有[單擊],則判定為 [雙擊] { key_time1++; // 計數 時間間隔 if(key_time1 >= KEY_INTERVAL) // 超過 時間間隔 { key_return = D_KEY; // 返回 有效按鍵:[雙擊] key_state1 = KEY_STATE_0; // 返回 按鍵狀態0,等待新的有效按鍵 } } break; default: // 特殊情況:key_state是其他值得情況,清零key_state。這種情況一般出現在 沒有初始化key_state,第一次執行這個函式的時候 key_state1 = KEY_STATE_0; break; } return key_return; // 返回 按鍵值 }

使用注意:
1)硬體:按鍵的一端接地(GND),另一端接IO口。IO為輸入,一定要有上拉電阻。
2)定時器:這裡為了精確的定時,所以使用了定時器,定時器的時間是10ms。
3)掃描週期:呼叫此函式時,一定確保”掃描週期“要小於10ms。不然按鍵內所涉及的時間就會不準,會偏大。所涉及的時間包括消抖時長,按鍵長按時長等。
掃描週期定義:從 第一次進入按鍵掃描程式 開始,到第二次進入按鍵掃描程式時 結束,之間所用的時間。
測量掃描週期的方法:可以在按鍵掃描程式的第一句,新增IO口取反函式,然後用示波器檢視改IO口,其IO口週期的一般就是掃描週期了。
4)特別注意以上程式的3個關於時間的巨集,相當的重要。如果想更改按鍵的單擊的靈敏度,雙擊(三擊)的速度,或者長按的時間,只需要修改這些巨集的值即可。
比如:針對於老人的使用的按鍵,就需要將雙擊的速度調節的慢一點,就可以將KEY_INTERVAL的值增大。

  • SINGLE_KEY_TIME:單擊的靈敏度,值越小,越靈敏。
  • KEY_INTERVAL :雙擊的點選速度,修改這個值,值越小,速度越快。同時這個值也決定了單擊的響應時間,因為單擊之後,還需要判斷在這個時間間隔內沒有第二次單擊,如果沒有才是真正的單擊,所以單擊響應的時間為:SINGLE_KEY_TIME+KEY_INTERVAL;
  • LONG_KEY_TIME :長按的時間,修改這個即可,值越大,時間越長。

4.按鍵程式的使用例項

這裡以C51位硬體平臺進行例項講解
1)例項程式的功能:

  • 單擊:取反LED1
  • 雙擊:取反LED2
  • 三擊:取反LED3
  • 長按:點亮LED1,LED2,LED3

2)硬體:

  • 按鍵IO:P1.0
  • LED1 :P2.0
  • LED2 :P2.1
  • LED3 :P2.2

以下是 main.c 原始碼:

// =========================== key.c ======================

#include "reg51.h"
#include "key.c"

#define LED_ALL_ON()    P2 |= 0x07
#define LED_ALL_OFF()   P2 &= ~0x07

sbit LED1 = P2.0;                   // 定義LEDIO口
sbit LED2 = P2.1;                   // 定義LEDIO口
sbit LED3 = P2.2;                   // 定義LEDIO口

unsigned char g_u8_KeyValue;        // 按鍵值
unsigned char g_flag_10ms_key;      // 10ms 計時標誌


void key_read();                    // 宣告讀取按鍵函式


void T0_Init_10ms(void)             // timer0,初始化函式 ,定時時間為 10ms
{
    TMOD |= 0x01;
    TH0 = (65535 - 10000)/256;
    TL0 = (65535 - 10000)%256;

    ET0 = 1;
    TR0 = 1;
    EA = 1;
}

// 主函式
void main(void)
{
    P1.0 = 1;                                       // P1.0 拉高
    T0_Init_10ms();                                 // 定時器0,初始化,定時10ms

    while(1)
    {
        if(g_flag_10ms_key)                         // 等待10ms,定時完成
        {
            g_flag_10ms_key = 0;                    // 清零10ms定時標誌

            g_u8_KeyValue = key_read();             // 讀取按鍵值

            switch(g_u8_KeyValue)
            {
                case S_KEY: LED1 = !LED1; break;    // 單擊 取反LED1
                case D_KEY: LED2 = !LED2; break;    // 雙擊 取反LED2
                case T_KEY: LED3 = !LED3; break;    // 三擊 取反LED3
                case L_KEY: LED_ALL_ON(); break;    // 長按 點亮所有的LED
            }
        }
    }
}

// timer0 中斷服務程式
void IRQ_T0(void) interrupt 1
{
    g_flag_10ms_key = 1;                        // 置位 10ms 定時標誌
}

Pillar Peng
2016.3.29 - 17:30


有之前單擊,雙擊,三擊的介紹,四擊以及N擊只是一小段程式的區別。有興趣的可以擴充套件一下4擊以及N擊,試著自己動手編寫程式,其實很簡單的,只需要更改key_read 函式即可,大家可以對照雙擊,三擊和四擊的程式,就可以看得出來,改動的地方很少。

將擴充套件的四擊程式附在這裡:

// =========================== key.c ======================
#include "reg51.h"

#define KEY_INPUT           P1.0    //  按鍵IO

#define KEY_STATE_0         0       //  按鍵狀態
#define KEY_STATE_1         1
#define KEY_STATE_2         2
#define KEY_STATE_3         3

#define SINGLE_KEY_TIME     3       //  SINGLE_KEY_TIME*10MS = 30MS     判定單擊的時間長度,軟體消抖
#define KEY_INTERVAL        30      //  KEY_INTERVAL*10MS    = 300MS 判定雙擊的時間間隔
#define LONG_KEY_TIME       300     //  LONG_KEY_TIME*10MS   = 3S   判定長按的時間長度


#define N_KEY               0       //  no click
#define S_KEY               1       //  single click        單擊
#define D_KEY               2       //  double click        雙擊
#define T_KEY               3       //  Triple click        三擊
#define Q_KEY               4       //  Quadruple click     四擊
#define L_KEY               10      //  long press          長按

// ----------------------------------- key_driver --------------------------
unsigned char key_driver(void) 
{     
    static unsigned char key_state = 0;
    static unsigned int  key_time = 0;
    unsigned char key_press, key_return; 

    key_return = N_KEY;                         //  清除 返回按鍵值

    key_press = key_input;                      //  讀取當前鍵值

    switch (key_state)     
    {       
        case KEY_STATE_0:                       //  按鍵狀態0:判斷有無按鍵按下
            if (!key_press)                     //  有按鍵按下
            {
                key_time = 0;                   //  清零時間間隔計數
                key_state = KEY_STATE_1;        //  然後進入 按鍵狀態1
            }        
            break;

        case KEY_STATE_1:                       //  按鍵狀態1:軟體消抖(確定按鍵是否有效,而不是誤觸)。按鍵有效的定義:按鍵持續按下超過設定的消抖時間。
            if (!key_press)                     
            {
                key_time++;                     //  一次10ms
                if(key_time>=SINGLE_KEY_TIME)   //  消抖時間為:SINGLE_KEY_TIME*10ms = 30ms;
                {
                    key_state = KEY_STATE_2;    //  如果按鍵時間超過 消抖時間,即判定為按下的按鍵有效。按鍵有效包括兩種:單擊或者長按,進入 按鍵狀態2, 繼續判定到底是那種有效按鍵
                }
            }         
            else key_state = KEY_STATE_0;       //  如果按鍵時間沒有超過,判定為誤觸,按鍵無效,返回 按鍵狀態0,繼續等待按鍵
            break; 

        case KEY_STATE_2:                       //  按鍵狀態2:判定按鍵有效的種類:是單擊,還是長按
            if(key_press)                       //  如果按鍵在 設定的長按時間 內釋放,則判定為單擊
            { 
                 key_return = S_key;            //  返回 有效按鍵值:單擊
                 key_state = KEY_STATE_0;       //  返回 按鍵狀態0,繼續等待按鍵
            } 
            else
            {
                key_time++;                     

                if(key_time >= LONG_KEY_TIME)   //  如果按鍵時間超過 設定的長按時間(LONG_KEY_TIME*10ms=300*10ms=3000ms), 則判定為 長按
                {
                    key_return = L_KEY;         //  返回 有效鍵值值:長按
                    key_state = KEY_STATE_3;    //  去狀態3,等待按鍵釋放
                }
            }
            break;

      case KEY_STATE_3:                         //  等待按鍵釋放
          if (key_press) 
          {
              key_state = KEY_STATE_0;          //  按鍵釋放後,進入 按鍵狀態0 ,進行下一次按鍵的判定
          }        
          break; 

        default:                                //  特殊情況:key_state是其他值得情況,清零key_state。這種情況一般出現在 沒有初始化key_state,第一次執行這個函式的時候
            key_state = KEY_STATE_0;
            break;
    }

    return  key_return;                         //  返回 按鍵值
} 

// ----------------------------------- key_read --------------------------------
void key_read(void)                             
{ 
    static unsigned char key_state1=0, key_time1=0;
    unsigned char key_return,key_temp;

    key_return = N_KEY;                         //  清零 返回按鍵值

    key_temp = key_driver();                    //  讀取鍵值

    switch(key_state1) 
    {         
        case KEY_STATE_0:                       //  按鍵狀態0:等待有效按鍵(通過 key_driver 返回的有效按鍵值)
            if (key_temp == S_key )             //  如果是[單擊],不馬上返回單擊按鍵值,先進入 按鍵狀態1,判斷是否有[雙擊]的可能
            { 
                 key_time1 = 0;                 //  清零計時
                 key_state1 = KEY_STATE_1; 
            }             
            else                                //  如果不是[單擊],直接返回按鍵值。這裡的按鍵值可能是:[長按],[無效按鍵]
            {
                 key_return = key_temp;         //  返回 按鍵值
            }
            break;

        case KEY_STATE_1:                       //  按鍵狀態1:判定是否有[雙擊]
            if (key_temp == S_key)              //  有[單擊]後,如果在 設定的時間間隔(KEY_INTERVAL*10ms=30*10ms=300ms) 內,再次有[單擊],則為[雙擊],但是不馬上返回 有效按鍵值為[雙擊],先進入 按鍵狀態2,判斷是否有[三擊]
            {
                key_time1 = 0;                  //  清零 時間間隔
                key_state1 = KEY_STATE_2;       //  改變 按鍵狀態值
            } 
            else                                //  有[單擊]後,如果在 設定的時間間隔(KEY_INTERVAL*10ms=30*10ms=300ms)內,沒有[單擊]出現,則判定為 [單擊]
            {
                key_time1++;                    //  計數 時間間隔
                if(key_time1 >= KEY_INTERVAL)   //  超過 時間間隔
                 { 
                    key_return = S_key;         //  返回 有效按鍵:[單擊]
                    key_state1 = KEY_STATE_0;   //  返回 按鍵狀態0,等待新的有效按鍵
                 }              
             }              
             break; 

        case KEY_STATE_2:                       // 按鍵狀態2:判定是否有[三擊]
            if (key_temp == S_key)              // 有[雙擊]後,如果在 設定的時間間隔(KEY_INTERVAL*10ms=30*10ms=300ms) 內,再次有[單擊],則為[三擊],由於這裡只擴充套件到[三擊],所以馬上返回 有效按鍵值為[三擊]
            {
                key_time1 = 0;                  // 清零 時間間隔
                key_state1 = KEY_STATE_3;       // 改變 按鍵狀態值 
            } 
            else                                // 有[雙擊]後,如果在 設定的時間間隔(KEY_INTERVAL*10ms=30*10ms=300ms)內,沒有[單擊],則判定為 [雙擊]
            {
                key_time1++;                    // 計數 時間間隔
                if(key_time1 >= KEY_INTERVAL)   // 超過 時間間隔
                 { 
                      key_return = D_KEY;       // 返回 有效按鍵:[雙擊]
                      key_state1 = KEY_STATE_0; // 返回 按鍵狀態0,等待新的有效按鍵
                 }
             }
             break; 

        case KEY_STATE_3:                       // 按鍵狀態3:等待按鍵釋放
            if (key_temp == S_key)              // 有[三擊]後,如果在 設定的時間間隔(KEY_INTERVAL*10ms=30*10ms=300ms) 內,再次有[單擊],則為[四擊],馬上返回有效按鍵值為[四擊],
            { 
                 key_return = Q_key;            // 返回 有效按鍵:[四擊]
                 key_state1 = KEY_STATE_0;      // 返回 按鍵狀態0,等待新的有效按鍵
            } 
            else                                
            {                                   // 有[三擊]後,如果在 設定的時間間隔(KEY_INTERVAL*10ms=30*10ms=300ms)內,沒有[單擊],則判定為 [三擊]                 
                key_time1++;                    // 計數 時間間隔
                if(key_time1 >= KEY_INTERVAL)   // 超過 時間間隔
                 { 
                      key_return = T_KEY;       // 返回 有效按鍵:[三擊]
                      key_state1 = KEY_STATE_0; // 返回 按鍵狀態0,等待新的有效按鍵
                 }              
             }             
             break; 

        default:                                //  特殊情況:key_state是其他值得情況,清零key_state。這種情況一般出現在 沒有初始化key_state,第一次執行這個函式的時候
            key_state1 = KEY_STATE_0;
            break;
    }

    return = key_return;                        // 返回 按鍵值
}     

Pillar Peng
2016.3.30 - 14:04


友情連結:

第一部分:【按鍵】[獨立按鍵] - 1:單擊

第二部分:【按鍵】[獨立按鍵] - 2:雙擊