C51学习笔记

Posted 十日十乞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C51学习笔记相关的知识,希望对你有一定的参考价值。

转自:http://blog.csdn.net/gongyuan073/article/details/7856878

单片机C51学习笔记

一,   C51内存结构深度剖析

二,   reg51.头文件剖析

三,   浅淡变量类型及其作用域

四,   C51常用头文件

五,   浅谈中断

六,   C51编译器的限制

七,                        小淡C51指针

八,                        预处理命令

      

                  

一,C51内存结构深度剖析

在编写应用程序时,定义一个变量,一个数组,或是说一个固定表格,到底存储在什么地方;

当定义变量大小超过MCU的内存范围时怎么办;

如何控制变量定义不超过存储范围;

以及如何定义变量才能使得变量访问速度最快,写出的程序运行效率最高。以下将一一解答。

 

类关键字(六类存储类型)

data  idata  xdata   pdata  code  bdata

    code:  code memory (程序存储器也即只读存储器)用来保存常量或是程序。code memory  采用16位地址线编码,可以是在片内,或是片外,大小被限制在64KB

            作用:定义常量,如八段数码表或是编程使用的常,在定义时加上code   或明确指明定义的常量保存到code memory(只读)

            使用方法:

              char  code  table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};

               此关键字的使用方法等同于const

 

data     data memory (数据存储区)只能用于声明变量,不能用来声明函数,该区域位于片内,采用8位地址线编码,具有最快的存储速度,但是数量被限制在128byte或更少。

         使用方法:

           unsigned char data fast_variable=0;

 

   idata       idata memory(数据存储区)只能用于声明变量,不能用来声明函数. 该区域位于片内,采用8位地址线编码,内存大小被限制在256byte或更少。该区域的低地址区与data memory地址一致;高地址区域是52系列在51系列基础上扩展的并与特殊功能寄存器具有相同地址编码的区域。即:data memory是idata memory的一个子集。

            

   xdata      xdata memory  只能用于声明变量,不能用来声明函数,该区域位于MCU

             外部,采用16位地址线进行编码,存储大小被限制在64KB以内。

             使用方法:

               unsigned char xdata count=0;

 

 

   pdata     pdata memory   只能用于声明变量,不能用来声明函数,该区域位于MCU外部,采用8位地址线进行编码。存储大小限制在256byte. 是xdata memory的低256byte。为其子集。

             使用方法

               unsigned char pdata count=0;

 

   bdata     bdata memory 只能用于声明变量,不能用来声明函数。该区域位于8051内部位数据地址。定义的量保存在内部位地址空间,可用位指令直接读写。

              使用方法:

                 unsigned char bdata varab=0

 

  注:有些资料讲,定义字符型变量时,在缺省unsigned 时,字符型变量,默认为无符号,与标准C不同,但我在Keil uVision3中测试的时候发现并非如此。在缺省的情况下默认为有符号。或许在以前的编译器是默认为无符号。所以看到有的资料上面这样讲的时候,要注意一下,不同的编译器或许不同。所以我们在写程序的时候,还是乖乖的把unsigned signed 加上,咱也别偷这个懒。

 2函数的参数和局部变量的存储模式

  C51 编译器允许采用三种存储器模式:SMALL,COMPACT 和LARGE。一个函数的存储器模式确定了函数的参数的局部变量在内存中的地址空间。处于SMALL模式下的函数参数和局部变量位于8051单片机内部RAM中,处于COMPACT和LARGE模式下的函数参数和局部变量则使用单片机外部RAM。在定义一个函数时可以明确指定该函数的存储器模式。方法是在形参表列的后面加上一存储模式。

   

   示例如下:

        #pragma large                  //此预编译必须放在所有头文前面

         int  func0(char  x,y) small;

        char  func1(int  x) large;

        int   func2(char x);

 注:

   上面例子在第一行用了一个预编译命令#pragma 它的意思是告诉c51编译器在对程序进行编译时,按该预编译命令后面给出的编译控制指令LARGE进行编译,即本例程序编译时的默认存储模式为LARGE.随后定义了三个函数,第一个定义为SMALL存储模式,第二个函数定义为LARGE第三个函数未指定,在用C51进行编译时,只有最后一个函数按LARGE存储器模式处理,其它则分别按它们各自指定的存储器模式处理。

  本例说明,C51编译器允许采用所谓的存储器混合模式,即允许在一个程序中将一些函数使用一种存储模式,而其它一些则按另一种存储器模式,采用存储器混合模式编程,可以充分利用8051系列单片机中有限的存储器空间,同时还可以加快程序的执行速度。

        

 

