定時器是單片機的重要功能模塊之一,在檢測、控制領域有廣泛應用。定時器常用作定時時鐘,以實現定時檢測,定時響應、定時控制,并且可以產生ms寬的脈沖信號,驅動步進電機。定時和計數的最終功能都是通過計數實現,若計數的事件源是周期固定的脈沖則可實現定時功能,否則只能實現計數功能。因此可以將定時和計數功能全由一個部件實現。通過下圖可以簡單分析定時器的結構與工作原理。 一、定時器 1、51單片機計數器的脈沖輸入腳。主要的脈沖輸入腳有Px,y, 也指對應T0的P3.4和對應T1的P3.5,主要用來檢測片外來的脈沖。而引腳18和19則對應著晶振的輸入脈沖,脈沖的頻率和周期為 F = f/12 = 11.0592M/12 = 0.9216MHZ T = 1/F = 1.085us 2、定時器有兩種工作模式,分別為計數模式和定時模式。對Px,y的輸入脈沖進行計數為計數模式。定時模式,則是對MCU的主時鐘經過12分頻后計數。因為主時鐘是相對穩定的,所以可以通過計數值推算出計數所經過的時間。 3、51計數器的計數值存放于特殊功能寄存器中。T0(TL0-0x8A, TH0-0x8C), T1(TL1-0x8B, TH1-0x8D) 4、TLx與THx之間的搭配關系 1)、TLx與THx之間32進制。即當TLx計到32個脈沖時,TLx歸0同時THx進1。這也稱為方式0。 2)、TLx與THx之間256進制。即當TLx計到256個脈沖時,TLx歸0同時THx進1。這也稱為方式1。在方式1時,最多計65536個脈沖產生溢出。在主頻為11.0592M時,每計一個脈沖為1.085us,所以溢出一次的時間為1.085usx65536=71.1ms。 3)、THx用于存放TLx溢出后,TLx下次計數的起點。這也稱為方式2。 4)、THx與TLx分別獨立對自己的輸入脈沖計數。這也稱為方式3。 5、定時器初始化 1)、確定定時器的計數模式。 2)、確定TLx與THx之間的搭配關系。 3)、確定計數起點值。即TLx與THx的初值。 4)、是否開始計數。TRx (1)和(2)可以由工作方式寄存器TMOD來設定,TMOD用于設置定時/計數器的工作方式,低四位用于T0,高四位用于T1。其格式如下: GATE:門控位,用于設置計數器計數與否,是否受P3.2或P3.3電壓狀態的影響。GATE=0時,表示計數器計數與否與兩端口電壓狀態無關;GATA=1時,計數器是否計數要參考引腳的狀態,即P3.2為高時T0才計數,P3.3為高時T1才計數。
C/T:定時/計數模式選擇位。 =0為定時模式; =1為計數模式。
M1M0:工作方式設置位。定時/計數器有四種工作方式,由M1M0進行設置。 6、計數器的溢出 計數器溢出后,THx與TLx都歸0。并將特殊功能區中對應的溢出標志位TFx寫為1。 好了,理論就講述到這。現在我們通過一些實驗來看看怎么使用定時器。 實驗一、P1口連接的8個LED燈以1秒鐘的頻率閃爍。 首先上代碼:
- #include "reg51.h"
- char c;
-
- void Timer0_Init() //初始化定時器
- {
- TMOD = 0x01; //
- TH0 = 0;
- TL0 = 0; //定時器的計數起點為0
- TR0 = 1; //啟動定時器0
- }
-
- void main()
- {
- Timer0_Init();
- while(1)
- {
- if(TF0 == 1) //檢測定時器0是否溢出,每到65535次
- {
- TF0=0;
- c++;
- if(c==14) //71ms乘以14為1s
- {
- c=0;
- P1=~P1;
- }
- }
- }
- }
復制代碼
上述代碼的思路是每計算14個溢出,則翻轉P1口狀態。產生一個溢出的時間是71.1ms,14個則約為1s。
- #include "reg51.h"
- sbit LD1 = P1^0;
-
- void Timer0_Init() //初始化定時器
- {
- TMOD = 0x01; //
- TH0 = 0;
- TL0 = 0; //定時器的計數起點為0
- TR0 = 1; //啟動定時器0
- }
-
- void Timer0_Overflow() //處理定時器0的溢出事件
- {
- static char c;
- if(TF0 == 1) //檢測定時器0是否溢出,每到65535次
- {
- TF0=0;
- c++;
- if(c==14) //71ms乘以14為1s
- {
- c=0;
- LD1=!LD1;
- }
- }
- }
-
- void main()
- {
- Timer0_Init(); //初始化定時器0
- while(1)
- {
- Timer0_Overflow();
- }
- }
復制代碼
相比于上個例子,這里有兩個區別,首先是將timer0的溢出事件作為子函數單獨出來,其次是注意翻轉一個led燈時候用的是“!”。
例子三、讓連接到P1口的LED1和LED8燈每1秒鐘閃爍。
- #include "reg51.h"
-
- void Timer0_Init() //初始化定時器
-
- {
- TMOD |= 0x01; //定時器0方式1,計數與否不受P3.2的影響
- TH0 = 0;
- TL0 = 0; //定時器的計數起點為0
- TR0 = 1; //啟動定時器0
- }
-
- void Timer0_Overflow() //´處理定時器0的溢出事件
- {
- static char c;
- if(TF0 == 1) //檢測定時器0是否溢出,每到65535次
- {
- TF0=0;
- c++;
- if(c==14) //71ms乘以14為1s
- {
- c=0;
- P1 ^= (1<<0);//LD1=!LD1;
- }
- }
- }
-
- void Timer1_Init()
- {
- TMOD|=0x10; //定時器1方式1,計數與否不受P3.3的影響
- TH1=0;
- TL1=0; //定時器1的計數起點為0
- TR1=1; //啟動定時器1
- }
-
- void Timer1_Overflow() //處理定時器1的溢出事件
- {
- static char c;
- if(TF1==1) //軟件查詢,主循環每跑完一圈才會到這里。
- {
- TF1=0;
- c++;
- if(c==14)
- {
- c=0;
- P1 ^= (1<<7);//LD8=!LD8;
- }
- }
- }
-
- void main()
- {
- Timer0_Init(); //初始化定時器0
- Timer1_Init(); //初始化定時器1
- while(1)
- {
- Timer0_Overflow();
- Timer1_Overflow();
- }
- }
復制代碼
相較于例二,例子三有幾個點值得注意:
1、TMOD初始化為什么采用TMOD |= 0x01或0x10的形式?
首先如果在定時器初始化函數中采用TMOD = 0x01和TMOD = 0x10,那么將造成LD1閃爍比LD8閃爍快8倍。分析一下,從main函數開始執行,先是初始化timer0,這時候定時器1設置為工作方式1。接著程序執行到Timer1_Init(),這時候TMOD=00010000,即選定了timer1在工作方式1,但同時timer0重新配置為工作方式0, 也就是32進制,所以產生快8倍現象。
那為什么用|這個符號就可以做到互不影響呢?|是或運算符,即有1出1,全0出0。什么意思呢?舉個例子,a是11110000,b是10101010,那么a|b就是11111010。通過引入這個符號,可以實現tmod對兩個定時器的獨立操作。
2、為什么使用P1 ^= (1<<0)可以實現LD1的控制呢?
首先解釋下^這個符號。^稱為異或運算符,相同出0,不同出1。舉個例子,a是11110000,b是10101010,那么a^b就是01011010。
然后再來分析 x ^= (1<<i), 假設x為10101010,當i為1時, (1<<i)為00000010,那么x^ (1<<i)=10101010^00000010=10101000。當i為2時,(1<<i)為00000100,那么x^ (1<<i)=10101010^00000100=10101110,以此類推。我們發現,x ^= (1<<i)是在將x的第i位翻轉而同時不影響其他位。
因此P1 ^= (1<<0)實際是在翻轉P0口第一位的值,因此也就是在閃爍LD1燈。
上面三個例子實際都是采用了軟件查詢法。即main函數會每次進入到溢出事件函數里去判斷TF0或1是否等于1,這樣就浪費了大量CPU時間。同時,實時性差,假如在執行Timer0_Overflow()的時候timer1也溢出了,這時候timer1的溢出事件就沒有及時處理。因此下面我們要引入中斷系統。
二、中斷系統
中斷系統是一套硬件電路,它可以在每個機器周期對所有的外設的標志位作查詢。相比于前面的軟件查詢(if(xx==1)),中斷系統也可以叫做硬件查詢。51的中斷系統可查詢以下6個標志位。
IE0(TCON.1),外部中斷0中斷請求標志位。
IT1(TCON.2),外部中斷1觸發方式控制位。
IE1(TCON.3),外部中斷1中斷請求標志位。
TF0(TCON.5),定時/計數器T0溢出中斷請求標志位。
TF1(TCON.7),定時/計數器T1溢出中斷請求標志位。
RI(SCON.0)或TI(SCON.1),串行口中斷請求標志。當串行口接收完一幀串行數據時置位RI或當串行口發送完一幀串行數據時置位TI,向CPU申請中斷。
當中斷系統查詢到外設的標志位變為1時,中斷系統可暫停當前的主循環,并且將程序跳轉到用戶預先指定的函數中執行。要啟動中斷系統,必須先進行中斷初始化,其流程如下:
a、是否要查詢外設標志(EA=0或EA=1,EA 也叫 CPU中斷允許(總允許)位)
b、查詢到標志1,是否要跳程序
c、跳轉的目標函數,即中斷服務子函數
所以在使用定時器中斷時,我們只需要首先初始化中斷系統,開啟總中斷(相當于總開關),開啟定時器對應的控制位(相當于支路開關),再初始化定時器即可。中斷系統作為單片機的外設,只有在某個中斷產生時才會打斷主循環,并由相應的中斷號引入到相應的中斷服務子函數。下圖是6個中斷標志位的信息。

