久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 32568|回復: 44
收起左側

第六章 中斷的學習

  [復制鏈接]
ID:1 發表于 2013-7-11 19:25 | 顯示全部樓層 |閱讀模式
   中斷是單片機系統的重點中的重點,因為有了中斷,單片機就具備了快速協調多模塊工作的能力,大家對本章節內容要多研究,最終要完全理解并且掌握。
6.1 C語言的數組6.1.1 數組的基本概念
我們第四章學過變量的基本類型,比如charint等等。這種類型描述的數據是比較有限的,當我們要處理非常大量數據的時候,就可以用到數組了,比如我們上節課的那個數碼管的真值表,我們就可以用一個數組來表達。
從概念上講,數組是具有相同數據類型的有序數據的組合,一般來講,數組定義后滿足以下三個條件。
(1)具有相同的數據類型;
(2)具有相同的名字;
(3)在存儲器中是被連續存放的。
    比如我們上節課定義的那個數碼管真值表,如果我們把關鍵字code去掉,數組元素將被保存在RAM中,在程序中可讀可寫,同時我們也可以在中括號里邊標明這個數組元素的個數,比如:
unsigned  char LedChar[16] = {
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e};
在這個數組中的每個值都稱之為數組的一個元素,這些元素都具備相同的數據類型就是unsigned char型,他們有一個共同的名字LedChar,不管放到RAM中還是FLASH中,他們都是存放在一塊連續的存儲空間里的。
有一點要特別注意,這個數組一共有16(中括號里面的數值)個元素,但是數組的元素的表達方式下標是從0開始,因此實際上上邊這個數組的首個元素LedChar[0]的值是0xC0,LedChar[15]的值是0x8e,下標從015一共是16個元素。
LedChar這個數組只有一個下標,我們稱之為一維數組,還有兩個下標或者多個下標的,我們稱之為多維數組。比如unsigned char a[2][3];表示這是一個23列的二維數組。在大多數情況下我們使用的是一維數組,對于初學來說,我們先來研究一維數組,多維數組遇到了再了解。
6.1.2 數組的聲明
一維數組的聲明格式如下:
數據類型   數組名[數組長度
(1)數組的數據類型聲明的是該數組的每個元素的類型,即一個數組中的元素具有相同的數據類型。
(2)數組名的聲明要符合C語言固定的標識符的聲明要求,只能由字母、數字、下劃線這三種符號組成,且第一個字符只能是字母或者下劃線。
(3)方括號中的數組長度是一個常量或常量表達式,并且必須是正整數。
6.1.3 數組的初始化
數組在進行聲明的同時可以進行初始化操作,格式如下:
數據類型   數組名[數組長度] = {初值列表}
還是以上節課我們用的數碼管的真值表為例來講解注意事項。
    unsigned  char LedChar[16] = {
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8e};
(1)初值列表里的數據之間要用逗號隔開。
(2)初值列表里的初值的數量必須小于或者等于數組長度,當小于數組長度時,數組的后邊沒有賦初值的元素由系統自動賦值0
(3)若給數組的所有元素賦初值,可以省略數組的長度,上節課的例子中我們實際上已經省略了數組的長度。
(4)系統為數組分配連續的存儲單元的時候,數組元素的相對次序由下標來決定,就是說LedChar[0]LedChar[1]... ... LedChar[15]是按照順序排下來的。
6.1.4 數組的使用和賦值
C語言程序中,是不能一次使用整個數組的,只能使用單個數組元素。一個數組元素相當于一個變量,使用數組元素的時候與使用相同數據類型的變量的方法一樣。比如這個LedChar這個數組,如果沒加code關鍵字,那么它可讀可寫,我們可以寫成a = LedChar[0]這樣來把數組的一個元素的值送個a這個變量,也可以寫成LedChar[0] = a這樣把a這個變量的值送給數組的一個元素,以下三點要注意:
(1)引用數組的時候,那個方括號里的數字代表的是數組元素的下標,而數組初始化的時候方括號里的數字代表的是這個數組元素的個數。
(2)數組元素的方括號里的下標可以是整型常數,整型變量或者表達式,而數組初始化的時候方括號里的數字必須是常數不能是變量。
(3)數組整體賦值只可以在初始化的時候操作,功能程序只能對單個元素賦值。
6.2 if語句
if語句已經不陌生了,前邊程序我們其實已經用過了,這里我們系統的介紹一下,方便后邊的深入學習。if語句有兩個關鍵字:ifelse,把這兩個關鍵字翻譯一下就是:“如果”和“否則”。if語句一共有三種格式,我們分別來看。
1.if語句的默認形式。
if (條件表達式)
      {語句 1;}
    其執行過程是,if(如果)條件表達式的值為“真”,則執行語句1;如果條件表達式的值為“假”,則不執行語句1。真和假的概念不再贅述,參考第五章。
    這里要提醒一句,C語言一個分號表示一句語句的結束,因此如果if后邊只有一條執行語句的時候,可以省略大括號,但是如果有多條執行語句的話,必須加上大括號。
    我們上節課的語句就很好理解了if(16 ==j) { j = 0;},如果j等于16的時候,括號里的值才是“真”,那么就執行j=0這一句,如果j不等于16,那么里邊就為“假”,就不執行這一句。
2.if...else語句
    有些情況下,我們除了判斷一下if括號里的是否滿足條件,執行相應的語句,在不滿足條件的時候,我們又要執行另外相應的語句,這個時候就用到了if...else語句,它的基本的語法形式是:
if (條件表達式)
    {語句 1;}
else
    {語句 2;}
比如上節課的后半段程序我們也可以寫成:
                P0 = LedChar[j];    //把數組里的對應值送給P0
                if(15 == j)         //當顯示到F后,歸0重新開始
                {j = 0;}
                else
                {j++;}
    這個程序大家可以改改下載進去試試,程序邏輯大家自己動腦分析一下,我就不解釋了。         
3.if....else if語句
    if...esle語句是一個二選一的語句,或者執行if條件下的語句,或者執行else條件下的語句。還有一種多選一的用法就是if...else if語句。他的基本語法格式是:
if (條件表達式1)            {語句 1;}
else if (條件表達式2)        {語句 2; }
else if (條件表達式3)        {語句 3; }
... ...
else                       {語句 n;}
他的執行過程是:依次判斷條件表達式的值,當出現某個值為“真”時,則執行相對應的語句,然后跳出整個if的語句塊,執行“語句n”后邊的程序;如果所有的表達式都為“假”,則執行“語句n”后,再執行“語句n”后邊的程序。
if語句在C語言編程的過程中使用頻率很高,用法也簡單,所以必須要熟練掌握。
6.3 switch語句
if....else語句在處理多分支的時候,分支太多就會顯得不方便,且容易出現ifelse配對出現錯誤的情況,在C語言中提供了另外一種多分支選擇的語句——switch語句,它的基本語法格式如下:
switch (表達式)
{
    case 常量表達式1:執行語句1
    case 常量表達式2:執行語句2
    ......
    case 常量表達式n:執行語句n
    default: 執行語句n+1;
}
它的執行過程是:首先計算“表達式”的值,然后從第一個case開始,與“常量表達式x”進行比較,如果與當前常量表達式的值不相等,那么就不執行冒號后邊的程序,一旦發現和一個常量表達式的值相等了,那么他會執行之后所有的,注意是所有的“執行語句”,顯然這不是我們想要的結果。
C語言中,有一條break語句,作用是跳出當前循環語句,不管是forwhile循環,還是switch循環,都可以用其搭配使用跳出循環switch語句一共有n+1種可能,而我們希望要的是一條多選一的語句,只執行其中一條然后直接退出該循環,不再執行下邊的任何語句,這個時候就需要用到break語句,比如我們在switch表達式上加上break語句,如下:
switch (表達式)
{
    case 常量表達式1:執行語句1break
    case 常量表達式2:執行語句2break
    ......
    case 常量表達式n:執行語句nbreak
    default:語句n+1;
}
加了這個break語句后,一旦“常量表達式x”與“表達式”相等了,那就執行“執行語句x”,執行完畢后,由于有了break,直接跳出switch語句,執行switch語句循環后邊的程序了,這樣就可以避免執行不必要的語句。了解了這個switch語句,我們將會在本章程序中使用鞏固。
6.4 數碼管的動態顯示6.4.1 動態顯示的基本原理
我們在上一章學習數碼管靜態顯示的時候說到,74HC138只能在同一時刻導通一個三極管,而我們的數碼管是靠了6個三極管來控制,那我們如何來讓數碼管同時顯示呢?這就用到了我們這節課的動態顯示。
多個數碼管顯示數字的時候,我們實際上是輪流點亮數碼管(一個時刻內只有一個數碼管是亮的),利用人眼的視覺暫留現象(也叫余輝效應),就可以做到看起來是所有數碼管都同時亮了,這就是動態掃描顯示的含義。
例如:我們有2個數碼管,我們要顯示“12”這個數字,讓高位的位選三極管導通,然后給它賦值“1”,延時一定時間后讓低位的位選三極管導通,然后給它賦值“2”。把這個流程以一定的速度循環運行就可以讓數碼管顯示出“12”,由于交替速度非常快,人肉眼識別到的就是“12”這個數字。
那么一個數碼管需要點亮多長時間呢?也就是說要多長時間完成一次全部數碼管的掃描呢(很明顯:整體掃描時間=單個數碼管點亮時間*數碼管個數)?答案是:10ms以內。當電視機和顯示器還處在CRT(電子顯像管)時代時,有一句很流行的廣告語——“100Hz無閃爍”,沒錯,只要刷新率大于100Hz,即刷新時間小于10ms,就可以做到無閃爍,這也就是我們的動態掃描的硬性指標。那么你也許會問,有最小值的限制嗎?理論上沒有,但實際上做到更快的刷新卻沒有任何進步的意義了,因為已經無閃爍了,再快也還是無閃爍,只是徒然增加CPU的負荷而已(因為1秒內要執行更多次的掃描程序)。所以,通常我們設計程序的時候,都是取一個接近10ms,又比較規整的值就行了。我們板子上有6個數碼管,我們下面用程序來驗證一下數碼管動態顯示程序。
#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,
};
void main()   
{
    unsigned int counter = 0;
    unsigned char j = 0;
    unsigned long stopwatch = 0;
    unsigned char LedNumber[6] = {0};
    ENLED = 0; ADDR3 = 1;P0 = 0XFF;   //74HC138P0初始化部分
    TMOD = 0x01; //設置定時器0為模式1
    TH0  = 0xFC;
    TL0  = 0x67; //定時值初值,定時1ms
    TR0  = 1;    //打開定時器0
    while(1)
    {
        if(1 == TF0)             //判斷定時器0是否溢出
        {
            TF0 = 0;
            TH0 = 0xFC;        //一旦溢出后,重新賦值
            TL0 = 0x67;
            counter++;
            if(1000 == counter)     //判斷定時器0溢出是否達到50
            {
                counter = 0;
                stopwatch++;                  //秒表數值一秒加1
                LedNumber[0] = stopwatch%10;
                LedNumber[1] = stopwatch/10%10;
                LedNumber[2] = stopwatch/100%10;     
                LedNumber[3] = stopwatch/1000%10;  //數碼管顯示值計算
                LedNumber[4] = stopwatch/10000%10;
                LedNumber[5] = stopwatch/100000%10;
            }
          if (0==j)
         { ADDR0=0; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[0]]; }
          else if (1==j)
         { ADDR0=1; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[1]]; }
          else if (2==j)
         { ADDR0=0; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[2]]; }
          else if (3==j)
         { ADDR0=1; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[3]]; }
          else if (4==j)
         { ADDR0=0; ADDR1=0; ADDR2=1; j++; P0=LedChar[LedNumber[4]]; }
          else if (5==j)
         { ADDR0=1; ADDR1=0; ADDR2=1; j=0; P0=LedChar[LedNumber[5]]; }
       }                   //數碼管動態刷新部分
    }
}
這程序,大家自己抄到Keil中,然后邊抄邊理解,最終下載到實驗板上實驗一下效果。其中下邊的if...else語句就是每1ms快速的刷新一個數碼管,這樣6個數碼管整體刷新一遍的時間就是6ms,視覺上就是6個數碼管無閃爍的同時亮起來了
另外一個簡單知識點這個地方也提一下,其實屬于小學三年級知識,但是很多同學剛接觸C語言,可能遇到了也會發懵。就是在數碼管顯示值計算這個地方,相信小學我們沒學小數之前,除法運算里邊有“被除數”、“除數”、“商”、“余數”這四個概念年。而在我們C語言中,“/”等同于數學里的除法運算,而“%”等同于我們小學學的求余數運算。如果是123456這個數字,我們要正常顯示在數碼管上,個位顯示,就是直接對10取余數,這個“6”就出來了,十位數字就是先除以10,然后再對10取余數,以此類推,就把6個數字全部顯示出來了。
對于多選一的動態刷新數碼管的方式,我們如果用switch會有更好的效果,大家來看一下我們用switch語句完成的情況。
#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,
};
void main()   
{
    unsigned int counter = 0;
    unsigned char j = 0;
    unsigned long stopwatch =0;
    unsigned char LedNumber[6]={0};
    ENLED = 0; ADDR3 = 1;P0 = 0XFF;   //74HC138P0初始化部分
    TMOD = 0x01; //設置定時器0為模式1
    TH0  = 0xFC;
    TL0  = 0x67; //定時值初值,定時1ms
    TR0  = 1; //打開定時器0
    while(1)
    {
        if(1 == TF0)             //判斷定時器0是否溢出
        {
            TF0 = 0;
            TH0 = 0xFC;        //一旦溢出后,重新賦值
            TL0 = 0x67;
            counter++;
            if(1000 == counter)     //判斷定時器0溢出是否達到1000
            {
                counter = 0;
                stopwatch++;                  //秒表數值一秒加1
                LedNumber[0] = stopwatch%10;
                LedNumber[1] = stopwatch/10%10;
                LedNumber[2] = stopwatch/100%10;     
                LedNumber[3] = stopwatch/1000%10;  //數碼管顯示值計算
                LedNumber[4] = stopwatch/10000%10;
                LedNumber[5] = stopwatch/100000%10;
            }
            switch(j)
            {
            case 0: ADDR0=0; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[0]];break;
            case 1: ADDR0=1; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[1]];break;
            case 2: ADDR0=0; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[2]];break;
            case 3: ADDR0=1; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[3]];break;
            case 4: ADDR0=0; ADDR1=0; ADDR2=1; j++; P0=LedChar[LedNumber[4]];break;
            case 5: ADDR0=1; ADDR1=0; ADDR2=1; j=0; P0=LedChar[LedNumber[5]];break;
            default: break;
             }
       }                   //數碼管動態刷新部分
    }
}
大家是否能感覺到switch語句比if...else語句顯得要整齊的多?
6.4.2 數碼管消隱處理
不知道細心的同學能否發現,我們的兩次數碼管動態刷新顯示的時候似乎并不是那么完美,第一個小問題,大家仔細看,數碼管的不應該顯示的段,似乎有微微的發亮,這種現象叫做“鬼影”,這個“鬼影”嚴重影響了我們的視覺效果,我們該如何解決呢?
同學們今后可能會遇到各種各樣的問題,可能有很多我是沒有講過的問題,遇到問題怎么辦呢?大家要相信,你作為初學者,遇到的問題肯定不是第一個遇到的,肯定有前輩會遇到同類問題,他們一般會在網上發表各種帖子,各種討論,所以大家遇到問題,首先解決方法就應該形成一個到網上搜索的條件反射,這個問題大家可以到網上搜:“數碼管消隱”或者“數碼管鬼影解決”,多找相關關鍵詞搜索,會搜索也是一種能力。
大家在網上搜了一下會發現,解決這類問題的普遍兩個方法,其中之一是延時,延時之后我們肉眼就可能看不到這個“鬼影”了。但是延時是一個非常拙劣的手段,且不說延時多久能讓我們看不到“鬼影”,延時后,我們的數碼管亮度會普遍降低。我們解決問題呢,不能只知其然,不知其所以然,所以我們首先要弄懂為什么會出現“鬼影”。
“鬼影”的出現,主要是因為我們數碼管位選和段選產生的瞬態所造成的。舉個簡單例子,我們在數碼管動態刷新的那部分程序中,實際上每一個數碼管點亮的持續時間是1ms的時間,1ms后進行下個數碼管的切換。在進行數碼管切換的時候,比如我們從case 5要切換到case 0的時候,case 5的位選用的是ADDR0=1; ADDR1=0; ADDR2=1;假如此刻case5也就是最高位數碼管對應的值是0。我們要切換成的case 0的數碼管位選是ADDR0=0; ADDR1=0; ADDR2=0;而對應的數碼管的值假如是1
因為我們的C語言程序是一句一句順序往下執行的,每一條語句都會占用一定的時間,即使這個時間非常非常短暫。但是當我們把“ADDR0=1”改變成“ADDR0=0”的時候,這個瞬間存在了一個中間狀態ADDR0=0; ADDR1=0; ADDR2=1;在這個瞬間上,我們就給case 4對應的數碼管DS5瞬間賦值了0。當我們全部寫完了ADDR0=0; ADDR1=0; ADDR2=0;后,這個時候,我們的P0還沒有正式賦值,而P0此刻卻保持了前一次的值,也就是在這個瞬間,我們又給case 0對應的數碼管DS1賦值了一個0。直到我們把case 0后邊的語句全部完成后,我們的刷新才正式完成。而在這個刷新過程中,有2次瞬間我們給了錯誤的數碼管賦值,雖然很弱(因為亮的時間很短),但是我們還是能夠發現。
那弄懂了原理后,解決起來就不是困難的事情了,我們只要避開這個瞬態就可以了。不產生瞬態的方法是,我們在進行刷新的賦值語句期間,避免一切數碼管的賦值即可。方法有兩個,一個方法是刷新之前關閉所有的段,改變好了位選后,再打開段即可;第二個方法是關閉數碼管的位,賦值過程都做好后,再重新打開即可。這個不是很難,答案我都公布一下。
關閉段:在switch(j)這句程序之前,加一句P0=0XFF;這樣就把數碼管所有的段都關閉了,當把“ADDR”的值全部搞定后,再給P0賦對應的值即可。
關閉位:在switch(j)這句程序之前,加上一句ENLED=1;等到把“ADDR=0; ADDR1=0; ADDR2=0; P0=LedChar[LedNumber[0]];這幾條刷新程序全部寫完后,再加上一句ENLED=0;然后再進行break操作即可。
這個地方稍微有點邏輯思路在里邊,大家一定要理解深刻,深刻理解,徹底弄明白,把這個瞬態弄明白,后邊很多牽扯到此類情況的問題,我們都可以一并搞定。
上邊的數碼管程序還有第二個問題,大家仔細看,我們的數碼管上的數字每一秒變化一次,變化的時候,不參加變化的數碼管可能出現一次抖動,這個抖動沒有什么專業的名字,我們就稱之為數碼管抖動吧。這種數碼管抖動是什么原因造成的呢?為何在數據改變的時候才抖動呢?
我們來看我們的程序。我們的程序在定時到1秒的時候,執行了“數碼管顯示值計算”這個過程,一個32位的除法運算,實際上是比較耗費時間的,至于這一段程序占用了多少時間,大家可以通過第四章講的Debug進入看看這段程序運行一共占據了多少時間。由于達到1秒的時候,程序多運行了這么一段,導致了某個數碼管的點亮時間比其他情況下要長一些,時間是1ms+程序消耗時間,于此同時,其它的數碼管就熄滅了5ms+程序消耗時間,如果這個程序消耗時間非常短,那么可以忽略不計,但很明顯,現在這段程序已經比較長了,嚴重影響我們的視覺效果了,所以我們要采取另外一種思路去解決這個問題。
6.5 中斷的學習6.5.1 中斷的產生背景
比如此刻我正在廚房用煤氣燒一壺水,燒開一壺水剛好需要10分鐘。我是一個主體,燒水是一個目的,而且我只能時時刻刻在這里燒水,因為一旦水開了,溢出來澆滅煤氣的話,有可能引發一場災難。而這個時候呢,我聽到了電視里傳來《天龍八部》的主題歌,馬上就要開演了,我真想奪門而出,去看我最喜歡的電視劇。然而,聽到這個水壺發出的“咕嘟”的聲音,我清楚:除非水開了,否則我是無法享受我喜歡的電視劇的。
這里邊主體只有我一個,而我要做的有兩件事情,一個是看電視,一個是燒水,而電視和燒水是兩個獨立的客體,他們是同時進行的。其中燒水需要10分鐘,但不需要了解燒水的過程的,只需要得到水燒開的這樣一個結果就行了,提下水壺和關閉煤氣只需要幾秒的時間而已。所以我們采取的辦法就是:燒水的時候,定上一個鬧鐘,定時10分鐘,然后我就可以安心看電視了。當10分鐘時間到了,鬧鐘響了,此刻水也燒開了,我就過去把煤氣滅掉,然后繼續回來看電視就可以了。
這個場景和單片機有什么關系呢?
在單片機的程序處理過程中也有很多類似的場景,當單片機正在專心致志的做一件事情的時候(如看電視),總會有一件或者多件緊迫或者不緊迫的事情發生,需要我們去關注,有一些需要我們停下手頭的工作去馬上完成(比如水開了),只有處理完,才能回頭繼續完成剛才的工作(看電視)。如果在這個地方用上了單片機的中斷機制,不僅僅我擁有了處理意外情況的能力,而且如果我能夠充分發揮這個機制的妙用,就可以“同時”完成多個任務了。如果還是一知半解關于中斷更詳細的介紹可以看這里: http://www.zg4o1577.cn/mcuteach/234.html
6.5.2 定時器中斷應用方法
在第五章我們學過定時器,而實際上定時器一般用法都是采取中斷方式來做的,我是故意在第五章用查詢法,就是使用if(TR0 ==0)這樣的語句先講定時器,目的是明確告訴同學們,定時器和中斷不是一回事,定時器是單片機模塊的一個資源,確確實實存在的一個模塊,而中斷,是單片機的一種運行機制。尤其是初學者們,很多人會誤以為定時器和中斷是一個東西,只有定時器才會觸發中斷,但實際上很多事件都會觸發中斷的,除了“燒水”,還有“有人按門鈴”,“來電話了”等等。
標準51中與中斷相關的寄存器,一共有2個,其中1個是中斷使能寄存器,另外1個是中斷優先級寄存器,這里先介紹中斷使能寄存器。隨著一些增強型51單片機的問世,可能會有增加的寄存器,大家這些理解了這里所講的,其他的通過自己研讀數據手冊全部可以理解明白并且使用起來。
表6-1 IE--中斷使能寄存器(地址:A8H)
       可位尋址;復位值:0x00;復位源:任何復位
