轉載地址:http://www.dwenzhao.cn/profession/mcu/mcu51keilc.html
Keil C51是一種專爲8051系列單片機設計的C編譯器,支持符合ANSI標準的C語言進行程序設計,同時針對8051系列單片機自身特點做了一些特殊擴展。
1. Keil C51程序設計基本語法:
1)Keil C51程序的一般結構:
C51程序由一個或多個函數構成,其中至少應包含一個主函數main()。程序執行時,一定是從main()函數開始,調用其他函數後又返回main()函數,被調用函數如果位於主調函數前面則可直接調用,否則要先說明後調用,函數之間可以互相調用。C51程序的一般結構如下:
預處理命令 //用於包含頭文件等
全局變量說明; //全局變量可被本程序的所有函數引用
函數1說明;
... ...
函數n說明;
/*主函數*/
main(){
局部變量說明; //局部變量只能在所定義的函數內部引用
執行語句;
函數調用(形式參數表);
}
/*其他函數定義*/
函數1(形式參數定義){
局部變量說明; //局部變量只能在所定義的函數內部引用
執行語句;
函數調用(形式參數表);
}
... ...
函數n(形式參數定義){
局部變量說明; //局部變量只能在所定義的函數內部引用
執行語句;
函數調用(形式參數表);
}
由此可見,C51是由函數組成的,函數之間可以相互調用,但main()函數只能調用其他功能函數,不能被其他函數調用。其他功能函數,可以是C51編譯器提供的庫函數,也可以是用戶按需要自行編寫的。不管main()函數處於程序中什麼位置,程序總是從main()開始執行。
編寫C51程序的注意點:
①函數以“{”開始,以“}”結束,二者必須成對出現,它們之間的部分爲函數體。
②C51程序沒有行號,一行內可以書寫多條語句,一條語句也可以分寫在多行上。
③每條語句最後必須以一個分號“;”結尾,分號是C51程序的必要組成部分。
④每個變量必須先定義後引用。函數內部定義的變量爲局部變量,又稱內部變量,只有在定義它的那個函數之內才能夠使用。在函數外部定義的變量爲全局變量,又稱外部變量,在定義它的那個程序文件中的函數都可以使用它。
⑤對程序語句的註釋必須放在雙斜槓“//”之後,或者放在“/*......*/”之內。
2)數據類型:
C51數據類型可分爲基本數據類型和複雜數據類型,複雜數據類型由基本數據類型構造而成。基本數據類型有char(字符型)、int(整型)、long(長整型)、float(浮點型)和*(指針型)。下表列出了Keil C51編譯器能夠識別的數據類型:
數據類型 |
長度 |
值域 |
unsigned char |
單字節 |
0~255 |
signed char |
單字節 |
-128~+127 |
unsigned int |
雙字節 |
0~65536 |
signed int |
雙字節 |
-32768~+32767 |
unsigned long |
四字節 |
0~4294967295 |
signed long |
四字節 |
-2147483648~+2147483647 |
float |
四字節 |
±1.175494E-38~±3.402823E+38 |
* |
1~3字節 |
對象的地址 |
bit |
位 |
0或1 |
sfr |
單字節 |
0~255 |
sfr16 |
雙字節 |
0~65536 |
sbit |
位 |
0或1 |
Keil C51編譯器除了支持基本數據類型之外,還支持擴充數據類型:
①bit:位類型,可以定義一個位變量,但不能定義位指針,也不能定義位數組。
②sfr:特殊功能寄存器,可以定義8051單片機的所有內部8位特殊功能寄存器。sfr型數據佔用一個內存單元,其取值範圍是0~255。
③sfr16:16位特殊功能寄存器,它佔用兩個內存單元,取值範圍0~65535,可以定義8051單片機內部的16位特殊功能寄存器。
④sbit:可尋址位,可以定義8051單片機內部RAM中的可尋址位或特殊功能寄存器中的可尋址位。
示例:採用如下語句可以將8051單片機P0口地址定義爲80H,將P0.1位定義爲FLAG1。
sfr P0=80H;
sbit FLAG1=P0^1;
3)常量、變量及其存儲模式:
常量包括整型變量、浮點型變量、字符型常量及字符串常量等。
變量,是一種在程序執行過程中其值能不斷變化的量。在使用一個變量之前,必須先進行定義,用一個標識符作爲變量名並指出它的數據類型和存儲類型,以便編譯系統爲它分配相應的存儲單元。在C51中對變量進行定義的格式如下:
[存儲種類] 數據類型 [存儲器類型] 變量名錶;
其中,“存儲種類”和“存儲器類型”是可選項。變量的存儲種類有4種:auto自動、extern外部、static靜態和register寄存器。定義變量時如果省略存儲種類選項,則該變量將爲自動變量。
Keil C51編譯器還允許說明變量的存儲器類型,使之能夠在8051單片機系統內準確地定位。下表列出了Keil C51編譯器所能識別的存儲器類型:
存儲器類型 |
說明 |
DATA |
直接尋址的片內數據存儲器(128B),訪問速度最快 |
BDATA |
可位尋址的片內數據存儲器(16B),允許位與字節混合訪問 |
IDATA |
間接訪問的片內數據存儲器(256B),允許訪問全部片內地址 |
PDATA |
分頁尋址的片外數據存儲器(256B),用MOVX @Ri指令訪問 |
XDATA |
片外數據存儲器(64KB),用MOVX @DPTR指令訪問 |
CODE |
程序存儲器(64KB),用MOVC @A+DPTR指令訪問 |
下面是一些變量定義的示例:
char data var1; //在DATA區定義字符型變量var1
int idata var2; //在IDATA區定義整型變量var2
char code text[]=”ENTER PARAMETER”; //在CODE區定義字符串數組text[]
long xdata array[100]; //在XDATA區定義長整數數組變量array[100]
extern float idata x,y,z; //在IDATA區定義外部浮點型變量x, y, z
char bdata flags; //在BDATA區定義字符型變量flags
sbit flag0=flags^0; //在BDATA區定義可位尋址變量flag0
sfr P0=ox80; //定義特殊功能寄存器P0
定義變量時如果省略“存儲器類型”選項,則按編譯時使用的存儲器模式SMALL、COMPACT或LARGE來規定默認存儲器類型,確定變量的存儲器空間。函數中不能採用寄存器傳遞的參數變量的過程變量也保存在默認的存儲器空間中。Keil C51編譯器的3種存儲器模式對變量的影響如下:
①SMALL:變量被定義在8051單片機片內數據存儲器中,對這種變量的訪問速度最快。另外,所有的對象,包括堆棧,都位於片內數據存儲器中,實際的堆棧長度取決於不同函數的嵌套深度。
②COMPACT:變量被定義在分頁尋址的片外數據存儲器中,每一頁片外數據存儲器的長度爲256B。這時對變量的訪問是通過寄存器間接尋址(MOVX @Ri)進行的,堆棧位於8051單片機片內數據存儲器中。採用這種模式的同時,必須適當改變啓動配置文件STARTUP.A51中的參數PDATASTART和PDATALEN;在用BL51進行連接時,還必須採用連接控制命令“PDATA”對P2口地址進行定位,這樣才能確保P2口爲所需要的高8位地址。
③LARGE:變量被定義在片外數據存儲器中(最大可達64KB),通常使用數據指針(DPTR)來間接訪問變量(MOVX @DPTR)。這種訪問數據的方法效率不高,尤其是對於2個以上字節的變量。
Keil C51編譯器在不同編譯模式下的存儲器類型:
編譯模式 |
存儲器類型 |
SMALL |
DATA |
COMPACT |
PDATA |
LARGE |
XDATA |
4)運算符與表達式:
Keil C51對數據有很強的表達能力,具有十分豐富的運算符。運算符就是完成某種特定運算的符號,表達式則是由運算符及運算對象所組成的具有特定含義的算式。
運算符,按其在表達式中所起的作用,可分爲賦值運算符、算術運算符、增量與減量運算符、關係運算符、邏輯運算符、位運算符、複合賦值運算符、逗號運算符、條件運算符、指針和地址運算符、強制類型轉換運算符等。
⑴賦值運算符:
在C51程序中,符號“=”稱爲賦值運算符,作用是將一個數據的值賦給一個變量。
⑵算術運算符:
C語言中的算術運算符有+、-、*、/(除)和%(取餘)運算符。其中加、減和乘法都符合一般的算術運算規則,但除法和取餘有所不同。如果兩個整數相除,其結果爲整數,捨去小數部分;如果是兩個浮點數相除,其結果爲浮點數。取餘運算要求兩個運算對象均爲整形數據。
⑶增量和減量運算符:
C51還提供有一種特殊的運算符,即++(增量)和--(減量)運算符,作用分別是對運算對象作加1和減1運算。增量和減量運算符只能用於變量,不能用於常數或表達式,在使用中還要注意運算符的位置。例如,++i與i++的意義不同,前者爲在使用i前先使i加1,而後者則是在使用i之後再使i的值加1。
⑷關係運算符:
C語言中有以下6種關係運算符:>、<、>=、<=、= =、!=。用關係運算符將兩個表達式連接起來即稱爲關係表達式。
⑸邏輯表達式:
C51中有以下3種邏輯運算符:||(邏輯或)、&&(邏輯與)、!(邏輯非)。用邏輯運算符將關係表達式或邏輯量連接起來即稱爲邏輯表達式。
關係運算符和邏輯運算符通常用來判別某個條件是否滿足,關係運算和邏輯運算的結果只有0和1兩種值。當所指定的條件滿足時結果爲1,條件不滿足時結果爲0。
⑹位運算符:
C51中共有以下6種位運算符:~(按位取反)、<<(左移)、>>(右移)、&(按位與)、^(按位異或)、|(按位或)。爲運算符的作用是按位對變量進行運算。位運算符不能用來對浮點型數據進行操作。
⑺複合賦值運算符:
在賦值運算符“=”的前面加上其他運算符,就構成了複合賦值運算符。C51中共有以下10種複合賦值運算符:+=(加法賦值)、-=(減法賦值)、*=(乘法賦值)、/=(除法賦值)、%=(取模賦值)、<<=(左移位賦值)、>>=(右移位賦值)、&=(邏輯與賦值)、|=(邏輯或賦值)、^=(邏輯異或賦值)、~=(邏輯非賦值)。
複合賦值運算,首先對變量進行某種操作,然後將運算的結果再賦給該變量。採用複合賦值運算符,可以使程序簡化,同時還可以提高程序的編譯效率。
⑻逗號運算符:
在C51程序中,逗號“,”是一種特殊的運算符,用逗號運算符連接起來的兩個(或多個)表達式,稱爲逗號表達式。在程序運行時,對逗號表達式的處理是從左至右依次計算出各個表達式的值,而整個逗號表達式的值是最右邊表達式(即表達式n)的值。
⑼條件運算符:
條件運算符“?:”是C51中唯一的三目運算符,它要求有3個運算對象,用它可以將3個表達式連接構成一個條件表達式。條件表達式的一般形式如下:
邏輯表達式 ? 表達式1 : 表達式2
其功能是:首先計算邏輯表達式,當值爲真(非0值)時,將表達式1的值作爲整個條件表達式的值;當邏輯表達式的值爲假(0值)時,將表達式2的值作爲整個條件表達式的值。
例如,條件表達式max=(a>b)?a:b的執行結果是將a和b中的較大者賦值給變量max。另外,條件表達式中邏輯表達式的類型可以與表達式1和表達式2的類型不一樣。
⑽指針和地址運算符:
C51中專門規定了一種指針類型的數據,變量的指針就是該變量的地址,還可以定義一個指向某個變量的指針變量。C51提供了以下兩個專門的運算符:*(取內容)、&(取地址)。
取內容和取地址運算的一般形式如下:
變量=*指針變量
指針變量=&目標變量
取內容運算的含義是將指針變量所指向的目標變量的值賦給左邊的變量;取地址運算的含義是將目標變量的地址賦給左邊的變量。需要注意,指針變量中只能存放地址(即指針型數據),不要將一個非指針類型的數據賦值給一個指針變量。示例:
char data *p //定義指針變量
p=30H //給指針變量賦值,30H爲8051片內RAM地址
⑾C51對存儲器和特殊功能寄存器的訪問:
由於8051單片機存儲器結構自身的特點,C51提供了利用庫函數中的絕對地址訪問頭文件“absacc.h”來訪問不同區域的存儲器以及片外擴展I/O端口的方法。
在“absacc.h”頭文件中進行了如下宏定義:
CBYTE[地址] (訪問CODE區char型)
DBYTE[地址] (訪問DATA區char型)
PBYTE[地址] (訪問PDATA區或I/O端口char型)
XBYTE[地址] (訪問XDATA區或I/O端口char型)
CWORD[地址] (訪問CODE區int型)
DWORD[地址] (訪問DATA區int型)
PWORD[地址] (訪問PDATA區或I/O端口int型)
XWORD[地址] (訪問XDATA區或I/O端口int型)
下面的語句是向片外擴展端口地址7FFFH寫入一個字符型數據:
XBYTE[0x7FFF]=0x80;
下面的語句是將int型數據0x9988送入外部RAM單元0000H和0001H:
XWORD[0]=0x9988;
如果採用如下語句定義一個D/A轉換器端口地址:
#define DAC0832 XBYTE[0x7FFF]
那麼程序文件中所有出現DAC0832的地方,就是對地址爲0x7FFFH的外部RAM單元或I/O端口進行訪問。
8051單片機具有100多個品種,爲了方便訪問不同品種單片機內部特殊功能寄存器,C51提供了多個相關頭文件,如reg51.h、reg52.h等。在頭文件中,對單片機內部特殊功能寄存器及其可尋址位進行了定義,編程時只要根據所採用的單片機,在程序文件的開始處用文件包含處理命令“#include”將相關頭文件包含進來,然後就可以直接引用特殊功能寄存器了。例如,下面語句完成的8051定時方式寄存器TMOD的賦值:
#include <reg51.h>
TMOD=0x20;
⑿強制類型轉換運算符:
C語言中的圓括號“()”也可作爲一種運算符使用,這就是強制類型轉換運算符,它的作用是將表達式或變量的類型強制轉換成爲所指定的類型。
數據類型轉換分爲隱式轉換和顯式轉換。隱式轉換是在對程序編譯時由編譯器自動處理的,並且只有基本數據類型(即char、int、long和float)可以進行隱式轉換,其他數據類型不能進行隱式轉換,這種情況下就必須利用強制類型轉換運算符來進行顯式轉換。強制類型轉換運算符的一般使用形式如下:
(類型)=表達式
顯式強制類型轉換在給指針變量賦值時特別有用。例如,預先在8051單片機的片外數據存儲器(XDATA)中定義了一個字符型指針變量px,如果想給這個指針變量賦一初值0xB000,可以寫成px=(char xdata *)0xB00
2. C51程序的基本語句:
C51提供了十分豐富的程序控制語句。
1)表達式語句:
表達式語句是最基本的一種語句。在表達式的後面加一個分號“;”,就構成了表達式語句。表達式語句也可以僅由一個分號“;”組成,這種語句稱爲空語句。當程序在語法上需要有一個語句,但在語義上並不要求有具體的動作時,便可以採用空語句。
2)複合語句:
複合語句,是由若干條語句組合而成的一種語句,是用“{}”將若干條語句組合在一起而形成的功能塊,內部的各條單語句需以“;”結束。複合語句的一般形式如下:
{
局部變量定義;
語句1;
語句2;
... ...
語句n;
}
複合語句在執行時,其中各條語句依次順序執行。整個複合語句在語法上等價於一條單語句。複合語句允許嵌套,即在複合語句內部還可以包含其他的複合語句。實際上,函數的執行部分(函數體)就是一個複合語句。複合語句中的單語句一般是可執行語句,也可以是變量定義語句。在複合語句內所定義的變量,稱爲該複合語句中的局部變量,僅在當前複合語句中有效。
3)條件語句:
條件語句,又稱爲分支語句,是用關鍵字“if”構成的。C51提供了3種形式的條件語句:
①if (表達式) 語句
含義是:若條件表達式的結果爲真(非0值),執行後面的語句;反之,若條件表達式的結果爲假(0值),不執行後面的語句。其中的語句也可以是複合語句。
②if (表達式) 語句1
else 語句2
含義是:若條件表達式的結果爲真(非0值),執行語句1;反之,若條件表達式的結果爲假(0值),執行語句2。其中的語句都可以是複合語句。
③if (條件表達式1) 語句1
else if (條件表達式2) 語句2
else if (條件表達式3) 語句3
... ...
else if (條件表達式m) 語句m
else 語句n
這種條件語句常用來實現多方向條件分支。當分支較多時,會使條件語句的嵌套層次太多,程序冗長,可讀性降低。
4)開關語句:
開關語句也是一種用來實現多方向條件分支的語句。開關語句直接處理多分支選擇,使程序結構清晰,使用方便。開關語句是用關鍵字switch構成的,一般形式如下:
switch (表達式)
{
case 常量表達式1:語句1;
break;
case 常量表達式2:語句2;
break;
... ...
case 常量表達式n:語句n;
break;
default:語句d;
}
開關語句的執行過程是:將switch後面的表達式的值與case後面各個常量表達式的值逐個進行比較,若遇到匹配時,就執行相應case後面的語句,然後執行break語句。若無匹配的情況,則只執行default後面的語句d。break語句又稱間斷語句,功能是中止當前語句的執行,使程序跳出switch語句。
5)循環語句:
實際應用中,很多地方要用到循環控制,如對於某種操作需要反覆進行多次等。在C51程序中,用來構成循環控制的語句有while、do-while、for以及goto等形式的語句。
①採用while構成的循環結構:
while (表達式) 語句;
含義是:當條件表達式的結果爲真(非0值)時,程序就重複執行後面的語句,一直執行到條件表達式的結果變爲假(0值)爲止。這種循環結構是先檢查條件表達式所給出的條件,再根據檢查結果決定是否執行後面的語句。如果條件表達式的結果一開始就爲假,則後面的語句一次也不會被執行。這裏的語句可以是複合語句。
②採用do-while構成的循環結構:
do 語句 while (表達式);
含義是:當條件表達式的值爲真(非0值)時,則重複執行循環體語句,直到條件表達式的值變爲假(0值)爲止。這種循環結構是先執行給定的循環語句,然後再檢查條件表達式的結果,任何條件下循環體語句至少會被執行一次。
③採用for構成的循環結構:
for ([初值設定表達式];[循環條件表達式];[更新表達式]) 語句;
for語句的執行過程是:先計算初值表達式的值作爲循環控制變量的初值,再檢查循環條件表達式的結果,當滿足條件時就執行循環體語句並計算更新表達式,然後再根據更新表達式的計算結果來判斷循環條件是否滿足... ...一直進行到循環條件表達式的結果爲假(0值)時退出循環體。
6)goto、break、continue語句:
①goto語句:
是一個無條件轉向語句,它的一般形式如下:
goto 語句標號;
其中,語句標號是一個帶冒號“:”的標識符。將goto語句與if語句一起使用,可以構成一個循環語句,但更常見的是採用goto語句來跳出多重循環。需要注意,只能用goto語句從內層循環跳到外層循環,而不允許從外層循環跳到內層循環。
②break語句:
break語句也用於跳出循環語句,只能用於開關語句和循環語句中,是一種具有特殊功能的無條件轉移語句。對於多重循環的情況,break語句只能跳出它所處的那一層循環。
③continue語句:
continue語句是一種中斷語句,功能是中斷本次循環,通常和條件語句一起用在由while、do-while和for語句構成的循環結構中,也是一種具有特殊功能的無條件轉移語句。但與break語句不同,continue語句並不跳出循環體,而只是根據循環控制條件確定是否繼續執行循環語句。
7)返回語句:
返回語句用於終止函數的執行,並控制程序返回到調用該函數時所處的位置。返回語句有兩種形式:
return (表達式);
return;
如果return語句後面帶有表達式,則要計算表達式的值,並將表達式的值作爲該函數的返回值。若使用不帶表達式的形式,則被調用函數返回主函數時函數值不確定。一個函數的內部,可以含有多個return語句,但程序僅執行其中的一個return語句而返回主調用函數。一個函數的內部也可以沒有return語句,在這種情況下,當程序執行到最後一個界限符“}”處時,就自動返回主調函數。
3. 函數:
從用戶的角度看,有兩種函數,即標準庫函數和用戶自定義函數。標準庫函數是Keil C51編譯器提供的,可以直接調用;用戶自定義函數是用戶根據自己的需要編寫的,必須先進行定義之後才能調用。
1)函數的定義:
函數定義的一般形式如下:
函數類型 函數名(形式參數表)
{
局部變量定義;
函數體語句;
}
其中,函數類型說明了自定義函數返回值的類型;函數名是用標識符表示的自定義函數的名字;形式參數表中則列出了主調用函數與被調用函數之間傳遞的數據,形式參數的類型必須加以說明。局部變量是對在函數內部使用的局部變量進行定義;函數體語句是爲完成該函數的特定功能而設置的各種語句。
ANSI C標準,允許在形式參數表中對形式參數的類型進行說明。如果定義的是無參函數,可以沒有形式參數表,但圓括號“()”不能省略。
2)函數的調用:
所謂函數調用,就是在一個函數體中引用另外一個已經定義了的函數,前者稱爲主調函數,後者稱爲被調用函數。C51程序中,函數是可以互相調用的。函數調用的一般形式爲:
函數名 (實際參數表)
其中,函數名指出被調用的函數;實際參數表中可以包括多個實際參數,各個參數之間用逗號“,”分開。實際參數的作用是將它的值傳遞給被調用函數中的形式參數。需要注意,函數調用中的實際參數與函數定義中的形式參數必須在個數、類型及順序上嚴格保持一致,以便將實際參數的值正確地傳遞給形式參數,否則在函數調用時會產生意想不到的結果。如果調用的是無參函數,則可以沒有形式參數表,但圓括號“()”不能省略。
C51中可以採用3種方式完成函數的調用:
①函數語句:
在主調函數中,將函數調用作爲一條語句。這是無參調用,不要求被調用函數返回一個確定的值,只要求它完成一定的操作。
②函數表達式:
在主調函數中,將函數調用作爲一個運算對象直接出現在表達式中,這種表達式稱爲函數表達式。這種函數調用方式通常要求被調用函數返回一個確定的值。
③函數參數:
在主調函數中,將函數調用作爲另一個函數調用的實際參數。這種在調用一個函數的過程中又調用了另外一個函數的方式,稱爲嵌套函數調用。
3)函數的說明:
與使用變量一樣,在調用一個函數之前(包括標準庫函數),必須對該函數的類型進行說明,即“先說明,後調用”。如果調用的是庫函數,一般應在程序的開始處用預處理命令#include將有關函數說明的頭文件包含進來。
如果調用的是用戶自定義函數,而且函數與調用它的主調函數在同一個文件中,一般應該在主調用函數中對被調用函數的類型進行說明。函數說明的一般形式如下:
類型標識符 被調用的函數名(形式參數表);
其中,類型標識符說明了函數的返回值的類型;形式參數表說明了各個形式參數的類型。
需要注意,函數定義與函數說明是完全不同的,書寫方式上也有差別。函數定義時,被定義函數名的圓括號後面沒有分號“;”,函數定義還未結束,後面應接着寫被定義的函數體部分;而函數說明結束時,在圓括號的後面需要有一個分號“;”作爲結束標誌。
4)中斷服務函數:
C51支持在C語言源程序中直接編寫8051單片機的中斷服務函數程序,一般形式爲:
函數類型 函數名(形式參數表) [interrupt n] [using n]
關鍵字interrupt後面的n是中斷號,n的取值範圍爲0~31.編譯器從8n+3處產生中斷向量,具體的中斷號n和中斷向量取決於8051系列單片機芯片的型號。常用的中斷源與中斷向量表如下表:
中斷號n |
中斷源 |
中斷向量8n+3 |
0 |
外部中斷0 |
0003H |
1 |
定時器0 |
000BH |
2 |
外部中斷1 |
0013H |
3 |
定時器1 |
001BH |
4 |
串行口 |
0023H |
8051系列單片機可以在片內PAM中使用4個不同的工作寄存器組,每個寄存器組中包含8個工作寄存器(R0~R7)。C51編譯器擴展了一個關鍵字using,專門用來選擇8051單片機中不同的工作寄存器組。using後面的n是一個0~3的常整數,分別選中4個不同的工作寄存器組。在定義一個函數時,using是一個選項,如果不用該選項,則由編譯器自動選擇一個寄存器組做絕對寄存器組訪問。
編寫8051單片機中斷函數時應遵循以下規則:
①中斷函數不能進行參數傳遞,也沒有返回值,因此一般定義爲void型。
②在任何情況下都不能直接調用中斷函數,否則會產生編譯錯誤。
③如果中斷函數中調用了其他函數,則被調用函數所使用的寄存器組必須與中斷函數相同。
④C51編譯器從絕對地址8n+3處產生一箇中斷向量,其中n爲中斷號,該向量包含一個到中斷函數入口地址的絕對跳轉。
5. Keil C51編譯器對ANSI C的擴展:
1)存儲器類型:
8051單片機的存儲器空間可分爲片內外統一編址的程序寄存器ROM、片內數據存儲器RAM和片外數據存儲器RAM。
①C51編譯器對ROM存儲器提供了存儲器類型標識符code,用戶的應用程序代碼以及各種表格常數被定爲在code空間。
②數據存儲器RAM用於存放各種變量,通常應儘可能將變量放在片內RAM中以加快操作速度。C51編譯器對片內RAM提供了3種存儲器類型標識符,即data、idata、bdata。data地址範圍爲0x00~0x7f,位於data空間的變量以直接尋址方式操作,速度很快;idata地址範圍爲0x00~0xff,位於idata空間的變量以寄存器間接尋址方式操作,速度略慢於data空間;bdata地址範圍爲0x20~0x2f,位於bdata空間的變量,除了可以進行直接尋址或間接尋址之外,還可以進行位尋址操作。
③片外數據RAM簡稱XRAM,C51提供了xdata和pdata兩個存儲器類型標識符。xdata空間地址範圍爲0x0000~0xffff,位於xdata空間的變量以MOVX @DPTR方式尋址,可以操作整個64KB地址範圍內的變量,但這種方式速度最慢;pdata空間又稱爲片外分頁XRAM空間,它將地址0x0000~0xffff均勻地分成256頁,每頁地址都爲0x00~0xff,位於pdata空間的變量以MOVX @R0、MOVX @R1方式尋址。實際上,XRAM空間並非全部用於存放變量,用戶擴展的I/O接口也位於XRAM地址範圍之內。有些新型的80C51單片機還提供有片內XRAM,其操作方式與傳統的XRAM相同,但一般要先對相應的特殊功能寄存器(SFR)進行配置之後才能使用。
一些新型的8051單片機能夠進行大容量存儲器擴展,如到8MB甚至16MB的code和xdata存儲器空間。C51編譯器針對這種大容量擴展存儲器定義了far和const far兩種存儲器類型,分別用以操作這種擴展的片外RAM和片外ROM空間。
對於傳統的8051單片機,如果具有可以映射到xdata的附加存儲器空間,或者提供了一種地址擴展特殊功能寄存器,則可以根據具體硬件電路通過修改配置文件XBANKING.A51來使用far和const far類型的變量。需要注意,在使用far和const far存儲器類型時,必須採用LX51擴展連接定位器,同時還必須採用OMF2格式的目標文件。
下表列出了Keil C51編譯器能夠識別的存儲器類型:
存儲器類型 |
說明 |
code |
程序存儲器(64KB),用MOVC @A+DPTR指令訪問 |
data |
直接尋址的片內數據存儲器(128B),訪問速度最快 |
idata |
間接訪問的片內數據存儲器(256B),允許訪問全部片內地址 |
bdata |
可位尋址的片內數據存儲器(16B),允許位與字節混合訪問 |
xdata |
片外數據存儲器(64KB),用MOVX @DPTR指令訪問 |
pdata |
分頁尋址的片外數據存儲器(256B),用MOVX @R0、MOVX @R1指令訪問 |
far |
高達16MB的擴展RAM和ROM,專用芯片擴展訪問或用戶自定義子程序進行訪問 |
2)編譯模式:
如果定義變量時沒有明確指出具體的存儲器類型,則按C51編譯器採用的編譯模式來確定變量的默認存儲器空間。Keil C51編譯器控制命令SMALL、COMPACT、LARGE對變量存儲空間的影響如下:
①SMALL:所有變量都被定義在8051單片機的片內RAM中,對這種變量的訪問速度最快。另外,堆棧也必須位於片內RAM中,實際的堆棧長度取決於不同函數的嵌套深度。採用SMALL編譯模式,與定義變量時指定data存儲器類型具有相同的效果。
②COMPACT:所有變量被定義在分頁尋址的片外XRAM中,每一頁片外XRAM的長度都爲256B。這時對變量的訪問是通過寄存器間接尋址(MOVX @R0、MOVX @R1)進行的,變量的低8位地址由R0或R1確定,變量的高8位地址由P2口確定。採用這種模式時,必須適當改變配置文件STARTUP.A51中參數PDATASTART和PDATALEN;同時還必須在編譯器菜單“option選項/BL51 Locator標籤欄/Pdata”打開的對話框中鍵入合適的地址參數,以確保P2口能輸出所需要的高8位地址。而堆棧則位於8051單片機片內數據存儲器中。採用COMPACT編譯模式,與定義變量時指定pdata存儲器類型具有相同的效果。
③LARGE:所有變量被定義在片外XRAM中(最大可達64KB),通常使用數據指針(DPTR)來間接訪問變量(MOVX @DPTR)。這種編譯模式對數據訪問的效率最低,而且將增加程序的代碼長度。採用LARGE編譯模式與定義變量時指定xdata存儲器類型具有相同的效果。
3)數據類型bit、sbit、sfr、sfr16:
Keil C51編譯器支持標準C語言的數據類型,另外還根據8051單片機的特點擴展了數據類型,如bit、sbit、sfr、sfr16。
①bit:所有bit類型的變量都被定位在8051片內RAM的可位尋址區。由於8051單片機的可位尋址區只有16個字節長,所以在某個範圍內最多隻能聲明128個bit類型變量。聲明bit類型變量時,可以帶有存儲器類型data、idata或bdata。對於bit類型變量有如下限制:如果在函數中採用預處理命令“#pragma disable”禁止了中斷,或者在函數聲明時採用關鍵字“using n”明確進行了寄存器組切換,則該函數不能返回bit類型的值,否則C51在進行編譯時會產生編譯錯誤;另外,不能定義bit類型指針,也不能定義bit類型數組。
②sbit:sbit用於定義可獨立尋址訪問的位變量。C51編譯器提供了一個存儲器類型bdata,帶有bdata存儲器類型的變量被定爲在8051單片機片內RAM的可位尋址區。帶有bdata存儲器類型的變量可以進行字節尋址,也可以進行位尋址,因此對bdata變量可用sbit指定其中任一位爲可位尋址變量。需要注意,採用bdata及sbit所定義的變量都必須是全局變量,並且採用sbit定義可位尋址變量時要求基址對象的存儲器類型爲bdata。
例如,可先定義變量的數據類型和存儲器類型:
int bdata ibase; //定義ibase爲bdata整型變量
char bdata bary[4]; //定義bary[4]爲bdata字符型數組
然後使用sbit定義可位尋址變量:
sbit mybit0= ibase^0; //定義mybit0爲ibase的第0位
sbit mybit15= ibase^15; //定義mybit15爲ibase的第15位
sbit Ary07= bary[0]^7; //定義Ary07爲 bary[0]的第7位
sbit Ary37= bary[3]^7; //定義Ary37爲 bary[3]的第7位
操作符“^”後面的數值範圍取決於基址變量的數據類型,對於char型是0~7,對於int型是0~15,對於long型是0~31。bdata變量ibase和bdata數組bary[4]可以進行字或字節尋址,sbit變量可以直接操作可尋址位。例如:
ibase=-1; //字尋址,對ibase賦值爲-1
bary[3]=’a’; //字節尋址,對bary[3]賦值爲’a’
Ary37=0; //清0 bary[3]的第7位
mybit15=1; //置1 ibase的第15位
對bdata變量可以向對data變量一樣處理,所不同的是,bdata變量必須位於8051單片機的片內RAM的可位尋址區,其長度不能超過16個字節。
sbit還可以用於定義結構與聯合,利用這一特點可以實現對float型數據指定bit變量。例如:
union lft {
float mf;
long ml;
};
bdata struct bad {
char mc;
union lft u;
}tcp;
sbir tcpf31=tcp.u.ml^31; //float數據的第31位
sbir tcpm10=tcp.mc^0;
sbir tcpm17=tcp.mc^7;
採用sbit類型時,需要指定一個變量作爲基地址,再通過指定該基地址變量的bit位來獲得實際的物理bit地址。並不是所有類型變量的物理bit地址都與其邏輯bit地址相一致。物理上的bit0對應第一個字節的bit0,物理上的bit8對應第二個字節的bit0.對於int類型的數據,由於它是按高字節在前的方式存儲的,int類型數據的bit0應位於第二個字節的bit0,因此採用sbit指定int類型數據bit0時應使用物理上的bit8。
③sfr:8051單片機片內RAM中與idata空間相重疊的高128字節(地址範圍爲80~FFH)稱爲特殊功能寄存器SFR區。單片機對片內集成的定時器、串行口、I/O等操作都是通過SFR來實現的。爲了能夠直接訪問8051系列單片機內部的SFR,C51編譯器擴充了關鍵字sfr和sfr16,利用這種擴充關鍵字可以在C51源程序中直接定義8051單片機的SFR,定義方法如下:
sfr 特殊功能寄存器名=地址常數;
例如:
sfr P0=0x80; //定義P0寄存器地址爲0x80
sfr SCON=0x90; //定義串行口控制寄存器地址爲0x90
需要注意,關鍵字sfr後面必須跟一個標識符作爲特殊功能寄存器名,名字可任意選取,但應符合一般習慣。等號後面必須是常數,不允許有帶運算符的表達式。傳統8051單片機地址常數的範圍是0x80~0xff。
④sfr16:在一些新型8051單片機中,特殊功能寄存器經常組合成16位來使用,採用關鍵字sfr16可以定義這種16位的SFR。例如,8052單片機的定時器T2,可定義爲:
sfr16 T2=0xCC; //定義TIMER2地址爲T2L=0xCC,T2H=0xCD
這裏的T2爲SFR名,等號後面使它的低字節地址,其高字節地址必須在物理上直接位於低字節之後。
4)SFR特定位的定義:
在8051單片機應用系統中經常需要訪問特殊功能寄存器SFR中的一些特定位,可以利用C51編譯器提供的擴充關鍵字sbit來定義特殊功能寄存器SFR中的可位尋址對象,定義方法有如下三種:
①sbit 位變量名=位地址;
這種方法將位的絕對地址賦給位變量,位地址位於0x80~0xFF之間。例如:
sbit OV= 0xD2; //定義溢出標誌位地址
sbit CY= 0xD7; //定義進位標誌位地址
②sbit 位變量名=特殊功能寄存器名^位;
當可尋址位位於特殊功能寄存器SFR中時可採用這種方法,位是一個0~7之間的常數。例如:
sfr PSW=0xD0; //定義PSW寄存器地址爲0xD0
sbit OV= PSW^2; //定義溢出標誌位
sbit CY= PSW^7; //定義進位標誌位
③sbit 位變量名=字節地址^位;
這種方法以一個常數(字節地址)作爲基地址,該常數必須在0x80~0xFF之間。位是一個0~7之間的常數。例如:
sbit OV= 0xD0^2; //定義溢出標誌位地址
sbit CY= 0xD0^7; //定義進位標誌位地址
需要注意,用sbit定義的特殊功能寄存器中的可尋址位是一個獨立的定義類(class),不能與其他位定義和位域互換。
5)一般指針與基於存儲器的指針及其轉換:
Keil C51編譯器支持兩種指針類型,即一般指針和基於存儲器的指針。一般指針需要佔用3個字節,基於存儲器的指針只需要佔用1~2個字節。一般指針具有較好的兼容性,但運行速度緩慢,基於存儲器的指針是C51編譯器專門針對8051單片機存儲器的特點進行的擴展,只適用於8051單片機,但具有較高的運行速度。
①一般指針:
定義一般指針的方法與ANSI C相同,例如:
char * sptr; //定義char型指針
int * numptr; //定義int型指針
一般指針在內存中佔用3字節,第一字節存放該指針的存儲器類型編碼(由編譯模式確定),第二和第三字節分別存放該指針的高位和低位地址偏移量。一般指針的存儲器類型編碼見表:
存儲器類型 |
idata/data/bdata |
xdata |
pdata |
code |
編碼值 |
0x00 |
0x01 |
0xFE |
0xFF |
一般指針可用於存取任何變量,而不用考慮變量在8051單片機存儲器空間的位置,許多C51庫函數都採用一般指針。函數可以利用一般指針來存取位於任何存儲器空間的數據。
定義一般指針時,可以在“*”後帶一個存儲器類型選項,用於指定一般指針本身的存儲器空間位置。例如:
char * xdata sptr; //定義位於xdata空間的一般指針,char型數據
int * data numptr; //定義位於data空間的一般指針,int型數據
long * idata varptr; //定義位於idata空間的一般指針,long型數據
由於一般指針所指對象的存儲器空間位置,只有在運行期間才能確定,編譯器在編譯期間無法優化存儲方式,必須生成一般代碼以保證能對任意空間的對象進行存取,因此一般指針所產生的代碼運行速度較慢。
②基於存儲器的指針:
基於存儲器的指針具有明確的存儲器空間,長度可爲1字節(存儲器類型爲idata、data、pdata)或2字節(存儲器類型爲code、xdata)。定義指針時,如果在“*”前面增加一個存儲器類型選項,該指針就被定義爲基於存儲器的指針。例如:
char data * str; //定義指向data空間char型數據的指針
int xdata * num; //定義指向xdata空間int型數據的指針
long code * pow; //定義指向code空間long型數據的指針
與一般指針類型類似,定義基於存儲器的指針時還可以指定指針本身的存儲器空間位置,即在“*”後面帶一個存儲器類型選項。例如:
char data * xdata str; //定義指向data空間char型數據的指針,指針在xdata空間
int xdata * data num; //定義指向xdata空間int型數據的指針,指針在data空間
long code * idata pow; //定義指向code空間long型數據的指針,指針在idata空間
基於存儲器的指針長度比一般指針短,可以節省存儲器空間,運行速度快,但它所指對象具有確定的存儲器空間,缺乏兼容性。
③一般指針與基於存儲器的指針之間的轉換:
一般指針與基於存儲器的指針可以相互轉換。在某些函數調用中進行參數傳遞時需要採用一般指針,例如C51的庫函數printf()、sprintf()、gets()等便是如此。當傳遞的參數是基於存儲器的指針時,若不特別指明,C51編譯器會自動將其轉換爲一般指針。
需要注意,如果採用基於存儲器的指針作爲自定義函數的參數,而程序中又沒有給出該函數原型,則基於存儲器的指針就自動轉換爲一般指針,有時會導致錯誤發生。爲了避免這類錯誤,應該在程序的開始處用預處理命令“#include”將函數原型說明文件包含進來,或者直接給出函數原型聲明。
6)C51編譯器對ANSI C函數定義的擴展:
C51編譯器提供了幾種對於ANSI C函數定義的擴展,可用於選擇函數的編譯模式、規定函數所使用的工作寄存器組、定義中斷服務函數、指定再入方式等。
C51程序中進行函數定義的一般格式如下:
函數類型 函數名(形式參數表) [編譯模式] [reentrant] [interrupt n] [using n]
{
局部變量定義
函數體語句
}
其中,函數類型說明了自定義函數返回值的類型,函數名是用標識符表示的自定義函數名字,形式參數表中列出了在主調用函數與被調用函數之間傳遞數據的形式參數,形式參數的類型必須加以說明。
編譯模式選項是C51對ANSI C的擴展,可以是SMALL、COMPACT或LARGE,用於指定函數中局部變量和參數的存儲器空間;reentrant選項是C51對ANSI C的擴展,用於定義再入函數;interrupt n選項是C51對ANSI C的擴展,用於定義中斷服務函數,n爲中斷號,可爲0~31;using n選項是C51對ANSI C的擴展,用於確定中斷服務程序所使用的工作寄存器組,n可以是0~3。
①堆棧及函數的參數傳遞:
函數在運行過程中需要使用堆棧,8051單片機的堆棧必須位於片內RAM空間,其最大範圍只有256B。(對於一些新型8051單片機,C51編譯器可以使用擴展堆棧區,最大可達幾千字節)。爲了節省堆棧空間,C51 編譯器採用一個固定的存儲器區域來進行函數參數的傳遞。發生函數調用時,主調函數先將實際參數複製到該固定的存儲器區域,然後再將程序流程控制交給被調函數,被調函數則從該固定的存儲器區域取得所需要的參數進行操作。這樣,就需要將函數的返回地址保存到堆棧區。由於中斷服務函數可能要進行工作寄存器組切換,因此需要採用較多的堆棧空間。
C51編譯器可以採用控制命令“REGPARMS”和“NOREGPARMS”來決定是否通過工作寄存器傳遞函數參數。默認狀態下,C51編譯器通過工作寄存器最多傳遞3個函數參數,這種方式可以提高程序執行效率。如果沒有寄存器可用,則通過固定的存儲器區域來傳遞函數的參數。
②函數的編譯模式:
不同類項的8051單片機片內RAM空間大小不同,有些衍生產品只有64個字節的片內RAM,因此在定義函數時要根據具體情況來決定應採用的編譯模式。函數參數和局部變量都存放在由編譯模式決定的默認存儲器空間中,可以根據需要對不同函數採用不同的編譯模式。在SMALL編譯模式下,函數參數和局部變量被存放在8051的片內RAM空間,這種方式對數據的處理效率最高。但片內RAM空間有限,對於較大的程序若採用SMALL編譯模式可能不能滿足要求,這時就需要採用其他編譯模式。下面的函數分別採用了不同的編譯模式:
#pragma small //默認編譯模式爲small
extern int calc(char i, int b) large reentrant; //採用LARGE編譯模式
extern int func(int i, float f) large; //採用LARGE編譯模式
extern void *tcp(char xdata *xp, int ndx) small; //採用SMALL編譯模式
int mtest(int i, int y) {
return(i*y+y*i+func(-1, 4.75)); //採用默認編譯模式
}
int large_func(int i, int k) { large
return(mtest(i, k)+2); //採用LARGE編譯模式
}
③寄存器組切換:
8051單片機片內RAM中最低32個字節平均分爲4組,每組8個字節都命名爲R0~R7,統稱爲工作寄存器組。利用擴展關鍵字“using”可以在定義函數時規定所使用的工作寄存器組,只要在後面跟一個數字0~3,即可規定所使用的工作寄存器組。
需要注意,關鍵字using不能用在以寄存器返回一個值的函數中,並且要保證任何寄存器組的切換都只在仔細控制的區域內發生,否則將產生不正確的結果。帶using屬性的函數原則上不能返回bit類型的值。
8051單片機復位時PSW的值爲0x00,因此在默認狀態下所有非中斷函數都將使用工作寄存器0區。C51編譯器可以通過控制命令“REGISTERBAN”爲源程序中的所有函數指定一個默認的工作寄存器組,爲此用戶需要修改啓動代碼選擇不同的寄存器組,然後採用控制命令“REGISTERBAN”來指定新的工作寄存器組。
在默認狀態下,C51編譯器生成的代碼將使用絕對尋址方式來訪問工作寄存器R0~R7,從而提高操作性能。絕對寄存器尋址方式可以通過編譯控制命令“AREGS”或“NOAREGS”來激活或禁止。採用了絕對寄存器的函數不能被另一個使用了不同工作寄存器組的函數所調用,否則會導致不可預知的結果。爲了使函數對當前工作寄存器組不敏感,該函數必須採用控制命令“NOAREGS”進行編譯。
需要注意,C51編譯器對函數之間使用的工作寄存器組是否匹配不做檢查,因此使用了交替寄存器組的函數只能調用沒有設定默認寄存器組的函數。
④中斷函數:
利用控制關鍵字“interrupt”可以直接在C51程序中定義中斷服務函數。在“interrupt”後跟一個0~31的數字,用於規定中斷源和中斷入口。關鍵字“interrupt”對中斷函數目標代碼的影響如下:
·在進入中斷函數時,特殊功能寄存器ACC、B、DPH、DPL、PSW將被保存入棧
·如果不使用關鍵字using進行工作寄存器切換,則將中斷函數中所有用到的工作寄存器入棧
·函數退出之前所有的寄存器內容出棧恢復
·中斷函數由8051單片機指令RETI結束
·C51編譯器根據中斷號自動生成中斷函數入口向量地址
⑤再入函數:
利用C51編譯器的擴展控制字“reentrant”可以定義一個再入函數。再入函數可以進行遞歸調用,或者同時被兩個以上的其他函數同時調用。通常,在實時系統應用中或在中斷函數與非中斷函數需要共享一個函數時,應將該函數定義爲再入函數。
再入函數可被遞歸調用,無論何時,包括中斷服務函數在內的任何函數都可調用再入函數。與非再入函數的參數傳遞和局部變量的存儲分配方法不同,C51編譯器爲再入函數生成一個模擬棧,通過這個模擬棧來完成參數傳遞和存放局部變量。根據再入函數所採用的編譯模式,模擬棧可以位於片內或片外存儲器空間,SMALL模式下的再入棧位於data空間,COMPACT模式下的再入棧位於pdata空間,LARGE模式下的再入棧位於xdata空間。當程序中包含有多種存儲器模式的再入函數時,C51編譯器爲每種模式單獨建立一個模擬棧並獨立管理各自的棧指針。再入函數的局部變量及參數都被放在再入棧中,從而使再入函數可以進行遞歸調用。而非再入函數的局部變量被放在再入棧之外的暫存區內,如果對非再入函數進行遞歸調用,則上次調用時使用的局部變量數據將被覆蓋。
Keil C51編譯器對於再入函數有如下規定:
·再入函數不能徹底傳遞bit類型的參數,也不能定義局部位變量。再入函數不能操作可位尋址變量。
·與PL/M51兼容的alien函數不能具有reentrant屬性,也不能調用再入函數。
·再入函數可以同時具有其他屬性,如interrupt、using等,還可以聲明存儲器模式SMALL、COMPACT、LARGE。
·在同一個程序中可以定義和使用不同存儲器模式的再入函數,每個再入函數都必須具有合適的函數原型,原型中還應包含該函數的存儲器模式。
·如果函數的返回地址保存在8051單片機的硬件堆棧內,任意其他的PUSH和POP指令都會影響8051硬件堆棧。
·不同存儲器模式下的再入函數具有其自己的模擬再入棧以及再入棧指針。例如,若在同一模塊內定義了SMALL和LARGE模式的再入函數,則C51編譯器會同時生成對應的兩種再入棧及其再入棧指針。
8051單片機的常規棧總是位於內部數據RAM中,而且是“向上生長”型的,而模擬再入棧是“向下生長”型的。如果編譯時採用SMALL模式,常規棧和再入函數的模擬棧將被放在內部RAM中,從而使有限的內部數據存儲器得到充分利用。模擬再入棧及其再入棧指針可以通過配置文件“STARTUP.A51”進行調整。使用再入函數時,應根據需要對該配置文件進行適當修改。
5. C51編譯器的數據調用協議:
1)bit類型的數據:只有一位長度,不允許定義位指針和位數組。Bit對象始終位於8051單片機內部可位尋址的數據存儲器空間(20H~2FH),只要有可能,BL51連接定位器將對位對象進行覆蓋操作。
2)char類型的數據:長度爲一個字節(8位),可存放於8051單片機內部或外部數據存儲器中。
3)int和short類型的數據:長度爲兩個字節(16位),可存放於8051單片機內部或外部數據存儲器中。數據在內存中按高字節地址在前、低字節地址在後的順序存放。例如,int類型數據0x1234,在內存中的存儲格式如下:
地址 |
+0 |
+1 |
內容 |
0x12 |
0x34 |
4)long類型的數據:長度爲4個字節(32位),可存放於8051單片機內部或外部數據存儲器中。數據在內存中按高字節地址在前、低字節地址在後的順序存放。例如,long類型數據0x12345678,在內存中的存儲格式如下:
地址 |
+0 |
+1 |
+2 |
+3 |
內容 |
0x12 |
0x34 |
0x56 |
0x78 |
5)float類型的數據:長度爲4個字節(32位),存放於8051單片機內部或外部數據存儲器中。一個float類型數據的數值範圍是:
在內存中按IEEE-754標準單精度32位浮點數的格式存儲:
地址 |
+0 |
+1 |
+2 |
+3 |
內容 |
SEEEEEEE |
EMMMMMMM |
MMMMMMMM |
MMMMMMMM |
其中,S爲符號位,0表示正,1表示負。E爲用原碼錶示的階碼,佔用8位二進制數,存放在兩個字節中,E的取值範圍是1~254。(實際上,以2爲底的指數要用E的值減去偏移量127,從而實際冪指數的取值範圍爲-126~+127)。M爲尾數的小數部分,用23位二進制表示,存放在3個字節中。尾數的整數部分永遠爲1,因此不予保存,但是隱含存在的。小數點位於隱含的整數位1的後面。
例如,一個值爲-12.5的float類型數據,在內存中的存儲格式如下:
地址 |
+0 |
+1 |
+2 |
+3 |
二進制內容 |
11000001 |
01001000 |
00000000 |
00000000 |
十六進制內容 |
0xC1 |
0x48 |
0x00 |
0x00 |
如果階碼E爲全1(即255),小數部分M爲全0,則根據符號位,分別表示正負無窮大:
正無窮:+INF=7F800000H 負無窮:-INF=FF800000H
階碼E爲全0,小數部分M也全0的浮點數,認爲是0。絕對值最小的正常浮點數爲階碼E爲1,小數部分M爲全0的數:
除了正常浮點數,在以及之間的數爲非正常數。按IEEE754標準,浮點數的數值如果在正常數值之外,即爲溢出錯誤,二進制表示爲:
非正常數: NaN=0FFFFFFFFH
6)指針:C51編譯器支持一般指針和基於存儲器的指針。基於存儲器類型data、idata和pdata的指針具有1個字節的長度,基於存儲器類型xdata和code的指針具有兩字節的長度,一般指針具有3字節的長度。
在一般指針的3個字節中,第一個字節表示存儲器類型,第二、第三字節表示指針的地址偏移量。一般指針在內存中的存儲格式如下:
地址 |
+0 |
+1 |
+2 |
內容 |
存儲器類型 |
高字節地址偏移量 |
低字節地址偏移量 |
在第一個字節中,存儲器類型的編碼如下:
存儲器類型 |
idata/data/bdata |
xdata |
pdata |
code |
編碼值(8051) |
0x00 |
0x01 |
0xFE |
0xFF |
編碼值(8051Mx) |
0x7F |
0x00 |
0x00 |
0x80 |
採用一般指針時,必須使用規定的存儲器類型編碼值。如果使用其他類型的值,將導致不可預測的後果。
例如,將xdata類型的地址0x1234作爲一般指針表示如下:
地址 |
+0 |
+1 |
+2 |
內容 |
0x01 |
0x12 |
0x34 |
6. 絕對地址訪問:
在進行8051 單片機應用系統程序設計時,用戶會關心如何直接操作各個存儲器的地址空間。C51程序經過編譯後,產生的目標代碼具有浮動地址,其絕對地址必須經過BL51連接定位後才能確定。爲了能夠在C51程序中直接對任意指定的存儲器地址進行操作,可以採用擴展關鍵字“_at_”、指針、預定義宏以及連接定位控制命令。
1)採用擴展關鍵字“_at_”或指針定義變量的絕對地址:
在C51源程序中定義變量時,可以利用C51編譯器提供的擴展關鍵字“_at_”來指定變量的存儲器空間絕對地址。一般格式如下:
[存儲器類型] 數據類型 標識符 _at_ 地址常數
其中,存儲器類型爲idata、data、xdata等C51編譯器能夠識別的所有類型,如果省略該選項,則按編譯模式SMALL、COMPACT和LARGE規定的默認存儲器類型確定變量的存儲器空間;數據類型除了可用int、long、float等基本類型外,還可以採用數組、結構等複雜數據類型;標識符爲要定義的變量名;地址常數規定了變量的絕對地址,它必須位於有效存儲器空間之內。下面是採用關鍵字“_at_”進行變量的絕對地址定位的示例:
struct link {
struct link idata * next;
char code * test;
};
idata struct link list _at_ 0x40; //結構變量list定位於idata空間地址0x40
xdata char text[256] _at_ 0xE000; //數組array定位於xdata空間地址0xE000
xdata int il _at_ 0x8000; //int變量il定位於xdata空間地址0x8000
利用擴展關鍵字“_at_”定義的變量稱爲絕對變量,對該變量的操作就是對指定存儲器空間絕對地址的直接操作,因此不能對絕對變量進行初始化。對於函數和位bit類型變量不能採用這種方法進行絕對地址定位。採用關鍵字“_at_”所定義的絕對變量必須是全局變量,在函數內部不能採用“_at_”關鍵字指定局部變量的絕對地址。另外,在XDATA空間定義全局變量的絕對地址時,還可以在變量前面加一個關鍵字“volatile”,這樣對該變量的訪問就不會被C51編譯器優化掉。
利用基於存儲器的指針也可以指定變量的存儲器絕對地址,其方法是先定義一個基於存儲器的指針變量,然後對該變量賦以存儲器絕對地址。下面是一個利用基於存儲器的指針進行變量的絕對地址定位的示例:
char xdata temp _at_ 0x4000; //定義全局變量temp,地址爲XDATA空間0x4000
void main() {
char xdata *xdp; //定義一個指向XDATA存儲器空間的指針
char data *dp; //定義一個指向DATA存儲器空間的指針
xdp=0x2000; //XDATA指針賦值,指向XDATA存儲器地址2000H
temp=*xdp; //讀取XDATA空間地址0x2000的內容送往0x4000單元
*xdp=0xAA; //將數據0xAA送往XDATA空間0x2000地址單元
dp=0x30; //XDATA指針賦值,指向DATA存儲器地址30H
*dp=0xBB; //將數據0xBB送往指定的DATA空間地址
2)採用預定義宏指定變量的絕對地址:
C51編譯器的運行庫中提供瞭如下一套預定義宏:
CBYTE CWORD FARRAY
DBYTE DWORD FCARRAY
PBYTE PWORD FCVAR
XBYTE XWORD FVAR
這些宏定義包含在頭文件“ABSACC.H”中。在C51源程序中可以利用這些宏來指定變量的絕對地址。例如:
#include <ABSACC.H>
char c_var;
int i_var;
XBYTE[0X12]=c_var; //向XDATA存儲器地址0012H寫入數據C_var
i_var=XWORD[0x100]; //從XDATA存儲器地址0100H中讀取數據並賦值給i_var
上面語句中的XWORD[0x100]是對地址“2*0x100”進行操作,將字節地址0x100和0x101的內容取出來並賦值給int型變量i_var。
用戶可以充分利用C51運行庫中提供的預定義宏來進行絕對地址的直接操作。例如,可以採用如下方法定義一個D/A轉換接口地址,每向該地址寫入一個數據,即可完成一次D/A轉換。
#include <ABSACC.H>
#define DAC0832 XBYTE[0x7FFF] //定義DAC0832端口地址
DAC0832=0x80; //啓動一次D/A轉換