0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

基于狀態(tài)機(jī)的按鍵驅(qū)動(dòng)設(shè)計(jì)

CHANBAEK ? 來(lái)源:嵌入式小書蟲 ? 作者:FledgingSu 支離蘇 ? 2023-07-04 11:43 ? 次閱讀

【說(shuō)在前面的話】

按鍵作為單片機(jī)的輸入設(shè)備,可以向單片機(jī)輸入數(shù)據(jù)、傳輸命令等,是設(shè)置參數(shù)和控制設(shè)備的常用接口。所以,學(xué)會(huì)按鍵驅(qū)動(dòng)也是初學(xué)者必不可少的能力。說(shuō)到按鍵驅(qū)動(dòng)程序,大家應(yīng)該也不陌生,而一般的按鍵驅(qū)動(dòng)流程圖如下

圖片

這里,可能有人會(huì)問(wèn),為什么要延時(shí)10ms???

那是因?yàn)榘存I被按下時(shí),不會(huì)像理想的情況非0即1,而是會(huì)有抖動(dòng),如下圖

圖片

當(dāng)機(jī)械按鍵被按下或松開(kāi)時(shí),會(huì)有10ms的抖動(dòng)時(shí)間,所以要延時(shí)10ms來(lái)消去波形抖動(dòng)(* ̄︶ ̄)

知道了這個(gè),一般初學(xué)者編寫的按鍵驅(qū)動(dòng)程序如下:

//延時(shí)1ms
void Delay1ms() {   //@12.000MHz
  unsigned char i, j;
  i = 12;
  j = 169;
  do{  
    while (--j);
  } while (--i);
}
//ms延時(shí) 
void delay_ms(int ms){
  char i = 0;
  for(i = 0; i < ms; i++){
    Delay1ms();  
  }
}
char get_key(){
  //檢測(cè)按鍵是否被按下
  if(KEY1 == 0){
    //延時(shí)10ms
    delay_ms(10);
    //再次檢測(cè)按鍵是否被按下
    if(KEY1 == 0){
      //等待按鍵松開(kāi)
      while(KEY1 == 0){ }
      delay_ms(10);
      return 1;  
    }
  }
  return 0;
}

像這種按鍵驅(qū)動(dòng)程序也很簡(jiǎn)單,作為基礎(chǔ)學(xué)習(xí)和一些簡(jiǎn)單的系統(tǒng)中還可以,但是在很多的產(chǎn)品設(shè)計(jì)中,這種按鍵程序還是有很大的不足和缺陷。因?yàn)樗粌H采用了軟件延時(shí)使單片機(jī)效率降低而且還在那里死等按鍵松開(kāi),系統(tǒng)的實(shí)時(shí)性也變得很差。為此,有人提出了 一種基于狀態(tài)機(jī)的按鍵驅(qū)動(dòng)程序 ,很好地解決了上述程序的缺陷。下面我們就簡(jiǎn)單講一下什么是狀態(tài)機(jī)。

【狀態(tài)機(jī)簡(jiǎn)介】

對(duì)于學(xué)電子的同學(xué),首先接觸到的狀態(tài)機(jī)應(yīng)該是在數(shù)字邏輯電路( 簡(jiǎn)稱數(shù)電 )中,狀態(tài)機(jī)的分析方法被應(yīng)用于時(shí)序邏輯電路的設(shè)計(jì)中,其實(shí)狀態(tài)機(jī)的思想對(duì)我們的軟件設(shè)計(jì)也很有用,首先簡(jiǎn)單介紹一下?tīng)顟B(tài)機(jī),它是由有限的狀態(tài)相互之間的遷移構(gòu)成的。在任何時(shí)候,只能處于狀態(tài)機(jī)的某一個(gè)狀態(tài),當(dāng)接收到一個(gè)轉(zhuǎn)移事件時(shí),狀態(tài)機(jī)進(jìn)行狀態(tài)的轉(zhuǎn)移。

下面,就以按鍵驅(qū)動(dòng)為例,畫出他的狀態(tài)轉(zhuǎn)移圖,如下

圖片