7
6
5
4
3
2
1
0
符號
EA
--
ET2
ES
ET1
EX1
ET0
EX0
表6-2 IE--中斷使能寄存器的位描述
符號
描述
7
EA
總中斷使能位,相當于總開關
6
--
--
5
ET2
定時器2中斷使能
4
ES
串口中斷使能
3
ET1
定時器1溢出中斷使能
2
EX1
外部中斷1使能
1
ET0
定時器0中斷使能
0
EX0
外部中斷0使能

中斷使能寄存器IE控制了6個中斷使能,其中第6位暫時不用,第七位是總開關,相當于我們家里或者學生宿舍里的那個電源總閘門。而05位這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 LedNumber[6] = {0}; //定義全局變量
unsigned char j = 0;
unsigned int counter = 0;
void main()
{
    unsigned long stopwatch =0;
    ENLED = 0; ADDR3 = 1; P0 = 0XFF;   //74HC138P0初始化部分
    TMOD = 0x01;  //設置定時器0為模式1
    TH0  = 0xFC;
    TL0  = 0x67;  //定時值初值,定時1ms
    TR0  = 1;     //打開定時器0
    EA = 1;       //打開中中斷
    ET0 = 1;      //打開定時器0中斷
    while(1)
    {
        if(1000 == counter)     //判斷定時器0溢出是否達到1000
        {
            counter = 0;
            stopwatch++;
            LedNumber[0] = stopwatch%10;
            LedNumber[1] = stopwatch/10%10;
            LedNumber[2] = stopwatch/100%10;
            LedNumber[3] = stopwatch/1000%10;
            LedNumber[4] = stopwatch/10000%10;
            LedNumber[5] = stopwatch/100000%10;
        }
    }
}
void InterruptTimer0() interrupt 1               //中斷函數的特殊寫法,數字’1’為中斷入口號
{
    TH0 = 0xFC;   //溢出后進入中斷重新賦值
    TL0 = 0x67;
    counter++;   //計數值counter1
    P0 = 0xFF;   //消隱
    switch(j)
    {
        case 0: ADDR0=0; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[0]]; break;
        case 1: ADDR0=1; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[1]]; break;
        case 2: ADDR0=0; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[2]]; break;
        case 3: ADDR0=1; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[3]]; break;
        case 4: ADDR0=0; ADDR1=0; ADDR2=1; j++; P0=LedChar[LedNumber[4]]; break;
        case 5: ADDR0=1; ADDR1=0; ADDR2=1; j=0; P0=LedChar[LedNumber[5]]; break;
        default: break;
    }    //動態刷新
}   
大家可以先把這個程序了解明白,下載到單片機里邊實驗一下,看看實際效果。是否可以看出來,近乎完美的顯示效果經過我們的努力終于做成功了。那下面我們還要來解析一下我們的這個程序。
在我們這個程序中,有兩個函數,一個是主函數,一個是中斷函數。主函數main()我們就不用說了,重點強調一下中斷函數,中斷函數的格式是固定的,首先中斷函數前邊void表示函數返回空,即中斷函數不返回任何值,函數名字是InterruptTimer0(),這個函數名字只要符合函數命名規則的前提下我們就可以隨便起,我這樣起名字是為了方便區分和記憶,而后是interrupt這個關鍵字不能錯,這個是中斷特有的關鍵字,另外后邊還有個數字1,這個數字1怎么來的呢?我們先來看一個表格。
6-3 中斷查詢序列
描述
中斷標志
向量地址
中斷使能
默認優先級
外部中斷0
IE0
0003H
EX0
1(最高)
T0中斷
TF0
000BH
ET0
2
外部中斷1
IE1
0013H
EX1
3
T1中斷
TF1
001BH
ET1
4
UART中斷
TI/RI
0023H
ES
5
T2中斷
TF2/EXF2
002BH
ET2
6
這個表格同樣不需要大家記住,需要的時候過來查就可以了。我們現在看第二行T0中斷,它的中斷標志是TF0,也就是當TF0變成1的時候,就會觸發中斷。而在interrupt后邊的數字x的計算方法是 x*8+3=向量地址,T0的向量地址是000BH,那么我們可以求得x的值是1。這樣這個中斷函數名字我們就徹底明白了。
中斷函數和普通函數有個不一樣的地方,普通函數一般是在程序中調用,而中斷函數因為有了中斷入口,達到中斷條件后,他會自動進入程序執行。比如咱這個程序,平時一直在主程序while(1)的循環中運行,假如程序有100行,當運行到了50行的時候,定時器溢出了,那么CPU就會立刻跑到中斷函數中執行中斷程序,中斷程序運行完畢后再自動返回到剛才的第50行處繼續運行下面的程序,這樣就保證了動態刷新是固定的1ms時間,不會因為程序運行時間不一致的原因導致數碼管的抖動了。
6.5.3 中斷的優先級
中斷優先級的內容,大家先通過我的介紹大概了解一下即可,后邊真正實際應用的時候我們再詳細理解。
在講中斷產生背景的時候,我們僅僅講了看電視和燒水的例子,但是實際生活當中還有更復雜的,比如我們正在看電視,這個時候來電話了,我們要進入接電話的“中斷”程序當中去,就在接電話的同時,聽到了水開的聲音,水開的“中斷”也發生了,我們要放下手上的電話,先把煤氣關掉,然后再回來聽電話,最后聽完了電話再看電視,這里就產生了一個優先級的問題。
還有一種情況,我們在看電視的時候,這個時候聽到水開的聲音,水開的“中斷”發生了,我們要進入關煤氣的“中斷”程序當中,而在關煤氣的同時,電話聲音響了,而這個時候,我們的處理方式是先把煤氣關閉,再去接聽電話,最后再看電視。
從這兩個過程中,我們可以得到一個結論,就是最最緊急的事情,一旦發生后,我們不管當時處在哪個“程序”當中,我們必須先去解決最最緊急的事情,解決完畢后再去解決其他事情。在我們的單片機程序當中有時候也是這樣的,有一般緊急的中斷,有特別緊急的中斷,這取決于具體的系統設計,這就牽扯到一個中斷優先級和中斷嵌套的概念,在本章節我們先簡單介紹一下相關寄存器,不做例程說明。
中斷優先級有兩種,一種是搶占優先級,一種是固有優先級,先介紹搶占優先級。
表6-4 IP--中斷優先級寄存器的位分配(地址:B8H)
       可位尋址;復位值:0x00;復位源:任何復位
