歡迎您光臨本站 登入註冊首頁

一種基於C51的多任務機制及應用

admin @ 2014-03-25 , reply:0

概述

 傳統的單片機程序一般採用單任務機制,單任務系統具有簡單直觀、易於控制的優點。然而由於程序只能按順序依次執行,缺乏靈活性,只能使用中斷函數實時地處理一些較短的任務,在較複雜的應用中使用極為不便。嵌入式……

 傳統的單片機程序一般採用單任務機制,單任務系統具有簡單直觀、易於控制的優點。然而由於程序只能按順序依次執行,缺乏靈活性,只能使用中斷函數實時地處理一些較短的任務,在較複雜的應用中使用極為不便。嵌入式多任務操作系統的出現解決了這個問題。在多任務系統中,可以同時執行多個并行任務,任務之間可以相互跳轉。但是嵌入式操作系統在提供強大功能的同時,也帶來了代碼量大,結構複雜、對硬體要求較高、開發難度大且成本高等問題。而很多時候只需要實現簡單的多任務操作就可以滿足實際需要,本文設計的這種簡單的多任務機制,在只增加極少量c語言代碼的前提下,不需使用彙編,無需對原本的程序進行大改動,就可以實現多任務操作。 
 實時操作系統RTOS的核心是中斷,利用中斷進行任務切換。在大部分RTOS如uC/OS-II中,每個任務都有自己的堆棧,用來保存任務的一些信息,任務之間通過信號量、郵箱、消息隊列等傳遞信息。在很多情況下並不需要這些功能,只需要使單片機在接收到控制信號后,切換到不同的工作狀態,也就是只要進行任務切換,不需要保存任務的相關信息。捨棄這些複雜的功能可以使程序結構變得簡潔易用。 

兩種機制在應用實例中的比較  
 下面用一個應用實例來說明本設計的思路。要設計一個智能安防系統,它的功能包括:當有人人侵時執行報警工作:用戶可以通過鍵盤板進行功能設置,主板能與管理中心進行通訊,當發生火災、地震等災情時,管理中心能通知用戶。其結構如圖1所示。平時狀態下,主板的CPU不斷地掃描各個感測器的狀態。當檢測到感測器的異常信號(有人闖入)時,CPU進入入侵報警狀態,執行響警鈴、撥打戶主電話、通知管理中心等工作。當發生火災地震時,管理中心發送一個串口代碼給主板CPU,使CPU進入災難報警狀態,執行響警鈴,語音報警等操作。用戶需要進行功能設置時可以通過鍵盤板使主板CPU進入功能設置狀態。因此主板的CPU有4種不同的工作狀態。
 
 如果採用單任務機制,主板的程序流程如圖2所示。在主函數中循環檢測感測器狀態,如有異常則調用報警函數。災難報警和功能設置在串口中斷中完成。這種單任務結構有兩個缺點。首先,在各種非平時狀態中,程序需要不停地檢測是否收到撤除信號,這個要求在程序代碼量大、執行工作較多的情況下很難實現。其次,各狀態之間的切換十分困難,用C語言寫的程序為求模塊化,一般函數數量較多,函數調用的嵌套層數也多,要從一個較深的嵌套立刻跳出到主函數,是非常困難的。一般的解決方法或是使用C51的庫函數setjmp()和longjmp()實現長跳轉,但是這兩個函數在中斷函數內部是無能為力的;再或是在C函數中嵌入彙編指令,雖然用彙編指令可以實現程序的長距離跳轉,但是這種方法的調試過程十分煩瑣,而且程序的可移植性差。對於習慣用C51編程而不想用彙編的設計者,該部分程序是一個難題。
 

實現多任務機制的程序結構
    本文提供了一種方法,可以在完全不使用彙編指令的前提下實現可移植性強的多任務程序,程序流程如圖3所示。實現這個多任務機制的完整源代碼如下:
