新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > 51系列單片機學(xué)習(xí)5—C編程程序語句

51系列單片機學(xué)習(xí)5—C編程程序語句

作者: 時間:2016-11-25 來源:網(wǎng)絡(luò) 收藏

#include

#include

void main(void)

{

unsigned int I = 1;

unsigned int SUM = 0; //設(shè)初值

SCON = 0x50; //串行口方式 1,允許接收

TMOD = 0x20; //定時器 1 定時方式 2

TCON = 0x40; //設(shè)定時器 1 開始計數(shù)

TH1 = 0xE8; //11.0592MHz 1200 波特率

TL1 = 0xE8; TI = 1;

TR1 = 1; //啟動定時器

while(I<=10)

{

SUM = I + SUM; //累加

printf ("%d SUM=%d",I,SUM); //顯示

I++;

}

while(1); //這句是為了不讓程序完后,程序指針繼續(xù)向下造成程序“跑飛”

}

//最后運行結(jié)果是 SUM=55;

do while 語句

do while 語句能說是 while 語句的補充,while 是先判斷條件是否成立再執(zhí)行循環(huán)體,

而 do while 則是先執(zhí)行循環(huán)體,再根據(jù)條件判斷是否要退出循環(huán)。這樣就決定了循環(huán)體無論在任何條件下都會至少被執(zhí)行一次。它的語法如下:

do 語句 while (條件表達式)

用 do while 怎么寫上面那個例程呢?先想一想,再參考下面的程序。

#include

#include

void main(void)

{ unsigned int I = 1;

unsigned int SUM = 0; //設(shè)初值

SCON = 0x50; //串行口方式 1,允許接收 TMOD = 0x20; //定時器 1 定時方式 2

TCON = 0x40; //設(shè)定時器 1 開始計數(shù)

TH1 = 0xE8; //11.0592MHz 1200 波特率 TL1 = 0xE8;

TI = 1;

TR1 = 1; //啟動定時器

do{

SUM = I + SUM; //累加

printf ("%d SUM=%d",I,SUM); //顯示 I++;

}while(I<=10);

while(1);

}在上面的程序看來 do while 語句和 while 語句似乎沒有什么兩樣,但在實際的應(yīng)用中要注

意任何 do while 的循環(huán)體一定會被執(zhí)行一次。如把上面兩個程序中 I 的初值設(shè)為 11,那么前一個程序不會得到顯示結(jié)果,而后一個程序則會得到 SUM=11。

for 語句

在明確循環(huán)次數(shù)的情況下,for 語句比以上說的循環(huán)語句都要方便簡單。它的語法如下:

for ([初值設(shè)定表達式];[循環(huán)條件表達式];[條件更新表達式]) 語句 中括號中的表達式是可選的,這樣 for 語句的變化就會很多樣了。for 語句的執(zhí)行:

先代入初值,再判斷條件是否為真,條件滿足時執(zhí)行循環(huán)體并更新條件,再判斷條件是否為真……直到條件為假時,退出循環(huán)。下面的例子所要實現(xiàn)的是和上二個例子一樣的,對照著 看不難理解幾個循環(huán)語句的差異。

#include

#include

void main(void)

{

unsigned int I;

unsigned int SUM = 0; //設(shè)初值

SCON = 0x50; //串行口方式 1,允許接收 TMOD = 0x20; //定時器 1 定時方式 2

TCON = 0x40; //設(shè)定時器 1 開始計數(shù)

TH1 = 0xE8; //11.0592MHz 1200 波特率 TL1 = 0xE8;

TI = 1;

TR1 = 1; //啟動定時器

for (I=1; I<=10; I++) //這里能設(shè)初始值,所以變量定義時能不設(shè)

{

SUM = I + SUM; //累加

printf ("%d SUM=%d",I,SUM); //顯示

}

while(1);

}

如果我們把程序中的 for 改成 for(; I<=10; I++)這樣條件的初值會變成當(dāng)前 I 變量的

值。如果改成 for(;;)會怎么樣呢?試試看。

continue 語句

continue 語句是用于中斷的語句,通常使用在循環(huán)中,它的作用是結(jié)束本次循環(huán),跳過循環(huán)體中沒有執(zhí)行的語句,跳轉(zhuǎn)到下一次循環(huán)周期。語法為:continue;

