找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

帖子
查看: 11098|回復(fù): 24
打印 上一主題 下一主題
收起左側(cè)

一個單片機(jī)按鍵消抖程序的分享

  [復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:825513 發(fā)表于 2021-5-23 19:26 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
一個之前自己寫的按鍵消抖的程序,相信有和我一樣的新手也有遇到過因為按鍵按下時抖動而導(dǎo)致失靈的情況,后來自己在學(xué)校里的一位師兄的啟發(fā)下弄懂了如何通過軟件方式進(jìn)行按鍵消抖,現(xiàn)在分享一下源程序。

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

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

單片機(jī)源程序如下:
#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;                //定義一個標(biāo)志位,變量Key的值置0,表示按鍵處于松開狀態(tài)
        while(1)
        {
                if(K5==0&&Key==0)                //判斷按鍵是否被按下
                {
                        Delay10ms();                //延時消抖
                        Key=1;                //把標(biāo)志位置1,防止程序循環(huán)執(zhí)行
                        if(K5==0)                //再次判斷按鍵狀態(tài),排除干擾
                        {
                                D8=~D8;
                        }
                }
                else if(K5==1)
                {
                        Key=0;                //當(dāng)按鍵沒有被按下時,重置標(biāo)志位
                }
        }
}

評分

參與人數(shù) 1黑幣 +50 收起 理由
admin + 50 共享資料的黑幣獎勵!

查看全部評分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏10 分享淘帖 頂 踩
回復(fù)

使用道具 舉報

沙發(fā)
ID:47286 發(fā)表于 2021-5-24 01:20 來自手機(jī) | 只看該作者
我個人認(rèn)為一切需要for或 whell這種死延時的防抖都不好 20mS的時間會影響很多事 至少要定時器防抖

評分

參與人數(shù) 1黑幣 +20 收起 理由
admin + 20 回帖助人的獎勵!

查看全部評分

回復(fù)

使用道具 舉報

板凳
ID:554500 發(fā)表于 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;
                                //任務(wù)代碼       
                        }                       
                }
        }
        else  //按鍵釋放
        {
                j=0;
                k=0;
        }
}

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

使用道具 舉報

地板
ID:301715 發(fā)表于 2021-5-24 16:55 | 只看該作者
用定時器計次數(shù)啊,Delay10ms();這些如果在程序大點的會耗費(fèi)很多資源。
回復(fù)

使用道具 舉報

5#
ID:475858 發(fā)表于 2021-5-24 17:41 | 只看該作者
用定時器計數(shù)可以
回復(fù)

使用道具 舉報

6#
ID:825513 發(fā)表于 2021-5-25 16:33 | 只看該作者
18701931930 發(fā)表于 2021-5-24 10:20
這個代碼還可以,但是感覺效率不高。
一定要想辦法去掉delay函數(shù)。小工程代碼看不出效果,工程代碼大了就 ...

感謝老哥,已受教
回復(fù)

使用道具 舉報

7#
ID:825513 發(fā)表于 2021-5-25 16:35 | 只看該作者
lihui2558 發(fā)表于 2021-5-24 17:41
用定時器計數(shù)可以

我覺得沒必要給按鍵用定時器,在一些稍微大一點的項目中,那樣就太浪費(fèi)定時器資源了
回復(fù)

使用道具 舉報

8#
ID:47286 發(fā)表于 2021-5-25 21:33 來自手機(jī) | 只看該作者
NIMITIZ 發(fā)表于 2021-5-25 16:35
我覺得沒必要給按鍵用定時器,在一些稍微大一點的項目中,那樣就太浪費(fèi)定時器資源了

定時器可以用多變量計數(shù) 能耽誤什么
回復(fù)

使用道具 舉報

9#
ID:384820 發(fā)表于 2021-5-25 21:53 | 只看該作者
發(fā)一個在用的按鍵消抖程序,供參考
定時器設(shè)置每隔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鍵按下,則按下標(biāo)記置1
                                        l_debnce =1;           //消抖標(biāo)記置1
                                        key_proc(1);           //判為key1短按
                                }
                        }                        
                        //--- 掃描key2
                        if(key2 ==0)
                        {
                                if(l_keypress ==0)
                                {
                                        l_keypress =1;         //key2鍵按下,則按下標(biāo)記置1
                                        l_debnce =1;           //消抖標(biāo)記置1
                                        key_proc(2);           //判為key2短按
                                }
                        }
                        //--- 掃描key3
                        if(key3 ==0)
                        {
                                if(l_keypress ==0)
                                {
                                        l_keypress =1;         //key3鍵按下,則按下標(biāo)記置1
                                        l_debnce =1;           //消抖標(biāo)記置1
                                        key_proc(3);           //判為key3短按
                                }
                        }
              }
    }
    //--- 無鍵按下,則清除標(biāo)記
    else  
                        l_keypress =0;
  }
  //--- 需要20ms的消抖
  else  
    l_debnce =0;
}