有了狀態(tài)轉(zhuǎn)移圖,那我們就用程序?qū)崿F(xiàn)一下這個(gè)按鍵驅(qū)動(dòng)程序。從圖中我們知道按鍵驅(qū)動(dòng)程序由3個(gè)狀態(tài),剛好可以用C語(yǔ)言的switch case語(yǔ)句來(lái)實(shí)現(xiàn)這3個(gè)狀態(tài),而狀態(tài)間的遷移就可以用if條件判斷語(yǔ)句來(lái)實(shí)現(xiàn)。知道了這個(gè),那我們就動(dòng)手實(shí)現(xiàn)一下。

基于狀態(tài)機(jī)的按鍵驅(qū)動(dòng)程序

首先,打開(kāi)原理圖,看一下按鍵接到了單片機(jī)的哪個(gè)管腳,如下

圖片

我們以按鍵1為例,接到了單片機(jī)的P33腳,當(dāng)按鍵 按下時(shí)為低電平 , 松開(kāi)為高電平 ,基于此我們的按鍵程序如下:

sbit KEY1 = P3^3;//key1
char get_key(){
  //保存按鍵狀態(tài)
  static char key_flag = 0;
  //軟件延時(shí)計(jì)時(shí)器
  static unsigned int s_Counter = 0;
  switch(key_flag){
    //狀態(tài)0為無(wú)按鍵按下
    case 0:
      if(KEY1 == 0){
        //如果有按鍵按下,轉(zhuǎn)為狀態(tài)1
        key_flag = 1;
      }
      break;
     //狀態(tài)1為延時(shí)消抖  
    case 1:
      s_Counter++;
      if(s_Counter > 1000){
        //延時(shí)10ms,計(jì)時(shí)器清零
        s_Counter = 0;
        if(KEY1 == 0){
          //如果按鍵被按下,轉(zhuǎn)為狀態(tài)2
          key_flag = 2;
          return 1;
        }else{
          //如果按鍵未按下,轉(zhuǎn)為狀態(tài)0
          key_flag = 0;
        }
      }
      break;
     //狀態(tài)2為等待按鍵釋放  
    case 2:
      if(KEY1 == 1){
        //如果按鍵松開(kāi),轉(zhuǎn)為狀態(tài)0
        key_flag = 0;
      }
      break;  
  }
  return 0;
}
  • 注意,每個(gè)case結(jié)束后都有一個(gè)break
  • 第18行,s_Counter > 1000相當(dāng)于延時(shí)10ms,當(dāng)然這個(gè)1000是隨便給的值,大家要根據(jù)具體情況設(shè)置此值,如果測(cè)試小于10ms就可以加大此值,我們只是為了說(shuō)明用s_Counter 可以延時(shí)。
  • 第24行,在延時(shí)去抖完成后就返回了1(相當(dāng)于按鍵按下),這樣做的好處就是可以提高按鍵響應(yīng)速度。當(dāng)然也可以在狀態(tài)2按鍵松開(kāi)后返回1。

基于狀態(tài)機(jī)的按鍵驅(qū)動(dòng)程序我們就簡(jiǎn)單寫完了,相信大家也get到重點(diǎn)了,這個(gè)只是簡(jiǎn)單實(shí)現(xiàn)了按鍵的單擊,當(dāng)然,我們也可以實(shí)現(xiàn)按鍵的雙擊和長(zhǎng)按。

哈哈,在編寫驅(qū)動(dòng)之前,我們先細(xì)化一下需求,首先區(qū)分單擊和長(zhǎng)按,這個(gè)很簡(jiǎn)單,規(guī)定一個(gè)時(shí)間就可以,我們定為1秒鐘。即按下時(shí)間小于1秒為單擊,大于1秒為長(zhǎng)按。

那雙擊怎么辦呢?

我們規(guī)定,當(dāng)?shù)谝淮伟聪鲁掷m(xù)時(shí)間小于500ms內(nèi)松開(kāi)按鍵,在之后500ms內(nèi)又按下按鍵,此時(shí)為雙擊事件。這里有兩點(diǎn)需要注意,1、第一次按下的時(shí)間不能超過(guò)500ms,否則就被判斷為單擊或長(zhǎng)按。2、在第一次按下松開(kāi)后開(kāi)始計(jì)時(shí),如果500ms內(nèi)沒(méi)有按鍵再次按下則為單擊。按鍵雙擊的原理如下圖所示