wordidataPC_Value,SP_Value;//儲存中斷返回點、SP初值的全局變數
byteidataCtrl_Code;//控制任務切換的全局變數,在中斷函數里被賦值
voidmain()
{
Initial();//初始化函數,與程序結構無關
SP_Value=SP;//獲取SP的初始值
PC_Value=Get_Next_PC();//獲取下一條指令的地址
EA=1;//獲取PC、SP初值后再開中斷保證穩定性
if(Ctrl_Code!=0)
SP=SP_Value;//重置堆棧指針,防止堆棧溢出
switch(Ctrl_Code)
//任務入口地址,即中斷的返回點
{
case1:goto
TASK1;
case2:goto
TASK2;
case3:goto
TASK3;
default:break;
}
TASK1:for(;;)
{//任務1代碼}
TASK2:for(;;)
{//任務2代碼}
TASK3:for(;;)
{//任務2代碼}
}
word Get_Next_PC(void)
//獲取下一條
指令的地址
{
wordaddress;
address=*((unsignedchar*)SP);
//PC的高位元組
address<<=8;
address+=*((unsigned char
*)(SP-1));//PC的低位元組
returnaddress+4;
//查看反彙編代碼,計算所得
}
voidChuan_Kou_Interrupt(void)
interrupt4using0
{
bytea1,a2;
a1=a1*a2;
*((unsigned char*)(SP-
5))=PC_Value>>8;
*((unsigned char*)(SP-
6))=PC_Value&0x00ff;
{
//接收串口代碼並根據代碼修
改Ctrl_Code的值
//其他操作
}
}
 

任務調度原理與實現
 程序的整體思路是在主函數main中依次放置幾個死循環作為任務框架,即每個任務都是一個死循環,利用中斷進行任務切換。以剛才所說的安防系統為例.由於主板,鍵盤、管理中心之間是通過串口通訊的,因此串口是用來觸發任務切換的理想中斷源。程序為所有任務設置一個總人口並放在主函數中,串口中斷每次返回時必須先經過這個總人口,在總人口處檢查任務控制變數(全局變數)的值,任務控制變數已在串口中斷中被賦值,其值決定要切換到哪個任務。
 設計中可以把平時狀態、入侵報警狀態,危機報警狀態、功能設置狀態分別作為任務1、任務2、任務3、任務4。主板CPU平常工作在平時狀態,即任務1,當串口收到管理中心的危機代碼,在串口中斷函數中令Ctrl_Code:3,中斷返回後會切換到任務3:同樣,接收到鍵盤的功能設置代碼后,會切換到任務4,由於入侵檢測是由主板CPU自己負責,因此如果檢測到有人人侵需要切換到入侵報警狀態時,可以藉由鍵盤中轉產生串口中斷,即向鍵盤發送一串口數據並要求鍵盤迴送。這樣就實現了各個狀態的切換。
 實現任務調度需要解決3個關鍵問題:

  1. 獲取任務入口點的程序地址。由於使用c語言不能直接獲取和修改程序計數器PC的值,而在調用函數時會將PC值人棧,利用這個特點在任務人口處之前調用Get Next_PC函數即可從堆棧中獲得人口地址。Get Next_PC中,SP為堆棧指針,得到的PC值要加4才是任務入口地址,因為查看反彙編窗口可知,將函數返回值傳給全局變數PC_Value需要兩條2位元組長的mov指令。
  2. 修改中斷返回地址。修改中斷返回地址的操作與獲取PC值類似,都是通過修改堆棧中的內容實現。但是由於編譯器自身的特點,在進入中斷時,編譯器除了把返回地址人棧外,還會計算自身及它所調用的函數對寄存器ACC、B、DPH、DPL,PSW、R0一R7的改變,並將它認為被改變了的寄存器也人棧保護。如果堆棧結構會隨中斷函數內容改變而變化,就沒辦法計算中斷返回地址堆棧中的位置。解決方法是,在中斷函數定義時加上關鍵字usmgo告訴編譯器中斷函數及其調用的函數將使用寄存器組O,這樣工作寄存器R0 R7將不會被保存。ACC、PSW、DPH、DPL在對PC_Value操作時已經用到,在中斷函數開頭定義兩個變數a1、b1並令它們相乘,使B寄存器也被入棧,這樣堆棧的結構就是固定的了。  
  3. 防止堆棧溢出。由於在調用函數時編譯器會將當前地址人棧,返回時再出棧,當任務切換即中斷多次發生在函數調用過程中時,堆棧會因為只人不出而最終導致溢出。這是不能容許的。因此,應在主函數開頭初始化后立刻將S P值保存,再在每次任務切換后都將sP恢復為初值,這可以有效防止堆棧溢出。

結語   
 根據以上的比較與分析可以看出這種實現多任務機制的方法具有如下優點:與採用單任務機制的程序相比,其結構簡單清晰,易於控制,利用中斷和堆棧實現任務切換時的長跳轉,完全不需使用彙編語言,可移植性強,增加的代碼量極小,實時性好,節省程序開發時間。
 以上介紹的方法已經通過測試並應用於幾個實際項目中,包括智能小區安防系統、汽車CAN匯流排控制系統等,取得了良好效果。只要根據具體的硬體與編譯環境稍作修改,就可應用於其他的單片機系統中。


[admin via 研發互助社區 ] 一種基於C51的多任務機制及應用已經有2600次圍觀

http://cocdig.com/docs/show-post-42187.html