C51

该文章的内容仅限于STC15F2K60S2单片机的程序设计

一.常用功能

1.逻辑运算和位运算,

  1.逻辑运算符:与(&&),或(||);非(!); 返回0为假,1为真,运算符连接的多为条件语句,例如 (a<10)&&(b==1);

  2.位运算符

  位与(&),位或(|),位异或(^),取反(~),左移(<<),右移(>>)

  位运算符出现的时候,都需要将运算数变成二进制形式,进行位运算,其中位与可以用于清0,位或可以用于置1,对于有符号数,在右移时,符号位将随同移动,当为正数时,最高位补0,而为负数时,最高位为1,最高位补0或是1取决于编译系统的规定(编译系统的规定是什么东西??)在左移的时候不需要考虑正负数的情况吗??

2.预处理

  1.宏定义(define)

    1.无参宏定义

      #define 标识符 字符串                   字符串可以是常数,表达式,格式串等       终止宏定义,可以用#undef命令

    2.带参宏定义

      #define 宏名(形参表) 字符串

  2.文件包含(include)

    #include "文件名"     功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连接成一个源文件。

    比如一些公用的符号常量或者宏定义等可以单独组成一个文件,在其他文件的开头包含该文件,就避免了重复书写公用量

    亲试 ,可以包含.c,.h文件,而且在.c 或者.h文件中编写的程序,定义的变量,在main()中都可以直接使用

    #include "文件名"   #include <文件名>    二者的区别是: 使用尖括号表示在包含文件目录中去查找(包含目录由用户在开发环境中设置,不清楚怎么设置,从未设置过。。),而不在源文件目录去查找,使用双引号表示首先在当前的源文件目录中查找,若找不到,才到包含目录中去查找。  

  3.条件编译

    就是按照不同 的条件去编译不同的程序部分,从而产生不同的目标代码文件,特别是在操作系统的裁剪中,经常用到条件编译。

    1.#ifdef 标识符

        程序段1

     #else 

        程序段2

     #endif

    它的功能是:如果标识符已经被#define命令定义过,则对程序段1进行编译,否则对于程序段2进行编译,当然也可以没有#else 程序段2

    2.#ifndef 标识符

        程序段1

      #else

        程序段2

      #endif

     与1.的功能正好相反,如果没有被定义过,则编译1

   3.#if 常量表达式

      程序段1

     #else

      程序段2

    #endif

    功能是:如果常量表达式的值为真(非0),则对1进行编译

   条件编译当然也可以用条件语句来实现,但是用条件语句会对整个源程序进行编译,生成的目标代码程序较长,而采用条件编译,则根据条件只编译程序段1,或者程序段2,生成的目标程序较短。

