原创 C-51语言程序设计基础

2008-12-19 20:35 3578 10 10 分类: MCU/ 嵌入式

c51设计基础<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />


1.7 寄存器组定义


8051系列的器件包含4个相同的寄存器组,每个寄存器组包括8个寄存器(R0R7),C51编译器可使在一函数中决定用哪一寄存器组成为可能


。绝对寄存器的访问可用AREGS/NOAREGSREGISTERBANK来控制。


定义一个带扩展性的函数语法如下:


返回类型 函数名([参数][模式][再入][中断 n]using n


再入和中断将在后两节讨论。


例:void rb_function(void) using 3;


“using”不允许用于外部函数,它对函数的目标代码影响如下:


l 函数入口处将当前寄存器保存入栈;


l 指它的寄存器还会改变;


l 函数退出前寄存器组被恢复。


“using”定义对于返回一个寄存器内的值的函数是无用的。编程者必须十分小心以保证任何寄存器切换都只在仔细控制的区域发生。如果不


做到这一点将会产生不正确的函数结果。即使当编程者使用同一寄存器组时,带“using”属性的函数原则上也不能返回一个位值。


实际产生的代码决定于编译器及不同开关条件,有时用命令产生绝对的寄存器地址,当需要进行这样的地址计算时,使用REGISTERBANK指令的


影响只是计算Arn寄存器使用的地址,而必进行实际切换。


1.8 中断服务程序


C51编译器及其对C语言的扩充允许编程者对中断的所有方面进行控制。这种支持能使系统编程者创建高效的中断服务程序,用户只需在普通和


高级方式下关心中断及必要的寄存器组切换操作,C51编译器将产生最合适的代码。


<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />1.8.1 中断服务程序的定义


使用中断服务函数的完整语法如下:


返回值 函数名([参数][模式][再入] interrupt n[using n]


“interrupt”后接一个031的常数,不允许使用表达式。


中断不允许用于外部函数,它对函数目标代码的影响如下:


l 当使用函数时,SFR中的ACCBDPHDPLPSW(当需要时)入栈;


l 如不使用寄存器组切换,甚至中断函数所需的所有工作寄存器(Rn)都入栈;


l 函数退出前,所有的寄存器内容出栈;


l 函数由8051控制命令“RETI”终止。


1.8.2 开发中断过程时的规则


l 不能进行参数传递,如果中断过程包括任何参数声明,编译器将产生一个错误信息;


l 无返回值,如果想定义一个返回值将产生错误,然而,如果返回整型值编译器将不产生错误信息,因为整型值是默认值,因而编译器


不能清楚识别。


l 编译器会识别对中断过程的直接调用并拒绝它们,在任何情况下不能直接调用中断过程,因为退出该过程是由操作码RETI完成的。


RETI影响8051芯片的硬件中断系统,由于硬件上没有中断请求存在,因而这个操作码的结果是不定的并且通常是致命的。由于疏忽,可能用指


针来间接调用它,这是值得注意的。


l 编译器从绝对地址8n+3处产生一个中断向量,其中n为中断号,该向量包括一个到中断过程的跳转,向量的产生可由指令NOINTVECTOR


压缩。因而用户有能力从独立的汇编模块中提供中断向量。


l C51编译器允许031个中断,究竟允许哪些中断依赖于使用的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";   /*清中断计数*/


}


}


19 再入函数


再入函数可被递归调用,调用可发生在任何时候,即使是在中断过程中。在实时处理的应用问题中常常需要再入函数。


使用关键字“reentrant”可有选择地定义函数有再入能力。在存贮器模式的基础上为再入函数在内部或外部存贮器中模拟了一个栈区域。由


MCS-51缺乏合适的寻址方法,使用栈结构是相当必要的。因而应尽量少用再入函数。


定义一再入函数的语法如下:


