

6.5單片機中斷系統
6.5.1中斷的產生背景
請設想這樣一個場景:此刻我正在廚房用煤氣燒一壺水,而燒開一壺水剛好需要10分鐘,我是一個主體,燒水是一個目的,而且我只能時時刻刻在這里燒水,因為一旦水開了溢出來澆滅煤氣的話,有可能引發一場災難。但就在這個時候呢,我又聽到了電視里傳來《天龍八部》的主題歌,馬上就要開演了,我真想奪門而出,去看我最喜歡的電視劇。然而,聽到這個水壺發出的“咕嘟”的聲音,我清楚:除非等水燒開了,否則我是無法享受我喜歡的電視劇的。
這里邊主體只有一個我,而我要做的有兩件事情,一個是看電視,一個是燒水,而電視和燒水是兩個獨立的客體,它們是同時進行的。其中燒水需要10分鐘,但不需要了解燒水的過程,只需要得到水燒開的這樣一個結果就行了,提下水壺和關閉煤氣只需要幾秒的時間而已。所以采取的辦法就是:燒水的時候,定上一個鬧鐘,定時10分鐘,然后我就可以安心看電視了。當10分鐘時間到了,鬧鐘響了,此刻水也燒開了,我就過去把煤氣滅掉,然后繼續回來看電視就可以了。
這個場景和單片機有什么關系呢?
在單片機的程序處理過程中也有很多類似的場景,當單片機正在專心致志的做一件事情(看電視)的時候,總會有一件或者多件緊迫或者不緊迫的事情發生,需要去關注,有一些需要停下手頭的工作去馬上去處理(比如水開了),只有處理完了,才能回頭繼續完成剛才的工作(看電視)。這種情況下單片機的中斷系統就該發揮它的強大作用了。合理巧妙的利用中斷,不僅可以使單片機獲得處理突發狀況的能力,而且可以讓它能夠“同時”完成多項任務。
6.5.2定時器中斷的應用
在第5章學過了定時器,實際應用中定時器一般用法都是采取中斷方式來做的,在第5章采用的是查詢法,使用if(TF0==1)語句的目的是明確告訴讀者,定時器和中斷不是一回事,定時器是單片機模塊的一個資源,確確實實存在的一個模塊,而中斷是單片機的一種運行機制。尤其是初學者,很多人會誤以為定時器和中斷是一個東西,只有定時器才會觸發中斷,但實際上很多事件都會觸發中斷,除了“燒水”,還有“有人按門鈴”,“來電話了”等。
標準51單片機控制中斷的寄存器有兩個,一個是中斷使能寄存器,另一個是中斷優先級寄存器,這里先介紹中斷使能寄存器,如表6-1和表6-2所示。隨著一些增強型51單片機的問世,可能會有增加的寄存器,大家理解了這里所講的,其它的通過自己研讀數據手冊就可以理解明白并且用起來了。
表6-1 IE——中斷使能寄存器的位分配(地址0xA8、可位尋址)

表6-2 IE——中斷使能寄存器的位描述