二. Keil C 和 ANSI C

  C51的基本语法和ANSI C相同,但对ANSI C进行了扩展,大多数扩展功能都是直接针对8051内核单片机的

  1.扩展关键字(19个)

    _at_      sbit   sfr     bit    sfr16   idata    bdata    xdata    pdata   data  code   alien   small   compact   large   using    reentrant   interrupt   _task_

  1.内存区域

    1.程序存储器

      code: 程序存储区,可以使用code定义表格常数

    2.内部数据存储器(内部RAM)

    .data 直接寻址区,内部RAM的低128B,地址范围是00H-7FH

    .idata 间接寻址区,包括整个内部的RAM区 256B,地址范围是00H-0FFH

    .bdata 可位寻址区,地址范围是20H-2FH

    3。外部数据存储器

    外部RAM视使用情况可由以下关键字标示

    xdata 可指定多达64KB的外部直接寻址区,地址范围是0000H-0FFFFH.

    pdata 能访问1页(256B)的外部RAM(很少用)

  例如 unsigned char xdata arr[10][4][4];

    4.特殊功能寄存器(sfr)

    STC15F2K60S2单片机的特殊功能寄存器(sfr)寻址区,用来控制定时器,计数器,串口,I/O即其他部件,为了支持SFR及其可位寻址的声明,引入sfr,sbit等关键词

    sfr 字节寻址      sfr P0=0X80;   0x80为P0口的地址,=后为常数,并且这个常数必须在特殊功能寄存器的地址范围内,位于0x80到0xff之间

    sfr16 字寻址         sfr16 DPTR = 0X82;  //指定DPTR的地址 DPL = 0X82   DPH = 0X83

    sbit 位寻址         用于声明可位寻址的特殊功能寄存器的位变量  sbit  CY = PSW^7;    sbit OV = 0XD0^2;   sbit EA = 0XAF;  (PSW为已经定义的SFR的名字)

    对于大多数8051内核单片机成员,KEIL提供了一个包含所有特殊功能寄存器和他们位定义的同文件reg51.h

  2._at_ 关键字

    若要实现变量的绝对定位(称为绝对变量),可以直接在数据定义后加上“_at_ 常数地址”即可    注意: 一。绝对变量不能初始化 二。bit型函数以及变量不能用_at_指定

  例如: unsigned char idata  ADCdata _at_ 0x40;

     unsigned char xdata buffer[20] _at_ 0x0010; 指定buffer数组从XRAM的0010H单元开始

  3.存储模式

     1.small 模式             

    所有的变量默认在内部数据存储器,和使用data 指定存储器类型的方式一样    优点是: 效率高,访问速度快,缺点是:空间有限,只适合小程序

    2.compact 模式

    缺省变量位于外部RAM区的一页内,和pdata指定存储器类型一样   特点: 空间比small宽裕,速度比small慢,比large快,是一种中间状态

    3.large 模式

    。。。。。。多大64kb的外部RAM区,和使用xdata一样,使用数据指针DPTR进行寻址,效率低(DPTR是什么啊。。。。。。。)

   4.变量或数据类型

    bit 

    bit型变量用于变量类型和函数声明。函数返回值等,存储于内部RAM的20H-2FH单元中

    注意: 使用禁止中断(#pragma disable)或包含明确的寄存器切换(using n)的函数不能返回位值,否则,编译器会识别出来并产生一个错误信息

        位不能声明为一个指针  不能有bit数组

      sbit可以 声明可独立访问可位寻址对象的位,  sbit声明要求基址对象的存储器的类型是bdata,否则只有绝对的位声明方法是合法的。 位的位置的最大值依赖于指定的基类型,对于char/unsigned char 是 0-7,对于 int /unsigned int /short/unsigned short 是0-15,对于long/unsigned long 是0-31,

例如      int bdata bittest _at_ 0x20;   //可以省略“_at_ 0x20"

    sbit bit0 = bittest^0;     //0x20单元的第0位

    sbit bit1 = bittest^15; //0x21单元的第7位

    注意:可位寻址对象的位声明必须放到main函数外部,作为全局变量使用

 2.扩展i/o口的使用

  由于使用C语言访问外部I/O时,要用到指针的功能,首先介绍Keil C51指针

  1.Keil C51

    Keil C51支持一般指针和存储器指针。一般指针的使用和声明和标准C相同,同时还可以说明指针的存储类型

    例如,如下指针都为指向保存在外部RAM中的unsigned char 数据的指针

      unsigned char xdata *pt;    //pt本身依据存储模式存放

        unsigned char xdata *data pt;   //pt被保存在内部的RAM中

      unsigned char xdatat *xdata pt;  //Pt被保存在外部RAM中

  一般指针使用三个字节存放,分别为存储器类型,高位偏移量和低位偏移量

    基于存储器的指针,说明时即指定了存储类型,例如 char data *str;   //str指向data区中的char型数据  这种指针存放时只需要两个字节

       堆栈指针SP        在编译后生成的.51文件中可以查看栈顶的位置,看一下是不是有足够的栈空间可用        另外,C51在startup.A51中设置SP指针,用CODE选项生成的汇编代码中找不到这段代码                    startup.A51 是C51的初始化代码,单片机复位以后先执行这一段代码,完成初始化后由它调用main()函数,特殊需要时,可以修改这段代码,然后连接到用户的程序中去。

    2.外部扩展I/O口的访问

    1.使用自定义指针。由于片外I/O端口和片外存储器统一编址,所以可以定义xdata类型的指针访问外部i/o端口   例如 char xdata *com;   com= 0x7ff3;  *com = 0x81;   //输出81H到端口 char xdata *com;   com = 0x7FF0; char i; i = *com;  //读PA端口到变量i   

    2.使用C51预定义指针    

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

                   ................

      例如  #include <absacc.h>

          #define PORTA XBYTE[0x7ff0]    //PORTA为程序定义的端口名

         void main(void ){char a;    PORTA = 0X81;  //输出81H到端口0x7ff0       a = PORTA; //读端口7ff0H到变量a

   3.Keil C51 函数

    1.中断函数的声明

      通过使用关键字interrupt 和中断号(0-31),来声明,中断号告诉编译器中断服务程序的入口地址

    2.指定工作寄存器区

      使用关键字using 后跟一个0-3的数字,对应着工作寄存器0-3区  unsigned char GetKey(void) using 1{}

    3.指定存储模式     用户可以使用small ,compact 及 large 说明存储模式  例如void fun(void) small{}

    4.函数的参数传递规则

      最多只能有3个参数通过寄存器传递

    5.函数返回值一律放在寄存器中

    6.函数的重入

      可以在函数前声明函数的可重入性,只对一个函数有效,如果函数声明为不可重入的,说明该函数调用过程中不可被中断。因为单片机一般使用寄存器传递参数,内部变量一般在RAM中,函数重入时会破坏上次调用的数据

  1.在相应函数前使用”#pragma disable"声明,只允许主程序或者中断之一调用。

  2.将该函数声明为可重入的        例如void func(param...)  reentrant;

    由于一般可重入函数由主程序和中断调用,所以通常中断程序使用和主程序不一样的工作寄存器组,另外对于可重入函数,在相应的函数前面加上开关#pragma noaregs ,以禁止编译器使用绝对寄存器寻址,可生成不依赖于寄存器组的代码

     在某些实时应用中,非重入函数是不可取得,因为,函数调用时可能被中断,而在中断程序中可能再次调用这个函数,所以C51允许将函数定义为重入函数,重入函数可被递归调用和多重调用,而不用担心变量被覆盖,因为每次函数调用时的局部变量都会被单独保存。  重入函数运行比较慢,因为有模拟堆栈

          

 

补充知识点:  #pragma  的用法  :其中的#pragma message("   ");可以用于调试,亲试可用,很方便。

        http://baike.baidu.com/link?url=QtvFurlWfDOP91v2VL4WlZr0Onvrin4H2R6RcH3nK-dJeUr7TTq6DehEWFagKCCGscEP_I7rFCRh_0491fjnK#2_1

      volatile 的用法

    http://baike.baidu.com/link?url=ewte5J9jkcAgWrWkhh3fBa7TrbhWTkxWmajalpbd5FhV9uKSwc-27XXXJzsOS4uWa2VfbH6sXE-kdc-R3o5iPa

    个人认为volatile的用法很重要,尤其是它的适用情况                         

                一般说来,volatile用在如下的几个地方:
                    1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
                    2、多任务环境下各任务间共享的标志应该加volatile;
                    3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;
                另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2 中可以          禁止任务调度,3中则只能依靠硬件的良好设计了。

posted on 2015-02-24 11:27  玲先霞  阅读(461)  评论(2编辑  收藏  举报

导航