返回值 函数名([参数][模式]reetrant[interrupt n][using n]


例:


int calc(char i,int b) reentrant {


   int x;


   x="table";


   return(x*b);


}


使用再入函数有如下规定:


l 不能传递类型为“bit”的参数。也不能声明一个局部标量,再入功能不能包括位操作及MCS-51可位寻址区域。


l 不能在“alien”函数调用再入函数。


l 再入函数可同时有其它属性,如“using”函数模式和“interrupt”


l 再入函数不能同时有“alien”属性,从而遵守PL/M规则。


l 返回地址及可能的PUSH/POP操作存入MCS-51的栈中或被执行(不在再入栈中)。


l 在同一模块中,任意模块的再入函数(small reentrantlage reentrantcompact reentrant)不能与具有不同模式的再入函数混


合。


再入函数举例:


/*这个再入函数可以从“main”及中断程序中调用*/


int calc(char i,int b)reentrant {


int x;


x=table;


return(x*b);


}


110 参数传递


通过CPU的寄存器可传递至多三个参数。这样产生与汇编子程序相当的有效参数机制。如果寄存器被占用,或说明了“#pragma NOREGPARMS”


,参数变量将使用固定的存贮器位置,存贮器模式决定了8051存贮器为参数提供的位置。


表:候选的参数寄存器


参数类型 CHAR1字节指针 INT2字节指针 LONGFLOAT   一般指针


一个参数        R7      R6R7    R4R7 R1R2R3


二个参数        R5      R4R5    R4R7 R1R2R3


三个参数        R3      R2R3       … R1R2R3


函数的返回值放在CPU固定的寄存器中,列表如下。这样,与汇编子程序的接口变得非常容易。


表:函数返回值的寄存器用法


              寄 存 器                       


        bit 进位标志 C


(unsigned) char R7


(unsigned) int R6,R7 高位在R6,低位在R7


(unsigned) long R4R7 高位在R4,低位在R7


      float R4R7 32IEEE格式


          R1,R2,R3 类型选择在R3,高位在R2,低位在R1


111 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编译器中才被考虑。


112 汇编接口


参数是通过固定的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


113 内部函数


Franklin C-51支持下列内部函数。内部函数既是再入的又是有效的。


表:C51的内部函数


                                        


memcpy,memsset,memchr,memmove,memcmp ANSI的内存操作功能


strcmp,strcpy ANSI字符串处理功能


_crol_,_irol_,lrol_ 左移字符、整数、长整数


_crolr_,_irolr_,lrolr_ 右移字符、整数、长整数


_nop_ 空操作


_testbit_ 测试并清位(JBC指令)


114 代码优化


Franklin C51可将即使有经验的程序员编制的代码进行优化。用户可选6个优化级,另外,用OPTIMIZESIZE),NOREGPARMSNOAREGS时会影


响生成代码的类型。C51的所有优化如下:


1) 一般优化:


l 常数折迭:发生在一个表达式或地址计算中的几个常数值组合为一个常数。


l 跳转优化:跳转转到最终的目标地址,以提高程序效率。


l 死码消除:不可执行代码(死码)可从程序中去掉。


l 寄存器变量:只要有可能,自动变量和参量放入寄存器中,为这些变量保留的数据存贮器将去除。


l 通过寄存器传递参数:寄存器中可传递最多三个参数。


l 全局公共子式消除:相同的子表达式或地址计算(多次发生在同一函数中)将被识别出来,并且只要有可能,将只计算一次。


2) 基于8051的优化:


l 窥孔(PEEPHOLE)优化:只要能节省存贮空间或执行时间,复杂的运算都将化简。


l 访问优化:常数和变量直接包含在操作中。


l 数据覆盖:函数的数据和位移被标记为OVERLAYABLE,被L51用其它数据和位覆盖。


l CASE/SWITCH优化:SWITCH/CASE语句优化为一个跳转或一串跳转。


3) 代码生成选项:


l OPTMIZESIZE):共同的“C”操作被子程序代替:程序码长被压缩。


l NOAREGS:不使用绝对寄存器访问,程序代码在这种方式下独立于寄存器组。


l NOREGPARMS:参数传递总是在本数据段完成,程序代码与早期C-51版本兼容。


115 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连接器的检查从而保证所有模块都用一种模式编译并自动选择编译库,从而使用户完全可以不用不同库的细节。


116 配置文件


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.OBJPUTCHAR.OBJ连接起来


