4 計時器設計模式

8051 的計時器有三個,TIMER 0.1.2。
計時器0負責計算時間,每隔一段時間產生一次中斷,並將所有時間計數器值做倒數遞減。
可用於其他驅動程式或應用程式的時間計數,或慢速脈波寬度調變(PWM)。 時間間隔可以是 250 微秒,1 毫秒,10 毫秒。端視實際情況的需要。
筆者以 250 微秒做計時範例。這種做法很耗費 CPU 運算資源,只要系統負擔不大且運作順暢,使用這種做法無彷。

計時器1用於紅外線的訊號計時器,紅外線訊號的0與1是靠訊號的長短區隔。配合 IRDA.C 實現紅外線驅動程式。

計時器2用於串列埠的鮑率產生器,用來產生 9600~115200 的鮑率。其中 57600 和 115200 誤差值太大,訊號接受不穩,不常使用。


4.1 計時器標頭檔

1.定義符號 TIMER_H,避免標頭檔的重複引入。
2.定義旗號常數 TIMNER_FLAG_TIMER1EVENT,用來紀錄計時器 1 的中斷事件。
3.宣告結構原型 TIMER_CONTROL,這是計時器控制結構的原型。
4.引出計時器控制結構 TimerCtrl。
5.引出計時器函式庫,之後所有引入標頭檔的程式檔都可以連結並呼叫計時器函式庫。

01 #ifndef TIMER_H
01 #define TIMER_H
01 #define TIMNER_FLAG_TIMER1EVENT 0x01
01 typedef struct{
01   U8 Timer;
01   U8 Timer1ms;
01   U8 Timer10ms;
01   U8 Timer100ms;
01   U8 Flag;
01 }TIMER_CONTROL;
01 extern TIMER_CONTROL data TimerCtrl;
01 extern void TimerInit(void);
01 extern void TimerTimer1Set(U16 TimerValue);
01 #endif


4.2 計時器函式庫

計時器函式庫。

函式名稱說明
TimerInit計時器初始化函式。
TimerTimer0Isr計時器 0 中斷處理函式。
TimerTimer1Isr計時器 1 中斷處理函式。
TimerTimer2Isr計時器 2 中斷處理函式。
TimerTimer1Set計時器 1 設定函式。


4.2.1 計時器初始化函式

設定計時器驅動程式變數。
1.清除驅動程式計時器,這是用來計算等待時間。
2.清除驅動程式旗號,這是用來紀錄計時器的事件。目前只有 WRITER 計算 ATMEL 的稍寫時間時會用到。

01 void TimerInit(void){      
01   TimerCtrl.Timer=0;       
01   TimerCtrl.Flag=0;        

設定計時器 0。
1.設定模式為 16 位元計時器。
2.將計時值寫入計時器,計時器的時脈是系統時脈 11.0592MHZ 除以 12,為了產生 1 豪秒的中斷,計時值為 921。
3.設定 TR0,啟動計數動作。
4.設定 ET0,致能計數器 0 中斷。當中斷發生時,程式會跳躍到計時器 0 中斷向量,執行中斷副程式。

01   TMOD|=0x01;              
01   TH0=(65536-921)/256;    
01   TL0=(65536-921)%256;    
01   TR0=1;                   
01   ET0=1;                   

設定計時器 1。
1.設定模式為 16 位元計時器。
2.將計時值 0 寫入計時器。
3.清除 TR1,停止計數動作。紅外線驅動程式會啟動計時器 1。
4.清除 ET1,除能計數器 1 中斷。

01   TMOD|=0x10;              
01   TL1=0;                   
01   TH1=0;                   
01   TR1=0;                   
01   ET1=0;                   

設定計時器 2。
1.設定 RCLK,表示計時器2為鮑率產生器。
1.設定 TCLK,表示計時器2為鮑率產生器。
2.使用條件編譯的方式,設定不同鮑率的計數值。RCAP2L 和 RCAP2H 的值會在溢位後重新載入 TL2、TH2,計算下一次鮑率發生的時間。
3.設定 TR2,啟動鮑率產生器。
4.清除 ET2,除能計數器 2 中斷。

01   RCLK=1;
01   TCLK=1;
01   RCAP2L=BAUDRATE_L;     
01   RCAP2H=BAUDRATE_H;     
01   TL2=BAUDRATE_L;        
01   TH2=BAUDRATE_H;        
01   TR2=1;                                                                 
01   //ET2=1;              
01 }


4.2.2 計時器 0 中斷處理函式

