精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于狀態機的按鍵驅動設計

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

【說在前面的話】

按鍵作為單片機的輸入設備,可以向單片機輸入數據、傳輸命令等,是設置參數和控制設備的常用接口。所以,學會按鍵驅動也是初學者必不可少的能力。說到按鍵驅動程序,大家應該也不陌生,而一般的按鍵驅動流程圖如下

圖片

這里,可能有人會問,為什么要延時10ms啊?

那是因為按鍵被按下時,不會像理想的情況非0即1,而是會有抖動,如下圖

圖片

機械按鍵被按下或松開時,會有10ms的抖動時間,所以要延時10ms來消去波形抖動(* ̄︶ ̄)

知道了這個,一般初學者編寫的按鍵驅動程序如下:

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

像這種按鍵驅動程序也很簡單,作為基礎學習和一些簡單的系統中還可以,但是在很多的產品設計中,這種按鍵程序還是有很大的不足和缺陷。因為他不僅采用了軟件延時使單片機效率降低而且還在那里死等按鍵松開,系統的實時性也變得很差。為此,有人提出了 一種基于狀態機的按鍵驅動程序 ,很好地解決了上述程序的缺陷。下面我們就簡單講一下什么是狀態機。

【狀態機簡介】

對于學電子的同學,首先接觸到的狀態機應該是在數字邏輯電路( 簡稱數電 )中,狀態機的分析方法被應用于時序邏輯電路的設計中,其實狀態機的思想對我們的軟件設計也很有用,首先簡單介紹一下狀態機,它是由有限的狀態相互之間的遷移構成的。在任何時候,只能處于狀態機的某一個狀態,當接收到一個轉移事件時,狀態機進行狀態的轉移。

下面,就以按鍵驅動為例,畫出他的狀態轉移圖,如下

圖片

有了狀態轉移圖,那我們就用程序實現一下這個按鍵驅動程序。從圖中我們知道按鍵驅動程序由3個狀態,剛好可以用C語言的switch case語句來實現這3個狀態,而狀態間的遷移就可以用if條件判斷語句來實現。知道了這個,那我們就動手實現一下。

基于狀態機的按鍵驅動程序

首先,打開原理圖,看一下按鍵接到了單片機的哪個管腳,如下

圖片

我們以按鍵1為例,接到了單片機的P33腳,當按鍵 按下時為低電平松開為高電平 ,基于此我們的按鍵程序如下:

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

基于狀態機的按鍵驅動程序我們就簡單寫完了,相信大家也get到重點了,這個只是簡單實現了按鍵的單擊,當然,我們也可以實現按鍵的雙擊和長按。

哈哈,在編寫驅動之前,我們先細化一下需求,首先區分單擊和長按,這個很簡單,規定一個時間就可以,我們定為1秒鐘。即按下時間小于1秒為單擊,大于1秒為長按。

那雙擊怎么辦呢?

我們規定,當第一次按下持續時間小于500ms內松開按鍵,在之后500ms內又按下按鍵,此時為雙擊事件。這里有兩點需要注意,1、第一次按下的時間不能超過500ms,否則就被判斷為單擊或長按。2、在第一次按下松開后開始計時,如果500ms內沒有按鍵再次按下則為單擊。按鍵雙擊的原理如下圖所示

圖片

按鍵雙擊和長按的需求我們講完了,接下來畫出他的狀態轉移圖,如下

圖片

哈哈哈,還可以吧,沒那么復雜。相信大家應該能看懂。這里有必要說一下狀態5,判斷雙擊其實就是第二次按下延時10ms消抖,如果確實按下則為雙擊否則為單擊。好了,看看程序怎么實現吧,如下

