在嵌入式軟件開(kāi)發(fā)中,狀態(tài)機(jī)編程是一個(gè)十分重要的編程思想,它也是嵌入式開(kāi)發(fā)中一個(gè)常用的編程框架。掌握了狀態(tài)機(jī)編程思想,可以更加邏輯清晰的實(shí)現(xiàn)復(fù)雜的業(yè)務(wù)邏輯功能。
1 狀態(tài)機(jī)思想
狀態(tài)機(jī),或稱(chēng)有限狀態(tài)機(jī)FSM(Finite?State Machine),是一種重要的編程思想。
狀態(tài)機(jī)有3要素:狀態(tài)、事件與響應(yīng)
狀態(tài):系統(tǒng)處在什么狀態(tài)?
事件:發(fā)生了什么事?
響應(yīng):此狀態(tài)下發(fā)生了這樣的事,系統(tǒng)要如何處理?
狀態(tài)機(jī)編程前,首先要根據(jù)需要實(shí)現(xiàn)的功能,整理出一個(gè)對(duì)應(yīng)的狀態(tài)轉(zhuǎn)換圖(狀態(tài)機(jī)圖),然后就可以根據(jù)這個(gè)狀態(tài)轉(zhuǎn)換圖,套用狀態(tài)機(jī)編程模板,實(shí)現(xiàn)對(duì)應(yīng)是狀態(tài)機(jī)代碼了。
狀態(tài)機(jī)編程主要有 3 種方法:switch-case 法、表格驅(qū)動(dòng)法、函數(shù)指針?lè)?/strong>,本篇先介紹最簡(jiǎn)單也最易理解的switch-case 法。
2 狀態(tài)機(jī)實(shí)例
下面以按鍵消抖功能,來(lái)介紹switch-case 法的狀態(tài)機(jī)編程思路。
2.1 按鈕消抖狀態(tài)轉(zhuǎn)換圖
狀態(tài)機(jī)機(jī)編程前,首先要明確的對(duì)應(yīng)功能的狀態(tài)機(jī)需要幾個(gè)狀態(tài),本例的按鍵功能,只檢測(cè)最基礎(chǔ)的按下與松開(kāi)狀態(tài)(暫不實(shí)現(xiàn)長(zhǎng)按、雙擊等狀態(tài)),并增加對(duì)應(yīng)的按鈕去抖功能,因此,需要用到4個(gè)狀態(tài):
穩(wěn)定松開(kāi)狀態(tài)
按下抖動(dòng)狀態(tài)
穩(wěn)定按下?tīng)顟B(tài)
松開(kāi)抖動(dòng)狀態(tài)
對(duì)應(yīng)的狀態(tài)轉(zhuǎn)換圖如下:
由于按鍵通常處于松開(kāi)狀態(tài),這里讓狀態(tài)機(jī)的初始化狀態(tài)為松開(kāi)狀態(tài),然后在這4個(gè)狀態(tài)中來(lái)回切換。
圖中的VT代表按鍵檢測(cè)到電平,VT=0即檢測(cè)到低電平,可能是按鍵按下,由初始的“穩(wěn)定松開(kāi)”狀態(tài)轉(zhuǎn)為“按下抖動(dòng)”狀態(tài)
當(dāng)持續(xù)檢測(cè)到低電平(VT=0)一段時(shí)間后,認(rèn)為消抖完成,由“按下抖動(dòng)”狀態(tài)轉(zhuǎn)為“穩(wěn)定按下”狀態(tài)
在“按下抖動(dòng)”狀態(tài)時(shí),在指定的一段時(shí)間內(nèi),再次檢測(cè)到高電平(VT=1),說(shuō)明確實(shí)是按鈕抖動(dòng)(比如按鍵被快速撥動(dòng)了一下又彈起,或強(qiáng)烈震動(dòng)導(dǎo)致的按鍵抖動(dòng)),則由“按下抖動(dòng)”狀態(tài)轉(zhuǎn)為“穩(wěn)定松開(kāi)”狀態(tài)
2.2 編程實(shí)現(xiàn)
2.2.1 狀態(tài)定義
對(duì)應(yīng)上面的按鈕狀態(tài)圖,可以知道需要用到4個(gè)狀態(tài):
穩(wěn)定松開(kāi)狀態(tài)(KS_RELEASE)
按下抖動(dòng)狀態(tài)(KS_PRESS_SHAKE)
穩(wěn)定按下?tīng)顟B(tài)(KS_PRESS)
松開(kāi)抖動(dòng)狀態(tài)(KS_RELEASE_SHAKE)
這里使用枚舉來(lái)定義這4個(gè)狀態(tài)。為了在調(diào)試時(shí),能夠把對(duì)應(yīng)狀態(tài)名稱(chēng)以字符串的形式打印出來(lái),這里使用宏定義的一個(gè)小技巧:
#符號(hào)+自定義的枚舉名稱(chēng)
即可自動(dòng)轉(zhuǎn)變?yōu)樽址问剑賹⑦@些字符串放到const char* key_status_name[]數(shù)組中,便可通過(guò)數(shù)組的形式訪(fǎng)問(wèn)這些狀態(tài)的字符串名稱(chēng)形式。
此外,為了不重復(fù)書(shū)寫(xiě)枚舉名稱(chēng)與對(duì)應(yīng)的枚舉字符串(#+枚舉名稱(chēng)),進(jìn)一步使用宏定義的方式,只定義一次狀態(tài),然后通過(guò)下面兩條宏定義,實(shí)現(xiàn)對(duì)枚舉項(xiàng)和枚舉項(xiàng)對(duì)應(yīng)的字符串的分別獲取:
#define?ENUM_ITEM(ITEM)?ITEM,#define?ENUM_STRING(ITEM)?#ITEM,
具體是宏定義、枚舉定義與枚舉名稱(chēng)數(shù)組聲明如下:
#define?ENUM_ITEM(ITEM)?ITEM,#define?ENUM_STRING(ITEM)?#ITEM,#define?KEY_STATUS_ENUM(STATUS)??????????????? ????STATUS(KS_RELEASE)???????/*穩(wěn)定松開(kāi)狀態(tài)*/??? ????STATUS(KS_PRESS_SHAKE)???/*按下抖動(dòng)狀態(tài)*/? ???? ??STATUS(KS_PRESS)???????/*穩(wěn)定按下?tīng)顟B(tài)*/???? ???STATUS(KS_RELEASE_SHAKE)?/*松開(kāi)抖動(dòng)狀態(tài)*/?????? ?STATUS(KS_NUM)???????????/*狀態(tài)總數(shù)(無(wú)效狀態(tài))*/?? ypedef?enum{KEY_STATUS_ENUM(ENUM_ITEM)}KEY_STATUS;const?char*?key_status_name[]?=?{KEY_STATUS_ENUM(ENUM_STRING)};
宏定義不便理解的,可以將宏定義分別帶入,轉(zhuǎn)為最終的結(jié)果,理解替代后的具體形式,比如下面的宏定義帶入替換示意:
/*KEY_STATUS_ENUM(STATUS)?-->? STATUS(KS_RELEASE)?...? STATUS(KS_NUM)KEY_STATUS_ENUM(ENUM_ITEM)-->? ENUM_ITEM(KS_RELEASE)?...? ENUM_ITEM(KS_NUM)-->?KS_RELEASE,?... ?KS_NUM,KEY_STATUS_ENUM(ENUM_STRING)-->? ENUM_STRING(KS_RELEASE)?... ?ENUM_STRING(KS_NUM)--> #KS_RELEASE,?...?#KS_NUM,*/
?
2.2.2 狀態(tài)機(jī)實(shí)現(xiàn)
下面是狀態(tài)機(jī)的具體實(shí)現(xiàn):
狀態(tài)機(jī)函數(shù)key_status_check在一個(gè)循環(huán)中,被每隔10ms調(diào)用一次
定義一個(gè)g_keyStatus表示狀態(tài)機(jī)所處的狀態(tài)
在每個(gè)循環(huán)中,switch根據(jù)當(dāng)前的狀態(tài),執(zhí)行對(duì)應(yīng)狀態(tài)所需要執(zhí)行的邏輯
定義一個(gè)g_DebounceCnt用于消抖時(shí)間計(jì)算,當(dāng)持續(xù)進(jìn)入消抖狀態(tài),每次循環(huán)(10ms)中將此值加1,持續(xù)一定次數(shù)(5次,即50ms),認(rèn)為是穩(wěn)定的按下或松開(kāi),消抖完成,跳轉(zhuǎn)到穩(wěn)定方向或穩(wěn)定松開(kāi)狀態(tài)
在每個(gè)狀態(tài)的執(zhí)行邏輯中,當(dāng)檢測(cè)到某些條件滿(mǎn)足時(shí),跳轉(zhuǎn)到其它的狀態(tài)
通過(guò)狀態(tài)的不斷跳轉(zhuǎn),實(shí)現(xiàn)狀態(tài)機(jī)的運(yùn)行
此外,為方便觀察狀態(tài)機(jī)中狀態(tài)的變化,定義了一個(gè)g_lastKeyStatus表示前一狀態(tài),當(dāng)狀態(tài)發(fā)生變化時(shí),可以將狀態(tài)名稱(chēng)打印出來(lái)
KEY_STATUS?g_keyStatus?=?KS_RELEASE;?//當(dāng)前按鍵的狀態(tài) KEY_STATUS?g_lastKeyStatus?=?KS_NUM;?//上一狀態(tài) int?g_DebounceCnt?=?0;?//消抖時(shí)間計(jì)數(shù) void?key_status_check(){switch(g_keyStatus){//按鍵釋放(初始狀態(tài)) case?KS_RELEASE:{//檢測(cè)到低電平,先進(jìn)行消抖if?(KEY0?==?0){g_keyStatus?=?KS_PRESS_SHAKE;g_DebounceCnt?=?0;}}break;//按下抖動(dòng) case?KS_PRESS_SHAKE:{g_DebounceCnt++;//確實(shí)是抖動(dòng) if?(KEY0?==?1){g_keyStatus?=?KS_RELEASE;}//消抖完成 else?if?(g_DebounceCnt?==?5){g_keyStatus?=?KS_PRESS;printf("=====>?key?press ");}}break;//穩(wěn)定按下 case?KS_PRESS:{//檢測(cè)到高電平,先進(jìn)行消抖 if?(KEY0?==?1){g_keyStatus?=?KS_RELEASE_SHAKE;g_DebounceCnt?=?0;}}break;//松開(kāi)抖動(dòng) case?KS_RELEASE_SHAKE:{g_DebounceCnt++;// 確實(shí)是抖動(dòng)if?(KEY0?==?0){g_keyStatus?=?KS_PRESS;}//消抖完成 else?if?(g_DebounceCnt?==?5){g_keyStatus?=?KS_RELEASE;printf("=====>?key?release ");}}break;default:break;}if?(g_keyStatus?!=?g_lastKeyStatus){g_lastKeyStatus?=?g_keyStatus;printf("new?key?status:%d(%s) ",?g_keyStatus,?key_status_name[g_keyStatus]);}}int?main(void){delay_init();????//延時(shí)函數(shù)初始化? ?KEY_Init();uart_init(115200);printf("hello ");while(1){key_status_check();delay_ms(10);}}
注:本例程需要使用一個(gè)按鍵,需要初始化對(duì)應(yīng)的GPIO,這里不再貼代碼。
2.3 使用測(cè)試
將完整的代碼編譯后燒錄到板子中,連接串口,按下與松開(kāi)按鍵,觀察串口輸出信息。
我的測(cè)試輸出信息如下:
前兩次撥動(dòng)按鍵模擬按鈕抖動(dòng)的情況,可以看到串口打印出兩次從松開(kāi)到按下抖動(dòng)的狀態(tài)切換。
然后是按下按鍵,再松開(kāi)按鍵,可以看到狀態(tài)的變化:松開(kāi)?-> 按下抖動(dòng) ->?按下?-> 松開(kāi)抖動(dòng) ->?松開(kāi)
3 總結(jié)
本篇介紹了嵌入式軟件開(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),分析狀態(tài)機(jī)的狀態(tài)跳轉(zhuǎn)過(guò)程。
編輯:黃飛
?
評(píng)論
查看更多