keil c 知識總結
阿新 • • 發佈:2019-01-11
Franklin C-51語言程式設計基礎
1.1 Franklin C-51資料型別
Franklin C-51編譯器支援下列資料型別:
資料型別 長度 值域
bit 1 位元組 0 或 1
signed char 1 位元組 -128~+127
unsigned char 1 位元組 0~255
signed int 2 位元組 -32768~+32867
unsigned int 2 位元組 0~65535
signed long 4 位元組 -2147483648~+2147483647
unsigned long 4 位元組 0~4294967295
float 4 位元組 ±1.176E-38~±3.40E+38
指標 1~3 位元組 物件地址
sbit 1 位 0 或 1
sfr 1 位元組 0~255
sfr16 2 位元組 0~65535
編譯的資料型別(如結構)包含上表所列的資料型別。由於8051系列是8位機,因而不存在位元組校準問題。這意味著資料結構成員是順序放
置的。
資料型別的轉換:當計算結果隱含著另外一種資料型別時,資料型別可以自動進行轉換,例如,將一個位變數賦給一個整型變數時,位型
值自動轉換為整型值,有符號變數的符號也能自動進行處理。這些轉換也可以用C語言的標準指令進行人工轉換。
1.2 資料型別的物理結構
1.2.1 bit
“bit”型別只有1位,不允許有位指標和位陣列。位物件始終位於8051 CPU的可定址RAM空間。如果程式控制流允許,L51將位物件交迭。
1.2.2 signed/unsigned char;data/idata/pdata 指標
“char”型別標量和基於存貯器的“data/idata/pdata”指標具有1個位元組長度(8 bits)。
1.2.3 signed/unsigned int/short;xdata/code 指標
“int”和“short”型別標量及指向xdata/code區域的指標具有2位元組長度(16
bits)。
整型值(或偏移)0x1234以下面方式儲存在記憶體中:
地址: +0 +1
內容: 0x12 0x34
1.2.4 signed/unsigned long
“long”型別標量長為4個位元組(32 bits),值0x12345678以下面方式放置:
地址: +0 +1 +2 +3
內容: 0x12 0x34 0x56 0x78
1.2.5 “一般”指標
“一般”指標包括3個位元組:2位元組偏移和1位元組存貯器型別:
地址: +0 +1 +2
內容: 存貯器型別 偏移高位 偏移低位
第一個位元組代表了指標的存貯器型別,存貯器型別編碼如下:
存貯器型別 IDATA XDATA PDATA DATA CODE
值 1 2 3 4 5
使用其它型別值可能導致不可預測的程式動作。
XDATA型別的0x1234地址作為指標表示如下:
地址: +0 +1 +2
內容: 0x02 0x12 0x34
當用常數作指標時,必須注意正確定義存貯器型別和偏移。下例將值0x41寫入絕對地址為0x8000的外部資料存貯器:
#define XBYTE ((char *)0x20000L)
XBYTE[0x8000]=0x41;
上例中用其它常數索引或索引變數也起作用。這樣,各種存貯器型別的絕對地址可以一種非常有效的方式訪問。但有一個例外,即
SFR。
注意:絕對地址定義為“long”型常量,低16位包含偏移,高8位表明了xdata型別。為了表示這種指標,必須用長整數來定義存貯器
型別。
C51編譯器不檢查指標常數,使用者必須選擇有實際意義的值。
1.2.6 float
“float”型別為4個位元組(32位),使用的格式與IEEE-754標準(32位)具有24位精度,尾數的高位始終為“1”,因而不儲存,位的分佈如
下:
l 1位符號
l 8位指數位
l 23位尾數
符號位是最高位,尾數為最低的位,記憶體中按位元組存貯如下:
地址: +0 +1 +2 +3
內容: MMMM MMMM MMMM MMMM E MMM MMMM S EEE EEEE
其中: S:符號位,1=負,0=正
E:指數(在兩個位元組中),偏移為127
M:23位尾數,最高位“1”
浮點值——12.5的十六進位制為0xC1480000,它按下面方式存貯:
地址: +0 +1 +2 +3
內容: 0x00 0x00 0x48 0xc1
8051不包括捕獲浮點錯誤(例外)的中斷向量。使用者軟體因此必須對錯誤條件作出適當反應。下面推薦一種方法(也可以用其它可靠
辦法):“union”用來儲存浮點值,這個“union”必須包括一個“float”和一個“unsigned long”,以根據IEEE對錯誤作出響應。除了通
常浮點值外,IEEE標準可能出錯的條件以下面二進位制值表示,為檢查可能出現的計算錯誤,可在計算後進行檢查。因為當執行一個運算時考慮
了每個運算子的錯誤狀態並且該狀態被送到結果中。
NaN 0xFFFFFFF 不是一個數
+INF 0x7F80000 正無窮(正溢位)
-INF 0XFF80000 負無窮(負溢位)
1.3 C-51 的擴充定義
1.3.1 特殊功能暫存器的宣告
MSC-51 系列包括多種暫存器,其中一些具有特殊功能,如定時器,埠的控制暫存器等,為了能夠直接訪問這些暫存器,C51編譯器提供
了一種定義的自主形式,這是必要的,因為這些定義與標準C語言是不相容的。
為了支援這些特殊功能暫存器(SFR)的宣告,引入了關鍵詞“sfr”,語法如下:
sfr-dcl:sfr sfr_name=int_constant
例:
sfr p0=0x80;
sfr p1=0x90;
必須注意的是“sfr”後不是一個地址而是一個名字。因此上例中名字P0和P1(port0和port1)定義為特殊功能暫存器並被賦予相應
的絕對地址,名字可按意願自由選取,原始檔中不應有先定義的sfr名字。
“=”號後的地址必須是常數,不允許帶有運算子的表示式,這個常數表示式必須在特殊功能暫存器的地址範圍內,位於0X80到0XFF
之間。
8051系列暫存器數量和型別是極其不同的,因此建議將所有特別的“sfr”宣告放入一個頭檔案,標頭檔案包括8051一些系列成員中的
SFR定義。進一步的定義可由使用者用一檔案編輯器產生。
1.3.2 對SFR的16位資料訪問
在新的8051系列產品中,SFR在功能上經常組合為16位的,為了有效的訪問這類SFR,使用定義“sfr16”,當“SFR”的高階直接位於低端
後時,對SFR16位的訪問是可能的。例如8052的定時器2就是這種情況,16位宣告的語法與“sfr”相同,SFR低地址部分必須作為sfr16的地址
:
例:sfr16 T2=0xCC /*Timer2:T2L=0CCH,T2H=0CDH */
sfr16 RCAP2=0xCA /*RCAP2L=0CAH,PCAP2H=0CBH */
本例中,T2(由T2L和T2H組成)和RCAP2(由RCAP2L和RCAP2H組成)被定義為16位SFR,即使在這種情況下,宣告中的名字後仍不是賦值語句,
而是一個SFR地址,高位元組必須直接位於低位元組之後,這種宣告適用於所有新的SFR,但不能用於Timer0和Timer1。
1.3.3 SBIT:特殊功能位宣告
在典型的8051應用問題中,經常需要單獨訪問SFR中的位,C51擴充功能使之成為可能,特殊位,象SFR一樣,不與標準C語言相容,使用保留字
“sbit”可訪問位定址物件。與SFR宣告一樣,用保留字“sbit”宣告某些特殊位接受符號名,“=”後語句將絕對值地址賦給變數名,這種地
址分配有三種方法:
方法1:sfr_name^int_constant
當位元組是特殊功能暫存器的地址可用這個方法。sfr_name必須是已定義的SFR的名字,“^”後的語句定義了基地址上的特殊位的位置,該位置
必須是一個0~7的數。
例: sfr PSW=0xD0;
sfr LE=0xA8;
sbit OV=PSW^2;
sbit CY=PSW^7;
方法2:int_constant^int_constant
這種方法以一整常數作基地址,該值必須在0x80~0xFF之間,並能被8整除,確定位的位置方法同上。
例: sbit OV=0xD0^2;
sbit CV=0xD0^7;
sbit EA=0xA8^7;
方法3: int_constant
這種方法是將位的絕對地址賦給變數,地址必須位於0x80~0xFF之間。
例: sbit OV=0xD2;
sbit CY=0xD7;
sbit EA=0xAF;
特殊功能位代表了一個獨立的宣告類,它不能與其它宣告和位域互換。
1.3.4 BIT:位標量宣告
除了通常的C資料型別外,C51編譯器支援“bit”資料型別,對此有下列擴充與限制:
(1) 函式可包含型別為“bit”的引數,也可將其作為返回值。
bit bfunc(bit b0,bit b1){
/*……*/
return(b1);
}
注:使用禁止中斷(#pragma disable)或包含明確的暫存器組切換(using n)的函式不能返回位值,在這種情況下,編譯器會識別出來併產
生一個錯誤資訊。
(2) 位標量宣告的語法及C宣告的語義
static bit dirction_bit;
extern bit lock_printer_port;
bit display_invers;
(3) 對於位宣告的限制
l 位不能宣告為一個指標(bit *bit_poiter)
l 不存在位陣列(bit b_array[5])
位宣告中允許定義存貯器型別,位都被放入一個位段,它總是在8051內部RAM中,因此存貯器型別限制為DATA或IDATA,宣告為其它存貯器型別
都將導致編譯出錯。
1.3.5 可位定址物件
可位定址物件指可以位元組或位定址的物件,當物件位於MSC-51可定址RAM中時會有這種情況,C51允許帶“bdata”型別的物件放入可位定址存
貯器中。
bdata int ibase; /*位定址指標 int*/
bdata char bary[4]; /*位定址陣列 arrray*/
使用“sbit”宣告可獨立訪問可位定址物件的位:
sbit mybit0=ibase^0;
sbit mybit15=ibase^15;
sbit ary07=bary[0]^7;
sbit ary37=bary[3]^7;
物件“ibase”和“bary”也可位定址:
ary37=0; /*定址“bary[3]”中的位7*/
ibase=-1; /*定址位元組地址*/
mybit15=0; /*定址“ibase”的位15*/
sbit宣告要求基址物件的存貯器型別為“bdata”,否則只有絕對的位宣告方法是合法的。位位置(‘^’操作符號後)的最大值依賴於指定的
基型別,這個值於char/uchar而言是0~7,對於int/uint/short/ushort而言是0~15,對於long/ulong而言是0~31。
在編譯器記憶體貯器型別bdata與data一樣操作,並且只作與可再定位的sbit的運算。注:可位定址的的段長最大不能超過16位元組,可再定位的
sbit宣告自動轉為公共的(PBULIC)以使它們能被其它C模組使用。
模組1: sbit ary37=bary[3]^7;
模組2: extern bit ary37;
sbit宣告也可為結構和函式所用:
union lft {float mf;long ml;} ;
bdata struct bad { char ml; union lft u; }tcp;
sbit tcpf31=tcp.u.ml^31; /*浮點限制*/
sbit tcpml0=tcp.ml^0;
sbit tcmpl7=tcp.ml.^7;
注:位位置的指定不能直接被float型別所用,如果需要這樣做,浮點標量必須與一個長整型標量一起放入一個聯合中並且位位置必須由長整
型標題指定(見上例)。
1.4 存貯器型別
C51編譯器完全支援8051微處理器及其系列的結構,可完全訪問MCS-51硬體系統所有部分。每個變數可準確地賦予不同的存貯器型別(data,
idata,pdata,xdata,code)。訪問內部資料存貯器(idata)要比訪問外部資料存貯器(xdata)相對要快一些,因此,可將經常使用的變
量置於內部資料存貯器中,而將較大及很少使用的資料單元置於外部資料存貯器中。
存貯器型別 描 述
data 直接定址內部資料存貯器,訪問變數速度最快(128bytes)
bdata 可位定址內部資料存貯器,允許位與位元組混合訪問(16 bytes)
iIdata 間接定址內部資料存貯器,可訪問全部地址空間(256bytes)
pPdata 分頁(256bytes)外部資料存貯器,由操作碼MOVX @Ri訪問
xdata 外部資料存貯器(64K),由MOVX @DPTR訪問
code 程式碼資料存貯器(64K),由MOVC @A+DPTR訪問
變數說明舉例:
data char charvar;
char code msg[]=”ENTER PARAMETER:”;
unsigned long xdata array[100];
float idata x,y,z;
unsigned char xdata vector[10][4][4];
sfr p0=0x80;
sbit RI=0x98;
char bdata flags;
sbit flago=flags^0;
如果在變數說明時略去存貯器型別標誌符,編譯器會自動選擇預設的存貯器型別。預設的存貯器型別進一步由控制指令SMALL、COMPACT和
LARGE限制。例如:如果宣告char charvar,則預設的存貯器模式為SMALL,charvar放在data存貯器;如果使用COMPACT模式,則charvar放入
idata存貯區;在使用LARGE模式的情況下,charvar被放入外部存貯區或xdata存貯區。
1.5 存貯器模式
存貯器模式決定了自動變數和預設存貯器型別,引數傳遞區和無明確存貯區型別的說明。在固定的存貯器地址變數引數傳遞是C51的一個標準
特徵,在SMALL模式下引數傳遞是在內部資料存貯區中完成的。LARGRE和COMPACT模式允許引數在外部存貯器中傳遞。C51同時也支援混合模式
,例如在LARGE模式下生成的程式可將一些函式分頁放入SMALL模式中從而加快執行速度。
存貯器模式 描 述
SMALL 引數及區域性變數放入可直接定址的內部暫存器(最大128bytes,預設存貯器型別是DATA)
COMAPCT 引數及區域性變數放入分頁外內部存貯區(最大256bytes,預設存貯器型別是PDATA)
LARGE 引數及區域性變數直接放入外部資料存貯器(最大64K,預設存貯器型別是XDATA)
1.6 指標
Franklin C-51支援“基於存貯器的”和“一般指標”。
1.6.1 基於存貯器的指標
基於存貯器的指標由C原始碼中存貯器型別決定並在編譯時確定,用這種指標可高效訪問物件且只需一個位元組(idata*,data*,pdata*)或2
個位元組(code*,xdata*)。操作較短指標的程式碼被縮短,一般被“內行”編碼;庫呼叫不再必要。
宣告舉例:
char xdata *pt 在XDATA存貯器中宣告一個指向物件型別為“CHAR”的指標。指標預設自身在預設存貯區(決定於編譯模式),長度為2位元組
。(值為0~0XFFFF)
char xdata *data pdx; 除了指標明確位於內部資料存貯器(data)中外,與上例相同。它與編譯模式無關。
data char xdata *pdx; 本例與上例完全相同。存貯器型別定義既可放在宣告的開頭也可直接放在宣告的物件之前。這種形式是為了與早期
C-51編譯器版本相容。
上面例子闡明瞭指標的一般宣告及使用。它們與所有的資料型別和存貯器型別相關。所有用於一般指標的操作同樣可用於基於存貯器的指標。
1.6.2 一般指標
“一般”指標需3個位元組:1個位元組為存貯型別,2個位元組為偏移量。存貯器型別決定了物件所用的8051存貯器空間,偏移量指向實際地址。一
個“一般”指標可訪問任何變數而不管它在8051存貯器空間中的位置。這樣就允許一般性函式,如memcpy將資料從任意一個地址拷貝到另一個
地址空間。
1.6.3 基於存貯器的指標與一般指標的轉換
一個已定位指標可轉換為一個一般指標(3位元組)及副本。這在某些時候是很有用的。例如庫函式包含一個一般指標變數,象printf(),
sprintf(),gets()等包含一個一般指標變數,如同以前的編譯器和庫版本一樣。這些函式因而廣泛適用。
例:extern int printf(void *format,…);
在printf呼叫中,2位元組指標自動轉換為一個3位元組指標,而printf的原型正需要一個一般指標(3位元組)作為其第一參量。
注:如果沒有函式原型,函式呼叫的參量中指標總是轉換為一般指標。如果函式確實需要一個短指標作參量,這會產生錯誤,為了避免在程式
中發生這類錯誤,需要使用標頭檔案,或某些函式宣告函式原型。這將保證讓編譯器轉換為所需型別,否則會產生型別不匹配錯誤。
例:指標定義說明
指 針 說 明 長 度 指 向
float *p; 3 位元組 所有8051存貯器空間中的“float”(一般指標)
char data *dp; 1位元組 “data”存貯器中的“char”
int idata *ip; 1位元組 “idata”中的“int”
long pdata *pp; 1位元組 “pdata”中的“long”
char xdata *xp; 2位元組 “xdata”中的“char”
int code *cp; 2位元組 “code”中的“int”
1.6.4 抽象指標型別
抽象指標型別用來在每個存貯區訪問任意絕對地址,或來產生絕對CALLs。在這個過程中,常數型別或字元型、整型都用抽象型別作了原則性
修改(型別整理)以允許進行絕對訪問或呼叫。
例:
char xdata *px;
char idata *pi;
char code *pc;
char c;
int i;
pc = (void*)main;
i = ((int(code*)(void))0xFF00(); /*LCALL 0FF00H */
c = *((char code*)0x8000); /*char [code[0x8000]]*/
i = *((int code*)0x1200); /*int from code[0x1200]*/
px = *((char xdata *xdata*)0x4000); /*x ptr from xdata[0x4000]*/
px = ((char xdata *xdata*)0x4000)[0]; /*同上*/
px = ((char xdata *xdata *)0x4000)[1] /*x ptr from xdata[0x4002]*/
1.7 暫存器組定義
8051系列的器件包含4個相同的暫存器組,每個暫存器組包括8個暫存器(R0~R7),C51編譯器可使在一函式中決定用哪一暫存器組成為可能
。絕對暫存器的訪問可用AREGS/NOAREGS和REGISTERBANK來控制。
定義一個帶擴充套件性的函式語法如下:
返回型別 函式名([引數])[模式][再入][中斷 n]using n
再入和中斷將在後兩節討論。
例:void rb_function(void) using 3;
“using”不允許用於外部函式,它對函式的目的碼影響如下:
l 函式入口處將當前暫存器儲存入棧;
l 指它的暫存器還會改變;
l 函式退出前暫存器組被恢復。
“using”定義對於返回一個暫存器內的值的函式是無用的。程式設計者必須十分小心以保證任何暫存器切換都只在仔細控制的區域發生。如果不
做到這一點將會產生不正確的函式結果。即使當程式設計者使用同一暫存器組時,帶“using”屬性的函式原則上也不能返回一個位值。
實際產生的程式碼決定於編譯器及不同開關條件,有時用命令產生絕對的暫存器地址,當需要進行這樣的地址計算時,使用REGISTERBANK指令的
影響只是計算Arn暫存器使用的地址,而必進行實際切換。
1.8 中斷服務程式
C51編譯器及其對C語言的擴充允許程式設計者對中斷的所有方面進行控制。這種支援能使系統程式設計者建立高效的中斷服務程式,使用者只需在普通和
高階方式下關心中斷及必要的暫存器組切換操作,C51編譯器將產生最合適的程式碼。
1.8.1 中斷服務程式的定義
使用中斷服務函式的完整語法如下:
返回值 函式名([引數])[模式][再入] interrupt n[using n]
“interrupt”後接一個0~31的常數,不允許使用表示式。
中斷不允許用於外部函式,它對函式目的碼的影響如下:
l 當使用函式時,SFR中的ACC、B、DPH、DPL和PSW(當需要時)入棧;
l 如不使用暫存器組切換,甚至中斷函式所需的所有工作暫存器(Rn)都入棧;
l 函式退出前,所有的暫存器內容出棧;
l 函式由8051控制命令“RETI”終止。
1.8.2 開發中斷過程時的規則
l 不能進行引數傳遞,如果中斷過程包括任何引數宣告,編譯器將產生一個錯誤資訊;
l 無返回值,如果想定義一個返回值將產生錯誤,然而,如果返回整型值編譯器將不產生錯誤資訊,因為整型值是預設值,因而編譯器
不能清楚識別。
l 編譯器會識別對中斷過程的直接呼叫並拒絕它們,在任何情況下不能直接呼叫中斷過程,因為退出該過程是由操作碼RETI完成的。
RETI影響8051晶片的硬體中斷系統,由於硬體上沒有中斷請求存在,因而這個操作碼的結果是不定的並且通常是致命的。由於疏忽,可能用指
針來間接呼叫它,這是值得注意的。
l 編譯器從絕對地址8n+3處產生一箇中斷向量,其中n為中斷號,該向量包括一個到中斷過程的跳轉,向量的產生可由指令NOINTVECTOR
壓縮。因而使用者有能力從獨立的彙編模組中提供中斷向量。
l C51編譯器允許0~31箇中斷,究竟允許哪些中斷依賴於使用的8051系列晶片,編譯器不能檢查。
l 如果中斷程式中有浮點運算,必須保持浮點暫存器狀態,當沒有其它程式執行浮點運算時,可能不儲存,函式“fsave”和
“fprestore”用來儲存浮點狀態。
l 中斷過程呼叫的函式所使用的暫存器必須與中斷過程相同,當沒有使用“using”指令時,編譯器會選擇一個暫存器組作絕對暫存器
訪問,當子程式使用另一個暫存器組時會發生錯誤,使用者必須保證按要求使用相應暫存器組,C編譯器不會對此檢查。
例:
unsigned int interruptent;
unsigned char second;
time() interrupt 1 using 2 /*定時器0中斷服務程式,工作暫存器使用2區*/
{
if(++interruptcnt==4000) {
second++; /*秒計數加一*/
interruptcnt=0; /*清中斷計數*/
}
}
1.9 再入函式
再入函式可被遞迴呼叫,呼叫可發生在任何時候,即使是在中斷過程中。在實時處理的應用問題中常常需要再入函式。
使用關鍵字“reentrant”可有選擇地定義函式有再入能力。在存貯器模式的基礎上為再入函式在內部或外部存貯器中模擬了一個棧區域。由
於MCS-51缺乏合適的定址方法,使用棧結構是相當必要的。因而應儘量少用再入函式。
定義一再入函式的語法如下:
返回值 函式名([引數])[模式]reetrant[interrupt n][using n]
例:
int calc(char i,int b) reentrant {
int x;
x=table[i];
return(x*b);
}
使用再入函式有如下規定:
l 不能傳遞型別為“bit”的引數。也不能宣告一個區域性標量,再入功能不能包括位操作及MCS-51可位定址區域。
l 不能在“alien”函式呼叫再入函式。
l 再入函式可同時有其它屬性,如“using”函式模式和“interrupt”。
l 再入函式不能同時有“alien”屬性,從而遵守PL/M規則。
l 返回地址及可能的PUSH/POP操作存入MCS-51的棧中或被執行(不在再入棧中)。
l 在同一模組中,任意模組的再入函式(small reentrant,lage reentrant,compact reentrant)不能與具有不同模式的再入函式混
合。
再入函式舉例:
/*這個再入函式可以從“main”及中斷程式中呼叫*/
int calc(char i,int b)reentrant {
int x;
x=table[i];
return(x*b);
}
1.10 引數傳遞
通過CPU的暫存器可傳遞至多三個引數。這樣產生與彙編子程式相當的有效引數機制。如果暫存器被佔用,或說明了“#pragma NOREGPARMS”
,引數變數將使用固定的存貯器位置,存貯器模式決定了8051存貯器為引數提供的位置。
表:候選的引數暫存器
引數型別 CHAR,1位元組指標 INT,2位元組指標 LONG,FLOAT 一般指標
一個引數 R7 R6,R7 R4~R7 R1,R2,R3
二個引數 R5 R4,R5 R4~R7 R1,R2,R3
三個引數 R3 R2,R3 … R1,R2,R3
函式的返回值放在CPU固定的暫存器中,列表如下。這樣,與彙編子程式的介面變得非常容易。
表:函式返回值的暫存器用法
返 回 值 寄 存 器 意 義
bit 進位標誌 C
(unsigned) char R7
(unsigned) int R6,R7 高位在R6,低位在R7
(unsigned) long R4~R7 高位在R4,低位在R7
float R4~R7 32位IEEE格式
指 針 R1,R2,R3 型別選擇在R3,高位在R2,低位在R1
1.11 PL/M51介面
Franklin C51利用關鍵字“alien”提供了一個與Intel PL/M-51直接和簡單和介面,關鍵字“alien”在所有存貯器模式下可用於“extern”
和“public”函式。現有的PL/M-51程式利用C語言的強大功能可與Franklin C-51連線起來。
使用關鍵字“alien”,C51可用PL/M-51規定的引數傳遞方式工作。“alien”可用於外部或公共函式,並可用於任一模式,這樣,已有的
PL/M-51程式可加入到C-51中。Alien函式始終包含一個標準的引數數量,因此,C中定義的三點(…)記號不被接受,且會產生一個錯誤資訊
。
例:
extern alien char plm_function(unsigned char,unsigned int);
extern char c_function(unsigned char x,unsigned char y) {
return(x*y);
}
PL/M-51相容函式必須定義以關鍵字“alien”。這樣,PL/M函式的引數傳遞及引數返回規定在C編譯器中才被考慮。
1.12 彙編介面
引數是通過固定的CPU暫存器傳給彙編程式的,當使用“#pragma NOREGPARMS”時,則通過固定的存貯器位置傳遞引數。這樣就給彙編與
Franklin C-51之間提供了一個非常簡潔的介面。返回值在CPU暫存器中。
下例為在彙編中用來編碼的“toupper”函式,引數傳遞發生在暫存器中。
UPPER SEGMENT CODE ;程式程式碼段
PUBLIC _toupper ;入口地址
RESG UPPER ;選擇程式程式碼段
toupper: MOV A,R7 ;char 引數在暫存器R7中
CJNE A,#’a’,UPP1
UPP1: JC UPPERT
CJNE A,#’z’+1,UPP2
UPP2: JNE UPPRET
CLR ACC.5
UPPRET: MOV R7,A ;char 返回值在暫存器R7中
RET ;返回C
1.13 內部函式
Franklin C-51支援下列內部函式。內部函式既是再入的又是有效的。
表:C51的內部函式
函 數 描 述
memcpy,memsset,memchr,memmove,memcmp ANSI的記憶體操作功能
strcmp,strcpy ANSI字串處理功能
_crol_,_irol_,lrol_ 左移字元、整數、長整數
_crolr_,_irolr_,lrolr_ 右移字元、整數、長整數
_nop_ 空操作
_testbit_ 測試並清位(JBC指令)
1.14 程式碼優化
Franklin C51可將即使有經驗的程式設計師編制的程式碼進行優化。使用者可選6個優化級,另外,用OPTIMIZE(SIZE),NOREGPARMS和NOAREGS時會影
響生成程式碼的型別。C51的所有優化如下:
(1) 一般優化:
l 常數折迭:發生在一個表示式或地址計算中的幾個常數值組合為一個常數。
l 跳轉優化:跳轉轉到最終的目標地址,以提高程式效率。
l 死碼消除:不可執行程式碼(死碼)可從程式中去掉。
l 暫存器變數:只要有可能,自動變數和參量放入暫存器中,為這些變數保留的資料存貯器將去除。
l 通過暫存器傳遞引數:暫存器中可傳遞最多三個引數。
l 全域性公共子式消除:相同的子表示式或地址計算(多次發生在同一函式中)將被識別出來,並且只要有可能,將只計算一次。
(2) 基於8051的優化:
l 窺孔(PEEPHOLE)優化:只要能節省存貯空間或執行時間,複雜的運算都將化簡。
l 訪問優化:常數和變數直接包含在操作中。
l 資料覆蓋:函式的資料和位移被標記為OVERLAYABLE,被L51用其它資料和位覆蓋。
l CASE/SWITCH優化:SWITCH/CASE語句優化為一個跳轉或一串跳轉。
(3) 程式碼生成選項:
l OPTMIZE(SIZE):共同的“C”操作被子程式代替:程式碼長被壓縮。
l NOAREGS:不使用絕對暫存器訪問,程式程式碼在這種方式下獨立於暫存器組。
l NOREGPARMS:引數傳遞總是在本資料段完成,程式程式碼與早期C-51版本相容。
1.15 C庫
C-51編譯器包含6個不同的編譯庫,可根據不同函式的需要進行優化,這些庫幾乎支援所有的ANSI函式呼叫。因此,用此標準的C程式可在編譯
和連線後立即執行。
庫 描 述
C51S.LIB SMALL模式,無浮點運算
C51FPS.LIB 浮點數學運算庫(SMALL模式)
C51C.LIB COMPACT模式,無浮點運算
C51FPC.LIB 浮點運算庫(COMPACT模式)
C51L.LIB LARGE模式,無浮點運算
C51FPL.LIB 浮點運算庫(LARGE模式)
C51編譯器包含的庫模組,都有原始碼,對它們可作與硬體相關的修改。使用者改變對於現有硬體輸入和輸出結構的兩個模組,就可修改所有庫
函式,同樣也可以重新很快地構造如“printf”和“puts”函式用LCD顯示。
L51聯結器的檢查從而保證所有模組都用一種模式編譯並自動選擇編譯庫,從而使使用者完全可以不用不同庫的細節。
1.16 配置檔案
C51編譯器可根據不同的硬體環境由4個檔案作出修改。下列配置檔案包括在C-51軟體包中:
STARTUP51.51: C51編譯器的啟動程式,所有的棧指標和存貯器,只要需要,將被初始化。
INIT.A51: 在檔案中已明確初始化了變數作初始化。如果系統裝入“看門狗”,該檔案可包含附加的“看門狗”重新整理。
PUBCHAR.C: 函式“printf”、“puts”等的字元輸出核心程式,該程式可根據使用者硬體加以修改(如LCD顯示)。
GETKEY.C: 函式“getchar”、“scanf”等的字元輸入核心程式,該程式可根據硬體加以修改(如矩陣鍵盤)。
所有的檔案都包含在C執行庫中,因此,不能在連線時指定呼叫。如果使用者改變一個檔案,可將其編譯後與其它目標檔案一起連線,因而不必
改動執行庫。庫中原檔案自動忽略。
例:
L51 DEMO1.OBJ,DEMO2.OBJ,STARTUP.OBJ,PUTCHAR.OBJ
本例將使用者建立的STARTUP.OBJ和PUTCHAR.OBJ連線起來
STARTUP.A51
檔案STARTUP.51開頭包含一些C編譯結構使用的EQU語句。每個EQU語句的功能描述如下:
IDATALEN 宣告系統開始時有多少記憶體需要用0初始化。預設值為80H,因為幾乎每個
8051指令至少包含128位元組內部RAM。對於256位元組內部RAM的8052可使用100H。當用戶程式在開始時需要使用0初始化的記憶體時才有必要作改動
。如果記憶體初始化必須保持對掉電模式系統的完全抑制,IDATALEN應設為0。這種情況下至少得保持所有位於段?C_LIB_DATA和?C_LIB_DBIT
中的變數都置為0。否則有些庫函式不能完全發揮作用,?C_LIB_DATA段的長度因不同應用問題而不同,其當前長度可在MAP檔案中找到。
XDATASTART
XDATALEN 表明了需要以0初始化的PDATA區首址和長度,XDATASTART指明瞭XDATA區首址,XDATALEN表明了需初始化的位元組數。
PDATASTART
PDATALEN 表明了需以0初始化的PDATA區首址及長度,PDATASTART指明瞭首址,XDATALEN指定了長度。
LBPSTACK
LBPSTACKTOP 定義了SMALL模式下建立的再入函式使用的棧區。LBPSTACK表明是否對棧指標(變數?C_LBP)初始化,LBPSTACKTOP指明瞭棧頂
首址。對於具有256位元組內部RAM的8051系統,當存貯區作首址為0XFF的棧時,可不初始化。C51不作棧區是否滿足特定應用的檢查,使用者必須
自己進行測試。
XBPSTACK
XBPSTACKTOP 為在LARGE模式下建立的再入函式定義了棧區,XBPSTACK表明指標(變數?C_XBP)是否初始化,XBPSTACKTOP指定了棧頂地址。
當存貯區作為首址為0Xffff(在XDATA區)的棧時,可不作初始化。同上一樣,C51不作棧檢查,需要使用者自己測試。
PBPSTACK
PBPSTACKTOP 為在COMPACT模式下建立的再入函式定義了棧區,PBPSTACK表明棧指標(變數?C_PBP)是否初始化。PBPSTACKTOP指定了棧頂地
址。當存貯區作為首址為0Xff(在PDATA區)的棧時,可不作初始化。同上一樣,C51不作棧檢查,需要使用者自己測試。
PPAGEENABLE
PPAGE 當在COMPACT模式中用16位定址XDATA存貯區時需要這些指令。對於使用LARGE模式的程式,可用它提高執行速度或減小程式碼長度。
PPAGEENABLE允許8051埠2的初始化,對埠2的定址允許在任意XDATA頁256位元組變數空間的對映。這兩個指令必須和L51的控制指令PDATA一
起使用。PDATA指定了XDATA存貯器中PDATA區的首址。例:在STARTUP.A51中,PPAGEENABLE置為1,PPAGE置為10H。這種情況下PDATA區首址為
1000H(10H頁),而L51必須包含一個值在1000和10FFH之間的控制語句:L51〈輸入模組〉PDATA(1050H)。注:L51和C51都不對PPAGE/PDATA
指令正確性進行檢查,使用者必須保證PPAGE和PDATA包含一個合適的值。
INIT.A51:
檔案INIT.A51包含一個定義了“看門狗”重新整理的巨集。當系統包括“看門狗”以及使用者變數初始化時間比“看門狗”重新整理時間要長時,必須改變
這個巨集。這種情況下,巨集WATCHCOG必須包含“看門狗”重新整理的程式碼。
例: ;Watchdog refresh for 80515 system
WATCHDOG MACRO
SETB WDT
SETB SWDT
ENDM
PUTCHAR.C
檔案PUTCHAR.C包含字元輸出的核心程式,該檔案通過序列口輸出。這種情況下考慮了XON/XOFF協議,字元LF()被轉為字串CR,LF,這在很
多終端中是需要的。使用者可按自己的要求改變putchar()函式。
GETKEY.C
檔案GETKEY.C包含字元輸入的核心程式,該檔案從序列介面讀入一個字元,不作資料轉換,使用者可根據需要修改getkey()函式。
1.17 優化程式
本節包含幾個怎樣提高8051程式效率的註解。
定位變數
經常訪問的資料物件應放入在片內資料RAM中,這可在任一模式(COMPACT/LARGE)下用輸入存貯器型別的方法實現。訪問片內資料RAM要比訪
問外部資料存貯器快得多。片內RAM由暫存器組,位資料區棧和其它由使用者用“data”型別定義的變數共享。由於片內RAM容量的限制(128~
256位元組,由使用的處理器決定),必須權衡利弊以解決訪問效率和這些物件的數量之間的矛盾。
總是使用可能的最小資料型別
8051系列CPU都是8位機,因此,顯然對具有“char”型別的物件的操作比“int”或“long”型別的物件方便得多。建議程式設計者只要
滿足要求,應儘量使用最小資料型別。
C51編譯器直接支援所有的位元組操作,因而如果不是運算子要求,就不作“int”型別的轉換,這可用一個乘積運算來清楚說明,兩個
“char型別”物件的乘積與8051操作碼“MUL AB”剛好相符。如果用整型量完成同樣的運算,則需要呼叫庫函式。
只要有可能,使用“unsigned”資料型別
8051系列CPU並不直接支援有符號數的運算。因而C51編譯器必須產生與之相關的更多的程式碼以解決這個問題。如果使用無符號型別,
1.1 Franklin C-51資料型別
Franklin C-51編譯器支援下列資料型別:
資料型別 長度 值域
bit 1 位元組 0 或 1
signed char 1 位元組 -128~+127
unsigned char 1 位元組 0~255
signed int 2 位元組 -32768~+32867
unsigned int 2 位元組 0~65535
signed long 4 位元組 -2147483648~+2147483647
unsigned long 4 位元組 0~4294967295
float 4 位元組 ±1.176E-38~±3.40E+38
指標 1~3 位元組 物件地址
sbit 1 位 0 或 1
sfr 1 位元組 0~255
sfr16 2 位元組 0~65535
編譯的資料型別(如結構)包含上表所列的資料型別。由於8051系列是8位機,因而不存在位元組校準問題。這意味著資料結構成員是順序放
置的。
資料型別的轉換:當計算結果隱含著另外一種資料型別時,資料型別可以自動進行轉換,例如,將一個位變數賦給一個整型變數時,位型
值自動轉換為整型值,有符號變數的符號也能自動進行處理。這些轉換也可以用C語言的標準指令進行人工轉換。
1.2 資料型別的物理結構
1.2.1 bit
“bit”型別只有1位,不允許有位指標和位陣列。位物件始終位於8051 CPU的可定址RAM空間。如果程式控制流允許,L51將位物件交迭。
1.2.2 signed/unsigned char;data/idata/pdata 指標
“char”型別標量和基於存貯器的“data/idata/pdata”指標具有1個位元組長度(8 bits)。
1.2.3 signed/unsigned int/short;xdata/code 指標
“int”和“short”型別標量及指向xdata/code區域的指標具有2位元組長度(16
bits)。
整型值(或偏移)0x1234以下面方式儲存在記憶體中:
地址: +0 +1
內容: 0x12 0x34
1.2.4 signed/unsigned long
“long”型別標量長為4個位元組(32 bits),值0x12345678以下面方式放置:
地址: +0 +1 +2 +3
內容: 0x12 0x34 0x56 0x78
1.2.5 “一般”指標
“一般”指標包括3個位元組:2位元組偏移和1位元組存貯器型別:
地址: +0 +1 +2
內容: 存貯器型別 偏移高位 偏移低位
第一個位元組代表了指標的存貯器型別,存貯器型別編碼如下:
存貯器型別 IDATA XDATA PDATA DATA CODE
值 1 2 3 4 5
使用其它型別值可能導致不可預測的程式動作。
XDATA型別的0x1234地址作為指標表示如下:
地址: +0 +1 +2
內容: 0x02 0x12 0x34
當用常數作指標時,必須注意正確定義存貯器型別和偏移。下例將值0x41寫入絕對地址為0x8000的外部資料存貯器:
#define XBYTE ((char *)0x20000L)
XBYTE[0x8000]=0x41;
上例中用其它常數索引或索引變數也起作用。這樣,各種存貯器型別的絕對地址可以一種非常有效的方式訪問。但有一個例外,即
SFR。
注意:絕對地址定義為“long”型常量,低16位包含偏移,高8位表明了xdata型別。為了表示這種指標,必須用長整數來定義存貯器
型別。
C51編譯器不檢查指標常數,使用者必須選擇有實際意義的值。
1.2.6 float
“float”型別為4個位元組(32位),使用的格式與IEEE-754標準(32位)具有24位精度,尾數的高位始終為“1”,因而不儲存,位的分佈如
下:
l 1位符號
l 8位指數位
l 23位尾數
符號位是最高位,尾數為最低的位,記憶體中按位元組存貯如下:
地址: +0 +1 +2 +3
內容: MMMM MMMM MMMM MMMM E MMM MMMM S EEE EEEE
其中: S:符號位,1=負,0=正
E:指數(在兩個位元組中),偏移為127
M:23位尾數,最高位“1”
浮點值——12.5的十六進位制為0xC1480000,它按下面方式存貯:
地址: +0 +1 +2 +3
內容: 0x00 0x00 0x48 0xc1
8051不包括捕獲浮點錯誤(例外)的中斷向量。使用者軟體因此必須對錯誤條件作出適當反應。下面推薦一種方法(也可以用其它可靠
辦法):“union”用來儲存浮點值,這個“union”必須包括一個“float”和一個“unsigned long”,以根據IEEE對錯誤作出響應。除了通
常浮點值外,IEEE標準可能出錯的條件以下面二進位制值表示,為檢查可能出現的計算錯誤,可在計算後進行檢查。因為當執行一個運算時考慮
了每個運算子的錯誤狀態並且該狀態被送到結果中。
NaN 0xFFFFFFF 不是一個數
+INF 0x7F80000 正無窮(正溢位)
-INF 0XFF80000 負無窮(負溢位)
1.3 C-51 的擴充定義
1.3.1 特殊功能暫存器的宣告
MSC-51 系列包括多種暫存器,其中一些具有特殊功能,如定時器,埠的控制暫存器等,為了能夠直接訪問這些暫存器,C51編譯器提供
了一種定義的自主形式,這是必要的,因為這些定義與標準C語言是不相容的。
為了支援這些特殊功能暫存器(SFR)的宣告,引入了關鍵詞“sfr”,語法如下:
sfr-dcl:sfr sfr_name=int_constant
例:
sfr p0=0x80;
sfr p1=0x90;
必須注意的是“sfr”後不是一個地址而是一個名字。因此上例中名字P0和P1(port0和port1)定義為特殊功能暫存器並被賦予相應
的絕對地址,名字可按意願自由選取,原始檔中不應有先定義的sfr名字。
“=”號後的地址必須是常數,不允許帶有運算子的表示式,這個常數表示式必須在特殊功能暫存器的地址範圍內,位於0X80到0XFF
之間。
8051系列暫存器數量和型別是極其不同的,因此建議將所有特別的“sfr”宣告放入一個頭檔案,標頭檔案包括8051一些系列成員中的
SFR定義。進一步的定義可由使用者用一檔案編輯器產生。
1.3.2 對SFR的16位資料訪問
在新的8051系列產品中,SFR在功能上經常組合為16位的,為了有效的訪問這類SFR,使用定義“sfr16”,當“SFR”的高階直接位於低端
後時,對SFR16位的訪問是可能的。例如8052的定時器2就是這種情況,16位宣告的語法與“sfr”相同,SFR低地址部分必須作為sfr16的地址
:
例:sfr16 T2=0xCC /*Timer2:T2L=0CCH,T2H=0CDH */
sfr16 RCAP2=0xCA /*RCAP2L=0CAH,PCAP2H=0CBH */
本例中,T2(由T2L和T2H組成)和RCAP2(由RCAP2L和RCAP2H組成)被定義為16位SFR,即使在這種情況下,宣告中的名字後仍不是賦值語句,
而是一個SFR地址,高位元組必須直接位於低位元組之後,這種宣告適用於所有新的SFR,但不能用於Timer0和Timer1。
1.3.3 SBIT:特殊功能位宣告
在典型的8051應用問題中,經常需要單獨訪問SFR中的位,C51擴充功能使之成為可能,特殊位,象SFR一樣,不與標準C語言相容,使用保留字
“sbit”可訪問位定址物件。與SFR宣告一樣,用保留字“sbit”宣告某些特殊位接受符號名,“=”後語句將絕對值地址賦給變數名,這種地
址分配有三種方法:
方法1:sfr_name^int_constant
當位元組是特殊功能暫存器的地址可用這個方法。sfr_name必須是已定義的SFR的名字,“^”後的語句定義了基地址上的特殊位的位置,該位置
必須是一個0~7的數。
例: sfr PSW=0xD0;
sfr LE=0xA8;
sbit OV=PSW^2;
sbit CY=PSW^7;
方法2:int_constant^int_constant
這種方法以一整常數作基地址,該值必須在0x80~0xFF之間,並能被8整除,確定位的位置方法同上。
例: sbit OV=0xD0^2;
sbit CV=0xD0^7;
sbit EA=0xA8^7;
方法3: int_constant
這種方法是將位的絕對地址賦給變數,地址必須位於0x80~0xFF之間。
例: sbit OV=0xD2;
sbit CY=0xD7;
sbit EA=0xAF;
特殊功能位代表了一個獨立的宣告類,它不能與其它宣告和位域互換。
1.3.4 BIT:位標量宣告
除了通常的C資料型別外,C51編譯器支援“bit”資料型別,對此有下列擴充與限制:
(1) 函式可包含型別為“bit”的引數,也可將其作為返回值。
bit bfunc(bit b0,bit b1){
/*……*/
return(b1);
}
注:使用禁止中斷(#pragma disable)或包含明確的暫存器組切換(using n)的函式不能返回位值,在這種情況下,編譯器會識別出來併產
生一個錯誤資訊。
(2) 位標量宣告的語法及C宣告的語義
static bit dirction_bit;
extern bit lock_printer_port;
bit display_invers;
(3) 對於位宣告的限制
l 位不能宣告為一個指標(bit *bit_poiter)
l 不存在位陣列(bit b_array[5])
位宣告中允許定義存貯器型別,位都被放入一個位段,它總是在8051內部RAM中,因此存貯器型別限制為DATA或IDATA,宣告為其它存貯器型別
都將導致編譯出錯。
1.3.5 可位定址物件
可位定址物件指可以位元組或位定址的物件,當物件位於MSC-51可定址RAM中時會有這種情況,C51允許帶“bdata”型別的物件放入可位定址存
貯器中。
bdata int ibase; /*位定址指標 int*/
bdata char bary[4]; /*位定址陣列 arrray*/
使用“sbit”宣告可獨立訪問可位定址物件的位:
sbit mybit0=ibase^0;
sbit mybit15=ibase^15;
sbit ary07=bary[0]^7;
sbit ary37=bary[3]^7;
物件“ibase”和“bary”也可位定址:
ary37=0; /*定址“bary[3]”中的位7*/
ibase=-1; /*定址位元組地址*/
mybit15=0; /*定址“ibase”的位15*/
sbit宣告要求基址物件的存貯器型別為“bdata”,否則只有絕對的位宣告方法是合法的。位位置(‘^’操作符號後)的最大值依賴於指定的
基型別,這個值於char/uchar而言是0~7,對於int/uint/short/ushort而言是0~15,對於long/ulong而言是0~31。
在編譯器記憶體貯器型別bdata與data一樣操作,並且只作與可再定位的sbit的運算。注:可位定址的的段長最大不能超過16位元組,可再定位的
sbit宣告自動轉為公共的(PBULIC)以使它們能被其它C模組使用。
模組1: sbit ary37=bary[3]^7;
模組2: extern bit ary37;
sbit宣告也可為結構和函式所用:
union lft {float mf;long ml;} ;
bdata struct bad { char ml; union lft u; }tcp;
sbit tcpf31=tcp.u.ml^31; /*浮點限制*/
sbit tcpml0=tcp.ml^0;
sbit tcmpl7=tcp.ml.^7;
注:位位置的指定不能直接被float型別所用,如果需要這樣做,浮點標量必須與一個長整型標量一起放入一個聯合中並且位位置必須由長整
型標題指定(見上例)。
1.4 存貯器型別
C51編譯器完全支援8051微處理器及其系列的結構,可完全訪問MCS-51硬體系統所有部分。每個變數可準確地賦予不同的存貯器型別(data,
idata,pdata,xdata,code)。訪問內部資料存貯器(idata)要比訪問外部資料存貯器(xdata)相對要快一些,因此,可將經常使用的變
量置於內部資料存貯器中,而將較大及很少使用的資料單元置於外部資料存貯器中。
存貯器型別 描 述
data 直接定址內部資料存貯器,訪問變數速度最快(128bytes)
bdata 可位定址內部資料存貯器,允許位與位元組混合訪問(16 bytes)
iIdata 間接定址內部資料存貯器,可訪問全部地址空間(256bytes)
pPdata 分頁(256bytes)外部資料存貯器,由操作碼MOVX @Ri訪問
xdata 外部資料存貯器(64K),由MOVX @DPTR訪問
code 程式碼資料存貯器(64K),由MOVC @A+DPTR訪問
變數說明舉例:
data char charvar;
char code msg[]=”ENTER PARAMETER:”;
unsigned long xdata array[100];
float idata x,y,z;
unsigned char xdata vector[10][4][4];
sfr p0=0x80;
sbit RI=0x98;
char bdata flags;
sbit flago=flags^0;
如果在變數說明時略去存貯器型別標誌符,編譯器會自動選擇預設的存貯器型別。預設的存貯器型別進一步由控制指令SMALL、COMPACT和
LARGE限制。例如:如果宣告char charvar,則預設的存貯器模式為SMALL,charvar放在data存貯器;如果使用COMPACT模式,則charvar放入
idata存貯區;在使用LARGE模式的情況下,charvar被放入外部存貯區或xdata存貯區。
1.5 存貯器模式
存貯器模式決定了自動變數和預設存貯器型別,引數傳遞區和無明確存貯區型別的說明。在固定的存貯器地址變數引數傳遞是C51的一個標準
特徵,在SMALL模式下引數傳遞是在內部資料存貯區中完成的。LARGRE和COMPACT模式允許引數在外部存貯器中傳遞。C51同時也支援混合模式
,例如在LARGE模式下生成的程式可將一些函式分頁放入SMALL模式中從而加快執行速度。
存貯器模式 描 述
SMALL 引數及區域性變數放入可直接定址的內部暫存器(最大128bytes,預設存貯器型別是DATA)
COMAPCT 引數及區域性變數放入分頁外內部存貯區(最大256bytes,預設存貯器型別是PDATA)
LARGE 引數及區域性變數直接放入外部資料存貯器(最大64K,預設存貯器型別是XDATA)
1.6 指標
Franklin C-51支援“基於存貯器的”和“一般指標”。
1.6.1 基於存貯器的指標
基於存貯器的指標由C原始碼中存貯器型別決定並在編譯時確定,用這種指標可高效訪問物件且只需一個位元組(idata*,data*,pdata*)或2
個位元組(code*,xdata*)。操作較短指標的程式碼被縮短,一般被“內行”編碼;庫呼叫不再必要。
宣告舉例:
char xdata *pt 在XDATA存貯器中宣告一個指向物件型別為“CHAR”的指標。指標預設自身在預設存貯區(決定於編譯模式),長度為2位元組
。(值為0~0XFFFF)
char xdata *data pdx; 除了指標明確位於內部資料存貯器(data)中外,與上例相同。它與編譯模式無關。
data char xdata *pdx; 本例與上例完全相同。存貯器型別定義既可放在宣告的開頭也可直接放在宣告的物件之前。這種形式是為了與早期
C-51編譯器版本相容。
上面例子闡明瞭指標的一般宣告及使用。它們與所有的資料型別和存貯器型別相關。所有用於一般指標的操作同樣可用於基於存貯器的指標。
1.6.2 一般指標
“一般”指標需3個位元組:1個位元組為存貯型別,2個位元組為偏移量。存貯器型別決定了物件所用的8051存貯器空間,偏移量指向實際地址。一
個“一般”指標可訪問任何變數而不管它在8051存貯器空間中的位置。這樣就允許一般性函式,如memcpy將資料從任意一個地址拷貝到另一個
地址空間。
1.6.3 基於存貯器的指標與一般指標的轉換
一個已定位指標可轉換為一個一般指標(3位元組)及副本。這在某些時候是很有用的。例如庫函式包含一個一般指標變數,象printf(),
sprintf(),gets()等包含一個一般指標變數,如同以前的編譯器和庫版本一樣。這些函式因而廣泛適用。
例:extern int printf(void *format,…);
在printf呼叫中,2位元組指標自動轉換為一個3位元組指標,而printf的原型正需要一個一般指標(3位元組)作為其第一參量。
注:如果沒有函式原型,函式呼叫的參量中指標總是轉換為一般指標。如果函式確實需要一個短指標作參量,這會產生錯誤,為了避免在程式
中發生這類錯誤,需要使用標頭檔案,或某些函式宣告函式原型。這將保證讓編譯器轉換為所需型別,否則會產生型別不匹配錯誤。
例:指標定義說明
指 針 說 明 長 度 指 向
float *p; 3 位元組 所有8051存貯器空間中的“float”(一般指標)
char data *dp; 1位元組 “data”存貯器中的“char”
int idata *ip; 1位元組 “idata”中的“int”
long pdata *pp; 1位元組 “pdata”中的“long”
char xdata *xp; 2位元組 “xdata”中的“char”
int code *cp; 2位元組 “code”中的“int”
1.6.4 抽象指標型別
抽象指標型別用來在每個存貯區訪問任意絕對地址,或來產生絕對CALLs。在這個過程中,常數型別或字元型、整型都用抽象型別作了原則性
修改(型別整理)以允許進行絕對訪問或呼叫。
例:
char xdata *px;
char idata *pi;
char code *pc;
char c;
int i;
pc = (void*)main;
i = ((int(code*)(void))0xFF00(); /*LCALL 0FF00H */
c = *((char code*)0x8000); /*char [code[0x8000]]*/
i = *((int code*)0x1200); /*int from code[0x1200]*/
px = *((char xdata *xdata*)0x4000); /*x ptr from xdata[0x4000]*/
px = ((char xdata *xdata*)0x4000)[0]; /*同上*/
px = ((char xdata *xdata *)0x4000)[1] /*x ptr from xdata[0x4002]*/
1.7 暫存器組定義
8051系列的器件包含4個相同的暫存器組,每個暫存器組包括8個暫存器(R0~R7),C51編譯器可使在一函式中決定用哪一暫存器組成為可能
。絕對暫存器的訪問可用AREGS/NOAREGS和REGISTERBANK來控制。
定義一個帶擴充套件性的函式語法如下:
返回型別 函式名([引數])[模式][再入][中斷 n]using n
再入和中斷將在後兩節討論。
例:void rb_function(void) using 3;
“using”不允許用於外部函式,它對函式的目的碼影響如下:
l 函式入口處將當前暫存器儲存入棧;
l 指它的暫存器還會改變;
l 函式退出前暫存器組被恢復。
“using”定義對於返回一個暫存器內的值的函式是無用的。程式設計者必須十分小心以保證任何暫存器切換都只在仔細控制的區域發生。如果不
做到這一點將會產生不正確的函式結果。即使當程式設計者使用同一暫存器組時,帶“using”屬性的函式原則上也不能返回一個位值。
實際產生的程式碼決定於編譯器及不同開關條件,有時用命令產生絕對的暫存器地址,當需要進行這樣的地址計算時,使用REGISTERBANK指令的
影響只是計算Arn暫存器使用的地址,而必進行實際切換。
1.8 中斷服務程式
C51編譯器及其對C語言的擴充允許程式設計者對中斷的所有方面進行控制。這種支援能使系統程式設計者建立高效的中斷服務程式,使用者只需在普通和
高階方式下關心中斷及必要的暫存器組切換操作,C51編譯器將產生最合適的程式碼。
1.8.1 中斷服務程式的定義
使用中斷服務函式的完整語法如下:
返回值 函式名([引數])[模式][再入] interrupt n[using n]
“interrupt”後接一個0~31的常數,不允許使用表示式。
中斷不允許用於外部函式,它對函式目的碼的影響如下:
l 當使用函式時,SFR中的ACC、B、DPH、DPL和PSW(當需要時)入棧;
l 如不使用暫存器組切換,甚至中斷函式所需的所有工作暫存器(Rn)都入棧;
l 函式退出前,所有的暫存器內容出棧;
l 函式由8051控制命令“RETI”終止。
1.8.2 開發中斷過程時的規則
l 不能進行引數傳遞,如果中斷過程包括任何引數宣告,編譯器將產生一個錯誤資訊;
l 無返回值,如果想定義一個返回值將產生錯誤,然而,如果返回整型值編譯器將不產生錯誤資訊,因為整型值是預設值,因而編譯器
不能清楚識別。
l 編譯器會識別對中斷過程的直接呼叫並拒絕它們,在任何情況下不能直接呼叫中斷過程,因為退出該過程是由操作碼RETI完成的。
RETI影響8051晶片的硬體中斷系統,由於硬體上沒有中斷請求存在,因而這個操作碼的結果是不定的並且通常是致命的。由於疏忽,可能用指
針來間接呼叫它,這是值得注意的。
l 編譯器從絕對地址8n+3處產生一箇中斷向量,其中n為中斷號,該向量包括一個到中斷過程的跳轉,向量的產生可由指令NOINTVECTOR
壓縮。因而使用者有能力從獨立的彙編模組中提供中斷向量。
l C51編譯器允許0~31箇中斷,究竟允許哪些中斷依賴於使用的8051系列晶片,編譯器不能檢查。
l 如果中斷程式中有浮點運算,必須保持浮點暫存器狀態,當沒有其它程式執行浮點運算時,可能不儲存,函式“fsave”和
“fprestore”用來儲存浮點狀態。
l 中斷過程呼叫的函式所使用的暫存器必須與中斷過程相同,當沒有使用“using”指令時,編譯器會選擇一個暫存器組作絕對暫存器
訪問,當子程式使用另一個暫存器組時會發生錯誤,使用者必須保證按要求使用相應暫存器組,C編譯器不會對此檢查。
例:
unsigned int interruptent;
unsigned char second;
time() interrupt 1 using 2 /*定時器0中斷服務程式,工作暫存器使用2區*/
{
if(++interruptcnt==4000) {
second++; /*秒計數加一*/
interruptcnt=0; /*清中斷計數*/
}
}
1.9 再入函式
再入函式可被遞迴呼叫,呼叫可發生在任何時候,即使是在中斷過程中。在實時處理的應用問題中常常需要再入函式。
使用關鍵字“reentrant”可有選擇地定義函式有再入能力。在存貯器模式的基礎上為再入函式在內部或外部存貯器中模擬了一個棧區域。由
於MCS-51缺乏合適的定址方法,使用棧結構是相當必要的。因而應儘量少用再入函式。
定義一再入函式的語法如下:
返回值 函式名([引數])[模式]reetrant[interrupt n][using n]
例:
int calc(char i,int b) reentrant {
int x;
x=table[i];
return(x*b);
}
使用再入函式有如下規定:
l 不能傳遞型別為“bit”的引數。也不能宣告一個區域性標量,再入功能不能包括位操作及MCS-51可位定址區域。
l 不能在“alien”函式呼叫再入函式。
l 再入函式可同時有其它屬性,如“using”函式模式和“interrupt”。
l 再入函式不能同時有“alien”屬性,從而遵守PL/M規則。
l 返回地址及可能的PUSH/POP操作存入MCS-51的棧中或被執行(不在再入棧中)。
l 在同一模組中,任意模組的再入函式(small reentrant,lage reentrant,compact reentrant)不能與具有不同模式的再入函式混
合。
再入函式舉例:
/*這個再入函式可以從“main”及中斷程式中呼叫*/
int calc(char i,int b)reentrant {
int x;
x=table[i];
return(x*b);
}
1.10 引數傳遞
通過CPU的暫存器可傳遞至多三個引數。這樣產生與彙編子程式相當的有效引數機制。如果暫存器被佔用,或說明了“#pragma NOREGPARMS”
,引數變數將使用固定的存貯器位置,存貯器模式決定了8051存貯器為引數提供的位置。
表:候選的引數暫存器
引數型別 CHAR,1位元組指標 INT,2位元組指標 LONG,FLOAT 一般指標
一個引數 R7 R6,R7 R4~R7 R1,R2,R3
二個引數 R5 R4,R5 R4~R7 R1,R2,R3
三個引數 R3 R2,R3 … R1,R2,R3
函式的返回值放在CPU固定的暫存器中,列表如下。這樣,與彙編子程式的介面變得非常容易。
表:函式返回值的暫存器用法
返 回 值 寄 存 器 意 義
bit 進位標誌 C
(unsigned) char R7
(unsigned) int R6,R7 高位在R6,低位在R7
(unsigned) long R4~R7 高位在R4,低位在R7
float R4~R7 32位IEEE格式
指 針 R1,R2,R3 型別選擇在R3,高位在R2,低位在R1
1.11 PL/M51介面
Franklin C51利用關鍵字“alien”提供了一個與Intel PL/M-51直接和簡單和介面,關鍵字“alien”在所有存貯器模式下可用於“extern”
和“public”函式。現有的PL/M-51程式利用C語言的強大功能可與Franklin C-51連線起來。
使用關鍵字“alien”,C51可用PL/M-51規定的引數傳遞方式工作。“alien”可用於外部或公共函式,並可用於任一模式,這樣,已有的
PL/M-51程式可加入到C-51中。Alien函式始終包含一個標準的引數數量,因此,C中定義的三點(…)記號不被接受,且會產生一個錯誤資訊
。
例:
extern alien char plm_function(unsigned char,unsigned int);
extern char c_function(unsigned char x,unsigned char y) {
return(x*y);
}
PL/M-51相容函式必須定義以關鍵字“alien”。這樣,PL/M函式的引數傳遞及引數返回規定在C編譯器中才被考慮。
1.12 彙編介面
引數是通過固定的CPU暫存器傳給彙編程式的,當使用“#pragma NOREGPARMS”時,則通過固定的存貯器位置傳遞引數。這樣就給彙編與
Franklin C-51之間提供了一個非常簡潔的介面。返回值在CPU暫存器中。
下例為在彙編中用來編碼的“toupper”函式,引數傳遞發生在暫存器中。
UPPER SEGMENT CODE ;程式程式碼段
PUBLIC _toupper ;入口地址
RESG UPPER ;選擇程式程式碼段
toupper: MOV A,R7 ;char 引數在暫存器R7中
CJNE A,#’a’,UPP1
UPP1: JC UPPERT
CJNE A,#’z’+1,UPP2
UPP2: JNE UPPRET
CLR ACC.5
UPPRET: MOV R7,A ;char 返回值在暫存器R7中
RET ;返回C
1.13 內部函式
Franklin C-51支援下列內部函式。內部函式既是再入的又是有效的。
表:C51的內部函式
函 數 描 述
memcpy,memsset,memchr,memmove,memcmp ANSI的記憶體操作功能
strcmp,strcpy ANSI字串處理功能
_crol_,_irol_,lrol_ 左移字元、整數、長整數
_crolr_,_irolr_,lrolr_ 右移字元、整數、長整數
_nop_ 空操作
_testbit_ 測試並清位(JBC指令)
1.14 程式碼優化
Franklin C51可將即使有經驗的程式設計師編制的程式碼進行優化。使用者可選6個優化級,另外,用OPTIMIZE(SIZE),NOREGPARMS和NOAREGS時會影
響生成程式碼的型別。C51的所有優化如下:
(1) 一般優化:
l 常數折迭:發生在一個表示式或地址計算中的幾個常數值組合為一個常數。
l 跳轉優化:跳轉轉到最終的目標地址,以提高程式效率。
l 死碼消除:不可執行程式碼(死碼)可從程式中去掉。
l 暫存器變數:只要有可能,自動變數和參量放入暫存器中,為這些變數保留的資料存貯器將去除。
l 通過暫存器傳遞引數:暫存器中可傳遞最多三個引數。
l 全域性公共子式消除:相同的子表示式或地址計算(多次發生在同一函式中)將被識別出來,並且只要有可能,將只計算一次。
(2) 基於8051的優化:
l 窺孔(PEEPHOLE)優化:只要能節省存貯空間或執行時間,複雜的運算都將化簡。
l 訪問優化:常數和變數直接包含在操作中。
l 資料覆蓋:函式的資料和位移被標記為OVERLAYABLE,被L51用其它資料和位覆蓋。
l CASE/SWITCH優化:SWITCH/CASE語句優化為一個跳轉或一串跳轉。
(3) 程式碼生成選項:
l OPTMIZE(SIZE):共同的“C”操作被子程式代替:程式碼長被壓縮。
l NOAREGS:不使用絕對暫存器訪問,程式程式碼在這種方式下獨立於暫存器組。
l NOREGPARMS:引數傳遞總是在本資料段完成,程式程式碼與早期C-51版本相容。
1.15 C庫
C-51編譯器包含6個不同的編譯庫,可根據不同函式的需要進行優化,這些庫幾乎支援所有的ANSI函式呼叫。因此,用此標準的C程式可在編譯
和連線後立即執行。
庫 描 述
C51S.LIB SMALL模式,無浮點運算
C51FPS.LIB 浮點數學運算庫(SMALL模式)
C51C.LIB COMPACT模式,無浮點運算
C51FPC.LIB 浮點運算庫(COMPACT模式)
C51L.LIB LARGE模式,無浮點運算
C51FPL.LIB 浮點運算庫(LARGE模式)
C51編譯器包含的庫模組,都有原始碼,對它們可作與硬體相關的修改。使用者改變對於現有硬體輸入和輸出結構的兩個模組,就可修改所有庫
函式,同樣也可以重新很快地構造如“printf”和“puts”函式用LCD顯示。
L51聯結器的檢查從而保證所有模組都用一種模式編譯並自動選擇編譯庫,從而使使用者完全可以不用不同庫的細節。
1.16 配置檔案
C51編譯器可根據不同的硬體環境由4個檔案作出修改。下列配置檔案包括在C-51軟體包中:
STARTUP51.51: C51編譯器的啟動程式,所有的棧指標和存貯器,只要需要,將被初始化。
INIT.A51: 在檔案中已明確初始化了變數作初始化。如果系統裝入“看門狗”,該檔案可包含附加的“看門狗”重新整理。
PUBCHAR.C: 函式“printf”、“puts”等的字元輸出核心程式,該程式可根據使用者硬體加以修改(如LCD顯示)。
GETKEY.C: 函式“getchar”、“scanf”等的字元輸入核心程式,該程式可根據硬體加以修改(如矩陣鍵盤)。
所有的檔案都包含在C執行庫中,因此,不能在連線時指定呼叫。如果使用者改變一個檔案,可將其編譯後與其它目標檔案一起連線,因而不必
改動執行庫。庫中原檔案自動忽略。
例:
L51 DEMO1.OBJ,DEMO2.OBJ,STARTUP.OBJ,PUTCHAR.OBJ
本例將使用者建立的STARTUP.OBJ和PUTCHAR.OBJ連線起來
STARTUP.A51
檔案STARTUP.51開頭包含一些C編譯結構使用的EQU語句。每個EQU語句的功能描述如下:
IDATALEN 宣告系統開始時有多少記憶體需要用0初始化。預設值為80H,因為幾乎每個
8051指令至少包含128位元組內部RAM。對於256位元組內部RAM的8052可使用100H。當用戶程式在開始時需要使用0初始化的記憶體時才有必要作改動
。如果記憶體初始化必須保持對掉電模式系統的完全抑制,IDATALEN應設為0。這種情況下至少得保持所有位於段?C_LIB_DATA和?C_LIB_DBIT
中的變數都置為0。否則有些庫函式不能完全發揮作用,?C_LIB_DATA段的長度因不同應用問題而不同,其當前長度可在MAP檔案中找到。
XDATASTART
XDATALEN 表明了需要以0初始化的PDATA區首址和長度,XDATASTART指明瞭XDATA區首址,XDATALEN表明了需初始化的位元組數。
PDATASTART
PDATALEN 表明了需以0初始化的PDATA區首址及長度,PDATASTART指明瞭首址,XDATALEN指定了長度。
LBPSTACK
LBPSTACKTOP 定義了SMALL模式下建立的再入函式使用的棧區。LBPSTACK表明是否對棧指標(變數?C_LBP)初始化,LBPSTACKTOP指明瞭棧頂
首址。對於具有256位元組內部RAM的8051系統,當存貯區作首址為0XFF的棧時,可不初始化。C51不作棧區是否滿足特定應用的檢查,使用者必須
自己進行測試。
XBPSTACK
XBPSTACKTOP 為在LARGE模式下建立的再入函式定義了棧區,XBPSTACK表明指標(變數?C_XBP)是否初始化,XBPSTACKTOP指定了棧頂地址。
當存貯區作為首址為0Xffff(在XDATA區)的棧時,可不作初始化。同上一樣,C51不作棧檢查,需要使用者自己測試。
PBPSTACK
PBPSTACKTOP 為在COMPACT模式下建立的再入函式定義了棧區,PBPSTACK表明棧指標(變數?C_PBP)是否初始化。PBPSTACKTOP指定了棧頂地
址。當存貯區作為首址為0Xff(在PDATA區)的棧時,可不作初始化。同上一樣,C51不作棧檢查,需要使用者自己測試。
PPAGEENABLE
PPAGE 當在COMPACT模式中用16位定址XDATA存貯區時需要這些指令。對於使用LARGE模式的程式,可用它提高執行速度或減小程式碼長度。
PPAGEENABLE允許8051埠2的初始化,對埠2的定址允許在任意XDATA頁256位元組變數空間的對映。這兩個指令必須和L51的控制指令PDATA一
起使用。PDATA指定了XDATA存貯器中PDATA區的首址。例:在STARTUP.A51中,PPAGEENABLE置為1,PPAGE置為10H。這種情況下PDATA區首址為
1000H(10H頁),而L51必須包含一個值在1000和10FFH之間的控制語句:L51〈輸入模組〉PDATA(1050H)。注:L51和C51都不對PPAGE/PDATA
指令正確性進行檢查,使用者必須保證PPAGE和PDATA包含一個合適的值。
INIT.A51:
檔案INIT.A51包含一個定義了“看門狗”重新整理的巨集。當系統包括“看門狗”以及使用者變數初始化時間比“看門狗”重新整理時間要長時,必須改變
這個巨集。這種情況下,巨集WATCHCOG必須包含“看門狗”重新整理的程式碼。
例: ;Watchdog refresh for 80515 system
WATCHDOG MACRO
SETB WDT
SETB SWDT
ENDM
PUTCHAR.C
檔案PUTCHAR.C包含字元輸出的核心程式,該檔案通過序列口輸出。這種情況下考慮了XON/XOFF協議,字元LF()被轉為字串CR,LF,這在很
多終端中是需要的。使用者可按自己的要求改變putchar()函式。
GETKEY.C
檔案GETKEY.C包含字元輸入的核心程式,該檔案從序列介面讀入一個字元,不作資料轉換,使用者可根據需要修改getkey()函式。
1.17 優化程式
本節包含幾個怎樣提高8051程式效率的註解。
定位變數
經常訪問的資料物件應放入在片內資料RAM中,這可在任一模式(COMPACT/LARGE)下用輸入存貯器型別的方法實現。訪問片內資料RAM要比訪
問外部資料存貯器快得多。片內RAM由暫存器組,位資料區棧和其它由使用者用“data”型別定義的變數共享。由於片內RAM容量的限制(128~
256位元組,由使用的處理器決定),必須權衡利弊以解決訪問效率和這些物件的數量之間的矛盾。
總是使用可能的最小資料型別
8051系列CPU都是8位機,因此,顯然對具有“char”型別的物件的操作比“int”或“long”型別的物件方便得多。建議程式設計者只要
滿足要求,應儘量使用最小資料型別。
C51編譯器直接支援所有的位元組操作,因而如果不是運算子要求,就不作“int”型別的轉換,這可用一個乘積運算來清楚說明,兩個
“char型別”物件的乘積與8051操作碼“MUL AB”剛好相符。如果用整型量完成同樣的運算,則需要呼叫庫函式。
只要有可能,使用“unsigned”資料型別
8051系列CPU並不直接支援有符號數的運算。因而C51編譯器必須產生與之相關的更多的程式碼以解決這個問題。如果使用無符號型別,