摘 要:在數據通訊軟件中,上位機(PC機)數據通訊協議棧的編寫工作通常由Switch/Case分支語句來完成。分支語句是一種簡單初級的邏輯表達式,因此不易做到模塊化,擴充性能和可移植性能都不理想,因而引入了狀態機技術。通過將通訊協議棧抽象為有序的不同狀態和行為,再嵌入到C++類中,可模擬狀態機實現此通訊處理,在Visual C++6.0平臺上運行可靠,穩定性好,并且狀態行為具有可繼承性。
關鍵詞:FSM;數據采集;協議棧;分支語句
中圖分類號:TP311.52 文獻標識碼:B
文章編號: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
在計算機數字通訊中經常要對通訊數據進行打包、解包和校驗等多種順序操作,相應的也要求協議軟件能對接收的數據依次進行相關處理,從而表現出數字通訊中的多階段性特征,為了滿足分階段處理通訊數據的需要,引進了狀態機技術。由于狀態機能以模塊化支撐協議,因而在數據通訊協議棧處理技術方面扮演著越來越重要的角色。
1 狀態機簡介
狀態機(有限狀態機)是一種具有離散輸入輸出系統的模型。任何時刻他都處于一個特定的狀態,狀態的轉換依賴于系統所接受的事件。當在某狀態下有事件發生時,系統會根據輸入的事件和當前的狀態做出反映,從而決定如何處理該事件以及是否轉換到下一狀態[1]。
狀態的觸發事件通常由外部信號來完成,當有效的觸發事件發生時,便進入下一狀態(當然也可以不發生狀態轉移),同時完成本狀態的具體任務,直到所有狀態完成,再回到初始狀態。當某一狀態出現異常時,也返回初始狀態,等待下一觸發事件的出現,如此反復循環。
狀態機通常有兩種表達方式:狀態表和狀態圖。
在圖1所示簡單狀態圖中,當狀態機處于狀態1時,完成動作1;事件1可以觸發狀態機跳轉到狀態2,以執行動作2;同樣,事件2也可以觸發狀態2的轉移,從而又回到狀態1。圖1所示狀態和轉移都很簡單,真正的狀態機比他要復雜的多。
圖1 狀態事件圖
對應圖1的狀態表如表1所示。其中,括號內為當前狀態動作和下一狀態,空網格表示在此狀態下,此事件無效,即不能觸發狀態的轉移,當然也不執行任何動作。
表1狀態事件表
2 狀態機的實現
在高級編程語言(如C,C++)中狀態機的典型實現主要有:
嵌套的Switch語句;
狀態表;
面向對象狀態設計模式。
其他技術幾乎是前面3種方式的組合[2]。
Switch/Case語句是一種內聯性很強、病態耦合的編程技術,是一種簡單初級的邏輯表達式,因此不易做到模塊化、靈活的可擴充性和可移植性,至于魯棒性就更差了。現代軟件技術講究松耦合、可移植、可快速擴充,并要求安全可靠,而狀態表的實現正是基于這一思想而發展的。當要在狀態機中增加新的狀態與控制邏輯,只需在狀態表中修改即可,甚至可以動態修改,C++的指針完美地支持這一點。在狀態變化不是很復雜的情況下,這是一種非??扇〉姆椒ā?/p>
3 數據采集系統PC端數據通訊協議棧的狀態機實現
在筆者所編寫的土壤滲流實驗數據采集系統PC端軟件中,很好的利用了狀態機技術來完成數據通訊協議棧的處理。此實驗數據的采集由下位機(單片機)和上位機(PC機)共同來完成。對于PC端軟件來說,正確的數據接收是后續工作的前提和保障。因此,有必要對接收到的每一幀消息進行檢測和校驗,即進行數據的預處理。只有符合通訊協議的數據才進行后續處理,不符合通訊協議的數據將丟棄掉,并且通知下位機數據出錯。下面將結合筆者參與開發的數據采集系統,論述狀態機技術在PC端軟件中的編程應用:
3.1 上下位機軟件的串行通訊協議
3.1.1 PDU協議
PDU數據單元分為兩類:指令類PDU和數據類PDU。指令PDU用于在通訊設備之間傳輸控制信息和狀態信息;數據PDU用于在通訊設備之間傳輸采集數據。本協議PDU格式如下:
功能代碼子功能代碼(可選)參數數據
3.1.2 物理層(PHY)
物理層接口的連接部件為D型9針連接器,其電平定義為標準RS 232電平標準,上下位機通過RS 232串口實現通訊。
3.1.3 邏輯鏈路子層(LLC)
邏輯鏈路子層的主要工作就是提供幀處理服務與差錯控制服務。
幀處理服務分為兩種級別——數據幀服務和消息幀服務。數據幀服務控制數據幀的比特數,停止位數;消息幀服務提供消息幀頭和消息幀尾來封裝消息幀,以實現發送與接收的消息同步,正確界定消息幀。在本通訊協議中采用消息幀,用ASCII字符“:”標記消息幀頭,以連續的ASCII字符CR+LR標記消息幀尾。
LLC層還提供消息幀差錯控制服務:在本協議中采用LRC校驗算法。通訊時,發送方按LRC算法生成LRC校驗碼,按照高位在前低位在后的順序附加在原始的消息幀尾部,最后以“:”和CR+LF對消息進行封裝。接收方按LRC算法將接收到的LRC校驗碼與在本地運算得到的校驗碼進行對比,從而判斷接收到的數據的正確性。
3.1.4 ADU格式
上位機(PC機)與下位機通訊協議的ADU格式如下:
3.2 狀態表的制定
按照本協議,上位機對接收到的每一幀消息進行校驗。其中功能代碼,數據,LRC碼在一幀消息中處于一定的先后位置,有相應的動作(如保存,校核等)與之對應,可以視為不同的狀態,另外,消息幀頭、幀尾等也視為不同的狀態。而對于每一個接收的字符,可以視其為觸發信號。如當接收的數據幀中出現數字時,視其為DATA_SIG信號,當接收的數據幀中出現回車(CR)字符時,視其為CR_SIG信號等,從而通過不斷接收字符來觸發狀態的行為和轉移,使狀態機循環工作下去。在分析數據通訊協議的基礎上,通過抽象,可將每幀消息分為如表2所示幾個狀態及觸發信號。
在表2中頂行列出了信號(觸發或事件),最左邊一列是狀態。各網格的內容是轉換,他表示為{動作,下一狀態}。例如,在BEGIN狀態中,當接收到COLON_SIG信號時,將會調用DoClear()這一函數,從而完成一些數據復位工作,同時,也觸發狀態到下一狀態——MAINCODE狀態。而對于其他觸發信號,狀態機狀態不變,仍在BEGIN狀態,且什么動作也不做。COLON_SIG信號對應數據幀的“:”字符,即當接收到“:”字符后,就向狀態機發出一COLON_SIG信號,使狀態機開始運行。而對接收的其他字符,在沒有收到“:”字符之前,協議要求不做處理。表中的其他動作解釋如下:
RecordType() //記錄功能代碼,指令類代碼或數據類代碼
DataAdd()//數據記錄
DoLRC() //完成LRC校驗工作
DoFinish()//數據檢驗完畢處理
ComRecord()//記錄子功能代碼
RecordState()//記錄數據通道狀態(正常或不正常)
DoError()//數據幀出錯報告
3.3 狀態表的實現
上位機軟件用Visual C++ 6.0 編寫,C++語言基于面向對象編程(OOP)思想,而狀態機的核心機制是行為繼承,這與OOP模式很相似,超狀態的行為能很容易地被子狀態所繼承。鑒于此,可以利用C++的類來表現狀態行為,即將狀態行為嵌入到C++類中,從而在Visual C++平臺上可以很好地運行狀態機。本狀態表的實現就是利用這一機制,父類封裝了抽象的狀態轉換和當前狀態行為,子類實現具體的狀態行為和狀態轉換。詳見如下核心代碼。
表2 數據通訊協議棧處理狀態表
父類StatusClass 類核心代碼如下:
// 定義成員函數指針
typedef void (StatusClass::* Action) ();
// 定義內層結構轉換
struct Translate
{
Action action; // 抽象行為動作
unsigned nextStatus; // 抽象的下一動作
};
// 狀態行為和轉換
void StatusClass::dispatch(unsigned const sig)
{
register Translateconst *t=myTable+ myState* myNsignals+sig;// 查狀態表
(this->*(t->action)) ();
myState=t->nextStatus;
}
// 無狀態行為的缺省動作
Void StatusClass::doNothing()
{
……
}
子類SubStatusClass 類核心代碼如下:
// 定義的信號
enum
Event{OTHER_SIG,C_SIG,D_SIG,DATA_SIG,CHAR_SIG,CR_SIG,LF_SIG,COLON_SIG,MAX_SIG};
//定義的狀態
enum
State{BEGIN,MAINCODE,DATA,SUBCODE,PARAM,MAX_STATE};
// 初始化狀態機
void SubStatusClass::init()
{
……
}
// 狀態表,包含實際的狀態行為和狀態轉換
(此狀態表對應于表2所示狀態和行為)
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 狀態類的調用
筆者已經將狀態行為封裝至C++類當中,所以在程序中調用時只須定義一個StatusClass類的對象即可運行狀態機了。同樣,隨著對象的銷毀,狀態機的生命周期也就結束。在本軟件中,數據的通訊協議棧處理是在單獨一個線程(預處理線程)中完成的,而此線程的觸發依賴于數據的接收。故當有數據幀接收時便會使預處理線程恢復,從而啟動狀態機,實現對數據幀的校驗。狀態類調用核心代碼如下:
DWORD WINAPI
CMycomFirDlg::ProtocalThread(LPVOID lpParameter )
{
……
// 創建狀態類對象
SubStatusClassClassmyStatus;
// 初始化狀態機
myStatus.init();
……
// 狀態機入口
myStatus.DealChar()
……
// 掛起預處理線程
SuspendThread(Protocal_handle);
}
4 結 語
實踐證明,將狀態機技術巧妙地運用于數據通訊協議棧處理程序中,收到了很好的效果,不但可以避免大量的Switch/Case語句,使程序簡潔,而且提高了程序的擴展性能,便于維護和修改。同時,由于狀態機的行為嵌入到了C++的類當中,可以視其為一個類,因而具有類的所有特征,可以被繼承,移植性能好,具有實用價值。
參考文獻
[1]魏先民.有限狀態機在嵌入式軟件中的應用[J].濰坊學報,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.
作者簡介 張臘明 男,1982年出生,湖南長沙人,大連理工大學機械學院碩士研究生。主要研究方向為機電控制。
注:本文中所涉及到的圖表、注解、公式等內容請以PDF格式閱讀原文。