1.1 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()函数。
本节包含几个怎样提高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编译器必须产生与之相关的更多的代码以解决这个问题。如果使用无符号类型,产生的代码要少得多。
只要有可能,使用局部函数变量
编译器总是尝试在寄存器里保持局中变量。这样,将索引变量(如FOR和WHILE循环中计数变量)声明为局部变量是最好的,这个优化步骤只为局部变量执行。使用“unsigned char/int”的对象通常能获得最好的结果。
2 C51编译器控制指令
编译选项可能被控制指令激活、禁止或改变。这些指令可在命令行输入或在源文件上加入#pragma给预处理器。控制指令分为两组,即首要的和一般的,并可分为三类:源控制、目标控制、列表控制。见表2.1编译控制指令。“P/G”列表明指令是首要的或是一般的,首要指令仅出现一次,一般指令按需要可出现多次。
表2.1 编译控制指令
分 类 |
P/G
|
指 令 |
缩 写 |
默 认 值 |
源 |
P G G G P |
DEFINE SAVE RESTORE DISABLE [NO]EXTEND |
DF — — — —— |
— — — — EXTEND |
目 标 |
P P P P P P G G G P P P |
[NO]DEBUG [NO]OBJECT OPTIMIZE(n) SMALL COMPACT LARGE [NO]REGPARMS REGISTERBANK(n) [NO]AREGS [NO]INTVECTER OBJECTEXTEND ROM() |
[NO]DB [NO]OJ OT SM CP LA [NO]RP RB [NO]AR [NO]IV OE — |
NODEBUG OBJECT(名字.OBJ) OPTIMIZE(2) SMALL — — REGPARMS REGISTERBANK(0) AREGS INTVECTOR — ROM(LARGE) |
列 表 |
P P P P P P P G P |
[NO]LISTINCLUDE [NO]SYMBOLS [NO]PREPRINT [NO]CODE [NO]PRINT [NO]CODE PAGELENGTH(n) EJECT PAGEWIDTH(n) |
[NO]LC [NO]SB [NO]PP [NO]CD [NO]PR [NO]CO PL EJ PW |
NOLISTINCLUDE NOSYMBOLS NOPREPRINT NOCODE PRINT(名字.OBJ) CODE PAGELENGTH(69) — PAGEWIDTH(132) |
2.1 源控制
2.1.1 DEFINE
名 字: DEFINE
缩 写: DF
变 量: 一个或几个由逗号分开的名字,与C语言的命令规格一致。
默认值: 无
DEFINE 指令定义了在命令行使用的名字,可为“if”,“ifdef”及“ifndef”等预处理命令作条件编译,定义的名字被精确拷贝,因而对大小写敏感。
例: C51 DEMO.C DEFINE(check,NoExtRam)
2.1.2 SAVE/RESTORE
名 字: SAVE/RESTORE
缩 写: 无
变 量: 无
默认值: 无
SAVE指令存储当前的ARGES,REGPARMS,OPTIMIZE因子和优化选项的SPEEDSIZE设置。这样,上面所述的设置被保留下来,例如在#INCLUDE语句之前用SAVE保护,然后用RESTORE指令恢复。
SAVE/RESTORE只能在源文件中以#pragma语句的参数形式出现,而不能用于命令行。
例:
#pragma save
#pragma noregparms
extern void test1(char c,int I);
extern char test2(long l,float f);
#pragma restore
两个外部函数的寄存器内参数传递被禁止,然后SAVE指令时的设置被恢复。
2.1.3 DISABLE
名 字: DISABLE
缩 写: 无
变 量: 无
默认值:无
DISABLE指令在一个函数执行期间禁止中断发生。DISABLE必须在函数前以#pragma指定并且只能用在一个函数中,因此它是由编译器内部设置的,每个要禁止的中断在函数前必须包括一个独立的#pragma指定,而不能用于命令行。
例: typedef using char uchar;
#pragma disable /*Disable interrupts */
uchar dfunc(uchar p1,uchar p2) {
return(p1*P2+P2*p1);
}
2.1.4 EXTEND/NOEXTEND
名 字: EXTEND/NOEXTEND
缩 写: 无
变 量: 无
默认值: EXTEND
EXTEND指令支持ANSI-C对C51的特殊扩展。NOEXTEND指令使编译器只处理ANSI-C。不同的保留字如bit,reentrant,using等对编译器来说是未知的。
例: C51 DEMO.C NOEXTEND
#pragma NOEXTEND
2.2 目标控制
2.2.1 DEBUG/NODEBUG
名 字: DEBUG/NODEBUG
缩 写: DB/NODB
变 量: 无
默认值:NODEBUG
DEBUG指令指示编译器将调试信息加入到目标文件中,该信息对于符号调试而言是必需的,它包括全局和局部变量的定义及其地址、以及函数名及其行号。目标模块中的调试信息在用L51连接过程中一直保持有效,它也可被与dscope-51或intel兼容的仿真器使用。NODEBUG指令指示在目标文件中不加入调试信息。QTH建议用户要加入DB信息。
例: C51 DEMO.C DEBUG
#pragma db
2.2.2 OBJECT/NOOBJECT
名 字: OBJECT/NOOBJECT
缩 写: OJ/NOOJ
变 量: 括号内文件名
默认值: OBJECT(bname.OBJ)
OBJECT(filename)指令改变生成的目标文件名,默认值是路径名加上基本扩展名“.OBJ”,NOOBJECT指令不产生目标文件。
例: C51 DEMO.C NOOBJEECT
#pragma oj(demo.obj)
2.2.3 OBJECTEXTEND
名 字: OBJECTEXTEND
缩 写: OE
变 量: 无
默认值: 无
OBJECTEXTEND使编译器产生的目标文件包含附加的变量类型定义信息。这个信息可在目标模块具有相同名字的时候将它们区别开来以供不同的仿真器使用。
注:QTH系列仿真器不需要该控制项。
2.2.4 OPTIMIZE
名 字: OPTIMIZE
缩 写: OT
变 量: 括号内一个0~5的十进制数,另外可选OPTIMIZE(SIZE)和OPTIMIZE(SPEED)
以决定优化重点是放在代码长度上还是执行速度上。
默认值:OPTIMIZE(2,SPEED)。
OPTIMIZE指令设置优化级,在这种设置中,高一级的优化级包含前一级较低的优化级的设置。
(1) OPTIMIZE(0)
l 常数折叠:编译时只要有可能,编译器就执行包含常数的计算,包括执行地址计算。
l 简单访问化:对8051系统内部数据和地址进行访问优化。
l 跳转优化:编译器总是将跳转延迟至最终目标上,因此跳转到跳转的指令被消除。
(2) OPTIMIZE(1)
l 死码消除:无用的代码将被消除。
l 跳转否决:根据一个测试反馈,条件转移被仔细检查,以决定是否能够进行简化或消除。
(3) OPTIMIZE(2)
l 数据覆盖:适用于静态覆盖的数据和位段被鉴别并标记出来。L51有这样一个功能,通过对全局数据流的分析,选择可静态覆盖的段。
(4) OPTIMIZE(3)
l “窥孔”优化:冗余的MOV指令被删去,这也包括不必要的、从存贮器装入对象及装入常数的操作。另外,当它能节省存贮器空间或执行时间时,复杂操作由简单操作所取代。
(5) OPTIMIZE(4)
l 寄存器变量:自动和参数变量位于寄存器中,只要有可能,将不为这些就是变量保留数据存贮器空间。
l 扩展访问优化:由IDATA,XDATA和CODE区域来的变量直接包含在操作中,因此在大多数时候装入中间寄存器是不必要的。
l 局部公共子式的消除:如果表达式中有一个重复执行的计算,只要有可能,第一次计算的结果将被用于后续的计算,因此可以从代码中消除繁杂的计算。
l CASE/SWITCH优化:CASE/SWITCH语句作为跳转表或跳转串被优化。
(6) OPTIMIZE(5)
l 全局公共子式消除:只要有可能,函数内相同的子表达式只计算一次。中间结果存入一个寄存器以代替新的计算。
l 简单循环优化:以常量占据一段内存的循环被转化并在运行时被优化。OPTIMIZE(5)包括了从0级到4的所有优化。
例: C51 SAMPLE.C OPTIMIZE(4)
#pragma ot(5,SIZE)
#pragma ot(size)
注: 全局优化从优化级4开始。同时,一个完整的函数被优化时,如果分配给生成优
化代码所必要的数据结构的内存不够,全局优化只执行一部分,或根本不执行。
2.2.5 SMALL/COMPACT/LARGE
名 字: SMALL/COMPACT/LARGE
缩 写: 无
变 量: 无
默认值: SMALL
SMALL,COMPACT,LARGE这些指令控制存贮器模式选择。存贮器模式对不同的变量定义有所影响。
SMALL:
所有函数和过程变量及局部数据段被定义在8051系统内部数据存贮器,因此以这种模式访问数据对象是非常有效的。这种模式的缺点是地址空间有限。
COMPACT:
所有函数和过程变量及局部数据段被定义在8051系统外部数据存贮器中,这个存贮区可达256字节(1页)。这种模式使用访问外部数据存贮器的简洁形式(@R0/R1)。
LARGE:
所有变量和局部变量数据段定义在8051系统的外部数据存贮器中,可访问达64K字节的地址空间。因此,它需要通过数据指针(DPTR),这是一种效率不高的数据访问形式。
注意:调用子程序的栈始终放在内部存贮器中。
例: C51 SAMPLE.C LARGE
#pragma compact
2.2.6 REGPARMS/NOREGPARMS
名 字: REGPARMS/NOREGPARMS
缩 写: RP/NORP
变 量: 无
默认值: REGPARMS
使用REGPARMS指令时,编译器在寄存器内至多传递三个参数。该指令产生的参数传递代码与早期的C51版本兼容。
REGPARMS和NOREGPARMS指令可以在源程序中定义多次。这样就允许程序的某一节使用寄存器进行参数传递,而其它函数仍然用原来的传递方式,汇编语言函数及库文件(已经存在的)则不必更改。
例: C51 DEMO.C NOREGPARMS
#pragma RP
2.2.7 REGISTERBANK
名 字: REGISTERBANK
缩 写: RB
变 量: 括号内一个0~3的数
默认值: REGISTERBANK(0)
REGISTERBANK指令选择当前调用时使用的寄存器组,除非使用“using”标志。当能够计算寄存器的绝对数量时,生成代码可能使用寄存器访问的绝对形式,用助记符“Arn”来表示。
与“using n”相比,REGISTERBANK不转换寄存器组!
有返回值的函数必须始终使用同一个寄存器组。否则,返回值可能放在错误的寄存器组中。
例: C51 DEMO.C REGISTERBANK
#pragma rp(3)
2.2.8 AREGS/NOAREGS
名 字: AREGS/NOAREGS
缩 写: AR/NOAR
变 量: 无
默认值: AREGS
AREGS使编译器使用绝对寄存器寻址方式,绝对寄存器寻址方式提高了效率。例如:PUSH和POP指令只能直接(绝对)寻址。用REGSITERBANK指令可建立指定使用的寄存器组。NOAREG指令关闭绝对寻址。未使用NOAREGS指令编译的函数不依赖于寄存器组,即它们可使用8051所有的寄存器。AREGS/NOAREGS指令可在程序中定义多次,然而,这只能在函数外使用。
例: C51 DEMO.C NOAREGS
#pragma AREGS
2.2.9 INTVECTOR/NOINTVECTOR
名 字: INTVECTOR/NOINTVECTOR
缩 写: IV/NOIV
变 量: 无
默认值: INTVECTOR
INTVECTOR指令使编译器产生3字节跳转(LJMP)目标值。这些向量放在绝对地址8n+3处,n为中断号。NOINTVECTOR指令可以防止产生这些中断向量,这种灵活性使用户可以利用其它编程工具提供的中断服务子程序。
例: C51 SAMPLE.C NOINTVECTOR
#pragma noiv
2.2.10 ROM
ROM指令用来决定程序内存的大小,它影响跳转指令的编码。
ROM(SMALL):
以CALL和JMP指令作为ACALL和AJMP指令的编码,最大程序空间可达2K字节,整个用户程序必须分布在这2K字节空间内。
ROM(COMPACT):
CALL指令以LCALL编码,函数内JMP指令以AJMP编码,因此函数长度不得超过2K字节,而整个程序长度不得超过64K字节,这种用法必须根据不同的目的而决定,看其是否比标准设置ROM(LARGE)效果更佳。
ROM(LARGE)
将CALL和JMP指令以LCALL和LJMP编码。这样就允许不加限制地使用整个地址空间,用户程序最大可达64K字节。
例: C51 DEMO.C ROM(SMALL)
#pragma ROM(SMALL)
2.3 列表控制
2.3.1 LISTINCLUDE
名 字: LISTINCLUDE/NOLISTINCLUDE
缩 写: LC/NOLC
变 量: 无
默认值: NOLISTINCLUDE
LISTINCLUDE指令导致在列表文件中出现头文件的内容。QTH仿真器建议用户使用默认值。
2.3.2 SYMBOLS/NOSYMBOLS
名 字: SYMBOLS/NOSYMBOLS
缩 写: SB/NOSB
变 量: 无
默认值: NOSYMBOL
SYMBOLS,NOSYMBOLS指令在编译时产生一个被程序模块使用过的符号表。对于每一个符号对象,包含了存贮器登录,存贮类型,偏移和对象大小。
例: C51 DEMO.C SB
#pragma sb
2.3.3 PREPRINT/NOPREPRINT
名 字: PREPRINT/NOPREPRINT
缩 写: PP/NOPP
变 量: 括号内可选的文件名
默认值: NOPREPRINT
PREPRINT指令产生一个预处理器列表,宏调用被扩展并且注释被删除。如果PREPRINT指令不带变量,则源文件名加上扩展名“·I”作为列表文件名,如果不用此名,则须指定一个文件名。
例: C51 DEMO.C PP(DEMO.LST)
2.3.4 CODE/NOCODE
名 字: CODE/NOCODE
缩 写: CD/NOCD
变 量: 无
默认值: NOCODE
CODE指令在列表文件后附加上一个汇编记忆表,源程序中的每个函数被表示为汇编代码。
2.3.5 PRINT/NOPRINT
名 字: PRINT/NOPRINT
缩 写: PR/NOPR
变 量: 括号内文件名
默认值: PRINT(bname.LST)
PRINT指令使编译器用路径,源文件名及扩展名“.LST”为每个被编译的程序产生一个列表文件,该列表文件名可以用PRINT选项重新定义,当使用NOPRINT时,将不产生列表文件。QTH仿真器建议用户使用默认值。
2.3.6 COND/NOCOND
名 字: COND/NOCOND
缩 写: CO/NOCO
变 量: 无
默认值: COND
指令COND和NOCOND决定源文件中条件编译部分是否出现在列表文件中,COND选项禁止向列表文件输出跳过的行。每当预处理器检测到该指令时,该指令就会对该行产生影响。QTH仿真器建议用户使用默认值。
2.3.7 PAGELENGTH
名 字: PAGELENGTH
缩 写: PL
变 量: 括号内一个最大为65535十进制数
默认值: PAGELENGTH(69)
PAGELENGTH指令指定了列表文件中每页的行数。默认值为69页。包括抬头和空行。
例: C51 DEMO.C PAGELENGTH(60)
#pragma pl(60)
2.3.8 PAGEWIDTH
名 字: PAGEWIDTH
缩 写: PW
变 量: 括号内一个78~132的十进制数
默认值: PATHWIDTH(132)
PAGEWIDTH指定了每行的字符数。若超过此数,该行将变为两行或多行。QTH仿真器建议用户使用默认值。
2.3.9 EJECT
名 字: EJECT
缩 写: EJ
变 量: 无
默认值: 无
EJECT指令使列表文件换页。该指令只能用在源文件上,且必须是#pragma的一部分。
3 C库函数
C-51软件包的库包含标准的应用程序,每个函数都在相应的头文件(.h)中有原型声明。如果使用库函数,必须在源程序中用预编译指令定义与该函数相关的头文件(包含了该函数的原型声明)。例如:
#include
#include
如果省掉头文件,编译器则期望标准的C参数类型,从而不能保证函数的正确执行。
3.1 CTYPE.H:字符函数
在CTYPE.H头文件中包含下列一些库函数:
函数名: isalpha
原 型: extern bit isalpha(char)
功 能: isalpha检查传入的字符是否在‘A’-‘Z’和‘a’-‘z’之间,如果为真返回
值为1,否则为0。
函数名: isalnum
原 型: extern bit isalnum(char)
功 能: isalnum检查字符是否位于‘A’-‘Z’,‘a’-‘z’或‘0’-‘9’之间,为真返
回值是1,否则为0。
函数名: iscntrl
原 型: extern bit iscntrl(char)
功 能: iscntrl检查字符是否位于0x00~0x1F之间或0x7F,为真返回值是1,否则为0。
函数名: isdigit
原 型: extern bit isdigit(char)
功 能: isdigit检查字符是否在‘0’-‘9’之间,为真返回值是1,否则为0。
函数名: isgraph
原 型: extern bit isgraph(char)
功 能: isgraph检查变量是否为可打印字符,可打印字符的值域为0x21~0x7E。若为可
打印,返回值为1,否则为0。
函数名: isprint
原 型: extern bit isprint(char)
功 能: 除与isgraph相同外,还接受空格字符(0X20)。
函数名: ispunct
原 型: extern bit ispunct(char)
功 能: ispunct检查字符是否位为标点或空格。如果该字符是个空格或32个标点和格式
字符之一(假定使用ASCII字符集中128个标准字符),则返回1,否则返回0。Ispunct对下列字符返回1:
(空格)!“$%^&()+,-./:<=>?_[‘~{
}
函数名: islower
原 型: extern bit islower(char)
功 能: islower检查字符变量是否位于‘a’-‘z’之间,为真返回值是1,否则为0。
函数名: isupper
原 型: extern bit isupper(char)
功 能: isupper检查字符变量是否位于‘A’-‘Z’之间,为真返回值是1,否则为0。
函数名: isspace
原 型: extern bit isspace(char)
功 能: isspace检查字符变量是否为下列之一:空格、制表符、回车、换行、垂直制表
符和送纸。为真返回值是1,否则为0。
函数名: isxdigit
原 型: extern bit isxdigit(char)
功 能: isxdigit检查字符变量是否位于‘0’-‘9’,‘A’-‘F’或‘a’-‘f’之间,
为真返回值是1,否则为0。
函数名: toascii
原 型: toascii(c)((c)&0x7F);
功 能: 该宏将任何整型值缩小到有效的ASCII范围内,它将变量和0x7F相与从而去掉低
7位以上所有数位。
函数名: toint
原 型: extern char toint(char)
功 能: toint将ASCII字符转换为16进制,返回值0到9由ASCII字符‘0’到‘9’得
到,10到15由ASCII字符‘a’-‘f’(与大小写无关)得到。
函数名: tolower
原 型: extern char tolower(char)
功 能: tolower将字符转换为小写形式,如果字符变量不在‘A’-‘Z’之间,则不作转
换,返回该字符。
函数名: _tolower
原 型: tolower(c);(c-‘A’+‘a’)
功 能: 该宏将0x20参量值逐位相或。
函数名: toupper
原 型: extern char toupper(char)
功 能: toupper将字符转换为大写形式,如果字符变量不在‘a’-‘z’之间,则不作转
换,返回该字符。
函数名: _toupper
原 型: _toupper(c);((c)-‘a’+’A’)
功 能: _toupper宏将c与0xDF逐位相与。
3.2 STDIO.H:一般I/O函数
C51编译器包含字符I/O函数,它们通过处理器的串行接口操作,为支持其它I/O机制,只需修改getkey()和putchar()函数,其它所有I/O支持函数依赖这两个模块,不需要改动。在使用8051串行口之前,必须将它们初始化,下例以2400波特率,12MHz初始化串口:
SCON=0x52 /*SCON*/
TMOD=0x20 /*TMOD*/
TR1=1 /*Timer 1 run flag*/
TH1=0Xf3 /*TH1*/
其它工作模式和波特率等细节问题可以从8051用户手册中得到。
函数名: _getkey
原 型: extern char _getkey();
功 能: _getkey()从8051串口读入一个字符,然后等待字符输入,这个函数是改变整个
输入端口机制应作修改的唯一一个函数。
函数名: getchar
原 型: extern char _getchar();
功 能: getchar()使用_getkey从串口读入字符,除了读入的字符马上传给putchar函数
以作响应外,与_getkey相同。
函数名: gets
原 型: extern char *gets(char *s,int n);
功 能: 该函数通过getchar从控制台设备读入一个字符送入由‘s’指向的数据组。考
虑到ANSI标准的建议,限制每次调用时能读入的最大字符数,函数提供了一个字符计数器‘n’,在所有情况下,当检测到换行符时,放弃字符输入。
函数名: ungetchar
原 型: extern char ungetchar(char);
功 能: ungetchar将输入字符推回输入缓冲区,因此下次gets或getchar可用该字符。
ungetchar成功时返回‘char’,失败时返回EOF,不可能用ungetchar处理多个字符。
函数名: _ungetchar
原 型: extern char _ungetchar(char);
功 能: _ungetchar将传入字符送回输入缓冲区并将其值返回给调用者,下次使用getkey
时可获得该字符,写回多个字符是不可能的。
函数名: putchar
原 型: extern putchar(char);
功 能: putchar通过8051串口输出‘char’,和函数getkey一样,putchar是改变整个
输出机制所需修改的唯一一个函数。
函数名: printf
原 型: extern int printf(const char*,…);
功 能: printf以一定格式通过8051串口输出数值和串,返回值为实际输出的字符数,
参量可以是指针、字符或数值,第一个参量是格式串指针。
注:允许作为printf参量的总字节数由C51库限制,因为8051结构上存贮空间有限,在SMALL和COMPACT模式下,最大可传递15个字节的参数(即5个指针,或1个指针和3个长字节),在LARGE模式下,至多可传递40个字节的参数。格式控制串包含下列域(方括号内的域是可能的):
%[flags][width][.precision]type
“width”域定义了参量欲显示的字符数,它必须是一个十进制数,如果实际显示的字符数小于“width”,输出左端补以空格,如果“width”域以0开始,则左端补0。
“flag”域用来定义下面选项:
Falg |
意 义 |
- |
输出左齐 |
+ |
输出值如果是有符号数值,则加上+/-号 |
‘ ‘(空格) |
输出值如果为正则左边补以空格显示 |
# |
如果它与0,x或X联用,则在输出前加上字符0、0x,0X。当与值类型g、G、f、e、E联用时,‘#’使输出数产生一个十进制小数点。 |
b,B |
它们与格式类型d、i、o、u、x、X联用,这样参量类型被接受为‘[unsigned]char’,如:%bu,%bd或%bx。 |
L,L |
它们与格式类型d、i、o、u、x、X联用,这样参量类型被接受为‘[unsigned]long’,如:%lu,%ld或%lx。 |
* |
下一个参量不作输出。 |
“type“域定义参量如下类型:
字 符 |
类 型 |
输 出 格 式 |
d |
int |
有符号十进制数(16位) |
U |
int |
无符号十进制数 |
o |
int |
无符号八进制数 |
X,x |
int |
无符号十六进制数 |
f |
float |
[-]dddd.dddd形式的浮点数 |
e,E |
float |
[-]d.ddddE[sign]dd形式的浮点数 |
g,G |
float |
e或f形式浮点数,看哪一种输出形式好。 |
c |
char |
字符 |
s |
pointer |
指向一个带结束符号的串 |
p |
pointer |
带存贮器指示符和偏移的指针。M:aaaa。 M:=C(ode),D(ata),I(data),P(data) aaaa:指针偏移值。 |
例子:
printf(“Int-Val%d,Char-Val%bd,Long-Val%d”,I,c,l);
printf(“String%s,Character%c”,array,character);
printf(“Pointer%p”,&array[10]);
函数名: sprintf
原 型: extern int sprintf(char *s,const char*,…);
功 能: sprintf与printf相似,但输出不显示在控制台上,而是通过一个指针S,送入
可寻址的缓冲区。
注:sprintf允许输出的参量总字节数与printf完全相同。
函数名: puts
原 型: extern int puts(const char*,…);
功 能: puts将串‘s’和换行符写入控制台设备,错误时返回EOF,否则返回一非负数。
函数名: scanf
原 型: extern int scanf(const char*,…);
功 能: scanf在格式串控制下,利用getcha函数由控制台读入数据,每遇到一个值(符
号格式串规定),就将它按顺序赋给每个参量,注意每个参量必须都是指针。scanf返回它所发现并转换的输入项数。若遇到错误返回EOF。格式串包括:
l 空格、制表符等,这些空白字符被忽略。
l 字符,除需匹配的“%”(格式控制字符)外。
l 转换指定字符“%”,后随几个可选字符;赋值抑制符“*”,一个指定最大域宽的数。
注:scanf参量允许的总字节数与printf相同,格式控制串可包括下列域(方括号内是可选的):
%[flags][width]type
格式串总是以百分号开始,每个域包含一个或多个字符或数。
“width”域定义了参量可接受的字符数,“width”必须是一个正十进制数。如果实际输入字符数量小于“width”,则不会进行填充。
‘flag’域用来定义下面选项:
Flag |
意 义 |
* |
输入被忽略 |
b,h |
它们用作格式类型d,i,o,u和x的前缀,用这些变量可定义参量是字符指针还是无符号字符指针。如%bu,%bd,%bx。 |
L |
它们被作格式类型d,i,o,u和x的前缀,使用这个前缀可定义参量是长指针还是无符号字长指针。如%lu,%ld,%lx。 |
“type”域定义参量为如下类型:
描 述 符 |
类 型 |
输 入 格 式 |
d |
ptr to int |
有符号十进制数(16位) |
i |
ptr to int |
如C中记号一样,整型值 |
u |
ptr to int |
无符号十进制数 |
o |
ptr to int |
无符号八进制数 |
x |
ptr to int |
无符号十六进制数 |
f,e,g |
ptr to float |
浮点数 |
c |
ptr to char |
一个字符 |
s |
ptr to string |
一个字串 |
例子:
scanf(“%d%bd%ld”,&i,&c,&l);
scanf(“%f”,&f);
scanf(“%3s,%c”,&string[0],&character);
函数名: sscanf
原 型: extern int sscanf(const *s,const char*,…);
功 能: sscanf与scanf方式相似,但串输入不是通过控制台,而是通过另一个以空结束
的指针。
注:sscanf参量允许的总字节数由C-51库限制,这是因为8051处理器结构内存的限制,在SMALL和COMPACT模式,最大允许15字节参数(即至多5个指针,或2个指针,2个长整型或1个字符型)的传递。在LARGE模式下,最大允许传送40个字节的参数。
3.3 STRING.H:串函数
串函数通常将指针串作输入值。一个串就包括2个或多个字符。串结以空字符表示。在函数memcmp,memcpy,memchr,memccpy,memmove和memset中,串长度由调用者明确规定,使这些函数可工作在任何模式下。
函数名: memchr
原 型: extern void *memchr(void *sl, char val,int len);
功 能: memchr顺序搜索s1中的len个字符找出字符val,成功时返回s1中指向val的
指针,失败时返回NULL。
函数名: memcmp
原 型: extern char memcmp(void *sl, void *s2,int len);
功 能: memcmp逐个字符比较串s1和s2的前len个字符。相等时返回0,如果串s1大
于或小于s2,则相应返回一个正数或负数。
函数名: memcpy
原 型: extern void *memcpy(void *dest, void *src,int len);
功 能: memcpy由src所指内存中拷贝len个字符到dest中,返回批向dest中的最后一
个字符的指针。如果src和dest发生交迭,则结果是不可预测的。
函数名: memccpy
原 型: extern void *memccpy(void *dest, void *src,char val,int len);
功 能: memccpy拷贝src中len个字符到dest中,如果实际拷贝了len个字符返回NULL。
拷贝过程在拷贝完字符val后停止,此时返回指向dest中下一个元素的指针。
函数名: memmove
原 型: extern void *memmove(void *dest, void *src,int len);
功 能: memmove工作方式与memcpy相同,但拷贝区可以交迭。
函数名: memset
原 型: extern void *memset(void *s, char val,int len);
功 能: memset 将val值填充指针s中len个单元。
函数名: strcat
原 型: extern char *strcat(char *s1, char *s2);
功 能: strcat将串s2拷贝到串s1结尾。它假定s1定义的地址区足以接受两个串。返
回指针指向s1串的第一字符。
函数名: strncat
原 型: extern char *strncat(char *s1, char *s2,int n);
功 能: strncat拷贝串s2中n个字符到串s1结尾。如果s2比n短,则只拷贝s2。
函数名: strcmp
原 型: extern char strcmp(char *s1, char *s2);
功 能: strcmp比较串s1和s2,如果相等返回0,如果s1
一个正数。
函数名: strncmp
原 型: extern char strncmp(char *s1, char *s2,int n);
功 能: strncmp比较串s1和s2中前n个字符,返回值与strncmp相同。
函数名: strcpy
原 型: extern char *strcpy(char *s1, char *s2);
功 能: strcpy将串s2包括结束符拷贝到s1,返回指向s1的第一个字符的指针。
函数名: strncpy
原 型: extern char *strncpy(char *s1, char *s2,int n);
功 能: strncpy与strcpy相似,但只拷贝n个字符。如果s2长度小于n,则s1串以‘0’
补齐到长度n。
函数名: strlen
原 型: extern int strlen(char *s1);
功 能: strlen返回串s1字符个数(包括结束字符)。
函数名: strchr,strpos
原 型: extern char *strchr(char *s1, char c);
extern int strpos(char *s1,char c);
功 能: strchr搜索s1串中第一个出现的‘c’字符,如果成功,返回指向该字符的别指
针,搜索也包括结束符。搜索一个空字符返回指向空字符的指针而不是空指针。
strpos与strchr相似,但它返回字符在串中的位置或-1,s1串的第一个字符位置是0。
函数名: strrchr,strrpos
原 型: extern char *strrchr(char *s1, char c);
extern int strrpos(char *s1,char c);
功 能: strrchr搜索s1串中最后一个出现的‘c’字符,如果成功,返回指向该字符的
指针,否则返回NULL。对s1搜索也返回指向字符的指针而不是空指针。
strrpos与strrchr相似,但它返回字符在串中的位置或-1。
函数名: strspn,strcspn,strpbrk,strrpbrk
原 型: extern int strspn(char *s1, char *set);
extern int strcspn(char *s1,char *set);
extern char *strpbrk(char *s1,char *set);
extern char *strpbrk(char *s1,char *set);
功 能: strspn搜索s1串中第一个不包含在set中的字符,返回值是s1中包含在set里
字符的个数。如果s1中所有字符都包含在set里,则返回s1的长度(包括结束符)。如果s1是空串,则返回0。
strcspn与strspn类似,但它搜索的是s1串中的第一个包含在set里的字符。strpbrk与strspn很相似,但它返回指向搜索到字符的指针,而不是个数,如果未找到,则返回NULL。
strrpbrk与strpbrk相似,但它返回s1中指向找到的set字集中最后一个字符的指针。
3.4 STDLIB.H:标准函数
函数名: atof
原 型: extern double atof(char *s1);
功 能: atof将s1串转换为浮点值并返回它。输入串必须包含与浮点值规定相符的数。
C51编译器对数据类型float和double相同对待。
函数名: atol
原 型: extern long atol(char *s1);
功 能: atol将s1串转换成一个长整型值并返回它。输入串必须包含与长整型值规定相
符的数。
函数名: atoi
原 型: extern int atoi(char *s1);
功 能: atoi将s1串转换为整型数并返回它。输入串必须包含与整型数规定相符的数。
3.5 MATH.H:数学函数
函数名: abs,cabs,fabs,labs
原 型: extern int abs(int va1);
extern char cabs(char val);
extern float fabs(float val);
extern long labs(long val);
功 能: abs决定了变量val的绝对值,如果val为正,则不作改变返回;如果为负,则
返回相反数。这四个函数除了变量和返回值的数据不一样外,它们功能相同。
函数名: exp,log,log10
原 型: extern float exp(float x);
extern float log(float x);
extern float log10(float x);
功 能:exp返回以e为底x的幂,log返回x的自然数(e=2.718282),log10返回x以10
为底的数。
函数名: sqrt
原 型: extern float sqrt(float x);
功 能: sqrt返回x的平方根。
函数名:rand,srand
原 型: extern int rand(void);
extern void srand(int n);
功 能: rand返回一个0到32767之间的伪随机数。srand用来将随机数发生器初始化成
一个已知(或期望)值,对rand的相继调用将产生相同序列的随机数。
函数名: cos,sin,tan
原 型: extern float cos(flaot x);
extern float sin(flaot x);
extern flaot tan(flaot x);
功 能: cos返回x的余弦值。Sin返回x的正弦值。tan返回x的正切值,所有函数变量
范围为-π/2~+π/2,变量必须在±65535之间,否则会产生一个NaN错误。
函数名: acos,asin,atan,atan2
原 型: extern float acos(float x);
extern float asin(float x);
extern float atan(float x);
extern float atan(float y,float x);
功 能: acos返回x的反余弦值,asin返回x的正弦值,atan返回x的反正切值,它们
的值域为-π/2~+π/2。atan2返回x/y的反正切,其值域为-π~+π。
函数名: cosh,sinh,tanh
原 型: extern float cosh(float x);
extern float sinh(float x);
extern float tanh(float x);
功 能: cosh返回x的双曲余弦值;sinh返回x的双曲正弦值;tanh返回x的双曲正切
值。
函数名: fpsave,fprestore
原 型: extern void fpsave(struct FPBUF *p);
extern void fprestore (struct FPBUF *p);
功 能: fpsave保存浮点子程序的状态。fprestore将浮点子程序的状态恢复为其原始状
态,当用中断程序执行浮点运算时这两个函数是有用的。
3.6 ABSACC.H:绝对地址访问
函数名: CBYTE,DBYTE,PBYTE,XBYTE
原 型: #define CBYTE((unsigned char *)0x50000L)
#define DBYTE((unsigned char *)0x40000L)
#define PBYTE((unsigned char *)0x30000L)
#define XBYTE((unsigned char *)0x20000L)
功 能: 上述宏定义用来对8051地址空间作绝对地址访问,因此,可以字节寻址。CBYTE
寻址CODE区,DBYTE寻址DATA区,PBYTE寻址XDATA区(通过MOVX @R0命令),XBYTE寻址XDATA区(通过MOVX @DPTR命令)。
例:下列指令在外存区访问地址0x1000
xval=XBYTE[0x1000];
XBYTE[0X1000]=20;
通过使用#define指令,用符号可定义绝对地址,如符号X10可与XBYTE[0x1000]地址相等:#define X10 XBYTE[0x1000]。
函数名: CWORD,DWORD,PWORD,XWORD
原 型: #define CWORD((unsigned int *)0x50000L)
#define DWORD((unsigned int *)0x40000L)
#define PWORD((unsigned int *)0x30000L)
#define XWORD((unsigned int *)0x20000L)
功 能:这些宏与上面相似,只是它们指定的类型为unsigned int。通过灵活的数据类型,
所有地址空间都可以访问。
3.7 INTRINS.H:内部函数
函数名: _crol_,_irol_,_lrol_
原 型: unsigned char _crol_(unsigned char val,unsigned char n);
unsigned int _irol_(unsigned int val,unsigned char n);
unsigned int _lrol_(unsigned int val,unsigned char n);
功 能:_crol_,_irol_,_lrol_以位形式将val左移n位,该函数与8051“RLA”指令
相关,上面几个函数不同于参数类型。
例:
#include
main()
{
unsigned int y;
y=0x00ff;
y=_irol_(y,4); /*y=0x0ff0*/
}
函数名: _cror_,_iror_,_lror_
原 型: unsigned char _cror_(unsigned char val,unsigned char n);
unsigned int _iror_(unsigned int val,unsigned char n);
unsigned int _lror_(unsigned int val,unsigned char n);
功 能:_cror_,_iror_,_lror_以位形式将val右移n位,该函数与8051“RRA”指令
相关,上面几个函数不同于参数类型。
例:
#include
main()
{
unsigned int y;
y=0x0ff00;
y=_iror_(y,4); /*y=0x0ff0*/
}
函数名: _nop_
原 型: void _nop_(void);
功 能:_nop_产生一个NOP指令,该函数可用作C程序的时间比较。C51编译器在_nop_函
数工作期间不产生函数调用,即在程序中直接执行了NOP指令。
例:
P()=1;
_nop_();
P()=0;
函数名: _testbit_
原 型:bit _testbit_(bit x);
功 能:_testbit_产生一个JBC指令,该函数测试一个位,当置位时返回1,否则返回0。
如果该位置为1,则将该位复位为0。8051的JBC指令即用作此目的。 _testbit_只能用于可直接寻址的位;在表达式中使用是不允许的。
STDARG.H:变量参数表
C51编译器允许再入函数的变量参数(记号为“…”)。头文件STDARG.H允许处理函数的参数表,在编译时它们的长度和数据类型是未知的。为此,定义了下列宏。
宏 名: va_list
功 能: 指向参数的指针。
宏 名: va_stat(va_list pointer,last_argument)
功 能: 初始化指向参数的指针。
宏 名: type va_arg(va_list pointer,type)
功 能:返回类型为type的参数。
宏 名: va_end(va_list pointer)
功 能: 识别表尾的哑宏。
3.8 SETJMP.H:全程跳转
Setjmp.h中的函数用作正常的系列数调用和函数结束,它允许从深层函数调用中直接返回。
函数名: setjmp
原 型: int setjmp(jmp_buf env);
功 能: setjmp将状态信息存入env供函数longjmp使用。当直接调用setjmp 时返回值
是0,当由longjmp调用时返回非零值,setjmp只能在语句IF或SWITCH中调用一次。
函数名: long jmp
原 型: long jmp(jmp_buf env,int val);
功 能:longjmp恢复调用setjmp时存在env中的状态。程序继续执行,似乎函数setjmp
已被执行过。由setjmp返回的值是在函数longjmp中传送的值val,由setjmp调用的函数中的所有自动变量和未用易失性定义的变量的值都要改变。
3.9 REGxxx.H:访问SFR和SFR-BIT地址
文件REG51.H,REG52.H和REG552.H允许访问8051系列的SFR和SFR-bit的地址,这些文件都包含#include指令,并定义了所需的所有SFR名以寻址8051系列的外围电路地址,对于8051系列中其它一些器件,用户可用文件编辑器容易地产生一个“.h”文件。
下例表明了对8051 PORT0和PORT1的访问:
#include
main() {
if(p0==0x10) p1=0x50;
}
上一篇:KeilC51中的库函数printf
下一篇:C51的数据类型和变量定义
推荐阅读最新更新时间:2024-03-16 15:19