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