定時器是生活中常見的一種定時裝置,常見的定時器有機械定時器和電子定時器
使用的時候我們給他設置時間,當我們指定的時間到了以后定時器會給我們提醒
其實,每天都跟我們打交道的單片機里面也有一個簡單的定時器,他一直站在單片機的角落,為機器精確的定時默默的奉獻出自己的一份力量
(1)單片機的心臟與機器周期
總所周知,單片機的心臟——晶振是一顆很重要的原件,晶振負責給單片機提供穩定的周期頻率,保證單片機的穩定運行
晶振是一顆能產生穩定脈沖頻率的電子期間,通電的時候,晶振每一秒發出脈沖頻率的次數分之一成為晶振頻率
晶振頻率 = 1秒/1秒內晶振發出脈沖的次數
我們把晶振頻率記作f,晶振周期記作T
晶振周期T= 1/晶振頻率
在單片機里面,單片機完成一次操作所用的時間叫做機器時間。這里不得不說明,在匯編里面,單片機的機器時間是12個晶振周期,但是在C語言里面,這個周期是不固定的,這也就是為什么用空循環來延遲,得不到精確的時間
void delay(unsigned int xms) //調用時需提供參數xms的實際值。
{
unsigned int i,j; //定義無符號整型變量i、j
for(i=0; i< xms; i++) //后面沒帶";",下一條語句是循環體。參數xms延時x毫秒。
for(j=0; j< 110; j++); //后面帶";",表示這個for語句的循環體是空語句。
}//這個值得學習,這里的代碼是延遲代碼
//復制請注明原作者 謝謝
//復制請注明原作者 謝謝
這也就是為什么我們不太建議用這個延遲精確時間的緣由
因為C51在匯編狀態的機器周期是12,所以可以認為單片機的機器周期值就是12
本小節講到這里就結束啦,這一節大家就要記住
·單片機的機器周期值是12,機器頻率 = 12f(晶振周期)
·f(晶振頻率) = 1/晶振1秒脈沖次數
·T(晶振周期)=1/f
(2)認識定時器的結構
單片機里面的定時器結構圖示如下(這個是我自己畫的,有點丑)
如圖所示,C51定時器原理如圖,定時器里面有一個寄存器,單片機每經過一次機器周期就會自動給這個寄存器+1,一直加,直到寄存器滿溢出,溢出后寄存器自動恢復0,并且給出一個信號告訴我們寄存器滿了.這就是定時器一次定時的工作流程
(3)定時器結構
C51中,一共有兩組定時器,記作T0,T1定時器不僅能定時,還可以當計數器使用。我們本節主要討論定時器。
定時器由三個寄存器組成
- 計數寄存器
C51里面的定時器有兩組(TH0,TL0)(TH0,TL1),每組由高八位和低八位組成的十六位寄存器
TH1,TL1屬于定時器1,TH0,TL0屬于定時器0
這里有個特別重要的概念!!,定時器的溢出
定時器的溢出不是滿了就算溢出,而是,滿了再加一才算溢出,拿T0舉例,現在T0的寄存器的數值是0xFFFF(65535,1111 1111 1111 1111),這時候再加一,才會溢出(也就是65535+1 = 65536才溢出),這就意味著我們計算的時候,溢出值要用65536來計算
為了方便使用定時器,我們需要給定時器的寄存器設置初始值,下面先介紹如何計算定時器初始值
假設單片機使用11.0592MHz的晶振,那么……
機器的頻率f = 1/11059200
前面我們知道,定時器會在每一次機器周期自動+1,也就是說定時器每加一一次的時間是機器周期t ,t = 12/晶振周期T
定時器計數計到65536才算溢出
假設延遲S(注意單位是秒),1000毫秒 = 1秒
計算的時候要使用Hz作為頻率單位,1MHz = 1 000 000Hz
假設定時器計數N次,溢出
那么步長N = (晶振頻率f * 要延遲的秒數S)/ 12(機器周期)
初始值X = 65536 - 步長N
/*
小練筆,假設晶振頻率是11.0592MHz
單片機周期是12
現在要延遲20ms
計算步長和初始值
20ms = 0.02s
步長N = 11059200 * 0.02/12 = 18432
初始值X=65536-N = 47104
*/
細心的朋友可能發現了,如果從0開始計時,C51定時器定時的時長最大值是0.07111
那我要如何延遲超過71ms的時間呢,這里有個方法:重復,讓定時器重復延遲多次,達到我們想要的時間,不過這就算是后話了,后面實例的時候再說吧
2.TCON寄存器
TCON寄存器在機器里的地址:0x88,可位尋址,復位值(單片機復位的時候寄存器的值) = 0x00(0000 0000)
在定時器里面,TCON寄存器主要有兩個作用,開關定時器,復位和檢測定時器是否溢出
TF :
檢測定時器是否溢出,沒有溢出的時候是0,溢出的時候是1。
當定時器溢出的時候要我們手動給TF寄存器寫0復位
TR:定時器開關,寫0的時候關閉定時器自動計數定時,寫1的時候啟用定時器計數定時。
TF1,TR1屬于定時器1,TF0,TR0屬于定時器0
注意這個寄存器是可位尋址的(可以直接訪問寄存器中的某個地址),舉個栗子哈,如果我們要哦將TR1寫0,我們不用操作整個寄存器,只需要
TR1 = 0;
即可
以下是TCON寄存器在定時器下的使用方法合集(這些關鍵字在reg52.h里面已經被定義,使用的時候像下面一樣用直接用)
TR0 = 0;//關閉Timer0
TR0 = 1;//打開Timer0
TR1 = 0;//關閉Timer1
TR1 = 1;//打開Timer1
//----------------------------------------
if (TF0 == 1){TF0 = 0;}//判斷Timer0是否溢出,如果溢出,重置溢出判斷位
if(TF1 ==1){TF1 = 0;}//判斷Timer1是否溢出,如果溢出,重置溢出判斷位
//特別提醒,溢出后一定要記得重置TF
特別提醒,溢出一定要記得重置TF位
3.TMOD寄存器
TMOD寄存器在機器里的地址:0x89,不可位尋址,復位值(單片機復位的時候寄存器的值) = 0x00(0000 0000)
TMOD寄存器設置寄存器的模式,我們先來看看TMOD寄存器的結構
高位對應Timer1,低位對應Timer0
名稱 | 功能 |
---|---|
GATE | 置1的時候為門控位,正常使用定時器置0 |
C/T | 切換定時,計數器。置1使用計數器,置0使用定時器 |
M1 | 定時器模式設置,請看下表 |
M0 |
我們發現,定時器有兩組模式設置位,這兩組模式設置為能組合出4種模式
M1 | M0 | 模式功能 |
---|---|---|
0 | 0 | 兼容8048單片機13位定時/計數器 |
0 | 1 | 16位定時/計數器 |
1 | 0 | 把一個定時器的寄存器拆開成兩個八位,第一個八位滿了就把這個值給到另一個8位,繼續計時 |
1 | 1 | 禁用Timer1,這時候Timer0相當于兩個8位Timer |
最常用的是M1 = 0,M0 = 1的16位定時器
總結,TMOD定時器的設置方法:設置一個16位定時器
功能 | 設置值 | 說明 |
---|---|---|
GATE | 0 | 不使用門控位 |
C/T | 0 | 設置值位0的時候使用定時器 |
M1 | 0 | 使用16位定時器 |
M0 | 1 |
這里必須要說明,TMOD寄存器是不可位尋址的,我們不能直接訪問這個寄存器里面的某一個地址。這就是說我們不能用GATE1 = 0;之類的方法來設置TMOD寄存器,修改的時候一次性修改整個TMOD寄存器
//按照上面的式設置寄存器
TMOD = 0x01;//TMOD寄存器修改值位0x01---》 0000 0001 16位定時器,Timer0 模式1
TMOD = 0x10;//TMOD寄存器修改值位0x01---》 0001 0000 16位定時器,Timer1 模式1
這樣就完成了最基本的16位定時器設置
定時器結構
這里大家做個簡單的了解就行
我們可以看到,OSC是晶振,/d得到機器周期,這就是給寄存器+1的信號
這個信號經過C/T(定時/計數器選擇)當C/T = 0的時候選擇定時器,當C/T = 1的時候選擇計數器
GATE是一個門控位置,GATE的信號經過一個非門取反,也就是說GATE = 0的時候非門的一端輸出1,GATE = 0的時候非門的一端輸出0GATE配合Intr(這是一個外部中斷輸入口,沒有信號的時候置0),這個電路后面經過一個或門,只要有一路或者兩路為高電平就輸出高電平,兩路都輸出低電平的時候輸出低電平
TR是使用定時/計數器的總開關,這里有個與門,只有兩路都輸入高電平的時候才會輸出高電平,輸出高電平的時候控制后面的開關,把信號輸入到計數器的寄存器
當計數器的寄存器溢出的時候就出發TF,TF = 1;
到了這,就不難理解這些寄存器怎么設置了
首先,我們把GATE設置成0,因為我們用不上Intr外部中斷輸入,Intr = 0,GATE設置成0以后機器經過或門的時候輸出1。
或門輸出1的時候,我們置TR = 1,這樣與門輸出1,信號就能通到計數寄存器上了
這時置C/T = 0,讓脈沖源來自機器周期的脈沖信號
修改M0=1,M1=0。使用16位寄存器
一個標準16位定時器設置值
GATE | C/T | M0 | M1 | TF | TR | TH | TL |
---|---|---|---|---|---|---|---|
0 | 0 | 1 | 0 | 0 | 1 | 根據需要設置值 |
以下是定時器操作的全部代碼,我們以Timer0為例子
#include< reg52.h >
void main()
{//注意設置定時器初始值要在main函數里面設置
TMOD = 0x01;
/*設置定時器模式寄存器TMOD為 0000 0001
GATE = 0
C/T = 0
M1 =0
M0 = 1
標準16位定時器
*/
//先設置定時器初始值
TL0 = 0x3A;//設置定時器初始值低位
TH0 = 0x8E;//設置定時器初始值高位
TR0 = 0;//記得一定要設置定時器開啟
while(1)
{
if (TF0 == 1)
{//這里可以先放下你的定時器計時滿了以后要執行的代碼
//溢出后你需要先給你的定時器再次設置初始值
TL0 = 0x3A;//設置定時器初始值低位
TH0 = 0x8E;//設置定時器初始值高位
TF0 = 0;//設置好初始值后再復位定時器
}
}
}
以上就是最基本的操作方法咯,前面我們說過,如果只使用定時器,單片機最長延遲71ms,如果需要延遲更長的時間,可以利用單片機重復延遲
使用11.0592MHz的晶振,在這里,我們計算出延遲1ms的初始值是921.6
這里,如果初始值設置為921,時間會略微段短一點,設置為922,又會略微長一點,這就是為什么我們要精確定時的時候,使用11.0592MHz的晶振會不準,使用12MHz的晶振是不錯的解決辦法,可惜使用了12MHz的晶振,又無法進行正常的串口通訊(這個后面再提吧)
這里我取的數值是922,因為偏差比921小
以下是延遲函數delay()的參考代碼
#include< reg52.h >
void main()
{//注意設置定時器初始值要在main函數里面設置
TMOD = 0x01;
/*設置定時器模式寄存器TMOD為 0000 0001
GATE = 0
C/T = 0
M1 =0
M0 = 1
標準16位定時器
*/
TL0 = 0x03;//設置定時器初始值低位
TH0 = 0x9A;//設置定時器初始值高位
void delay(unsigned int Ms)//延遲函數,輸入的單位是毫秒
{
unsigned int counter = 0;
TR0 =1;//啟用定時器
for(counter = 0;counter< Ms;counter++)
{
while(TF0 !=0){}//循環一直進行,直到TF0不再是0
TL0 = 0x03;//設置定時器初始值低位
TH0 = 0x9A;//設置定時器初始值高位
TF0 = 0;//復位溢出位,復位之前一定要先設置好初始值
}
TR0 = 0;//關閉定時器
}
while(1)
{
delay(1);//測試定時器
delay(10);
delay(100);
delay(1000);
}
}
這個參考代碼不算特別完整,僅供多次定時器延遲達到一個長于71ms的一種方法
-
單片機
+關注
關注
6032文章
44525瀏覽量
633247 -
寄存器
+關注
關注
31文章
5325瀏覽量
120052 -
晶振
+關注
關注
34文章
2834瀏覽量
67919 -
定時器
+關注
關注
23文章
3241瀏覽量
114511 -
C51單片機
+關注
關注
12文章
164瀏覽量
34727
發布評論請先 登錄
相關推薦
評論