嵌入式狀態機軟件實現方式
回想剛開始工作的時候,寫代碼感覺都會很凌亂,不知道從哪里開始入手,而且寫完老是漏掉各種情況處理。只能靠測試來發現缺陷,著實苦惱。直到某天聽到辦公室某個大佬指導另一個同事:“你搞個狀態機去管理啊......你知道什么是狀態機不?”于是便抱著好奇的心態去搜索了一下,從此打開了新的大門哈哈哈!發現尤其適合于嵌入式編程,所以十分推薦新手都了解一下。
狀態機,這是在嵌入式系統中大量使用的軟件模式,簡單點說,就是會基于當前的狀態去做不同的動作,切換到下一個狀態。
下面以交通信號燈控制器作為狀態機范例演示一下:(該圖來自《嵌入式系統設計與實踐》作者:Elecia White)
功能需求很簡單:紅燈停,綠燈行,黃燈等。

狀態機一共有三個組成部分:狀態、事件、動作;
以上范例中,可如下拆解:
狀態:紅燈狀態RedState、綠燈狀態GreenState、黃燈狀態YellowState;
事件:停止命令StopCmd、前進命令GoCmd、超時TimeOut;
動作:切換到紅燈RedLedOn、切換到綠燈GreenLedOn、切換到黃燈YellowLedOn;
在實際編程中,以不同的部分為中心,可以有不同的軟件實現方法:
以狀態為中心的狀態機
我是誰->發生了什么事->我要干什么
每個狀態負責自己的行為和遷移邏輯。基本上一個case對應一個狀態,可讀性比較強,是最常用的風格。
但可擴展性一般,如果狀態事件太多,那處理函數很容易膨脹,所以不太適用于事件多的場景。
void TrafficLightSystemStateMachine(event_e event)
{
static state_e current_state = RedState;
switch (current_state) {
case RedState:
if (event == GoCmd) {
GreenLedOn();
current_state = GreenState;
}
break;
case GreenState:
if (event == TimeOut) {
YellowLedOn();
current_state = YellowState;
}
break;
case YellowState:
if (event == TimeOut) {
RedLedOn();
current_state = RedState;
}
break;
default:
break;
}
}
以隱式狀態為中心的狀態機
同樣是以狀態為中心,不過上面那種是顯式的。區別就在于把遷移邏輯隱藏到狀態函數內部里。
隱藏之后的好處是可以做到模塊化處理,每個狀態函數獨立封裝,易于單元測試。
(獨立的好處是可以不用太管他人,那壞處就是無法太管得了他人。)
state_e red_state(void)
{
if (received_event == GoCmd) {
GreenLedOn();
return GreenState;
}
return RedState;
}
state_e green_state(void)
{
if (received_event == TimeOut) {
YellowLedOn();
return YellowState;
} else if (received_event == StopCmd) {
RedLedOn();
return RedState;
}
return GreenState;
}
state_e yellow_state(void)
{
if (received_event == TimeOut) {
RedLedOn();
return RedState;
}
return YellowState;
}
state_e (*state_functions[])(void) = {
red_state, green_state, yellow_state
};
void TrafficLightSystemStateMachine(void)
{
while (1) {
current_state = state_functions[current_state]();
}
}
以事件為中心的狀態機
發生了什么事->我是誰->我要干什么
比較適用于事件驅動場景,尤其是事件頻繁,來源復雜的情況。
void TrafficLightSystemStateMachine(event_e event)
{
static state_e current_state = RedState;
switch (event) {
case StopCmd:
if (current_state != RedState) {
RedLedOn();
current_state = RedState;
}
break;
case GoCmd:
if (current_state == RedState) {
GreenLedOn();
current_state = GreenState;
}
break;
case TimeOut:
if (current_state == GreenState) {
YellowLedOn();
current_state = YellowState;
} else if (current_state == YellowState) {
RedLedOn();
current_state = RedState;
}
break;
default:
break;
}
}
以動作為中心的狀態機
現在是什么情況->我要干什么
以狀態+事件判斷來決定動作和下一個狀態。
把遷移邏輯集中在表里,抽象程度高,可擴展性和可移植性強。
很適合狀態 + 事件組合很多的場景
typedef struct {
state_e current;
event_e event;
state_e next;
void (*action)(void);
} fsm_entry_t;
fsm_entry_t fsm_table[] = {
{ RedState, GoCmd, GreenState, GreenLedOn },
{ GreenState, TimeOut, YellowState, YellowLedOn },
{ YellowState, TimeOut, RedState, RedLedOn },
{ GreenState, StopCmd, RedState, RedLedOn },
};
void TrafficLightSystemStateMachine(event_e event)
{
for (int i = 0; i < sizeof(fsm_table)/sizeof(fsm_table[0]); i++) {
if (fsm_table[i].current == current_state && fsm_table[i].event == event) {
current_state = fsm_table[i].next;
fsm_table[i].action();
break;
}
}
}
總結:
哪個部分多就適合以哪個部分作為中心。狀態多適合以狀態為中心,事件多適合以事件為中心,狀態和事件都多適合以動作為中心。在實踐過程中,其實重要的不是實現方式,而是狀態機的設計思想。但是狀態機代碼往往不利于維護和評審,所以提供狀態機圖等文檔是十分必要的。
練習:
大家可以以電子表作為練習,寫在評論區里交流一下,功能需求如下:
1.有液晶顯示屏顯示年月日時分秒;
2.有4個按鍵:
a.LIGH鍵:夜光功能,按下后表燈會亮起,提供夜晚或昏暗環境下的時間顯示。
b.MODE鍵:功能鍵,用于選擇需要調節的鬧鐘時間等。
c.START鍵/加鍵:秒表鍵,用于開始、停止和繼續計時/數字加。
d.RESET鍵/減鍵:用于歸零計時功能/數字減。
3.可以實現顯示時間、調節時間、秒表、設置鬧鐘、倒計時功能。

浙公網安備 33010602011771號