continue 同時也是一個無條件跳轉(zhuǎn)語句,但功能和前面說到的 break 語句有所不一樣, continue 執(zhí)行后不是跳出循環(huán),而是跳到循環(huán)的開始并執(zhí)行下一次的循環(huán)。在上面的例子 中的循環(huán)體加入 if (I==5) continue;看看什么結(jié)果?

return 語句

return 語句是返回語句,不屬于循環(huán)語句,是要學(xué)習(xí)的最后一個語句所以一并寫下了。 返回語句是用于結(jié)束函數(shù)的執(zhí)行,返回到調(diào)用函數(shù)時的位置。語法有二種:

return (表達式);

return; 語法中因帶有表達式,返回時先計算表達式,再返回表達式的值。不帶表達式則返回的

值不確定。下面是一個同樣是計算 1-10 的累加,所不一樣是的用了函數(shù)的方式。

#include

#include

int Count(void); //聲明函數(shù)

void main(void)

{

unsigned int temp;

SCON = 0x50; //串行口方式 1,允許接收 TMOD = 0x20; //定時器 1 定時方式 2

TCON = 0x40; //設(shè)定時器 1 開始計數(shù)

TH1 = 0xE8; //11.0592MHz 1200 波特率 TL1 = 0xE8;

TI = 1;TR1 = 1; //啟動定時器

temp = Count();

printf ("1-10 SUM=%d",temp); //顯示

while(1);

}

int Count(void)

{

unsigned int I, SUM;

for (I=1; I<=10; I++)

{

SUM = I + SUM; //累加

}

return (SUM);

}

上一篇的最后一個例子中有用到函數(shù),其實一直出現(xiàn)在例子中的 main()也算是一個函數(shù),只不過它比較特殊,編譯時以它做為程序的開始段。有了函數(shù) C 語言就有了模塊化的優(yōu)點,一般功能較多的程序,會在編寫程序時把每項單獨的功能分成數(shù)個子程序模塊,每個子程序就能用函數(shù)來實現(xiàn)。函數(shù)還能被反復(fù)的調(diào)用,因此一些常用的函數(shù)能做成函數(shù)庫以供在編寫程序時直接調(diào)用,從而更好的實現(xiàn)模塊化的設(shè)計,大大提高編程工作的效率。

一.函數(shù)定義

通常 C 語言的編譯器會自帶標(biāo)準(zhǔn)的函數(shù)庫,這些都是一些常用的函數(shù),Keil uv 中也不例外。標(biāo)準(zhǔn)函數(shù)已由編譯器軟件商編寫定義,使用者直接調(diào)用就能了,而無需定義。但是 標(biāo)準(zhǔn)的函數(shù)不足以滿足使用者的特殊要求,因此 C 語言允許使用者根據(jù)需要編寫特定功能的 函數(shù),要調(diào)用它必須要先對其進行定義。定義的模式如下:

函數(shù)類型函數(shù)名稱(形式參數(shù)表)

函數(shù)體

函數(shù)類型是說明所定義函數(shù)返回值的類型。返回值其實就是一個變量,只要按變量類型來定義函數(shù)類型就行了。如函數(shù)不需要返回值函數(shù)類型能寫作“void”表示該函數(shù)沒有返回值。注意的是函數(shù)體返回值的類型一定要和函數(shù)類型一致,不然會造成錯誤。函數(shù)名稱的定義在遵循 C 語言變量命名規(guī)則的同時,不能在同一程序中定義同名的函數(shù)這將會造成編譯錯誤(同一程序中是允許有同名變量的,因為變量有全局和局部變量之分)。形式參數(shù)是指調(diào)用函數(shù)時要傳入到函數(shù)體內(nèi)參與運算的變量,它能有一個、幾個或沒有,當(dāng)不需要形式參數(shù)也就是無參函數(shù),括號內(nèi)能為空或?qū)懭?ldquo;void”表示,但括號不能少。函數(shù)體中能包含有局部變量的定義和程序語句,如函數(shù)要返回運算值則要使用 return 語句進行返回。在函數(shù)的{}號中也能什么也不寫,這就成了空函數(shù),在一個程序項目中能寫一些空函數(shù),在以后的修改和升級中能方便的在這些空函數(shù)中進行功能擴充。

