中斷是單片機系統重點(diǎn)中的重點(diǎn),因為有了中斷,單片機就具備了快速協(xié)調多模塊工作的能力,可以完成復雜的任務(wù)。本章將首先帶領(lǐng)大家學(xué)習一些必要的C語(yǔ)言基礎知識,然后講解數碼管動(dòng)態(tài)顯示的原理,并最終借助于中斷系統來(lái)完成實(shí)用的數碼管顯示程序。大家對本章節內容要多多研究,要完全掌握并能熟練運用。 本文引用地址:http://dyxdggzs.com/article/201611/318571.htm1.1C語(yǔ)言的數組1.1.1數組的基本概念 第四章已經(jīng)學(xué)過(guò)變量的基本類(lèi)型,比如char、int等等。這種類(lèi)型描述的都是單個(gè)具有特定意義的數據,當我們要處理?yè)碛型?lèi)意義但是卻包含很多個(gè)數據的時(shí)候,就可以用到數組了,比如我們上節課那個(gè)數碼管的真值表,就是用一個(gè)數組來(lái)表達的。 從概念上講,數組是具有相同數據類(lèi)型的有序數據的組合,一般來(lái)講,數組定義后滿(mǎn)足以下三個(gè)條件。 1、具有相同的數據類(lèi)型; 2、具有相同的名字; 3、在存儲器中是被連續存放的。 比如我們上節課定義的那個(gè)數碼管真值表,如果我們把關(guān)鍵字code去掉,數組元素將被保存在RAM中,在程序中可讀可寫(xiě),同時(shí)我們也可以在中括號里邊標明這個(gè)數組所包含的元素個(gè)數,比如: unsignedcharLedChar[16]={ 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8, 0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E }; 在這個(gè)數組中的每個(gè)值都稱(chēng)之為數組的一個(gè)元素,這些元素都具備相同的數據類(lèi)型就是unsignedchar型,他們有一個(gè)共同的名字LedChar,不管放到RAM中還是FLASH中,他們都是存放在一塊連續的存儲空間里的。 有一點(diǎn)要特別注意,這個(gè)數組一共有16(中括號里面的數值)個(gè)元素,但是數組的單個(gè)元素的表達方式——下標是從0開(kāi)始,因此實(shí)際上上邊這個(gè)數組的首個(gè)元素LedChar[0]的值是0xC0,而LedChar[15]的值是0x8E,下標從0到15一共是16個(gè)元素。 LedChar這個(gè)數組只有一個(gè)下標,我們稱(chēng)之為一維數組,還有兩個(gè)下標和多個(gè)下標的,我們稱(chēng)之為二維數組和多維數組。比如unsignedchara[2][3];表示這是一個(gè)2行3列的二維數組。在大多數情況下我們使用的是一維數組,對于初學(xué)來(lái)說(shuō),我們先來(lái)研究一維數組,多維數組等遇到了再來(lái)了解。 1.1.2數組的聲明 一維數組的聲明格式如下: 數據類(lèi)型數組名[數組長(cháng)度]; 1、數組的數據類(lèi)型聲明的是該數組的每個(gè)元素的類(lèi)型,即一個(gè)數組中的元素具有相同的數據類(lèi)型。 2、數組名的聲明要符合C語(yǔ)言固定的標識符的聲明要求,只能由字母、數字、下劃線(xiàn)這三種符號組成,且第一個(gè)字符只能是字母或者下劃線(xiàn)。 3、方括號中的數組長(cháng)度是一個(gè)常量或常量表達式,并且必須是正整數。 1.1.3數組的初始化數組在進(jìn)行聲明的同時(shí)可以進(jìn)行初始化操作,格式如下: 數據類(lèi)型數組名[數組長(cháng)度]={初值列表}; 還是以上節課我們用的數碼管的真值表為例來(lái)講解注意事項。 unsignedcharLedChar[16]={ 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8, 0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E }; 1、初值列表里的數據之間要用逗號隔開(kāi); 2、初值列表里的初值的數量必須等于或小于數組長(cháng)度,當小于數組長(cháng)度時(shí),數組的后邊沒(méi)有賦初值的元素由系統自動(dòng)賦值為0。 3、若給數組的所有元素都賦初值,那么可以省略數組的長(cháng)度,上節課的例子中我們實(shí)際上已經(jīng)省略了數組的長(cháng)度。 4、系統為數組分配連續的存儲單元的時(shí)候,數組元素的相對次序由下標來(lái)決定,就是說(shuō)LedChar[0]、LedChar[1]……LedChar[15]是按照順序緊挨著(zhù)依次排下來(lái)的。 1.1.4數組的使用和賦值 在C語(yǔ)言程序中,是不能一次使用整個(gè)數組的,只能使用數組的單個(gè)元素。一個(gè)數組元素相當于一個(gè)變量,使用數組元素的時(shí)候與使用相同數據類(lèi)型的變量的方法是一樣的。比如LedChar這個(gè)數組,如果沒(méi)加code關(guān)鍵字,那么它可讀可寫(xiě),我們可以寫(xiě)成a=LedChar[0]這樣來(lái)把數組的一個(gè)元素的值送個(gè)a這個(gè)變量,也可以寫(xiě)成LedChar[0]=a這樣把a這個(gè)變量的值送給數組中的一個(gè)元素,以下三點(diǎn)要注意: 1、引用數組的時(shí)候,那個(gè)方括號里的數字代表的是數組元素的下標,而數組初始化的時(shí)候方括號里的數字代表的是這個(gè)數組中元素的總數。 2、數組元素的方括號里的下標可以是整型常數,整型變量或者表達式,而數組初始化的時(shí)候方括號里的數字必須是常數不能是變量。 3、數組整體賦值只能在初始化的時(shí)候進(jìn)行,程序執行代碼中只能對單個(gè)元素賦值。 1.2if語(yǔ)句 到目前為止,我們對if語(yǔ)句應該已經(jīng)不陌生了,前邊程序已用過(guò)多次了,這里我們系統的介紹一下,方便后邊的深入學(xué)習。if語(yǔ)句有兩個(gè)關(guān)鍵字:if和else,把這兩個(gè)關(guān)鍵字翻譯一下就是:“如果”和“否則”。if語(yǔ)句一共有三種格式,我們分別來(lái)看。 1、if語(yǔ)句的默認形式: if(條件表達式) { 語(yǔ)句1; } 其執行過(guò)程是,if(即如果)條件表達式的值為“真”,則執行語(yǔ)句1;如果條件表達式的值為“假”,則不執行語(yǔ)句1。真和假的概念不再贅述,參考第五章。 這里要提醒大家一點(diǎn),C語(yǔ)言一個(gè)分號表示一條語(yǔ)句的結束,因此如果if后邊只有一條執行語(yǔ)句的時(shí)候,可以省略大括號,但是如果有多條執行語(yǔ)句的話(huà),必須加上大括號。 那么現在,我們上節課的語(yǔ)句就很好理解了: if(sec>=16) { sec=0; } 當sec的值大于或等于16的時(shí)候,括號里的值才是“真”,那么就執行sec=0這一句,當sec的值小于16時(shí),那么括號里就為“假”,就不執行這一句。 2、if...else語(yǔ)句 有些情況下,我們除了要在括號里條件滿(mǎn)足時(shí)執行相應的語(yǔ)句外,在不滿(mǎn)足該條件的時(shí)候,也要執行一些另外的語(yǔ)句,這時(shí)候就用到了if...else語(yǔ)句,它的基本語(yǔ)法形式是: if(條件表達式) { 語(yǔ)句1; } else { 語(yǔ)句2; } 比如上節課的最后一段程序我們也可以寫(xiě)成: P0=LedChar[sec]; if(sec>=15) { sec=0; } else { Sec++; } 這個(gè)程序大家可以修改下載到單片機里驗證一下,程序邏輯大家自己動(dòng)腦筋分析,注意條件表達式內16到15的變化,想一下為什么,我就不多解釋了。 3、if....elseif語(yǔ)句 if...esle語(yǔ)句是一個(gè)二選一的語(yǔ)句,或者執行if分支后的語(yǔ)句,或者執行else分支后的語(yǔ)句。還有一種多選一的用法就是if...elseif語(yǔ)句。他的基本語(yǔ)法格式是: if(條件表達式1){語(yǔ)句1;} elseif(條件表達式2){語(yǔ)句2;} elseif(條件表達式3){語(yǔ)句3;} ...... else{語(yǔ)句n;} 他的執行過(guò)程是:依次判斷條件表達式的值,當出現某個(gè)值為“真”時(shí),則執行相對應的語(yǔ)句,然后跳出整個(gè)if的語(yǔ)句塊,執行“語(yǔ)句n”后面的程序;如果所有的表達式都為“假”,則執行else分支的“語(yǔ)句n”后,再執行“語(yǔ)句n”后邊的程序。 if語(yǔ)句在C語(yǔ)言編程中使用頻率很高,用法也不復雜,所以必須要熟練掌握。 1.3switch語(yǔ)句 用if....else語(yǔ)句在處理多分支的時(shí)候,分支太多就會(huì )顯得不方便,且容易出現if和else配對出現錯誤的情況,在C語(yǔ)言中提供了另外一種多分支選擇的語(yǔ)句——switch語(yǔ)句,它的基本語(yǔ)法格式如下: switch(表達式) { case常量表達式1:語(yǔ)句1; case常量表達式2:語(yǔ)句2; ...... case常量表達式n:語(yǔ)句n; default:語(yǔ)句n+1; } 它的執行過(guò)程是:首先計算“表達式”的值,然后從第一個(gè)case開(kāi)始,與“常量表達式x”進(jìn)行比較,如果與當前常量表達式的值不相等,那么就不執行冒號后邊的語(yǔ)句x,一旦發(fā)現和某個(gè)常量表達式的值相等了,那么它會(huì )執行之后所有的語(yǔ)句,如果直到最后一個(gè)“常量表達式n”都沒(méi)有找到相等的值,那么就執行default后的“語(yǔ)句n+1”。請特別注意一點(diǎn),當找到一個(gè)相等的case分支后,會(huì )執行該分支以及之后所有分支的語(yǔ)句,很明顯這不是我們想要的結果。 在C語(yǔ)言中,有一條break語(yǔ)句,作用是跳出當前的循環(huán)語(yǔ)句,包括for循環(huán)和while循環(huán),同時(shí),它還能用來(lái)結束switch語(yǔ)句塊。switch的分支語(yǔ)句一共有n+1種,而我們通常希望的都是選擇其中的一個(gè)分支來(lái)執行,執行完后就結束整個(gè)switch語(yǔ)句,而繼續執行switch后面的語(yǔ)句,此時(shí)就可以通過(guò)在每個(gè)分支后加上break語(yǔ)句來(lái)實(shí)現了。如下: switch(表達式) { case常量表達式1:語(yǔ)句1;break; case常量表達式2:語(yǔ)句2;break; ...... case常量表達式n:語(yǔ)句n;break; default:語(yǔ)句n+1;break; } 加了這個(gè)break語(yǔ)句后,一旦“常量表達式x”與“表達式”的值相等了,那么就執行“語(yǔ)句x”,執行完畢后,由于有了break則直接跳出switch語(yǔ)句,繼續執行switch語(yǔ)句后面的程序了,這樣就可以避免執行不必要的語(yǔ)句。了解了這個(gè)switch語(yǔ)句后,我們馬上會(huì )在本章程序中使用鞏固它。 1.4數碼管的動(dòng)態(tài)顯示1.4.1動(dòng)態(tài)顯示的基本原理 我們在上一章學(xué)習數碼管靜態(tài)顯示的時(shí)候說(shuō)到,74HC138只能在同一時(shí)刻導通一個(gè)三極管,而我們的數碼管是靠了6個(gè)三極管來(lái)控制,那我們如何來(lái)讓數碼管同時(shí)顯示呢?這就用到了動(dòng)態(tài)顯示的概念。 多個(gè)數碼管顯示數字的時(shí)候,我們實(shí)際上是輪流點(diǎn)亮數碼管(一個(gè)時(shí)刻內只有一個(gè)數碼管是亮的),利用人眼的視覺(jué)暫留現象(也叫余輝效應),就可以做到看起來(lái)是所有數碼管都同時(shí)亮了,這就是動(dòng)態(tài)顯示,也叫做動(dòng)態(tài)掃描。 例如:有2個(gè)數碼管,我們要顯示“12”這個(gè)數字,先讓高位的位選三極管導通,然后控制段選讓其顯示“1”,延時(shí)一定時(shí)間后再讓低位的位選三極管導通,然后控制段選讓其顯示“2”。把這個(gè)流程以一定的速度循環(huán)運行就可以讓數碼管顯示出“12”,由于交替速度非???,人眼識別到的就是“12”這兩位數字同時(shí)亮了。 那么一個(gè)數碼管需要點(diǎn)亮多長(cháng)時(shí)間呢?也就是說(shuō)要多長(cháng)時(shí)間完成一次全部數碼管的掃描呢(很明顯:整體掃描時(shí)間=單個(gè)數碼管點(diǎn)亮時(shí)間*數碼管個(gè)數)?答案是:10ms以?xún)?。當電視機和顯示器還處在CRT(電子顯像管)時(shí)代的時(shí)候,有一句很流行的廣告語(yǔ)——“100Hz無(wú)閃爍”,沒(méi)錯,只要刷新率大于100Hz,即刷新時(shí)間小于10ms,就可以做到無(wú)閃爍,這也就是我們的動(dòng)態(tài)掃描的硬性指標。那么你也許會(huì )問(wèn),有最小值的限制嗎?理論上沒(méi)有,但實(shí)際上做到更快的刷新卻沒(méi)有任何進(jìn)步的意義了,因為已經(jīng)無(wú)閃爍了,再快也還是無(wú)閃爍,只是徒然增加CPU的負荷而已(因為1秒內要執行更多次的掃描程序)。所以,通常我們設計程序的時(shí)候,都是取一個(gè)接近10ms,又比較規整的值就行了。我們開(kāi)發(fā)板上有6個(gè)數碼管,那么我們現在就來(lái)著(zhù)手寫(xiě)一個(gè)數碼管動(dòng)態(tài)掃描的程序,實(shí)現兼驗證上面講的動(dòng)態(tài)顯示原理。 我們的目標還是實(shí)現秒表功能,只不過(guò)這次有6個(gè)位了,最大可以計到999999秒。那么現在要實(shí)現的這個(gè)程序相對于前幾章的例程來(lái)說(shuō)就要復雜的多了,既要處理秒表計數,又要處理動(dòng)態(tài)掃描。在編寫(xiě)這類(lèi)稍復雜的程序時(shí),建議初學(xué)者們先用程序流程圖來(lái)把程序的整個(gè)流程理清,在動(dòng)手寫(xiě)程序之前先把整個(gè)程序的結構框架搭好,把每一個(gè)環(huán)節要實(shí)現的功能先細化出來(lái),然后再用程序代碼一步一步的去實(shí)現出來(lái)。這樣就可以避免無(wú)處下筆的迷茫感了。如圖6-1就是本例的程序流程圖,大家先根據流程圖把程序的執行經(jīng)過(guò)在大腦里走一遍,然后再看接下來(lái)的程序代碼,體會(huì )一下流程圖的作用,看是不是能幫助你更順暢的理清程序流程。 
圖6-1數碼管動(dòng)態(tài)顯示秒表程序流程圖 #include sbitADDR0=P1^0; sbitADDR1=P1^1; sbitADDR2=P1^2; sbitADDR3=P1^3; sbitENLED=P1^4; unsignedcharcodeLedChar[]={//數碼管顯示字符轉換表 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8, 0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E }; unsignedcharLedBuff[6]={//數碼管顯示緩沖區,初值0xFF確保啟動(dòng)時(shí)都不亮 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; voidmain() { unsignedchari=0;//動(dòng)態(tài)掃描的索引 unsignedintcnt=0;//記錄T0中斷次數 unsignedlongsec=0;//記錄經(jīng)過(guò)的秒數 ENLED=0;//使能U3,選擇控制數碼管 ADDR3=1;//因為需要動(dòng)態(tài)改變ADDR0-2的值,所以不需要再初始化了 TMOD=0x01;//設置T0為模式1 TH0=0xFC;//為T(mén)0賦初值0xFC67,定時(shí)1ms TL0=0x67; TR0=1;//啟動(dòng)T0 while(1) { if(TF0==1)//判斷T0是否溢出 { TF0=0;//T0溢出后,清零中斷標志 TH0=0xFC;//并重新賦初值 TL0=0x67; cnt++;//計數值自加1 if(cnt>=1000)//判斷T0溢出是否達到1000次 { cnt=0;//達到1000次后計數值清零 sec++;//秒計數自加1 //以下代碼將sec按十進(jìn)制位從低到高依次提取并轉為數碼管顯示字符 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]; } //以下代碼完成數碼管動(dòng)態(tài)掃描刷新 if(i==0) {ADDR2=0;ADDR1=0;ADDR0=0;i++;P0=LedBuff[0];} elseif(i==1) {ADDR2=0;ADDR1=0;ADDR0=1;i++;P0=LedBuff[1];} elseif(i==2) {ADDR2=0;ADDR1=1;ADDR0=0;i++;P0=LedBuff[2];} elseif(i==3) {ADDR2=0;ADDR1=1;ADDR0=1;i++;P0=LedBuff[3];} elseif(i==4) {ADDR2=1;ADDR1=0;ADDR0=0;i++;P0=LedBuff[4];} elseif(i==5) {ADDR2=1;ADDR1=0;ADDR0=1;i=0;P0=LedBuff[5];} } } } 這段程序,大家自己抄到Keil中,然后邊抄邊結合程序流程圖來(lái)理解,最終下載到實(shí)驗板上看一下運行結果。其中下邊的if...else語(yǔ)句就是每1ms快速的刷新一個(gè)數碼管,這樣6個(gè)數碼管整體刷新一遍的時(shí)間就是6ms,視覺(jué)感官上就是6個(gè)數碼管同時(shí)亮起來(lái)了。 在C語(yǔ)言中,“/”等同于數學(xué)里的除法運算,而“%”等同于我們小學(xué)學(xué)的求余數運算,這個(gè)前邊已有介紹。如果是123456這個(gè)數字,我們要正常顯示在數碼管上,個(gè)位顯示,就是直接對10取余數,這個(gè)“6”就出來(lái)了,十位數字就是先除以10,然后再對10取余數,以此類(lèi)推,就把6個(gè)數字全部顯示出來(lái)了。 對于多選一的動(dòng)態(tài)刷新數碼管的方式,我們如果用switch會(huì )有更好的效果,大家來(lái)看一下我們用switch語(yǔ)句完成的情況。 #include sbitADDR0=P1^0; sbitADDR1=P1^1; sbitADDR2=P1^2; sbitADDR3=P1^3; sbitENLED=P1^4; unsignedcharcodeLedChar[]={//數碼管顯示字符轉換表 0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8, 0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E }; unsignedcharLedBuff[6]={//數碼管顯示緩沖區,初值0xFF確保啟動(dòng)時(shí)都不亮 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; voidmain() { unsignedchari=0;//動(dòng)態(tài)掃描的索引 unsignedintcnt=0;//記錄T0中斷次數 unsignedlongsec=0;//記錄經(jīng)過(guò)的秒數 ENLED=0;//使能U3,選擇控制數碼管 ADDR3=1;//因為需要動(dòng)態(tài)改變ADDR0-2的值,所以不需要再初始化了 TMOD=0x01;//設置T0為模式1 TH0=0xFC;//為T(mén)0賦初值0xFC67,定時(shí)1ms TL0=0x67; TR0=1;//啟動(dòng)T0 while(1) { if(TF0==1)//判斷T0是否溢出 { TF0=0;//T0溢出后,清零中斷標志 TH0=0xFC;//并重新賦初值 TL0=0x67; cnt++;//計數值自加1 if(cnt>=1000)//判斷T0溢出是否達到1000次 { cnt=0;//達到1000次后計數值清零 sec++;//秒計數自加1 //以下代碼將sec按十進(jìn)制位從低到高依次提取并轉為數碼管顯示字符 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]; } //以下代碼完成數碼管動(dòng)態(tài)掃描刷新 switch(i) { case0:ADDR2=0;ADDR1=0;ADDR0=0;i++;P0=LedBuff[0];break; case1:ADDR2=0;ADDR1=0;ADDR0=1;i++;P0=LedBuff[1];break; case2:ADDR2=0;ADDR1=1;ADDR0=0;i++;P0=LedBuff[2];break; case3:ADDR2=0;ADDR1=1;ADDR0=1;i++;P0=LedBuff[3];break; case4:ADDR2=1;ADDR1=0;ADDR0=0;i++;P0=LedBuff[4];break; case5:ADDR2=1;ADDR1=0;ADDR0=1;i=0;P0=LedBuff[5];break; default:break; } } } } 程序完成的功能是一模一樣的,但大家看一下,switch語(yǔ)句是不是比if...else語(yǔ)句顯得要整齊清爽呢。 1.4.2數碼管顯示消隱 不知道同學(xué)們是否發(fā)現了,我們的這兩個(gè)數碼管動(dòng)態(tài)顯示程序的運行效果似乎并不是那么完美,第一個(gè)小問(wèn)題,大家仔細看,數碼管的不應該亮的段,似乎有微微的發(fā)亮,這種現象叫做“鬼影”,這個(gè)“鬼影”嚴重影響了我們的視覺(jué)效果,我們該如何解決呢? 同學(xué)們在今后可能會(huì )遇到各種各樣的實(shí)際問(wèn)題,可能很多都是我們沒(méi)有講過(guò)的,遇到問(wèn)題怎么辦呢?大家要相信,你作為初學(xué)者,遇到的問(wèn)題肯定不是第一個(gè)遇到的,肯定有前輩已經(jīng)遇到過(guò)相同的或類(lèi)似的問(wèn)題,他們一般都會(huì )在網(wǎng)上發(fā)表各種帖子,各種討論,所以大家遇到問(wèn)題,首先就應該形成一個(gè)到網(wǎng)上搜索的條件反射,這個(gè)問(wèn)題大家可以到網(wǎng)上搜:“數碼管消隱”或者“數碼管鬼影解決”,多找相關(guān)關(guān)鍵詞搜索試試,會(huì )搜索也是一種能力。 大家在網(wǎng)上搜了一下會(huì )發(fā)現,解決這類(lèi)問(wèn)題的方法有兩個(gè),其中之一是延時(shí),延時(shí)之后我們肉眼就可能看不到這個(gè)“鬼影”了。但是延時(shí)是一個(gè)非常拙劣的手段,且不說(shuō)延時(shí)多久能讓我們看不到“鬼影”,延時(shí)后,我們的數碼管亮度會(huì )普遍降低。我們解決問(wèn)題呢,不能只知其然,還要知其所以然,那么我們首先就來(lái)弄明白為什么會(huì )出現“鬼影”。 “鬼影”的出現,主要是在數碼管位選和段選產(chǎn)生的瞬態(tài)造成的。舉個(gè)簡(jiǎn)單例子,我們在數碼管動(dòng)態(tài)顯示的那部分程序中,實(shí)際上每一個(gè)數碼管點(diǎn)亮的持續時(shí)間是1ms的時(shí)間,1ms后進(jìn)行下個(gè)數碼管的切換。在進(jìn)行數碼管切換的時(shí)候,比如我們從case5要切換到case0的時(shí)候,case5的位選用的是ADDR0=1;ADDR1=0;ADDR2=1;假如此刻case5也就是最高位數碼管對應的值是0,我們要切換成的case0的數碼管位選是ADDR0=0;ADDR1=0;ADDR2=0;而對應的數碼管的值假如是1。又因為C語(yǔ)言程序是一句一句順序往下執行的,每一條語(yǔ)句的執行都會(huì )占用一定的時(shí)間,即使這個(gè)時(shí)間非常非常短暫。但是當我們把“ADDR0=1”改變成“ADDR0=0”的時(shí)候,這個(gè)瞬間存在了一個(gè)中間狀態(tài)ADDR0=0;ADDR1=0;ADDR2=1;在這個(gè)瞬間上,我們就給case4對應的數碼管DS5瞬間賦值了0。當我們全部寫(xiě)完了ADDR0=0;ADDR1=0;ADDR2=0;后,這個(gè)時(shí)候,我們的P0還沒(méi)有正式賦值,而P0此刻卻保持了前一次的值,也就是在這個(gè)瞬間,我們又給case0對應的數碼管DS1賦值了一個(gè)0。直到我們把case0后邊的語(yǔ)句全部完成后,我們的刷新才正式完成。而在這個(gè)刷新過(guò)程中,有2個(gè)瞬間我們給錯誤的數碼管賦了值,雖然很弱(因為亮的時(shí)間很短),但是我們還是能夠發(fā)現。 那么搞明白了原理后,解決起來(lái)就不是困難的事情了,我們只要避開(kāi)這個(gè)瞬間錯誤就可以了。不產(chǎn)生瞬間錯誤的方法是,在進(jìn)行位選切換期間,避免一切數碼管的賦值即可。方法有兩個(gè),一個(gè)方法是刷新之前關(guān)閉所有的段,改變好了位選后,再打開(kāi)段即可;第二個(gè)方法是關(guān)閉數碼管的位,賦值過(guò)程都做好后,再重新打開(kāi)即可。這個(gè)不是很難,答案我都公布一下。 關(guān)閉段:在switch(i)這句程序之前,加一句P0=0xFF;這樣就把數碼管所有的段都關(guān)閉了,當把“ADDR”的值全部搞定后,再給P0賦對應的值即可。 關(guān)閉位:在switch(i)這句程序之前,加上一句ENLED=1;等到把ADDR2=0;ADDR1=0;ADDR0=0;i++;P0=LedBuff[0];這幾條刷新程序全部寫(xiě)完后,再加上一句ENLED=0;然后再進(jìn)行break操作即可。 這個(gè)地方邏輯思路上稍微有點(diǎn)復雜,大家一定要理解深刻,深刻理解,徹底弄明白,把這個(gè)瞬間的問(wèn)題弄明白了,后邊很多牽扯到此類(lèi)情況的問(wèn)題,我們都可以一并搞定。 上邊的數碼管程序還有第二個(gè)問(wèn)題,大家仔細看,我們的數碼管上的數字每一秒變化一次,變化的時(shí)候,不參加變化的數碼管可能出現一次抖動(dòng),這個(gè)抖動(dòng)沒(méi)有什么專(zhuān)業(yè)的名字,我們就稱(chēng)之為數碼管抖動(dòng)吧。這種數碼管抖動(dòng)是什么原因造成的呢?為何在數據改變的時(shí)候才抖動(dòng)呢? 來(lái)分析一下我們的程序,程序在定時(shí)到1秒的時(shí)候,執行了“秒數+1并轉換為數碼管顯示字符”這個(gè)操作,一個(gè)32位整型數的除法運算,實(shí)際上是比較耗費時(shí)間的,至于這一段程序究竟耗費了多少時(shí)間,大家可以通過(guò)第四章講的調試方法來(lái)看看這段程序運行用了多少時(shí)間。由于每次定時(shí)到1秒的時(shí)候,程序都多運行了這么一段,導致了某個(gè)數碼管的點(diǎn)亮時(shí)間比其他情況下要長(cháng)一些,總時(shí)間就變成了1ms+本段程序運行時(shí)間,于此同時(shí),其它的數碼管就熄滅了5ms+本段程序運行時(shí)間,如果這段程序運行時(shí)間非常短,那么可以忽略不計,但很明顯,現在這段程序運行時(shí)間已經(jīng)比較長(cháng)了,以致于嚴重影響到視覺(jué)效果了,所以我們要采取另外一種思路去解決這個(gè)問(wèn)題。 1.5單片機中斷系統1.5.1中斷的產(chǎn)生背景 請設想這樣一個(gè)場(chǎng)景:此刻我正在廚房用煤氣燒一壺水,而燒開(kāi)一壺水剛好需要10分鐘,我是一個(gè)主體,燒水是一個(gè)目的,而且我只能時(shí)時(shí)刻刻在這里燒水,因為一旦水開(kāi)了,溢出來(lái)澆滅煤氣的話(huà),有可能引發(fā)一場(chǎng)災難。但就在這個(gè)時(shí)候呢,我又聽(tīng)到了電視里傳來(lái)《天龍八部》的主題歌,馬上就要開(kāi)演了,我真想奪門(mén)而出,去看我最喜歡的電視劇。然而,聽(tīng)到這個(gè)水壺發(fā)出的“咕嘟”的聲音,我清楚:除非等水燒開(kāi)了,否則我是無(wú)法享受我喜歡的電視劇的。 這里邊主體只有一個(gè)我,而我要做的有兩件事情,一個(gè)是看電視,一個(gè)是燒水,而電視和燒水是兩個(gè)獨立的客體,它們是同時(shí)進(jìn)行的。其中燒水需要10分鐘,但不需要了解燒水的過(guò)程,只需要得到水燒開(kāi)的這樣一個(gè)結果就行了,提下水壺和關(guān)閉煤氣只需要幾秒的時(shí)間而已。所以我們采取的辦法就是:燒水的時(shí)候,定上一個(gè)鬧鐘,定時(shí)10分鐘,然后我就可以安心看電視了。當10分鐘時(shí)間到了,鬧鐘響了,此刻水也燒開(kāi)了,我就過(guò)去把煤氣滅掉,然后繼續回來(lái)看電視就可以了。 這個(gè)場(chǎng)景和單片機有什么關(guān)系呢? 在單片機的程序處理過(guò)程中也有很多類(lèi)似的場(chǎng)景,當單片機正在專(zhuān)心致志的做一件事情(看電視)的時(shí)候,總會(huì )有一件或者多件緊迫或者不緊迫的事情發(fā)生,需要我們去關(guān)注,有一些需要我們停下手頭的工作去馬上去處理(比如水開(kāi)了),只有處理完了,才能回頭繼續完成剛才的工作(看電視)。這種情況下單片機的中斷系統就該發(fā)揮它的強大作用了,合理巧妙的利用中斷,不僅可以使我們獲得處理突發(fā)狀況的能力,而且可以使單片機能夠“同時(shí)”完成多項任務(wù)。 1.5.2定時(shí)器中斷的應用 在第五章我們學(xué)過(guò)了定時(shí)器,而實(shí)際上定時(shí)器一般用法都是采取中斷方式來(lái)做的,我是故意在第五章用查詢(xún)法,就是使用if(TF0==1)這樣的語(yǔ)句先用定時(shí)器,目的是明確告訴同學(xué)們,定時(shí)器和中斷不是一回事,定時(shí)器是單片機模塊的一個(gè)資源,確確實(shí)實(shí)存在的一個(gè)模塊,而中斷,是單片機的一種運行機制。尤其是初學(xué)者們,很多人會(huì )誤以為定時(shí)器和中斷是一個(gè)東西,只有定時(shí)器才會(huì )觸發(fā)中斷,但實(shí)際上很多事件都會(huì )觸發(fā)中斷的,除了“燒水”,還有“有人按門(mén)鈴”,“來(lái)電話(huà)了”等等。 標準51單片機中控制中斷的寄存器有兩個(gè),一個(gè)是中斷使能寄存器,另一個(gè)是中斷優(yōu)先級寄存器,這里先介紹中斷使能寄存器,如表6-1和表6-2所示。隨著(zhù)一些增強型51單片機的問(wèn)世,可能會(huì )有增加的寄存器,大家理解了我們這里所講的,其它的通過(guò)自己研讀數據手冊就可以理解明白并且用起來(lái)了。 |
評論