標題: 一個單片機按鍵消抖程序的分享 [打印本頁]

作者: NIMITIZ    時間: 2021-5-23 19:26
標題: 一個單片機按鍵消抖程序的分享
一個之前自己寫的按鍵消抖的程序,相信有和我一樣的新手也有遇到過因為按鍵按下時抖動而導致失靈的情況,后來自己在學校里的一位師兄的啟發(fā)下弄懂了如何通過軟件方式進行按鍵消抖,現(xiàn)在分享一下源程序。

開發(fā)板上的按鍵由于機械觸點的彈性作用,當按下按鍵時機械觸點并不會立刻穩(wěn)定閉合,松開按鍵時機械觸點也不會立刻穩(wěn)定斷開,因此在按下或松開按鍵的瞬間(在大約10ms內)電位會有一定的波動(或者叫做抖動);軟件消抖就是當檢測到按鍵狀態(tài)發(fā)生變化后,先延時大約10ms待電位穩(wěn)定后,再進行一次檢測,如果仍然保持閉合狀態(tài)的電位,則確認為按鍵真正被按下。

按鍵消抖程序如下:
# K5表示按鍵,D8表示LED燈,按下獨立按鍵K5,LEDD8亮;再次按下K5,D8滅。

單片機源程序如下:
#include<reg51.h>

sbit K5=P3^5;
sbit D8=P2^7;

void Delay10ms()                //延時10ms
{
        unsigned char i,j;
        i=20;
        j=113;
        do
        {
                while(--j);
        }
        while(--i);
}

void main()
{
        int Key=0;                //定義一個標志位,變量Key的值置0,表示按鍵處于松開狀態(tài)
        while(1)
        {
                if(K5==0&&Key==0)                //判斷按鍵是否被按下
                {
                        Delay10ms();                //延時消抖
                        Key=1;                //把標志位置1,防止程序循環(huán)執(zhí)行
                        if(K5==0)                //再次判斷按鍵狀態(tài),排除干擾
                        {
                                D8=~D8;
                        }
                }
                else if(K5==1)
                {
                        Key=0;                //當按鍵沒有被按下時,重置標志位
                }
        }
}


作者: dzbj    時間: 2021-5-24 01:20
我個人認為一切需要for或 whell這種死延時的防抖都不好 20mS的時間會影響很多事 至少要定時器防抖
作者: 18701931930    時間: 2021-5-24 10:20
這個代碼還可以,但是感覺效率不高。
一定要想辦法去掉delay函數(shù)。小工程代碼看不出效果,工程代碼大了就體現(xiàn)出來了。
#include "reg52.h"

sbit key=P1^2;
//按鍵掃描
void Key_Scan()
{
        static u8 j=0,k=0;
       
        if(key==0)
        {
                if(j==0)
                {
                        if(key==0&&k++>=20)
                        {
                                j=1;
                                //任務代碼       
                        }                       
                }
        }
        else  //按鍵釋放
        {
                j=0;
                k=0;
        }
}

//k的取值和函數(shù)調度周期有關系,適當調節(jié),穩(wěn)定性還是不錯的。
//k取值越大,靈敏度越低,反之越高

作者: vtif    時間: 2021-5-24 16:55
用定時器計次數(shù)啊,Delay10ms();這些如果在程序大點的會耗費很多資源。
作者: lihui2558    時間: 2021-5-24 17:41
用定時器計數(shù)可以
作者: NIMITIZ    時間: 2021-5-25 16:33
18701931930 發(fā)表于 2021-5-24 10:20
這個代碼還可以,但是感覺效率不高。
一定要想辦法去掉delay函數(shù)。小工程代碼看不出效果,工程代碼大了就 ...

感謝老哥,已受教
作者: NIMITIZ    時間: 2021-5-25 16:35
lihui2558 發(fā)表于 2021-5-24 17:41
用定時器計數(shù)可以

我覺得沒必要給按鍵用定時器,在一些稍微大一點的項目中,那樣就太浪費定時器資源了
作者: dzbj    時間: 2021-5-25 21:33
NIMITIZ 發(fā)表于 2021-5-25 16:35
我覺得沒必要給按鍵用定時器,在一些稍微大一點的項目中,那樣就太浪費定時器資源了

定時器可以用多變量計數(shù) 能耽誤什么
作者: 順德動力    時間: 2021-5-25 21:53
發(fā)一個在用的按鍵消抖程序,供參考
定時器設置每隔20ms掃描一次按鍵

