基於輪詢和狀態機實現的類似協程的微控制器程式架構
本文思考在不使用作業系統的情況下,如何簡單的構建多工微控制器程式結構(裸機開發)。本文圍繞含有數碼管顯示和按鍵檢測兩個任務的微控制器程式展開說明輪詢、狀態機和協程思想的引入及解決的問題。
1、輪詢
首先看一段輪詢實現的虛擬碼。主函式依次迴圈呼叫連個任務,顯示任務函式執行一次顯示所有位,按鍵任務函式執行一次檢測所有按鍵,延時函式通過阻塞處理器實現。
1 /*輪詢法實現*/ 2 3 /*阻塞延時,延時完成才返回*/ 4 void Delay (int Time) 5 { 6 while (Time--); 7 } 8 9 /*掃描式數碼管顯示,執行一次顯示所有位,數碼管至少兩位*/ 10 void Display_Task (void) 11 { 12 Display_1 (...); /*1位顯示*/ 13 Delay (...); /*延時一段時間*/ 14 15 Display_2 (...); /*2位顯示*/ 16 Delay (...); 17 } 18 19 /*按鍵檢測,執行一次掃描所有按鍵*/ 20 void Key_San_Task (...) 21 { 22 if (Key1 == 0){ /*一次檢測*/ 23 Delay (...); /*延時*/ 24 if(Key2 == 0){ /*二次檢測*/ 25 Mark_Key1 (...); /*標記按鍵時間*/ 26 } 27 } 28 29 if (Key1 == 0){ /*一次檢測*/ 30 Delay (...); /*延時*/ 31 if(Key2 == 0){ /*二次檢測*/ 32 Mark_Key1 (...); /*標記按鍵時間*/ 33 }34 } 35 } 36 37 /*主函式*/ 38 void main (void) 39 { 40 while(1){ 41 Key_San_Task (...); 42 Display_Task (...); 43 } 44 }
對於任務少且簡單的情況,這樣實現問題也不大。任務複雜是,可能需要在很多的地方使用延時函式,這樣單個任務的執行時間就會嚴重的加長,而且大部分時間在阻塞,效率低。
2、狀態機
將延時函式分為開始延時和延時結束並使用狀態機可以解決上述問題,提高程式效率。
在系統中設計一個時鐘,隨著自然時間的推進而推進,不受主程式影響,解析度至少是毫秒級,一般通過定時器中斷更新一個全域性變數實現。延時函式不在使用阻塞方式,而是通過記錄開始時刻和判斷當前時刻距開始時刻的時長實現延時。每個任務通過if語句分割為多個部分,通過各個全域性變數儲存斷點資訊(狀態機),執行那個任務片段由斷點資訊決定。看下面一段使用狀態機的虛擬碼。
1 /*狀態機實現*/ 2 3 /*記錄當前時刻*/ 4 int Current_Moment (void) 5 { 6 return Moment; /* Moment為當前時刻*/ 7 } 8 9 /*計算當前時刻距開始時刻的時長*/ 10 int Timing (int M) 11 { 12 return Moment - M; /* M為Current_Moment記錄的開始時刻 */ 13 } 14 15 /*掃描式數碼管顯示,執行一次顯示所有位,數碼管至少兩位*/ 16 int Display_Begin; /*開始時刻*/ 17 void Display_Task (int *Node, ...) 18 { 19 if (0 == *Node){ /*任務片段0, 初始化*/ 20 Display_Begin = Current_Moment (); /*記錄開始時刻*/ 21 *Node = 1; 22 else if (1 == *Node){ /*任務片段2,顯示1位*/ 23 if(Timing (Display_Begin) > 20){ /*距開始時刻夠久*/ 24 Display_1 (...); /*1位顯示*/ 25 Display_Begin = Current_Moment (); /*記錄開始時刻*/ 26 *Node = 2; 27 } 28 else if (2 == *Node){ /*任務片段2,顯示2位*/ 29 if(Timing (Display_Begin) > 20){ 30 Display_2 (...); /*2位顯示*/ 31 Display_Begin = Current_Moment (); /*記錄開始時刻*/ 32 *Node = 1; 33 } 34 } 35 } 36 37 /*按鍵檢測,執行一次掃描所有按鍵*/ 38 int Key_Start; 39 int Key1_Flag=0; 40 int Key2_Flag=0; 41 void Key_San_Task (int *Node, ...) 42 { 43 if (0 == *Node){ /*任務片段0,初始化*/ 44 45 Key_Start = Current_Moment (); 46 *Node = 1; 47 48 }else if(1 == *Node){ /*任務片段1,固定事件檢測按鍵*/ 49 50 if(Timing (Key_Start) > 20){ 51 if(Key1 == 0){ 52 if(0 == Key1_Flag){ 53 Key1_Flag = 1; 54 }else{ 55 Mark_Key1 (...); 56 } 57 } 58 59 if(Key2 == 0){ 60 if(0 == Key2_Flag){ 61 Key2_Flag = 1; 62 }else{ 63 Mark_Key2 (...); 64 } 65 } 66 } 67 } 68 } 69 70 /*主函式*/ 71 void main (void) 72 { 73 int Display_Node =0; 74 int Key_Node = 0; 75 76 while(1){ 77 Key_San_Task (&Key_Node, ...); 78 Display_Task (&Display_Node, ...); 79 } 80 }
這樣實現,有協程的樣子,但程式碼亂得多。
3、協程
協程也需要主動讓出處理器,但可以做到在任意位置讓出,也能在讓出的位置恢復。協程實現任意位置讓出和恢復就需要操作棧,看起來就不那麼人畜無害了。或者使用goto實現,goto看起來也不那麼人畜無害。已有大牛開發出了可以在微控制器上執行的協程庫。
4、目標
協程使用起來方便,但需要對底層操作,對於不同微控制器不能夠不加修改移植,而且體量還是相對較大,不能控制得隨心所欲。所以,把輪詢和狀態機結合起來,再加入一些多工的操作或特性,做得易於使用,程式碼清晰,加入優先順序,加入任務通訊等,就是本文的目標。以協程原理為指導,實現一個基於輪詢和狀態機的微控制器程式架構。