7
6
5
4
3
2
1
0
符號
--
--
PT2
PS
PT1
PX1
PT0
PX0
6-5 IP--中斷優先級寄存器的位描述(地址:B8H)
符號
描述
7
--
保留
6
--
保留
5
PT2
定時器2中斷優先級控制位
4
PS
串口中斷優先級控制位
3
PT1
定時器1中斷優先級控制位
2
PX1
外部中斷1中斷優先級控制位
1
PT0
定時器0中斷優先級控制位
0
PX0
外部中斷0中斷優先級控制位
這個寄存器的每一位,表示對應的中斷功能的優先級,每一位的復位值都是0,當我們把某一位設置為1的時候,這一位的優先級就比其他位的優先級高。比如我們設置了PT0位為1后,當程序運行在主循環里邊,或者任何其他中斷程序內部的時候,一旦定時器0發生中斷,作為更高級的優先級,程序馬上就會跑到定時器0的中斷程序中運行。同理,當程序此刻運行在定時器0中斷中時,其他低級的中斷發生后,程序還是會繼續運行定時器0中斷程序,直到把定時器0中的中斷程序運行完成后,再會去相應其他中斷程序。
我們在專業的術語中,當進入低級中斷以后,發生高級中斷,我們先進入高級中斷運行,處理完了高級中斷后,返回處理低級中斷,低級中斷處理完了再返回主函數,這種叫做中斷嵌套。在搶占優先級配置過程中,優先級高的中斷是可以搶占優先級低的中斷,形成中斷嵌套的,當然,優先級低的是不能搶占優先級高的中斷的。
第二種是固有優先級,大家可能在看表6-3中斷查詢序列里就看到了有一個中斷優先級列表,在這個列表中,中斷優先級是從高到低排列的。但是固有優先級和搶占優先級不同,首先固有優先級不會形成中斷嵌套,也就是只要當前程序進入中斷執行程序了,其他任何中斷來了,都會先執行完了當前的中斷再回頭響應的。
那這個固有優先級的作用是什么呢?還有一種情況,就是當中斷同時發生,或者是我們在開中斷前,已經有幾個中斷標志位置位了,也就是說我們可以理解為同時檢測到幾個中斷產生了,那么我們會先相應表6-3中的優先級高的中斷,處理完后再來相應優先級低的中斷。
6.6 作業
1、掌握C語言的數組的概念、定義和應用。
2、掌握if語句和switch語句的用法及區別,編程的時候能夠正確選擇使用哪個語句。
3、徹底理解中斷的原理和應用方法,關閉教程自己獨立把本章節程序編寫完畢并且下載到實驗板上實踐。
4、大家嘗試修改程序,讓我們的數碼管只顯示有效位,也就是高位的0不顯示。
5、大家改動程序,寫一個數碼管從999999倒計時程序,并且改用定時器1的中斷來完成,通過寫這個程序,熟練掌握定時器和中斷的應用。