圖片

按鍵雙擊和長(zhǎng)按的需求我們講完了,接下來(lái)畫出他的狀態(tài)轉(zhuǎn)移圖,如下

圖片

哈哈哈,還可以吧,沒(méi)那么復(fù)雜。相信大家應(yīng)該能看懂。這里有必要說(shuō)一下?tīng)顟B(tài)5,判斷雙擊其實(shí)就是第二次按下延時(shí)10ms消抖,如果確實(shí)按下則為雙擊否則為單擊。好了,看看程序怎么實(shí)現(xiàn)吧,如下

#define DELAY_10ms   500
#define DELAY_500ms   10000
char get_key3(){
  static char key_flag = 0;
  static unsigned int s_Counter = 0;
  switch(key_flag){
    case 0://無(wú)按鍵按下
      if(KEY1 == 0){
        key_flag = 1;
      }
      break;
    case 1://延時(shí)10ms消抖
      s_Counter++;
      if(s_Counter > DELAY_10ms){
        s_Counter = 0;
        if(KEY1 == 0){
          key_flag = 2;
        }else{
          key_flag = 0;
        }
      }
      break;
    case 2://計(jì)時(shí)500ms,等待按鍵松開(kāi)
      s_Counter++;
      if(s_Counter > DELAY_500ms){//500ms內(nèi)按鍵未松開(kāi)
        s_Counter = 0;  
        key_flag = 6;
      }
      if(KEY1 == 1){//500ms內(nèi)按鍵松開(kāi)
        s_Counter = 0;  
        key_flag = 3;
      }
      break;
    case 3://按鍵松開(kāi),延時(shí)10ms消抖
      s_Counter++;
      if(s_Counter > DELAY_10ms){
        s_Counter = 0;  
        key_flag = 4;
      }
    break;
    case 4://等待雙擊
      s_Counter++;
      if(s_Counter > DELAY_500ms){
        s_Counter = 0;
        key_flag = 7;//500ms內(nèi)按鍵未按下
      }
      if(KEY1 == 0){//500ms內(nèi)按鍵被按下
        s_Counter = 0;
        key_flag = 5;
      }
    break;
    case 5://延時(shí)10ms消抖
      s_Counter++;
      if(s_Counter > DELAY_10ms){
        s_Counter = 0;        
        if(KEY1 == 0){
          key_flag = 8;//等待按鍵松開(kāi)      
          return 2;//雙擊
        }else{          
          key_flag = 7;//單擊
        }
      }
    break;    
    case 6:
      s_Counter++;
      if(s_Counter > DELAY_500ms){
        s_Counter = 0;  
        key_flag = 8;//等待按鍵松開(kāi)      
        return 3;//長(zhǎng)按
      }
      if(KEY1 == 1){
        s_Counter = 0;  
        key_flag = 7;
      }
    break;
    case 7://單擊
      key_flag = 8;
      return 1;//單擊
      break;
    case 8://等待按鍵松開(kāi)      
      if(KEY1 == 1){
        s_Counter = 0;  
        key_flag = 0;        
      }
    break;  
  }
  return 0;
}
  • 在開(kāi)頭定義了延時(shí)10ms和500ms的宏
  • 用返回值代表不同的按鍵事件,返回1為單擊,返回2為雙擊,返回3為長(zhǎng)按
  • 這里提醒大家按鍵在按下和松開(kāi)時(shí)記得延時(shí)消抖

怎么樣,雙擊和長(zhǎng)按是不是很簡(jiǎn)單,接下來(lái)的彩蛋也很精彩哦。