中斷使能寄存器IE的位0~5控制了6個中斷使能,而第6位沒有用到,第7位是總開關。總開關就相當于家里或者學生宿舍里的那個電源總閘門,而0~5位這6個位相當于每個分開關。也就是說,只要用到中斷,就要寫EA = 1這一句打開中斷總開關,然后用到哪個分中斷,再打開相對應的控制位就可以了。
現在就把前面的數碼管動態顯示的程序改用中斷再實現出來,同時數碼管顯示抖動和“鬼影”也一并處理掉了。程序運行的流程跟圖6-1所示的流程圖是基本一致的,但因為加入了中斷,所以整個流程被分成了兩部分,轉換為數碼管顯示字符的部分還留在主循環內,而實現1秒定時和動態掃描部分則移到了中斷函數內,并加入了消隱的處理。下面來看程序:
#include
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[6] = { //數碼管顯示緩沖區,初值0xFF確保啟動時都不亮
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char i = 0; //動態掃描的索引
unsigned int cnt = 0; //記錄T0中斷次數
unsigned char flag1s = 0; //1秒定時標志
void main()
{
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 (flag1s == 1) //判斷1秒定時標志
{
flag1s = 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];
}
}
}
/* 定時器0中斷服務函數 */
void InterruptTimer0() interrupt 1
{
TH0 = 0xFC; //重新加載初值
TL0 = 0x67;
cnt++; //中斷次數計數值加1
if (cnt >= 1000) //中斷1000次即1秒
{
cnt = 0; //清零計數值以重新開始下1秒計時
flag1s = 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;
}
}
先把程序抄下來,編譯下載到單片機里運行,看看實際效果。是否可以看到,近乎完美的顯示效果經過努力終于做成功了,下面來解析一下這個程序。
在這個程序中,有兩個函數,一個是主函數,一個是中斷服務函數。主函數main()就不用說了,重點強調一下中斷服務函數,它的書寫格式是固定的,首先中斷函數前邊void表示函數返回空,即中斷函數不返回任何值,函數名是InterruptTimer0(),這個函數名在符合函數命名規則的前提下可以隨便取,取這個名字是為了方便區分和記憶,而后是interrupt這個關鍵字,一定不能錯,這是中斷特有的關鍵字,另外后邊還有個數字1,這個數字1怎么來的呢?來看表6-3。
表6-3 中斷查詢序列

這個表格同樣不需要記憶,有需要的時候過來查。第二行的T0中斷,要使能這個中斷那么就要把它的中斷使能位ET0置1,當它的中斷標志位TF0變為1時,就會觸發T0中斷了,那么這時就應該來執行中斷函數了,單片機又怎樣找到這個中斷函數呢?靠的就是中斷向量地址,所以interrupt后面中斷函數編號的數字x就是根據中斷向量得出的,它的計算方法是x*8+3=向量地址。當然表中都已經給算好放在第一欄了,可以直接查出來用就行了。到此為止,中斷函數的命名規則就都搞清楚了。
中斷函數寫好后,每當滿足中斷條件而觸發中斷后,系統就會自動來調用中斷函數。比如前面這個程序,平時一直在主程序while(1)的循環中執行,假如程序有100行,當執行到50行時,定時器溢出了,那么單片機就會立刻跑到中斷函數中執行中斷程序,中斷程序執行完畢后再自動返回到剛才的第50行處繼續執行下面的程序,這樣就保證了動態顯示間隔是固定的1ms,不會因為程序執行時間不一致的原因導致數碼管顯示的抖動了。
6.5.3中斷的優先級
中斷優先級的內容,本小節先做簡單介紹,后邊實際應用的時候再詳細介紹。
在講中斷產生背景的時候,僅僅講了看電視和燒水的例子,但是實際生活當中還有更復雜的,比如我正在看電視,這個時候來電話了,我要進入接電話的“中斷”程序當中去,就在接電話的同時,聽到了水開的聲音,水開的“中斷”也發生了,我就必須要放下手上的電話,先把煤氣關掉,然后再回來聽電話,最后聽完了電話再看電視,這里就產生了一個優先級的問題。
還有一種情況,我在看電視的時候,這個時候聽到水開的聲音,水開的“中斷”發生了,我要進入關煤氣的“中斷”程序當中,而在關煤氣的同時,電話聲音響了,而這個時候的處理方式是先把煤氣關閉,再去接聽電話,最后再看電視。
從這兩個過程中,可以得到一個結論,就是最最緊急的事情,一旦發生后,不管當時處在哪個“程序”當中,必須先去處理最最緊急的事情,處理完畢后再去解決其它事情。在單片機程序當中有時候也是這樣的,有一般緊急的中斷,有特別緊急的中斷,這取決于具體的系統設計,這就涉及到中斷優先級和中斷嵌套的概念,在本章節先簡單介紹一下相關寄存器,不做例程說明。
中斷優先級有兩種,搶占優先級和固有優先級。先介紹搶占優先級,如表6-4和表6-5。
表6-4 IP——中斷優先級寄存器的位分配(地址0xB8、可位尋址)

表6-5 IP——中斷優先級寄存器的位描述

