[工程科技]C8051第3章幻灯.ppt_第1页
[工程科技]C8051第3章幻灯.ppt_第2页
[工程科技]C8051第3章幻灯.ppt_第3页
[工程科技]C8051第3章幻灯.ppt_第4页
[工程科技]C8051第3章幻灯.ppt_第5页
已阅读5页,还剩71页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第3章 C51程序设计 使用C语言实现单片机编程是单片机系统开发的发展方向,C语言是通用计算机程序设计语言,现在也广泛用于单片机系统开发。 3.1 C51程序与汇编程序的不同之处 对于51单片机来说,C51编写程序比汇编语言程序具有如下优缺点。 1. 优点 (1)采用C语言编程,以行为方式描述单片机实现的任务,开发效率高、时间短。 (2)将单片机实现的任务分别用程序模块实现, (3)可移植性好 (4)提供数学函数并支持浮点运算, (5)不用深入了解单片机的指令系统,仅需要了解51单片机存储器结构。,2. 缺点 实时性比汇编语言差,因为编写汇编的时候可以清楚的知道每一条指令究竟要多少个机器周期,而C51语句与执行时间没有确切关系。 一般来说,C51程序代码量较汇编程序代码量大,但随着C编译器编译效率的提高和存储器容量的增加,已经不是大问题。 3. 使用助记符 与汇编程序一样,采用C51语言还是需要了解如何初始化单片机中众多特殊功能寄存器,因为这些寄存器是控制硬件功能的,因此需要了解单片机内部各个模块的工作原理,这也是初学单片机遇到的最大困难。 C51程序中,也是采用助记符代表寄存器地址,助记符与寄存器地址之间的对应关系保存在“头文件”中,由于每种单片机的助记符、助记符对应的寄存器地址不相同,因此每种单片机都有自己的头文件。为方便记忆,助记符常与手册中给出的特殊寄存器名相同。例如对于AT89S51单片机,就有头文件“AT89X51.H”,其内容为特殊功能寄存器的定义。 在头文件的支持下,写C51程序时可以直接用助记符代替地址,容易记忆,并增加可读性。,4. 存储类型 C51中变量的存储类型与51单片机存储空间的对应关系如下。 data 直接存取51单片机内部RAM(128B空间)。 idata 以MOV Rn间接存取52单片机內部RAM(256B空间)。 bdata 以位寻址方式存取单片机内部数据RAM中的位寻址区(16B)。 xdata 以MOVX DPTR存取外部扩展RAM(64KB空间)。 pdata 以MOVXRn分页存取外部扩展RAM(256B,外部扩展RAM的第一个页面)。 code 以MOVCA+DPTR指令存取Flash存储器(64KB空间)。 在C51中定义变量时,可以定义变量的存储类型,例如, unsigned char code sm =0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90 ,0x88,0x83,0xc6,0xA1,0x86,0x8e;,5. 需要启动文件 在51单片机中运行用户所编制的C51程序时,在执行main()程序时,需要先运行启动程序startup.a51,该汇编程序的工作是把idata、xdata、pdata存储区清0,初始化堆栈。还要执行init.a51程序初始化非零变量。 6. Keil软件编译C51程序 在Keil软件中的文本编辑器编辑完成C51程序(.c )后,经过C51编译器编译后,生成浮动目标文件(.obj)和列表文件(.lst);在库文件的支持下,经过L51链接器后,得到绝对定位目标文件(.hex)。 Keil软件开发C51的过程与开发汇编程序的过程基本相同,但需要注意如下两点。 在使用Keil软件时,注意汇编程序不需要启动程序startup.a51,而C51程序需要该启动程序;另外C51程序文件的扩展名是.c,而汇编程序文件的扩展名是.asm。,3.2 C语言基础知识 3.2.1 C语言的标识符和关键字 C语言中的部分保留字如下: atuo break case const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union void volatile while 在C保留字的基础上,C51的关键字如下: bit sbit sfr sfr16 data bdata idata pdata xdata code interrupt reentrant using,3.2.2 数据类型,3.2.3 常量 在程序运行过程中,数值不改变的量称为常量。 (1)整型常量 (2)字符型常量 (3)字符串型常量由双引号内的字符组成,如“test”,“OK”等。 (4)符号常量 代替常量的标识符,称为符号常量。 符号常量的定义: #define False 0x0; /用预定义语句可以定义常量 #define True 0x1; /这里定义False为0,True为1 #define on=0xfe; #define off=0xff; 使用符号常量可以使程序更易读,因为可以“见名知意”而且可以做到一改全改。,3.2.4 变量 1. 变量的定义 在C语言中,定义变量需要四方面的内容: (1)变量的数据类型,例如int,char等。 (2)变量的作用范围,与变量声明的位置有关。 (3)变量的存储种类,就是变量的存储方法,不同的存储方法,影响变量的存在时间。 (4)变量的存储类型,就是确定变量存储在哪类存储器中。 2. 变量的定义格式 C51定义一个变量的格式如下: 存储类型说明符 类型说明符 修饰符 标识符 =初值,标识符 =初值; 说明: 类型说明符与标识符必须存在。 算术运算尽可能使用无符号数。 浮点数只有32位。 对比与C语言,C51增加了bit、sfr、sfr16和sbit类型说明符。 存储类型说明符, 存储类型说明符 存储类型说明符为:自动变量(auto)、外部变量(extern)、静态变量(static)和寄存器变量 (register): auto变量: 在变量前加存储种类说明符号auto,则该变量为自动变量。 static变量: 使用存储种类说明符号static定义的变量称为静态变量, extern变量: 使用存储类型说明符号extern声明的变量为外部变量, register变量: C语言允许使用频率高的变量定义为寄存器变量,这样的变量前需要加存储类型符号register。,类型说明符 除前面数据类型一节中介绍的9种数据类型外,还有关键字typedef定义的类型别名。 修饰符 修饰符用于变量的特殊修饰,但并不是必须的。 常量修饰符const: 易失性修饰符volatile C51还增加了存储空间修饰符 data修饰变量时,C51编译器将其定位在片内128B的数据存储器。 bdata修饰变量时,C51编译器将其定位在片内数据存储器中的位寻址区。 idata修饰变量时,C51编译器将其定位在片内256B的数据存储器中。 code修饰变量时,C51编译器将其定位片内或片外FLASH存储器中。,如果省略存储空间修饰符,C51编译器则会按存储模式SMALL,COMPACT或LARGE所规定的默认存储类型去指定变量的存储区域。存储模式说明: SMALL存储模式,把变量和堆栈都放在51单片机片内RAM(128字)中,默认数据类型是data,这使得程序访问数据非常快,但SMALL存储模式的地址空间受限,只能编写小型程序。 COMPACT存储模式,把变量和数据定位在51单片机的片外存储区前256B,默认的存储类型是pdata,通过寄存器R0和R1(R0/R1)间接寻址,堆栈在片内存储器中。 LARGE存储模式,把变量都定位在51单片机系统的外部64KB数据存储器中,默认存储类型是xdata,要求用DPTR数据指针访问数据。 例如,声明 unsigned char m;则在SMALL模式中,m被定位在片内RAM中(data);在COMPACT存储模式下,m被定位在片外分页区中(idata);在LARGE存储模式下,m被定位在外数据存储器中(xdata)。 需要说明的是C51中,经常将修饰符放在类型说明符之前。, 标识符 没有前导符“*”的标识符是变量。有“*”前导符的标识符为指针。 变量的初始化 变量的初始化可有可无,根据程序需要确定。 3. 定义特殊功能寄存器有关的变量 定义特殊寄存器有关变量的关键字sfr,sfr16,sbit是C51特有的。 在头文件 “AT89X51.H”中,已给出了51单片机寄存器地址、位寻址寄存器的位地址助记符与地址之间的关系,例如P0口各引脚的助记符与其地址之间的关系如下。 sbit P0_0 = 0x80;/*P0 Bit Registers*/ sbit P0_1 = 0x81; sbit P0_2 = 0x82; sbit P0_3 = 0x83; sbit P0_4 = 0x84; sbit P0_5 = 0x85; sbit P0_6 = 0x86; sbit P0_7 = 0x87;,3.3 运算符 3.3.1 算术运算符与赋值运算符 1. C51最基本的算术运算符有5种: +(加法,或正值符号)、(减法,或负值符号)、(乘法)、/(除法)、%(模(求余)运算)。 2. 赋值运算符“=” 赋值运算符是一个二元运算符,左边必须是变量或数组元素,右边是表达式。,3. 数据类型转换 (1)自动转换 用运算符连接的运算对象如果具有不同的数据类型,首先按照以下顺序转换: float转double,char转int 只有char、int、long和float类型可以进行自动转换。 (2)强制转换: 强制转换运算符的作用是将一个表达式转换成所需类型,格式为: (类型名)(表达式) 例如: (int)a; /将a转换为整型 (int)(7.2/2); /将7.2/2的结果转换为整型,结果为3; (float)(a+b); /将a+b的结果转换为浮点数,a+b的括号不能省,否则是将a转换为浮点数后和b相加。,3.3.2 关系运算符 “关系运算”实际上是“比较运算”。将两个值进行比较,判断比较的结果是否符合给定的条件。 例如,a3是一个关系表达式,式中的大于号()是一个关系运算符。若a3,则输出为“真”;若是a,=,= =,!=,其中: (1)前4种关系运算符(,=)的优先级别相同,后两种(= =,!=)也相同。且前4种优先级高于后2种。 (2)关系运算符的优先级低于算术运算符。 (3)关系运算符的优先级高于赋值运算符。 用关系运算符连接的式子称为关系表达式。在编写关系表达式时,为避免优先级别的干扰,最好用括号区分优先级别。,3.3.3逻辑运算符 C51语言提供了三种逻辑运算:&(逻辑与),|(逻辑或),!(逻辑非)。其中“&”和“|”是双目运算符,要求有两个操作数。“!”是一目运算符,只要求有一个操作数。 逻辑非运算的优先级别高于逻辑或、逻辑与运算。 逻辑运算符举例: a&b 若a, b都为真,则a&b为真 a| |b 若a, b之一为真,则a| |b为真 !a 若a为真,则! a为假 用逻辑运算符连接的式子称为逻辑表达式。 C语言中逻辑运算的结果用1表示“真”,用0表示“假”,因此非零数为真。,3.3.4 位运算符 C语言支持二进制位操作,位操作是对字节或位进行测试、设置或移位的操作,这里字节或位是针对C标准中的字符型和整型类型而言的,非整型数据不能用于位操作。 1. 按位与“&”运算符 2. 按位或“|”运算符 3. 按位异或“”运算符 4. 按位取反“”运算符 5. 位左移“”运算符 在位运算中还需要注意: 位运算符与“=”的复合运算符相当于将位运算完成后,再赋值。 在C语言中,一般是通过读修改写的方法实现单个位操作:,还可以按照如下的方法实现置位与清除bit3的操作。 #define BIT3 (0x13) static int a; void set_bit3(void) a |= BIT3; /置位 void clear_bit3(void) a /清除 ,3.3.5 ?与 :运算符 C语言提供了一个可代替ifthenelse 结构的运算符“?”与“:”,这个运算符是三元的,其一般形式为: 表达式1:表达式2:表达式3; 运算符“?”作用是在计算表达式1之后,如果表达式1为真,则计算表达式2,并将结果作为整个表达式的数值;如果表达式1的值是假,则计算表达式3,以它的结果作为整个表达式的值。 例如:y=(ab)? 3:5; 如果ab的值为假,则赋给y的数值是5,这相当于下面的ifelse 语句: If (ab) y=3; else y=5;,3.3.6 自增自减运算符 自增自减运算符可以使变量值自动加1或减1。 例如: +j;在使用j的值之前,使j的值加1,j+;在使用j的值之后,使j的值加1。 j;在使用j的值之前,使j的值减1,j;在使用j的值之后,使j的值减1。 自增自减运算符只能用于变量。 4.3.7 复合运算 赋值运算符“=”与其他运算符复合运算符, 复合算术运算:+=,=,*=,/=,%=。 复合位运算:=,=,|=,=,=。 复合算术运算的意义如下: x+=a; /等价于x=x+a x=a; /等价于x=xa x*=a; /等价于x=x*a x/=a; /等价于x=x/a x%=a; /等价于x=x%a,3.4 C程序基本结构与流程控制 3.4.1 C程序设计的3种基本结构 C程序由若干个基本结构组成。每个基本结构又包含一个或几个语句。C语言中有三种基本结构。 1. 顺序结构,流程如图3-1(a)所示。先执行A操作,再执行B操作,两者是顺序执行的关系。 2. 选择结构,流程如图3-1(b)所示。P代表一个条件,当P条件成立(或称为“真”)时执行A,否则P条件不成立(称为“假”)则执行B。注意,只能执行A或B之一。,3. 循环结构,有两种循环结构: (1)当型(while)循环结构,如图3-1(c)所示。当条件成立(“真”)时,反复执行操作。直到为“假”时才停止循环。 (2)直到型(do while)循环结构,如图3-1(d)所示。先执行B操作,再判断是否为“假”,若为“假”,再执行B,如此反复,直到为“真”为止,3.4.2 条件与分支结构 1. 条件(if)语句 C语言提供了三种形式的if语句: (1)if (表达式) 语句; 例如:if(xy) x=1; (2)if (表达式) 语句1; else 语句2; 例如:if(xy) x=1; else x=5; (3)if (表达式1) 语句1; else if (表达式2) 语句2; else if (表达式3) 语句3; else if (表达式m) 语句m; else 语句n;,2. 分支(switch)语句 switch 语句是多分支选择语句,非常适合实际问题中需要用到多分支的选择。 switch语句的一般格式如下: switch (表达式) case 常量表达式1:语句1;break; case 常量表达式2:语句2;break; case 常量表达式n:语句n;break; default :语句n+1;break; 对于switch语句,需要注意: (1)常量表达式的值必须是整型,字符型或者枚举类型。 (2)break语句用于跳出switch结构。 (3)每一个case后的常量表达式不能相同。 (4)case后语句的排列顺序,与程序执行结果无关。,3.4.3 各种循环结构 C语言中有三种循环语句:for、while和do while语句。 1. for循环结构 C语言中的for语句使用最为灵活,不仅可以用于循环次数已经确定的情况,而且可以用于循环次数不确定而只给出循环结束条件的情况,它完全可以代替while语句。 for语句的一般形式为: for(循环变量初始化表达式1;关系表达式2;循环变量运算表达式3;) 循环执行语句; 一些说明: (1)for 语句一般形式中的“循环变量初始化表达式1”可以省略,此时应在for语句之前给循环变量赋初值。注意省略循环变量表达式1时,其后的分号不能省略。例如, sum=0; i=0; /设置循环变量初值 for(;i=100;i+) sum=sum+i;,(2)如果省略关系表达式2,即不判断循环条件,循环无终止的进行下去。 (3)循环变量运算表达式3也可省略,但其后必须添加相应语句使其正常执行。例如, sum=0; for(i=1;i0;i-) /这里i是无符号整型,j是无符号字符型 for(j=0;j255;j+);,2. while循环 while语句常用来实现“当型”循环结构。其一般格式如下: while (表达式) 循环体语句; 当表达式为非0值时执行while语句中的内嵌语句。其特点是:先判断表达式,后执行语句。 需要注意: (1)循环体如果包含一个以上的语句,应该用花括弧括起来,以复合语句形式出现。如果不加花括弧,则while语句的范围只到while后面的第一个分号处。 (2)在循环体中应有使循环趋向于结束的语句。例如,在本例中循环结束的条件是“i100” (3)无限次循环的格式是while(1),因为while表达式中的值永远不是0。 (4)没有循环体的无限循环的while语句为:while(1);,3. do_while 循环 常用来实现“直到型”循环结构。其一般形式为: do 语句; while (表达式); 例:用do_while 语句求1到100的和 int j, sum=0; j=1; do sum=sum+1; j+; while (j=100); 在一般情况下,用while语句和用do-while语句处理同一问题时,若二者的循环体部分是一样的,则结果也一样。但是while后面的表达式一开始就为假(0值)时,两种循环的结果是不同的,因为do-while语句中的循环体已经执行了一次。,4. goto语句 goto语句为无条件转向语句,格式为: goto 语句标号: 例如:goto label_1; 是合法的,而“goto 123;”是不合法的。 一般来说,可以有两种用途: (1)与if语句一起构成循环结构; (2)从循环体中跳转到循环体外,可以用break语句和continue语句跳出本层循环和结束本次循环。goto语句的使用机会已大大减少,只是需要从多层循环的内层循环跳到外层循环时才用到goto 语句。,5. break 语句 在循环语句中,break语句的作用是在循环体中测试到应立即结束循环条件时,控制程序立即跳出循环结构,转而执行循环语句后的语句。 例:求到200进行累加,如果和超过3000,则跳出循环,求此时和值。 int j , sum; sum=0; for (j=1;j3000) break; 这里,break语句使程序跳出了for循环 6. continue 语句 continue 语句只能用于循环结构中,作用是结束本次循环。一旦执行了continue语句,程序就跳过循环体中位于该语句后的所有语句,提前结束本次循环周期并开始新一轮循环。,3.5 C51中的构造数据类型 3.5.1 数组 1. 一维数组 一维数组的声明格式为: 类型说明符 数组名常量表达式; 例如,char tab12; /定义一个12个元素的数组。 一维数组初始化: (1)在定义数组时对数组元素赋值 例如,显示十进制数字09的共阳数码管段译码数组: unsigned char seg10=0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90; 该数组有10个元素,第一个元素seg0=0xC0,第2个元素seg1=0xF9,等等 (2)可以给部分数组元素赋初值。 (3)全部元素赋初值的数组可以不指定数组元素个数。 (4)所有元素不赋初值的数组,所有元素自动赋初值0。 引用数组元素时的格式为: 数组名 数组下标;,2. 二维数组 二维数组的格式为: 类型说明符 数组名行常量表达式1列常量表达式2; 例如:unsigned char tab34; /定义tab为3行4列的二维数组 如下是一个二维数组初始化的例子: unsigned int table410 = 0 , 1 , 2, 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10, 11, 12, 13, 14, 15, 16, 17, 18 , 19 , 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39; 只要正确给定数组下标,就可以从数组中取出相应的元素。,3. 字符数组 字符数组的格式为: 类型说明符 数组名字符个数 例如:char ch10; /定义ch为10个字符的数组 赋予各个数组元素字符的方法为字符数组赋初值: char cha10=a, b, c, d, e, f, g, h, i; 用字符串初始化字符数组: char chb =“china shanxi taiyuan”;/编译器还将在字符串常量后加结束符号“0” 二维数组初始化: char chb325=“china shanxi taiyuan”, “TUT ”, “AUTO”;,3.5.2 指针 1. 指针的基本概念 对于变量来说,变量名是数据的名字;变量的值是数据。 指针就是变量的地址,又称为变量的指针,简称指针。 为操作指针,常将指针用变量表示,称为指针变量。为使用指针变量,需要在使用之前定义指针变量,格式为: 类型标识符 *指针变量; 例如:char *xp; int * yp; unsigned char *my_ptr,*anther_ptr; unsigned int *int_ptr; 指针变量前的“*“只是表明该变量是个指针,以区别于一般变量。指针变量前的类型标识符说明指针指向的变量类型。,定义好的指针变量为空,因为没有指向任何变量。因此变量与指针变量定义后,还需要指针变量指向变量。指针变量取变量地址是通过取地址运算符 unsigned char k; 而且,yp=&k; 则:*yp就是k;&*yp就是&k;*&k就是k。,2. 数组指针与指向数组的指针变量 数组的指针指向数组的首地址,指向数组的指针变量,就是一个变量,该变量中保存数组首地址。 若有如下定义: #define uchar unsigned char uchar y10; /定义数组 uchar *yp; /定义指针 uchar j; /定义变量j 则有:yp= 在定义指针时,使其获得数组首地址 uchar y=10; 通常引用数组的方法是下标法,例如引用数组第4个元素,可以写为y3。也可以采用指针法,如果yp是数组y10的首地址,则yp+j就是数组元素yj的地址,指向数组y的第j个元素。而*(yp+j)指向数组元素yj。*(yp+j)与ypj等价。 若指针变量yp已经指向数组y的第一个元素(yp=y;),则有 yp+;指向数组的下一个元素y1。,3. C51中的指针 (1)基于存储器的指针 基于存储器的指针以存储器类型为参量,对于idata *,data *和pdata *为1个字节,对于code *和xdata *为两个字节。 例如,char xdata *xp;/定义了在xdata存储器中指向char类型变量的指针,该指针长度为2个字节。 char data * str; /str指向data区中char型数据。,3.5.3 结构体 将互相关联的不同类型的数据组合在一起就是结构型数据。例如,要保存一组采样值:时间(月、日、时、分)、温度、流量等。应该首先定义结构数据类型,然后再定义结构数据类型的变量。 1. 定义结构数据类型与变量 (1)先定义结构数据类型,再定义变量 格式如下: struct 结构名 结构成员说明 ; 结构成员说明的格式为: 类型标识符 成员名; 例如:定义名为time的结构数据类型如下: struct time unsigned char hour; unsigned char min; unsigned char sec 则可以定义time类型的时间变量t1、t2如下: time t1,t2; /定义time类型的变量t1和t2,(2)在定义结构数据类型的同时定义该结构的变量 格式如下: struct 结构名 结构成员说明 ;变量名1,变量名2,变量名n; 例如,定义time结构类型的同时定义时间变量t1、t2如下。 struct time unsigned char hour; unsigned char min; unsigned char sec t1,t2;,(3)直接定义结构类型的变量 格式如下: struct 结构成员说明 ;变量名1,变量名2,变量名n; 例如,直接定义结构类型的变量t1和t2的例子如下。 struct unsigned char hour; unsigned char min; unsigned char sec t1,t2; 注意结构体类型与结构体变量是两个概念,结构体变量可以与其他变量重名。,2. 引用结构体变量 引用结构类型变量时应该注意: (1)结构不能作为一个整体参加赋值、存取与运算,也不能作为函数的参数或函数的返回值。 可用&运算符取得地址后,引用结构变量;或是对接变量的成员分别引用,引用格式为: 结构变量.成员名 这里“.”是成员运算符,与“”等同。 (2)结构类型变量的成员可以象普通变量一样实现各种运算。 (3)Keil C编译器为结构提供了连续的存储空间。指向结构变量的指针,可以访问结构的成员。,3. 结构数组 当数组中的每个元素都具有相同结构类型的结构变量,则称为结构数组。结构数组中的每一个元素,都具有同一个结构类型的结构变量都含有相同的成员项。 结构数组的定义与结构变量的定义的方法基本相同,差别就是将定义变量改成定义数组。 例如,定义一个5元素结构数组data5。 struct unsigned char month; unsigned char day; unsigned int year; data5; /定义5个元素的结构数组,3.5.4 共用体 共用体中包含多个不同数据类型的成员,所有成员分时共享同一存储空间。 共用体类型的定义格式为: union 共用体类型标识符 类型说明符 变量名; ; 共用体变量的定义格式为: union 共用体类型标识符 共用体变量名表 例如,定义一个名为u1的共用体类型,再定义一个共用体变量ux。 定义共用体u1: union u1 int j; char k; ; 定义共用体变量ux union un ux;,3.5.5 枚举 枚举数据类型是有名的一些整数常量的集合,具有枚举数据类型的变量可在这些整数常量之间取值。 枚举类型的定义格式为: enum 枚举名 枚举符号列表; 枚举变量定义格式为: enum 枚举名 变量列表 在枚举列表中,每项枚举符号代表一个整数值,在缺省情况下,第1项枚举符号取值为0,第2项枚取值为1,。还可以通过初始化确定各项枚举符号的取值。 例如,enum motor run=3,stop=5,start=7; /定义枚举数据类型 enum motor state; /定义枚举变量 在定义了枚举变量后,有如下分支程序。 switch(state) case run:其他语句; break; case stop:其他语句; break; case start:其他语句; break; ,3.5.6 用typedef重定义数据类型 类型定义可以把已有的类型标识符定义成新的类型标识符,定义后,新的类型标识符就可以作为原标识符使用,相当于给老的类型标识符改了一个名字。 格式: typedef 原类型名 新类型名; 例如:typedef int integer; /用integer代替int typedef float real; typedef unsigned int UINT; typedef unsigned long ULONG; typedef unsigned char BYTE; 定义后,就可以用新类型名定义变量: integer m,n; /定义整型变量i,j real kk; typedef只能声明各种类型名,目的是增加一种已有数据类型的数据类型新名称,与#define的区别是:在编译之前进行预处理时,#define实现的是字符串替换。,3.6 函数 3.6.1 定义函数 函数一般分为主函数main()和普通函数,普通函数分为: (1)标准库函数 编译软件提供的函数,使用这些函数,需要在C程序中包含库函数文件。 (2)用户自定义函数 就是用户自己根据需要编写的函数,这些函数分为:无参数函数、有参数函数和空函数(无语句,用于功能扩充)。 C程序总是从main函数开始执行。除主函数和中断函数不能调用外,其他函数都可以调用。 根据是否传递参数,函数又分为: (1)无参函数,在调用函数时,不向被调用函数传递参数,被调函数也不返回参数。 (2)有参函数,在调用函数时,向被调用函数传递实际参数,并向调用函数返回参数。,1. C51中函数的说明 (1)定义函数 函数定义的格式: 存储类型说明符 类型说明符 标识符(形参表)reentrantinterrupt musing n存储模式 函数体 (2)引用函数(原型说明) extern 类型说明符 标识符(形参表)reentrant using n存储模式 (3)函数调用 标识符(实参表); 或 x=标识符(实参表); 这里: 存储类型说明符: static 使函数只能在定义该函数的文件中,而且是在定义之后才可被调用。,类型说明符:用于说明函数的返回值数据类型。 数据类型为: 简单类型(char,unsigned char,int,unsigned int,long,unsigned long,float),结构与共用(struct,union),位类型(bit),无类型(void)。若函数返回的是指针,在函数名前加“*” 标识符: 函数名() 带或不带返回值的函数。 *函数名() 返回指针的函数。 (* 函数名)(void) 定义带或不带返回值的无参数函数指针。 (* 函数名)(形参表) 定义带或不带返回值的有参数函数指针。 *(* 函数名)(void) 定义返回指针的无参数函数指针。 *(* 函数名)(形参表) 定义返回指针的有参数函数指针。 形参表:传入函数的形式参数列表,格式如下: (类型说明符 变量名,类型说明符 变量名,) 或(void) 或() 这里(void)表示无参数输入,有的编译器支持空括号()。,修饰符 寄存器组使用修饰符using n 该修饰符用于指定函数使用的寄存器组,这里n(03)用于指定寄存器组。每个寄存器组包含8个工作寄存器(R0R7)。若是不用该修饰符,则编译器自动选择一组寄存器。 using n 不能用于具有返回值的函数,因为C51是将返回值放在寄存器中的,在返回后寄存器改变了,返回值也就不对了。 中断函数修饰符interrupt m 该修饰符将函数转化为中断函数,这里m(031),m与中断向量之间的关系如表所示。,中断函数必须是无参数无返回量的函数。因此格式为: void 函数名(void)interrupt m 函数体; 中断函数体中可以调用其他函数,但应该保证被调用函数与中断函数使用一组寄存器。不使用using n修饰符的情况下,编译器自动统一寄存器组。 C51自动为中断函数前和后增加汇编语句保护相应的寄存器。 任何情况下不能直接调用中断函数,否则产生编译错误。 如果中断中用到浮点运算,则应该保存浮点寄存器状态,(使用math.h中的库函数pfsave和fprestore函数) 中断函数由51单片机指令RETI结束。,重入函数修饰符reentrant 允许被嵌套调用的函数必须是可重入函数,函数的嵌套调用是指当一个函数正在被调用尚未返回时,有被本函数或其他函数调用的情况,重入函数必须具有分层保护参数与局部变量的专门重入堆栈机制。 重入函数是在函数体内调用其自身的一种函数,重入函数的定义格式为: 类型说明符 函数名(形式参数表)reentrant 函数体; 其中:reentrant 是C51编译器中的扩展关键字,专门用于定义重入函数。 任何函数都可以调用重入函数,与一般函数调用过程不同,C51为重入函数生成一个模拟堆栈,并通过这个模拟堆栈实现参数传递与存放局部变量。 在重入函数中不能传递位(bit)类型的参数,也不能定义局部位量,不能包含位操作以及涉及到位寻址区。 C51编译器为重入函数分配模拟堆栈区,称为重入栈,重入函数的局部变量与参数放在重入栈中,所以重入函数可以递归调用。,存储模式修饰符 为函数指定存储模式的格式为: 类型说明符 标识符(形参表) 存储模式修饰符 函数体; 这里,存储模式修饰符为small,compact和large。,3.6.2 调用函数 1. 调用函数的条件 一个函数调用另一个函数必须具备如下条件: (1)被调用的函数必须是已经存在的函数(库函数或自定义函数)。 (2)如果是库函数,应该在程序开头用#include命令声明相应的库函数,例如: #include (3)如果是自定义函数,而且被调用函数与调用函数在同一文件内,则应该: 如果被调用函数在调用函数之后,需要在调用函数中的调用函数语句前声明被调用函数,声明格式为: 返回值类型说明 被调用函数名(形参表);。,2. 函数调用语句 (1)函数作为语句 把函数作为一个语句,函数无返回值,只是完成某种操作。语句格式为: 函数名(参数); 如果没有参数,可以用空括号。 例如,Init( ); /调初始化函数 Keyscan( ); /调键盘扫描函数函数 (2)函数作为表达式中的一个运算对象 函数可以出现在一个表达式中,例如: sum=c+add(a,b); /全加和等于进位加上本位加法和,这里add(a,b)时本位加法函数 (3)函数作为参数 被调用的函数作为另外一个函数的参数,例如: sub=SUB (c add(a,b); /将add函数的返回值作为SUB函数的参数 (4)函数可以嵌套调用 函数的嵌套调用方法就是一个函数调用另一个函数,而被调用的函数又调用第3个函数。其调用方法与前述方法相同。,(5)函数的递归调用 在调用一个函数的过程中,又直接或间接地调用该函数本身,称为函数的递归调用。函数自己调用自己就是函数的递归调用。只有重入函数才可以递归调用。 (6)用函数指针变量调用函数 每一个函数都占据一段存储空间,若用指针变量指向其起始地址,则可以通过该指针变量访问该函数。 指向函数的指针变量格式为: 函数返回值类型标识符(*指针变量名)(函数形参表) 若指针变量名为p,函数的定义为long func(int n),则long(*p)(int n)表示定义了一个指向函数的指针变量,用来保存函数入口地址。 指针变量获得函数地址的语句中,只需要给出函数名就可以。例如,若是函数名为func,则p=(void *)func; 一旦函数指针变量已经指向了函数,则在调用函数时,可用(* p)(实参表)代替函数名和实参表。 (7)数组作为函数参数 若是主函数中定义了赋有初值的一个10元素数组s10,而函数m的形式参数为数组,则主函数调用函数m时,数组s10可以作为实际参数传递到函数。这就是数组作为函数参数。,3.6.3 函数变量的作用域 按照变量的有效作用范围可划分为局部变量和全局变量。 1. 局部变量 在函数内部定义的变量称为局部变量。 2. 全局变量 在所有函数模块中起作用的变量称为全局变量或是外部变量。,3.7 编译预处理 1. #define #define语句又称为宏定义语句。 该指令格式如下: #define 标识符 常量表达式 /注意没有分号 例如:#define TRUE 1 /用TRUE表示1 一旦定义了一个标识符,则该标识符可以作为其他标识符的一部分: 例如: #define ONE 1 则有 #define TWO ONE+ONE 通常,C程序中用大写字母代表被定义的标识符,这样就可以使阅读程序的时候,一目了然地知道这是一个宏替换。 #define语句还可以带有参数,格式如下: #define 标识符(参数表) 表达式 例如:#define S(r) 3.14*r*r /注意S与(r)之间不能有空格 终止宏定义语句: #undef 语句可以终止#define语句定义标识符。该语句后,#define语句定义标识符不再有效。,2. 文件包含 #include语句可以在一个源文件A中包含其他源文件中的内容,这样源文件A就是由多个源文件组成,文件包含的格式如下: #include 尖括号表示被包含文件的搜索方式由编译器控制,这意味着包含文件在特殊的文件夹中。 #include“文件名” 双引号表示首先搜索当前工作文件夹(用户自己建立的项目文件夹),若是没有找到文件,则搜索尖括号情况下的文件夹。 经常使用的包含文件是后缀为h的文件,例如,51单片机的头文件: #include 一般来说,对应每个C源文件,都有一个对应的扩展名为h文件,该文件中包含C文件中的常量、数据结构、函数、全局变量的声明等。 在调用其他文件中的函数时,可以在本文件开始时,声明被调用函数、被调用函数外部变量(extern)的.h文件,该.h文件与.c文件经常具有相同的名字。,3. 条件编译 对源程序的各部分有选择地进行编译称为条件编译。 (1)#if、#else、#elif、#endif 例如:#define MAX 10 void main(void) #if MAX90 语句序列1; #else 语句序列2; #endif 因为MAX=10,所以对于该例来说,编译语句系列2。,(2)条件编译的另外一种方法是使用#ifdef、#elif、#endif控制编译代码 1)常量表达式条件的格式如下: #ifdef 常量表达式1 程序段1 #elif 常量表达式2 程序段2 #elif 常量表达式3 程序段3 #else 常量表达式n 程序段n #endif,2)标识符条件格式 #ifdef 标识符 程序段1 #else 程序段2 #endif (3)未定义标识符条件#ifndef、#else、#endif控制编译代码 #ifndef 标识符 程序段1 #else 程序段2 #endif,3.8 C51内部函数 1. 本征函数intrins.h _crol_(unsigned c,unsigned b) 将字符c左移b位。 _cror_(unsigned c,unsigned b) 将字符c右移b位。 _irol_(int i,unsigned b) 将整数i左移b位。 _iror_(int i,unsigned b) 将整数i右移b位。 _lrol_(long l,unsigned b) 将整数l左移b位。 _lror_(long l,unsigned b) 将整数l右移b位。 _nop_ 插入NOP,延时一个机器周期。 _testbit_(bit) 相当于JBC 指令,测试该位变量并同时清除该位。只能测试可直接寻址的位变量。该函数返回b的值。 _chkfloat_ 检测浮点数状态。 _nop()_ 是void型的,而_crol_等函数是值传递的,程序中应该使用赋值语句 temp= _crol_( temp, 1);,2. 绝对地址存取库函数absacc.h “include“即可使用其中定义的宏来访问绝对地址。 以字节(char)为存取对象: #define CBYTE (unsigned char *) 0x50000L) /code 空间 #define DBYTE (unsigned char *) 0x40000L) /data 空间 #define PBYTE (unsigned char *) 0x30000L) /cdata 空间 #define XBYTE (unsigned char *) 0x20000L) /xdata 空间 以字(int)为存取对象: #define CWORD (unsigned int *) 0x50000L) /code 空间 #define DWORD (unsigned int *) 0x40000L) /data 空间 #define PWORD (unsigned int *) 0x30000L) /cdata 空间 #define XWORD (unsigned int *) 0x20000L) /xdata 空间 绝对对象存取例: val=CBYTE0x0002; /从程序存储器地址0002地址读出内容 val=DBYTE0x0002; /从data存储器地址0002中读出数据 DBYTE0x0002=5; /向data存储器地址0002中写入数据,3.9 C51编译过程中用到的文件 1. 特殊功能寄存器头文件 单片机生产厂家提供的头文件放在keil C51 inc文件夹中。 2. C51编译器的配置文件 C51编译过程中,用到一些配置文件(启动文件),如果只是开发一般的51单片机程序,用缺省的配置文件就可以满足需求,特别是对初学51单片机,用缺省配置文件完全满足需求。 (1)启动文件 STARTUP.A51 STARTUP.A51文件放在keil C51 Lib文件夹中,该文件在51单片机上电复位后立即执行,并进行如下操作。 启动代码随单片机的类型不同而不同,表3-7所示的是一些厂商单片机的启动文件。 (2)静态变量初始化启动代码 INIT.A51 INIT.A51文件用于外部变量和静态变量初始化,,3.10 获得错误与警告信息

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论