二.函數(shù)的調(diào)用

函數(shù)定義好以后,要被其它函數(shù)調(diào)用了才能被執(zhí)行。C 語言的函數(shù)是能相互調(diào)用的,但在調(diào)用函數(shù)前,必須對函數(shù)的類型進行說明,就算是標(biāo)準(zhǔn)庫函數(shù)也不例外。標(biāo)準(zhǔn)庫函數(shù)的說明會被按功能分別寫在不一樣的頭文件中,使用時只要在文件最前面用#include 預(yù)處理語句引入相應(yīng)的頭文件。如前面一直有使用的printf 函數(shù)說明就是放在文件名為 stdio.h 的頭文件中。調(diào)用就是指一個函數(shù)體中引用另一個已定義的函數(shù)來實現(xiàn)所需要的功能,這個時候函數(shù)體稱為主調(diào)用函數(shù),函數(shù)體中所引用的函數(shù)稱為被調(diào)用函數(shù)。一個函數(shù)體中能調(diào)用數(shù)個其它的函數(shù),這些被調(diào)用的函數(shù)同樣也能調(diào)用其它函數(shù),也能嵌套調(diào)用。筆者本人認(rèn)為 主函數(shù)只是相對于被調(diào)用函數(shù)而言。在 c51 語言中有一個函數(shù)是不能被其它函數(shù)所調(diào)用的, 它就是 main 主函數(shù)。

調(diào)用函數(shù)的一般形式如下:

函數(shù)名 (實際參數(shù)表) “函數(shù)名”就是指被調(diào)用的函數(shù)。實際參數(shù)表能為零或多個參數(shù),多個參數(shù)時要用逗號隔開,每個參數(shù)的類型、位置應(yīng)與函數(shù)定義時所的形式參數(shù)一一對應(yīng),它的作用就是把參數(shù)傳到被調(diào)用函數(shù)中的形式參數(shù),如果類型不對應(yīng)就會產(chǎn)生一些錯誤。調(diào)用的函數(shù)是無參函數(shù)時不寫參數(shù),但不能省后面的括號。

在以前的一些例子我們也能看不一樣的調(diào)用方式:

1.函數(shù)語句

如 printf ("Hello World!"); 這是在 我們的第一個程序中出現(xiàn)的,它以 "HelloWorld!"為參數(shù)調(diào)用 printf 這個庫函數(shù)。在這里函數(shù)調(diào)用被看作了一條語句。

2.函數(shù)參數(shù) “函數(shù)參數(shù)”這種方式是指被調(diào)用函數(shù)的返回值當(dāng)作另一個被調(diào)用函數(shù)的實際參數(shù),如 temp=StrToInt(CharB(16));CharB 的返回值作為 StrToInt 函數(shù)的實際參數(shù)傳遞。

3.函數(shù)表達式

而在上一篇的例子中有 temp = Count();這樣一句,這個時候函數(shù)的調(diào)用作為一個運算對象出現(xiàn)在表達式中,能稱為函數(shù)表達式。例子中 Count()返回一個 int 類型的返回 值直接賦值給 temp。注意的是這種調(diào)用方式要求被調(diào)用的函數(shù)能返回一個同類型的值, 不然會出現(xiàn)不可預(yù)料的錯誤。

前面說到調(diào)用函數(shù)前要對被調(diào)用的函數(shù)進行說明。標(biāo)準(zhǔn)庫函數(shù)只要用#include 引入已寫好說明的頭文件,在程序就能直接調(diào)用函數(shù)了。如調(diào)用的是自定義的函數(shù)則要用如下形式編寫函數(shù)類型說明類型標(biāo)識符

函數(shù)的名稱(形式參數(shù)表);

這樣的說明方式是用在被調(diào)函數(shù)定義和主調(diào)函數(shù)是在同一文件中。你也能把這些寫到文件名.h 的文件中用#include "文件名.h"引入。如果被調(diào)函數(shù)的定義和主調(diào)函數(shù)不是在同一文件中的,則要用如下的方式進行說明,說明被調(diào)函數(shù)的定義在同一項目的不一樣文件之上,其實庫函數(shù)的頭文件也是如此說明庫函數(shù)的,如果說明的函數(shù)也能稱為外部函數(shù)。