實驗四、使用中斷系統實現LD1燈每1秒鐘閃爍。
- #include "reg51.h"
-
- void Timer0_Init()
- {
- TMOD|=0x01;
- TH0=56320/256; //計數起點為56320 ==10ms溢出一次
- TL0=56320%256;
- TR0=1;
- }
-
- void Timer1_Init()
- {
-
- }
-
- void ISR_Init() //初始化中斷系統
- {
- EA=1; //啟動中斷系統
- EX0=0; //-->IE0
- ET0=1; //-->TF0 控制位置1,表明當TF0置1時,中斷系統將介入
- EX1=0; //-->IE1
- ET1=0; //-->TF1
- ES=0; //-->RI,TI
-
- }
-
- //以下中斷服務子程序,我們希望中斷系統來調用,而不是我們在main函數里面調用,因此使用interrupt. */
-
- void IE0_isr() interrupt 0
- {
-
- }
-
- /*void TF0_isr() interrupt 1 //71.1ms 進入一次,但如果要求10MS進來一次呢?
- {
- static char c;
- c++;
- if(c==14)
- {
- P1^=(1<<0);
- c=0;
- }
- }
- */
- void TF0_isr() interrupt 1 //10ms 進入一次
- {
- static char c;
- TH0=56320/256; //重裝初值
- TL0=56320%256;
- c++;
- if(c==100)
- {
- P1^=(1<<0);
- c=0;
- }
- }
-
- void IE1_isr() interrupt 2
- {
-
- }
-
- void TF1_isr() interrupt 3
- {
-
- }
-
- void RI_TI_isr() interrupt 4
- {
-
- }
-
- void main()
- {
- Timer0_Init();
- Timer1_Init();
- ISR_Init();
-
- while(1)
- {
- //...
- //發現溢出后,中斷系統根據中斷號尋找中斷子服務函數,并強行暫停主循環并進入子函數
- //...
- }
- }
復制代碼
顯然使用中斷系統查詢得到的1s更為精確。因為中斷系統獨立于main函數運行。另外本程序還預裝了timer0的初值,這樣的話就可以實現比71ms更小的時間片,比如要求10ms就進入中斷。關于初值的設定,請參考下圖。 實驗五、用定時器實現數碼管顯示1234。 - //數碼管的定時掃描,每5ms顯示一個數碼管,也就是說相同的數碼管,每20ms會被重新裝入同樣的數值,根據人眼的延遲效應,人眼觀測到的數碼管上的數值是靜態的。
- #include "reg51.h"
- unsigned int count;
- extern void load_smg();
- void Timer0_Init()
- {
- TMOD|=0X01;
- TH0=60928/256;
- TL0=60928%256;//每5ms進入一次中斷
- TR0=1;
- }
-
- void isr_Init()
- {
- EA=1;
- ET0=1; //TF0 如果這個標志為1,進入中斷子函數
- }
-
- void TF0_isr() interrupt 1
- {
- TH0=60928/256;
- TL0=60928%256;//重裝初值
- load_smg();
- }
-
- void main()
- {
- Timer0_Init();
- isr_Init();
- while(1)
- {
-
- }
-
- }
-
- #include "reg51.h"
- //char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
- code char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};
- char smgbuf[4]={1,2,3,4}; //從RAM的smgbuf這個地址開始連續存放4個數,并且每個數占一個單元。
- extern unsigned int count; //外部申明,表示并不在這里申明
-
- void fill_smgbuf() //向LED緩沖區填充數據
- {
- smgbuf[0]=count/1000; //千位
- smgbuf[1]=(count%1000)/100; //百位
- smgbuf[2]=((count%1000)%100)/10; //十位
- smgbuf[3]=((count%1000)%100)%10; //個位
- }
-
- void load_smg() //將數碼管顯示緩沖區的數據,顯示到數碼管上
- {
- static char i;
- fill_smgbuf();
- i++;
- if(i>=4)
- {
- i=0;
- }
- P0=0xFF; //消除上一個循環的影子
- P2 = ~(1<<i);
- P0 = seg[smgbuf[i]];
- }
復制代碼
實驗六、實現按鈕控制數碼管上的數值加1或減1,并且當按住按鈕不放時,能實現快速的增減。 這里的關鍵點在于如何實現快速增減,具體請詳細分析代碼。代碼鏈接點擊打開鏈接 https://zhidao.baidu.com/question/561457023576622684.html 單片機中TCON和TMOD寄存器,無論是匯編程序還是C語言程序,都可以直接賦值。具體賦值多少,根據以上兩寄存器各位具體功能確定。 