上一課:第五章 定時器和數碼管
下一課:第七章 點陣LED的學習

評分

參與人數 2黑幣 +10 收起 理由
chenjuncom11 + 5 贊一個!
hei51ck + 5 共享資料的黑幣獎勵!

查看全部評分

回復

使用道具 舉報

ID:37147 發表于 2013-9-7 12:04 | 顯示全部樓層
很有用!我要反復的看看
回復

使用道具 舉報

ID:61042 發表于 2014-4-23 22:59 | 顯示全部樓層

高手請幫忙看看這個程序哪里有什么問題?

本帖最后由 lmjnkj 于 2014-4-23 23:02 編輯

include<reg52.h>
#include<intrins.h>
sbit D14=P1^4;
sbit D24=P2^4;
sbit D25=P2^5;
sbit D26=P2^6;
sbit D27=P2^7;
unsigned char da_l,da_h,i;
volatile unsigned char e;
void delay();
void main()
{
    TMOD=0X01;
    EA=1;
    ET0=1;
    TH0=0xb1;
    TL0=0xe0;
    TR0=1;
    P0=0Xff;
    D27=1;
    D27=0;
    D14=1;
    P0=0X00;
    da_l=0xff;//164輸出高電平有效
     //下面循環程序是將164從Q7到Q0全總部輸出為高電平
    for(i=0;i<8;i++)
    {
       D24=0;
       D25=da_l&0x80;
       D24=1;
       da_l<<=1;
    }
    P0=0XFF;
    D26=1;
    da_h=0xfe; //Y為低電平有效
   
    while(1);
   
}
void time0(void) interrupt 1
{
   
   
    TH0=0xb1;
    TL0=0xe0;
    e++;
    if(e==50)
    {
          e=0;
          P0=da_h;
         
      
          da_h=_crol_(da_h,1);//循環移位程序
    }   
    P0=0XFF;
}
回復