STARTUP.A51


文件STARTUP.51开头包含一些C编译结构使用的EQU语句。每个EQU语句的功能描述如下:


IDATALEN    声明系统开始时有多少内存需要用0初始化。默认值为80H,因为几乎每个


8051指令至少包含128字节内部RAM。对于256字节内部RAM8052可使用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字节内部RAM8051系统,当存贮区作首址为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的寻址允许在任意XDATA256字节变量空间的映射。这两个指令必须和L51的控制指令PDATA


起使用。PDATA指定了XDATA存贮器中PDATA区的首址。例:在STARTUP.A51中,PPAGEENABLE置为1PPAGE置为10H。这种情况下PDATA区首址为


1000H10H页),而L51必须包含一个值在100010FFH之间的控制语句:L51〈输入模块〉PDATA1050H)。注:L51C51都不对PPAGE/PDATA


指令正确性进行检查,用户必须保证PPAGEPDATA包含一个合适的值。


INIT.A51


文件INIT.A51包含一个定义了看门狗刷新的宏。当系统包括看门狗以及用户变量初始化时间比看门狗刷新时间要长时,必须改变


这个宏。这种情况下,宏WATCHCOG必须包含看门狗刷新的代码。


例:   Watchdog refresh for 80515 system


   WATCHDOG MACRO


      SETB WDT


      SETB SWDT


      ENDM


PUTCHAR.C


文件PUTCHAR.C包含字符输出的核心程序,该文件通过串行口输出。这种情况下考虑了XON/XOFF协议,字符LF()被转为字符串CRLF,这在很


多终端中是需要的。用户可按自己的要求改变putchar()函数。


GETKEY.C


文件GETKEY.C包含字符输入的核心程序,该文件从串行接口读入一个字符,不作数据转换,用户可根据需要修改getkey()函数。


117 优化程序


本节包含几个怎样提高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编译器必须产生与之相关的更多的代码以解决这个问题。如果使用无符号类型,


 


 


51系列中data,idata,xdata,pdata的区别


data:
固定指前面0x00-0x7f128RAM,可以用acc直接读写的,速度最快,生成的代码也最小。

idata:
固定指前面0x00-0xff256RAM,其中前128data128完全相同,只是因为访问的方式不同。idata是用类似C中的指针方式 访问的。汇编中的语句为:mox ACC,@Rx.(不重要的补充:cidata做指针式的访问效果很好)

xdata:
外部扩展RAM,一般指外部0x0000-0xffff空间,DPTR访问。 pdata:外部扩展RAM的低256个字节,地址出现在A0-A7的上时读写,movx ACC,@Rx读写。这个比较特殊,而且C51好象有对此BUG, 建议少用。但也有他的优点,具体用法属于中级问题,这里不提。

startup.a51
的作用
和汇编一样,C中定义的那些变量和数组的初始化就在startup.a51中进行,如果你在定义全局变量时带有数值,unsigned char data xxx="100";,startup.a51中就会有相关的赋值。如果没有=100,startup.a51就会把他清 0。(startup.a51==变量的初始化)。 这些初始化完毕后,还会设置SP指针。对非变量区域,如堆栈区,将不会有赋值或清零动作。
有人喜欢改startup.a51,为了满足自己一些想当然的爱好,这是不必要的,有可能错误的。比如掉电保护的时候想保存一些变量, 但改startup.a51来实现是很笨的方法,实际只要利用非变量区域的特性,定义一个指针变量指向堆栈低部:0xff处就可实现。, 为什么还要去改? 可以这么说:任何时候都可以不需要改startup.a51,如果你明白它的特性


 


 


Franklin C-51语言程序设计基础


11 Franklin C-51数据类型


    Franklin C-51编译器支持下列数据类型:


数据类型 长度 值域


bit         1 字节 0 1


signed char 1 字节 -128+127


unsigned char 1 字节 0255


signed int 2 字节 -32768+32867


unsigned int 2 字节 065535


signed long 4 字节 -2147483648+2147483647


unsigned long 4 字节 04294967295


float         4 字节 ±1.176E-38±3.40E+38