3绝对地址访问 absacc.h(相当重要)

#define CBYTE ((unsigned char volatile code  *) 0)

#define DBYTE ((unsigned char volatile data  *) 0)

#define PBYTE ((unsigned char volatile pdata *) 0)

#define XBYTE ((unsigned char volatile xdata *) 0)

  功能:CBYTE  寻址    CODE

        DBYTE  寻址    DATA

        PBYTE  寻址    XDATA(低256)区

        XBYTE  寻址    XDATA

   例: 如下指令在对外部存储器区域访问地址0x1000

       xvar=XBYTE[0x1000];

       XBYTE[0x1000]=20;

 

#define CWORD ((unsigned int volatile code  *) 0)

#define DWORD ((unsigned int volatile data  *) 0)

#define PWORD ((unsigned int volatile pdata *) 0)

#define XWORD ((unsigned int volatile xdata *) 0)

   功能:与前面的一个宏相似,只是它们指定的数据类型为unsigned int .。

         通过灵活运用不同的数据类型,所有的8051地址空间都是可以进行访问。

      如

DWORD[0x0004]=0x12F8;

即内部数据存储器中(0x08)=0x12; (0x09)=0xF8

 

注:用以上八个函数,可以完成对单片机内部任意ROMRAM进行访问,非常方便。还有一种方法,那就是用指钟,后面会对C51的指针有详细的介绍。

 

4寄存器变量(register)

           为了提高程序的执行效率,C语言允许将一些频率最高的那些变量,定义为能够直接使用硬件寄存器的所谓的寄存器变量。定义一个变量时,在变量类型名前冠以“register” 即将该变量定义成为了寄存器变量。寄存器变量可以认为是一自动变量的一种。有效作用范围也自动变量相同。由于计算机寄存器中寄存器是有限的。不能将所有变量都定义成为寄存器变量,通常在程序中定义寄存器变量时,只是给编译器一个建议,该变量是否真正成为寄存器变量,要由编译器根据实际情况来确定。另一方面,C51编译器能够识别程序中使用频率最高的变量,在可能的情况下,即使程序中并未将该变量定义为寄存器变量,编译器也会自动将其作为寄存器变量处理。被定义的变量是否真正能成为寄存器变量,最终是由编译器决定的。

 

5内存访问杂谈

   1指钟

指钟本身是一个变量,其中存放的内容是变量的地址,也即特定的数据。8051的地址是16位的,所以指针变量本身占用两个存储单元。指针的说明与变量的说明类似,仅在指针名前加上“*”即可。

  如    int  *int_point;      声明一个整型指针

       char  *char_point;   声明一个字符型指针

  利用指针可以间接存取变量。实现这一点要用到两个特殊运算符

          &  取变量地址

          *   取指针指向单元的数据

               

 

 

示例一:

int   a,b;

                   int  *int_point;     //定义一个指向整型变量的指针

                   a=15;

                   int_point=&a;       //int_point指向 a

                  *int_point=5;        //给int_point指向的变量a 赋值5 等同于a=5;

示例二:

                  char  i,table[6],*char_point;

                  char_point=table;

                  for(i=0;i<6;i++)

                    {

                      char_point=i;

                      char_point++;

}

         指针可以进行运算,它可以与整数进行加减运算(移动指针)。但要注意,移动指针后,其地址的增减量是随指针类型而异的,如,浮点指针进行自增后,其内部将在原有的基础上加4,而字符指针当进生自增的时候,其内容将加1。原因是浮点数,占4个内存单元,而字符占一个字节。

 