使用道具 舉報

ID:78273 發表于 2015-5-28 00:12 | 顯示全部樓層
中斷是單片機系統的重點中的重點,
回復

使用道具 舉報

ID:86177 發表于 2015-7-20 22:15 | 顯示全部樓層
實在是太謝謝你了
回復

使用道具 舉報

ID:88499 發表于 2015-8-27 06:09 | 顯示全部樓層
不錯!
回復

使用道具 舉報

ID:112090 發表于 2016-4-3 20:10 | 顯示全部樓層
你好   我想請教一下  如何高位滅0啊  就是這個教程的作業4
回復

使用道具 舉報

ID:124050 發表于 2016-6-1 16:26 | 顯示全部樓層
超級好人    學習了
回復

使用道具 舉報

ID:126696 發表于 2016-6-15 11:37 | 顯示全部樓層
大好人 先保存 暑假再好好學習
回復

使用道具 舉報

ID:147082 發表于 2016-11-18 11:41 | 顯示全部樓層
那個if(1000==counter)判斷是否溢出50次不是很明白,有沒有大神指點一下
回復

使用道具 舉報

ID:160490 發表于 2017-1-6 19:43 | 顯示全部樓層
太有用了!
回復

使用道具 舉報

ID:151661 發表于 2017-3-25 19:13 來自手機 | 顯示全部樓層
難度逐漸大了。
回復

