許多嵌入式應用領域,軟件都是基於輸入響應的組織方式,也叫反應式系統。把輸入 信息進行歸類有:離散的事件(如二值開關信號)、可以表示某個外部信號引發的中斷或者例如發生了定時器溢出等。而數值信號則用於傳遞例如一次A/D采樣的結果。有限狀態機正是利用了這些輸入的事件做為狀態變更的依據,每一種狀態對應執行一組操作。
(個人觀點)所以這種方式最好是執行在一個由中斷建立起來的硬件環境。例如鍵盤的輸入是與中斷相結和的,所以會有周立公的ZLG7290芯片(當然7290並不是因為這樣才產生的)。這樣就可充分利用這種機制而不必頻繁的執行掃描。
不過由於需要根據輸入的事件做相應的狀態轉換,使利用狀態機進行軟件的設計和編碼帶來的額外的負擔。系統的復雜程度直接影響了設計人員對軟件的理解和組織。借助有效的狀態機設計工具可以降低設計人員的負擔。
根據多任務操作系統的分類有協作型(也叫任務輪循),一個任務一直運行,直到該任主動放棄CPU,調度器安排另外一個任務運行。相應的在沒有調度器的情況下,我們可以把對狀態機的調用過程依次安排在一個超循環里。
While( 1 ) { //調用狀態機1,處理事件1。 StateMachine_NO_1( ); //調用狀態機2,處理事件2。 StateMachine_NO_1( ); 。。。。。。。。。。。。。。。。。。。 //狀態機n StateMachine_NO_n( ); }
各狀態機在設計的時候必須保證不會出現死循環,長時間等待某一事件。例如以往的前后台方式等待定時器溢出使用的方法是不斷查詢標志位直到溢出為止,此時應該改為首先打開定時器,然后切換狀態進入查詢是否溢出,未溢出立即退出,轉而執行其它狀態機。待下一次執行到該狀態機時由於狀態仍處在查詢定時溢出的狀態之下,如果此時查詢結果是定時器溢出將切換到另一種狀態執行相應操作。
StateMachine_NO_1(void ) { Static uint8 sm_no1_state = 0; //用於存放狀態機當前狀態的值 。。。。。。。。。。。。。。。。。。。。。。。。。; //其它變量。 If( sm_no1_state = = 0){//狀態機等於‘0’嗎? //執行操作,例如執行點亮一個LED 500mS 。。。。。。。。。。。。。。。。。。。。。。。。。。 //執行完打開LED操作后切換狀態進入查詢500ms時間到否 sm_no1_state = 1; //並打開定時器計數,500ms Star_Timer0_Delay_Ms(500); } If( sm_no1_state = = 1){ //狀態機等於‘1’嗎? //判斷定時器是否溢出。 If( Rt_Timer0_full( ) = = 1){//每次只查詢一次 //定時器溢出,關閉定時器,關閉LED 。。。。。。。。。。。。。。。。。。 //同時切換狀態 sm_no1_state = 2; //再次打開定時器 Star_Timer0_Delay_Ms(500); } else ;//定時器未溢出保持當前狀態退出,轉而執行其它狀態機。 } If( sm_no1_state = = 2){ //狀態機等於‘2’嗎? If( Rt_Timer0_full( ) = = 1){ //定時器溢出,關閉定時器 。。。。。。。。。。。。。。。。。。 //同時切換狀態 sm_no1_state = 0; } else ;//定時器未溢出保持當前狀態退出,轉而執行其它狀態機。 } Return ; //狀態機1執行返回主循環。 }
如上所示狀態機’1’執行了一個每隔1S點亮和關閉LED的任務。
類似的只要合理設計每個狀態機所等待和處理的事件,就可實現多任務的並發執行。解決的狀態機的原理后還需要考慮各狀態機之間的資源共享和同步問題。
除狀態機輪循這種簡單的利用等待事件發生的空閑時間處理並發執行外還有基於事件優先級等等組織方式。不過由於過於復雜,大家可以借助於UML實時系統建模生成的框架,設計一個並發執行的軟件只需要設計好狀態圖,當然實際實行起來UML過於抽象。
補充:
狀態機在執行狀態切換時要執行入口操作和退出操作。
相對於使用RTOS的優點是對硬件的資源消耗較少特別是SRAM,但各狀態機所使用的變量生存期需要根據實際好好斟酌。相比於前后台方式會有大量的靜態變量。動態分配的變量仍然只存在於可重入函數中作臨時審請的變量。
另外UML也支持RTOS,我想RTOS+狀態機的開發方式再復雜的項目也能輕松應對了。
由於表達能力的不足無法很好的詮釋狀態機的更多優點和特性。而其缺點和應該注意的地方我想大家在往后的應用中會有很好的體會。過於復雜的項目如果沒有掌握好狀態機的設計工具建議不要選擇這種方式,很可能會導致項目失敗。