新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > 51系列單片機學習5—C編程程序語句

        51系列單片機學習5—C編程程序語句

        作者: 時間:2016-11-25 來源:網絡 收藏

        #include

        #include

        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

        #include

        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

        #include

        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

        #include

        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);

        }

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

        一.函數定義

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

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

        函數體

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

        二.函數的調用

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

        調用函數的一般形式如下:

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

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

        1.函數語句

        如 printf ("Hello World!"); 這是在 我們的第一個程序中出現的,它以 "HelloWorld!"為參數調用 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 芯片的中斷向量和中 斷號。

        中斷號

        中斷源

        中斷向量

        0

        外部中斷 0

        0003H

        1

        定時器/計數器 0

        000BH

        2

        外部中斷 1

        0013H

        3

        定時器/計數器 1

        001BH

        4

        串行口

        0023H

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

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

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

        P1LED 狀態保持先前按下 P3.2 時 P3 的狀態。

        #include

        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;

        }

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

        數據類型

        數組名

        [常量表達式];

        數據類型

        數組名

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

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

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

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

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

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

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

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

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

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

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

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

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

        unsigned char code skydata[]={0x02,0x34,0x22,0x32,0x21,0x12}; //數據保存在 code 區

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

        #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 少用很多循環

        if (taxis2[TempCycB+1]>taxis2[TempCycB]) //當后一個數大于前一個 數

        {

        Temp = taxis2[TempCycB]; //前后 2 數交換

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

        taxis2[TempCycB+1] = Temp; //因函數參數是數組名調用形參的變動影響實參

        }

        }

        }



        評論


        技術專區

        關閉
        主站蜘蛛池模板: 灵璧县| 陵川县| 恭城| 雷州市| 昭平县| 双鸭山市| 中阳县| 佛山市| 肥乡县| 曲沃县| 即墨市| 溆浦县| 石首市| 峨眉山市| 苗栗市| 古交市| 北宁市| 玉环县| 太和县| 陈巴尔虎旗| 苗栗市| 紫金县| 新余市| 千阳县| 常宁市| 英超| 丹巴县| 辽宁省| 札达县| 太湖县| 加查县| 克什克腾旗| 安岳县| 盖州市| 池州市| 吉安市| 双桥区| 台山市| 岳池县| 平南县| 铜川市|