使用狀態(tài)機編程嵌入式系統
大多數嵌入式系統本質上是被動的。他們用傳感器測量環(huán)境的某些特性,并對變化作出反應。例如,它們顯示某些東西,移動一個馬達,或向另一個系統發(fā)送通知。一個反應系統最好由一個狀態(tài)機來表示--一個系統總是在一個有限的和定義明確的可能狀態(tài)集中。
手動編程有限狀態(tài)機可以成為壓倒性的任務,并產生錯綜復雜且難以維護的結果。圖形化設計工具幫助您跟蹤系統的所有可能狀態(tài)和動作。本文將為您介紹一個狀態(tài)機編程,重點是圖形化設計工具。此外,您將學習如何將生成的平臺無關代碼與自定義硬件專用代碼集成,以便與硬件進行交互--在本例中,這是一個Arduno板。
狀態(tài)機是開發(fā)反應系統的理想范例。這些反應系統最重要的特點是,它們使用傳感器和執(zhí)行器與環(huán)境相互作用。傳感器的例子是運動、亮度或溫度傳感器。常見的執(zhí)行機構包括LED、顯示器、閥門和電動機。這些系統的另一個重要特點是,它們有一個有限的可能狀態(tài)集,而且它們總是在其中之一,使用狀態(tài)機可以很容易地實現。
對于具有實際意義的狀態(tài)機來說,可能最簡單的例子是光開關控制,如圖1所示。不出所料,兩個進程中只有一個進程可以同時活動。當一個所謂的進程 過渡 已經帶走了。在這個例子中,這種情況發(fā)生在 按按鈕的 事件就會發(fā)生。
圖1光開關控制的兩種狀態(tài)和過渡.(資料來源:項目組)
畫出你的系統的所有狀態(tài)可以幫助你提前計劃,并清楚地看到你的系統在不同情況下的預期行為。然后,您可以使用該圖表作為藍圖,以基礎源代碼和測試。然而,如果以后改變代碼,就像通常情況下的情況一樣,而圖表沒有改變,則兩者都有分歧。如果有人試圖根據現在已經過時的圖表開發(fā)測試,那么他們就會失敗。如果模型僅僅用于規(guī)范或文檔,它就會成為一個巨大的問題。因此,圖表不應該只是代碼的藍圖,它應該是 成為 密碼。
如果您已經繪制了圖表,為什么要自己編寫代碼?所有需要的邏輯已經在圖表中指定.將圖表轉換為等效的源代碼,比如java或c,只是一個機械任務,可以由機器執(zhí)行。使用圖表作為唯一的真相源并自動生成代碼,被稱為模型驅動方法。然而,要利用這一原則,簡單的繪圖板是做不到的。
相反,你應該使用適當的建模工具繪制狀態(tài)圖(狀態(tài)圖)。用這種工具創(chuàng)建的圖表很容易掌握。它們改善了軟件開發(fā)人員和領域專家之間的溝通。此外,與紙張上的圖表或繪圖應用程序中的圖表不同,建模工具對狀態(tài)機是什么有正式的理解。這使他們(和你)能夠模擬和測試他們的行為--甚至不編寫一行代碼。模型本身是獨立于平臺的,因此您可以從它們生成任何您喜歡的語言中的源代碼。工具通常支持C,C++,Java,和比頓。
如果您仍然不確定模型驅動的軟件開發(fā)是如何工作的,請不要擔心--我們現在將通過實例來探索它。我們將使用標桿和代碼生成來開發(fā)一個非常簡單的自動化光,只需要一些輸入和輸出。
我們的例子:自動和動作激活燈
自動照明的任務相當簡單:只有在黑暗的時候才應該有光,但是它不應該浪費能量,而實際上沒有人在周圍。為了實現這一點,大多數樓梯燈都是由定時器控制的。按下按鈕,燈就會被激活,在一段時間后,它會自動關閉。然而,作為一個狀態(tài)示例,這將是相當乏味的,因此本文通過加入一個由運動傳感器驅動的附加模式,使其更加有趣。
光線應該有三種可能的操作方式:
· 永久地離開
· 有時間控制的關閉
· 自動帶有運動傳感器
一個按鈕允許用戶循環(huán)這些模式.兩個LED顯示當前選定的操作模式。
通過這些規(guī)范,您可以很容易地推導出狀態(tài)機的基本結構,如圖2所示:
圖2自動和動作激活燈。
在這兩者之間的變化 離開 , 計時器 和 自動動作 進程是由 事件-或者按下按鈕,或者在計時器過期后。如果用戶按一次按鈕,計時器模式被激活,燈就會打開,30秒后自動關閉。如果用戶在30秒運行完畢前再次按下按鈕,則激活運動傳感器模式。當運動傳感器檢測到某人(或某物)在移動時,如果需要的話,燈就會被打開30秒。每次檢測到某一動作時,計時器都會重置.表明當前模式的兩個發(fā)光二極管在進入或離開各自的狀態(tài)時按需要被激活和停用。這樣,整個控制器邏輯完全封裝在狀態(tài)機中,也稱為自動機。
如果自動機是要運行在一個嵌入式系統,我們現在可以直接從圖表生成C或C++代碼。生成的代碼包含來自模型的所有邏輯。只需要手動編寫與實際硬件接口的代碼。在這個例子中,這包括提高 按鈕 當實際按鈕按下時,控制實際的樓梯燈,控制狀態(tài)LED。這種手動編程是需要的,因為生成的代碼與目標平臺無關。計時器也是如此--在不同的目標平臺上,時間處理方式非常不同。
實現狀態(tài)機有許多可能的方法.最常使用的方法是狀態(tài)表、基于開關的案例構造或狀態(tài)模式--通常在面向對象編程語言中使用。如果你想更深入地了解這個話題,你可以找到一個廣泛的比較.在默認情況下,雅辛杜狀態(tài)工具使用開關案例語句生成狀態(tài)機代碼。這確保了良好的性能,同時也保持了源代碼的良好可讀性。
生成的代碼是如何工作的
如上所述,狀態(tài)機代碼是作為開關案例語句實現的。執(zhí)行的主要部分將在 循環(huán)車 職能:
void Lightswitch::runCycle()
{
clearOutEvents();
for (stateConfVectorPosition = 0;
stateConfVectorPosition < maxOrthogonalStates;
stateConfVectorPosition++)
{
switch (stateConfVector[stateConfVectorPosition])
{
case lightswitch_Off :
{
lightswitch_Off_react(true);
break;
}
case lightswitch_Timer :
{
lightswitch_Timer_react(true);
break;
}
case lightswitch_Motion_Automatic_motion_Motion :
{
lightswitch_Motion_Automatic_motion_Motion_react(true);
break;
}
case lightswitch_Motion_Automatic_motion_No_Motion :
{
lightswitch_Motion_Automatic_motion_No_Motion_react(true);
break;
}
default:
break;
}
}
clearInEvents();
}
… 循環(huán)車 每當出現事件時,就會調用功能.它迭代所有正交的狀態(tài)來做任何要做的事情。開關案例語句決定調用哪個函數來執(zhí)行相應的狀態(tài)反應。例如,離開狀態(tài)有一個輸入反應,將輕變量設置為假,只在進入狀態(tài)時執(zhí)行。它有一個向外和一個向外過渡。如果 按鈕 事件發(fā)生后,進程將退出。這種行為在 lightswitch_Off_react 職能:
sc_boolean Lightswitch::lightswitch_Off_react(const sc_boolean try_transition) {
/* The reactions of state Off. */
sc_boolean did_transition = try_transition;
if (try_transition)
{
if (iface.button_raised)
{
exseq_lightswitch_Off();
enseq_lightswitch_Timer_default();
react();
} else
{
did_transition = false;
}
}
if ((did_transition) == (false))
{
did_transition = react();
}
return did_transition;
}
所以,假設說退出狀態(tài)已經進入了。每次 循環(huán)車 函數被調用,它必須檢查按鈕事件是否被提升。在 lightswitch_Off_react 職能。如果 按鈕 事件的確發(fā)生了,必須做兩件事: 出口 當前狀態(tài)的順序和執(zhí)行 加入 目標狀態(tài)的順序:
if (iface.button_raised)
{
exseq_lightswitch_Off();
enseq_lightswitch_Timer_default();
react();
}
關于一個Arduino聯合國組織的實施
圖3Arduino原理圖。
圖3顯示了一個ArduinoUNO實現的示意圖。實際的樓梯燈是象征著機上的LED,以保持電路簡單。這兩個顯示模式的發(fā)光二極管連接到針9和10,運動傳感器到針7。如果需要,這些密碼可以更改。按鈕必須連接到銷2或銷3,因為只有這些才能觸發(fā)中斷。LED系列的電阻為220歐,按鈕連接到22kc拉下電阻。
該軟件由兩個核心組件組成:由狀態(tài)生成的C++代碼和用于連接非平板獨立狀態(tài)機邏輯和硬件的手寫膠水代碼。
代碼生成器根據模型中定義的事件和變量創(chuàng)建狀態(tài)機的接口:空升-按鈕();空升-運動();SCOOLOL-光();SCOST;SCOL-運動();CXOOL-運動();SXOOL-內();球形進入();
對于接口狀態(tài)機,必須定義特定狀態(tài)機類型的對象,這里:光開關。這個對象代表實際的狀態(tài)機,可以用編程方式與實際的狀態(tài)機進行交互。例如:
光開關;
Lightswitch lightswitch;
int main(){
lightswitch.init();
lightswitch.enter();
lightswitch.raise_button();
}
有了這個簡單的實現,光開關狀態(tài)機將被初始化,輸入,按鈕事件將被提升。當然,這不是辦法。目標是連接硬件(在這種情況下,阿爾杜伊諾與連接的LED,傳感器和按鈕)到狀態(tài)機。為此,我們將在一個非常簡單的輸入過程輸出模式中使用狀態(tài)機。這是一個簡單的循環(huán):
· 檢查硬件和傳感器是否有變化
· 把這些信息轉移到進程機器的輸入中
· 讓狀態(tài)機處理這些輸入
· 檢查狀態(tài)機的輸出并對其作出反應。
最初,計時器用當前時間刷新。在阿杜伊諾號上,我們使用 米利斯 函數,以獲得系統啟動以來所經過的毫秒數。如果需要,計時器將觸發(fā)狀態(tài)機中的時間事件。
long now = millis();
if(now - time_ms > 0) {
timerInterface->proceed(now - time_ms);
time_ms = millis();
}
基于其他輸入,如按鈕按或運動檢測,我們可以提高狀態(tài)機的"在"事件。這里,我們不必擔心狀態(tài)機當前的模式--生成的狀態(tài)機代碼封裝了所有的邏輯。我們只是提出事件,然后讓進程機器決定它是否要對它作出反應。
// handle button press from ISR
if(buttonPressed) {
lightswitch.raise_button();
buttonPressed = false;
}
// read out motion sensor
if(digitalRead(7)) {
lightswitch.raise_motion();
}
在處理了所有"內"事件之后,狀態(tài)機已經正確地設置了布爾變量。我們可以用它們來控制"樓梯燈"和指示燈。
// set light
digitalWrite(13, lightswitch.get_light());
// set mode LEDs
digitalWrite(9, lightswitch.get_led_timer());
digitalWrite(10, lightswitch.get_led_motion());
最后,我們將把阿杜伊諾放到睡眠模式中,如果它是在 離開 為了省點體力。如果用戶按下這個按鈕,它的中斷服務例程將被調用,并且Arduno再次醒來。請注意更新 米利斯 在睡眠狀態(tài)下沒有更新。依靠軟件計時器 米利斯 因此在睡眠狀態(tài)下不會更新。在這個例子中,沒有計時器運行 離開 狀態(tài)是活躍的,所以我們可以安然入睡。
// if in Off-state, go to sleep (wake up by ISR)
if(lightswitch.isStateActive(Lightswitch::lightswitch_Off)) {
enterSleep();
}
閃爍的阿杜伊諾是完成了通常的阿杜伊諾。為此,我們導入了將狀態(tài)機作為庫的項目,并只手動編寫上面的ArduinnoIDI中所示的與Arduino相關的代碼。
結論
這個例子清楚地顯示了在軟件開發(fā)中使用模型的優(yōu)點,比如說使用標桿。主要優(yōu)勢是:
· 狀態(tài)機是正式的,可以執(zhí)行.
· 進程記錄是圖形化的,易于理解。
· 設備的執(zhí)行邏輯和相關硬件相關代碼完全脫鉤。
· 解除硬件和設備邏輯的連接提高了可移植性,減少了更改或進一步版本所需的努力。
· 它們可以分開開發(fā)。
這個例子可以擴展,并提供一個完美的游樂場進行狀態(tài)機的實驗。