#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://無按鍵按下
      if(KEY1 == 0){
        key_flag = 1;
      }
      break;
    case 1://延時10ms消抖
      s_Counter++;
      if(s_Counter > DELAY_10ms){
        s_Counter = 0;
        if(KEY1 == 0){
          key_flag = 2;
        }else{
          key_flag = 0;
        }
      }
      break;
    case 2://計時500ms,等待按鍵松開
      s_Counter++;
      if(s_Counter > DELAY_500ms){//500ms內按鍵未松開
        s_Counter = 0;  
        key_flag = 6;
      }
      if(KEY1 == 1){//500ms內按鍵松開
        s_Counter = 0;  
        key_flag = 3;
      }
      break;
    case 3://按鍵松開,延時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內按鍵未按下
      }
      if(KEY1 == 0){//500ms內按鍵被按下
        s_Counter = 0;
        key_flag = 5;
      }
    break;
    case 5://延時10ms消抖
      s_Counter++;
      if(s_Counter > DELAY_10ms){
        s_Counter = 0;        
        if(KEY1 == 0){
          key_flag = 8;//等待按鍵松開      
          return 2;//雙擊
        }else{          
          key_flag = 7;//單擊
        }
      }
    break;    
    case 6:
      s_Counter++;
      if(s_Counter > DELAY_500ms){
        s_Counter = 0;  
        key_flag = 8;//等待按鍵松開      
        return 3;//長按
      }
      if(KEY1 == 1){
        s_Counter = 0;  
        key_flag = 7;
      }
    break;
    case 7://單擊
      key_flag = 8;
      return 1;//單擊
      break;
    case 8://等待按鍵松開      
      if(KEY1 == 1){
        s_Counter = 0;  
        key_flag = 0;        
      }
    break;  
  }
  return 0;
}
  • 在開頭定義了延時10ms和500ms的宏
  • 用返回值代表不同的按鍵事件,返回1為單擊,返回2為雙擊,返回3為長按
  • 這里提醒大家按鍵在按下和松開時記得延時消抖

怎么樣,雙擊和長按是不是很簡單,接下來的彩蛋也很精彩哦。

今天的彩蛋環節依然是對上面的代碼進行化簡,使其變得更簡潔和優雅。在簡化之前,我們要在講一個知識點,那就是 子狀態機 。顧名思義,就是我們可以把上面的復雜狀態機(包含8個狀態的狀態機)拆成多個簡單的狀態機,而拆開的每一個狀態機就是一個 子狀態機

這個概念是懂了,那怎么把上面的狀態機拆開呢?

哈哈,這個就需要在“雙擊”事件中做文章,大家可以這樣想,雙擊其實就是兩次單擊,只不過兩次單擊的間隔時間小于500ms而已。基于此,我們就可以先用一個子狀態機來區分單擊和長按,然后再用一個狀態機來區分雙擊,這樣我們就把上面的復雜狀態機拆成了兩個狀態機了。

可能這樣說,大家還是不太明白,那我們就直接畫出狀態轉移圖,如下,是一個區分短按和長按的子狀態機

圖片

有了狀態轉移圖,程序也很簡單,如下

#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://無按鍵按下
      if(KEY1 == 0){
        s_Counter = 0;
        key_flag = 1;
      }
      break;
    case 1://延時10ms消抖
      s_Counter++;
      if(s_Counter > DELAY_10ms){
        s_Counter = 0;
        if(KEY1 == 0){
          key_flag = 2;
        }else{
          key_flag = 0;
        }
      }
      break;  
    case 2://計時1000ms,
      s_Counter++;
      if(s_Counter > DELAY_1000ms){//大于1000ms為長按
        key_flag = 3;//等待按鍵松開      
        return s_Counter;//長按
      }
      if(KEY1 == 1){//小于1000ms為短按
        key_flag = 0;
        return s_Counter;//短按
      }
      break;
    case 3://等待按鍵松開      
      if(KEY1 == 1){        
        key_flag = 0;        
      }
    break;      
  }
  return 0;
}
  • 在狀態2中,我們判斷是長按還是短按,大于1000ms為長按,否則為短按
  • 注意 ,我們這次返回的是計數器s_Counter的值,這個是為了方便之后判斷是不是雙擊(第一次單擊持續時間小于500ms要等待雙擊事件)

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

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個狀態,而且都很簡單,所以沒有畫它的狀態轉移圖
  • 在狀態0中,我們根據key_time 來判斷長按還是短按,如果大于1秒為長按,返回3;如果大于500ms小于1s為單擊返回1;如果按下的時間小于500ms,就轉為狀態1,等待雙擊。
  • 在狀態1中,我們等待500ms,如果時間到了還沒有按鍵按下則返回1,如果有按鍵按下(key_time 大于0小于500ms),則為雙擊返回2
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 單片機
    +關注

    關注

    6032

    文章

    44520

    瀏覽量

    633076
  • 驅動程序
    +關注

    關注

    19

    文章

    827

    瀏覽量

    47959
  • 狀態機
    +關注

    關注

    2

    文章

    492

    瀏覽量

    27478
  • 驅動設計
    +關注

    關注

    1

    文章

    110

    瀏覽量

    15268
  • 按鍵驅動
    +關注

    關注

    0

    文章

    11

    瀏覽量

    7126
