【單片機技術】—— 89C52中斷和定時器操作1

一、中斷

1.1 基本概念

其實中斷的概念很好理解:試想一下你正在教室裏面搞單片機(這相當於與CPU正在執行主程序)這時,防空警報響了(一箇中斷信號),因此你得停下你手頭的工作,去響應這個防空警報(中斷響應),這個防空警報需要你執行一些行爲(比如逃出教室或者躲在課桌底下),當你執行了一段時間之後,防空警報結束了(中斷服務執行完了),那麼你就從新回到教室繼續搞單片機。

這個過程就是中斷。

我們先看看中斷都有哪幾種:(1)外部中斷0【中斷號 0】,它的優先級是最高的。(2)定時器/計數器0 【中斷號1】 (3)外部中斷1【中斷號 2】 (4)定時器/計數器 1【中斷號 3】
(5)串口中斷【中斷號 4】 (6)定時器/計數器 2 【中斷號 5】

先看看下面的圖:其中,TCON, IE, IP 等都是與中斷有關的寄存器。
在這裏插入圖片描述
要想在單片機中給一箇中斷(我們以外部中斷0爲例),看看需要什麼條件:

  1. 我們看到上圖的 EA,它是中斷總允許,也就是說,我們得把 EA 置成 1 ,纔可能輸入中斷信號
  2. EA=1 之後,我們就要看中斷源允許,也就是說,我們要選擇輸入的中斷種類。EX0 表示外部中斷0,ET0 表示計數器/定時器 T0 中斷、、、
  3. 如果我們想給外部中斷0(其實外部中斷0和外部中斷1原理一樣),那麼,我們還需要給出 IT0IT0,當這個信號是0時,表示低電平觸發、1表示下降沿觸發。

1.2 代碼和實驗部分

下面,我們想實現這樣一個效果:
【代碼一】:實現數碼管1,3,5亮,每隔一定時間同時亮一個數,然後我們在一些特定時刻給它外部中斷0,中斷時,讓CPU點亮第一個LED燈,我們看看是什麼效果:

#include<reg52.h>
#define unchar unsigned char
#define uint unsigned int

sbit WEI = P2^7;    //定義控制數碼管的位選信號的單片機端口位(或者說定義單片機的P2.7口是WEI)
sbit DULA = P2^6;   //定義單片機P2.6口是DULA(控制數碼管的段選信號)
sbit D1 = P1^0;     //定義控制第一個LED的單片機端口位
sbit int0 = P2^3;
uchar num;

uchar code table[] = {0x3f, 0x06,0x5b,0x4f,0x66,0x6d,
                      0x7d,0x07,0x7f,0x6f,0x77,0x7c,
                      0x39,0x5e,0x79,0x71};    //定義數碼管的編碼表,爲了節約內存空間
void delay(uint);

void main()
{
	EA=1;  //先打開中斷總允許
	EX0=1;  //打開中斷源允許EX0,表示允許外部中斷0輸入
	IT0 = 1;  //表示下降沿觸發(因爲一上電默認單片機P3.2口是高電平的)
	
	//下面是數碼管的顯示:先位選
	WEI = 1;    //打開位選
	P0 = 0xea;
	WEI = 0;    //鎖住位選信號
	
	while(1)
	{
		for(num=0;num<16;num++)
		{
			DULA = 1;      //打開段選
			P0 = table[num];
			DULA = 0;
			if(num == 3)
			{
				//如果num == 3那麼就給一個下降沿,即讓P3.2口變成低電平
				int0 = 0;
			}
		}
	}
}

void exter0() interrupt 0
{
	D1 = 0;    //中斷響應就是要先讓第一個LED亮
	delay(1000);  //延時
	D1 = 1;    //滅LED
	int0 = 1;   //把P3.2口變回高電平,用以準備下一次的中斷。

}

void delay(uint z)
{
	//延時函數
	uint x,y;
	for(x=z;x>0;x--)
		for(y=1000;y>0;y--);
}

也就是說:我們想要給出一箇中斷信號(這裏我們暫時先不管定時器和串口)就以外部中斷0、外部中斷1爲例。那麼我們就首先需要打開總中斷允許(即令 EA=1);接下來,就要選取中斷種類(即令EX0, ET0, EX1, ET1,ES),選哪個就把那個置1,最後一步就是選擇觸發方式:(IT0, IT1)置1表示下降沿觸發、置0表示低電平觸發

二、定時器

2.1 原理簡析

這是單片機裏面一個相當重要的概念:首先,51單片機裏面有兩個定時器 T0, T1。

定時器一但啓動,它便在原來的數值上開始加1計數,若在程序開始時,我們沒有設置 TH0 和TL0,它們的默認值都是0,假設時鐘頻率爲12MHz,12個時鐘週期爲一個機器週期,那麼此時機器週期爲1us.

假如定時器工作在模式一:也就是我們先在 TL0 這8位裏面記錄時間,每當記滿 TL0 的八位後,才向 TH0 進 1. 那麼,在TH0、TL0的初始值都是0時,我們記滿整個定時器需要的機器週期就是:(28)2=256(2^8)^2 = 256x256256 = 6553665536個機器週期,再來多一個週期,那麼計數器就溢出,那麼隨機向CPU 發出中斷請求。