宏晶科技最新一代STC12C5A360S2系列,每一个单片机出厂时都有全球唯一身份证号码(ID号),用户可以在单片机上电后读取内部RAM单元F1H~F7H的数值,来获取此单片机的唯一身份证号码。使用MOV  @Ri 指令来读取。下面介绍C51 获取方法:

        char  id[7]={0};

        char  i;

        char idata  *point;

        for(i=0;i<7;i++)

           {

              id[i]=*point;

              point++;

}

  

(此处只是对指针做一个小的介绍,达到访问内部任何空间的方式,后述有对指针使用的详细介绍)

2SFRRAM ROM的直接存取

C51提供了一组可以直接对其操作的扩展函数

若源程序中,#include包含头文件,io51.h 后,就可以在扩展函数中使用特殊功能寄存器的地址名,以增强程序的可读性:

 

   此方法对SFR,RAM,ROM的直接存取不建议使用.因为,io51.h这个头文件在KEIL中无法打开,可用指针,或是采用absacc.h头文件,

 

3 PWMPCA

STC12系列有两路PWM/PCA

     PWM:(Pulse Width Modulation)脉宽调制,是一种使用程序来控制波形占空比,周期,相位波形的技术。

     PCA:(Programmable Counter Array)可编程计数阵列,它比通常的定时/计数器的定时能力强,需要CPU的干预少。其优势一是软件简单,二是精度大有提高。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

二, reg51.头文件剖析

  

      我们平时写单片机应用程序的时候,所使用的头文件大多都是用的的reg51.h或是用reg52.h。会写C51的人都会用,但对其头文件内部的定义有所了解的人确并不多。

下面对其内部做详细解释,方便读者作进一步的了解,并能运用各类型号的单片机。因为增强型号的单片机的增强功能都是通过特殊功能寄存器控制。

 

打开   reg52.h  头文件,会发现是由大量的 sfr ,sbit的声明组成,甚至于还有sfr16.其实这样的声明都是与单片机内部功能寄存器(特殊功能寄存器)联系起来的,下面对其做出详细解释

 

sfr:  声明变量

 SFR 声明一个变量,它的声明与其它的C变量声明基本相同,唯一的区别,SFR在声明的同时为其指定特殊功能寄存器作为存储地址,而不同于C变量声明的整型,字符型等等由编译器自动分配存储空间。

 

   如reg52.h头文件,第一条声明就是sfr P0    = 0x80;

此处声明一个变量P0,并指定其存储地址为特殊功能寄存器0x80;,在加入reg52.h头文件后。编写应用程序时P0就可以直接使用而无需定义,对P0的操作就是,对内部特殊功能寄存器(0x80对应用MCU的P0口)的操作,可进行读写操作。

如果将第一条声明改为sfr K0    = 0x80; 那么,如果要把单片机的P0口全部拉低,则不能写P0=0x00;而应保存后再在应用程序中写成K0=0x00;否则编译器会提示“P0为未定义标识符”

   使用方法:

     sfr  [variable]  =  [address]   //为变量分配一个特殊功能寄存器。

  

1  等号右边,只能是十进制,十六进制整型的数据常量,,不允许带操作符的表达式

      经典的8051内核支持的SFR地址从0x80H~0xFF 飞利浦80C51MX系列0x180H~0x1FF

   2  SFR不能声明于任何函数内部,包括main函数。只能声明于函数外。

   3  用SFR声明一个变量后,不能用取地址运算符&获取其地址, 编译无法通过,编译器会提示非法操作。

   4  有一点须特别注意,51内核0x80~0xff,为特殊功能寄存器地址区间,但并不是所有的地址都有定义,如果说你所用的MCU芯片上对于某个地址没有定义,那么用sfr在定义变量的时候,不要把变量的地址分配到未定义的特殊功能寄存器上,虽然编译时能通过,用KEIL仿真时貌似是没有问题,但下载到芯片里运行时,是会出问题的。比如说,向一个未定义的特殊功能寄存器执行读操作,读出来的就是一个未知的数。(读者可自行测试,先把串口通信调通,然后做一个简单的人机交互。读出一个数后,再发给计算机,用串口调试助手或是串口监控查看。这用方法在仿真的时候很有用。)所以具体那些特殊功能寄存器能够用,就要查看你使用的芯片手册。

