中文字幕一级黄色A级片|免费特级毛片。性欧美日本|偷拍亚洲欧美1级片|成人黄色中文小说网|A级片视频在线观看|老司机网址在线观看|免费一级无码激情黄所|欧美三级片区精品网站999|日韩av超碰日本青青草成人|一区二区亚洲AV婷婷

您當(dāng)前的位置:檢測資訊 > 科研開發(fā)

如何實(shí)現(xiàn)按鍵的短按、長按檢測?

嘉峪檢測網(wǎng)        2023-08-20 20:45

電子產(chǎn)品中經(jīng)常用到按鍵,尤其是經(jīng)常需要MCU判斷短按和長按這兩種動作,本篇我們來專門聊下這個話題。
 
只談理論太無聊,我們還是結(jié)合著實(shí)際應(yīng)用來說明。例子默認(rèn)的功能是藍(lán)牙連接后不斷的發(fā)送數(shù)據(jù),從而不斷的拍照。而實(shí)際中的遙控器通常是按一次按鍵,控制一次,我們在來實(shí)現(xiàn)該功能。
板子上只有兩個按鍵,一個是RESET按鍵,一個是DOWNLOAD按鍵,我們使用DOWNLAOD按鍵,按鍵的一端接GND,另外一端接CH573的PB22引腳。
原理圖中有一個NC的C5,但是實(shí)際板子上我卻沒有找到它,可能是版本不一致。
 
提前說明一下:CH573的代碼里跑了TMOS(Task Management Operating System),可以理解為一個簡單的操作系統(tǒng),所以下面的代碼一般的裸機(jī)代碼看著略有不同,不過核心思想都是一樣的,用在其他地方也很容易移植,只需要將其中的定時器部分改寫即可。
 
最初我是這么做的,把PB22配置為上拉輸入,開啟下降沿中斷,在中斷服務(wù)函數(shù)里,啟動一個事件,執(zhí)行藍(lán)牙發(fā)送。代碼如下:
 
void Key_Init()
{
  GPIOB_ModeCfg( GPIO_Pin_22, GPIO_ModeIN_PU );
  GPIOB_ITModeCfg( GPIO_Pin_22, GPIO_ITMode_FallEdge );
  PFIC_EnableIRQ( GPIO_B_IRQn );
}
void GPIOB_IRQHandler( void )
{
  if(GPIOB_ReadPortPin(GPIO_Pin_22)==0)
  {
      GPIOB_ClearITFlagBit( GPIO_Pin_22);
      tmos_set_event( hidEmuTaskId, START_REPORT_EVT );
  }
}
這么寫能工作,但是有問題,就是經(jīng)常會出現(xiàn)按一下誤判為多次按下。原因大家應(yīng)該都清楚,因?yàn)榘存I存在抖動,所以一次按下有可能進(jìn)入多次進(jìn)入中斷。
 
理想中的按下-彈起波形是這樣的:
 
但是實(shí)際由于按鍵抖動的存在,實(shí)際的波形可能是這樣的:
 
 
不信的話你可以接上示波器看看,或者軟件驗(yàn)證,比如在GPIO中斷服務(wù)函數(shù)里,設(shè)置一個全局變量,讓它每次進(jìn)入中斷后加1,按按鍵觀察這個變量的值。
 
那么該如何消除抖動呢?一種方法是硬件消抖,即按鍵兩端并聯(lián)一個小電容(電容大小由按鍵的機(jī)械特性來決定),另外一種方法是我們今天要重點(diǎn)介紹的軟件消抖。
 
方法一:常用的加延時函數(shù)
 
在中斷服務(wù)函數(shù)中加一個比如10ms的延時函數(shù),延時時間的長短取決于實(shí)際所用的按鍵特性,只要延時時間比抖動時間略大即可。原理很簡單,加了延時就避開了抖動的這段時間,在延時之后判斷引腳電平,如果為低電平就表示是按下。
 
void GPIOB_IRQHandler( void )
{
  if(GPIOB_ReadPortPin(GPIO_Pin_22)==0)
  {
      mDelaymS(10);
      if(GPIOB_ReadPortPin(GPIO_Pin_22)==0)
          tmos_set_event( hidEmuTaskId, START_REPORT_EVT );
      GPIOB_ClearITFlagBit( GPIO_Pin_22);
  }
}
這個方法很簡單,但是不好的地方是延時占用MCU資源。尤其是這里的BLE應(yīng)用,在中斷服務(wù)函數(shù)中執(zhí)行時間長會引起藍(lán)牙連接中斷,所以這里不能這么用,我實(shí)際測試當(dāng)按鍵按快一點(diǎn)就很容易引起藍(lán)牙連接中斷。
 
方法二:加定時器
 
它的原理和方法一類似,只不過是不在中斷服務(wù)函數(shù)中阻塞等待,而是用一個定時器,代碼如下:
 
void GPIOB_IRQHandler( void )
{
  if(GPIOB_ReadPortPin(GPIO_Pin_22)==0)
  {
      GPIOB_ClearITFlagBit( GPIO_Pin_22);
 
      tmos_stop_task(hidEmuTaskId, START_DEBOUNCE_EVT);
      tmos_start_task(hidEmuTaskId, START_DEBOUNCE_EVT,16);
  }
}
    if(events & START_DEBOUNCE_EVT)
    {
        if(GPIOB_ReadPortPin(GPIO_Pin_22)==0)
        {
            PRINT("short press\n");
            tmos_set_event( hidEmuTaskId, START_REPORT_EVT );
        }
 
        return (events ^ START_DEBOUNCE_EVT);
    }
