從零開始,漫談狀態(tài)機
ID:邏輯思維
作者:GorgonMeducer
【說在前面的話】

(圖片來源:https://en.wikipedia.org/wiki/Finite-state_machine)
【正文】

怎么理解狀態(tài)?如何才算一個狀態(tài)
extern?bool?serial_out(uint8_t?chByte);函數(shù)serial_out()可以用來向某個串行外設(shè)發(fā)送一個字符,比如UART。如果成功了就返回true,如果設(shè)備正忙導(dǎo)致本次發(fā)送失敗則立即返回false。由于外設(shè)的發(fā)送速度相對CPU的運行頻率來說差了好幾個數(shù)量級——在CPU眼中外設(shè)慢得跟蝸牛一樣,所以每次通過serial_out()?發(fā)送字符不一定是成功——很可能外設(shè)還在努力“消化”上一次的字符。這種情況下,如果我們要在狀態(tài)機中描述發(fā)送字符這樣的行為,就值得為其單獨分配一個狀態(tài),因為它滿足了我們前面說的條件:1)一件事情你要不停的嘗試才有可能成功,而且2)每次做都可能會產(chǎn)生2個以上的結(jié)果。習(xí)慣上,我們會用圖示的方法來描述狀態(tài),以發(fā)送字符'H'為例:

從圖中很容易注意到:
我們用圓圈來表示一個狀態(tài);
圓圈中心我們會寫一些注釋性質(zhì)的內(nèi)容用來幫助人們理解這個狀態(tài)是做什么的;
圖中有三個箭頭,最左上角單純“指向”狀態(tài)的箭頭表示從別的什么地方“躍遷”到了當(dāng)前狀態(tài)——我們稱為“扇入”;下方從當(dāng)前狀態(tài)指向別的什么地方的箭頭表示從當(dāng)前狀態(tài)離開;——我們成為“扇出”;右上角從當(dāng)前狀態(tài)“扇出”后又“返回到”當(dāng)前狀態(tài)的情況,我們稱之為“自返”——也就是返回自己的意思。是不是特別簡單。
實際使用的時候,如果單憑一個狀態(tài)圓圈里面的注釋文字,我們?nèi)匀徊荒芾斫膺@個狀態(tài)實際做了什么事情;或者說我們非常好奇這個狀態(tài)實際嘗試做了什么動作,就可以通過以下的標(biāo)注方法追加更多的信息,比如:

你看,是不是更加清晰了?同樣的情況還可以推廣到“調(diào)用一個函數(shù)而函數(shù)有多個不同的返回值”的情況;或者是“我們通過調(diào)用函數(shù)做了一件事情,雖然函數(shù)沒有返回值,但是我們可以通過多種其它手段來獲得這件事情的多個不同結(jié)果”的情況等等——領(lǐng)會精神,以此類推。
【第二種情況】:假設(shè)我們只是單純的在等待某一個事情發(fā)生;或者等待某個一結(jié)果——這個結(jié)果由2個以上的返回值組成等等,那么這個等待行為就需要分配一個獨立的狀態(tài)。舉個例子:
int32_t?get_sensor_voltage(void);函數(shù)get_sensor_volatage()可以返回某個傳感器的電壓值;我們設(shè)置了上下兩個門限,一旦電壓超過了任何一個門限,我們就切換到其它狀態(tài),對應(yīng)的狀態(tài)圖示如下:

在這里,HIGH_THRESHOLD和LOW_THRESHOLD是兩個宏表示上下兩個門限。可以看到,這個狀態(tài)表示:如果傳感器的電壓值在兩個門限之間,我們就留在當(dāng)前狀態(tài)(通過自返回);如果任意門限被超過,我們就相應(yīng)的跳轉(zhuǎn)到別的狀態(tài)去。
所有的神奇都在狀態(tài)躍遷上

在這個例子中,我們注意到:
雖然左上角扇入Delay狀態(tài)的躍遷條件我們并不知道,但在此時復(fù)位計數(shù)器s_wCounter是再好不過了。所以我們空出了躍遷條件,并在橫線的下方寫下了計數(shù)器的初始化代碼;
右上角的躍遷條件是:“如果計數(shù)器的值小于延時1s所需的最大值”,那么對應(yīng)的動作就是讓計數(shù)器自增;
右下角躍遷的條件是:“計數(shù)器的值超過了規(guī)定的最大值”,因此直接跳到目標(biāo)狀態(tài)而無需做其它動作。
狀態(tài)機的起點和終點

容易看出,這里 start?不僅是整個狀態(tài)機的起點,還兼任了扇入Delay狀態(tài)的躍遷的條件——從圖上來看,很容易理解成:“當(dāng)狀態(tài)機開始時復(fù)位計數(shù)器s_wCounter”——可謂一目了然。
狀態(tài)機有多簡單


“不要問,問就是子狀態(tài)機”
如果狀態(tài)機不能調(diào)用子狀態(tài)機,那它跟咸魚有什么兩樣?那么如何用圖示表示子狀態(tài)機呢?廢話少說,直接上圖:

如圖所示:
子狀態(tài)機是被圓角矩形包裹的
子狀態(tài)機的右上角有一個自反的狀態(tài)遷移,條件是“on going”意味子狀態(tài)機正在執(zhí)行,還未得出一個結(jié)果;
子狀態(tài)機的右下角(或者別的什么位置)需要有一個標(biāo)記有cpl條件的狀態(tài)遷移,表示當(dāng)子狀態(tài)機內(nèi)部達到了終點cpl以后,子狀態(tài)機從這里退出并躍遷到指定的狀態(tài);
子狀態(tài)機有一個標(biāo)題欄,里面分別列舉了狀態(tài)機的名稱以及傳遞給當(dāng)前子狀態(tài)機的形參列表。(狀態(tài)機的返回值只能是類似cpl, on-going這樣的狀態(tài),所以不需要特別標(biāo)記)
通過子狀態(tài)機調(diào)用,我們很容易用已有的狀態(tài)機實現(xiàn)搭積木的功能,比如假設(shè)我們將此前Delay的狀態(tài)機也做成子狀態(tài)機,配合這個已有的print_hello子狀態(tài)機,就可以輕松實現(xiàn)一個“打印hello然后延時1秒”的狀態(tài)機:

(這里需要注意,當(dāng)子狀態(tài)機被調(diào)用時,它使用圓角矩形替代了普通狀態(tài)的圓圈。)

怎么樣,是不是很簡單?
【后記】