5           若遇到增强性的单片机,只要知道其扩展的特殊功能寄存器的地址,用SFR定

就可以很方便进行编程。

 

 

 

sbit:  声明变量

   sbit 同样是声明一个变量,和SFR 使用方法类似,但是SBIT是用来声明一个位变量,因为,在51系列的应用中,非常有必要对SFR的单个位进行存取,而通过bit 数据类型,使其具备位寻址功能。

      如,在reg52.h中有如下声明

          sfr IE    = 0xA8;

          sbit EA    = IE^7;

sbit ET2   = IE^5; //8052 only

sbit ES    = IE^4;

sbit ET1   = IE^3;

sbit EX1   = IE^2;

sbit ET0   = IE^1;

sbit EX0   = IE^0;

   所以,对EA的操作即是对IE最高位的操作。

 

但如果想让 SP   DPL  DPH   PCON   TMOC  TL0  TL1  TH0  TH1  SBUF这些特殊功能寄存器具备位寻址,采用上述如IE类似的定义,是不行的,虽然修改后,在编译的时候不会出现错误,但只要用到你定义的位变量名时就会出错。原因是,只有特殊功能寄存器的地址是8的倍数(十六进制以0或8结尾)才能进行位寻址。

 打开reg52.h头文件可以看到,所有用sbit声明了的特殊功能寄存器的地址均是以0或8结尾

如硬要达到上述要求,可用带参的宏定义来完成。此处不做详细说明(意义并不大)。

 

下面对sbit的使用做详细介绍:

随着8051的应用,非常有必要对特殊功能寄存器的单个bit位进行存取,C51编译器通过sbit 数据类型,提供了对特殊功能寄存器的位操作。

       以下是sbit的三种应用形式:

一,    sbit  name = sfr-name^bit-position;

  sfr   PSW =0xD0;

  sfr   IE  =0xA8;

 

  sbit   OV= PSW^2;

  sbit   CY=PSW^7;

  sbit   EA= IE^7;

二,    sbit  name= sft-address^bit-position;

        sbit  OV =0xD0^2;

        sbit  CY =0xD0^7;

        sbit  EA =0xA8^7;

三,    sbit  name= sbit-address;

        sbit  OV =0xD2;

        sbit  CY =0xD7;

        sbit  EA =0xAF;

 

 

   现对上述三种形式的声明做必要的说明

       第一种形式sbit  name = sfr-name^bit-position;sbit   OV= PSW^2;  当中的这个特殊功能寄存器必须在此之前已经用sfr 定义,否则编译会出错。

bit-position范围从0~7;

       第二种形式  sbit  name= sft-address^bit-positionsbit  OV =0xD0^2;  与第一种形式不同之外在于,此处直接使用PSW的地址.第一种形式须先定义PSW

       第三种形式.  sbit  name= sbit-address  sbit  OV =0xD2 是直接用的OV的地址

OV的地址计算方式,是OV所在的寄存器地址加上OV的bit-position

                     

      注意:

             不是所有的SFR都可位寻址。只有特殊功能寄存器的地址是8的倍数(十六进制以0或8结尾)才能进行位寻址,并且sbit声明的变量名,虽可以是任意取,但是最好不要以下划线开头,因为以下划线开头的都保留给了C51的头文件做保留字。

          

 

sfr16: 声明变量

      许多8051的派生型单片机,用两个连续地址的特殊功能寄存器,来存储一个16bit的值。例如,8052就用了0xCC和0xCD来保存定时/计数寄存器2的高字节和低字节。编译器提供sfr16这种数据类型,来保存两个字节的数据。虚拟出一个16bit的寄存器。

       如下:

               sfr16 T2 = 0xCC

     存储方面为小端存储方式,低字节在前,高字节在后。定义时,只写低字节地址,如上,则定义T2为一个16位的特殊功能寄存器。 T2L= 0CCh, T2H= 0CDh

    使用方法:

     sfr  [variable]  =  [low_address]      

