中斷是微控制器系統重點中的重點,因為有了中斷,微控制器就具備了快速協調多模組工作的能力,可以完成複雜的任務。
中斷的產生背景
合理巧妙的利用中斷,不僅可以使我們獲得處理突發狀況的能力,而且可以使微控制器能夠“同時”完成多項任務
定時器中斷的應用
定時器一般用法都是採取中斷方式來做的。
定時器和中斷不是一回事,定時器是微控制器模組的一個資源,確確實實存在的一個模組,而中斷,是微控制器的一種執行機制。
標準 51 微控制器中控制中斷的暫存器有兩個,一個是中斷使能暫存器,另一個是中斷優先順序暫存器。
位 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
符號 | EA | -- | ET2 | ES | ET1 | EX1 | ET0 | EX0 |
復位值 | 0 | -- | 0 | 0 | 0 | 0 | 0 | 0 |
位 | 符號 | 描述 |
7 | EA | 總中斷使能位,相當於總開關 |
6 | -- | -- |
5 | ET2 | 定時器2中斷使能 |
4 | ES | 串列埠中斷使能 |
3 | ET1 | 定時器1中斷使能 |
2 | EX1 | 外部中斷1使能 |
1 | ET0 | 定時器0中斷是使能 |
0 | EX0 | 外部中斷0使能 |
中斷使能暫存器IE的位0~5控制了6箇中斷使能,而第6位沒有用到,第7位是宗門光開關。總開關就相當於我們家裡或者學生宿舍的那個電源總閘門,而0~5位這6個位相當於每個分開關。那麼也就是說,我們只要用到中斷,就要寫EA=1
這一句,開啟中斷總開關,然後用到哪個分中斷,再開啟相對應的控制位就可以了。
數碼管動態顯示,帶鬼影
#include <reg52.h>
sbit ADDR0 = P1 ^ 0;
sbit ADDR1 = P1 ^ 1;
sbit ADDR2 = P1 ^ 2;
sbit ADDR3 = P1 ^ 3;
sbit ENLED = P1 ^ 4;
unsigned char code LedChar[] = //數碼管顯示字元轉換表
{
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[] = //數碼管顯示緩衝區,初值為0xFF確保啟動時都不亮
{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
void main(void)
{
unsigned char i = 0;//動態掃描的索引
unsigned int cnt = 0;//記錄T0中斷次數
unsigned long sec = 0;//記錄經過的秒數
ENLED = 0;//使能U3,選擇控制數碼管
ADDR3 = 1;//因為需要動態改變ADDR0-2的值,所以不需要再初始化了
TMOD = 0x01;//設定T0為模式1
TH0 = 0xFC;//設定T0賦初值為0xFC67,定時1ms
TL0 = 0x67;
TR0 = 1;//啟動T0
while (1)
{
if (1 == TF0)//判斷T0是否溢位
{
TF0 = 0;//T0溢位後,清零中斷標誌
TH0 = 0xFC;//重新賦初值
TL0 = 0x67;
cnt++;//計數值加1
if (cnt >= 1000)//判讀T0溢位是否達到1000次
{
cnt = 0;//達到1000次後計數值清零
sec++;//秒計數加1
//以下程式碼將sec按十進位制位從低到高依次提取並轉為數碼管顯示字元
LedBuff[0] = LedChar[sec % 10];
LedBuff[1] = LedChar[sec / 10 % 10];
LedBuff[2] = LedChar[sec / 100 % 10];
LedBuff[3] = LedChar[sec / 1000 % 10];
LedBuff[4] = LedChar[sec / 10000 % 10];
LedBuff[5] = LedChar[sec / 100000 % 10];
}
//以下程式碼完成數碼管動態掃描重新整理
switch (i)
{
case 0:
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
i++;
P0 = LedBuff[0];
break;
case 1:
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 1;
i++;
P0 = LedBuff[1];
break;
case 2:
ADDR2 = 0;
ADDR1 = 1;
ADDR0 = 0;
i++;
P0 = LedBuff[2];
break;
case 3:
ADDR2 = 0;
ADDR1 = 1;
ADDR0 = 1;
i++;
P0 = LedBuff[3];
break;
case 4:
ADDR2 = 1;
ADDR1 = 0;
ADDR0 = 0;
i++;
P0 = LedBuff[4];
break;
case 5:
ADDR2 = 1;
ADDR1 = 0;
ADDR0 = 1;
i = 0;
P0 = LedBuff[5];
break;
default:
break;
}
}
}
}
數碼管動態顯示,不帶鬼影
#include <reg52.h>
sbit ADDR0 = P1 ^ 0;
sbit ADDR1 = P1 ^ 1;
sbit ADDR2 = P1 ^ 2;
sbit ADDR3 = P1 ^ 3;
sbit ENLED = P1 ^ 4;
unsigned char code LedChar[] = //數碼管顯示字元轉換表
{
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[] = //數碼管顯示緩衝區,初值為0xFF確保啟動時都不亮
{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char i = 0;//動態掃描的索引
unsigned int cnt = 0;//記錄T0中斷次數
unsigned char flagls = 0;//1秒定時標誌
void main(void)
{
unsigned long sec = 0;//記錄經過的秒數
EA = 1;//使能總中斷
ENLED = 0;//使能U3,選擇控制數碼管
ADDR3 = 1;//因為需要動態改變ADDR0-2的值,所以不需要再初始化了
TMOD = 0x01;//設定T0為模式1
TH0 = 0xFC;//設定T0賦初值為0xFC67,定時1ms
TL0 = 0x67;
ET0 = 1;//使能T0中斷
TR0 = 1;//啟動T0
while (1)
{
if(1 == flagls)//判斷1秒定時標誌
{
flagls = 0;//1秒定時標誌清零
sec++;//秒計數加1
//以下程式碼將sec按十進位制位從低到高依次提取並轉為數碼管顯示字元
LedBuff[0] = LedChar[sec % 10];
LedBuff[1] = LedChar[sec / 10 % 10];
LedBuff[2] = LedChar[sec / 100 % 10];
LedBuff[3] = LedChar[sec / 1000 % 10];
LedBuff[4] = LedChar[sec / 10000 % 10];
LedBuff[5] = LedChar[sec / 100000 % 10];
}
}
}
/* 定時器中斷服務函式 */
void InterruptTimer0() interrupt 1
{
TH0 = 0xFC;//重新賦初值
TL0 = 0x67;
cnt++;//中斷次數計數值加1
if (cnt >= 1000)//中斷1000次即1秒
{
cnt = 0;//清零計數值以重新開始下一秒計時
flagls = 1;//設定1秒定時標誌位1
}
//以下程式碼完成數碼管動態掃描重新整理
P0 = 0xFF;//顯示消隱
switch (i)
{
case 0:
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
i++;
P0 = LedBuff[0];
break;
case 1:
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 1;
i++;
P0 = LedBuff[1];
break;
case 2:
ADDR2 = 0;
ADDR1 = 1;
ADDR0 = 0;
i++;
P0 = LedBuff[2];
break;
case 3:
ADDR2 = 0;
ADDR1 = 1;
ADDR0 = 1;
i++;
P0 = LedBuff[3];
break;
case 4:
ADDR2 = 1;
ADDR1 = 0;
ADDR0 = 0;
i++;
P0 = LedBuff[4];
break;
case 5:
ADDR2 = 1;
ADDR1 = 0;
ADDR0 = 1;
i = 0;
P0 = LedBuff[5];
break;
default:
break;
}
}
在程式中,有兩個函式,一個是主函式,一個是中斷服務函式。
中斷服務函式的書寫格式是固定的,首先中斷服務函式前邊void表示函式返回值為空,即中斷服務函式不返回任何值,函式名是可以在滿足命名規則的前提下隨機取的,而後是 interrupt 這個關鍵字,一定不能錯,這是中斷所特有的關鍵字,另外後邊還有個數字1,數字1是中斷函式編號
中斷函式編號 | 中斷名稱 | 中斷標誌位 | 中斷使能位 | 中斷向量地址 | 預設優先順序 |
0 | 外部中斷0 | IE0 | EX0 | 0x0003 | 1(最高) |
1 | T0中斷 | TF0 | ET0 | 0x000B | 2 |
2 | 外部中斷1 | IE1 | EX1 | 0x0013 | 3 |
3 | T1中斷 | TF1 | ET1 | 0x001B | 4 |
4 | UART中斷 | T1/R1 | ES | 0x0023 | 5 |
5 | T2中斷 | TF2/EXF2 | ET2 | 0x002B | 6 |
如上表,如果需要使能T0中斷,就要把它的中斷使能位ET0置1,當它的中斷標誌位TF0變為1時,就會觸發T0中斷了,那麼這時就應該執行中斷函式了,微控制器又是怎樣找到這個中斷函式呢?靠的就是中斷向量地址,所以 interrupt 後面的中斷函式編號的數字 x 技術根據中斷向量得出的,它的計算方法是 x*8+3=向量地址。最後算出即為表中的值。
中斷函式寫好後,每當滿足中斷條件而觸發中斷後,系統就會自動來呼叫中斷函式。
中斷優先順序
中斷優先順序有兩種,一種是搶佔優先順序,一種是固有優先順序。
IP這個暫存器的每一位,表示對應中斷搶佔優先順序,每一位的復位值都是0,當我們把某一位設定為1的時候,這一位的優先順序就比其他位的優先順序高了。
當進入低優先順序中斷中執行時,如又發生高優先順序的中斷,則立刻進入高優先順序中斷執行,處理完高優先順序極中斷後,再返回處理低優先順序中斷,這個過程就叫做中斷巢狀,也稱為搶佔。
所以搶佔優先順序的概念就是,優先順序高的中斷可以打斷優先順序低的中斷的執行,從而形成巢狀。當然反過來,優先順序低的中斷是不能打斷優先順序高的中斷的。
那麼既然有搶佔優先順序,自然就有非搶佔優先順序,也稱為固有優先順序。在“終端查詢序列”表中的最後一列給出的技術固有優先順序,請注意,在中斷優先順序的編號中,一般是數字越小優先順序越高。從表中可以看出一共有1~6共6級優先順序,這裡的優先順序與搶佔優先順序的一個不同點就是,它不具有搶佔的特性,也就是說即使在低優先順序中斷執行過程中又發生了高優先順序的中斷,那麼這個高優先順序的中斷也只能等到低優先順序中斷完成後才能得到響應。既然不能搶佔,那麼這個優先順序有什麼作用呢?
答案是多箇中斷同時存在時的仲裁。比如說有多箇中斷同時發生了,當然實際上發生這種情況的機率很低,但另外一種情況就常見的多了,那就是出於某種原因我們暫時關閉了總中斷,即EA=0,執行完一段程式碼後又重新使能了總中斷,即EA=1,那麼在這段時間裡就很可能有多箇中斷都同時發生了,但因為總中斷是關閉的,所以它們當時都得不到響應,而當總中斷再次使能後,它們就會在同時請求響應了,很明顯,這時也必須有個先後順序才行,這就是非搶佔優先順序的作用。
搶佔優先順序和非搶佔優先順序的協同,可以使微控制器中斷系統有條不紊的工作,既不會無休止的巢狀,又可以保證必要時緊急任務得到優先處理。