今天的彩蛋環(huán)節(jié)依然是對(duì)上面的代碼進(jìn)行化簡(jiǎn),使其變得更簡(jiǎn)潔和優(yōu)雅。在簡(jiǎn)化之前,我們要在講一個(gè)知識(shí)點(diǎn),那就是 子狀態(tài)機(jī) 。顧名思義,就是我們可以把上面的復(fù)雜狀態(tài)機(jī)(包含8個(gè)狀態(tài)的狀態(tài)機(jī))拆成多個(gè)簡(jiǎn)單的狀態(tài)機(jī),而拆開(kāi)的每一個(gè)狀態(tài)機(jī)就是一個(gè) 子狀態(tài)機(jī) 。

這個(gè)概念是懂了,那怎么把上面的狀態(tài)機(jī)拆開(kāi)呢?

哈哈,這個(gè)就需要在“雙擊”事件中做文章,大家可以這樣想,雙擊其實(shí)就是兩次單擊,只不過(guò)兩次單擊的間隔時(shí)間小于500ms而已?;诖?,我們就可以先用一個(gè)子狀態(tài)機(jī)來(lái)區(qū)分單擊和長(zhǎng)按,然后再用一個(gè)狀態(tài)機(jī)來(lái)區(qū)分雙擊,這樣我們就把上面的復(fù)雜狀態(tài)機(jī)拆成了兩個(gè)狀態(tài)機(jī)了。

可能這樣說(shuō),大家還是不太明白,那我們就直接畫出狀態(tài)轉(zhuǎn)移圖,如下,是一個(gè)區(qū)分短按和長(zhǎng)按的子狀態(tài)機(jī)

圖片

有了狀態(tài)轉(zhuǎn)移圖,程序也很簡(jiǎn)單,如下

#define DELAY_1000ms   20000
unsigned int  get_key_short_or_long(){
  static char key_flag = 0;
  static unsigned int s_Counter = 0;
  switch(key_flag){
    case 0://無(wú)按鍵按下
      if(KEY1 == 0){
        s_Counter = 0;
        key_flag = 1;
      }
      break;
    case 1://延時(shí)10ms消抖
      s_Counter++;
      if(s_Counter > DELAY_10ms){
        s_Counter = 0;
        if(KEY1 == 0){
          key_flag = 2;
        }else{
          key_flag = 0;
        }
      }
      break;  
    case 2://計(jì)時(shí)1000ms,
      s_Counter++;
      if(s_Counter > DELAY_1000ms){//大于1000ms為長(zhǎng)按
        key_flag = 3;//等待按鍵松開(kāi)      
        return s_Counter;//長(zhǎng)按
      }
      if(KEY1 == 1){//小于1000ms為短按
        key_flag = 0;
        return s_Counter;//短按
      }
      break;
    case 3://等待按鍵松開(kāi)      
      if(KEY1 == 1){        
        key_flag = 0;        
      }
    break;      
  }
  return 0;
}
  • 在狀態(tài)2中,我們判斷是長(zhǎng)按還是短按,大于1000ms為長(zhǎng)按,否則為短按
  • 注意 ,我們這次返回的是計(jì)數(shù)器s_Counter的值,這個(gè)是為了方便之后判斷是不是雙擊(第一次單擊持續(xù)時(shí)間小于500ms要等待雙擊事件)

好,接下來(lái)我們就看看程序怎么判斷是雙擊的,如下

