單片機多任務的時間片方式實現 .

引言
    由於單片機具有價格低、運行要求低、易於開發、穩定可靠等優點,廣泛應用於儀器儀表、家用電器、醫用設備、航空航天、專用設備的智能化管理及過程控制等領域。但是,單片機的位數少、頻率低、內存小、I/O口少等缺點限制了其加載操作系統的可能。因此,單片機不能像ARM等較高性能的處理器一樣,利用加載的操作系統實現管理與配置內存、決定系統資源供需的優先次序、控制輸入與輸出設備、操作網絡與管理文件系統等功能。
    但是,我們可以根據單片機所擁有的內存大小、CPU頻率等因素,來爲單片機量身定做一個小型的操作系統,以實現單片機的多任務運行。

1 微機實現多任務的方式
    
微機實現多任務的方式一般是由加載的操作系統來實現的。通過操作系統提供的函數來創建多進程或者多線程來實現多任務方式。由於多進程耗費的資源多,而多線程的開銷相對小的多,因此我們採用單片機模仿多線程的方式來實現。
    操作系統創建多個線程後,將管理各個線程佔用CPU的時間。操作系統以輪換方式向線程提供CPU時間片,從而使多個線程看起來是同時運行的,而不是等待一個線程執行結束後再去執行下一個線程。
    PC(Program Counter,程序計數器)是用於存放下一條指令地址的地方。某個線程正在佔用CPU時間,其實是PC值指向該線程所佔的內存,並正在逐條取到CPU寄存器中進行運算。該時間片結束後,PC值要指向下一個線程所佔用的內存中,進行類似的運算。其他線程都輪流一遍後,將又回到原來那個線程暫停的位置繼續運算。所以,從一個線程轉換到另外一個線程去執行時,要保存此線程的“現場”,包括此線程下一條指令的位置(PC值)、此線程所使用的各個寄存器值等。當此線程又擁有CPU時間時,將保存的PC值賦給PC寄存器,保存的各個寄存器值再賦給各個寄存器。
    除了保存“現場”與恢復“現場”外,另外關鍵的一點是,操作系統能夠改變PC值——強制把使用CPU的權限從一個任務切換到另一個任務,這就用到了中斷。微機是用操作系統來管理中斷的,用戶只能間接使用中斷。

2 單片機實現多任務的思路
    
由上面的介紹,我們知道微機中多線程輪流佔用CPU時間,關鍵點在於:
    ①保存“現場”與恢復“現場”,即保存和恢復下一條指令的位置和通用寄存器的值。
    ②能夠改變PC值,從而可以在多個線程中進行切換,以便同時運行。
    在51系列單片機中,如何實現上面的兩個關鍵點呢?
    (1)保存此“現場”,恢復另一“現場”
    給每個任務開闢一個堆棧,各個任務的堆棧不能交叉。各個任務的對應堆棧用於實現以下功能:
    ①保存“現場”,在PC離開此任務前保存該任務所用到的通用寄存器值(寄存器A、B、Rn和位寄存器C等)。
    ②恢復“現場”,先獲得下一個任務的堆棧地址,然後取出堆棧中所保存的通用寄存器值;
    ③在調用子函數時,用以保存下一條指令的地址。
    (2)每隔一段時間片,改變PC值幾乎所有的處理器指令中,沒有可以直接改變PC值的指令,但是系統發生中斷時可以改變PC值,中斷流程如圖1所示。

a.JPG


    由圖1可以看出,在倒數第二個步驟中,單片機會把棧頂的兩個字節彈出給PC,由此來改變PC值,進而來改變程序的執行流程。所以,我們可以在出棧彈出字節給PC前改變棧頂的兩個字節的內容,進而主動改變PC值。
    有了主動改變PC值的能力,我們就可以將這個中斷設爲定時器中斷,每隔一段時間來切換PC值,進而實現多任務運行。


3 具體實現代碼及注意事項
3.1 進入主循環前的工作
    
根據上面的思路和技巧,進入主循環前的工作流程如圖2所示。

b.JPG


    圖2爲進入主循環前的初始化工作。假定有3個任務,3個任務分別爲Task1、Task2、Task3(這3個任務都應是死循環),如果開設每個堆棧大小爲16字節,3個任務對應的堆棧範圍爲40H~4FH、50H~5FH、60H~6FH,則初始各個任務地址到對應堆棧如下:
    c.JPG
    sp1、sp2、sp3爲定義的3個全局變量,用以存儲各個任務的棧頂地址。 

    初始化定時器後,要進入某個任務的死循環當中。假設我們要進入任務1中,則如下所示:
    d.JPG
    TaskIndex爲全局變量,用以存儲當前執行的任務序號;難點在於ret的妙用。ret一般用於子函數的最後一條,以回到調用函數前下一條指令的地址。ret的實質是取出此時堆棧中棧頂的兩個字節賦給PC寄存器,以返回調用函數前的位置。所以,上述代碼是先把任務1的地址放進堆棧中,然後調用ret來取出地址給PC,以重新跳到任務1中去執行。
3.2 多任務切換的主循環
    
進入某個任務進行死循環後,程序的主循環流程如圖3所示。當程序進入到某個任務進行死循環時,如上面的任務i,定時器中斷週期發生,發生時意味着該任務的時間片結束,準備執行下一個任務。這些準備工作是在中斷裏做的,如圖3所示。首先,應保存此時用到的各個寄存器值,以便下次輪到該任務時取出繼續執行,還要保存棧頂的位置,以便下次能取出所保存的值;然後通過全局變量TaskIndex取得下一個任務的序號,通過任務序號,得到下一個任務的堆棧棧頂的地址,賦給棧頂寄存器SP;然後通過SP取出保存的各個通用寄存器值;最後,重設定時器值,使中斷能夠再次進行任務切換。

e.JPG


    這裏重要的是整個思路,沒有比較難的代碼,故沒有貼出代碼。值得提醒的是,保存通用寄存器值時,並不需要保存所有的通用寄存器值,只需要保存任務中用到的就可以。這裏解釋前面程序中提及的45H、55H、65H:各個任務堆棧的開始處存儲各個任務的地址,然後再把要保護的寄存器值入棧,棧頂擡高;而要恢復下一個任務時,需將上次保護寄存器後的棧頂值賦給SP寄存器,然後逐個出棧賦值給各個寄存器值,直到棧底處存儲的上次任務暫停處的地址。因爲本文的驗證程序只保護了A、B、R0、R2 4個寄存器值,堆棧剛好到達45H、55H、65H。

總結
    
單片機實現多任務的另一種常用方式是把任務切成小片,然後放在主循環裏。這樣,每個循環執行一次各個任務的一小片,從而看起來所有的任務都同時進行。切片的思想是把一個任務細分成多個步驟,而每次只執行其中一小步。如多段數碼管的顯示可以每次只顯示一段,這是更常用的方式,但並不是每個任務都可以切片的。
    本文所講的這種實現單片機多任務的方式要求程序員要有比較好的彙編基礎,要求對中斷的實現過程比較熟悉,對ret指令的實質要理解,能夠根據任務來分配堆棧,對操作系統管理CPU時間片有大致理解,因此要求比較高。另一方面,時間片定多少需要程序員根據任務的不同來選擇,需要測試多次來達到性能的最優化。



from: http://www.eccn.com/tech_260_2013072410298862.htm
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章