extern 類型標(biāo)識符 函數(shù)的名稱(形式參數(shù)表);

函數(shù)的定義和說明是完全不一樣的,在編譯的角度上看函數(shù)的定義是把函數(shù)編譯存放在ROM 的某一段地址上,而函數(shù)說明是告訴編譯器要在程序中使用那些函數(shù)并確定函數(shù)的地址。如果在同一文件中被調(diào)函數(shù)的定義在主調(diào)函數(shù)之前,這個時候能不用說明函數(shù)類型。也就 是說在 main 函數(shù)之前定義的函數(shù),在程序中就能不用寫函數(shù)類型說明了。能在一個函數(shù)體調(diào)用另一個函數(shù)(嵌套調(diào)用),但不允許在一個函數(shù)定義中定義另一個函數(shù)。還要注意的是函數(shù)定義和說明中的“類型、形參表、名稱”等都要相一致。

三.中斷函數(shù)

中斷服務(wù)函數(shù)是編寫單片機應(yīng)用程序不可缺少的。中斷服務(wù)函數(shù)只有在中斷源請求響應(yīng)中斷時才會被執(zhí)行,這在處理突發(fā)事件和實時控制是十分有效的。例如:電路中一個按鈕,要求按鈕后 LED 點亮,這個按鈕何時會被按下是不可預(yù)知的,為了要捕獲這個按鈕的事件,通常會有三種方法,一是用循環(huán)語句不斷的對按鈕進行查詢,二是用定時中斷在間隔時間內(nèi)掃描按鈕,三是用外部中斷服務(wù)函數(shù)對按鈕進行捕獲。在這個應(yīng)用中只有單一的按鈕功能,那么第一種方式就能勝任了,程序也很簡單,但是它會不停的在對按鈕進行查詢浪費了CPU 的時間。實際應(yīng)用中一般都會還有其它的功能要求同時實現(xiàn),這個時候能根據(jù)需要選用第 二或第三種方式,第三種方式占用的 CPU 時間最少,只有在有按鈕事件發(fā)生時,中斷服務(wù)函 數(shù)才會被執(zhí)行,其余的時間則是執(zhí)行其它的任務(wù)。

如果你學(xué)習(xí)過匯編語言的話,剛開始寫匯編的中斷應(yīng)用程序時,你一定會為出入堆棧的 問題而困擾過。單片機c語言語言擴展了函數(shù)的定義使它能直接編寫中斷服務(wù)函數(shù),你能不必考 慮出入堆棧的問題,從而提高了工作的效率。擴展的關(guān)鍵字是 interrupt,它是函數(shù)定義時的一個選項,只要在一個函數(shù)定義后面加上這個選項,那么這個函數(shù)就變成了中斷服務(wù)函數(shù)。在后面還能加上一個選項 using,這個選項是指定選用 51 芯片內(nèi)部 4 組工作寄存器中的那個組。開始學(xué)習(xí)者能不必去做工作寄存器設(shè)定,而由編譯器自動選擇,避免產(chǎn)生不必要的錯誤。定義中斷服務(wù)函數(shù)時能用如下的形式。

函數(shù)類型 函數(shù)名 (形式參數(shù)) interrupt n [using n]

interrupt 關(guān)鍵字是不可缺少的,由它告訴編譯器該函數(shù)是中斷服務(wù)函數(shù),并由后面的

n 指明所使用的中斷號。n 的取值范圍為 0-31,但具體的中斷號要取決于芯片的型號,像 AT89c51 實際上就使用 0-4 號中斷。每個中斷號都對應(yīng)一個中斷向量,具體地址為 8n+3, 中斷源響應(yīng)后處理器會跳轉(zhuǎn)到中斷向量所處的地址執(zhí)行程序,編譯器會在這地址上產(chǎn)生一個無條件跳轉(zhuǎn)語句,轉(zhuǎn)到中斷服務(wù)函數(shù)所在的地址執(zhí)行程序。下表是 51 芯片的中斷向量和中 斷號。

中斷號

中斷源

中斷向量

0

外部中斷 0

0003H

1

定時器/計數(shù)器 0

000BH

2

外部中斷 1

0013H

3