使用道具 舉報

ID:193871 發表于 2017-4-26 16:11 | 顯示全部樓層
好多中斷啊,學習了
回復

使用道具 舉報

ID:192838 發表于 2017-4-27 20:14 | 顯示全部樓層
Bnuzdaxian 發表于 2016-11-18 11:41
**** 作者被禁止或刪除 內容自動屏蔽 ****

應該是溢出1000次
回復

使用道具 舉報

ID:219424 發表于 2017-7-17 12:47 | 顯示全部樓層
怎么看不到原文呢,前面幾張講得很好,先保存了
回復

使用道具 舉報

ID:214997 發表于 2017-7-19 20:28 | 顯示全部樓層
效果是什么樣的?我是一閃一閃的呢?是這樣嗎?
回復

使用道具 舉報

ID:224296 發表于 2017-8-3 10:24 | 顯示全部樓層
以前就看到這個帖子了,最近在學這個。正好來看看,中斷好像比32的簡單一點
回復

使用道具 舉報

ID:220342 發表于 2017-8-8 09:29 | 顯示全部樓層
Bnuzdaxian 發表于 2016-11-18 11:41
**** 作者被禁止或刪除 內容自動屏蔽 ****

他寫錯了,應該是判斷是否溢出1000次,1000次相當于1秒。
回復