收藏 人收藏

    評論

    相關推薦

    STM32按鍵消抖——入門狀態機思維

    本篇介紹了嵌入式軟件開發中常用的狀態機編程實現,并通過按鍵消抖實例,以常用的switch-case形式,實現了對應的狀態機編程代碼實現,并通過測試,串口打印對應狀態,分析
    的頭像 發表于 09-02 21:54 ?4768次閱讀
    STM32<b class='flag-5'>按鍵</b>消抖——入門<b class='flag-5'>狀態機</b>思維

    STM32按鍵狀態機2——狀態簡化與增加長按功能

    本篇繼續介紹狀態機的使用,在上篇的基礎上,通過簡化按鍵去抖邏輯,并增加按鍵長按功能,進一步介紹狀態圖的修改與狀態機代碼的實現,并通過實際測試
    的頭像 發表于 09-03 21:26 ?4019次閱讀
    STM32<b class='flag-5'>按鍵</b><b class='flag-5'>狀態機</b>2——<b class='flag-5'>狀態</b>簡化與增加長按功能

    狀態機舉例

    狀態機舉例 你可以指定狀態寄存器和狀態機狀態。以下是一個有四種狀態的普通狀態機。 // Th
    發表于 03-28 15:18 ?976次閱讀

    利用狀態機按鍵消抖程序

    利用狀態機按鍵消抖程序講解,很好的資料下載吧。
    發表于 01-11 09:32 ?30次下載

    狀態機原理及用法

    狀態機原理及用法狀態機原理及用法狀態機原理及用法
    發表于 03-15 15:25 ?0次下載

    基于狀態機的單片按鍵短按長按功能的實現

    本文主要介紹了基于狀態機的單片按鍵短按長按功能的實現,按鍵的擊鍵過程也是一種狀態的切換,也可以看著是一個
    發表于 12-28 08:43 ?1.9w次閱讀
    基于<b class='flag-5'>狀態機</b>的單片<b class='flag-5'>機</b><b class='flag-5'>按鍵</b>短按長按功能的實現

    狀態機概述 如何理解狀態機

    本篇文章包括狀態機的基本概述以及通過簡單的實例理解狀態機
    的頭像 發表于 01-02 18:03 ?1w次閱讀
    <b class='flag-5'>狀態機</b>概述  如何理解<b class='flag-5'>狀態機</b>

    FPGA:狀態機簡述

    本文目錄 前言 狀態機簡介 狀態機分類 Mealy 型狀態機 Moore 型狀態機 狀態機描述 一段式
    的頭像 發表于 11-05 17:58 ?7302次閱讀
    FPGA:<b class='flag-5'>狀態機</b>簡述

    什么是狀態機狀態機5要素

    玩單片還可以,各個外設也都會驅動,但是如果讓你完整的寫一套代碼時,卻無邏輯與框架可言。這說明編程還處于比較低的水平,你需要學會一種好的編程框架或者一種編程思想!比如模塊化編程、狀態機編程、分層思想
    的頭像 發表于 07-27 11:23 ?2w次閱讀
    什么是<b class='flag-5'>狀態機</b>?<b class='flag-5'>狀態機</b>5要素

    基于事件驅動的有限狀態機介紹

    ? 一、介紹 EFSM(event finite state machine,事件驅動型有限狀態機),是一個基于事件驅動的有限狀態機,主要應用于嵌入式設備的軟件系統中。 EFSM的設計
    的頭像 發表于 11-16 15:29 ?2317次閱讀

    基于STM32按鍵的防抖和松開處理:狀態機

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

    STM32實現按鍵有限狀態機(超詳細,易移植)

    STM32實現按鍵有限狀態機(超詳細,易移植)一、狀態機簡而言之,狀態機是使不同狀態之間的改變以及狀態
    發表于 12-17 18:37 ?26次下載
    STM32實現<b class='flag-5'>按鍵</b>有限<b class='flag-5'>狀態機</b>(超詳細,易移植)

    基于事件驅動的有限狀態機介紹

    EFSM(event finite state machine,事件驅動型有限狀態機),是一個基于事件驅動的有限狀態機,主要應用于嵌入式設備的軟件系統中。
    的頭像 發表于 02-11 10:17 ?1023次閱讀

    按鍵狀態機代碼

    自己寫的按鍵狀態機,需要的時候根據情況修改一下
    發表于 03-27 10:42 ?8次下載

    什么是狀態機狀態機的種類與實現

    狀態機,又稱有限狀態機(Finite State Machine,FSM)或米利狀態機(Mealy Machine),是一種描述系統狀態變化的模型。在芯片設計中,
    的頭像 發表于 10-19 10:27 ?9016次閱讀