而上面的情況,從計數開始到向CPU發出中斷,所花的時間是:1us x 65536 = 65536us =65.536ms
那麼,假如我們給定計數器一個初始值,那麼我們就可以控制計數器在經過規定的時間後產生中斷。

要知道,在此之前,我們想要計時都是通過 delay()delay() 去做的,不僅要手工調參,而且定時精度不佳,但是利用定時器定時就非常精確!

2.2 調用定時器的步驟

首先,51單片機的定時器由兩個寄存器 TMODTMODTCONTCON 控制:
在這裏插入圖片描述
TMOD也是一個八位寄存器,低4位控制 T0 定時器,高四位控制 T1 定時器。
如果我們只是使用T0定時器,那麼我們就直接把高四位置0即可。

我們看看:00表示模式0、01表示模式1、10表示模式2、11表示模式3.
我們分析一下模式1:模式1就是我們在上文提到的那種計數方式:也就是我們先在 TL0 這8位裏面記錄時間,每當記滿 TL0 的八位後,才向 TH0 進 1.

=========================================
那麼舉個例子:假設我們要定時 50ms,也就是50000us.那麼我們看一下TH0和TL0裏面的值到底怎麼算:

因爲我們想要計數器精準記到50000us,之後這個計數器得告訴我們從開始到現在剛好走完了50000us,這個“告訴我們”的過程,就是計數器發出中斷(即計數到65536的時候)。那麼也就是說,這個計數器就不可能是從0開始的,它得是從某一個值開始計數,記了50000次剛好到65535.

那麼,很簡單,這個初始值就是 (65536-50000) = 15536

現在我們要做的,就是把這個15536分配到 TH0 和 TL0 中。我們會議一下模式一,是在 TL0 得8位都記滿,即256個機器週期,纔會向 TH0 進一位。因此,TH0我們可以這樣表示:
TH0=(6553650000)/256 TH0 = (65536-50000)/256
這裏,’/’ 表示取模,也就是商(整數)

TL0 我們可以這樣表示:TL0=(6553615536)%256 TL0 = (65536-15536)\%256
這裏的 ‘%’ 表示 取餘數

在這裏插入圖片描述
對於 TCON的設置,我們不用把所有位的置一次,只需要定義 TR0TR0(用於控制 T0 的啓動和停止)
如果要同時用 T0 和 T1,那麼我們就令 TR0 = 1; TR1 = 1; 即可。

由於定時器在計數滿了溢出的時候也會向 CPU 發送中斷請求,因此,我們也需要向調用外部中斷0那樣設置一些東西:比如打開中斷總允許 EA(EA=1)、在打開 ET0(ET0=1)

重點:使用定時器的步驟!!

  1. 首先規定定時器的初始值 TH0 和 TL0 (如果是Mode 1 可以套用上面的公式)
  2. 然後我們設置控制定時器的第一個寄存器 TMOD
  3. 設置中斷允許的信號
  4. 最後設置控制定時器的第二個寄存器 TCON

2.3 代碼與實驗

現在,我們還是希望點亮1、3、5三個數碼管,每一次顯示一個數字,但是閃爍的間隔時間我們不再使用之前的 delay()delay() 函數,而是採用定時器。我們看看代碼:

注意:我們這裏是假定了單片機的振盪頻率是 12MHz,因爲在12MHz時,晶振週期是 1/12M,而恰好1個機器週期等於 12 個晶振週期,那麼一個機器週期也就是 (1/12)x12 = 1us。如果不是12MHz,那麼一個機器週期的時間需要重新算一下。

#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int

sbit WEI = P2^7;     //定義位選信號端
sbit DULA = P2^6;    //定義段選信號端
uchar num,t;
uchar code table[] = {0x3f, 0x06,0x5b,0x4f,0x66,0x6d,
                      0x7d,0x07,0x7f,0x6f,0x77,0x7c,
                      0x39,0x5e,0x79,0x71};
                      
void main()
{
	TH0 = (65536-50000)/256;     //定義定時器初始高位
	TL0 = (65536-50000)%256;     //定義定時器初始低位

	TMOD = 0x01;                 //定義TMOD(也就是控制T0的第一個寄存器)

	EA=1;    //打開中斷總允許
	ET0=1;   //打卡定時器T0的中斷允許

	TR0 = 1//定時器T0啓動
	
	WEI=1;
	P0=0Xea;
	WEI=0;
	for(num=0;num<8;num++)
	{
		if(t == 20)
		{
			DULA=1;
			P0=table[num];
			DULA=0;
		}
	}
}

void extraT0() interrupt 1
{
	TH0=(65536-50000)/256;
	TL0=(65536-50000)%256;   //因爲在CPU執行這一段中斷代碼時,計數器已經溢出了,所以要把它的初始值置成一開始的那個
	t++;   //這裏 t 每自增一次,代表已經經過了50ms,那麼t = 20時也就是1s	
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章