
?
第十課 C51表達式語句及仿真器
第十一課 C51復合語句和條件語句
第十二課 C51開關分支語句
第十三課 C51循環語句
第十四課 C51函數
第十課、C51表達式語句及仿真器
前面學習了大部分的基本語法,以下所要學習的各種基本語句的語法能說是組成程序的靈魂。在前面的課程中的例子里,也簡單理解過一些語句的使用方法,能看出C語言是一種結構化的程序設計語言。C 語言供給了相當豐富的程序控制語句。學習掌握這些語句的使用方法也是單片機C語言學習中的重點。
表達式語句是最基本的一種語句。不一樣的程序設計語言都會有不一樣的表達式語句,如VB就是在表達式后面加入回車就構成了VB 的表達式語句,而在51單片機的C語言中則是加入分號“;”構成表達式語句。舉例如下:
b = b * 10; Count++;
X = A;Y = B;
Page = (a+b)/a-1;
以上的都是合法的表達式語句。在我收到的一些網友的 Email 中,發現很多開始學習的朋友一般在編寫調試程序時忽略了分號“;”,造成程序不能被正常的編譯。我本人的經驗是在遇 到編譯錯誤時先語法是否有誤,這在開始學習時一般會因在程序中加入了全角符號、運算符打錯 漏掉或沒有在后面加“;”。
在 C 語言中有一個特殊的表達式語句,稱為空語句,它僅僅是由一個分號“;”組成。 有時候為了使語法正確,那么就要求有一個語句,但這個語句又沒有實際的運行效果那么這 時就要有一個空語句。說起來就像大家在晚自修的時候用書包占位一樣,呵呵。
空語句通常用會以下兩種使用方法。
(1)while,for 構成的循環語句后面加一個分號,形成一個不執行其它操作的空循環體。 我會會常常用它來寫等待事件發生的程序。大家要注意的是“;”號作為空語句使用時,要 與語句中有效組成部分的分號相區別,如 for (;a《50000;a++);第一個分號也應該算是 空語句,它會使 a 賦值為 0(但要注意的是如程序前有 a 值,則 a 的初值為 a 的當前值),最后一個分號則使整個語句行成一個空循環。若此時 a=0,那么 for (;a《50000;a++);就相當
于 for (a=0;a《50000;a++);我本人習慣是寫后面的寫法,這樣能使人更不難讀明白。 (2)在程序中為有關語句供給標號,標記程序執行的位置,使相關語句能跳轉到要執行
的位置。這會用在 goto 語句中。
下面的示例程序是簡單說明 while 空語句的使用方法。硬件的功能很簡單,就是在 P3.7 上 接一個開關,當開關按下時 P1 上的燈會全亮起來。當然實際應用中按鈕的功能實現并沒有 這么的簡單,一般還要進行防抖動處理等。
先在我們的實驗板上加一個按鈕。電路圖如圖 10-1。
程序如下:
#include 《AT89x51.h》
void main(void)
{
圖 10-1 加了按鈕的實驗電路圖
unsigned int a;
do
{
P1 = 0xFF; //關閉 P1 上的 LED
while(P3_7); //空語句,等待 P3_7 按下為低電平,低電平時執行下面的語句 P1 = 0; //點亮 LED
for(;a《60000;a++); //這也是空語句的使用方法,注意 a 的初值為當前值
} //這樣第一次按下時會有一延時點亮一段時間,以后按多久就亮多久
while(1); //點亮一段時間后關閉再次判斷 P3_7,如此循環
}
上面的實驗電路已加入了 RS232 串行口電路,只要稍微改變一下,就能變為具有仿真功能的 實驗電路。這個改變的關鍵就是把芯片改用 SST89C58,并在芯片中燒入仿真監控程序。 SST89C58 同樣也是一種 51 架構的單片機,它具有 24K+8K 的兩個程序存儲區,能選擇其 一做為程序的啟動區。只要把一個叫 SOFTICE.HEX 的監控程序用支持 SST89C58 的編程器燒 錄到芯片中(使用編程器或用 CA 版的 SST89C58 燒錄 SOFTICE 的具體方法和文件能參考 ),就 能把上 面 的電路升級為
MON51 仿真實驗器。那么怎么用它和 KEIL 實現聯機仿真呢?
圖 10-2 項目設置菜單
圖 10-3 項目設置
????? ?首先要在你要仿真的程序項目設置仿真器所使用的驅動,在 Debug 頁中選擇對應本仿真器的 KeilMon51 驅 動,如圖 10 中 1 所示。圖 10-3 的 3 是選擇在仿真時能使用的工具窗口,如內存顯示,斷點等等。按 2 進 行圖 10-4 中的仿真器設置。設置好串行口號,波特率,晶體震蕩器為 11.0592M 時選 38400。Cache Options 為仿真 緩選取后會加快仿真的運行的速度。設好后編譯運行程序就能連接仿真器了,連接成功會出現如圖 10-
5 的畫面。如連接不成功就出現圖 10-6 的圖,這個時候能先復位電路再按“Try Again”,還不成功連接的話則 應檢查軟件設置和硬件電路。圖 10-5 中 1 是指示仿真器的固件版本為 F-MON51V3.4 版。點擊 3 中小紅 點位置時為設置和取消斷點,點擊 2 則運行到下一個斷點。圖 10-7 則是變量和存儲器的查看。仿真器在
軟件大概的使用方法和軟件仿真相差不多。
圖 10-4 仿真器設置
圖 10-5 仿真器連接成功
圖 10-6 連接不成功提示
圖 10-7 變量及內存查看
第十一課 C51復合語句和條件語句
曾經在BBS上有朋友問過我{}是什么意思?什么作用?在 C 中是有不少的括號,如{},[],()等,確實會讓一些初入門的朋友不解。在 VB 等一些語言中同一個()號會有不一樣的 作用,它能用于組合若干條語句形成功能塊,能用做數組的下標等,而在 C 中括號的分 工較為明顯,{}號是用于將若干條語句組合在一起形成一種功能塊,這種由若干條語句組合 而成的語句就叫復合語句。復合語句之間用{}分隔,而它內部的各條語句還是需要以分號“;” 結束。復合語句是允許嵌套的,也是就是在{}中的{}也是復合語句。復合語句在程序運行時,{}中的各行單語句是依次順序執行的。單片機C語言中能將復合語句視為一條單語句,也就是說 在語法上等同于一條單語句。對于一個函數而言,函數體就是一個復合語句,也許大家會因 此知道復合語句中不單能用可執行語句組成,還能用變量定義語句組成。要注意的是在 復合語句中所定義的變量,稱為局部變量,所謂局部變量就是指它的有效范圍只在復合語句 中,而函數也算是復合語句,所以函數內定義的變量有效范圍也只在函數內部。下面用一段簡單的例子簡單說明復合語句和局部變量的使用。
#include 《at89x51.h》
#include 《stdio.h》
void main(void)
{
unsigned int a,b,c,d; //這個定義會在整個 main 函數中?
SCON = 0x50; //串行口方式 1,允許接收 TMOD = 0x20; //定時器 1 定時方式 2
TH1 = 0xE8; //11.0592MHz 1200 波特率 TL1 = 0xE8;
TI = 1;
TR1 = 1; //啟動定時器
a = 5; b = 6; c = 7;
d = 8; //這會在整個函數有效
printf(“0: %d,%d,%d,%d ”,a,b,c,d);
{ //復合語句 1
unsigned int a,e; //只在復合語句 1 中有效
a = 10,e = 100;
printf(“1: %d,%d,%d,%d,%d ”,a,b,c,d,e);
{ //復合語句 2
unsigned int b,f; //只在復合語句 2 中有效
b = 11,f = 200;
printf(“2: %d,%d,%d,%d,%d,%d ”,a,b,c,d,e,f);
}//復合語句 2 結束
printf(“1: %d,%d,%d,%d,%d ”,a,b,c,d,e);
}//復合語句 1 結束
printf(“0: %d,%d,%d,%d ”,a,b,c,d);
while(1);
}
運行結果:
0:5,6,7,8
1: 10,6,7,8,100
2: 10,11,7,8,100,200
1: 10,6,7,8,100
0:5,6,7,8 結合以上的說明想想為何結果會是這樣。
讀完前面的文章大家都會大概對條件語句這個概念有所認識吧?是的,就如學習語文中 的條件語句一樣,C 語言也一樣是“如果 XX 就 XX”或是“如果 XX 就 XX 不然 XX”。也就是 當條件符合時就執行語句。條件語句又被稱為分支語句,也有人會稱為判斷語句,其關鍵字 是由 if 構成,這大眾多的高級語言中都是基本相同的。C 語言供給了 3 種形式的條件語句:
1: if (條件表達式) 語句 當條件表達式的結果為真時,就執行語句,不然就跳過。 如 if (a==b) a++; 當 a 等于 b 時,a 就加 1
2: if (條件表達式) 語句 1
else 語句 2
當條件表達式成立時,就執行語句 1,不然就執行語句 2 如 if (a==b)
a++;
else
a--;
當 a 等于 b 時,a 加 1,不然 a-1。
3:if (條件表達式 1) 語句 1
else if (條件表達式 2) 語句 2
else if (條件表達式 3) 語句 3
else if (條件表達式 m) 語句 n else 語句 m
這是由 if else 語句組成的嵌套,用來實現多方向條件分支,使用應注意 if 和 else 的配對使用,要是少了一個就會語法出錯,記住 else 總是與最臨近的 if 相配對。一般條件 語句只會用作單一條件或少數量的分支,如果多數量的分支時則更多的會用到下一篇中的開 關語句。如果使用條件語句來編寫超過 3 個以上的分支程序的話,會使程序變得不是那么清晰易讀。
第十二課 C51開關分支語句
學習了條件語句,用多個條件語句能實現多方向條件分支,但是能發現使用過多的 條件語句實現多方向分支會使條件語句嵌套過多,程序冗長,這樣讀起來也很不好讀。這個時候 使用開關語句同樣能達到處理多分支選擇的目的,又能使程序結構清晰。它的語法為下:
switch (表達式)
{
case 常量表達式 1: 語句 1; break; case 常量表達式 2: 語句 2; break; case 常量表達式 3: 語句 3; break; case 常量表達式 n: 語句 n; break; default: 語句
}
運行中 switch 后面的表達式的值將會做為條件,與 case 后面的各個常量表達式的值相 對比,如果相等時則執行 case 后面的語句,再執行 break(間斷語句)語句,跳出 switch 語句。如果 case 后沒有和條件相等的值時就執行 default 后的語句。當要求沒有符合的條 件時不做任何處理,則能不寫 default 語句。
在上面的章節中我們一直在用 printf 這個標準的 C 輸出函數做字符的輸出,使用它當 然會很方便,但它的功能強大,所占用的存儲空間自然也很大,要 1K 左右字節空間,如果 再加上 scanf 輸入函數就要達到 2K 左右的字節,這樣的話如果要求用 2K 存儲空間的芯片時 就無法再使用這兩個函數,例如 AT89C2051。在這些小項目中,通常我們只是要求簡單的字 符輸入輸出,這里以筆者發表在本人網站的一個簡單的串行口應用實例為例,一來學習使用開 關語句的使用,二來簡單了解 51 芯片串行口基本編程。這個實例是用 PC 串行口通過上位機程序 與由 AT89c51 組成的下位機相通信,實現用 PC 軟件控制 AT89c51 芯片的 IO 口,這樣也就可 以再通過相關電路實現對設備的控制。為了方便實驗,在此所使用的硬件還是用回以上課程 中做好的硬件,以串行口和 PC 連接,用 LED 查看實驗的結果。原代碼請到在筆者的網站 下載,上面有 單片機c語言 下位機源碼、PC 上位機源碼、電路圖等資料。
代碼中有多處使用開關語句的,使用它對不一樣的條件做不一樣的處理,如在 CSToOut 函數 中根據 CN[1]來選擇輸出到那個 IO 口,CN[1]=0 則把 CN[2]的值送到 P0,CN[1]=1 則送到 P1, 這樣的寫法比起用 if (CN[1]==0)這樣的判斷語句來的清晰明了。當然它們的效果沒有太大 的差別(在不考慮編譯后的代碼執行效率的情況下)。
在這段代碼主要的作用就是通過串行口和上位機軟件進行通信,跟據上位機的命令字串, 對指定的 IO 端口進行讀寫。InitCom 函數,原型為 void InitCom(unsigned char BaudRate), 其作用為初始化串行口。它的輸入參數為一個字節,程序就是用這個參數做為開關語句的選擇 參數。如調用 InitCom(6),函數就會把波特率設置為 9600。當然這段代碼只使用了一種波特 率,能用更高效率的語句去編寫,這里就不多討論了。
看到這里,你也許會問函數中的 SCON,TCON,TMOD,SCOM 等是代表什么?它們是特殊 功能寄存器。
SBUF 數據緩沖寄存器 這是一個能直接尋址的串行口專用寄存器。有朋友這樣問起 過“為何在串行口收發中,都只是使用到同一個寄存器 SBUF?而不是收發各用一個寄存器。” 實際上 SBUF 包含了兩個獨立的寄存器,一個是發送寄存,另一個是接收寄存器,但它們都 共同使用同一個尋址地址-99H。CPU 在讀 SBUF 時會指到接收寄存器,在寫時會指到發送寄
存器,而且接收寄存器是雙緩沖寄存器,這樣能避免接收中斷沒有及時的被響應,數據沒
有被取走,下一幀數據已到來,而造成的數據重疊問題。發送器則不需要用到雙緩沖,一般 情況下我們在寫發送程序時也不必用到發送中斷去外理發送數據。操作 SBUF 寄存器的方法 則很簡單,只要把這個 99H 地址用關鍵字 sfr 定義為一個變量就能對其進行讀寫操作了,
如 sfr SBUF = 0x99;當然你也能用其它的名稱。通常在標準的 reg51.h 或 at89x51.h 等 頭文件中已對其做了定義,只要用#include 引用就能了。
SCON 串行口控制寄存器 通常在芯片或設備中為了監視或控制接口狀態,都會引用 到接口控制寄存器。SCON 就是 51 芯片的串行口控制寄存器。它的尋址地址是 98H,是一個 能位尋址的寄存器,作用就是監視和控制 51 芯片串行口的工作狀態。51 芯片的串行口能 工作在幾個不一樣的工作模式下,其工作模式的設置就是使用 SCON 寄存器。它的各個位的具 體定義如下:
(MSB) (LSB) SM0 SM1 SM2 REN TB8 RB8 TI RI
表 8-1 串行口控制寄存器 SCON
SM0、SM1 為串行口工作模式設置位,這樣兩位能對應進行四種模式的設置。看表 8
-2 串行口工作模式設置。
表 8-2 串行口工作模式設置
在這里只說明最常用的模式 1,其它的模式也就一一略過,有興趣的朋友能找相關的 硬件資料查看。表中的 fosc 代表振蕩器的頻率,也就是晶體震蕩器的頻率。UART 為(Universal Asynchronous Receiver)的英文縮寫。
SM2 在模式 2、模式 3 中為多處理機通信使能位。在模式 0 中要求該位為 0。
REM 為允許接收位,REM 置 1 時串行口允許接收,置 0 時禁止接收。REM 是由軟件置位或 清零。如果在一個電路中接收和發送引腳 P3.0,P3.1 都和上位機相連,在軟件上有串行口中斷 處理程序,當要求在處理某個子程序時不允許串行口被上位機來的控制字符產生中斷,那么可 以在這個子程序的開始處加入 REM=0 來禁止接收,在子程序結束處加入 REM=1 再次打開串行口 接收。大家也能用上面的實際源碼加入 REM=0 來進行實驗。
TB8 發送數據位 8,在模式 2 和 3 是要發送的第 9 位。該位能用軟件根據需要置位或 清除,通常這位在通信協議中做奇偶位,在多處理機通信中這一位則用于表示是地址幀還是 數據幀。
RB8 接收數據位 8,在模式 2 和 3 是已接收數據的第 9 位。該位可能是奇偶位,地址/ 數據標識位。在模式 0 中,RB8 為保留位沒有被使用。在模式 1 中,當 SM2=0,RB8 是已接 收數據的停止位。
TI 發送中斷標識位。在模式 0,發送完第 8 位數據時,由硬件置位。其它模式中則是在 發送停止位之初,由硬件置位。TI 置位后,申請中斷,CPU 響應中斷后,發送下一幀數據。 在任何模式下,TI 都必須由軟件來清除,也就是說在數據寫入到 SBUF 后,硬件發送數據,
中斷響應(如中斷打開),這個時候 TI=1,表明發送已完成,TI 不會由硬件清除,所以這個時候必須
用軟件對其清零。
RI 接收中斷標識位。在模式 0,接收第 8 位結束時,由硬件置位。其它模式中則是在接 收停止位的半中間,由硬件置位。RI=1,申請中斷,要求 CPU 取走數據。但在模式 1 中,SM2=1 時,當未收到有效的停止位,則不會對 RI 置位。同樣 RI 也必須要靠軟件清除。
常用的串行口模式 1 是傳輸 10 個位的,1 位起始位為 0,8 位數據位,低位在先,1 位停止 位為 1。它的波特率是可變的,其速率是取決于定時器 1 或定時器 2 的定時值(溢出速率)。 AT89c51 和 AT89C2051 等 51 系列芯片只有兩個定時器,定時器 0 和定時器 1,而定時器 2
是 89C52 系列芯片才有的。
波特率 在使用串行口做通信時,一個很重要的參數就是波特率,只有上下位機的波特率 一樣時才能進行正常通信。波特率是指串行端口每秒內能傳輸的波特位數。有一些開始學習 的朋友認為波特率是指每秒傳輸的字節數,如標準 9600 會被誤認為每秒種能傳送 9600 個字節,而實際上它是指每秒能傳送 9600 個二進位,而一個字節要 8 個二進位,如用串 口模式 1 來傳輸那么加上起始位和停止位,每個數據字節就要占用 10 個二進位,9600 波特 率用模式 1 傳輸時,每秒傳輸的字節數是 9600÷10=960 字節。51 芯片的串行口工作模式 0 的波特率是固定的,為 fosc/12,以一個 12M 的晶體震蕩器來計算,那么它的波特率能達到 1M。 模式 2 的波特率是固定在 fosc/64 或 fosc/32,具體用那一種就取決于 PCON 寄存器中的 SMOD 位,如 SMOD 為 0,波特率為 focs/64,SMOD 為 1,波特率為 focs/32。模式 1 和模式 3 的波 特率是可變的,取決于定時器 1 或 2(52 芯片)的溢出速率。那么我們怎么去計算這兩個模 式的波特率設置時相關的寄存器的值呢?能用以下的公式去計算。
波特率=(2SMOD÷32)×定時器 1 溢出速率
上式中如設置了 PCON 寄存器中的 SMOD 位為 1 時就能把波特率提升 2 倍。通常會使用 定時器 1 工作在定時器工作模式 2 下,這個時候定時值中的 TL1 做為計數,TH1 做為自動重裝值 , 這個定時模式下,定時器溢出后,TH1 的值會自動裝載到 TL1,再次開始計數,這樣能不 用軟件去干預,使得定時更準確。在這個定時模式 2 下定時器 1 溢出速率的計算公式如下:
溢出速率=(計數速率)/(256-TH1) 上式中的“計數速率”與所使用的晶體振蕩器頻率有關,在 51 芯片中定時器啟動后會
在每一個機器周期使定時寄存器 TH 的值增加一,一個機器周期等于十二個振蕩周期,所以
能得知 51 芯片的計數速率為晶體振蕩器頻率的 1/12,一個 12M 的晶體震蕩器用在 51 芯片上, 那么 51 的計數速率就為 1M。通常用 11.0592M 晶體是為了得到標準的無誤差的波特率,那 么為何呢?計算一下就知道了。如我們要得到 9600 的波特率,晶體震蕩器為 11.0592M 和 12M,定 時器 1 為模式 2,SMOD 設為 1,分別看看那所要求的 TH1 為何值。代入公式:
11.0592M
9600=(2÷32)×((11.0592M/12)/(256-TH1))
TH1=250 //看看是不是和上面實例中的使用的數值一樣?
12M
9600=(2÷32)×((12M/12)/(256-TH1)) TH1≈249.49
上面的計算能看出使用 12M 晶體的時候計算出來的 TH1 不為整數,而 TH1 的值只能取
整數,這樣它就會有一定的誤差存在不能產生精確的 9600 波特率。當然一定的誤差是能 在使用中被接受的,就算使用 11.0592M 的晶體振蕩器也會因晶體本身所存在的誤差使波特
率產生誤差,但晶體本身的誤差對波特率的影響是十分之小的,能忽略不計。
第十三課 C51循環語句
循環語句是幾乎每個程序都會用到的,它的作用就是用來實現需要反復進行多次的操 作。如一個 12M 的 51 芯片應用電路中要求實現 1 毫秒的延時,那么就要執行 1000 次空語句 才能達到延時的目的(當然能使用定時器來做,這里就不討論),如果是寫 1000 條空語 句那是多么麻煩的事情,再者就是要占用很多的存儲空間。我們能知道這 1000 條空語句, 無非就是一條空語句重復執行 1000 次,因此我們就能用循環語句去寫,這樣不但使程序
結構清晰明了,而且使其編譯的效率大大的提高。在 C 語言中構成循環控制的語句有 while,do-while,for 和 goto 語句。同樣都是起到循環作用,但具體的作用和使用方法又大不一 樣。我們具體來看看。
goto 語句
這個語句在很多高級語言中都會有,記得小時候用 BASIC 時就很喜歡用這個語句。它是 一個無條件的轉向語句,只要執行到這個語句,程序指針就會跳轉到 goto 后的標號所在的 程序段。它的語法如下:
goto 語句標號; 其中的語句標號為一個帶冒號的標識符。示例如下
void main(void)
{
unsigned char a;
start: a++;
if (a==10) goto end;
goto start;
end:;
}
上面一段程序只是說明一下 goto 的使用方法,實際編寫很少使用這樣的手法。這段程序的意思
是在程序開始處用標識符“start:”標識,表示程序這是程序的開始,“end:”標識程序的 結束,標識符的定義應遵循前面所講的標識符定義原則,不能用 C 的關鍵字也不能和其它變 量和函數名相同,不然就會出錯了。程序執行 a++,a 的值加 1,當 a 等于 10 時程序會跳到 end 標識處結束程序,不然跳回到 start 標識處繼續 a++,直到 a 等于 10。上面的示例說明 goto 不但能無條件的轉向,而且能和 if 語句構成一個循環結構,這些在 C 程序員的程 序中都不太常見,常見的 goto 語句使用方法是用它來跳出多重循環,不過它只能從內層循環 跳到外層循環,不能從外層循環跳到內層循環。在下面說到 for 循環語句時再略為提一提。 為何大多數 C 程序員都不喜歡用 goto 語句?那是因為過多的使用它時會程序結構不清晰,
過多的跳轉就使程序又回到了匯編的編程風格,使程序失去了 C 的模塊化的優點。
while 語句
while 語句的意思很不難理解,在英語中它的意思是“當…的時候…”,在這里我們可 以理解為“當條件為真的時候就執行后面的語句”,它的語法如下:
while (條件表達式) 語句;
使用 while 語句時要注意當條件表達式為真時,它才執行后面的語句,執行完后再次回
到 while 執行條件判斷,為真時重復執行語句,為假時退出循環體。當條件一開始就為假時, 那么 while 后面的循環體(語句或復合語句)將一次都不執行就退出循環。在調試程序時要
注意 while 的判斷條件不能為假而造成的死循環,調試時適當的在 while 處加入斷點,也許 會使你的調試工作更加順利。當然有時會使用到死循環來等待中斷或 IO 信號等,如在第一 篇時我們就用了 while(1)來不停的輸出“Hello World!”。下面的例子是顯示從 1 到 10 的累 加和,讀者能修改一下 while 中的條件看看結果會如果,從而體會一下 while 的使用方法。
#include 《AT89X51.H》
#include 《stdio.h》
void main(void)
{
unsigned int I = 1;
unsigned int SUM = 0; //設初值
SCON = 0x50; //串行口方式 1,允許接收
TMOD = 0x20; //定時器 1 定時方式 2
TCON = 0x40; //設定時器 1 開始計數
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); //這句是為了不讓程序完后,程序指針繼續向下造成程序“跑飛”
}
//最后運行結果是 SUM=55;
do while 語句
do while 語句能說是 while 語句的補充,while 是先判斷條件是否成立再執行循環體,
而 do while 則是先執行循環體,再根據條件判斷是否要退出循環。這樣就決定了循環體無 論在任何條件下都會至少被執行一次。它的語法如下:
do 語句 while (條件表達式)
用 do while 怎么寫上面那個例程呢?先想一想,再參考下面的程序。
#include 《AT89X51.H》
#include 《stdio.h》
void main(void)
{
unsigned int I = 1;
unsigned int SUM = 0; //設初值
SCON = 0x50; //串行口方式 1,允許接收 TMOD = 0x20; //定時器 1 定時方式 2
TCON = 0x40; //設定時器 1 開始計數
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 語句似乎沒有什么兩樣,但在實際的應用中要注
意任何 do while 的循環體一定會被執行一次。如把上面兩個程序中 I 的初值設為 11,那么 前一個程序不會得到顯示結果,而后一個程序則會得到 SUM=11。
for 語句
在明確循環次數的情況下,for 語句比以上說的循環語句都要方便簡單。它的語法如下: for ([初值設定表達式];[循環條件表達式];[條件更新表達式]) 語句 中括號中的表達式是可選的,這樣 for 語句的變化就會很多樣了。for 語句的執行:先
代入初值,再判斷條件是否為真,條件滿足時執行循環體并更新條件,再判斷條件是否為 真……直到條件為假時,退出循環。下面的例子所要實現的是和上二個例子一樣的,對照著 看不難理解幾個循環語句的差異。
#include 《AT89X51.H》
#include 《stdio.h》
void main(void)
{
unsigned int I;
unsigned int SUM = 0; //設初值
SCON = 0x50; //串行口方式 1,允許接收 TMOD = 0x20; //定時器 1 定時方式 2
TCON = 0x40; //設定時器 1 開始計數
TH1 = 0xE8; //11.0592MHz 1200 波特率 TL1 = 0xE8;
TI = 1;
TR1 = 1; //啟動定時器
for (I=1; I《=10; I++) //這里能設初始值,所以變量定義時能不設
{
SUM = I + SUM; //累加
printf (“%d SUM=%d ”,I,SUM); //顯示
}
while(1);
}
如果我們把程序中的 for 改成 for(; I《=10; I++)這樣條件的初值會變成當前 I 變量的
值。如果改成 for(;;)會怎么樣呢?試試看。
continue 語句
continue 語句是用于中斷的語句,通常使用在循環中,它的作用是結束本次循環,跳 過循環體中沒有執行的語句,跳轉到下一次循環周期。語法為:
continue;
continue 同時也是一個無條件跳轉語句,但功能和前面說到的 break 語句有所不一樣, continue 執行后不是跳出循環,而是跳到循環的開始并執行下一次的循環。在上面的例子 中的循環體加入 if (I==5) continue;看看什么結果?
return 語句
return 語句是返回語句,不屬于循環語句,是要學習的最后一個語句所以一并寫下了。 返回語句是用于結束函數的執行,返回到調用函數時的位置。語法有二種:
return (表達式);
return; 語法中因帶有表達式,返回時先計算表達式,再返回表達式的值。不帶表達式則返回的
值不確定。
下面是一個同樣是計算 1-10 的累加,所不一樣是的用了函數的方式。
#include 《AT89X51.H》
#include 《stdio.h》
int Count(void); //聲明函數
void main(void)
{
unsigned int temp;
SCON = 0x50; //串行口方式 1,允許接收 TMOD = 0x20; //定時器 1 定時方式 2
TCON = 0x40; //設定時器 1 開始計數
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);
}
?
第十四課 C51函數
上一篇的最后一個例子中有用到函數,其實一直出現在例子中的 main()也算是一個函數,只不過它比較特殊,編譯時以它做為程序的開始段。有了函數 C 語言就有了模塊化的優 點,一般功能較多的程序,會在編寫程序時把每項單獨的功能分成數個子程序模塊,每個子 程序就能用函數來實現。函數還能被反復的調用,因此一些常用的函數能做成函數庫 以供在編寫程序時直接調用,從而更好的實現模塊化的設計,大大提高編程工作的效率。 一.函數定義
通常 C 語言的編譯器會自帶標準的函數庫,這些都是一些常用的函數,Keil uv 中也不 例外。標準函數已由編譯器軟件商編寫定義,使用者直接調用就能了,而無需定義。但是 標準的函數不足以滿足使用者的特殊要求,因此 C 語言允許使用者根據需要編寫特定功能的 函數,要調用它必須要先對其進行定義。定義的模式如下:
函數類型 函數名稱(形式參數表)
{
函數體
}
函數類型是說明所定義函數返回值的類型。返回值其實就是一個變量,只要按變量
類型來定義函數類型就行了。如函數不需要返回值函數類型能寫作“void”表示該函數沒 有返回值。注意的是函數體返回值的類型一定要和函數類型一致,不然會造成錯誤。函數名 稱的定義在遵循 C 語言變量命名規則的同時,不能在同一程序中定義同名的函數這將會造成 編譯錯誤(同一程序中是允許有同名變量的,因為變量有全局和局部變量之分)。形式參數 是指調用函數時要傳入到函數體內參與運算的變量,它能有一個、幾個或沒有,當不需要 形式參數也就是無參函數,括號內能為空或寫入“void”表示,但括號不能少。函數體中 能包含有局部變量的定義和程序語句,如函數要返回運算值則要使用 return 語句進行返 回。在函數的{}號中也能什么也不寫,這就成了空函數,在一個程序項目中能寫一些 空函數,在以后的修改和升級中能方便的在這些空函數中進行功能擴充。
二.函數的調用
函數定義好以后,要被其它函數調用了才能被執行。C 語言的函數是能相互調用的, 但在調用函數前,必須對函數的類型進行說明,就算是標準庫函數也不例外。標準庫函數的 說明會被按功能分別寫在不一樣的頭文件中,使用時只要在文件最前面用#include 預處理語 句引入相應的頭文件。如前面一直有使用的 printf 函數說明就是放在文件名為 stdio.h 的 頭文件中。調用就是指一個函數體中引用另一個已定義的函數來實現所需要的功能,這個時候函 數體稱為主調用函數,函數體中所引用的函數稱為被調用函數。一個函數體中能調用數個 其它的函數,這些被調用的函數同樣也能調用其它函數,也能嵌套調用。筆者本人認為 主函數只是相對于被調用函數而言。在 c51 語言中有一個函數是不能被其它函數所調用的, 它就是 main 主函數。調用函數的一般形式如下:
函數名 (實際參數表) “函數名”就是指被調用的函數。實際參數表能為零或多個參數,多個參數時要用逗
號隔開,每個參數的類型、位置應與函數定義時所的形式參數一一對應,它的作用就是把參 數傳到被調用函數中的形式參數,如果類型不對應就會產生一些錯誤。調用的函數是無參函 數時不寫參數,但不能省后面的括號。
在以前的一些例子我們也能看不一樣的調用方式:
1.函數語句
如 printf (“Hello World! ”); 這是在 我們的第一個程序中出現的,它以 “Hello
World! ”為參數調用 printf 這個庫函數。在這里函數調用被看作了一條語句。
2.函數參數 “函數參數”這種方式是指被調用函數的返回值當作另一個被調用函數的實際參
數,如 temp=StrToInt(CharB(16));CharB 的返回值作為 StrToInt 函數的實際參數傳遞。
3.函數表達式
而在上一篇的例子中有 temp = Count();這樣一句,這個時候函數的調用作為一個運算 對象出現在表達式中,能稱為函數表達式。例子中 Count()返回一個 int 類型的返回 值直接賦值給 temp。注意的是這種調用方式要求被調用的函數能返回一個同類型的值, 不然會出現不可預料的錯誤。
前面說到調用函數前要對被調用的函數進行說明。標準庫函數只要用#include 引入已 寫好說明的頭文件,在程序就能直接調用函數了。如調用的是自定義的函數則要用如下形 式編寫函數類型說明
類型標識符 函數的名稱(形式參數表); 這樣的說明方式是用在被調函數定義和主調函數是在同一文件中。你也能把這些寫到
文件名.h 的文件中用#include “文件名.h”引入。如果被調函數的定義和主調函數不是在同 一文件中的,則要用如下的方式進行說明,說明被調函數的定義在同一項目的不一樣文件之上, 其實庫函數的頭文件也是如此說明庫函數的,如果說明的函數也能稱為外部函數。
extern 類型標識符 函數的名稱(形式參數表); 函數的定義和說明是完全不一樣的,在編譯的角度上看函數的定義是把函數編譯存放在
ROM 的某一段地址上,而函數說明是告訴編譯器要在程序中使用那些函數并確定函數的地 址。如果在同一文件中被調函數的定義在主調函數之前,這個時候能不用說明函數類型。也就 是說在 main 函數之前定義的函數,在程序中就能不用寫函數類型說明了。能在一個函 數體調用另一個函數(嵌套調用),但不允許在一個函數定義中定義另一個函數。還要注意 的是函數定義和說明中的“類型、形參表、名稱”等都要相一致。
三.中斷函數 中斷服務函數是編寫單片機應用程序不可缺少的。中斷服務函數只有在中斷源請求響應
中斷時才會被執行,這在處理突發事件和實時控制是十分有效的。例如:電路中一個按鈕, 要求按鈕后 LED 點亮,這個按鈕何時會被按下是不可預知的,為了要捕獲這個按鈕的事件, 通常會有三種方法,一是用循環語句不斷的對按鈕進行查詢,二是用定時中斷在間隔時間內 掃描按鈕,三是用外部中斷服務函數對按鈕進行捕獲。在這個應用中只有單一的按鈕功能, 那么第一種方式就能勝任了,程序也很簡單,但是它會不停的在對按鈕進行查詢浪費了
CPU 的時間。實際應用中一般都會還有其它的功能要求同時實現,這個時候能根據需要選用第 二或第三種方式,第三種方式占用的 CPU 時間最少,只有在有按鈕事件發生時,中斷服務函 數才會被執行,其余的時間則是執行其它的任務。
如果你學習過匯編語言的話,剛開始寫匯編的中斷應用程序時,你一定會為出入堆棧的 問題而困擾過。單片機c語言 語言擴展了函數的定義使它能直接編寫中斷服務函數,你能不必考 慮出入堆棧的問題,從而提高了工作的效率。擴展的關鍵字是 interrupt,它是函數定義時 的一個選項,只要在一個函數定義后面加上這個選項,那么這個函數就變成了中斷服務函數。
在后面還能加上一個選項 using,這個選項是指定選用 51 芯片內部 4 組工作寄存器中的
那個組。開始學習者能不必去做工作寄存器設定,而由編譯器自動選擇,避免產生不必要的錯 誤。定義中斷服務函數時能用如下的形式。
函數類型 函數名 (形式參數) interrupt n [using n]
interrupt 關鍵字是不可缺少的,由它告訴編譯器該函數是中斷服務函數,并由后面的
n 指明所使用的中斷號。n 的取值范圍為 0-31,但具體的中斷號要取決于芯片的型號,像 AT89c51 實際上就使用 0-4 號中斷。每個中斷號都對應一個中斷向量,具體地址為 8n+3, 中斷源響應后處理器會跳轉到中斷向量所處的地址執行程序,編譯器會在這地址上產生一個 無條件跳轉語句,轉到中斷服務函數所在的地址執行程序。下表是 51 芯片的中斷向量和中 斷號。
表 9-1 AT89c51 芯片中斷號和中斷向量
使用中斷服務函數時應注意:中斷函數不能直接調用中斷函數;不能通過形參傳速參數; 在中斷函數中調用其它函數,兩者所使用的寄存器組應相同。限于篇幅其它與函數相關的知 識這里不能一一加以說明,如變量的傳遞、存儲,局部變量、全部變量等,有興趣的朋友可 以訪問筆者的網站 閱讀更多相關文章。
下面是簡單的例子。首先要在前面做好的實驗電路中加多一個按鈕,接在 P3.2(12 引腳外 部中斷 INT0)和地線之間。把編譯好后的程序燒錄到芯片后,當接在 P3.2 引腳的按鈕接下 時,中斷服務函數 Int0Demo 就會被執行,把 P3 當前的狀態反映到 P1,如按鈕接下后 P3.7
(之前有在這腳裝過一按鈕)為低,這個時候 P1.7 上的 LED 就會熄滅。放開 P3.2 上的按鈕后,
P1LED 狀態保持先前按下 P3.2 時 P3 的狀態。
#include 《at89x51.h》
unsigned char P3State(void); //函數的說明,中斷函數不用說明
void main(void)
{
IT0 = 0; //設外部中斷 0 為低電平觸發
EX0 = 1; //允許響應外部中斷 0
EA = 1; //總中斷開關
while(1);
}
//外部中斷 0 演示,使用 2 號寄存器組
void Int0Demo(void) interrupt 0 using 2
{
unsigned int Temp; //定義局部變量
P1 = ~P3State(); //調用函數取得 p2 的狀態反相后并賦給 P1
for (Temp=0; Temp《50; Temp++); //延時 這里只是演示局部變量的使用
}
//用于返回 P3 的狀態,演示函數的使用
unsigned char P3State(void)
{
unsigned char Temp;
Temp = P3; //讀取 P3 的引腳狀態并保存在變量 Temp 中
//這樣只有一句語句實在沒必要做成函數,這里只是學習函數的基本使用方法
return Temp;
}
?
評論