1  等号右边,只写两个特殊功能寄存器的低地址,且只能是十进制,十六进制的整型数据常量,不允许带操作符的表达式

   2  SFR不能声明于任何函数内部,包括main函数。只能声明于函数外。

   3  用SFR声明一个变量后,不能用取地址运算符&获取其地址, 编译无法通过,编译器会提示非法操作。

      4 当你向一个sfr16写入数据的时候,KEIL CX51 编译器生成的代码,是先写高字节,后写低字节,(可通过返汇编窗口查看)在有些情况下,这并非我们所想要的操作顺序。使用时,须注意。

      5 当你所要写入sfr16的数据,当是高字节先写还是低字节先写非常重要的时候,就只能用sfr 这个关键字来定义,并且任意时刻只保存一个字节,这样操作才能保证写入正确。

 

 

 

 

 

 

 

三, 浅淡变量类型及其作用域

        

变量可分为   1.局部变量

                                           (按变量的有效作用范围划分)

 2.全局变量

 

    1.局部变量

         是指函数内部(包括main函数)定义的变量,仅在定义它的那个函数范围内有效,不同函数可使用相同的局部变量名,函数的形式参数也属于局部变量,在一个函数的内部复合语句中也可以定义局部变量,该局部变量只在该复合语合中有效。

    2.全局变量

         是指函数外部定义的变量,以称外部变量。可为多个函数共同使用,其有效作用范围是从它定义开始到整个程序文件结束。如果全局变量,定义在一个程序文件的开始处,则在整个程序文件范围都可以使用它,如果一个全局变量不是在程序文件的开始处定义,但又希望在它定义之前的函数中引用该变量,这时应在引用该变量的函数中用关键字extern将其声明为“外部变量”。另个,如果在一个程序模块文件中引用另一个程序模块文件中定义的变量时,也必须用extern进行说明。

          外部变量的说明与外部变量的定义是不同的,外部变量定义只能有一次,定义的位置在所有函数之外,而同一个程序文件中(不是指模块文件)的外部变量声明可以有多次,声明的置在需要引用该变量的函数之内,外部变量的声明的作用只是声明该变量是一个已经在外部定义过了的变量而已。

          如在同一个程序文件中,全局变量与局部变量同名,则在局部变量的有效作用范围之内,全局变量不起作用,也就是说,局部变量的优先级比全局变量高。

          在编写C语言程序时,不是特别必要的地方一般不要使用全局变量,而应当尽可能的使用局部变量。因为局部变量只在使用它的时候,才为其分配内存单元,而全局变量在整个程序的执行过程中都要占用内存单元,且当全局变量使用过多时,会降低程序的可读性。

 

     变量的存储种类

   1自动变量(auto)

        定义变量时,在变量类型名前加上  “auto” ,自动变量是C语言中使用最为广泛的一类变量,在函数体内部或是复合语句内部定义的变量,如果省略了存储种类说明,则该变量默认为自动变量。

       例如:

            {               等价于      {

               char  x;                      auto  char x;

               int   y;                      auto  int   y;

               ……                         ……

            }                            }

     

注:

         自动变量的作用范围在定义它的函数体或是复合语句内部,只有在定义它的函数内被调用,或是定义它的复合语句被执行时,编译器才会为其分配内存空间,开始其生存期。当函数调用结束返回,或复合语句执行结束,自动变量所占用的内存空间就被释放,变量的值当然也就不复存在,其生存期结束。当函数再次调用,或是复合语句被再次执行时,编译器又会为其内部的自动变量重新分配内存空间。但不会保留上一次运行的值。而必须被重新分配。因此自动变量始终是相对于函数或复合语句的局部变量。