/***** 按鍵掃描 *****/
void keyscan()
{
  if(l_debnce ==0)               //是否需要消抖?
  {              
    P1 |=0x0F;                   //先拉高端口
                if((P1 &0x0F)!=0x0F)         //不需消抖,則判斷有鍵按下否
                {
                        //--- 掃描key1
                        if(key1 ==0)
                        {
                                if(l_keypress ==0)
                                {
                                        l_keypress =1;         //key1鍵按下,則按下標記置1
                                        l_debnce =1;           //消抖標記置1
                                        key_proc(1);           //判為key1短按
                                }
                        }                        
                        //--- 掃描key2
                        if(key2 ==0)
                        {
                                if(l_keypress ==0)
                                {
                                        l_keypress =1;         //key2鍵按下,則按下標記置1
                                        l_debnce =1;           //消抖標記置1
                                        key_proc(2);           //判為key2短按
                                }
                        }
                        //--- 掃描key3
                        if(key3 ==0)
                        {
                                if(l_keypress ==0)
                                {
                                        l_keypress =1;         //key3鍵按下,則按下標記置1
                                        l_debnce =1;           //消抖標記置1
                                        key_proc(3);           //判為key3短按
                                }
                        }
              }
    }
    //--- 無鍵按下,則清除標記
    else  
                        l_keypress =0;
  }
  //--- 需要20ms的消抖
  else  
    l_debnce =0;
}

若加入 n_keypress 計數(shù)器,還可以修改為判斷長按短按鍵的情況
有興趣可以私信留言交流
作者: ii11nnocent    時間: 2021-5-26 14:42
直接狀態(tài)機吧,定時器消抖,效率高還好用。
作者: xqleft    時間: 2021-5-26 18:09
NIMITIZ 發(fā)表于 2021-5-25 16:35
我覺得沒必要給按鍵用定時器,在一些稍微大一點的項目中,那樣就太浪費定時器資源了

什么是浪費資源? 沒有懂。你這個delay 就是在浪費資源。定時器有毒是不是,為什么不用定時器來定時呢。你如果只是亮燈,只是檢測按鍵,適當?shù)难訒r沒有太大的問題。 但是你是檢測按鍵,又是LED燈,雙是輸出PWM,叒是檢測電池電壓,即ADC檢測,叕是定時自動關機。 你說怎么弄? 用定時器設置一個基準定時,比如5MS,所有的操作都在這5MS的基準之上,比如按鍵檢測,消抖需要40MS,給個變量加8次,一樣的到了40MS, LED燈一樣的效果,實現(xiàn)1S的閃爍,變量到達100的時候打開達到200的時候關閉。一樣的效果,同樣定時自動關機,如果自動定時一分鐘,一個unsigned int 變量 到達12000的時候就表示時間到了需要關機。 這些都是可以同時同步執(zhí)行的,如果delay你想想,一次delay假如說延時40ms 那到一分鐘的時候不知道延時到幾分鐘去了。所以定時器,不要浪費,它是你提高效率的重要組成部分。
作者: NIMITIZ    時間: 2021-5-28 14:38
順德動力 發(fā)表于 2021-5-25 21:53
發(fā)一個在用的按鍵消抖程序,供參考
定時器設置每隔20ms掃描一次按鍵

你好,我還不太懂 變量l_debnce 和函數(shù) key_proc() ,可以講解一下嗎,謝謝
作者: dzbj    時間: 2021-5-28 21:14
NIMITIZ 發(fā)表于 2021-5-28 14:38
你好,我還不太懂 變量l_debnce 和函數(shù) key_proc() ,可以講解一下嗎,謝謝

他程序給的不太全 我也看不懂 而且 感覺有點麻煩

大概是它吧按鍵接在P1的0123口上 檢測前先拉高端口 如果按下 那么端口拉不高 然后 應該定義了sbit key1=P1^0;之類的

I_keypress和i_debnce應該是bit 不是char 標記 不是變量 做兩個標記 靠標記變化來判斷和處理 不過沒看見變量累加延時或者計數(shù)器累加延時 不知道怎么實現(xiàn)20ms防抖的 也沒有判斷長短按的變量

其實不用先拉高端口吧 每個端口接個10k的弱上拉或者開單片機內部上拉電阻 只要沒按下就一定是高 直接判斷就行了 這樣少一層if嵌套 另外 如果KEY1和KEY2同時按下 怎么防抖 雖然理論上20ms的防抖很短 不會沖突 不過感覺用起來還是不太保險吧 多個單獨按鍵檢測 我是每個按鍵一套標記 獨立的 防止干擾
作者: 51jia    時間: 2021-5-28 22:16
樓上正解,用定時器,效率比較高。
作者: 絲瓜偵探    時間: 2021-10-26 17:58
dzbj 發(fā)表于 2021-5-28 21:14
他程序給的不太全 我也看不懂 而且 感覺有點麻煩

大概是它吧按鍵接在P1的0123口上 檢測前先拉高端口  ...