定時器/計數(shù)器 1

001BH

4

串行口

0023H

表 9-1 AT89c51 芯片中斷號和中斷向量

使用中斷服務(wù)函數(shù)時應(yīng)注意:中斷函數(shù)不能直接調(diào)用中斷函數(shù);不能通過形參傳速參數(shù); 在中斷函數(shù)中調(diào)用其它函數(shù),兩者所使用的寄存器組應(yīng)相同。限于篇幅其它與函數(shù)相關(guān)的知識這里不能一一加以說明,如變量的傳遞、存儲,局部變量、全部變量等,有興趣的朋友可 以訪問筆者的網(wǎng)站 閱讀更多相關(guān)文章。

下面是簡單的例子。首先要在前面做好的實驗電路中加多一個按鈕,接在 P3.2(12 引腳外 部中斷 INT0)和地線之間。把編譯好后的程序燒錄到芯片后,當(dāng)接在 P3.2 引腳的按鈕接下 時,中斷服務(wù)函數(shù) Int0Demo 就會被執(zhí)行,把 P3 當(dāng)前的狀態(tài)反映到 P1,如按鈕接下后 P3.7(之前有在這腳裝過一按鈕)為低,這個時候 P1.7 上的 LED 就會熄滅。放開 P3.2 上的按鈕后,

P1LED 狀態(tài)保持先前按下 P3.2 時 P3 的狀態(tài)。

#include

unsigned char P3State(void); //函數(shù)的說明,中斷函數(shù)不用說明

void main(void)

{

IT0 = 0; //設(shè)外部中斷 0 為低電平觸發(fā)

EX0 = 1; //允許響應(yīng)外部中斷 0

EA = 1; //總中斷開關(guān)

while(1);

}

//外部中斷 0 演示,使用 2 號寄存器組

void Int0Demo(void) interrupt 0 using 2

{

unsigned int Temp; //定義局部變量

P1 = ~P3State(); //調(diào)用函數(shù)取得 p2 的狀態(tài)反相后并賦給 P1

for (Temp=0; Temp<50; Temp++); //延時這里只是演示局部變量的使用

}

//用于返回 P3 的狀態(tài),演示函數(shù)的使用

unsigned char P3State(void)

{

unsigned char Temp;

Temp = P3; //讀取 P3 的引腳狀態(tài)并保存在變量 Temp 中,這樣只有一句語句實在沒必要做成函數(shù),這里只是學(xué)習(xí)函數(shù)的基本使用方法

return Temp;

}

前面的文章中,都是介紹單個數(shù)據(jù)變量的使用,在“走馬燈”等的例子中略有使用到數(shù)組,不難看出,數(shù)組不過就是同一類型變量的有序集合。形象的能這樣去理解,就像一個學(xué)校在操場上排隊,每一個級代表一個數(shù)據(jù)類型,每一個班級為一個數(shù)組,每一個學(xué)生就是數(shù)組中的一個數(shù)據(jù)。數(shù)據(jù)中的每個數(shù)據(jù)都能用唯一的下標(biāo)來確定其位置,下標(biāo)能是一維 或多維的。就如在學(xué)校的方隊中要找一個學(xué)生,這個學(xué)生在 I 年級 H 班 X 組 Y 號的,那么 能把這個學(xué)生看做在 I 類型的 H 數(shù)組中(X,Y)下標(biāo)位置中。數(shù)組和普通變量一樣,要求先定義了才能使用,下面是定義一維或多維數(shù)組的方式:

數(shù)據(jù)類型

數(shù)組名

[常量表達式];

數(shù)據(jù)類型

數(shù)組名

[常量表達式 1]...... [常量表達式 N];

“數(shù)據(jù)類型”是指數(shù)組中的各數(shù)據(jù)單元的類型,每個數(shù)組中的數(shù)據(jù)單元只能是同一數(shù)據(jù)類型。“數(shù)組名”是整個數(shù)組的標(biāo)識,命名方法和變量命名方法是一樣的。在編譯時系統(tǒng)會根據(jù)數(shù)組大小和類型為變量分配空間,數(shù)組名能說就是所分配空間的首地址的標(biāo)識。“常量表達式”是表示數(shù)組的長度和維數(shù),它必須用“[]”括起,括號里的數(shù)不能是變量只能是 常量。