若加入 n_keypress 計數(shù)器,還可以修改為判斷長按短按鍵的情況
有興趣可以私信留言交流
回復(fù)

使用道具 舉報

10#
ID:702127 發(fā)表于 2021-5-26 14:42 | 只看該作者
直接狀態(tài)機(jī)吧,定時器消抖,效率高還好用。
回復(fù)

使用道具 舉報

11#
ID:548551 發(fā)表于 2021-5-26 18:09 | 只看該作者
NIMITIZ 發(fā)表于 2021-5-25 16:35
我覺得沒必要給按鍵用定時器,在一些稍微大一點的項目中,那樣就太浪費(fèi)定時器資源了

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

使用道具 舉報

12#
ID:825513 發(fā)表于 2021-5-28 14:38 | 只看該作者
順德動力 發(fā)表于 2021-5-25 21:53
發(fā)一個在用的按鍵消抖程序,供參考
定時器設(shè)置每隔20ms掃描一次按鍵

你好,我還不太懂 變量l_debnce 和函數(shù) key_proc() ,可以講解一下嗎,謝謝
回復(fù)

使用道具 舉報

13#
ID:47286 發(fā)表于 2021-5-28 21:14 | 只看該作者
NIMITIZ 發(fā)表于 2021-5-28 14:38
你好,我還不太懂 變量l_debnce 和函數(shù) key_proc() ,可以講解一下嗎,謝謝

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

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

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

其實不用先拉高端口吧 每個端口接個10k的弱上拉或者開單片機(jī)內(nèi)部上拉電阻 只要沒按下就一定是高 直接判斷就行了 這樣少一層if嵌套 另外 如果KEY1和KEY2同時按下 怎么防抖 雖然理論上20ms的防抖很短 不會沖突 不過感覺用起來還是不太保險吧 多個單獨(dú)按鍵檢測 我是每個按鍵一套標(biāo)記 獨(dú)立的 防止干擾
回復(fù)

使用道具 舉報

14#
ID:141497 發(fā)表于 2021-5-28 22:16 | 只看該作者
樓上正解,用定時器,效率比較高。
回復(fù)

使用道具 舉報

15#
ID:970121 發(fā)表于 2021-10-26 17:58 | 只看該作者
dzbj 發(fā)表于 2021-5-28 21:14
他程序給的不太全 我也看不懂 而且 感覺有點麻煩

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

每個字我都能看懂,也明白意思,但邏輯關(guān)系還是搞不定,能出一個示例嗎?
回復(fù)

使用道具 舉報

16#
ID:384475 發(fā)表于 2021-10-26 21:13 | 只看該作者
下面這個是金沙灘宋老師的教程,很好:
那么消抖操作所需要的延時該怎么處理呢?其實除了這種簡單的延時,我們還有更優(yōu)異
的方法來處理按鍵抖動問題。舉個例子:我們啟用一個定時中斷,每 2ms 進(jìn)一次中斷,掃描
一次按鍵狀態(tài)并且存儲起來,連續(xù)掃描 8 次后,看看這連續(xù) 8 次的按鍵狀態(tài)是否是一致的。
8 次按鍵的時間大概是 16ms,這 16ms 內(nèi)如果按鍵狀態(tài)一直保持一致,那就可以確定現(xiàn)在按
鍵處于穩(wěn)定的階段,而非處于抖動的階段,如圖 8-12。 圖 8-12 按鍵連續(xù)掃描判斷
假如左邊時間是起始 0 時刻,每經(jīng)過 2ms 左移一次,每移動一次,判斷當(dāng)前連續(xù)的 8 次
按鍵狀態(tài)是不是全 1 或者全 0,如果是全 1 則判定為彈起,如果是全 0 則判定為按下,如果
0 和 1 交錯,就認(rèn)為是抖動,不做任何判定。想一下,這樣是不是比簡單的延時更加可靠?
利用這種方法,就可以避免通過延時消抖占用單片機(jī)執(zhí)行時間,而是轉(zhuǎn)化成了一種按鍵
狀態(tài)判定而非按鍵過程判定,我們只對當(dāng)前按鍵的連續(xù) 16ms 的 8 次狀態(tài)進(jìn)行判斷,而不再
關(guān)心它在這 16ms 內(nèi)都做了什么事情,那么下面就按照這種思路用程序?qū)崿F(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ù)碼管顯示字符轉(zhuǎn)換表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
bit KeySta = 1; //當(dāng)前按鍵狀態(tài)
void main()
{
bit backup = 1; //按鍵值備份,保存前一次的掃描值
unsigned char cnt = 0; //按鍵計數(shù),記錄按鍵按下的次數(shù)

EA = 1; //使能總中斷
ENLED = 0; //選擇數(shù)碼管 DS1 進(jìn)行顯示
ADDR3 = 1;
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
TMOD = 0x01; //設(shè)置 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) //當(dāng)前值與前次值不相等說明此時按鍵有動作
{
if (backup == 0) //如果前次值為 0,則說明當(dāng)前是彈起動作
{
cnt++; //按鍵次數(shù)+1
if (cnt >= 10)
{ //只用 1 個數(shù)碼管顯示,所以加到 10 就清零重新開始
cnt = 0;
}
P0 = LedChar[cnt]; //計數(shù)值顯示到數(shù)碼管上
}
backup = KeySta; //更新備份為當(dāng)前值,以備進(jìn)行下次比較
}
} }
/* T0 中斷服務(wù)函數(shù),用于按鍵狀態(tài)的掃描并消抖 */
void InterruptTimer0() interrupt 1
{
static unsigned char keybuf = 0xFF; //掃描緩沖區(qū),保存一段時間內(nèi)的掃描值

TH0 = 0xF8; //重新加載初值
TL0 = 0xCD;
keybuf = (keybuf<<1) | KEY4; //緩沖區(qū)左移一位,并將當(dāng)前掃描值移入最低位
if (keybuf == 0x00)
{ //連續(xù) 8 次掃描值都為 0,即 16ms 內(nèi)都只檢測到按下狀態(tài)時,可認(rèn)為按鍵已按下
KeySta = 0;
}
else if (keybuf == 0xFF)
{ //連續(xù) 8 次掃描值都為 1,即 16ms 內(nèi)都只檢測到彈起狀態(tài)時,可認(rèn)為按鍵已彈起
KeySta = 1;
}
else
{} //其它情況則說明按鍵狀態(tài)尚未穩(wěn)定,則不對 KeySta 變量值進(jìn)行更新
}
這個算法是我們在實際工程中經(jīng)常使用按鍵所總結(jié)的一個比較好的方法,介紹給大家
回復(fù)

