關(guān)于狀態(tài)機(jī),基礎(chǔ)的知識(shí)點(diǎn)可以自行理解。本文主要講解的是一個(gè)有限狀態(tài)機(jī)FSM通用的寫法,目的在于更好理解,移植,節(jié)省代碼閱讀與調(diào)試時(shí)間,體現(xiàn)出編程之美。
傳統(tǒng)的實(shí)現(xiàn)方案:
if...else : 搞一大堆if else, 一個(gè)函數(shù)寫很長(zhǎng)很長(zhǎng)......
swich...case : 也搞一大堆一個(gè)函數(shù)寫很長(zhǎng)很長(zhǎng)......
下面,我們先來(lái)看看最近做的一個(gè)項(xiàng)目,無(wú)線通信協(xié)議實(shí)現(xiàn)的狀態(tài)機(jī)是什么樣子的:
有三種類型的事件:上層下達(dá)的命令事件;下層到達(dá)的標(biāo)志和數(shù)據(jù)傳輸事件;超時(shí)定時(shí)器超時(shí)事件。有10種狀態(tài),關(guān)聯(lián)性很大,復(fù)雜了吧,這要是各種if/else的要寫到什么時(shí)候呢。
偷偷放一張討論的圖,亂七八糟形容很恰當(dāng)。
在事件中判斷狀態(tài),在狀態(tài)中判斷事件,橫豎兩種寫法的代碼都比較冗長(zhǎng),看起來(lái)呢也不大好,一旦增減,就又要?jiǎng)幽X子重新梳理一遍,很累的。
怎么去寫呢?其狀態(tài)機(jī)原理:在根據(jù)當(dāng)前狀態(tài)(cur_state) 下,發(fā)生事件(event)后,轉(zhuǎn)移到下一個(gè)狀態(tài)號(hào)(nxt_state),決定執(zhí)行的動(dòng)作(action)。盜用一個(gè)圖吧!
這里我們首先定義一個(gè)結(jié)構(gòu)體如下:
?
typedef struct { ?State curState;//當(dāng)前狀態(tài) ?EventID eventId;//事件ID ?State nextState;//下個(gè)狀態(tài) ?Action action;//具體表現(xiàn)}StateTransform;
?
我們假設(shè)有3種狀態(tài),這里可以隨意增加,狀態(tài)枚舉如下:
?
typedef enum { ?state_1=1, ?state_2, ?state_3}State;
?
我們假設(shè)有5個(gè)事件,也可以隨意增加,事件ID枚舉如下:
?
typedef enum{ ?event_1=1, ?event_2, ?event_3, ?event_4, ?event_5}EventID;
?
將其封裝起來(lái)在StateMachine中:
?
typedef struct{ ?State state; ?int transNum; ?StateTransform* transform;}StateMachine;
?
具體流程:當(dāng)前狀態(tài)-有事件觸發(fā)-跳到下個(gè)狀態(tài)-具體表現(xiàn),重構(gòu)代碼。
?
StateTransform* findTranss(StateMachine* pSM, ?const EventID evt){ ?int i; ?for (i = 0; i < pSM->transNum; i++) { ? ?if ((pSM->transform[i].curState == pSM->state) && (pSM->transform[i].eventId == evt)) { ? ? ?return &pSM->transform[i]; ? ?} ?} ?return NULL;}
?
狀態(tài)機(jī)實(shí)現(xiàn)如下:
?
void runStateMachine(StateMachine* pSM, EventID evt) { ?StateTransform* pTrans; ?pTrans = findTranss(pSM, evt); ?if (pTrans == NULL) ?{ ? ?xil_printf( "CurState= %s Do not process enent: %s ", pSM->state,evt); ? ?return; ?} ?pSM->state = pTrans->nextState; ?Action act = pTrans->action; ?if (act == NULL) { ? ?xil_printf( "change state to %s. No action ",pSM->state); ? ?return; ?} ?act(&evt);}
?
最后,模擬一些隨機(jī)事件,我們只需要弄清楚事件ID,狀態(tài)切換,具體表現(xiàn)就可以了,在代碼中就是填寫 ?stateTran[] 這個(gè)表,一旦有增減事件,狀態(tài)等等,也不需要再去使用switch/case,特費(fèi)腦,其代碼如下:
?
int run(){ ?StateMachine stateMachine; ?stateMachine.state = state_1; ?stateMachine.transNum = 7; ?StateTransform stateTran[] = { ? ?{state_1,event_3,state_2,f121}, ? ?{state_1,event_4,state_2,NULL}, ? ?{state_2,event_1,state_3,f231}, ? ?{state_2,event_4,state_2,f221}, ? ?{state_3,event_2,state_1,f311}, ? ?{state_3,event_3,state_2,f321}, ? ?{state_3,event_5,state_3,f331} ?}; ?stateMachine.transform = stateTran;
?
?EventID inputEvent[15] = ?{ event_1, event_2, event_3, event_4, event_5, ? ?event_1, event_2, event_3, event_4, event_5, ? ?event_1, event_2, event_3, event_4, event_5 };
?int i; ?for (i = 0; i < 15; i++) { ? ?runStateMachine(&stateMachine, inputEvent[i]); ?} ?return 0;}
最后運(yùn)行結(jié)果如下:
總結(jié):
狀態(tài)機(jī)應(yīng)用很廣泛,也可以鍛煉我們寫代碼的邏輯思維,看清問(wèn)題的本質(zhì),寫的代碼才能賞心悅目,希望大家能夠多多指點(diǎn),找到編程的樂(lè)趣,欣賞到編程之美。
?
評(píng)論
查看更多