摘 要:在數(shù)據(jù)通訊軟件中,上位機(jī)(PC機(jī))數(shù)據(jù)通訊協(xié)議棧的編寫(xiě)工作通常由Switch/Case分支語(yǔ)句來(lái)完成。分支語(yǔ)句是一種簡(jiǎn)單初級(jí)的邏輯表達(dá)式,因此不易做到模塊化,擴(kuò)充性能和可移植性能都不理想,因而引入了狀態(tài)機(jī)技術(shù)。通過(guò)將通訊協(xié)議棧抽象為有序的不同狀態(tài)和行為,再嵌入到C++類(lèi)中,可模擬狀態(tài)機(jī)實(shí)現(xiàn)此通訊處理,在Visual C++6.0平臺(tái)上運(yùn)行可靠,穩(wěn)定性好,并且狀態(tài)行為具有可繼承性。
關(guān)鍵詞:FSM;數(shù)據(jù)采集;協(xié)議棧;分支語(yǔ)句
中圖分類(lèi)號(hào):TP311.52 文獻(xiàn)標(biāo)識(shí)碼:B
文章編號(hào):1004373X(2008)0314603
FSM Methodology and Its Application in Communication Protocol Stack
ZHANG Laming,TONG Yu
(School of Mechanical Engineering,Dalian University of Technology,Dalian,116024,China)
Abstract:Switch/Case syntax is the traditional method to program the communication protocol stack in the software of data communication.However,Switch/Case syntax as a simple logical expression neither have expansibility and transplantable performance nor can be modularization.A new method based on FSM methodology is introduced in this paper.Dividing communication protocol stack into a lot of states and actions,which is embedded into the C++ class and can be inherited by other classes,making the software work reliably and stability on Visual C++ platform.
Keywords:FSM;data acquisition;protocol stack;syntax
在計(jì)算機(jī)數(shù)字通訊中經(jīng)常要對(duì)通訊數(shù)據(jù)進(jìn)行打包、解包和校驗(yàn)等多種順序操作,相應(yīng)的也要求協(xié)議軟件能對(duì)接收的數(shù)據(jù)依次進(jìn)行相關(guān)處理,從而表現(xiàn)出數(shù)字通訊中的多階段性特征,為了滿(mǎn)足分階段處理通訊數(shù)據(jù)的需要,引進(jìn)了狀態(tài)機(jī)技術(shù)。由于狀態(tài)機(jī)能以模塊化支撐協(xié)議,因而在數(shù)據(jù)通訊協(xié)議棧處理技術(shù)方面扮演著越來(lái)越重要的角色。
1 狀態(tài)機(jī)簡(jiǎn)介
狀態(tài)機(jī)(有限狀態(tài)機(jī))是一種具有離散輸入輸出系統(tǒng)的模型。任何時(shí)刻他都處于一個(gè)特定的狀態(tài),狀態(tài)的轉(zhuǎn)換依賴(lài)于系統(tǒng)所接受的事件。當(dāng)在某狀態(tài)下有事件發(fā)生時(shí),系統(tǒng)會(huì)根據(jù)輸入的事件和當(dāng)前的狀態(tài)做出反映,從而決定如何處理該事件以及是否轉(zhuǎn)換到下一狀態(tài)[1]。
狀態(tài)的觸發(fā)事件通常由外部信號(hào)來(lái)完成,當(dāng)有效的觸發(fā)事件發(fā)生時(shí),便進(jìn)入下一狀態(tài)(當(dāng)然也可以不發(fā)生狀態(tài)轉(zhuǎn)移),同時(shí)完成本狀態(tài)的具體任務(wù),直到所有狀態(tài)完成,再回到初始狀態(tài)。當(dāng)某一狀態(tài)出現(xiàn)異常時(shí),也返回初始狀態(tài),等待下一觸發(fā)事件的出現(xiàn),如此反復(fù)循環(huán)。
狀態(tài)機(jī)通常有兩種表達(dá)方式:狀態(tài)表和狀態(tài)圖。
在圖1所示簡(jiǎn)單狀態(tài)圖中,當(dāng)狀態(tài)機(jī)處于狀態(tài)1時(shí),完成動(dòng)作1;事件1可以觸發(fā)狀態(tài)機(jī)跳轉(zhuǎn)到狀態(tài)2,以執(zhí)行動(dòng)作2;同樣,事件2也可以觸發(fā)狀態(tài)2的轉(zhuǎn)移,從而又回到狀態(tài)1。圖1所示狀態(tài)和轉(zhuǎn)移都很簡(jiǎn)單,真正的狀態(tài)機(jī)比他要復(fù)雜的多。
圖1 狀態(tài)事件圖
對(duì)應(yīng)圖1的狀態(tài)表如表1所示。其中,括號(hào)內(nèi)為當(dāng)前狀態(tài)動(dòng)作和下一狀態(tài),空網(wǎng)格表示在此狀態(tài)下,此事件無(wú)效,即不能觸發(fā)狀態(tài)的轉(zhuǎn)移,當(dāng)然也不執(zhí)行任何動(dòng)作。
表1狀態(tài)事件表
2 狀態(tài)機(jī)的實(shí)現(xiàn)
在高級(jí)編程語(yǔ)言(如C,C++)中狀態(tài)機(jī)的典型實(shí)現(xiàn)主要有:
嵌套的Switch語(yǔ)句;
狀態(tài)表;
面向?qū)ο鬆顟B(tài)設(shè)計(jì)模式。
其他技術(shù)幾乎是前面3種方式的組合[2]。
Switch/Case語(yǔ)句是一種內(nèi)聯(lián)性很強(qiáng)、病態(tài)耦合的編程技術(shù),是一種簡(jiǎn)單初級(jí)的邏輯表達(dá)式,因此不易做到模塊化、靈活的可擴(kuò)充性和可移植性,至于魯棒性就更差了?,F(xiàn)代軟件技術(shù)講究松耦合、可移植、可快速擴(kuò)充,并要求安全可靠,而狀態(tài)表的實(shí)現(xiàn)正是基于這一思想而發(fā)展的。當(dāng)要在狀態(tài)機(jī)中增加新的狀態(tài)與控制邏輯,只需在狀態(tài)表中修改即可,甚至可以動(dòng)態(tài)修改,C++的指針完美地支持這一點(diǎn)。在狀態(tài)變化不是很復(fù)雜的情況下,這是一種非??扇〉姆椒?。
3 數(shù)據(jù)采集系統(tǒng)PC端數(shù)據(jù)通訊協(xié)議棧的狀態(tài)機(jī)實(shí)現(xiàn)
在筆者所編寫(xiě)的土壤滲流實(shí)驗(yàn)數(shù)據(jù)采集系統(tǒng)PC端軟件中,很好的利用了狀態(tài)機(jī)技術(shù)來(lái)完成數(shù)據(jù)通訊協(xié)議棧的處理。此實(shí)驗(yàn)數(shù)據(jù)的采集由下位機(jī)(單片機(jī))和上位機(jī)(PC機(jī))共同來(lái)完成。對(duì)于PC端軟件來(lái)說(shuō),正確的數(shù)據(jù)接收是后續(xù)工作的前提和保障。因此,有必要對(duì)接收到的每一幀消息進(jìn)行檢測(cè)和校驗(yàn),即進(jìn)行數(shù)據(jù)的預(yù)處理。只有符合通訊協(xié)議的數(shù)據(jù)才進(jìn)行后續(xù)處理,不符合通訊協(xié)議的數(shù)據(jù)將丟棄掉,并且通知下位機(jī)數(shù)據(jù)出錯(cuò)。下面將結(jié)合筆者參與開(kāi)發(fā)的數(shù)據(jù)采集系統(tǒng),論述狀態(tài)機(jī)技術(shù)在PC端軟件中的編程應(yīng)用:
3.1 上下位機(jī)軟件的串行通訊協(xié)議
3.1.1 PDU協(xié)議
PDU數(shù)據(jù)單元分為兩類(lèi):指令類(lèi)PDU和數(shù)據(jù)類(lèi)PDU。指令PDU用于在通訊設(shè)備之間傳輸控制信息和狀態(tài)信息;數(shù)據(jù)PDU用于在通訊設(shè)備之間傳輸采集數(shù)據(jù)。本協(xié)議PDU格式如下:
功能代碼子功能代碼(可選)參數(shù)數(shù)據(jù)
3.1.2 物理層(PHY)
物理層接口的連接部件為D型9針連接器,其電平定義為標(biāo)準(zhǔn)RS 232電平標(biāo)準(zhǔn),上下位機(jī)通過(guò)RS 232串口實(shí)現(xiàn)通訊。
3.1.3 邏輯鏈路子層(LLC)
邏輯鏈路子層的主要工作就是提供幀處理服務(wù)與差錯(cuò)控制服務(wù)。
幀處理服務(wù)分為兩種級(jí)別——數(shù)據(jù)幀服務(wù)和消息幀服務(wù)。數(shù)據(jù)幀服務(wù)控制數(shù)據(jù)幀的比特?cái)?shù),停止位數(shù);消息幀服務(wù)提供消息幀頭和消息幀尾來(lái)封裝消息幀,以實(shí)現(xiàn)發(fā)送與接收的消息同步,正確界定消息幀。在本通訊協(xié)議中采用消息幀,用ASCII字符“:”標(biāo)記消息幀頭,以連續(xù)的ASCII字符CR+LR標(biāo)記消息幀尾。
LLC層還提供消息幀差錯(cuò)控制服務(wù):在本協(xié)議中采用LRC校驗(yàn)算法。通訊時(shí),發(fā)送方按LRC算法生成LRC校驗(yàn)碼,按照高位在前低位在后的順序附加在原始的消息幀尾部,最后以“:”和CR+LF對(duì)消息進(jìn)行封裝。接收方按LRC算法將接收到的LRC校驗(yàn)碼與在本地運(yùn)算得到的校驗(yàn)碼進(jìn)行對(duì)比,從而判斷接收到的數(shù)據(jù)的正確性。
3.1.4 ADU格式
上位機(jī)(PC機(jī))與下位機(jī)通訊協(xié)議的ADU格式如下:
3.2 狀態(tài)表的制定
按照本協(xié)議,上位機(jī)對(duì)接收到的每一幀消息進(jìn)行校驗(yàn)。其中功能代碼,數(shù)據(jù),LRC碼在一幀消息中處于一定的先后位置,有相應(yīng)的動(dòng)作(如保存,校核等)與之對(duì)應(yīng),可以視為不同的狀態(tài),另外,消息幀頭、幀尾等也視為不同的狀態(tài)。而對(duì)于每一個(gè)接收的字符,可以視其為觸發(fā)信號(hào)。如當(dāng)接收的數(shù)據(jù)幀中出現(xiàn)數(shù)字時(shí),視其為DATA_SIG信號(hào),當(dāng)接收的數(shù)據(jù)幀中出現(xiàn)回車(chē)(CR)字符時(shí),視其為CR_SIG信號(hào)等,從而通過(guò)不斷接收字符來(lái)觸發(fā)狀態(tài)的行為和轉(zhuǎn)移,使?fàn)顟B(tài)機(jī)循環(huán)工作下去。在分析數(shù)據(jù)通訊協(xié)議的基礎(chǔ)上,通過(guò)抽象,可將每幀消息分為如表2所示幾個(gè)狀態(tài)及觸發(fā)信號(hào)。
在表2中頂行列出了信號(hào)(觸發(fā)或事件),最左邊一列是狀態(tài)。各網(wǎng)格的內(nèi)容是轉(zhuǎn)換,他表示為{動(dòng)作,下一狀態(tài)}。例如,在BEGIN狀態(tài)中,當(dāng)接收到COLON_SIG信號(hào)時(shí),將會(huì)調(diào)用DoClear()這一函數(shù),從而完成一些數(shù)據(jù)復(fù)位工作,同時(shí),也觸發(fā)狀態(tài)到下一狀態(tài)——MAINCODE狀態(tài)。而對(duì)于其他觸發(fā)信號(hào),狀態(tài)機(jī)狀態(tài)不變,仍在BEGIN狀態(tài),且什么動(dòng)作也不做。COLON_SIG信號(hào)對(duì)應(yīng)數(shù)據(jù)幀的“:”字符,即當(dāng)接收到“:”字符后,就向狀態(tài)機(jī)發(fā)出一COLON_SIG信號(hào),使?fàn)顟B(tài)機(jī)開(kāi)始運(yùn)行。而對(duì)接收的其他字符,在沒(méi)有收到“:”字符之前,協(xié)議要求不做處理。表中的其他動(dòng)作解釋如下:
RecordType() //記錄功能代碼,指令類(lèi)代碼或數(shù)據(jù)類(lèi)代碼
DataAdd()//數(shù)據(jù)記錄
DoLRC() //完成LRC校驗(yàn)工作
DoFinish()//數(shù)據(jù)檢驗(yàn)完畢處理
ComRecord()//記錄子功能代碼
RecordState()//記錄數(shù)據(jù)通道狀態(tài)(正?;虿徽#?/p>
DoError()//數(shù)據(jù)幀出錯(cuò)報(bào)告
3.3 狀態(tài)表的實(shí)現(xiàn)
上位機(jī)軟件用Visual C++ 6.0 編寫(xiě),C++語(yǔ)言基于面向?qū)ο缶幊蹋∣OP)思想,而狀態(tài)機(jī)的核心機(jī)制是行為繼承,這與OOP模式很相似,超狀態(tài)的行為能很容易地被子狀態(tài)所繼承。鑒于此,可以利用C++的類(lèi)來(lái)表現(xiàn)狀態(tài)行為,即將狀態(tài)行為嵌入到C++類(lèi)中,從而在Visual C++平臺(tái)上可以很好地運(yùn)行狀態(tài)機(jī)。本狀態(tài)表的實(shí)現(xiàn)就是利用這一機(jī)制,父類(lèi)封裝了抽象的狀態(tài)轉(zhuǎn)換和當(dāng)前狀態(tài)行為,子類(lèi)實(shí)現(xiàn)具體的狀態(tài)行為和狀態(tài)轉(zhuǎn)換。詳見(jiàn)如下核心代碼。
表2 數(shù)據(jù)通訊協(xié)議棧處理狀態(tài)表
父類(lèi)StatusClass 類(lèi)核心代碼如下:
// 定義成員函數(shù)指針
typedef void (StatusClass::* Action) ();
// 定義內(nèi)層結(jié)構(gòu)轉(zhuǎn)換
struct Translate
{
Action action; // 抽象行為動(dòng)作
unsigned nextStatus; // 抽象的下一動(dòng)作
};
// 狀態(tài)行為和轉(zhuǎn)換
void StatusClass::dispatch(unsigned const sig)
{
register Translateconst *t=myTable+ myState* myNsignals+sig;// 查狀態(tài)表
(this->*(t->action)) ();
myState=t->nextStatus;
}
// 無(wú)狀態(tài)行為的缺省動(dòng)作
Void StatusClass::doNothing()
{
……
}
子類(lèi)SubStatusClass 類(lèi)核心代碼如下:
// 定義的信號(hào)
enum
Event{OTHER_SIG,C_SIG,D_SIG,DATA_SIG,CHAR_SIG,CR_SIG,LF_SIG,COLON_SIG,MAX_SIG};
//定義的狀態(tài)
enum
State{BEGIN,MAINCODE,DATA,SUBCODE,PARAM,MAX_STATE};
// 初始化狀態(tài)機(jī)
void SubStatusClass::init()
{
……
}
// 狀態(tài)表,包含實(shí)際的狀態(tài)行為和狀態(tài)轉(zhuǎn)換
(此狀態(tài)表對(duì)應(yīng)于表2所示狀態(tài)和行為)
StatusClass::Tran const SubStatusClassClass::myTable[MAX_STATE][MAX_SIG]= {
{{StatusClass::doNothing,BEGIN},{StatusClass::doNothing,BEGIN},{StatusClass::doNothing,BEGIN},{StatusClass::doNothing,BEGIN},
{StatusClass::doNothing,BEGIN},{StatusClass::doNothing,BEGIN},{StatusClass::doNothing,BEGIN},
{static_cast
……
};
3.4 狀態(tài)類(lèi)的調(diào)用
筆者已經(jīng)將狀態(tài)行為封裝至C++類(lèi)當(dāng)中,所以在程序中調(diào)用時(shí)只須定義一個(gè)StatusClass類(lèi)的對(duì)象即可運(yùn)行狀態(tài)機(jī)了。同樣,隨著對(duì)象的銷(xiāo)毀,狀態(tài)機(jī)的生命周期也就結(jié)束。在本軟件中,數(shù)據(jù)的通訊協(xié)議棧處理是在單獨(dú)一個(gè)線(xiàn)程(預(yù)處理線(xiàn)程)中完成的,而此線(xiàn)程的觸發(fā)依賴(lài)于數(shù)據(jù)的接收。故當(dāng)有數(shù)據(jù)幀接收時(shí)便會(huì)使預(yù)處理線(xiàn)程恢復(fù),從而啟動(dòng)狀態(tài)機(jī),實(shí)現(xiàn)對(duì)數(shù)據(jù)幀的校驗(yàn)。狀態(tài)類(lèi)調(diào)用核心代碼如下:
DWORD WINAPI
CMycomFirDlg::ProtocalThread(LPVOID lpParameter )
{
……
// 創(chuàng)建狀態(tài)類(lèi)對(duì)象
SubStatusClassClassmyStatus;
// 初始化狀態(tài)機(jī)
myStatus.init();
……
// 狀態(tài)機(jī)入口
myStatus.DealChar()
……
// 掛起預(yù)處理線(xiàn)程
SuspendThread(Protocal_handle);
}
4 結(jié) 語(yǔ)
實(shí)踐證明,將狀態(tài)機(jī)技術(shù)巧妙地運(yùn)用于數(shù)據(jù)通訊協(xié)議棧處理程序中,收到了很好的效果,不但可以避免大量的Switch/Case語(yǔ)句,使程序簡(jiǎn)潔,而且提高了程序的擴(kuò)展性能,便于維護(hù)和修改。同時(shí),由于狀態(tài)機(jī)的行為嵌入到了C++的類(lèi)當(dāng)中,可以視其為一個(gè)類(lèi),因而具有類(lèi)的所有特征,可以被繼承,移植性能好,具有實(shí)用價(jià)值。
參考文獻(xiàn)
[1]魏先民.有限狀態(tài)機(jī)在嵌入式軟件中的應(yīng)用[J].濰坊學(xué)報(bào),2007,6(4):24—25.
[2]Samek Mico.Practical Statecharts in C/C++——Quantem Programming for Embedded Systems[M].USA:CMP Books (CMP Media LLC),2002.
作者簡(jiǎn)介 張臘明 男,1982年出生,湖南長(zhǎng)沙人,大連理工大學(xué)機(jī)械學(xué)院碩士研究生。主要研究方向?yàn)闄C(jī)電控制。
注:本文中所涉及到的圖表、注解、公式等內(nèi)容請(qǐng)以PDF格式閱讀原文。