2  外部变量(extern)

         用说明符“extern”定义的变量称为外部变量。按缺省规则,凡是在所有函数之前,在函数外部定义的变量都是外部变量,定义时可以不写extern说明符,但是一个函数体内说明一个已在该函数体外或别的程序模块文件中定义过的外部变量时,刚必须要使用extern说明符。外部变量定义后,它就被分配了固定的内存空间。外部变量的生存期为程序的整个执行时间。 外部变量的存储不会随函数或复合语句执行完毕而释放,因此外部变量属于全局变量。

          C语言允许将大型程序分解为若干个独立的程序模块文件,各个模块可分别进行编译,然后再将它们连接在一起,如果某个变量需要在所有程序模块文件中使用,只要在一个程序模块文件中将该变量定义成全局变量,而在其它程序模块文件中用extern声明该变量是已被定义过的外部变量就可以了。

          函数是可以相互调用的,定义函数时,如果冠以关键字extern 即将其明确定义为一个外部函数。例如  extern  int  func2(char a,b) 。如果在定义函数时省略关键字extern,则隐含为外部函数。如果在调用一个在本程序模块文件以外的其它模块文件所定义的函数,则必须要用关键字extern说明被调用的函数是一个外部函数。对于具有外部函数相互调用的多模块程序,可用C51编译器分别对各个模块文件进行编译,最后再用L51连接定位器将它们连接成为一个完整的程序。

 

 

 

 

如下为一个多模块程序

程序模块1,文件名为file1.c

#include<stdio.h>

  int x=5;

void main()

 {

   extern void fun1(  );

   extern viod fun2(int  y);

   fun1( );

   fun1( );

   fun1( );

printf( “\n%d  %d\n”,x,fun2(x));

}

 

程序模块2,文件名为file2.c

#include<stdio.h>

extern  int x;

void fun1( )

 {

  static  int  a=5;    //静态变量只在第一次调用函数时赋值,退出函数时

//会保留上次的值,下次调用不再重新赋值。

 int b=5;

printf(“%d  %d  %d  |”,a,b,x);

a-=2;

b-=2

x-=2;

printf(“%d  %d  %d  |”,a,b,x);

}

int fun2(int y)

 {

  return(35*x*y);

}

程序执行如果如下:

       5    5    5  |   3    3    3

       3    5    3  |   1    3    1

       1    5    1  |   -1    3    1

       -1    35

注:

   C语言不允许在一个函数内嵌套定义另一个函数。为了能够访问不同文件中各个函数的变量,除了可以采用参数传递的方法外,还可以采用外部变量的方法,上面的例子就说了这一点。不过,尽管使用外部变量在不同函数之间传递数据有时比使用函数参数传递更为方便,不过当外部变量过多时,会增加程序的调试排错的困难。使得程序不便于维护。别外不通过参数传递直接在函数中改变全局变量的值,有时还会发生一些意想不到的副作用。因些最好还是使用函数参数来传递数据。

