色婷婷丁香精品亚洲69,国产999精品免费国产,操女生黄色视频,骚女啊啊啊啊啊啊啊啊啊

您的位置:首頁 > 熱點資訊 >

消息!狀態(tài)機(jī)編程實例-嵌套switch-case法

2023-06-15 09:16:33 來源:碼農(nóng)愛學(xué)習(xí)

嵌入式軟件開發(fā)中,狀態(tài)機(jī)編程是一個比較實用的代碼實現(xiàn)方式,特別適用于事件驅(qū)動的系統(tǒng)。


(資料圖片)

本篇,以一個炸彈拆除的小游戲為例,介紹狀態(tài)機(jī)編程的思路。

C/C++語言實現(xiàn)狀態(tài)機(jī)編程的方式有很多,本篇先來介紹最簡單最容易理解的switch-case方法。

1 狀態(tài)機(jī)實例介紹

1.1 炸彈拆除游戲

如下是一個自制的炸彈拆除小游戲的硬件實物,由3個按鍵:

UP鍵:用于游戲開始前設(shè)置增加倒計時時間;用于游戲開始后,輸入拆除密碼“1”DOWN鍵:用于游戲開始前設(shè)置減小倒計時時間;用于游戲開始后,輸入拆除密碼“0”ARM鍵:用于從設(shè)置時間切換到開始游戲;用于輸入拆除密碼后,確認(rèn)拆除

還有一個屏幕,用于顯示倒計時時間,輸入的拆除密碼等

游戲的玩法:

游戲開始前,通過UP或DOWN鍵,設(shè)置炸彈拆除的倒計時時間;也可以不設(shè)置,使用默認(rèn)的時間按下ARM鍵,進(jìn)入倒計時狀態(tài);此時再通過UP或DOWN鍵,UP代表1,DOWN代表0,輸入拆除密碼(正確的密碼在程序中設(shè)定了,不可修改,如默認(rèn)是二進(jìn)制的1101)再按下ARM鍵,確認(rèn)拆除;若密碼正確,則拆除成功;若密碼錯誤,可以再次嘗試輸入密碼在倒計時狀態(tài),若倒計時到0時,還沒有拆除成功,則顯示拆除失敗拆除成功或失敗后,會再次回到初始狀態(tài),可重新開始玩

1.2 狀態(tài)圖

使用狀態(tài)機(jī)思路進(jìn)行編程,首先要畫出對應(yīng)的UML狀態(tài)圖,在畫圖之前,需要先明確此狀態(tài)機(jī)有哪些****狀態(tài),以及哪些 事件。

對于本篇介紹的炸彈拆除小游戲,可以歸納為兩個狀態(tài):

設(shè)置狀態(tài)(SETTING_STATE):游戲開始前,通過UP和DOWN鍵設(shè)置此次游戲的超時時間;通過ARM鍵開始游戲倒計時狀態(tài) (TIMING_STATE):游戲開始后,通過UP和DOWN鍵輸入密碼,UP代表1,DOWN代表0;通過ARM鍵確認(rèn)拆除

對于事件(或稱信號),有3個按鍵事件,還有一個Tick節(jié)拍事件:

UP鍵信號(UP_SIG):游戲開始前設(shè)置增加倒計時時間;游戲開始后,輸入拆除密碼“1”DOWN鍵信號(DOWN_SIG):游戲開始前設(shè)置減小倒計時時間;游戲開始后,輸入拆除密碼“0”ARM鍵信號(ARM_SIG):從設(shè)置時間切換到開始游戲;輸入拆除密碼后,確認(rèn)拆除Tick節(jié)拍信號(TICK_SIG):用于倒計時的時間遞減

相關(guān)的結(jié)構(gòu)定義如下

// 炸彈狀態(tài)機(jī)的所有狀態(tài)enum BombStates{    SETTING_STATE, // 設(shè)置狀態(tài)    TIMING_STATE   // 倒計時狀態(tài)};?// 炸彈狀態(tài)機(jī)的所有信號(事件)enum BombSignals{    UP_SIG,   // UP鍵信號    DOWN_SIG, // DOWN鍵信號    ARM_SIG,  // ARM鍵信號    TICK_SIG,  // Tick節(jié)拍信號    SIG_MAX};

為了便于維護(hù)狀態(tài)機(jī)所需要用到一些變量,可以將其定義為一個數(shù)據(jù)結(jié)構(gòu)體,如下:

// 超時的初始值#define INIT_TIMEOUT 10?// 炸彈狀態(tài)機(jī)數(shù)據(jù)結(jié)構(gòu)typedef struct Bomb1Tag{    uint8_t state;   // 標(biāo)量狀態(tài)變量    uint8_t timeout; // 爆炸前的秒數(shù)    uint8_t code;    // 當(dāng)前輸入的解除炸彈的密碼    uint8_t defuse;  // 解除炸彈的拆除密碼    uint8_t errcnt;  // 當(dāng)前拆除失敗的次數(shù)} Bomb1;

數(shù)據(jù)結(jié)構(gòu)定義好之后,可以設(shè)計UML狀態(tài)圖了,關(guān)于UML狀態(tài)圖的畫法與介紹,可參考之前的文章:https://www.elecfans.com/d/2076524.html,這里使用visio畫圖。

分析這個狀態(tài)圖:

初始默認(rèn)進(jìn)行“設(shè)置狀態(tài)”進(jìn)入“設(shè)置狀態(tài)”后,會先執(zhí)行****entry的初始化處理:設(shè)置默認(rèn)的超時時間,用戶的輸入錯誤次數(shù)清零處于“設(shè)置狀態(tài)”時:通過****UP和DOWN鍵設(shè)置此次游戲的超時時間,并在屏幕上顯示設(shè)置的時間,這里有最大最小時間的限制(1~60s)通過****ARM鍵開始游戲,并清除用戶的拆除密碼處于“倒計時狀態(tài)”時:通過****UP和DOWN鍵輸入密碼,UP代表1,DOWN代表0,并在屏幕上顯示輸入的密碼通過****ARM鍵確認(rèn)拆除,若密碼正常,屏幕顯示拆除成功,并進(jìn)入到“設(shè)置狀態(tài)”;若密碼不正確,則清除輸入的密碼,并顯示已失敗的次數(shù)Tick節(jié)拍事件(每1/10s一次,即100ms)到來,當(dāng)精細(xì)的時間(fine_time)為0時,說明過去了1s,則倒計時時間減1,屏幕顯示當(dāng)時的倒計時時間;若倒計時為0,則顯示拆除失敗,并進(jìn)入到“設(shè)置狀態(tài)”

1.3 事件表示

對于上述的狀態(tài)機(jī)事件,可以分為兩類,一類是按鍵事件:UP、DOWN和ARM,一類是Tick。對于第一類事件,指需要單一的事件變量即可區(qū)分,對于第二類的Tick,由于引入了1/10s的精細(xì)時間,所以這個時間還需要一個額外的****事件參數(shù)表示此次Tick事件的精細(xì)時間(fine_time)。

這里再介紹一個編程技巧,通過結(jié)構(gòu)體的繼承關(guān)系(實際就是嵌套),實現(xiàn)對事件數(shù)據(jù)結(jié)構(gòu)的設(shè)計,如下圖:

**子圖(a)**表示TickEvt與Event是繼承關(guān)系,這是UML類圖的畫法,關(guān)于UML類圖的介紹可參考之前的文章:https://www.elecfans.com/d/2072902.html。

**子圖(b)**是這兩個結(jié)構(gòu)體的定義,可以看到TickEvt結(jié)構(gòu)體內(nèi)部的第1個成員,就是Event結(jié)構(gòu)體,第2個成員,用于表示Tick事件的事件參數(shù)。

**子圖(c)**是TickEvt數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中的存儲示意,先存儲的是基類結(jié)構(gòu)體的super實例,也就是Event這個結(jié)構(gòu)體,然后存儲的是子類結(jié)構(gòu)的自定義成員,也就是Tick事件的事件參數(shù)fine_time。

這兩個結(jié)構(gòu)體的定義如下:

typedef struct EventTag{    uint16_t sig; // 事件的信號} Event;?typedef struct TickEvtTag{    Event super;       // 派生自Event結(jié)構(gòu)    uint8_t fine_time; // 精細(xì)的1/10秒計數(shù)器} TickEvt;

**這樣定義的好處是,對于狀態(tài)機(jī)事件調(diào)度函數(shù)Bomb1_dispatch的參數(shù)形式,可以統(tǒng)一使用(Event *)類型,將TickEvt類型傳入時,可以取其地址,再轉(zhuǎn)為(Event *)類型,如下面實例代碼中l(wèi)oop函數(shù)中的使用;而在Bomb1_dispatch函數(shù)內(nèi)部需要處理TICK_SIG事件時,又可以再將(Event )類型強(qiáng)制轉(zhuǎn)為(TickEvt )類型,如下面實例代碼中Bomb1_dispatch函數(shù)中的使用。

//狀態(tài)機(jī)事件調(diào)度void Bomb1_dispatch(Bomb1 *me, Event const *e){    //省略...    case TICK_SIG: //Tick信號    {        if (((TickEvt const *)e)- >fine_time == 0)        {            --me- >timeout;            bsp_display_remain_time(me- >timeout); //顯示倒計時時間            if (me- >timeout == 0)            {                bsp_display_bomb(); //顯示爆炸效果                Bomb1_init(me);            }        }        break;    }    //省略...}?//狀態(tài)機(jī)循環(huán)void loop(void){  static TickEvt tick_evt = {TICK_SIG, 0};  delay(100); /*狀態(tài)機(jī)以100ms的循環(huán)運行*/?  if (++tick_evt.fine_time == 10)  {    tick_evt.fine_time = 0;  }?  Bomb1_dispatch(&l_bomb, (Event *)&tick_evt); /*調(diào)度處理tick事件*/  //省略...}

2 switch-case嵌套法

狀態(tài)圖設(shè)計好之后,就可以對照著狀態(tài)圖,進(jìn)行編程實現(xiàn)了。

本篇先使用最簡單最容易理解的switch-case方法,來實現(xiàn)狀態(tài)機(jī)編程。

2.1 狀態(tài)機(jī)處理

使用switch-case法實現(xiàn)狀態(tài)機(jī),一般需要兩層switch結(jié)構(gòu)。

2.1.1 第一層switch處理狀態(tài)

void Bomb1_dispatch(Bomb1 *me, Event const *e){    //第一層switch處理狀態(tài)    switch (me- >state)    {        //設(shè)置狀態(tài)        case SETTING_STATE:        {            //...            break;        }        //倒計時狀態(tài)        case TIMING_STATE:        {//...            break;        }    }}

2.1.2 第二層switch處理事件

這里以狀態(tài)機(jī)處于“設(shè)置狀態(tài)”時,對事件(信號)的處理為例

//設(shè)置狀態(tài)case SETTING_STATE:{    //第二層switch處理事件(信號)    switch (e- >sig)    {        //UP按鍵信號        case UP_SIG:        {            //...            break;        }        //DOWN按鍵信號        case DOWN_SIG:        {            //...            break;        }        //ARM按鍵信號        case ARM_SIG:        {            //...            break;        }    }    break;}

2.1.3 兩層switch-case狀態(tài)機(jī)完整代碼

// 用于進(jìn)行狀態(tài)轉(zhuǎn)換的宏#define TRAN(target_) (me- >state = (uint8_t)(target_))?//狀態(tài)機(jī)事件調(diào)度void Bomb1_dispatch(Bomb1 *me, Event const *e){  //第一層switch處理狀態(tài)  switch (me- >state)  {    //設(shè)置狀態(tài)    case SETTING_STATE:      {        //第二層switch處理事件(信號)        switch (e- >sig)        {          //UP按鍵信號          case UP_SIG:            {              if (me- >timeout < 60)              {                ++me- >timeout; //設(shè)置超時時間+1                bsp_display_set_time(me- >timeout); //顯示設(shè)置的超時時間              }              break;            }          //DOWN按鍵信號          case DOWN_SIG:            {              if (me- >timeout > 1)              {                --me- >timeout; //設(shè)置超時時間-1                bsp_display_set_time(me- >timeout); //顯示設(shè)置的超時時間              }              break;            }          //ARM按鍵信號          case ARM_SIG:            {              me- >code = 0;              TRAN(TIMING_STATE); //轉(zhuǎn)換到倒計時狀態(tài)              break;            }        }        break;      }    //倒計時狀態(tài)    case TIMING_STATE:      {        switch (e- >sig)        {          case UP_SIG: //UP按鍵信號            {              me- >code < <= 1;              me- >code |= 1; //添加一個1              bsp_display_user_code(me- >code);              break;            }          case DOWN_SIG: //DWON按鍵信號            {              me- >code < <= 1; //添加一個0              bsp_display_user_code(me- >code);              break;            }          case ARM_SIG: //ARM按鍵信號            {              if (me- >code == me- >defuse)              {                TRAN(SETTING_STATE); //轉(zhuǎn)換到設(shè)置狀態(tài)                bsp_display_user_success(); //炸彈拆除成功                Bomb1_init(me);              }              else              {                me- >code = 0;                bsp_display_user_code(me- >code);                bsp_display_user_err(++me- >errcnt);              }              break;            }          case TICK_SIG: //Tick信號            {              if (((TickEvt const *)e)- >fine_time == 0)              {                --me- >timeout;                bsp_display_remain_time(me- >timeout); //顯示倒計時時間                if (me- >timeout == 0)                {                  bsp_display_bomb(); //顯示爆炸效果                  Bomb1_init(me);                }              }              break;            }        }        break;      }  }}

2.2 主函數(shù)

兩層switch-case狀態(tài)機(jī)邏輯編寫好之后,還需要將狀態(tài)機(jī)運行起來。

運行狀態(tài)機(jī)的本質(zhì),就是周期性的調(diào)用狀態(tài)機(jī)(上面實現(xiàn)的兩層switch-case),當(dāng)有事件觸發(fā)時,設(shè)置對應(yīng)的事件,狀態(tài)機(jī)在運行時,即可處理對應(yīng)的事件,從而實現(xiàn)狀態(tài)的切換,或是其它的邏輯處理。

2.2.1 狀態(tài)機(jī)的運行

狀態(tài)機(jī)運行的整體邏輯如下:

void loop(void){  static TickEvt tick_evt = {TICK_SIG, 0};  delay(100); /*狀態(tài)機(jī)以100ms的循環(huán)運行*/?  if (++tick_evt.fine_time == 10)  {    tick_evt.fine_time = 0;  }?  char tmp_buffer[256];  sprintf(tmp_buffer, "T(%1d)%c", tick_evt.fine_time, (tick_evt.fine_time == 0) ? "\\n" : " ");  Serial.print(tmp_buffer);?  Bomb1_dispatch(&l_bomb, (Event *)&tick_evt); /*調(diào)度處理tick事件*/?  BombSignals userSignal = bsp_key_check_signal();  if (userSignal != SIG_MAX)  {    static Event const up_evt = {UP_SIG};    static Event const down_evt = {DOWN_SIG};    static Event const arm_evt = {ARM_SIG};    Event const *e = (Event *)0;?    switch (userSignal)    {      //監(jiān)測按鍵是否按下, 按下則設(shè)置對應(yīng)的事件e    }?    if (e != (Event *)0) /*有指定的按鍵按下*/    {      Bomb1_dispatch(&l_bomb, e);  /*調(diào)度處理按鍵事件*/    }  }}

2.2.2 事件的觸發(fā)

**在狀態(tài)機(jī)的每個狀態(tài)循環(huán)執(zhí)行前,都檢測一下是否有事件觸發(fā),本例中就是UP、DOWN和ARM的按鍵事件,另外Tick事件是周期性的觸發(fā)的。UP、DOWN和ARM的按鍵事件的觸發(fā)檢測代碼如下,檢測到對應(yīng)的按鍵事件后,則設(shè)置對應(yīng)的事件給狀態(tài)機(jī),狀態(tài)機(jī)即可在下次狀態(tài)循環(huán)中進(jìn)行處理。 **

switch (userSignal){    case UP_SIG: //UP鍵事件        {            Serial.print("\\nUP  : ");            e = &up_evt;            break;        }    case DOWN_SIG: //DOWN鍵事件        {            Serial.print("\\nDOWN: ");            e = &down_evt;            break;        }    case ARM_SIG: //ARM鍵事件        {            Serial.print("\\nARM : ");            e = &arm_evt;            break;        }    default:break;}

3 測試

本例程,使用Arduino作為控制器進(jìn)行測試,外接3個獨立按鍵和一個IIC接口的OLED顯示屏。

演示視頻

4 總結(jié)

本篇以一個炸彈拆除的小游戲為例,介紹了嵌入式軟件開發(fā)中,狀態(tài)機(jī)編程的思路:

分析系統(tǒng)需要哪幾種狀態(tài),哪幾種事件定義這些狀態(tài)、事件,以及狀態(tài)機(jī)的數(shù)據(jù)結(jié)構(gòu)使用UML建模,設(shè)計對應(yīng)的狀態(tài)圖根據(jù)狀態(tài)圖,使用C/C++語言,編程實現(xiàn)對應(yīng)的功能結(jié)合硬件進(jìn)行調(diào)試,分析

另外,本篇中,還需要體會的是,對事件的表示,通過結(jié)構(gòu)體繼承(嵌套)的方式,實現(xiàn)一個額外的件參數(shù)這種用法。審核編輯:湯梓紅

最近更新

彭泽县| 清新县| 武夷山市| 宝坻区| 湘西| 霍州市| 云浮市| 怀安县| 枣阳市| 阆中市| 新竹市| 科技| 万州区| 南华县| 甘孜县| 铜梁县| 湘乡市| 昌宁县| 南溪县| 襄樊市| 玉山县| 六安市| 赞皇县| 中卫市| 南澳县| 仁怀市| 霞浦县| 彭水| 恩施市| 裕民县| 古田县| 惠州市| 凤山县| 吴堡县| 忻州市| 云龙县| 张北县| 永善县| 新源县| 四子王旗| 鞍山市|