char get_key4(){
  static char key_flag = 0;
  static unsigned int s_Counter = 0;
  unsigned int key_time = 0;
  key_time = get_key_short_or_long();
  switch(key_flag){
    case 0:
      if(key_time >= DELAY_1000ms){
        return 3;
      }else if(key_time >= DELAY_500ms){
        return 1;
      }else if(key_time > 0){
        s_Counter = 0;
        key_flag = 1;
      }
      break;
    case 1://等待雙擊
      s_Counter++;
      if(s_Counter > DELAY_500ms){        
        key_flag = 0;
        return 1;
      }
      if(key_time > 0){
        key_flag = 0;
        return 2;
      }
      break;
  }    
}
  • 由于只有2個(gè)狀態(tài),而且都很簡(jiǎn)單,所以沒(méi)有畫它的狀態(tài)轉(zhuǎn)移圖
  • 在狀態(tài)0中,我們根據(jù)key_time 來(lái)判斷長(zhǎng)按還是短按,如果大于1秒為長(zhǎng)按,返回3;如果大于500ms小于1s為單擊返回1;如果按下的時(shí)間小于500ms,就轉(zhuǎn)為狀態(tài)1,等待雙擊。
  • 在狀態(tài)1中,我們等待500ms,如果時(shí)間到了還沒(méi)有按鍵按下則返回1,如果有按鍵按下(key_time 大于0小于500ms),則為雙擊返回2
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    STM32按鍵消抖——入門狀態(tài)機(jī)思維

    本篇介紹了嵌入式軟件開(kāi)發(fā)中常用的狀態(tài)機(jī)編程實(shí)現(xiàn),并通過(guò)按鍵消抖實(shí)例,以常用的switch-case形式,實(shí)現(xiàn)了對(duì)應(yīng)的狀態(tài)機(jī)編程代碼實(shí)現(xiàn),并通過(guò)測(cè)試,串口打印對(duì)應(yīng)狀態(tài),分析
    的頭像 發(fā)表于 09-02 21:54 ?4604次閱讀
    STM32<b class='flag-5'>按鍵</b>消抖——入門<b class='flag-5'>狀態(tài)機(jī)</b>思維

    STM32按鍵狀態(tài)機(jī)2——狀態(tài)簡(jiǎn)化與增加長(zhǎng)按功能

    本篇繼續(xù)介紹狀態(tài)機(jī)的使用,在上篇的基礎(chǔ)上,通過(guò)簡(jiǎn)化按鍵去抖邏輯,并增加按鍵長(zhǎng)按功能,進(jìn)一步介紹狀態(tài)圖的修改與狀態(tài)機(jī)代碼的實(shí)現(xiàn),并通過(guò)實(shí)際測(cè)試
    的頭像 發(fā)表于 09-03 21:26 ?3821次閱讀
    STM32<b class='flag-5'>按鍵</b><b class='flag-5'>狀態(tài)機(jī)</b>2——<b class='flag-5'>狀態(tài)</b>簡(jiǎn)化與增加長(zhǎng)按功能

    狀態(tài)機(jī)舉例

    狀態(tài)機(jī)舉例 你可以指定狀態(tài)寄存器和狀態(tài)機(jī)狀態(tài)。以下是一個(gè)有四種狀態(tài)的普通狀態(tài)機(jī)。 // Th
    發(fā)表于 03-28 15:18 ?943次閱讀

    利用狀態(tài)機(jī)按鍵消抖程序

    利用狀態(tài)機(jī)按鍵消抖程序講解,很好的資料下載吧。
    發(fā)表于 01-11 09:32 ?30次下載

    狀態(tài)機(jī)原理及用法

    狀態(tài)機(jī)原理及用法狀態(tài)機(jī)原理及用法狀態(tài)機(jī)原理及用法
    發(fā)表于 03-15 15:25 ?0次下載

    基于狀態(tài)機(jī)的單片機(jī)按鍵短按長(zhǎng)按功能的實(shí)現(xiàn)

    本文主要介紹了基于狀態(tài)機(jī)的單片機(jī)按鍵短按長(zhǎng)按功能的實(shí)現(xiàn),按鍵的擊鍵過(guò)程也是一種狀態(tài)的切換,也可以看著是一個(gè)
    發(fā)表于 12-28 08:43 ?1.9w次閱讀
    基于<b class='flag-5'>狀態(tài)機(jī)</b>的單片<b class='flag-5'>機(jī)</b><b class='flag-5'>按鍵</b>短按長(zhǎng)按功能的實(shí)現(xiàn)

    狀態(tài)機(jī)概述 如何理解狀態(tài)機(jī)

    本篇文章包括狀態(tài)機(jī)的基本概述以及通過(guò)簡(jiǎn)單的實(shí)例理解狀態(tài)機(jī)
    的頭像 發(fā)表于 01-02 18:03 ?1w次閱讀
    <b class='flag-5'>狀態(tài)機(jī)</b>概述  如何理解<b class='flag-5'>狀態(tài)機(jī)</b>

    FPGA:狀態(tài)機(jī)簡(jiǎn)述

    本文目錄 前言 狀態(tài)機(jī)簡(jiǎn)介 狀態(tài)機(jī)分類 Mealy 型狀態(tài)機(jī) Moore 型狀態(tài)機(jī) 狀態(tài)機(jī)描述 一段式
    的頭像 發(fā)表于 11-05 17:58 ?7097次閱讀
    FPGA:<b class='flag-5'>狀態(tài)機(jī)</b>簡(jiǎn)述

    什么是狀態(tài)機(jī)?狀態(tài)機(jī)5要素

    玩單片機(jī)還可以,各個(gè)外設(shè)也都會(huì)驅(qū)動(dòng),但是如果讓你完整的寫一套代碼時(shí),卻無(wú)邏輯與框架可言。這說(shuō)明編程還處于比較低的水平,你需要學(xué)會(huì)一種好的編程框架或者一種編程思想!比如模塊化編程、狀態(tài)機(jī)編程、分層思想
    的頭像 發(fā)表于 07-27 11:23 ?2w次閱讀
    什么是<b class='flag-5'>狀態(tài)機(jī)</b>?<b class='flag-5'>狀態(tài)機(jī)</b>5要素

    基于事件驅(qū)動(dòng)的有限狀態(tài)機(jī)介紹

    ? 一、介紹 EFSM(event finite state machine,事件驅(qū)動(dòng)型有限狀態(tài)機(jī)),是一個(gè)基于事件驅(qū)動(dòng)的有限狀態(tài)機(jī),主要應(yīng)用于嵌入式設(shè)備的軟件系統(tǒng)中。 EFSM的設(shè)計(jì)
    的頭像 發(fā)表于 11-16 15:29 ?2218次閱讀

    基于STM32按鍵的防抖和松開(kāi)處理:狀態(tài)機(jī)

    用延時(shí)和while();去處理按鍵很浪費(fèi)資源,這里我們用定時(shí)器來(lái)做一個(gè)按鍵的處理-狀態(tài)機(jī);typedef enum {KEY_RELEASED,KEY_PRESSED,KEY_PROCESSED
    發(fā)表于 12-09 09:21 ?7次下載
    基于STM32<b class='flag-5'>按鍵</b>的防抖和松開(kāi)處理:<b class='flag-5'>狀態(tài)機(jī)</b>

    STM32實(shí)現(xiàn)按鍵有限狀態(tài)機(jī)(超詳細(xì),易移植)

    STM32實(shí)現(xiàn)按鍵有限狀態(tài)機(jī)(超詳細(xì),易移植)一、狀態(tài)機(jī)簡(jiǎn)而言之,狀態(tài)機(jī)是使不同狀態(tài)之間的改變以及狀態(tài)
    發(fā)表于 12-17 18:37 ?26次下載
    STM32實(shí)現(xiàn)<b class='flag-5'>按鍵</b>有限<b class='flag-5'>狀態(tài)機(jī)</b>(超詳細(xì),易移植)

    基于事件驅(qū)動(dòng)的有限狀態(tài)機(jī)介紹

    EFSM(event finite state machine,事件驅(qū)動(dòng)型有限狀態(tài)機(jī)),是一個(gè)基于事件驅(qū)動(dòng)的有限狀態(tài)機(jī),主要應(yīng)用于嵌入式設(shè)備的軟件系統(tǒng)中。
    的頭像 發(fā)表于 02-11 10:17 ?949次閱讀

    按鍵狀態(tài)機(jī)代碼

    自己寫的按鍵狀態(tài)機(jī),需要的時(shí)候根據(jù)情況修改一下
    發(fā)表于 03-27 10:42 ?7次下載

    什么是狀態(tài)機(jī)?狀態(tài)機(jī)的種類與實(shí)現(xiàn)

    狀態(tài)機(jī),又稱有限狀態(tài)機(jī)(Finite State Machine,F(xiàn)SM)或米利狀態(tài)機(jī)(Mealy Machine),是一種描述系統(tǒng)狀態(tài)變化的模型。在芯片設(shè)計(jì)中,
    的頭像 發(fā)表于 10-19 10:27 ?8050次閱讀