使用道具 舉報

ID:227864 發表于 2017-8-16 11:57 | 顯示全部樓層
講得通俗易懂,很不錯
回復

使用道具 舉報

ID:199513 發表于 2017-9-8 18:27 | 顯示全部樓層
unsigned char LedNumber[6]={0};  各位高手能夠否講解一下,這一句中的   {0}  代表的是啥? 謝謝!
回復

使用道具 舉報

ID:155764 發表于 2017-10-24 13:07 | 顯示全部樓層
贊一個!
回復

使用道具 舉報

ID:233325 發表于 2017-11-1 12:40 來自手機 | 顯示全部樓層
看得很仔細、就是忘得也挺快
回復

使用道具 舉報

ID:237247 發表于 2017-11-5 21:21 | 顯示全部樓層
關閉位時,ENLED=0;74HC138就不起作用了,也起不到選取六個數碼管了?不理解。幫解釋一下
回復

使用道具 舉報

ID:248102 發表于 2017-11-10 15:54 | 顯示全部樓層
沙灬漠 發表于 2017-9-8 18:27
unsigned char LedNumber[6]={0};  各位高手能夠否講解一下,這一句中的   {0}  代表的是啥? 謝謝!

意思是這個數組里的每一個值都初始化為0
回復

使用道具 舉報

ID:248102 發表于 2017-11-10 16:33 | 顯示全部樓層
請教一個問題:
在利用中斷解決“數碼管抖動”的問題時,假如在執行到 if(1000 == counter) 里面中間位置時,LedNumber:0,1,2已經更新,3,4,5沒有更新,這時定時器發生中斷,在中斷服務程序中去設置P0的值會不會出錯?
回復

使用道具 舉報

ID:250781 發表于 2017-11-24 13:56 來自手機 | 顯示全部樓層
學到不少知識啊,太給力了
回復

使用道具 舉報

ID:253047 發表于 2017-11-24 14:32 | 顯示全部樓層
太給力了  學習很多
回復

使用道具 舉報

ID:247525 發表于 2017-12-16 17:49 | 顯示全部樓層
switch(j)             {             case 0: ADDR0=0; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[0]];break;             case 1: ADDR0=1; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[1]];break;             case 2: ADDR0=0; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[2]];break;             case 3: ADDR0=1; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[3]];break;             case 4: ADDR0=0; ADDR1=0; ADDR2=1; j++; P0=LedChar[LedNumber[4]];break;             case 5: ADDR0=1; ADDR1=0; ADDR2=1; j=0; P0=LedChar[LedNumber[5]];break;             default: break;              }#在這里快速回復#case后面沒有常量判斷表達式那么往下是執行哪一條語句
回復

使用道具 舉報

ID:280978 發表于 2018-1-30 22:44 | 顯示全部樓層
#include <reg52.h>               //包含寄存器的庫文件                  

unsigned char code LedChar[] = {   //用數組來表示數碼管真值表
    0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80};
