




版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第3章
C51語言編程基礎
1單片機應用系統日趨復雜,對程序的可讀性、升級與維護以及模塊化要求越來越高,對軟件編程要求也越來越高,要求編程人員在短時間內編寫出執行效率高、運行可靠的程序代碼。同時,也要方便多個編程人員來進行協同開發。C51語言是近年來在8051單片機開發中,普遍使用的程序設計語言,能直接對8051單片機硬件進行操作,既有高級語言特點,又有匯編語言特點,因此在8051單片機程序設計中,得到廣泛使用。本章介紹8051單片機的C51語言,以及如何使用C51語言集成化開發平臺KeilμVision,進行C51程序設計與開發。23.1C51編程語言概述用于8051單片機編程的C語言,在標準C基礎上針對8051硬件特點進行擴展,并向8051上移植,經多年努力,C51已成為公認的高效、簡潔的8051單片機的實用高級編程語言。與8051匯編語言相比,C51語言在功能上、結構性、可讀性、可維護性上有明顯優勢,易學易用。3.1.1C51語言與8051匯編語言比較與8051匯編語言相比,C51有如下優點。(1)可讀性好。C51語言程序比匯編語言程序的可讀性好,編程效率高,程序便于修改、維護以及升級。(2)模塊化開發與資源共享。用C51開發的程序模塊可不經修改,直接被其他工程所用,使得開發者能夠很好地利用已有的大量標準C程序資源與豐富的庫函數,減少重復勞動,同時也有利于多個工程師進行協同開發。(3)可移植性好。為某種型號單片機開發的C語言程序,只需把與硬件相關的頭文件和編譯鏈接的參數進行適當修改,就可方便地移植到其他型號的單片機上。例如,為8051單片機編寫的程序通過改寫頭文件以及少量的程序行,就可方便地移植到PIC單片機上。(4)生成的代碼效率高。當前較好的C51語言編譯系統編譯出來的代碼效率只比直接使用匯編語言低20%左右,如果使用優化編譯選項,最高可達到90%左右。43.1.2C51語言與標準C語言的比較C51語言與標準C語言間有許多相同地方,但也有自身特點。不同的嵌入式C語言編譯系統之所以與標準C語言有不同的地方,主要是由于它們所針對的硬件系統不同。對于8051單片機,目前廣泛使用的是C51語言。C51語言基本語法與標準C相同,是在標準C的基礎上進行適合8051內核單片機硬件的擴展。深入理解C51語言對標準C語言的擴展部分以及它們的不同之處,是掌握C51語言的關鍵之一。5C51語言與標準C語言一些差別如下。(1)庫函數不同。標準C中不適合于嵌入式控制器系統的庫函數,被排除在C51語言之外,如字符屏幕和圖形函數。有些庫函數必須針對8051的硬件特點來做出相應的開發。
例如,在標準C中,庫函數printf和scanf,常用于屏幕打印和接收字符,而在C51語言中,主要用于串行口數據的收發。(2)數據類型有一定區別。在C51中增加幾種8051單片機的數據類型,在標準C的基礎上又擴展了4種類型。例如,8051單片機包含位操作空間和豐富的位操作指令,因此,C51語言與標準C語言相比增加了位操作類型。6(3)C51語言變量存儲模式與標準C語言中變量存儲模式數據不一樣。標準C最初是為通用計算機設計的,在通用計算機中只有一個程序和數據統一尋址的內存空間,而C51語言中變量的存儲模式與8051單片機的各種存儲器區緊密相關。(4)數據存儲類型不同。8051存儲區可分為內部數據存儲區、外部數據存儲區以及程序存儲區。
內部數據存儲區可分為3個不同的C51存儲類型:data、idata和bdata。
外部數據存儲區分為2個不同的C51存儲類型:xdata和pdata。
程序存儲區只能讀不能寫,可能在8051內部或者在外部,C51語言提供的code存儲類型用來訪問程序存儲區。7(5)標準C語言沒有處理單片機中斷的定義,而C51語言中有專門的中斷函數。 (6)頭文件不同。C51語言頭文件必須把8051單片機內部的外設硬件資源(如定時器、中斷、I/O等)相應的特殊功能寄存器寫入到頭文件內,而標準C不用。(7)程序結構的差異。由于8051單片機的硬件資源有限,它的編譯系統不允許太多的程序嵌套。8但從數據運算操作、程序控制語句以及函數的使用上來說,C51與標準C幾乎沒有什么明顯差別。如果程序設計者具備了有關標準C語言的編程基礎,只要注意C51與標準C不同之處,并熟悉8051單片機的硬件結構,就能較快掌握C51編程。3.2C51語言程序設計基礎本節在標準C基礎上,了解掌握C51的數據類型和存儲類型、C51的基本運算與流程控制語句,為C51的程序開發打下基礎。93.2.1C51語言中的數據類型與存儲類型1.數據類型數據是單片機操作的對象,數據的不同格式就稱為數據類型。KeilC51支持的基本數據類型見表3-1。針對8051的硬件特點,C51在標準C基礎上,擴展了4種數據類型(見表3-1中最后4行)。注意,擴展的4種數據類型,不能使用指針來對它們存取。102.C51的擴展數據類型下面對擴展的4種數據類型說明。(1)位變量bit的值可以是1(true),也可是0(false)。(2)特殊功能寄存器sfr。8051單片機的特殊功能寄存器分布在片內數據存儲區的地址單元80H~FFH之間,“sfr”數據類型占用一個內存單元。利用它可訪問8051單片機內部的所有特殊功能寄存器。
例如:sfrP1=0x90這一語句定義了P1端口在片內的寄存器,在程序后續的語句中可以用“P1=0xff”,使P1的所有引腳輸出為高電平的語句來操作特殊功能寄存器。12(3)特殊功能寄存器sfr16。
“sfr16”數據類型占用兩個內存單元,用于操作占兩個字節的特殊功能寄存器。例如:“sfr16DPTR=0x82”語句定義了片內16位數據指針寄存器DPTR,其低8位字節地址為82H,高8位字節地址為83H。在程序的后續語句中就可對DPTR進行操作。(4)特殊功能位sbit。
sbit是指AT89S51片內特殊功能寄存器的可尋址位。例如:
sfr PSW=0xd0; //定義PSW寄存器地址為0xd0 sbit OV=PSW^2; //定義OV位為PSW.2符號“^”前是特殊功能寄存器名字,“^”后的數字定義特殊功能寄存器可尋址位在寄存器中的位置,取值必須是0~7。注意,不要把bit與sbit相混淆。bit定義普通的位變量,只能是二進制的0或1。sbit是定義特殊功能寄存器的可尋址位,值是可以進行位尋址的特殊功能寄存器的某位絕對地址,例如,PSW寄存器OV位的絕對地址0xd2。上面的例子還涉及到C51注釋的寫法問題,C51的注釋寫法有兩種:(1)//……,兩個斜杠后面跟著的為注釋語句,本寫法只能注釋一行,當換行時,必須在新行上重新寫兩個斜杠。(2)/*……*/,一個斜杠與星號結合使用,本寫法可注釋任一行,即斜杠星號與星號斜杠之間的所有文字都作為注釋,即注釋有多行時,只需在注釋的開始處,加斜杠星號,在注釋的結尾處,加上星號斜杠即可。加注釋的目的是為了便于讀懂程序,所有注釋都不參與程序編譯,編譯器在編譯過程中會自動刪去注釋。143.數據存儲類型在討論C51數據類型時,須同時提及它的存儲類型,以及它與8051單片機存儲器結構的關系,因為C51定義的任何數據類型必須以一定的方式,定位在8051單片機的某一存儲區中,否則沒有任何實際意義。8051有片內、片外數據存儲區,還有程序存儲區。片內的數據存儲區是可讀寫的,8051的衍生系列最多可有256字節的內部數據存儲區(例如AT89S52單片機),其中低128字節可直接尋址,高128字節(80H~FFH)只能間接尋址,從地址20H開始的16字節可位尋址。內部數據存儲區可分為3個不同的數據存儲類型:data、idata和bdata。
訪問片外數據存儲區比訪問片內數據存儲區慢,因為訪問片外數據存儲區要通過對數據指針加載地址來間接尋址訪問。
C51提供兩種不同的數據存儲類型xdata和pdata來訪問片外數據存儲區。程序存儲區只能讀不能寫,可能在8051單片機內部或者外部,或外部和內部都有,由8051單片機硬件決定,C51提供了code存儲類型來訪問程序存儲區。C51存儲類型與8051實際的存儲空間的對應關系見表3-2。下面對表3-2各種存儲區作以說明。(1)DATA區。尋址是最快的,應把常使用的變量放在該區,但該區存儲空間有限,DATA區除了包含程序變量外,還包含了堆棧和寄存器組。DATA區聲明中的存儲類型標識符為data,通常指片內RAM128字節的內部數據存儲的變量,可直接尋址。1617聲明舉例:
unsignedchardatasystem_status=0; unsignedintdataunit_id[8]; chardatainp_string[20];
標準變量和用戶自聲明變量都可存儲在DATA區中,只要不超過DATA區的范圍即可,由于C51用默認的寄存器組來傳遞參數,這樣DATA區至少失去8字節空間。另外,當內部堆棧溢出的時候,程序會莫名其妙地復位。這是因為8051沒有報錯機制,堆棧溢出只能以這種方式表示,因此要留有較大的堆棧空間來防止堆棧溢出。(2)BDATA區。DATA中的位尋址區,在該區中聲明變量就可進行位尋址。BDATA區聲明中的存儲類型標識符為bdata,指的是片內RAM可位尋址的1618字節存儲區(字節地址為20H~2FH)中的128個位。下面是在BDATA區中聲明的位變量和使用位變量的例子:unsignedcharbdatastatus_byte;unsignedintbdatastatus_word;sbitstat_flag=status_byte^4;if(status_word^15){……}stat_flag=1;C51編譯器不允許在BDATA區中聲明float和double型變量。
(3)IDATA區。該區使用寄存器作為指針來對片內RAM進行間接尋址,常用來存放使用比較頻繁的變量。與外部存儲器尋址相比,它的指令執行周期和代碼長度相對較短。
IDATA區聲明中的存儲類型標識符為idata,指的是片內RAM的256字節的存儲區,只能間接尋址,速度比直接尋址慢。19聲明舉例如下:unsignedcharidatasystem_status=0;unsignedintidataunit_id[8];charidatainp_string[16];floatidataout_value;(4)PDATA區和XDATA區位于片外存儲區,PDATA區和XDATA區聲明中的存儲類型標識符分別為pdata和xdata。
PDATA區只有256字節,僅指定256字節的外部數據存儲區。但XDATA區最多可達64KB,對應的xdata存儲類型標識符可指定外部數據區64KB內的任何地址。20
對PDATA區的尋址要比對XDATA區尋址快,因為對PDATA區尋址,只需裝入8位地址,而對XDATA區尋址要裝入16位地址,所以盡量把外部數據存儲在PDATA區中。對PDATA區和XDATA區的聲明舉例如下:unsignedcharxdatasystem_status=0;unsignedintpdataunit_id[8];charxdatainp_string[16];floatpdataout_value;由于外部數據存儲器與外部I/O口是統一編址的,外部數據存儲器地址段中除了包含數據存儲器地址外,還包含外部I/O口的地址。對外部數據存儲器及外部I/O口的尋址將在本章的絕對地址尋址中介紹。21(5)程序存儲區CODE。程序存儲區CODE聲明的標識符為code,儲存的數據是不可改變的。在C51編譯器中可以用存儲區類型標識符code來訪問程序存儲區。聲明舉例如下:unsignedcharcodea[]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};上面介紹了C51的數據存儲類型,其大小和值域見表3-3。單片機訪問片內RAM比訪問片外RAM相對快一些,所以應盡量把頻繁使用的變量置于片內RAM。即采用data、bdata或idata存儲類型,而將容量較大或使用不太頻繁的那些變量置于片外RAM,即采用pdata或xdata存儲類型。常量只能采用code存儲類型。2223變量存儲類型定義舉例:(1)chardataa1; /*字符變量a1被定義為data型,分配在 片內RAM低128字節中*/(2)floatidatax,y; /*浮點變量x和y被定義為idata型,定位在
片內RAM中,只能用間接尋址方式尋址*/
(3)bitbdatap; /*位變量p被定義為bdata型,定位在片內
RAM中的位尋址區*/
(4)unsignedintpdatavar1;/*無符號整型變量var1定義為pdata型,定位在片外RAM中,相當于@Ri間接尋址*/
(5)unsignedcharxdataa[2][4];/*無符號字符型二維數組變量a[2][4]被定義為xdata存儲類型,定位在片外RAM中,占據2×4=8字節,相當于@DPTR間接尋址*/
4.數據存儲模式如果在變量定義時略去存儲類型標識符,編譯器會自動默認存儲類型。24進一步由SMALL、COMPACT和LARGE存儲模式指令限制。例如,若聲明charvar1,則在使用SMALL存儲模式下,var1被定位在data存儲區,在使用COMPACT模式下,var1被定位在idata存儲區;在LARGE模式下,var1被定位在xdata存儲區中。下面對存儲模式作進一步說明。(1)SMALL模式。該模式下,所有變量都默認位于8051單片機內部的數據存儲器,與使用data指定存儲器類型的方式一樣。在此模式下,變量訪問的效率高,但是所有數據對象和堆棧必須使用內部RAM。(2)COMPACT模式
本模式下所有變量都默認在外部數據存儲器的1頁(256字節)內,這與25使用pdata指定存儲器類型是一樣的。該類型適用于變量不超過256字節的情況,此限制是由尋址方式決定的,相當于使用數據指針@Ri尋址。與SMALL模式相比,該存儲模式的效率比較低,對變量訪問的速度也慢一些,但比LARGE模式快。(3)LARGE模式
本模式下所有變量都默認位于外部數據存儲器,相當于用@DPTR尋址。通過數據指針訪問外部數據存儲器的效率較低,特別是當變量為2字節或更多字節時,該模式要比SMALL和COMPACT產生更多的代碼。263.2.2C51語言的特殊功能寄存器及位變量定義介紹C51如何對特殊功能寄存器及位變量進行定義并訪問。1.特殊功能寄存器的C51定義C51語言允許通過使用關鍵字sfr、sbit或直接引用編譯器提供的頭文件來對特殊功能寄存器(SFR)進行訪問,特殊功能寄存器分布在片內RAM高128字節中,只能采用直接尋址方式。(1)使用關鍵字定義sfr。為能直接訪問特殊功能寄存器SFR,C51提供了一種定義方法,即引入關鍵字sfr,語法如下:
sfr
特殊功能寄存器名字=特殊功能寄存器地址;例如:sfrIE=0xA8; //中斷允許寄存器IE地址A8HsfrTCON=0x88; //定時器/計數器控制寄存器地址88HsfrSCON=0x98; //串行口控制寄存器地址98H在8051中,要訪問16位SFR,要用關鍵字sfr16。16位SFR的低字節地址須作為“sfr16”的定義地址,例如:28
sfr16DPTR=0x82 //DPTR的低8位地址為82H,高8位地址為83H(2)通過頭文件訪問SFR。各種衍生型的8051單片機的特殊功能寄存器的數量與類型有時是不相同的,對其訪問可通過頭文件訪問來進行。為用戶處理方便,C51把8051(或8052單片機)常用的特殊功能寄存器和其中的可尋址位進行了定義,放在一個reg51.h(或reg52.h)的頭文件中。當用戶要使用時,只需在使用之前用一條預處理命令#include<reg51.h>把這個頭文件包含到程序中,就可使用特殊功能寄存器名和其中的可尋址位名稱了。用戶可對頭文件進行增減。29頭文件引用舉例如下:#include<reg51.h> //包含8051單片機的頭文件voidmain(void){ TL0=0xf0; //給T0低字節TL0設置時間常數,已在reg51.h中定義
TH0=0x3f; //給T0高字節TH0設置時間常數,已在reg51.h中定義
TR0=1; //啟動定時器0 ……}(3)特殊功能寄存器中的位定義。對SFR中的可尋址位的訪問,要使用關鍵字來定義可尋址位,共3種方法。①sbit位名=特殊功能寄存器^位置;例如:sfrPSW=0xd0; //定義PSW寄存器的字節地址0xd0sbitCY=PSW^7; //定義CY位為PSW.7,地址為0xd0sbitOV=PSW^2; //定義OV位為PSW.2,地址為0xd2②sbit位名=字節地址^位置;
例如:sbitCY=0xd0^7; //CY位地址為0xd7sbitOV=0xd0^2; //OV位地址為0xd2③sbit位名=位地址;將位的絕對地址賦給變量,位地址必須在0x80~0xff。例如: sbitCY=0xd7; //CY位地址為0xd7sbitOV=0xd2; //OV位地址為0xd231【例】AT89S51單片機片內P1口的各尋址位的定義如下:sfrP1=0x90;sbitP1_7=P1^7;sbitP1_6=P1^6;sbitP1_5=P1^5;sbitP1_4=P1^4;sbitP1_3=P1^3;sbitP1_2=P1^2;sbitP1_1=P1^1;sbitP1_0=P1^0;
2.位變量的C51定義(1)由于8051可位操作,C51擴展的“bit”數據類型用來定義位變量,這是與標準C的不同之處。32C51采用關鍵字“bit”來定義位變量,一般格式為:bitbit_name;例如:
bitov_flag; //將ov_flag定義為位變量bitlock_pointer; //將lock_pointer定義為位變量(2)函數可以包含類型為bit的參數,也可將其作為返回值。C51程序函數可以包含類型為“bit”的參數,也可將其作為返回值。例如:bitfunc(bitb0,bitb1); //位變量b0與b1作為函數func的參數{
……
return(b1); //位變量b1作為return函數的返回值}33(3)位變量定義的限制。位變量不能用來定義指針和數組。例如:
bit*ptr;//錯誤,不能用位變量來定義指針
bitarray[]; //錯誤,不能用位變量來定義數組array[]
定義位變量時,允許定義存儲類型,位變量都被放入一個位段,此段總是位于8051的片內RAM中,因此其存儲類型限制為DATA或IDATA,如果將位變量定義成其他類型,將會導致編譯時出錯。343.2.3C51語言的絕對地址訪問如何對8051片內RAM、片外RAM及I/O空間進行訪問,C51提供兩種常用的訪問絕對地址的方法。1.絕對宏編譯器提供了一組宏定義對code、data、pdata和xdata空間進行絕對尋址。程序中用“#include<absacc.h>”來對absacc.h中聲明的宏來訪問絕對地址,包括CBYTE、CWORD、DBYTE、DWORD、XBYTE、XWORD、PBYTE、PWORD,具體使用參見absacc.h頭文件。其中:35CBYTE以字節形式對code區尋址;CWORD以字形式對code區尋址;DBYTE以字節形式對data區尋址;DWORD以字形式對data區尋址;XBYTE以字節形式對xdata區尋址;XWORD以字形式對xdata區尋址;PBYTE以字節形式對pdata區尋址;PWORD以字形式對pdata區尋址。例如:#include<absacc.h>#definePORTAXBYTE[0xffc0] //將PORTA定義為外部I/O口,地址為0xffc0
,長度8位#defineNRAMDBYTE[0x50] //將NRAM定義為內部RAM,地址為0x50,
長度8位【例3-2】片內RAM、片外RAM及I/O定義的程序如下:
#include<absacc.h>#definePORTAXBYTE[0xFFC0]//將PORTA定義為外部I/O口,地址為0xFFC0,長度8位#defineNRAMDBYTE[0x50] //將NRAM定義為片內RAM,地址為0x50,長度8位main(){ PORTA=0x3d;//將數據3DH寫入地址為0xffc0的外部I/O端口PORTA中
NRAM=0x01;//將數據01H寫入片內RAM的0x40單元}372._at_關鍵字關鍵字_at_可對指定的存儲器空間的絕對地址訪問,格式如下:[存儲器類型]數據類型說明符變量名_at_地址常數其中,存儲器類型為C51能識別的數據類型;數據類型為C51支持的數據類型;地址常數用于指定變量的絕對地址,必須位于有效的存儲器空間之內;使用_at_定義的變量必須為全局變量。38【例3-3】使用關鍵字_at_實現絕對地址的訪問,程序如下:voidmain(void){ dataunsignedchary1
_at_0x50;//在data區定義字節變量y1,地址為50Hxdataunsignedinty2
_at_0x4000;//在xdata區定義字變量y2,地址為//4000H
y1=0xff;
y2=0x1234;
……
while(1);}【例3-4】將片外RAM2000H開始的連續20字節清0,程序如下:39xdataunsignedcharbuffer[20]_at_0x2000;voidmain(void){ unsignedchari;
for(i=0;i<20;i++)
{ buffer[i]=0
}}如把片內RAM40H單元開始的8個單元內容清0,程序如下:xdataunsignedcharbuffer[8]_at_0x40;voidmain(void){40unsignedcharj; for(j=0;j<8;j++) {
buffer[j]=0
}}3.2.4C51的基本運算與標準C類似,主要包括算術運算、關系運算、邏輯運算、位運算和賦值運算及其表達式等。1.算術運算符算術運算符及說明見表3-4。4142C51中表示加1和減1時可以采用自增運算符和自減運算符,自增和自減運算符是使變量自動加1或減1,自增和自減運算符放在變量前和變量之后是不同的,見表3-5。432.邏輯運算符邏輯運算的結果只有“真”和“假”兩種,“1”表示真,“0”表示假。表3-6列出了邏輯運算符及其說明。例如條件“10>20”為假,“2<6”
為真,則邏輯與運算為:
(10>20)&&(2<6)=0&&1=0。3.關系運算符關系運算符是判斷兩個數之間的關系。說明如表3-7所示。44454.位運算位運算符及其說明見表3-8。
46在實際應用中,常想改變I/O口中某一位的值,而不影響其他位,如果I/O口可位尋址的,這個問題就很簡單。但有時外擴的I/O口只能進行字節操作,要想實現單獨位控,就要采用位操作?!纠?/p>
編程將擴展的某I/O
口
PORTA(只能字節操作)的PORTA.5清
0,PORTA.1置1,程序如下:#define<absacc.h> //定義片外
I/O
口變量PORTA要用該頭文件#definePORTAXBYTE[0xffc0]//定義一個片外
I/O
口變量PORTAvoidmain(){
……
PORTA=(PORTA&0xdf)│0x02;
……}47程序中,第2行定義一個片外
I/O
口變量PORTA,地址為片外數據存儲區的0xffc0。在main()函數中,“PORTA=(PORTA&0xdf)│0x02”的作用是先用運算符“&”將PORTA.5置成0,然后再用“│0x02”運算將PORTA.1置為1。5.指針和取地址運算符指針是C51語言中一個十分重要的概念,指針變量用于存儲某個變量的地址,C51用“*”和“&”運算符來提取變量內容和變量地址,見表3-9。48提取變量的內容和變量的地址的一般形式分別為:目標變量=*指針變量//將指針變量所指的存儲單元內容賦值給目標變量指針變量=&目標變量 //將目標變量的地址賦值給指針變量例如:a=&b; //取b變量的地址送至變量ac=*b; //把以指針變量b為地址的單元內容送至變量c指針變量中只能存放地址(即指針型數據),不能將非指針類型的數據賦值給指針變量。例如:inti; //定義整型變量iint*b; //定義指向整數的指針變量bb=&i; //將變量i的地址賦給指針變量bb=i;//錯,指針變量b只能存放變量指針(變量地址),不能存放變量i的值493.2.5C51的分支與循環程序結構C51程序按結構可分為3類,即順序、分支和循環結構。順序結構是基本結構,程序自上而下,從main(
)的函數開始一直到程序結束,只有一條路可走,無其他路徑可選,結構較簡單和便于理解,這里僅介紹分支結構和循環結構。1.分支控制語句分支控制語句有:if語句和switch語句。(1)if語句用來判定所給定的條件是否滿足,根據判定結果決定執行兩種操作之一。if語句的基本結構如下:if(表達式){語句}
50括號中的表達式成立時,程序執行大括號內的語句,否則程序跳過大括號中的語句部分,而直接執行下面的其他語句。C51提供3種形式的if語句:形式1
if(表達式){語句}例如:
if(x>y){max=x;min=y;}即如果x>y,則x賦給max,y賦給min。如果x>y不成立,則不執行大括號中的賦值運算。形式2if(表達式){語句1;}else{語句2;}例如:if(x>y){max=x;}else{max=y;}
本形式相當于雙分支選擇結構。形式3if(表達式1){語句1;}elseif(表達式2){語句2;}elseif(表達式3){語句3;}……else{語句n;}52例如:if(x>100){y=1;}elseif(x>50){y=2;}elseif(x>30){y=3;}elseif(x>20){y=4;}else{y=5;}本形式相當于串行多分支選擇結構。在if語句中又含有一個或多個if語句,這稱為if語句的嵌套。應當注意if與else的對應關系,else總是與它前面最近的一個if語句相對應。53(2)switch語句。if語句只有兩個分支可選擇,而switch語句是多分支選擇語句。switch語句的一般形式如下:switch(表達式1){ case常量表達式1:{語句1;}break;
case常量表達式2:{語句2;}break; …… case常量表達式n:{語句n;}break; default:{語句n+1;}}上述switch語句說明如下。54(1)每一case常量表達式須互不相同,否則將混亂。(2)各個case和default出現次序,不影響程序執行的結果。(3)switch括號內表達式的值與某case后面的常量表達式的值相同時,就執行它后面的語句,遇到break語句則退出switch語句。若所有的case中的常量表達式的值都沒有與switch語句表達式的值相匹配時,就執行default后面的語句。(4)如果在case語句中遺忘了break語句,則程序執行了本行之后,不會按規定退出switch語句,而是將執行后續的case語句。在執行1個case分支后,使流程跳出switch結構,即中止switch語句的執行,可以用1條break語句完成。55switch語句的最后一個分支可以不加break語句,結束后直接退出switch結構。【例3-6】在單片機程序設計中,常用switch語句作為鍵盤中按鍵按下的判別,并根據按下鍵的鍵號跳向各自的分支處理程序。input:keynum=keyscan()switch(keynum){ case1: key1();break; //如果按下鍵為1鍵,則執行函數key1() case2: key2();break; //如果按下鍵為2鍵,則執行函數key2() case3: key3();break; //如果按下鍵為3鍵,則執行函數key3() case4: key4();break; //如果按下鍵為4鍵,則執行函數key4()
……
default:gotoinput}例子中的keyscan()是另行編寫的一個鍵盤掃描函數,如有鍵按下,該函數就會得到按下鍵的鍵值,將鍵值賦予變量keynum。如果鍵值為2,則執行鍵值處理函數key2()后返回;如果鍵值為4,則執行key4()函數后返回。57執行完1個鍵值處理函數后,則跳出switch語句,從而達到按下不同的按鍵來進行不同的鍵值處理的目的。2.循環控制語句許多實用程序都包含循環結構,熟練掌握和運用循環結構的程序設計是C51語言程序設計的基本要求。實現循環結構的語句有以下3種:while語句、do-while語句和for語句。(1)while語句。語法形式為:58while(表達式)
{
循環體語句;
}表達式是while循環能否繼續的條件,如果表達式為真,就重復執行循環體語句;反之,則終止循環體內的語句。while循環結構特點:循環條件測試在循環體開頭,要想執行重復操作,首先必須進行循環條件的測試,如條件不成立,則循環體內的重復操作一次也不能執行。 例如:while((P1&0x80)==0){}while中的條件語句對AT89S8051單片機的P1口的P1.7位進行測試,如果P1.7為低(0),則由于循環體無實際操作語句,故繼續測試下去(等待),一旦P1.7的電平變高(1),則循環終止。(2)do-while語句。語法形式為:do{ 循環體語句;}while(表達式);
do-while語句特點是先執行內嵌的循環體語句,再計算表達式,如表達式的值為非0,則繼續執行循環體語句,直到表達式的值為0時結束循環。由do-while構成的循環與while循環的重要區別是:while循環的控制出現在循環體之前,只有當while后面表達式的值非0時,才可能執行循環體;在do-while構成的循環中,總是先執行一次循環體,然后再求表達式的值,因此無論表達式的值是0還是非0,循環體至少要被執行一次。
在do-while循環體中,要有能使while后表達式的值變為0的操作,否則,循環會無限制地進行下去。根據經驗,do-while循環用的并不多,大多數的循環用while來實現會直觀。【例3-7】實型數組sample存有10個采樣值,編寫程序段,要求返回其平均值(平均值濾波)。程序如下:floatavg(float*sample){ floatsum=0; charn=0;
do { sum+=sample[n];
n++;
}while(n<10);return(sum/10);}(3)基于for語句的循環。3種循環常用的是for循環。不僅可用于循環次數已知的情況,也可用于循環次數不確定而只給出循環條件情況,完全可替代while語句。for循環的一般格式為:for(表達式1;表達式2;表達式3){
循環體語句;}for是關鍵字,括號中常含有3個表達式,各表達式間用“;”隔開。這3個表達式可以是任意形式的表達式,通常主要用于for循環控制。緊跟在for()之后的循環體,在語法上要求是
1
條語句;若在循環體內需要多條語句,應用大括號括起來組成復合語句。for執行過程如下:①計算“表達式1”,表達式1通常稱為“初值設定表達式”。②計算“表達式2”,表達式2通常稱為“終值條件表達式”,若滿足條件,轉下一步,若不滿足條件,則轉步驟⑤。③執行1次for循環體。④計算“表達式3”,“表達式3”通常稱為“更新表達式”轉向步驟②。⑤結束循環,執行for循環之后的語句。下面對for語句的幾個特例進行說明。①for語句中的小括號內的3個表達式全部為空。 例如:for(;;){
循環體語句;}在小括號內只有兩分號,無表達式,這意味著沒有設初值,無判斷條件,循環變量為增值,它的作用相當于while(1),這將導致一個無限循環。一般在編程時,需要無限循環時,可采用這種形式的for循環語句。②for語句的3個表達式中,表達式1缺省。例如:for(;i<=100;i++)sum=sum+i;
即不對i設初值。③for語句的3個表達式中,表達式2缺省。 例如:for(i=1;;i++)sum=sum+i;即不判斷循環條件,認為表達式始終為真,循環將無休止地進行下去。④for語句的3個表達式中,表達式1、表達式3省略。例如:for(;i<=100;){ sum=sum+i; i++;}⑤沒有循環體的for語句。 例如:inta=1000;for(t=0;t<a;t++){;}本例典型應用就是軟件延時??捎醚h結構來實現,即循環執行指令,消磨一段已知的時間。指令的執行時間是靠一定數量的時鐘周期來計時的,如果使用12MHz晶振,則12個時鐘周期花費的時間為1μs。【例3-8】編寫一個延時1ms程序。voiddelayms(unsignedcharintj){ unsignedchari;
while(j--)
{ for(i=0;i<125;i++)
{;}
}}如把上述程序段編譯成匯編代碼分析,用for的內部循環大約延時8μs,但不是特別精確。不同編譯器會產生不同延時,因此i的上限值125應根據實際情況進行補償調整?!纠?-9】求1+2+3…+100的累加和。 用for語句編寫的程序如下:#include<reg51.h>#include<stdio.h>main(){ intnvar1,nsum;
for(nvar1=0,nsum=1;nsum<=100;nsum++)
nVar1+=ncount; //累加求和
while(1);}【例-10】無限循環的結構實現。 編寫無限循環程序段,可用以下3種結構。①使用while(1)的結構:while(1){
代碼段;}
②使用for(;;)的結構:for(;;){
代碼段;}
③使用do-while(1)的結構:do{
代碼段;}while(1);
3.break語句、continue語句和goto語句在循環體執行中,如滿足循環判定條件的情況下跳出代碼段,可使用
break語句或continue語句;如要從任意地方跳轉到代碼某地方,可使用goto語句。(1)break語句循環結構中,可使用break語句跳出本層循環體,馬上結束本層循環?!纠?-11】執行如下程序段。voidmain(void) { inti,sum; sum=0; for(i=1;i<=10;i++) { sum=sum+i; if(sum>5)break;print(“sum=%d\n”,sum);/*通過串口向計算機屏幕輸出顯示sum值*/}}本例如沒有break語句,程序將進行10次循環;當i=3時,sum的值為6,此時,if語句的表達式“sum>5”的值為1,于是執行break語句,跳出for循環,從而提前終止循環。因此在一個循環程序中,既可通過循環語句中的表達式來控制循環是否結束,還可直接通過break語句強行退出循環結構。(2)continue語句作用及用法與break語句類似,區別:當前循環遇到break,是直接結束循環,若遇上continue,則是停止當前這一層循環,然后直接嘗試下一層循環。可見,continue并不結束整個循環,而僅僅是中斷這一層循環,然后跳到循環條件處,繼續下一層的循環。當然,如果跳到循環條件處,發現條件已不成立,那么循環也會結束?!纠?-12】輸出整數1~100的累加值,但要求跳過所有個位為3的數。為完成題目要求,在循環中加一個判斷,如果該數各位是3,就跳過該數不加。如何來判斷1~100的數中那些數個位是3呢?用求余數的運算符“%”,將一個兩位以內的正整數,除以10后,余數是3,就說明這個數的個位為3。例如對于數73,除以10后,余數是3。根據以上分析,參考程序如下:voidmain(void) { inti,sum=0; sum=0; for(i=1;i<=100;i++) { if(i%10==3)
continue; sum=sum+i; }print(“sum=%d\n”,sum);/*在計算機屏幕顯示sum值,了解本語句的功能即可*/}(3)goto語句無條件轉移語句,當執行goto語句時,將程序指針跳轉到goto給出的下一條代碼?;靖袷饺缦拢篻oto 標號【例3-13】計算整數1~100的累加值,存放到sum中。voidmain(void) { unsignedchariintsum; sumadd:sum=sum+i;i++;if(i<101) { gotosumadd; }}goto語句在C51中經常用于無條件跳轉某條必須執行的語句以及在死循環程序中退出循環。為方便閱讀,也為了避免跳轉時引發錯誤,在程序設計中要慎重使用goto語句。3.2.6C51的數組在C51程序設計中,數組使用的較為廣泛。1.數組簡介數組是同類數據的一個有序結合,用數組名來標識。整型變量的有序結合稱為整型數組,字符型變量的有序結合稱為字符型數組。數組中的數據,稱為數組元素。數組中各元素的順序用下標表示,下標為n的元素可以表示為數組名[n]。改變[]中的下標就可以訪問數組中的所有的元素。數組有一維、二維、三維和多維數組之分。C51語言中常用的一維、二維數組和字符數組。(1)一維數組具有一個下標的數組元素組成的數組成為一維數組,一維數組形式如下: 類型說明符數組名[元素個數];其中,數組名是一個標識符,元素個數是一個常量表達式,不能是含有變量的表達式:例如:
intarray1[8]
定義名為array1的數組,包含8個整型元素,在定義數組時,可對數組進行整體初始化,若定義后對數組賦值,則只能對每個元素分別賦值。例如:
inta[3]={2,4,6};/*給全部元素賦值,a[0]=2,a[1]=4,a[2]=6*/intb[4]={5,4,3,2};/*給全部元素賦值,b[0]=5,b[1]=4,b[2]=3,b[3]=2*/(2)二維數組或多維數組具有兩個或兩個以上下標的數組,稱為二維數組或多維數組。定義二維數組的一般形式如下:類型說明符數組名[行數][列數];其中,數組名是一個標識符,行數和列數都是常量表達式。例如:floatarray2[4][3]/*array2數組,4行3列共12個浮點型元素*/二維數組可以在定義時進行整體初始化,也可在定義后單個地進行賦值。例如:inta[3][4]={1,2,3,4},{5,6,7,8},{9,10,11,12};/*a數組全部初始化*/intb[3][4]={1,3,5,7},{2,4,6,8},{};/*b數組部分初始化,未初始化的元素為0*/(3)字符數組 若一個數組的元素是字符型的,則該數組就是一個字符數組。例如:chara[10]={‘B’,‘E’,‘I’,‘’,‘J’,‘I’,‘N’,‘G’,‘\0’};/*字符串數組*/定義了一個字符型數組a[],有10個數組元素,并且將9個字符(其中包括一個字符串結束標志‘\0’
)分別賦給了a[0]~a[8],剩余的a[9]被系統自動賦予空格字符。C51還允許用字符串直接給字符數組置初值,例如:chara[10]={“BEIJING”};用雙引號括起來的一串字符,成為字符串常量,C51編譯器會自動地在字符串末尾加上結束符‘\0’。用單引號括起來的字符為字符的ASCII碼值,而不是字符串。例如‘a’表示a的ASCII碼值61H,而“a”表示一個字符串,由兩個字符a和\0組成。一個字符串可以用一維數組來裝入,但數組的元素數目一定要比字符多一個,以便C51編譯器自動在其后面加入結束符‘\0’。2.數組的應用在C51的編程中,數組一個非常有用的功能是查表。例如數學運算,編程者更愿意采用查表計算而不是公式計算。例如,對于傳感器的非線性轉換需要進行補償,使用查表法就要有效的多。再如,LED顯示程序中根據要顯示的數值,找到對應的顯示段碼送到LED顯示器顯示。表可以事先計算好后裝入程序存儲器中?!纠?-14】使用查表法,計算數0~9的平方。#defineucharunsignedcharucharcodesquare[0,1,4,9,16,25,36,49,64,81];/*0~9的平方表*/ucharfuction(ucharnumber){ returnsquare[number]};/*返回要求得其平方的數*/main() { result=fuction(7);/*函數fuction()的返回值為7,其平方49存入result單元*/}程序開始,“ucharcodesquare[0,1,4,9,16,25,36,49,64,81];”定義了一個無符號字符型的數組square[],并對其進行了初始化,將數0~9的平方值賦予了數組square[],數據類型代碼code指定編譯器將平方表定位在程序存儲器中。主函數調用函數fuction(),假設得到返回值number=7;從square數組中查表獲得相應的求得其平方的數為49。執行result=fuction(7)后,result的結果為相應的平方數493.數組與存儲空間當程序中設定了一個數組時,C51編譯器就會在系統的存儲空間中開辟一個區域,用于存放數組的內容。數組就包含在這個由連續存儲單元組成的模塊的存儲體內。對字符數組而言,占據了內存中一連串的字節位置。對整型(int)數組而言,將在存儲區中占據一連串連續的字節對的位置。對長整型(long)數組或浮點型(float)數組,一個數組成員將占有4字節的存儲空間。當一維數組被創建時,C51編譯器就會根據數組的類型在內存中開辟一塊大小等于數組長度乘以數據類型長度(即類型占有的字節數)的區域。對于二維數組a[m][n]而言,其存儲順序是按行存儲,先存第0行元素的第0列、第1列、第2列,直至第n-1列,然后返回到存第1行元素的第0列、第1列、第2列,直至第n-1列,……,如此順序存儲,直到第m-1行的第n-1列。當數組特別是多維數組中大多數元素沒有被有效利用地利用時,就會浪費大量的存儲空間。對于51單片機,不擁有大量的存儲區,其存儲資源極為有限,因此在進行C51語言編程開發時,要仔細地根據需要來選擇數組的大小。3.2.7C51語言的指針C51支持基于存儲器的指針和一般指針兩種指針類型。當定義一個指針變量時,若未給出它所指向的對象的存儲類型,則被認為是一般指針,反之若給出了它所指向對象的存儲類型,則被認為是基于存儲器的指針?;诖鎯ζ鞯闹羔橆愋陀蒀51語言源代碼中存儲類型決定,用這種指針可以高效訪問對象,且只需1~2字節。
一般指針占用3字節:1個字節為存儲器類型,2個字節為偏移量。存儲器類型決定了對象所用的8051的存儲空間,偏移量指向實際地址。一個一般指針可以訪問任何變量而不管它在8051存儲器的位置。1.通用指針C51提供一個3字節的通用指針,通用指針聲明和使用與標準C語言完全一樣。通用指針的形式如下:數據類型*指針變量;
例如:
uchar*pz例中pz就是通用指針,用3字節來存儲指針,第一字節表示存儲器類型,第二、三字節分別是指針所指向數據地址的高字節和低字節,這種定義很方便但速度較慢,在所指向的目標存儲器空間不明確時普遍使用。2.存儲器指針存儲器指針在定義時指明了存儲器類型,并且指針總是指向特定的存儲器空間(片內數據RAM、片外數據RAM或程序ROM)。例如:charxdata*str;//str指向xdata區中的char型數據intxdata*pd; //pd指向外部RAM區中的int型整數由于定義中已經指明了存儲器類型,因此,相對于通用指針而言,指針第一個字節省略,對于data、bdata、idata與pdata存儲器類型,指針僅需要1B,因為它們的尋址空間都在256B以內,而code和xdata存儲器類型則需要2B指針,因為它們的尋址空間最大為64KB。
使用存儲器指針好處是節省了存儲空間,編譯器不用為存儲器選擇和決定正確的存儲器操作指令來產生代碼,使代碼更加簡短,但必須保證指針不指向所聲明的存儲區以外的地方,否則會產生錯誤。通用指針產生的代碼執行速度比指定存儲區的指針要慢,因為存儲區在運行前是未知的,編譯器不能優化存儲區訪問,必須產生可以訪問任何存儲區的通用代碼。由上所述,使用存儲器指針比使用通用指針效率高,存儲器指針所占空間小,速度更快,在存儲器空間明確時,建議使用存儲器指針,如果存儲器空間不明確則使用通用指針。3.3C51語言的函數函數是一個完成一定相關功能的執行代碼段。在高級語言中,函數與另外兩個名詞“子程序”和“過程”用來描述同樣的事情。在C51語言中使用的是函數這個術語。C51語言中函數的數目是不限制的,但是一個C51程序必須至少有一個函數,以main為名,稱為主函數,主函數是唯一的,整個程序從這個主函數開始執行。C51語言還可建立和使用庫函數,可由用戶根據需求調用。3.3.1函數的分類從結構上分,C51語言函數可分為主函數main()和普通函數兩種。而普通函數又劃分為兩種:標準庫函數和用戶自定義函數。1.標準庫函數標準庫函數是由C51編譯器提供的。編程者在進行程序設計時,應該善于充分利用這些功能強大、資源豐富的標準庫函數資源,以提高編程效率。用戶可直接調用C51庫函數而不需為這個函數寫任何代碼,只需要包含具有該函數說明的頭文件即可。例如調用輸出函數printf時,要求程序在調用輸出庫函數前包含以下的include命令:
#include<stdio.h>2.用戶自定義函數用戶自定義函數是用戶根據需要所編寫的函數。從函數定義的形式分為:無參函數、有參函數和空函數。(1)無參函數此種函數在被調用時,既無參數輸入,也不返回結果給調用函數,只是為完成某種操作而編寫的函數。無參函數的定義形式為:
返回值類型標識符函數名(){ 函數體;}無參函數一般不帶返回值,因此函數的返回值類型的標識符可省略。例如函數:main(),為無參函數,返回值類型的標識符可省略,默認值是int類型。(2)有參函數調用此種函數時,必須提供實際的輸入函數。有參函數的定義形式為:返回值類型標識符函數名(形式參數列表)形式參數說明{ 函數體;}【例-15】定義一個函數max(),用于求兩個數中的大數。inta,bintmax(a,b){ if(a>b)return(a); elsereturn(b);}程序段中,a、b為形式參數。return()為返回語句。(3)空函數此種函數體內是空白的。調用空函數時,什么工作也不做,不起任何作用。定義空函數的目的,并不是為了執行某種操作,而是為了以后程序功能的擴充。先將一些基本模塊的功能函數定義成空函數,占好位置,并寫好注釋,以后再用一個編好的函數代替它。這樣整個程序的結構清晰,可讀性好,以后擴充新功能方便。空函數的定義形式為:返回值類型標識符函數名(){}例如:floatmin(){} /*空函數,占好位置*/3.3.2函數的調用在一個函數中需要用到某個函數的功能時,就調用該函數。調用者稱為主調函數,被調用者稱為被調函數。1.函數調用的一般形式函數調用的一般形式: 函數名 {實際參數列表};若被調函數是有參函數,則主調函數必須把被調函數所需的參數傳遞給被調函數。傳遞給被調函數的數據稱為實際參數(簡稱實參),必須與形參的數據在數量、類型和順序上都一致。實參可以是常量、變量和表達式。實參對形參的數據是單向的,即只能將實參傳遞給形參.2.函數調用的方式主調用函數對被調用函數的調用有以下3種方式。(1)函數調用語句函數調用語句把被調用函數的函數名作為主調函數的一個語句。例如:print_message();此時,并不要求函數返回結果數值,只要求函數完成某種操作。(2)函數結果作為表達式的一個運算對象函數結果作為表達式的一個運算對象,例如:
result=2*gcd(a,b);被調用函數以一個運算對象出現在表達式中。這要求被調用函數帶有return語句,以便返回一個明確的數值參加表達式的運算。被調用函數gcd為表達式的一部分,它的返回值乘2再賦給變量result。
(3)函數參數函數參數即被調用函數作為另一個函數的實際參數。例如:m=max(a,gcd(u,v));其中,gcd(u,v)是一次函數調用,它的值作為另一個函數的max()的實際參數之一。3.對調用函數的說明在一個函數調另一個函數調用另一個函數時,須具備以下條件:(1)被調用函數必須是已經存在的函數(庫函數或用戶自定義的函數)。(2)如果程序中使用了庫函數,或使用了不在同一文件中的另外自定義函數,則應該在程序的開頭處使用
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 顧客心理在新零售體驗設計中的重要性
- 項目管理知識與實戰經驗分享
- 非遺在商業綜合體設計中傳承與創新的設計理念與實踐案例分析
- 顧客體驗優化新零售商業模式的創新方向
- 非物質文化遺產的數字記錄與保存策略研究
- 顧客體驗升級從傳統零售到新零售的轉變
- 音樂、電影、設計多元文化創意產業的碰撞與融合
- 零售行業中的大數據分析與決策支持
- 非線性系統分析方法在處理物流業務復雜風控中的作用
- 隱私合規的商業價值銀行業務的持續發展
- 指導腎性貧血患者自我管理的中國專家共識(2024版)解讀課件
- 外泌體研究進展和應用
- 污水處理廠事故應急響應預案
- 2025年中國融通文化教育集團有限公司招聘筆試參考題庫含答案解析
- 統編版(2025春)七年級下冊道德與法治第三單元素養評價測試卷(含答案)
- 中醫診斷學(切診-按診)
- 2025年廈門大學嘉庚學院圖書館員招考高頻重點模擬試卷提升(共500題附帶答案詳解)
- 交通部《公路建設項目可行性研究報告編制辦法》(新)
- 肺癌的護理查房 課件
- 高級護理實踐知到智慧樹章節測試課后答案2024年秋浙江中醫藥大學
- 【數學】現實中的變量教學設計 2024-2025學年北師大版數學七年級下冊
評論
0/150
提交評論