3寄存器变量(register

           为了提高程序的执行效率,C语言允许将一些频率最高的那些变量,定义为能够直接使用硬件寄存器的所谓的寄存器变量。定义一个变量时,在变量类型名前冠以“register” 即将该变量定义成为了寄存器变量。寄存器变量可以认为是一自动变量的一种。有效作用范围也自动变量相同。由于计算机寄存器中寄存器是有限的。不能将所有变量都定义成为寄存器变量,通常在程序中定义寄存器变量时,只是给编译器一个建议,该变量是否真正成为寄存器变量,要由编译器根据实际情况来确定。另一方面,C51编译器能够识别程序中使用频率最高的变量,在可能的情况下,即使程序中并未将该变量定义为寄存器变量,编译器也会自动将其作为寄存器变量处理。被定义的变量是否真正能成为寄存器变量,最终是由编译器决定的。

4静态变量(static)

使用存储种类说明符“static”定义的变量为静态变量,在上面模块2程序文件中使用了一个静态变量:static  int a =5 ;由于这个变量是在函数fun1( )内部定义,因此称为内部静态变量或局部静态变量。局部静态变量始终都是存在的,但只有在定义它的函数内部进行访问,退出函数之后,变量的值仍然保持,但不能进行访问。

还有一种全局静态变量,它是在函数外部被定义的。作用范围从它的定义点开始,一直到程序结束,当一个C语言程序由若干个模块文件所组成时,全局静态变量始终存在,但它只能在被定义的模块文件中访问,其数据值可为该模块文件内的所有函数共享,退出该文件后,虽然变量的值仍然保持着,但不能被其它模块文件访问。在一个较大的程序中,这就方便了多人设计时,各自写的程序模块不会被别的模块文件所引用。

  全局静态变量和单纯的全局变量,在编译时就已经为期分配了固定的内存空间,只是他们的作用范围不同而已。

 

        局部静态变量是一种在两次函数调用之间仍能保持其值的局部变量。

        如下,局部变量的使用——计算度输出1~5的阶乘值。

           #include<stdio.h>

            int  fac( int  n)

               {

                static  int  f=1;

f=f*n;

return(f);

}

             main( )

                {

                  int  i;

                  for(i=1;i<=5;i++)

                   printf(“%d!=%d\n”,i,fac(i));

}

程序执行结果

   1!=1

   2!=2

   3!=6

   4!=24

   5!=120

       注:

          在这个程序中一共调用了5次计算阶乘的函数fac(i),每次调用后输出一个阶乘值i!,同时保留了这个i!值,以便下次再乘(i+1).由此可见,如果要保留函数上一次调用结束时的值,或是在初始化之后变量只被引用而不改变其值,则这时使用局部静态变量;较为方便,以免在每调用时都要重新进行赋值,但是,使用局部静态变量需要占用较多的内存空间,而且降低了程序的可读性,因此并不建议多用局部静态变量。

 

        静态函数:

           对于函数也可以定义成为具为静态存储种类的属性,定义函数时在函数名前冠以关键字static即将其定义为一个静态函数。例如static  int  func1(char x, y)函数是外部型的,使用静态函数可以使该函数只局限于当前定义它的模块文件中。其它模块文件是不能调用它的。换名话说,就是在其它模块文件中可以定义与静态函数完全同名的另一个函数。不会因为程序中存在相同的函数名而发生函数调用时的混乱。 这一点对于进行模块化程序设计是很有用的。

 

, C51常用头文件

在KEIL 中,对于单片机所使用的头文件,除了reg51 reg52以外,还有一些从各芯片制商的官网下载与reg51,reg52功能类似的头文件,需了解透外,还要对各类型单片机均可通用且相当有用的的头文件,做相应的了解。因为,内部所包含的函数与宏定义,可以及大的方便我们编写应用程序。

1字符函数  ctype.h

  

   1  extern bit isalpha(char);

        功能:检查参数字符是否为英文字母,是则返回1

2         extern bit isalnum(char)

     功能:检查字符是否为英文字母或数字字符,是则返回1

3         extern bit iscntrl(char)

            功能:检查参数值是否在0x00~0x1f 之间或等于0x7f,是则返回1

4   extern bit isdigit(char)

     功能: 检查参数是否为数字字符,是则返回1

5          extern bit isgraph(char)

            功能: 检查参数值是否为可打印字符,是则返回1,可打印字符为0x21~0x7e

6         extern bit isprint(char)

     功能:除了与isgraph相同之外,还接受空格符0x20

7         extern bit ispunct(char)

  功能:不做介绍。

8         extern bit islower(char)

     功能:检查参数字符的值是否为小写英文字母,是则返回1

9         extern bit isupper(char)

            功能:检查参数字符的值是否为大写英文字母,是则返回1

10     extern bit isspace(char)

     功能:检查字符是否为下列之一,空格,制表符,回车,换行,垂直制表符和送纸。如果为真则返回1

11     extern bit isxdigit(char)

            功能:检查参数字符是否为16进制数字字符,是则返回1

12     extern char toint(char)

     功能:将ASCII字符0~9  a~f(大小写无关)转换成对应的16进制数字,

返回值00H~0FH

13     extern char tolower(char)

            功能:将大写字符转换成小写形式,如字符变量不在A~Z之间,则不作转换而直接返回该字符

14     extern char toupper(char)

     功能:将小写字符转换成大写形式,如字符变量不在a~z之间,则不作转换而直接返回该字符

15     define toascii(c)  ((c)&0x7f)

功能:该宏将任何整形数值缩小到有效的ASCII范围之内,它将变量和0x7f相与从而去掉第7位以上的所有数位

16     #define tolower(c)  (c-‘A’+’a’)

功能:该宏将字符与常数0x20 逐位相或

17     #define toupper(c)  ((c)-‘a’+’A’)

功能:该宏将字符与常数0xdf 逐位相与

2数学函数 math.h

extern int    abs  (int   val);

extern char  cabs  (char  val);

extern long  labs  (long  val);

extern float  fabs  (float  val);

功能:返回绝对值。上面四个函数,除了形参和返回值不一样之外,

其它功能完全相同。

 

extern float exp    (float val);

extern float log    (float val);

extern float log10  (float val);

  功能: exp   返回eval

         log   返回 val 的自然对数

         log10 返回 以10为底,val的对数

        

extern float sqrt  (float val);

   功能:      返回val的正平方根

 

      extern int rand();

      extern void srand(int n);

         功能:  rand返回一个0到32767之间的伪随机数,srand用来将随机数发生器初始化成一个已知的(期望)值。

        Keil uVision3中的math.h库中,不包含此函数。

 

       extern float sin   (float val);

extern float cos   (float val);

extern float tan   (float val);

   功能:   返回val的正弦,余弦,正切值。val为弧度  fabs(var) <=65535

extern float asin  (float val);

extern float acos  (float val);

extern float atan  (float val);

extern float atan2 (float y, float x);

  功能: asin 返回val的反正弦值。acos 返回val的反余弦值。

         atan 返回val的反正切值。

         asin atan acos的值域均为  -π/2~+π/2

        atan2返回x/y,的反正切值,其值域为-π~+π

 

extern float sinh  (float val);

extern float cosh  (float val);

extern float tanh  (float val);

  功能:cosh返回var的双曲余弦值,sinh返回var的双曲正弦值,

        tanh返回var的双曲正切值。

 

extern float ceil  (float val);

  功能:  向上取整,返回一个大于val的最小整数。

extern float floor (float val);

  功能:  向下取整,返回一个小于val的最大整数。

extern float pow   (float x, float y);

  功能: 计算计算xy的值。当(x=0,y<=0)或(x<0.y不是整数)时会发生错误。

extern void fpsave(struct FPBUF *p)

extern void fprestore(struct FPBUF *p)

  功能:fpsave 保存浮点了程序的状态,fprestore恢复浮点子程序的原始状态,当中断程序中需要执行浮点运算时,这两个函数是很有用的。

      注:   Keil uVision3中的math.h库中,不包含此函数。

 

3绝对地址访问 absacc.h

#define CBYTE ((unsigned char volatile code  *) 0)

#define DBYTE ((unsigned char volatile data  *) 0)

#define PBYTE ((unsigned char volatile pdata *) 0)

#define XBYTE ((unsigned char volatile xdata *) 0)

  功能:CBYTE  寻址    CODE

        DBYTE  寻址    DATA

        PBYTE  寻址    XDATA(低256)区

        XBYTE  寻址    XDATA

   例: 如下指令在对外部存储器区域访问地址0x1000

       xvar=XBYTE[0x1000];

       XBYTE[0x1000]=20;

 

#define CWORD ((unsigned int volatile code  *) 0)

#define DWORD ((unsigned int volatile data  *) 0)

#define PWORD ((unsigned int volatile pdata *) 0)

#define XWORD ((unsigned int volatile xdata *) 0)

   功能:与前面的一个宏相似,只是它们指定的数据类型为unsigned int .。

         通过灵活运用不同的数据类型,所有的8051地址空间都是可以进行访问。

 如

DWORD[0x0004]=0x12F8;

即内部数据存储器中(0x08)=0x12; (0x09)=0xF8

 

4      内部函数 intrins.h

extern unsigned char _cror_    (unsigned char  var, unsigned char  n);

extern unsigned int  _iror_    (unsigned int    var, unsigned char  n);

extern unsigned long _lror_    (unsigned long  var, unsigned char  n);

  功能:将变量var 循环右移 n 位。

上三个函数的区别在于,参数及返回值的类型不同

extern unsigned char _

以上是关于C51学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

51单片机学习笔记3 C51数据类型及最小系统

迪文屏幕T5L平台学习笔记二:第一个C51C程序Demo

迪文屏幕T5L平台学习笔记四:C51使用printf或者sprintf注意事项

迪文屏幕T5L平台学习笔记四:C51使用printf或者sprintf注意事项

迪文屏幕T5L平台学习笔记五:C51使用UART2打印log

proteus学习笔记一:点亮LED