硬件集成電路設計熱門培訓內容之單片機多任務的實現方式

時間:2018-03-23 10:26:42

由于單片機具有價格低、運行要求低、易于開發、穩定可靠等優點,廣泛應用于儀器儀表、家用電器、醫用設備、航空航天、專用設備的智能化管理及過程控制等領域。但是,單片機的位數少、頻率低、內存小、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所示。


    
    由圖1可以看出,在倒數第二個步驟中,單片機會把棧頂的兩個字節彈出給PC,由此來改變PC值,進而來改變程序的執行流程。所以,我們可以在出棧彈出字節給PC前改變棧頂的兩個字節的內容,進而主動改變PC值。
    有了主動改變PC值的能力,我們就可以將這個中斷設為定時器中斷,每隔一段時間來切換PC值,進而實現多任務運行。
3 具體實現代碼及注意事項
3.1 進入主循環前的工作
    根據上面的思路和技巧,進入主循環前的工作流程如圖2所示。

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

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


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

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

? 江苏快3号码表