unsigned int counter = 0;
unsigned char j = 5;
unsigned char LedNumber[6] = {0};
void main()   
{
   
    unsigned long stopwatch = 999999;
   
    P2=0xe0;P0=0x80;P2=0x00;   //74HC138和P0初始化部分
    TMOD = 0x01; //設置定時器0為模式1
    TH0  = 0xFC;
    TL0  = 0x67; //定時值初值,定時1ms
    TR0  = 1;    //打開定時器0
        EA = 1;       //打開中中斷
    ET0 = 1;      //打開定時器0中斷
    while(1)
    {
        
            if(1000 == counter)     //判斷定時器0溢出是否達到50次
            {
                counter = 0;
                stopwatch--;                  //秒表數值一秒加1
                LedNumber[0] = stopwatch%10;
                LedNumber[1] = stopwatch/10%10;
                LedNumber[2] = stopwatch/100%10;     
                LedNumber[3] = stopwatch/1000%10;  //數碼管顯示值計算
                LedNumber[4] = stopwatch/10000%10;
                LedNumber[5] = stopwatch/100000%10;
            }
         
                          //數碼管動態刷新部分
    }
}
void InterruptTimer0() interrupt 1               //中斷函數的特殊寫法,數字’1’為中斷入口號
{
    TH0 = 0xFC;   //溢出后進入中斷重新賦值
    TL0 = 0x67;
    counter++;   //計數值counter加1
    P0=0xff;   //消隱
        switch(j)
            {
            case 0: P2=0xe0; j=5; P0=LedChar[LedNumber[5]];P2=0x00;P2=0xc0;P0=0x01;P2=0x00;break;
            case 1: P2=0xe0; j--; P0=LedChar[LedNumber[4]];P2=0x00;P2=0xc0;P0=0x02;P2=0x00;break;
            case 2: P2=0xe0; j--; P0=LedChar[LedNumber[3]];P2=0x00;P2=0xc0;P0=0x04;P2=0x00;break;
            case 3: P2=0xe0; j--; P0=LedChar[LedNumber[2]];P2=0x00;P2=0xc0;P0=0x08;P2=0x00;break;
            case 4: P2=0xe0; j--; P0=LedChar[LedNumber[1]];P2=0x00;P2=0xc0;P0=0x10;P2=0x00;break;
            case 5: P2=0xe0; j--; P0=LedChar[LedNumber[0]];P2=0x00;P2=0xc0;P0=0x20;P2=0x00;break;
            default: break;
             }


}
求教作業題就是那個999999,做不出來。。。
回復

使用道具 舉報

ID:285691 發表于 2018-2-28 10:14 | 顯示全部樓層
Bnuzdaxian 發表于 2016-11-18 11:41
**** 作者被禁止或刪除 內容自動屏蔽 ****

這個是筆誤

回復

使用道具 舉報

ID:315638 發表于 2018-4-25 10:05 | 顯示全部樓層
請問有沒有開發板的原理圖?可不可以分享一下!!!!
回復

使用道具 舉報

ID:320487 發表于 2018-5-3 16:15 | 顯示全部樓層
太難了  這一章根本看不懂
回復

使用道具 舉報

ID:320487 發表于 2018-5-5 07:05 | 顯示全部樓層
越看越復雜呀
這個應該是高中的程度啦  
回復

使用道具 舉報

ID:93625 發表于 2018-6-29 10:24 | 顯示全部樓層
繼續學習!
回復

使用道具 舉報

ID:378091 發表于 2018-7-23 17:45 | 顯示全部樓層
我用定時器1時,很明顯沒到1秒數字就跳了,這是為什么啊?
回復

使用道具 舉報

ID:341430 發表于 2018-7-25 15:28 | 顯示全部樓層
        case 0: ADDR0=0; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[0]]; break;
        case 1: ADDR0=1; ADDR1=0; ADDR2=0; j++; P0=LedChar[LedNumber[1]]; break;
        case 2: ADDR0=0; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[2]]; break;
        case 3: ADDR0=1; ADDR1=1; ADDR2=0; j++; P0=LedChar[LedNumber[3]]; break;
        case 4: ADDR0=0; ADDR1=0; ADDR2=1; j++; P0=LedChar[LedNumber[4]]; break;
        case 5: ADDR0=1; ADDR1=0; ADDR2=1; j=0; P0=LedChar[LedNumber[5]]; break;
請問下這段的意思是什么
回復

使用道具 舉報

ID:341901 發表于 2018-9-2 10:58 來自手機 | 顯示全部樓層
沙灬漠 發表于 2017-9-8 18:27
**** 作者被禁止或刪除 內容自動屏蔽 ****

按道理說里面應該有六組數,實際就寫了第一組為0,缺少的五組數據自動默認為0,這里的首位0你可以更改為任意數都一樣的,代表的是下標0的數
回復

使用道具 舉報

ID:313459 發表于 2018-9-26 20:08 來自手機 | 顯示全部樓層
很有用,謝謝
回復

使用道具 舉報

ID:490950 發表于 2019-4-25 12:56 來自手機 | 顯示全部樓層
問一下,怎么高位滅0
回復

使用道具 舉報

ID:490950 發表于 2019-5-11 23:42 來自手機 | 顯示全部樓層
數碼管怎么高位滅0啊求教
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: 日韩中文字幕 | 久久99久久99精品免视看婷婷 | 国产精品一区二区av | 亚洲成人免费视频在线观看 | 成人h动漫精品一区二区器材 | 欧美区日韩区 | 黄色永久免费 | 日韩国产中文字幕 | 91看片免费版 | 一区二区三区四区在线视频 | 精品国产一区二区三区久久 | www.久久99| 日韩成人一区二区 | 日韩www | 精品视频999 | 成人精品一区二区 | 日本粉嫩一区二区三区视频 | 自拍偷拍第一页 | 综合久久av| 欧美日韩在线国产 | 中文字幕日韩在线 | 午夜av免费 | 精品福利在线 | 日本一区二区高清不卡 | 久久成人免费视频 | 国产成人一区二区三区电影 | 欧美一区免费 | 亚洲三区在线播放 | 亚洲性综合网 | 国产精品免费视频一区 | 99在线资源 | 一区二区久久精品 | av黄色片在线观看 | 欧美日韩国产中文 | 亚洲精品久久久久久下一站 | 精品日韩一区二区 | 一区二区在线免费观看 | 亚洲 中文 欧美 日韩 在线观看 | 亚洲高清成人在线 | 日韩黄a | 西西裸体做爰视频 |