它的邏輯是每次抖動的下降沿重新開啟10ms定時器,在定時器時間到之后判斷IO電平狀態(tài)來判斷按鍵是否按下。
 
需要注意的是:10ms定時器不是一個周期性的定時器,它是一次性的,即時間到了之后就停止計(jì)時了。另外每次進(jìn)中斷后先讓定時器重新重頭開始計(jì)時。如果大家用其他代碼實(shí)現(xiàn)時要注意這兩點(diǎn)。
 
此方法的好處不像加延時函數(shù)那樣占用MCU資源。我實(shí)際測試這個方法可用,不會引起藍(lán)牙連接中斷。
 
以上介紹了使用中斷的方式來判斷按鍵短按,可以看到它判斷的依據(jù)是按鍵按下(由高電平變到低電平)這個狀態(tài)。下面在方法二的基礎(chǔ)上我們來實(shí)現(xiàn)長按的檢測,判斷長按的依據(jù)是按下后持續(xù)的維持一段時間低電平。代碼如下:
 
if(events & START_DEBOUNCE_EVT)
{
    if(GPIOB_ReadPortPin(GPIO_Pin_22)==0)
    {
        PRINT("short press\n");
        tmos_set_event( hidEmuTaskId, START_REPORT_EVT );
        tmos_start_task( hidEmuTaskId, START_LONGCHECK_TIMER,16 );
    }
 
    return (events ^ START_DEBOUNCE_EVT);
}
    if(events & START_LONGCHECK_TIMER)
    {
        static int cnt=0;
        if(GPIOB_ReadPortPin(GPIO_Pin_22)==0)
        {
            cnt++;
            if(cnt>100)
            {
                PRINT("long press\n");
                tmos_stop_task( hidEmuTaskId, START_LONGCHECK_TIMER);
                cnt =0;
            }
            else
                tmos_start_task( hidEmuTaskId, START_LONGCHECK_TIMER,16 );
        }
        else
        {
            cnt=0;
            tmos_stop_task( hidEmuTaskId, START_LONGCHECK_TIMER );
        }
 
        return (events ^ START_LONGCHECK_TIMER);
    }
實(shí)現(xiàn)的邏輯是:當(dāng)檢測到短按時,再開啟一個10ms定時器,在定時器到時之中判斷電平狀態(tài),如果為低電平,就讓cnt變量加1,否則cnt=0,當(dāng)cnt>100,即低電平持續(xù)1s認(rèn)為是長按。我在這里當(dāng)判斷到長按之后或者IO變高之后會停止掉這個定時器,否則周期定時,因?yàn)闆]必要一直開著定時器。
 
除了上述的中斷方式,還可以使用輪詢的方式來實(shí)現(xiàn),代碼如下:
 
void Key_Init()
{
  GPIOB_ModeCfg( GPIO_Pin_22, GPIO_ModeIN_PU );
}
if(events & START_KEYSCAN_EVT)
{
    KeyScan();
    tmos_start_task(hidEmuTaskId, START_KEYSCAN_EVT,160);// 100ms執(zhí)行一次KeyScan()
    return (events ^ START_KEYSCAN_EVT);
}
bool key_press_flag = false;      // 按下標(biāo)志
bool key_long_press_flag = false; // 長按標(biāo)志
 
void KeyScan()
{
  if(GPIOB_ReadPortPin(GPIO_Pin_22) == 0) // 低電平
  {
    if(key_press_flag == false)
      tmos_start_task( hidEmuTaskId, START_LONGCHECK_TIMER, 1600 ); // 啟動1s定時器
 
    key_press_flag = true;    // 置位按下標(biāo)志
  }
  else if(key_press_flag == true) // 高電平同時按鍵被按下過 ,表示是按下后的彈起
  {
      key_press_flag = false; // 清除按下標(biāo)志
 
      if(key_long_press_flag == false)// 短按后的彈起
      {
        tmos_stop_task(hidEmuTaskId, START_LONGCHECK_TIMER);
        PRINT("short press\n");
        tmos_set_event( hidEmuTaskId, START_REPORT_EVT );
      }
      else // 長按后的彈起
      {
          key_long_press_flag =false;
      }
  }
  else
  {
    key_press_flag = false;
    key_long_press_flag = false;
  }
 
}
if(events & START_LONGCHECK_TIMER)
{
    key_long_press_flag =true;
    PRINT("long press\n");
    return (events ^ START_LONGCHECK_TIMER);
}
上面的這段代碼初次看著有點(diǎn)繞,但是看明白了之后會覺得這個實(shí)現(xiàn)邏輯還是挺好的,注釋寫了,這里不再詳細(xì)解釋了,我在多個項(xiàng)目里使用的都是它。它兼顧了去抖和短按/長按的檢測,并且長按可以判斷出長按按下/長按彈起。短按是檢測到彈起時認(rèn)為是短按動作。另外如果想同時支持多個長按,也很方便添加。
 
輪詢和中斷各有優(yōu)缺點(diǎn),大家可以根據(jù)實(shí)際情況來選擇,你一般常用哪種方式呢?
 

分享到:

來源:TopSemic嵌入式

相關(guān)新聞: