還有一種寫法for( ; ; ),這樣寫后,這個(gè)for循環(huán)就變成了死循環(huán)了,就不停的執(zhí)行(需要執(zhí)行的語(yǔ)句),和我們前邊講的while(1)的意思是一樣的。那while這個(gè)語(yǔ)法是如何用的呢?
1.4 while循環(huán)語(yǔ)句
在我們單片機(jī)C語(yǔ)言編程的時(shí)候,每個(gè)程序我們都會(huì)固定的加一句while(1),這條語(yǔ)句就可以起到死循環(huán)的作用。對(duì)于while語(yǔ)句來(lái)說(shuō),他的一般形式是:
While (表達(dá)式)
{
循環(huán)體語(yǔ)句;
}
在C語(yǔ)言里,通常表達(dá)式符合條件,我們叫做真,不符合條件,叫做假。比如前邊i<30000,當(dāng)i等于0的時(shí)候,那這個(gè)條件成立,就是真,如果i大于30000的時(shí)候,條件不成立,叫做假。
while(表達(dá)式)這個(gè)括號(hào)里的表達(dá)式,為真的時(shí)候,就會(huì)執(zhí)行循環(huán)體語(yǔ)句,當(dāng)為假的時(shí)候,就不執(zhí)行。在這里先不舉例,后邊遇到時(shí)再詳細(xì)說(shuō)明。
還有另外一種情況,就是我們C語(yǔ)言里邊,除了表達(dá)式外,還有常數(shù),習(xí)慣上,我們非0的常數(shù)都認(rèn)為是真,只有0認(rèn)為是假,所以我們程序中加了while(1),這個(gè)數(shù)字1,可以改成2,3,4......等等都可以,都是一個(gè)死循環(huán),不停的執(zhí)行循環(huán)體的語(yǔ)句,但是如果把這個(gè)數(shù)字改成0,那么就不會(huì)執(zhí)行循環(huán)體的語(yǔ)句了。
1.5 函數(shù)的簡(jiǎn)單介紹
函數(shù)定義的一般形式如下:
函數(shù)值類型 函數(shù)名 (形式參數(shù)列表)
{
函數(shù)體
}
1、函數(shù)值類型,就是函數(shù)返回值的類型。在我們后邊程序使用中,會(huì)有很多函數(shù)中有return x這個(gè)東西,這個(gè)返回值也就是函數(shù)本身的類型。還有一種情況,就是這個(gè)函數(shù)只執(zhí)行操作,不需要返回任何值,那么這個(gè)時(shí)候它的類型就是空類型void,這個(gè)void按道理來(lái)說(shuō)是可以省略的,但是一旦省略,Keil軟件會(huì)報(bào)一個(gè)警告,所以我們通常也不省。
2、函數(shù)名。可以是任何合法的標(biāo)示符,但是不能與其他函數(shù)或者變量重名,也不能是關(guān)鍵字。什么是關(guān)鍵字,后邊我們慢慢接觸,比如char這類,都是關(guān)鍵字,是我們程序中具備特殊功能的標(biāo)志符,這種東西不可以命名函數(shù)。
3、形式參數(shù)列表,我們也叫做形參,這個(gè)是函數(shù)調(diào)用的時(shí)候,相互傳遞數(shù)據(jù)用的。有的函數(shù),我們不需要傳遞參數(shù),那么可以用void來(lái)替代,void同樣可以省略,但是那個(gè)括號(hào)是不能省略的。
4、函數(shù)體。函數(shù)體包含了聲明語(yǔ)句部分和執(zhí)行語(yǔ)句部分。聲明語(yǔ)句部分主要用于聲明函數(shù)內(nèi)部所使用的變量,執(zhí)行語(yǔ)句部分主要是一些函數(shù)需要執(zhí)行的語(yǔ)句。特別注意,所有的聲明語(yǔ)句部分必須放在執(zhí)行語(yǔ)句之前,否則編譯的時(shí)候會(huì)報(bào)錯(cuò)。
5、一個(gè)工程文件必須有且僅能有一個(gè)main函數(shù),程序執(zhí)行的時(shí)候,都是從main函數(shù)開始的。
6、關(guān)于形參和實(shí)參的概念,我們后邊再總結(jié),如果遇到程序里有,大家再跟著抄一段時(shí)間。先用,后講解,這樣更有利于理解。
我們?cè)賮?lái)回顧一下我們上節(jié)課閃爍LED程序部分
void main() //void即函數(shù)類型
{
unsigned int i = 0; //定義一個(gè)無(wú)符號(hào)整數(shù)i,變量范圍是0~65535
//并且賦一個(gè)初值0
ENLED = 0; //先定義變量i,后寫執(zhí)行部分
ADDR0 = 0;
ADDR1 = 1;
ADDR2 = 1;
ADDR3 = 1; //74HC138開啟三極管
while(1) //程序死循環(huán)
{
LED = 0; //點(diǎn)亮小燈
for(i=0;i<30000;i++); //for延時(shí)操作
LED = 1; //熄滅小燈
for(i=0;i<30000;i++); //for延時(shí)操作
}
}
1.6 Keil軟件延時(shí)
C語(yǔ)言常用的延時(shí)辦法,有以下4種
圖4-2 C語(yǔ)言延時(shí)
圖4-2是我們編程語(yǔ)言常用的4種延時(shí)方法,其中兩種非精確延時(shí),兩種精確一些的延時(shí)。for語(yǔ)句和while語(yǔ)句都可以通過(guò)改變i的范圍值來(lái)改變延時(shí)時(shí)間,但是C語(yǔ)言的時(shí)間都是不能通過(guò)程序看出來(lái)的。
精確延時(shí)有兩個(gè)方法,一個(gè)方法是用定時(shí)器來(lái)延時(shí),這個(gè)方法我們后邊課程要詳細(xì)介紹,定時(shí)器是單片機(jī)的一個(gè)重點(diǎn)。另外一個(gè)就是用庫(kù)函數(shù)_nop_();,一個(gè)NOP的時(shí)間是一個(gè)機(jī)器周期的時(shí)間,這個(gè)后邊也要介紹。
非精確延時(shí),只是在我們做一些簡(jiǎn)單的比如小燈閃爍,流水燈等簡(jiǎn)單實(shí)驗(yàn)中使用,而實(shí)際做實(shí)際開發(fā)程序中其實(shí)這種非精確延時(shí)用的極少,這里我們只是做演示功能使用。
好了,介紹完了,我們就要實(shí)戰(zhàn)了。上節(jié)課的LED小燈閃爍的程序,我們用的延時(shí)方式是for(i=0;i<30000;i++);大家如果把這里的i改成100,下載進(jìn)入單片機(jī),會(huì)發(fā)現(xiàn)小燈一直亮,而不是閃爍狀態(tài),現(xiàn)在大家都把這個(gè)程序改一下,都改成100,然后下載觀察一下現(xiàn)象再繼續(xù)。
觀察完了,毫無(wú)疑問(wèn),實(shí)際現(xiàn)象和我提到的理論是相符合的,這是為什么呢?這里介紹一個(gè)常識(shí)。我們?nèi)说娜庋?/span>對(duì)閃爍的光線有一個(gè)最低分辨能力,通常情況下當(dāng)閃爍的頻率高于50Hz時(shí),我們看到的信號(hào)就是常亮的。即,延時(shí)的時(shí)間低于20ms的時(shí)候,我們的肉眼是分辨不出來(lái)小燈是在閃爍的,可能最多看到的是小燈亮暗稍微變化了一下。要想清楚的看到小燈閃爍,延時(shí)的值必須大一點(diǎn),大到什么程度呢,不同的亮度的燈不完全一樣,大家可以自己做實(shí)驗(yàn)。
那么如何觀察延時(shí)有多長(zhǎng)時(shí)間呢?大家鼠標(biāo)點(diǎn)Keil的Project-->Options for Target ‘Target1’,或點(diǎn)Target1右側(cè)圖標(biāo)
,進(jìn)入設(shè)置選項(xiàng),如圖4-3所示
圖4-3 Options for Target
首先我們打開Target這個(gè)選項(xiàng)卡,找到里邊的Xtal(MHz)這個(gè)位置,這是填寫我們進(jìn)行模擬時(shí)間的晶振選項(xiàng),從我們?cè)韴D以及板子上都可以看到,我們單片機(jī)所配的晶振是11.0592MHz,所以這個(gè)地方我們要填上11.0592。然后找到Debug這個(gè)選項(xiàng),選擇左側(cè)的Use Simulator,然后點(diǎn)擊最下邊的OK就可以了,如圖4-4所示。
圖4-4 Debug配置信息
點(diǎn)擊Debug菜單里的Start/Stop Debug Session,或者鼠標(biāo)點(diǎn)做左側(cè)的這個(gè)Debug圖標(biāo)
,會(huì)進(jìn)入一個(gè)新的頁(yè)面,如圖4-5所示。
圖4-5 Debug窗口顯示
最左側(cè)那一欄是單片機(jī)的一些寄存器和系統(tǒng)信息,最上邊那一欄是Keil將C語(yǔ)言轉(zhuǎn)換成匯編的代碼,下邊就是我們C語(yǔ)言的程序,還有各種窗口都可以打開,在view菜單可以打開或者關(guān)閉我們的各種窗口。這節(jié)課我們只關(guān)心我們需要的窗口,其他窗口用到再說(shuō)。那么有時(shí)候我們覺得這種分布不是特別的好,所以我們想改變一下窗口分布怎么辦呢?比如Disassembly(匯編)窗口,我們先用鼠標(biāo)拖動(dòng)它,然后中間會(huì)出現(xiàn)一個(gè)方向符號(hào),再用鼠標(biāo)點(diǎn)那個(gè)方向符號(hào),他就給我們分布了,如圖4-6所示。

圖4-6 Keil窗口移動(dòng)(一)
我們點(diǎn)擊最右邊的那個(gè)箭頭,然后窗口變化成如4-7圖所示�;蛘呶覀�?nèi)绻貌坏絽R編的程序,也可以直接關(guān)掉。
圖4-7所示 Keil窗口移動(dòng)(二)
細(xì)心的同學(xué)會(huì)看到在C語(yǔ)言的程序里有個(gè)黃色的箭頭,這個(gè)箭頭代表的就是這個(gè)程序當(dāng)前運(yùn)行的位置,在這個(gè)Debug里邊,我們可以看到我們的程序運(yùn)行的過(guò)程。在左上角有這三個(gè)圖標(biāo)
,第一個(gè)是復(fù)位,點(diǎn)擊一下之后,程序就會(huì)跑到最開始的位置運(yùn)行,第二個(gè)圖標(biāo)是全速運(yùn)行圖標(biāo),點(diǎn)擊一下程序就會(huì)全速運(yùn)行跑起來(lái),第三個(gè)圖標(biāo)是停止圖標(biāo),當(dāng)程序全速運(yùn)行跑起來(lái)后,我們可以通過(guò)點(diǎn)擊第三個(gè)圖標(biāo)來(lái)讓程序停止,觀察程序運(yùn)行到哪里了。點(diǎn)擊一下復(fù)位后,我們會(huì)發(fā)現(xiàn)C語(yǔ)言程序左側(cè)有的灰色或者綠色,有的地方還是保持原來(lái)的白色,我們可以在我們灰色的位置雙擊鼠標(biāo)設(shè)置斷點(diǎn),就是比如程序一共20行,在第十行設(shè)置斷點(diǎn)后,點(diǎn)全速運(yùn)行,程序就會(huì)運(yùn)行到第十行停止,方便我們觀察運(yùn)行到這個(gè)地方的情況。
同學(xué)們會(huì)發(fā)現(xiàn),有的位置可以設(shè)置斷點(diǎn),有的地方不可以設(shè)置斷點(diǎn),這是為什么呢?Keil軟件本身具備優(yōu)化我們程序的功能,如果大家想在所有的位置設(shè)置斷點(diǎn),可以把優(yōu)化選項(xiàng)設(shè)置到0位置,就是程序不進(jìn)行優(yōu)化。如圖4-8所示。
圖4-8 優(yōu)化選項(xiàng)設(shè)置
這節(jié)課我們重點(diǎn)是看看C語(yǔ)言代碼的運(yùn)行時(shí)間,在最左側(cè)的register那個(gè)框內(nèi),有一個(gè)sec選項(xiàng),這個(gè)選項(xiàng)就是單片機(jī)運(yùn)行時(shí)間的統(tǒng)計(jì)選項(xiàng),大家點(diǎn)一下復(fù)位按鈕,會(huì)發(fā)現(xiàn)這個(gè)sec變成了0,然后我們?cè)?/font>LED = 0; 這一句加一個(gè)斷點(diǎn),在LED = 1;這個(gè)位置加一個(gè)斷點(diǎn),我們點(diǎn)擊全速運(yùn)行按鈕,會(huì)直接停留在LED = 0;我們會(huì)看到我們的時(shí)間變化成0.000197秒,如圖4-9所示。

圖4-9 斷點(diǎn)設(shè)置
我們?cè)冱c(diǎn)一下全速運(yùn)行,會(huì)發(fā)現(xiàn)sec變成了0.07530650秒,那么這樣一個(gè)for循環(huán)的時(shí)間大概有75ms左右,我們也可以通過(guò)改變30000這個(gè)數(shù)字來(lái)改變這個(gè)間隔時(shí)間。當(dāng)然了,大家要注意i的變量范圍,你如果寫成了大于65535的值以后,程序就會(huì)一直運(yùn)行不下去了,因?yàn)?font face="Times New Roman">i無(wú)論如何變化,都不會(huì)大于這個(gè)值,如果要大于這個(gè)值正常運(yùn)行,必須改變i定義的類型了。后邊如果我們要求看一段程序運(yùn)行多長(zhǎng)時(shí)間,都可以通過(guò)這種方式來(lái)看。
1.7 流水燈程序
我們前邊學(xué)了點(diǎn)亮LED小燈,然后又學(xué)了LED小燈閃爍,下邊我們要進(jìn)一步了解一下如何讓8個(gè)小燈依次一個(gè)一個(gè)點(diǎn)亮,流動(dòng)起來(lái)。
圖4-10 LED小燈電路圖
通過(guò)前面的課程,我們可以了解到控制引腳P0.0通過(guò)了74HC245控制DB0,P0.1控制DB1......P0.7控制DB7。我們還學(xué)到一個(gè)字節(jié)是8位,我們?nèi)绻麑懸粋€(gè)P0,就代表了P0.0到P0.7的共8個(gè)位。比如我們寫P0 = 0xFE;轉(zhuǎn)換成二進(jìn)制就是0b11111110,所以點(diǎn)亮LED小燈的程序,實(shí)際上我們可以改成另外一種寫法,如下所示。
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
void main()
{
ENLED = 0;
ADDR0 = 0;
ADDR1 = 1;
ADDR2 = 1;
ADDR3 = 1; //74HC138開啟三極管
P0 = 0xFE;
while(1); //程序停止在這里
}
通過(guò)上邊這個(gè)程序我們可以看出來(lái),可以通過(guò)P0來(lái)控制所有的8個(gè)LED小燈的亮和滅。我們下邊要進(jìn)行依次亮和滅,怎么辦呢?從這里就可以得到方法了,如果想讓單片機(jī)流水燈流動(dòng)起來(lái),依次要實(shí)現(xiàn)的結(jié)果是:0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F。
在我們的C語(yǔ)言當(dāng)中,有一個(gè)移位操作,其中<<代表的是左移,>>代表的是右移。比如a = 0x01 << 1就是a 的結(jié)果等于0x01左移一位。大家注意,移位都是指二進(jìn)制移位,那么移位完了,本來(lái)在第0位的1移動(dòng)到了第一位上,移動(dòng)完了低位是補(bǔ)0的。所以a的值最終是等于0x02。
還要學(xué)習(xí)另外一個(gè)運(yùn)算符~,這個(gè)符號(hào)是按位取反的意思,同樣,按位取反也是針對(duì)二進(jìn)制而言。比如a = ~(0x01),0x01的二進(jìn)制是0b00000001,按位取反是0b11111110,那么a的值就是0xFE了。
學(xué)會(huì)了這兩個(gè)符號(hào)后,那么我們就可以把流水燈的程序?qū)懗鰜?lái),先把程序貼上。
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
void main()
{
unsigned char j = 0;
unsigned int i = 0;
ENLED = 0;
ADDR0 = 0;
ADDR1 = 1;
ADDR2 = 1;
ADDR3 = 1; //74HC138開啟三極管Q16
while(1) //程序死循環(huán)
{
P0 = ~(0x01 << j++); //P0等于1左移j位,并且j++
for(i=0; i<20000; i++); //延時(shí)
if(j == 8) //如果j等于8,重新給j賦值0
{
j = 0;
}
}
}
這里我只講兩種情況,當(dāng)j等于0的時(shí)候,1左移0位還是1,那么寫成二進(jìn)制后就是0b00000001,對(duì)這個(gè)數(shù)字按位取反就是0b11111110,亮的是最右邊的小燈。當(dāng)j等于7的時(shí)候,1左移7位就是0b10000000,按位取反0b01111111,亮的是最左邊的小燈。中間過(guò)程大家自己分析一下。
流水燈結(jié)束后,關(guān)于小燈的講解,我們暫時(shí)告一段落,后邊還有小燈的高級(jí)用法,我們到時(shí)候再詳細(xì)講解。
1.8 作業(yè)
1、熟練掌握二進(jìn)制、十進(jìn)制和十六進(jìn)制的轉(zhuǎn)換方法。
2、掌握C語(yǔ)言變量類型和范圍,for、while等基本語(yǔ)句的用法。
3、了解函數(shù)的基本結(jié)構(gòu),能夠獨(dú)立進(jìn)入程序Debug,多多動(dòng)手操作,熟練Keil軟件環(huán)境的一些基本操作。
4、將流水燈左移理解透徹后,獨(dú)立完成流水燈右移操作以及流水燈先左移后右移等簡(jiǎn)單的花樣操作。
上一課:第三章 硬件基礎(chǔ)知識(shí)學(xué)習(xí)
下一課:第五章 定時(shí)器和數(shù)碼管