指针         1~3 字节 对象地址


sbit         1 0 1


sfr         1 字节 0255


sfr16         2 字节 065535


     编译的数据类型(如结构)包含上表所列的数据类型。由于8051系列是8位机,因而不存在字节校准问题。这意味着数据结构成员是顺序放


置的。


    数据类型的转换:当计算结果隐含着另外一种数据类型时,数据类型可以自动进行转换,例如,将一个位变量赋给一个整型变量时,位型


值自动转换为整型值,有符号变量的符号也能自动进行处理。这些转换也可以用C语言的标准指令进行人工转换。


1.2 数据类型的物理结构


1.2.1 bit


   “bit”类型只有1位,不允许有位指针和位数组。位对象始终位于8051 CPU的可寻址RAM空间。如果程序控制流允许,L51将位对象交迭。


122 signed/unsigned chardata/idata/pdata 指针


“char”类型标量和基于存贮器的“data/idata/pdata”指针具有1个字节长度(8 bits)。


123 signed/unsigned int/shortxdata/code 指针


“int”“short”类型标量及指向xdata/code区域的指针具有2字节长度(16


bits)。


整型值(或偏移)0x1234以下面方式保存在内存中:


地址:    +0   +1


内容:     0x12   0x34


124 signed/unsigned long


“long”类型标量长为4个字节(32 bits),值0x12345678以下面方式放置:


地址:    +0    +1    +2    +3


内容:    0x12    0x34    0x56    0x78


125 “一般指针


一般指针包括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编译器不检查指针常数,用户必须选择有实际意义的值。


126 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


   M23位尾数,最高位“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 的扩充定义


131 特殊功能寄存器的声明


    MSC-51 系列包括多种寄存器,其中一些具有特殊功能,如定时器,端口的控制寄存器等,为了能够直接访问这些寄存器,C51编译器提供


了一种定义的自主形式,这是必要的,因为这些定义与标准C语言是不兼容的。


为了支持这些特殊功能寄存器(SFR)的声明,引入了关键词“sfr”,语法如下:


sfr-dclsfr sfr_name=int_constant


例:


sfr p0=0x80


sfr p1=0x90


必须注意的是“sfr”后不是一个地址而是一个名字。因此上例中名字P0P1port0port1)定义为特殊功能寄存器并被赋予相应


的绝对地址,名字可按意愿自由选取,源文件中不应有先定义的sfr名字。


“=”号后的地址必须是常数,不允许带有运算符的表达式,这个常数表达式必须在特殊功能寄存器的地址范围内,位于0X800XFF


之间。


8051系列寄存器数量和类型是极其不同的,因此建议将所有特别的“sfr”声明放入一个头文件,头文件包括8051一些系列成员中的


SFR定义。进一步的定义可由用户用一文件编辑器产生。


132 SFR16位数据访问


    在新的8051系列产品中,SFR在功能上经常组合为16位的,为了有效的访问这类SFR,使用定义“sfr16”,当“SFR”的高端直接位于低端


后时,对SFR16位的访问是可能的。例如8052的定时器2就是这种情况,16位声明的语法与“sfr”相同,SFR低地址部分必须作为sfr16的地址



例:sfr16 T2=0xCC      /*Timer2T2L=0CCHT2H=0CDH */


     sfr16 RCAP2=0xCA   /*RCAP2L=0CAHPCAP2H=0CBH */


本例中,T2(由T2LT2H组成)和RCAP2(由RCAP2LRCAP2H组成)被定义为16SFR,即使在这种情况下,声明中的名字后仍不是赋值语句,


而是一个SFR地址,高字节必须直接位于低字节之后,这种声明适用于所有新的SFR,但不能用于Timer0Timer1


133 SBIT:特殊功能位声明


在典型的8051应用问题中,经常需要单独访问SFR中的位,C51扩充功能使之成为可能,特殊位,象SFR一样,不与标准C语言兼容,使用保留字


“sbit”可访问位寻址对象。与SFR声明一样,用保留字“sbit”声明某些特殊位接受符号名,“=”后语句将绝对值地址赋给变量名,这种地


址分配有三种方法:


方法1sfr_name^int_constant


当字节是特殊功能寄存器的地址可用这个方法。sfr_name必须是已定义的SFR的名字,“^”后的语句定义了基地址上的特殊位的位置,该位置


必须是一个07的数。


例: sfr   PSW="0xD0"


sfr   LE="0xA8"


sbit OV="PSW"^2


sbit CY="PSW"^7


方法2int_constant^int_constant


这种方法以一整常数作基地址,该值必须在0x800xFF之间,并能被8整除,确定位的位置方法同上。


例: sbit OV="0xD0"^2


sbit CV="0xD0"^7


sbit EA="0xA8"^7


方法3 int_constant


这种方法是将位的绝对地址赋给变量,地址必须位于0x800xFF之间。


例: sbit OV="0xD2"


   sbit CY="0xD7"


   sbit EA="0xAF"


特殊功能位代表了一个独立的声明类,它不能与其它声明和位域互换。


134 BIT:位标量声明


除了通常的C数据类型外,C51编译器支持“bit”数据类型,对此有下列扩充与限制:


1) 函数可包含类型为“bit”的参数,也可将其作为返回值。


bit bfuncbit b0bit b1{


/*……*/


returnb1);


}


注:使用禁止中断(#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中,因此存贮器类型限制为DATAIDATA,声明为其它存贮器类型


都将导致编译出错。


135 可位寻址对象


可位寻址对象指可以字节或位寻址的对象,当对象位于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而言是07,对于int/uint/short/ushort而言是015,对于long/ulong而言是031


在编译器内存贮器类型bdatadata一样操作,并且只作与可再定位的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类型所用,如果需要这样做,浮点标量必须与一个长整型标量一起放入一个联合中并且位位置必须由长整


型标题指定(见上例)。


14 存贮器类型


C51编译器完全支持8051微处理器及其系列的结构,可完全访问MCS-51硬件系统所有部分。每个变量可准确地赋予不同的存贮器类型(data


idatapdataxdatacode)。访问内部数据存贮器(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;


如果在变量说明时略去存贮器类型标志符,编译器会自动选择默认的存贮器类型。默认的存贮器类型进一步由控制指令SMALLCOMPACT


LARGE限制。例如:如果声明char charvar,则默认的存贮器模式为SMALLcharvar放在data存贮器;如果使用COMPACT模式,则charvar放入


idata存贮区;在使用LARGE模式的情况下,charvar被放入外部存贮区或xdata存贮区。


15 存贮器模式


存贮器模式决定了自动变量和默认存贮器类型,参数传递区和无明确存贮区类型的说明。在固定的存贮器地址变量参数传递是C51的一个标准


特征,在SMALL模式下参数传递是在内部数据存贮区中完成的。LARGRECOMPACT模式允许参数在外部存贮器中传递。C51同时也支持混合模式


,例如在LARGE模式下生成的程序可将一些函数分页放入SMALL模式中从而加快执行速度。


存贮器模式                                       


SMALL 参数及局部变量放入可直接寻址的内部寄存器(最大128bytes,默认存贮器类型是DATA


COMAPCT 参数及局部变量放入分页外内部存贮区(最大256bytes,默认存贮器类型是PDATA


LARGE 参数及局部变量直接放入外部数据存贮器(最大64K,默认存贮器类型是XDATA


16 指针


Franklin C-51支持基于存贮器的一般指针


1.6.1 基于存贮器的指针


基于存贮器的指针由C源代码中存贮器类型决定并在编译时确定,用这种指针可高效访问对象且只需一个字节(idata*data*pdata*)或2


个字节(code*xdata*)。操作较短指针的代码被缩短,一般被内行编码;库调用不再必要。


声明举例:


char xdata *pt XDATA存贮器中声明一个指向对象类型为“CHAR”的指针。指针默认自身在默认存贮区(决定于编译模式),长度为2字节


。(值为00XFFFF


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 printfvoid *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]*/


 


 


 


 


 


 


 


 


 


 


 

文章评论0条评论)

登录后参与讨论
我要评论
0
10
关闭 站长推荐上一条 /2 下一条