當計時器 0 發生溢位,發生中斷,8051 的程式計數器會跳躍到中斷向量 1,0003H 的位址上執行中斷副程式。
計時器 0 中斷處理函式宣告時必須加上 interrupt 1,表明這是要安裝到中斷向量 1 的副程式。
計時器中斷是用 250us 做為中斷時間間隔。
在使用 12MHZ 的標準 8051,12 CLOCK 執行一個指令,250 微秒中大約有 25 微秒是固定要消耗在計時器中斷,因為進出中斷要做暫存器堆疊(PUSH)與彈出(POP),包括 PC、PSW、DPTR、R0~R7。
雖然這會導致計時器中斷頻繁而消耗運算資源,但系統的工作量本來就不大,所以還可以承受。
計時器處理步驟是
1.停止計時器計數。
2.設定計數器值,這是下一次計時器中斷的時間間格。
3.將所有程式的計數器以 1 毫秒、10 毫秒、100 毫秒做倒數遞減,直到 0 為止。
4.啟動計時器計數。

01 void TimerTimer0Isr(void) interrupt 1{
01   TR0=0;                                   
01   TH0=(65536-230)/256;                     
01   TL0=(65536-230)%256;                     
01   TimerCtrl.Timer1ms--;
01   if(TimerCtrl.Timer1ms==0){
01     TimerCtrl.Timer1ms=4;                                
01     TimerCtrl.Timer10ms--;    
01     //handle 1ms base timer here...max 250ms
01     if(TimerCtrl.Timer!=0) TimerCtrl.Timer--;
01     if(System.State!=SystemMonitorState){
01       TR0=1; 
01       return; //system is not bootup complete,no handle other timers.
01     }
01     if(StepMotorCtrl.Timer!=0) StepMotorCtrl.Timer--;
01     if(LedCtrl.Timer!=0) LedCtrl.Timer--;
01     if(RtcCtrl.Timer!=0) RtcCtrl.Timer--;
01     if(LcmCtrl.Timer!=0) LcmCtrl.Timer--;
01     if(Ps2Ctrl.Timer!=0) Ps2Ctrl.Timer--;
01     if(Ps2TimerAsm!=0) Ps2TimerAsm--;
01     #if DEBUG_ENABLE==1
01       if(ShellCtrl.Timer!=0) ShellCtrl.Timer--;
01     #endif
01     #if SDCP_ENABLE==1
01       if(SdcpCtrl.Timer!=0) SdcpCtrl.Timer--;
01     #endif
01     if(TimerCtrl.Timer10ms==0){
01       TimerCtrl.Timer10ms=10;
01       TimerCtrl.Timer100ms--;    
01       //handle 10ms base timer here...max 2.5s
01       if(TimerCtrl.Timer100ms==0){
01         TimerCtrl.Timer100ms=10;
01         //handle 10ms base timer here...max 2.5s
01       }
01     }   
01   }
01   TR0=1;
01 }


4.2.3 計時器 1 中斷處理函式

當計時器 1 發生溢位,發生中斷,8051 的程式計數器會跳躍到中斷向量 3,0013H 的位址上執行中斷副程式。
計時器 1 中斷服務函式宣告時必須加上 interrupt 3,表明這是要安裝到中斷向量 3 的副程式。
執行步驟是
1.關閉計時器。計時器的內容值會被紅外線驅動程式取用,用來判斷訊號的 0 與 1。
2.設定時間管理器的計時器 1 事件旗號,表示發生計時器 1 中斷。
2.清除紅外線控制器的訊號偵測旗號,表示訊號長度太長,約 65MS,故忽略不紀錄之。

01 void TimerTimer1Isr(void) interrupt 3 {
01   TR1=0;                                   
01   ET1=0;                                      
01   TimerCtrl.Flag|=TIMNER_FLAG_TIMER1EVENT; 
01   IrdaCtrl.Status&=~cIrdaSignalDetect;     
01 }


4.2.4 計時器 2 中斷處理函式

當計時器 2 發生溢位,發生中斷,8051 的程式計數器會跳躍到中斷向量 5,0023H 的位址上執行中斷副程式。
計時器 2 中斷服務函式宣告時必須加上 interrupt 5,表明這是要安裝到中斷向量 5 的副程式。
因為沒有使用到這個中斷,所以僅清除中斷旗號。事實上,這個中斷函式不會被執行。

01 void TimerTimer2Isr(void) interrupt 5 {
01   TF2=0;
01 }


4.2.5 計時器 1 設定函式

使用計時器 1 計時一段小時間,使用震盪器頻率為 11.0592 MHZ 時的時脈時間單位是 12/11.0592 us。
當中斷發生時,設定 TimerCtrl.Flag 的 TIMNER_FLAG_TIMER1EVENT 旗號。

目前只有在燒寫 ATMEL AT89S52 時會被 WRITER 用到,用於計算燒寫一個位元組的時間 400us。
這個函式會和紅外線接受器驅動程式衝突,但考慮到他們不會同時動作,所以就這樣寫了。
也可以把 WRITER 的時間延遲用 FOR LOOP 處理,這樣就沒有衝突的問題了。

01 void TimerTimer1Set(U16 TimerValue){
01   TimerCtrl.Flag&=~TIMNER_FLAG_TIMER1EVENT;
01   TH1=(65536-TimerValue)/256;
01   TL1=(65536-TimerValue)%256;
01   TR1=1;                     
01   ET1=1;                     
01 }