https://blog.csdn.net/zn2016/article/details/53353818 51定時器使用2016年11月26日 18:56:03 閱讀數:1020 1.設置特殊功能寄存器TMOD,配置好工作模式。 TMOD中M0/M1的配置決定定時器(0或1)的工作模式。 M1 = 0,M0 = 0,工作模式0,由THn的8位和TLn的5位組成一個13位的定時器。 M1 = 0,M0 = 1,工作模式1,由THn和TLn組成1個16位的定時器。 M1 = 1,M0 = 0,工作模式2,8位自動重裝模式,定時器溢出后由THn重裝的TLn中。 M1 = 1,M0 = 1,工作模式3,禁用定時器1,定時器0變成兩個8位的定時器。 2.設置計數寄存器TH0,TL0的初值。 3.設置TCON,通過TR0置1,來讓定時器開始計數。 4.判斷TCON寄存器的TF0位,檢測定時器是否溢出。 注意:定時器計數溢出后,TF0會置位,如果沒有開定時器中斷則需要軟件清零TF0位。如果開定時器中斷則TF0位由硬件清理。謹記:定時器溢出后給TH0,TL0 重裝載值。 定時器在每一個機器周期計數向上加1。
完整的Word格式文檔51黑下載地址:
51單片機定時器的原理與使用.docx
(1.34 MB, 下載次數: 52)
2018-5-9 10:07 上傳
點擊文件名下載附件
下載積分: 黑幣 -5
|