IP這個寄存器的每一位,表示對應中斷的搶占優先級,每一位的復位值都是0,當把某一位設置為1的時候,這一位的優先級就比其它位的優先級高了。比如設置了PT0位為1后,當單片機在主循環或者任何其它中斷程序中執行時,一旦定時器T0發生中斷,作為更高的優先級,程序馬上就會跑到T0的中斷程序中來執行。反過來,當單片機正在T0中斷程序中執行時,如果有其它中斷發生了,還是會繼續執行T0中斷程序,直到把T0中的中斷程序執行完畢以后,才會去執行其它中斷程序。
當進入低優先級中斷中執行時,如又發生了高優先級的中斷,則立刻進入高優先級中斷執行,處理完高優先級級中斷后,再返回處理低優先級中斷,這個過程就叫做中斷嵌套,也稱為搶占。所以搶占優先級的概念就是,優先級高的中斷可以打斷優先級低的中斷的執行,從而形成嵌套。當然反過來,優先級低的中斷是不能打斷優先級高的中斷的。
那么既然有搶占優先級,自然就也有非搶占優先級了,也稱為固有優先級。在表6-3中的最后一列給出的就是固有優先級,請注意,在中斷優先級的編號中,一般都是數字越小優先級越高。從表中可以看到一共有1~6共6級的優先級,這里的優先級與搶占優先級的不同點就是,它不具有搶占的特性,也就是說即使在低優先級中斷執行過程中又發生了高優先級的中斷,那么這個高優先級的中斷也只能等到低優先級中斷執行完后才能得到響應。既然不能搶占,那么這個優先級有什么用呢?
答案是多個中斷同時存在時的仲裁。比如說有多個中斷同時發生了,當然實際上發生這種情況的概率很低,但另外一種情況就常見的多了,那就是出于某種原因暫時關閉了總中斷,即EA=0,執行完一段代碼后又重新使能了總中斷,即EA=1,那么在這段時間里就很可能有多個中斷都發生了,但因為總中斷是關閉的,所以它們當時都得不到響應,而當總中斷再次使能后,它們就會同時請求響應,很明顯,這時也必需有個先后順序才行,這就是非搶占優先級的作用——如表6-3中,誰優先級最高先響應誰,然后按編號排隊,依次得到響應。
搶占優先級和非搶占優先級的協同,可以使單片機中斷系統有條不紊的工作,既不會無休止的嵌套,又可以保證必要時緊急任務得到優先處理。在后續的學習過程中,中斷系統與讀者如影隨形,處處都有它的身影,隨著學習的深入,相信會對它的理解也會更加的深入。
6.6練習題
1、掌握C語言數組的概念、定義和應用。
2、掌握if語句和switch語句的用法及區別,編程的時候能夠正確選擇使用哪個語句。
3、徹底理解中斷的原理和應用方法,關閉教程自己獨立把本章節程序編寫完畢并且下載到實驗板上實踐。
4、嘗試修改程序,讓數碼管只顯示有效位,也就是高位的0不顯示。
5、嘗試寫一個從999999開始倒計時的程序,并且改用定時器T1的中斷來完成,通過寫這個程序,熟練掌握定時器和中斷的應用。
審核編輯 黃宇
-
數碼管
+關注
關注
32文章
1894瀏覽量
94131
發布評論請先 登錄
數碼管 選型手冊2025年最新版
【EASY EAI Nano-TB(RV1126B)開發板試用】+串行數碼管的顯示驅動
原廠 FZH853 8×4顯示掃描模式單線通訊LED恒流驅動專用電路
第7章 變量進階與點陣LED(7.3 7.4)
第6章 中斷與數碼管動態顯示(6.1 6.2)
【瑞薩RA6E2】ADC數據采集與數碼管顯示
原廠 FZH114C 一款LED(發光二極管、數碼管、點陣屏)驅動控制專用芯片
FZH114 LED(發光二極管、數碼管、點陣屏)驅動控制專用芯片,集成數字通訊電路、 解碼電路、數據鎖存器、震蕩器
【「高速數字設計(基礎篇)」閱讀體驗】+第6章閱讀體驗
級聯動態掃描顯示數碼管問題
抗噪數顯驅動數碼管顯示IC芯片VK1616
【應用】工業現場的“智能顯示管家”:億佰特EID051-1xx系列 Modbus數碼管解析
第6章 中斷與數碼管動態顯示(6.5 6.6)
評論