每個字我都能看懂,也明白意思,但邏輯關系還是搞不定,能出一個示例嗎?
作者: jjkk11    時間: 2021-10-26 21:13
下面這個是金沙灘宋老師的教程,很好:
那么消抖操作所需要的延時該怎么處理呢?其實除了這種簡單的延時,我們還有更優(yōu)異
的方法來處理按鍵抖動問題。舉個例子:我們啟用一個定時中斷,每 2ms 進一次中斷,掃描
一次按鍵狀態(tài)并且存儲起來,連續(xù)掃描 8 次后,看看這連續(xù) 8 次的按鍵狀態(tài)是否是一致的。
8 次按鍵的時間大概是 16ms,這 16ms 內如果按鍵狀態(tài)一直保持一致,那就可以確定現(xiàn)在按
鍵處于穩(wěn)定的階段,而非處于抖動的階段,如圖 8-12。 圖 8-12 按鍵連續(xù)掃描判斷
假如左邊時間是起始 0 時刻,每經過 2ms 左移一次,每移動一次,判斷當前連續(xù)的 8 次
按鍵狀態(tài)是不是全 1 或者全 0,如果是全 1 則判定為彈起,如果是全 0 則判定為按下,如果
0 和 1 交錯,就認為是抖動,不做任何判定。想一下,這樣是不是比簡單的延時更加可靠?
利用這種方法,就可以避免通過延時消抖占用單片機執(zhí)行時間,而是轉化成了一種按鍵
狀態(tài)判定而非按鍵過程判定,我們只對當前按鍵的連續(xù) 16ms 的 8 次狀態(tài)進行判斷,而不再
關心它在這 16ms 內都做了什么事情,那么下面就按照這種思路用程序實現(xiàn)出來,同樣只以
K4 為例。
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4 = P2^7;
unsigned char code LedChar[] = { //數(shù)碼管顯示字符轉換表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
bit KeySta = 1; //當前按鍵狀態(tài)
void main()
{
bit backup = 1; //按鍵值備份,保存前一次的掃描值
unsigned char cnt = 0; //按鍵計數(shù),記錄按鍵按下的次數(shù)

EA = 1; //使能總中斷
ENLED = 0; //選擇數(shù)碼管 DS1 進行顯示
ADDR3 = 1;
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
TMOD = 0x01; //設置 T0 為模式 1
TH0 = 0xF8; //為 T0 賦初值 0xF8CD,定時 2ms
TL0 = 0xCD;
ET0 = 1; //使能 T0 中斷
TR0 = 1; //啟動 T0
P2 = 0xF7; //P2.3 置 0,即 KeyOut1 輸出低電平
P0 = LedChar[cnt]; //顯示按鍵次數(shù)初值

while (1)
{
if (KeySta != backup) //當前值與前次值不相等說明此時按鍵有動作
{
if (backup == 0) //如果前次值為 0,則說明當前是彈起動作
{
cnt++; //按鍵次數(shù)+1
if (cnt >= 10)
{ //只用 1 個數(shù)碼管顯示,所以加到 10 就清零重新開始
cnt = 0;
}
P0 = LedChar[cnt]; //計數(shù)值顯示到數(shù)碼管上
}
backup = KeySta; //更新備份為當前值,以備進行下次比較
}
} }
/* T0 中斷服務函數(shù),用于按鍵狀態(tài)的掃描并消抖 */
void InterruptTimer0() interrupt 1
{
static unsigned char keybuf = 0xFF; //掃描緩沖區(qū),保存一段時間內的掃描值

TH0 = 0xF8; //重新加載初值
TL0 = 0xCD;
keybuf = (keybuf<<1) | KEY4; //緩沖區(qū)左移一位,并將當前掃描值移入最低位
if (keybuf == 0x00)
{ //連續(xù) 8 次掃描值都為 0,即 16ms 內都只檢測到按下狀態(tài)時,可認為按鍵已按下
KeySta = 0;
}
else if (keybuf == 0xFF)
{ //連續(xù) 8 次掃描值都為 1,即 16ms 內都只檢測到彈起狀態(tài)時,可認為按鍵已彈起
KeySta = 1;
}
else
{} //其它情況則說明按鍵狀態(tài)尚未穩(wěn)定,則不對 KeySta 變量值進行更新
}
這個算法是我們在實際工程中經常使用按鍵所總結的一個比較好的方法,介紹給大家
作者: hz_dyg    時間: 2021-10-27 10:13
jjkk11 發(fā)表于 2021-10-26 21:13
下面這個是金沙灘宋老師的教程,很好:
那么消抖操作所需要的延時該怎么處理呢?其實除了這種簡單的延時,我 ...

教程只是用來學習的,沒有別的方法來教你,只能用這種方式。實際項目絕對不是你認知里這樣方式來實現(xiàn)的,等你向上跳過這個階段,你就明白我說的意思了。千萬不要用的自己的認知來誤導別人。
作者: 29417765    時間: 2021-10-27 16:29
我常用的,可作為參考。
uchar KeyGetKeyCode(void)
{
    if(IDR_ADJ== 0)
    {
        return  KEY_ADJ;
    }
    if(IDR_SET== 0)
    {
        return  KEY_SET;
    }
    return  KEY_NONE;

void KeyScan(void)
{
    uchar CurrentkeyVal;
    static uint Cnt;
    if(PLScanStatus == NotAllowScan)return; //5ms 掃描一次,時間不到則返回
    PLScanStatus = NotAllowScan;
    switch(KeyScanState)
    {
        case KEY_STATE_Idle: //在空閑狀態(tài)下
            CurrentkeyVal = KeyGetKeyCode(); //讀取當前的按鍵值,
            if(CurrentkeyVal != KEYCODE_NOPRESS) //如果不等于KEYCODE_NOPRESS,說明此時有按鍵按下
            {
                KeyScanState = KEY_STATE_FirstPress; //把掃描狀態(tài)置為第一次按下狀態(tài)
                KeyVal = CurrentkeyVal;  //并且記錄當前的按鍵值
            }
            else
                KeyVal = KEYCODE_NOPRESS;
            break;
        case KEY_STATE_FirstPress://第一次按下狀態(tài)
            if(KeyVal == KeyGetKeyCode()) //如果KeyGetKeyCode讀出的值和KeyVal一致的話(即和空閑狀態(tài)下的記錄的按鍵值一致的話)
            {
                KeyScanState = KEY_STATE_RepeatPress; //把按鍵狀態(tài)置為 重復按下狀態(tài),
                //  dPuts("\r\n key repeat");

                Cnt = 0;
            }
            else  //不一致的話(和上次讀的按鍵不一致),
            {
                KeyScanState = KEY_STATE_Idle; //把按鍵狀態(tài)置為空閑狀態(tài),要重新再讀
                KeyVal = KEYCODE_NOPRESS;

            }
            break;
        case KEY_STATE_RepeatPress://在重復按下狀態(tài)下,
            if(KeyVal == KeyGetKeyCode())
            {
                if(++Cnt == 60)
                {
                    Cnt = 0;
                    KeyScanState = KEY_STATE_LongPress;
                    // dPuts("\r\n key long");
                }
            }
            else
            {
                KeyScanState = KEY_STATE_Idle;
                GetKey = KeyVal;                              //得到鍵值
                //   dPuts("\r\nget key");
                //  if(GetKey == KEY_MENU)  //
                //     GetKey = KEY_NONE;
            }
            break;
        case KEY_STATE_LongPress://長按狀態(tài)
            if(KeyVal == KeyGetKeyCode())
            {
                if(++Cnt == 15)
                {
                    Cnt = 0;
                    GetKey = KeyVal + 0x10;
                    //   dPuts("\r\nget long key");
                    //  if(GetKey == KEY_SET_LONG)
                    KeyScanState = KEY_STATE_NoneStatus;
                }
            }
            else
            {
                Cnt = 0;
                KeyScanState = KEY_STATE_Idle;
                KeyVal = KEYCODE_NOPRESS;
            }
            break;

        case KEY_STATE_NoneStatus:
            if( KeyGetKeyCode() == KEY_NONE)
            {
                KeyScanState = KEY_STATE_Idle; //把按鍵狀態(tài)置為空閑狀態(tài),要重新再讀
                KeyVal = KEYCODE_NOPRESS;
            }
            break;

    }
}



作者: 3花木城    時間: 2022-2-13 17:41
18701931930 發(fā)表于 2021-5-24 10:20
這個代碼還可以,但是感覺效率不高。
一定要想辦法去掉delay函數(shù)。小工程代碼看不出效果,工程代碼大了就 ...

親測好用。
作者: chen_天天來    時間: 2022-3-20 18:24
試了一下,很好用
作者: xyymyy    時間: 2022-3-22 16:45
挺好,很多可以學習參考的
作者: 霍特    時間: 2022-3-23 16:43
我認為你這個做不到消抖的目的,要不你認真看一下程序
作者: 乙豬    時間: 2022-3-23 16:52
為什么不做硬消斗?有什么原因嗎??
作者: wojiaoguogai    時間: 2022-3-23 16:56
乙豬 發(fā)表于 2022-3-23 16:52
為什么不做硬消斗?有什么原因嗎??

機械輕觸按鍵,硬消抖,錢多?
作者: 韓晗    時間: 2022-3-23 17:39
任何形式的while或for循環(huán)的延時在實際項目中都是不被允許的,一個延時會錯過好多事情的。




歡迎光臨 (http://www.torrancerestoration.com/bbs/) Powered by Discuz! X3.1