unsigned int xcount [10]; //定義無符號整形數(shù)組,有 10 個數(shù)據(jù)單元

char inputstring [5]; //定義字符形數(shù)組,有 5 個數(shù)據(jù)單元

float outnum [10],[10];//定義浮點型數(shù)組,有 100 個數(shù)據(jù)單元

在 C 語言中數(shù)組的下標(biāo)是從 0 開始的而不是從 1 開始,如一個具有 10 個數(shù)據(jù)單元的數(shù)組 count,它的下標(biāo)就是從 count[0]到 count[9],引用單個元素就是數(shù)組名加下標(biāo),如 count[1] 就是引用 count 數(shù)組中的第 2 個元素,如果錯用了 count[10]就會有錯誤出現(xiàn)了。還有一點要 注意的就是在程序中只能逐個引用數(shù)組中的元素,不能一次引用整個數(shù)組,但是字符型的數(shù)組就能一次引用整個數(shù)組。

數(shù)組也是能賦初值的。在上面介紹的定義方式只適用于定義在內(nèi)存 DATA 存儲器使 用的內(nèi)存,有的時候我們需要把一些數(shù)據(jù)表存放在數(shù)組中,通常這些數(shù)據(jù)是不用在程序中改變數(shù)值的,這個時候就要把這些數(shù)據(jù)在程序編寫時就賦給數(shù)組變量。因為 51 芯片的片內(nèi) RAM 很有限,通常會把 RAM 分給參與運算的變量或數(shù)組,而那些程序中不變數(shù)據(jù)則應(yīng)存放在片 內(nèi)的 CODE 存儲區(qū),以節(jié)省寶貴的 RAM。賦初值的方式如下:

數(shù)據(jù)類型 [存儲器類型] 數(shù)組名 [常量表達式] = {常量表達式};

數(shù)據(jù)類型 [ 存儲器類型] 數(shù)組名 [ 常量表達式 1]...... [ 常量表達式 N]={{ 常量表達 式}...{常量表達式 N}};

在定義并為數(shù)組賦初值時,開始學(xué)習(xí)的朋友一般會搞錯初值個數(shù)和數(shù)組長度的關(guān)系,而致使編譯出錯。初值個數(shù)必須小于或等于數(shù)組長度,不指定數(shù)組長度則會在編譯時由實際的初值 個數(shù)自動設(shè)置。

unsigned char LEDNUM[2]={12,35}; //一維數(shù)組賦初值

int Key[2][3]={{1,2,4},{2,2,1}}; //二維數(shù)組賦初值

unsigned char IOStr[]={3,5,2,5,3}; //沒有指定數(shù)組長度,編譯器自動設(shè)置

unsigned char code skydata[]={0x02,0x34,0x22,0x32,0x21,0x12}; //數(shù)據(jù)保存在 code 區(qū)

下面的一個簡單例子是對數(shù)組中的數(shù)據(jù)進行排序,使用的是冒泡法,一來了解數(shù)組的使用,二來掌握基本的排序算法。冒泡排序算法是一種基本的排序算法,它每次順序取數(shù)組中的兩個數(shù),并按需要按其大小排列,在下一次循環(huán)中則取下一次的一個數(shù)和數(shù)組中下一個數(shù) 進行排序,直到數(shù)組中的數(shù)據(jù)全部排序完成。

#include

#include

void taxisfun (int taxis2[])

{

unsigned char TempCycA,TempCycB,Temp;

for (TempCycA=0; TempCycA<=8; TempCycA++)

for (TempCycB=0; TempCycB<=8-TempCycA; TempCycB++)

{//TempCycB<8-TempCycA 比用 TempCycB<=8 少用很多循環(huán)

if (taxis2[TempCycB+1]>taxis2[TempCycB]) //當(dāng)后一個數(shù)大于前一個 數(shù)

{

Temp = taxis2[TempCycB]; //前后 2 數(shù)交換

taxis2[TempCycB] = taxis2[TempCycB+1];

taxis2[TempCycB+1] = Temp; //因函數(shù)參數(shù)是數(shù)組名調(diào)用形參的變動影響實參

}

}

}



評論


技術(shù)專區(qū)

關(guān)閉