概述
在程序設計中我們經常會用到延時,對于精度要求不高的應用來說我們一般采用插入語句等待的方式來實現,對于精度高的應用來說我們一般采用定時器中斷來實現。本章主要內容包括:
- 延時時間的調試
- 定時器功能介紹
- 定時器應用實例介紹
6.1延時調試
前面程序中用到的延時通過執行多條語句來實現的,那么我們會問執行一條語句需要多長時間,延時1ms又需要多少條語句,延時的精度高嗎?下面講解的內容將包含以上內容。眾所周知,晶振為單片機提供了時間基準,在晶振的節拍下程序按時間順序往下執行,在單片機應用中有三個重要的時間概念,分別為時鐘周期、機器周期和指令周期。
時鐘周期: 單片機晶振頻率的倒數,例如開發板的板載晶振為11.0592MHz,時鐘周期為1/11.0592us,是51單片機的最基本,最小的時間單位。
機器周期: 單片機完成一條最基本指令操作所需要的時間,51單片機的一個機器周期等于12個時鐘周期,RY-51開發板機器周期:12/11.0592us=1.09us。
指令周期: 執行一條指令所需的時間,一般由若干個機器周期組成。當然根據指令的不同所需的機器周期也不同,只需一個機器周期的簡單指令,稱之為單周期指令,包含兩個機器周期的指令稱之為雙周期指令。
以RY-51單片機開發板為例,執行一條最簡單的語句需要至少1.09us,復雜的語句需要多個1.09us來實現。因此,我們常常在程序中通過添加數目不等的簡單語句來實現延時,但是延時的精度往往不會太高,我們可以通過keil軟件的仿真功能來確定延時的大概數值。接下來講解延時函數仿真功能,我們以延時10ms為例進行介紹。
#include< reg52.h >
sbit led0 = P1^0;//LED小燈管腳定義
sbit FM = P2^4;//蜂鳴器管腳位定義
sbit Key17 = P3^0;//獨立按鍵管腳定義
void delayms(unsigned int z)//延時函數
{
unsigned int x,y;
for(x=z;x >0;x--)
for(y=70;y >0;y--);
}
void main()
{
while(1)
{
delayms(10);//延時10ms
delayms(10);//延時10ms
delayms(10);//延時10ms
}
}
首先,根據前面介紹的步驟建立一個工程,并編輯好如上所示的代碼。這個代碼和前面講解的代碼有點不同,我們先把延時的語句寫成一個函數delayms(),將其放在主程序之外。在主程序中調用函數來實現延時。改變延時函數的輸入參數便可改變延時語句的條數,因此延時變得非常的靈活。主程序中我們對延時函數進行了三次調用,接下來我們看看執行一次函數調用需要多長時間。
在keil軟件快捷按鈕菜單欄中找到配置,點擊進入如下圖所示的子菜單:
如上圖所示,在”Target”目錄先將仿真晶振設置為“11.0592”。
如圖所示,在“C51”目錄下將代碼優化等級設置為“0:Constant folding”。
設置好如上規則后,點擊菜單欄的debug快捷鍵如下圖所示,進入調試模式,可以看到程序進程已經執行到了第17條代碼。
接下來給17,18,19三條語句設置斷點,將鼠標移到語句處點擊右鍵選擇“insert/Remove Breakpoint”,設置好之后語句左邊會出現紅色的小方塊。當程序執行到斷點時,程序便會停止下來。
如圖6-5所示,當程序順序執行的黃色箭頭停留在第17行時,所使用的時間為:0.00042209s,如圖左側所示。
點擊如圖6-6左上角的程序執行快捷鍵,程序進程黃色箭頭停留在第18行,即執行完了一條延時函數語句delayms(10),觀察左下角的進程時間為:0.01057943s將執行語句前后時間相減,結果約等于10.1ms,因此與我們延遲10ms的要求相符。
通過調用函數實現延時的方法在單片機編程中是非常常見的,經過上面的延時調試可見這種方法的精度并不是很高,對應特殊應用場合我們一般采用定時器的方式實現。
6.2定時器介紹
定時器的功能很容易理解,就是到了某個指定的時間會提示設定者,我們平常使用的鬧鐘實際上可看作是一個定時器。定時器是單片機的重要資源,那么我們什么時候會使用到單片機的定時器功能呢?正常情況下我們一直在運行單片機的主程序,在主程序中假設我們需要1s之后去執行某個操作,這時我們只好在主程序中進行延時,直到1s時間到了再去執行相應的操作,那么在這個延時的過程中主程序別的事情干不了了,這樣就很浪費系統的資源。
如果我們這個時候使用定時器功能就可以很好的解決這個問題。首先,我們在主程序中設定定時器定時1s,并啟動定時器開始計時,因為定時器的運行和單片機的主程序執行是分開的,不會相互影響,因此主程序繼續往下執行。當1s的時間到了,定時器告訴主程序,這個時候主程序停下當前正在干的活而去響應定時器。
STC89C52系列單片機內置2個16位的定時器,同時也可以當作計數器來用,分別為定時器0(T0),定時器1(T1),每個定時器有4種工作方式。那么定時器是如何計時的呢?我們以定時器T0工作在16位模式下進行介紹。16位的二進制的最大值為:0b1111,1111,1111,1111=65535。首先,我們需要給這個16位的寄存器賦初始值,假設為1000,當我們在主程序中啟動定時器T0,此時這個寄存器會每12個時鐘周期從1000開始往上加1,直到加到65535,當再加1后,定時器T0就溢出了,寄存器值從65535變成0,當溢出后定時器會告訴主程序,定時時間到了。所以我們只要改變這個初始值1000就可以得到不同的定時時間了。細心的同學可能會發現,當初始值為0的時候可以定時的時間最長。那么我們來看看定時器T0工作在16位模式下最長能定時多久。我們開發采用的時鐘頻率為11.0592MHz,因此,每12個時鐘周期時間為:12/11.0592us=1.09us。啟動定時器后,從初始值0累加到65535再到溢出為0,總共累加了65536次。因此,最大定時長度為:1.09us*65536=71.4ms。
6.3定時器使用
前面介紹了51系列單片機中定時器的工作原理,這節將重介紹定時器的使用。前面介紹過單片機的某個功能的實現都有特殊功能寄存器SFR有關,當然定時器的使用也不例外,特殊功能寄存器列表如下表所示:
表6-1 定時器/計數器特殊功能寄存器列表
TCON是一個8位定時器/計數器中斷控制寄存器,可位尋址,即每一位可單獨賦值。B7、B6為定時器T1控制位,B5、B4為定時器T0控制位,如下表所示:
表6-2 TCON寄存器
TF0:定時器/計數器0中斷溢出標志位。T1被允許計數后,從初始值開始加1計數。當最高位產生溢出時由硬件置“1”TF0,向CPU請求中斷,一直保持到CPU響應中斷時,才由硬件清零“0”TF0,另外,TF0也可由程序查詢清零“0”。
TF1:定時器/計數器1中斷溢出標志位。功能與TF0類似。
TR0:定時器T0的運行控制位。該位由軟件置位和清零。當TR0=1時就允許T0開始計數。
TR1:定時器T1的運行控制位。該位由軟件置位和清零。當TR1=1時就允許T1開始計數。
其它位為外部中斷相關內容,與定時器功能無關,這里暫時不做介紹。
前面講的這幾個位是定時器中斷控制位,TR0賦值為1后,定時器T0開始運行,當定時器T0溢出時,單片機硬件會將TF0位置1。我們可以在程序中通過查詢這個位是否為1來確定定時是否到達。另外,我們如果設置了定時器中斷函數,當定時到達后,單片機程序會跳轉到定時器中斷函數,并且由硬件將TF0清零。
TCON是一個可位尋址的寄存器,在單片機程序中可以直接對TCON賦值,或者對其中的位進行直接賦值。
圖6-7 定時器模式寄存器
TMOD為定時器模式寄存器,寄存器的高4位為定時器1模式位,低四位為定時器0模式位,高低位的功能類似,下面以定時器0為例:
C/T:定時器、計數器功能選擇位。清零作為定時器,置1作為計數器。
表6-3 模式選擇
定時器/計數器功能原理圖如下圖所示:
圖6-8 定時器/計數器功能原理圖
如上圖所示,當作為定時器或計數器時唯一的區別為輸入的時鐘不一樣。當作為定時器時,輸入的時鐘為系統時鐘,而當作定時器時,系統輸入時鐘為外部引腳。
6.4定時器應用實例
本節我們介紹定時器的兩種應用實例進行介紹,第一種為程序查詢方式響應定時器的溢出,第二種為中斷函數處理的方式響應定時器溢出。應用定時器實現的功能為每隔50ms使led0小燈閃爍一次,并使用定時器T0,工作在模式1,即16位定時的模式下。
6.4.1程序查詢方式實例
定時器使用步驟:
- 配置定時器模式控制寄存器TMOD;
- 裝載定時器初始值TH0,T0;
- 置位TR0,啟動定時器開始計時;
- 主程序查詢定時器中斷標志位TF0。
假設設置定時器0為每1ms溢出1次,因此在主程序中累計查詢到50次便使led小燈閃爍一次。根據前面介紹的方法計算出定時器0初始值65536-FOSC/12 * 1 * 10-3,程序設計如下圖所示:
#include< reg52.h >
#define FOSC 11059200 //單片機晶振頻率
#define T_1ms (65536 - FOSC/12/1000) //定時器初始值計算
sbit led0 = P1^0;
unsigned char count = 0;
void main()
{
TMOD = 0x01; //定時器工作模式配置
TL0 = T_1ms; //裝載初始值
TH0 = T_1ms >>8;
TR0 = 1; //啟動定時器
while(1)
{
if(TF0==1)
{
TF0 = 0; //軟件清零
TL0 = T_1ms;//重裝初始值
TH0 = T_1ms >>8;
count++;
if(count >=50)// 每一毫秒進入一次中斷,達到50次則為50ms,翻轉小燈。
{
count = 0;
led0 = ~led0;
}
}
}
}
如上圖所示,在主程序開始階段,對TMOD進行賦值來配置定時器T0為工作模式1,然后對TL0,TH0寄存器進行初始化賦值,緊接著啟動定時器T0開始計數。完成上述步驟后,進入主程序循環,在循環中不斷的檢測TF0,當檢測到定時器溢出后將TF0清零,重新轉載定時器初始值,當溢出達到50次后翻轉led小燈的值。這里需要注意的地方是,當判斷到溢出后需要通過軟件對TF0進行軟件清零。結合我們前面學習的知識,大家可以根據自己的需求來改變程序的功能,加深對定時器功能的理解。
6.4.2中斷響應方式實例
中斷響應方式與程序查詢方式略有不同,在程序初始化處需要打開定時器中斷,當定時器溢出后程序跳轉到中斷入口程序,并由硬件自動清理TF0,可在中斷程序中實現led小燈閃爍的功能。
定時器使用步驟:
- 配置定時器模式控制寄存器TMOD;
- 裝載定時器初始值TH0,T0;
- 置位TR0,啟動定時器開始計時;
- 允許定時器中斷,并開啟總中斷;
- 進入中斷程序。
- 在中斷程序中重載定時器初始值,并閃爍led小燈。
程序設計如下圖所示:
#include< reg52.h >
#define FOSC 11059200 //單片機晶振頻率
#define T_1ms (65536 - FOSC/12/1000) //定時器初始值計算
sbit led1 = P1^1;
unsigned char count = 0;
void main()
{
TMOD = 0x01; //定時器工作模式配置
TL0 = T_1ms; //裝載初始值
TH0 = T_1ms >>8;
TR0 = 1; //啟動定時器
ET0 = 1; //允許定時器中斷
EA = 1; //開總中斷
while(1); //循環
}
void timer0() interrupt 1
{
TL0 = T_1ms;//重裝初始值
TH0 = T_1ms >>8;
count++;
if(count >=50)// 每一毫秒進入一次中斷,達到50次則為50ms,翻轉小燈。
{
count = 0;
led1 = ~led1;
}
}
6.4本章小結
本章介紹了延時函數的調試,定時器基礎知識的介紹以及定時器功能的應用實例。結合我們我們程序的介紹,多多練習下載試驗逐步的熟練掌握延時函數、定時器功能的應用。
-
單片機
+關注
關注
6044文章
44628瀏覽量
638994 -
晶振
+關注
關注
34文章
2902瀏覽量
68361 -
定時器
+關注
關注
23文章
3256瀏覽量
115450 -
開發板
+關注
關注
25文章
5134瀏覽量
98333
發布評論請先 登錄
相關推薦
評論