使用道具 舉報

17#
ID:298123 發(fā)表于 2021-10-27 10:13 | 只看該作者
jjkk11 發(fā)表于 2021-10-26 21:13
下面這個是金沙灘宋老師的教程,很好:
那么消抖操作所需要的延時該怎么處理呢?其實除了這種簡單的延時,我 ...

教程只是用來學(xué)習(xí)的,沒有別的方法來教你,只能用這種方式。實際項目絕對不是你認(rèn)知里這樣方式來實現(xiàn)的,等你向上跳過這個階段,你就明白我說的意思了。千萬不要用的自己的認(rèn)知來誤導(dǎo)別人。
回復(fù)

使用道具 舉報

18#
ID:954686 發(fā)表于 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(); //讀取當(dāng)前的按鍵值,
            if(CurrentkeyVal != KEYCODE_NOPRESS) //如果不等于KEYCODE_NOPRESS,說明此時有按鍵按下
            {
                KeyScanState = KEY_STATE_FirstPress; //把掃描狀態(tài)置為第一次按下狀態(tài)
                KeyVal = CurrentkeyVal;  //并且記錄當(dāng)前的按鍵值
            }
            else
                KeyVal = KEYCODE_NOPRESS;
            break;
        case KEY_STATE_FirstPress://第一次按下狀態(tài)
            if(KeyVal == KeyGetKeyCode()) //如果KeyGetKeyCode讀出的值和KeyVal一致的話(即和空閑狀態(tài)下的記錄的按鍵值一致的話)
            {
                KeyScanState = KEY_STATE_RepeatPress; //把按鍵狀態(tài)置為 重復(fù)按下狀態(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://在重復(fù)按下狀態(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;

    }
}


回復(fù)

使用道具 舉報

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

親測好用。
回復(fù)

使用道具 舉報

20#
ID:1006076 發(fā)表于 2022-3-20 18:24 | 只看該作者
試了一下,很好用
回復(fù)

使用道具 舉報

21#
ID:1006181 發(fā)表于 2022-3-22 16:45 來自手機(jī) | 只看該作者
挺好,很多可以學(xué)習(xí)參考的
回復(fù)

使用道具 舉報

22#
ID:148126 發(fā)表于 2022-3-23 16:43 | 只看該作者
我認(rèn)為你這個做不到消抖的目的,要不你認(rèn)真看一下程序
回復(fù)

使用道具 舉報

23#
ID:1011952 發(fā)表于 2022-3-23 16:52 | 只看該作者
為什么不做硬消斗?有什么原因嗎??
回復(fù)

使用道具 舉報

24#
ID:415064 發(fā)表于 2022-3-23 16:56 | 只看該作者
乙豬 發(fā)表于 2022-3-23 16:52
為什么不做硬消斗?有什么原因嗎??

機(jī)械輕觸按鍵,硬消抖,錢多?
回復(fù)

使用道具 舉報

25#
ID:499136 發(fā)表于 2022-3-23 17:39 | 只看該作者
任何形式的while或for循環(huán)的延時在實際項目中都是不被允許的,一個延時會錯過好多事情的。
回復(fù)

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表