在第4課中我們分別用位操作法和總線操作法點亮了第一個發光二極光,也可以說正式邁出了單片機程序設計的第一步,相信通過前面的學習,大家已經可以很輕松的點亮發光二極管。但光會點亮發光二極管肯定不夠的,大家經常可以看到外面大街小巷各種各樣漂亮的閃爍的流水或者說廣告燈,如圖1,這是怎么實現的呢,這節課我們就來講下如讓發光二極管閃爍和流水燈程序的設計。
圖1 漂亮的廣告燈
1如何實現發光二極管閃爍和簡單延時
學過匯編的同學都清楚,單片機機在執行指令時,是一條一條指令順序執行的,對于C語言也一樣,也是一條一條語句執行,而每執行一條指令或一條語句,都要占用一定的時間,利用單片機執行程序的這個特點就可能用來實現發光二極管閃爍。
具體方法就如圖2所示,是先點亮發光二極管,然后寫一條延時語句,在這段延時時間內,單片機什么事也不做,而此時發光二極管還是一直保持亮的狀態,然后延時時間完后,再讓發光二極管熄滅,熄滅后也寫一條延時程序,而在這段時間內,發光二極管一直保持熄滅,直到延時時間結束再復上而的亮的過程,這樣一直不斷的循環下去就實現在發光二極不斷閃爍。
圖2 發光二極管閃爍
延時可以由while語句來實現,也可由for語句實現,由于前面我們已經介紹過while語句,所以這里先介紹如何利用while語句實現延時。
(1) 利用while語句進行延時
由于while語句在前我們已經講解過,這里直接給出程序如例1。
例1 利用whiel語句實現發光二極管閃爍程序
#include<reg52.h>
sbit D1=P1^0;
unsigned int a;
void main()
{
while(1)
{
a=50000;
D1=0;
while(a--);
a=50000;
D1=1;
while(a--);
}
}
程序分析:上面程序中出現了新的語句,unsigned int a;由于程序的需要,這里我們先定義了一個變量a,所謂變量其實就是代表單片機內存中具有特定屬性的一個存儲單元,它用來存放數據,也就是變量的值,如下面的a=50000,在程序中這些值是可以改變的。其中a表示變量的名字,unsigned int是無符號整形的意思,它的取值范圍為0-65535,也就是定義成此類型后,在對程序編譯時,編譯系統會給這個變量分配一個存儲空間用來存放數據,且無符號整形的空間存數的范圍為0-65535。 后面的程序對a賦值時,其范圍只能是0-65535,如程序后面的a=50000,如果此時我們對a賦值a=70000,此時編譯時就是報錯。我們也可以把變量a定義為其它類型的,如unsigned char a表示為把變量a定義為無符號字符型,其賦值范圍為0-255
當然我們也可以定義其它的類型 short int、float等,但作為單片機初學者,一般只用到無符號字符型unsigned char(賦值范圍為0-255)和無符號整形unsigned int(賦值范圍為0-65535)兩種,其它類型可以暫時不管,具體請參考C語言相關書籍。
unsigned int a明白后,下面就是一個大循環while(1),在大循環里,我們先給a賦值50000,然后燈亮D1=0,再進行延時while(a--)(a--,表示自減1的意思,while(a--),表示a從50000開始自減50000次,此時每自減1有一定的時間,所以自減50000次相當于延時的意思),再后的程序就是延時后再燈滅,再延時,再重復前面的過程,最終就可以實現燈一亮一滅閃爍的現象。下載到實驗室的現象如圖3所示。
圖3 發光管閃爍
通過上面程序我們實現 了發光管閃爍的功能,但這里閃爍的間隔時間我們并不清楚具體是多少,我們只能根據具體的多次實驗確定我們所需的時間間隔,如果實在要知道的話,可以通過51單片機的軟件仿真得出,這里我們不作講解,以后等大家單片機學得差不多再去學習。
下面我們再給出另一種程序延時的for語句的寫法,這種寫法在具體的項目用得更多。
(2) for語句及其延時
for語句是C語言中重要的語句,其格式如下:
格式:
利用for語句同樣可以寫出延時程序,例如:
unsigned int i;
for(i=1;i<=3;i++) ;
這里首先定義了一個無符號變量i,在for語句中,i=1為表達式1;i<=100為表達式2;i++為表達式3,for語句內程序執行如下
1、給變量i賦值為1;
2、判斷i是否小于等于3;此時值為真,即執行for中的語句,此處for后面的語句為空語句,省略不寫,
相當于什么都不執行,然后跳到語句3;
3、執行i++,i++為自加1的意思,此時i的值變為2,
4、跳到第2 步判斷i是否小于等于3,此時仍然小于等于3,又執行空語句;
5、執行i++,此時i的值變為3;
6、跳到第2 步判斷i是否小于等于3,此時等于3,直接跳出;
通過上面6步,for語句執行完畢,注意,單片機在執行每一條語句的時候都需要一定的時間,此時我們只需要改變表達式2的值就
可以得到我們想要的延時時間了。
此處需要注意的時,我們把i定義為無符號整形后,i的值最大值只能為65535,也就是利用上面的格式,我們可以寫出最大的延時程序如下:
unsigned int i;
for(i=1;i<=65536;i++)
但我們此時如還需要更長的時間時,如果再讓i值變大的話,編譯時就會出錯,此時我們可以寫成如下的寫法:
unsigned int i;
unsigned int j;
for(i=1;i<=65536;i++)
{
for(j=1;j<=100;j++);
}
上面的語句稱為c語句的嵌套,是指一個for語句中又包含一個完整的for語句,內嵌的語句還可以嵌套for語句,這稱為多層嵌套,我們這里只有兩層。
注意第一個for語句后面沒有“;”,這里第一個for語句執行了65535次,第二個for語句執行了100次,相當于總共執行了655535x100次,這樣的寫法就可以寫出較長的延時時間。
這里的for語句和上面的while語句都可以用來進行延時,這種方法稱為軟件延時,和上面for語句一樣,其準確的延時時間在C語言程序中不容易計算出來。如果想要得到精確的延時時間,我們則可以用到單片機內部的硬件資源定時器,它可以精確的微秒級,這個后面的課程中會講到。雖然得不到精確的延時時間,但大概的時間我們剛可以通過軟件仿真的方法得出,關于仿真具體的調試方法我們這里不作講解,這此我們給出一個51單片機經常用到延時時間:
for語句中兩個變量的類型都為unsigned int 時,內層的for語句變量的值恒為110時,外層for語句變量的值為多少,這個for嵌套語句延時時間就約為多少毫秒。如下面的程序:
unsigned int i;
unsigned int j;
for(i=1;i<=1000;i++)
{
for(j=1;j<=110;j++);
}
這個程序中外層for語句中變的值為1000,其延時的時間就約為1000毫秒,即1秒,若要想得到其它的延時時間,只需要改變i變量的值就可以了。
下面我們利用 for語句寫一個程序,其功能為讓L1燈以間隔1秒的時間閃爍。程序如例2所示。
例2 L2燈以1秒的時間時隔閃爍
#include<reg52.h>
#define unsigned int;
sbit D1=P1^0;
uint i,j;
void main()
{
while(1)
{
D1=0;
for(i=1;i<=1000;i++)
{
j=1;j<=110;j++;
}
D1=1;
for(i=1;i<=1000;i++)
{
j=1;j<=110;j++;
}
}
}
下載到實驗板上現象如圖4所示:
圖4 L1以1秒的間隔閃爍。
例2程序中,在寫延時程序時,分別用到了兩個延時程序,并且這兩個程序內容格式完全相同,此時從簡化程序的角度出發,我們可以采取另外一種寫法-子函數調用法.下面對其進行介紹.
3、不帶參數的子函數調用
我們把上式中的延時函數單獨提取出來,然后寫成一個函數—此函數稱為一個程序中的子函數,然后在主函數中直接調用即可。上式中延時函數的子函數寫法如下。
void delay1s()
{
for(i=1;i<=1000;i++)
{
j=1;j<=110;j++;
}
}
上式中,void意為無返回值,也就是此函數執行完畢后不返回任何值。delay1s表示函數名,這里用戶可以隨便取,只需要滿意C語言的命名規則即,因為這個子程序在上面已經寫過,是延時1鈔的函數,所以我們此時命名延時1秒(delay1ms)樣可以便于大家閱讀和記憶。函數名delay1s后面緊跟一小括號,括號里面什么內容也沒有,這表示此子函數不帶任何參數(后面會介紹到帶參數的子函數)。
需要注意的是,程序中中的子函數,如果是寫在主函數之前,此不需要聲明,但如果是寫在主函數之后,則需要聲明,聲明的方法如下:將返回值特性、函數名及后面的小括號完全復制,若是無參函數,則小括號內為空,若是有參函數,則需要在小括號內依次寫上參數類型,參數類型之間用逗號隔開。最后在小括號后面加上分號即可。
下面對例2的程序改為不帶參數子函數的寫法,程序如例3所示。
例2 L2燈以1秒的時間時隔閃爍(不帶參數子函數的寫法)
#include<reg52.h>
#define unsigned int;
sbit D1=P1^0;
void delay1s()
void main()
{
while(1)
{
D1=0;
delay1s();
D1=1;
delay1s();
}
}
void delay1s()
{
uint i,j;
for(i=1;i<=1000;i++)
{
j=1;j<=110;j++;
}
}
在例3中,我們注意到“uint i,j”語句,i和j兩個變量的定義放到了子函數里,而沒有寫在主函數的最外面。在主函數外面定義的變量叫做全局變量,像這種定義在了函數內部的稱為局部變量,這里i和j就是局部變量;局部變量只在當前函數中起作用,程序一但執行完當前子函數,它內部的所有變量將自動銷毀,當下次再調用此子函數時,編譯器重新為其分配內存空間。我們要知道,在一個程序中,每個變量都占據著單片機的固定的RAM, 局部變量是用時隨時分配,不用時立即銷毀。一個單片機的RAM是很有限的,像STC89C52內部有256字節的RAM,所以用時我們必須節約,顯然,放在子程序中更能節省RAM空間。將來程序下載到實驗板,我們此時依然可以看到如例2中的現象,L1燈以1秒的時間間隔閃爍。
4、帶參數子函數的寫法
上面我們講到過,C語言中子函數分為兩種,一種是不帶參數子函數,另一種是帶參數的子函數。下面我們講一下帶參數子函數。在例3中,我們延時 1000ms,i=1000.如果我們要延時500ms,剛i=500.如果要延時300ms,i=300,這樣程序改起來就很麻煩,特別是在一些大型的程序中,這里我們如果寫成帶參數的子函數就方便很多。程序寫法如下:
void delayxms(unsigned int z)
{
for(i=z;i>0;i--)
{
j=1;j<=110;j++;
}
}
上面程序中小括號內多了“unsigned int z”,其中“z”這個就是這個子函數帶的參數,unsigned int 是定義參數z的類型為無符號整型。子函數中z這個參數我們稱為形參,在調用子函數時我們用一個真實的數據代替此形參,這個真實的數據我們稱為實參。在調用子函數時,只需要的延時時間改變小括號內的數據就可以了,如要延時1000s,則delayxms(1000),800ms時,delayxms(800)。
下面我們寫一個程序讓8個發光二極管以間隔800ms的時間閃爍。
例5 8個發光二極管以間隔500ms的時間間隔閃爍。
#include<reg52.h>
#define unsigned int;
sbit D1=P1^0;
void delayxms(unsigned int z)
{
for(i=z;i>0;i--)
{
j=1;j<=110;j++;
}
}
void main()
{
while(1)
{
D1=0;
delay1s();
D1=1;
delay1s();
}
}
下載到實驗板上的現象如下: