《第4章单片机的C语言编程》由会员分享,可在线阅读,更多相关《第4章单片机的C语言编程(119页珍藏版)》请在金锄头文库上搜索。
1、第第4 4章章 单片机的单片机的C C语言编程语言编程 单片机应用系统是由硬件和软件组成的,这是与一般的数字逻单片机应用系统是由硬件和软件组成的,这是与一般的数字逻辑电路系统的不同之处。辑电路系统的不同之处。 汇编语言汇编语言是能够利用单片机所有特性直接控制硬件的唯一语言,是能够利用单片机所有特性直接控制硬件的唯一语言,对于一些需要直接控制硬件的场合,汇编语言是必不可少的。对于一些需要直接控制硬件的场合,汇编语言是必不可少的。 但汇编语言不是一种结构化的程序设计语言,对于较复杂的单但汇编语言不是一种结构化的程序设计语言,对于较复杂的单片机应用系统,它的编写效率很低。片机应用系统,它的编写效率很
2、低。 为了提高软件的开发效率,许多软件公司致力于单片机为了提高软件的开发效率,许多软件公司致力于单片机高级语高级语言言的开发研究,许多型号的单片机内部的开发研究,许多型号的单片机内部ROMROM已经达到已经达到64KB64KB甚至更大,甚至更大,且具备在且具备在系统编程系统编程(ISP, In System ProgrammableISP, In System Programmable)功能,进一)功能,进一步推动了步推动了高级语言高级语言在单片机应用系统开发中的应用。在单片机应用系统开发中的应用。 51 51系列单片机支持三种高级语言系列单片机支持三种高级语言: : PL/MPL/M、 C
3、C、 BASICBASIC BASIC BASIC语言适用于简单编程而对编程效率、运行速度要求不高语言适用于简单编程而对编程效率、运行速度要求不高的场合,的场合,80528052单片机内固化有单片机内固化有BASICBASIC语言解释器。语言解释器。 PL/MPL/M是一种结构化的语言,很象是一种结构化的语言,很象PASCALPASCAL,PL/MPL/M编译器好象汇编编译器好象汇编器一样产生紧凑的机器代码,可以说是高级汇编语言,但它不支持器一样产生紧凑的机器代码,可以说是高级汇编语言,但它不支持复杂的算术运算,无丰富库函数支持,学习复杂的算术运算,无丰富库函数支持,学习PL/MPL/M无异于
4、学习一种新无异于学习一种新的语言。的语言。 C C语言是一种通用的程序设计语言,其语言是一种通用的程序设计语言,其代码率高代码率高,数据类型及,数据类型及运算符丰富,并具有良好的运算符丰富,并具有良好的程序结构程序结构,适用于各种应用的程序设计,适用于各种应用的程序设计,是目前使用最广的单片机编程语言。是目前使用最广的单片机编程语言。 C C语言作为一种非常方便的语言而得到广泛的支持,很多硬件语言作为一种非常方便的语言而得到广泛的支持,很多硬件开发都用开发都用C C语言编程,如:各种单片机、语言编程,如:各种单片机、DSPDSP、ARMARM等。等。 C C语言程序本身不依赖于机器硬件系统语言
5、程序本身不依赖于机器硬件系统,基本上不作修改或仅,基本上不作修改或仅做简单修改就可将程序从不同的单片机中做简单修改就可将程序从不同的单片机中移植移植过来直接使用。过来直接使用。 C C语言提供了很多数学函数并支持浮点运算,开发效率高,故语言提供了很多数学函数并支持浮点运算,开发效率高,故可缩短开发时间,增加程序可缩短开发时间,增加程序可读性可读性和和可维护性可维护性。 单片机的单片机的C C语言编程称为语言编程称为C51C51编程编程,应用,应用C51C51编程具有以下优点:编程具有以下优点: (1 1)C51C51编译器编译器管理内部寄存器和存贮器的分配,编程时,无需管理内部寄存器和存贮器的
6、分配,编程时,无需考虑不同存储器的寻址和数据类型等细节问题考虑不同存储器的寻址和数据类型等细节问题; (2 2)程序有规范的结构,可分成不同的函数,这种方式具有良)程序有规范的结构,可分成不同的函数,这种方式具有良好的好的模块化结构,使已编好程序容易移植模块化结构,使已编好程序容易移植; (3 3)有丰富的)有丰富的子程序库子程序库可直接引用,可直接引用,具有较强的数据处理能力具有较强的数据处理能力,从而大大减少用户编程的工作量;从而大大减少用户编程的工作量; (4 4)C C语言和汇编语言可以交叉使用语言和汇编语言可以交叉使用。 汇编语言程序代码短、运行速度快、但复杂运算编程耗汇编语言程序代
7、码短、运行速度快、但复杂运算编程耗 时。时。用汇编语言编写与硬件有关的部分程序用汇编语言编写与硬件有关的部分程序,用用C C语言编写与硬件无关语言编写与硬件无关的运算部分程序的运算部分程序,充分发挥两种语言的长处,提高开发效率。,充分发挥两种语言的长处,提高开发效率。 C51C51编译器(编译器(KeilKeil C C)与标准)与标准ANSI CANSI C编译器的主要区别编译器的主要区别 单片机单片机C C编译器之所以与编译器之所以与ANSI CANSI C有所不同,主要是由于它们所有所不同,主要是由于它们所针对的针对的硬件系统硬件系统有其各自不同的特点。有其各自不同的特点。C51C51的
8、特点和功能主要是的特点和功能主要是80C5180C51单片机自身特点引起的。单片机自身特点引起的。 (1 1)头文件头文件:5151单片机有不同的厂家和系列,不同单片机的主单片机有不同的厂家和系列,不同单片机的主要区别在于内部资源,为了实现内部资源功能,只需将相应的功能要区别在于内部资源,为了实现内部资源功能,只需将相应的功能寄存器的头文件加载在程序中,就可实现指定的功能。寄存器的头文件加载在程序中,就可实现指定的功能。因此,因此,C51C51系列头文件集中体现了各系列芯片的不同功能。系列头文件集中体现了各系列芯片的不同功能。 (2 2)数据类型数据类型:由于由于5151系列器件包含了位操作空
9、间和丰富的位系列器件包含了位操作空间和丰富的位操作指令,因此操作指令,因此 C51C51比比ANSI CANSI C多一种多一种位类型位类型。 (3 3)数据存储类型数据存储类型:5151系列单片机有程序存储器和数据存储器。系列单片机有程序存储器和数据存储器。数据存储器又分片内和片外数据存储器。片内数据存储器还分直接数据存储器又分片内和片外数据存储器。片内数据存储器还分直接寻址区和间接寻址区,分别对应寻址区和间接寻址区,分别对应codecode、datadata、idataidata、xdataxdata以及根以及根据据80C5180C51系列特点而设定的系列特点而设定的pdatapdata类
10、型。类型。 (4 4)数据运算操作和程序控制数据运算操作和程序控制:从数据运算操作和程序控制语从数据运算操作和程序控制语句以及函数的使用上来讲,它们几乎没有什么明显的区别。只是在句以及函数的使用上来讲,它们几乎没有什么明显的区别。只是在函数的使用上,函数的使用上,由于单片机系统的资源有限,它的编译系统不允许由于单片机系统的资源有限,它的编译系统不允许太多的程序嵌套太多的程序嵌套。 由于由于5151系列单片机是系列单片机是8 8位机,所以位机,所以扩展扩展1616位字符位字符UnicodeUnicode不被不被C51C51支持。支持。 ANSI C ANSI C所具备的所具备的递归特性不被递归特
11、性不被C51C51支持支持,所以在,所以在C51C51中如果要使中如果要使用递归特性,必须用用递归特性,必须用REETRANTREETRANT关键字声明。关键字声明。 (5 5)C51C51与标准与标准ANSI CANSI C库函数库函数:部分库函数不适合单片机处理部分库函数不适合单片机处理系统,因此被排除在外,如字符屏幕和图形函数。系统,因此被排除在外,如字符屏幕和图形函数。 也有一些库函数继续使用,但这些库函数是厂家针对硬件特点也有一些库函数继续使用,但这些库函数是厂家针对硬件特点相应开发的,与相应开发的,与ANSI CANSI C的构成和用法有很大的区别,如的构成和用法有很大的区别,如p
12、rintfprintf和和scanfscanf。在。在ANSI CANSI C中,这两个函数通常用作中,这两个函数通常用作屏幕打印屏幕打印和和接收字符接收字符,而在而在C51C51中,主要用于中,主要用于串口数据的发送和接收串口数据的发送和接收。 4.1 C514.1 C51程序结构特点程序结构特点同标准同标准C C一样,一样,C51C51的程序是由函数组成。的程序是由函数组成。C C语言的函数以语言的函数以“ ”开始,以开始,以“ ”结束。结束。 其中必须有一个其中必须有一个主函数主函数mainmain()(),程序的执行从主函数程序的执行从主函数main() main() 开始,调用其开始
13、,调用其他函数后返回主函数他函数后返回主函数main()main(),最后最后在主函数中结束整个程序在主函数中结束整个程序, ,而不管函数的排列而不管函数的排列顺序如何。顺序如何。 C C语言的语句规则:语言的语句规则: 1.1.每个变量必须先说明后引用。每个变量必须先说明后引用。 2.C 2.C语言程序一行可以书写多条语句,但每个语句必须以语言程序一行可以书写多条语句,但每个语句必须以“;”结尾,一个语句也可以多行书写。结尾,一个语句也可以多行书写。 3.C 3.C语言的注释用语言的注释用/*/*/*/表示表示。 4. 4.“ ”花括号必须成对花括号必须成对,位置随意,多个花括号可同行书写,
14、位置随意,多个花括号可同行书写,也可逐行书写也可逐行书写。 为层次分明,增加可读性,同一层的为层次分明,增加可读性,同一层的“”花括对齐,采用花括对齐,采用逐层缩进逐层缩进方式书写方式书写。 C C语言程序的组成结构:语言程序的组成结构:全局变量全局变量说明说明 /*/*可被各函数引用可被各函数引用* */ / mainmain( ) /*( ) /*主函数主函数* */ / 局部变量局部变量说明说明 /*/*只在本函数引用只在本函数引用* */ /执行语句执行语句( (包括函数调用语句包括函数调用语句); ); fun1fun1( (形式参数表形式参数表) /*) /*函数函数1*/ 1*/
15、 形式参数说明形式参数说明 局部变量说明局部变量说明 执行语句执行语句( (包括调用其他函数语句包括调用其他函数语句) ) funnfunn( (形式参数表形式参数表) /*) /*函数函数n*/ n*/ 形式参数说明形式参数说明 局部变量说明局部变量说明 执行语句执行语句 标识符标识符用来标识源程序中某个对象的名字,这些对象可以是语用来标识源程序中某个对象的名字,这些对象可以是语句、数据类型、函数、变量、数组等。句、数据类型、函数、变量、数组等。标识符区分大小写,第一个标识符区分大小写,第一个字符必须是字母或下划线。字符必须是字母或下划线。 C51C51中有些库函数的标识符是以下划线开头的,
16、所以中有些库函数的标识符是以下划线开头的,所以一般不要以一般不要以下划线开头命名标识符下划线开头命名标识符。 C51C51编译器规定标识符最长可达编译器规定标识符最长可达255255个字符,但只有前面个字符,但只有前面3232个字个字符在编译时有效符在编译时有效,因此在编写源程序时标识符的长度不要超过,因此在编写源程序时标识符的长度不要超过3232个个字符,这对于一般应用程序来说已经足够了字符,这对于一般应用程序来说已经足够了。 关键字关键字是编程语言保留的特殊标识符,有时又称为是编程语言保留的特殊标识符,有时又称为保留字保留字,它,它们具有固定名称和含义,们具有固定名称和含义,在在C C语言
17、的程序编写中不允许标识符与关语言的程序编写中不允许标识符与关键字相同键字相同。 与其他计算机语言相比,与其他计算机语言相比,C C语言的关键字较少,语言的关键字较少,ANSI CANSI C标准一共标准一共规定了规定了3232个关键字。个关键字。 KeilKeil C51 C51编译器的关键字除了有编译器的关键字除了有ANSI CANSI C标准的标准的3232个关键字外还个关键字外还根据根据5151单片机的特点单片机的特点扩展了相关的关键字扩展了相关的关键字。在。在KeilKeil C51 C51开发环境的开发环境的文本编辑器中编写文本编辑器中编写C C程序,系统可以把保留字以不同颜色显示,
18、程序,系统可以把保留字以不同颜色显示,缺缺省颜色为蓝色省颜色为蓝色。4.2 4.2 C51C51的标识符和关键字的标识符和关键字关键字用途说明auto存储种类说明用以说明局部变量,缺省值为此break程序语句退出最内层循环体case程序语句switch语句中的选择项char数据类型说明单字节整型数或字符型数据const存储类型说明在程序执行过程中不可更改的常量值continue程序语句转向下一次循环default程序语句switch语句中的失败选择项do程序语句构成dowhile循环结构double数据类型说明双精度浮点数else程序语句构成ifelse选择结构enum数据类型说明枚举exte
19、rn存储种类说明在其他程序模块中说明了的全局变量float数据类型说明单精度浮点数for程序语句构成for循环结构goto程序语句构成goto转移结构if程序语句构成ifelse选择结构ANSI CANSI C标准关键字(标准关键字(1 1)ANSI CANSI C标准关键字(标准关键字(2 2)关键字用途说明int数据类型说明基本整型数long数据类型说明长整型数register存储种类说明使用CPU内部寄存的变量return程序语句函数返回short数据类型说明短整型数signed数据类型说明有符号数,二进制数据的最高位为符号位sizeof运算符计算表达式或数据类型的字节数static存储
20、种类说明静态变量struct数据类型说明结构类型数据switch程序语句构成switch选择结构typedef数据类型说明重新进行数据类型定义union数据类型说明联合类型数据unsigned数据类型说明无符号数据void数据类型说明无类型数据volatile数据类型说明该变量在程序执行中可被隐含地改变while程序语句构成while和dowhile循环结构KeilKeil C51 C51编译器扩展的关键字编译器扩展的关键字(1 1)关键字用途说明_at_地址定位为变量定义存储空间绝对地址alien函数特性说明声明与PL/M51兼容的函数bdata存储器类型说明可位寻址的内部RAMbit位标量
21、声明声明一个位标量或位类型的函数code存储器类型说明程序存储器空间compact存储器模式使用外部分页RAM的存储模式data存储器类型说明直接寻址的8051内部数据存储器idata存储器类型说明间接寻址的8051内部数据存储器interrupt中断函数声明定义一个中断函数large存储器模式使用外部RAM的存储模式pdata存储器类型说明“分页”寻址的8051外部数据存储器_priority_多任务优先声明RTX51的任务优先级reentrant再入函数声明定义一个再入函数sbit位变量声明声明一个可位寻址变量sfr特殊功能寄存器声明声明一个特殊功能寄存器(8位)KeilKeil C51
22、C51编译器扩展的关键字编译器扩展的关键字(2 2)关键字用途说明sfr16特殊功能寄存器声明声明一个16位的特殊功能寄存器small存储器模式内部RAM的存储模式_task_任务声明定义实时多任务函数using寄存器组定义定义8051的工作寄存器组xdata存储器类型说明8051外部数据存储器4.3 C514.3 C51的数据类型的数据类型C51C51的数据有常量和变量之分。的数据有常量和变量之分。 常量常量 在程序运行中在程序运行中其值不变其值不变的量。的量。 数值型常量数值型常量:可以为十进制数、:可以为十进制数、 十六进制数十六进制数( ( 用用0x0x表示表示) )和字和字符(符(
23、括号括起括号括起)。 符号型常量符号型常量:用符号表示常量,此符号需用宏定义指令:用符号表示常量,此符号需用宏定义指令(#define)(#define)对其进行对其进行 定义定义( (相当于汇编的相当于汇编的“EQUEQU”伪指令伪指令) ) 。 变量变量 在程序运行中其值可以改变的量。在程序运行中其值可以改变的量。 定义一个变量,编译系统就会自动为它安排一个存贮区,具体定义一个变量,编译系统就会自动为它安排一个存贮区,具体的地址值的地址值 ,用户不必在意。,用户不必在意。 一个变量由一个变量由变量名变量名和和变量值变量值构成构成. . 变量名:存贮单元地址的符号表示。变量名:存贮单元地址的
24、符号表示。 变量的值:变量所在地址单元存放的内容。变量的值:变量所在地址单元存放的内容。 数据类型:数据的长度。数据类型:数据的长度。C51C51编译器支持数据类型编译器支持数据类型数数 据据 类类 型型长长 度度值值 域域位位型型bit1Bit0或或1字字符符型型signedchar1Byte-128+127unsignedchar1Byte0255整整形形signedint2Byte-32768+32767unsignedint2Byte065535signedlong4Byte-2147483648+2147483647unsignedlong4Byte04294967295实型实型Fl
25、oat4Byte1.176E-383.40E+38指针型指针型data/idata/pdata1Byte1字节地址字节地址code/xdata2Byte2字节地址字节地址通用指针通用指针3Byte其中其中1字节为储存器类型编码,字节为储存器类型编码,2,3字节为地字节为地址偏移量址偏移量访问访问SFR的数据类的数据类型型sbit1Bit0或或1sfr1Byte0255sfr162Byte065535 1 1、在、在C51C51语言程序中,有可能会出言程序中,有可能会出现在运算中数据在运算中数据类型不一致型不一致的情况。的情况。C51C51允允许任何任何标准数据准数据类型的型的隐式式转换,隐式式
26、转换的的优先先级顺序如下:序如下:bitbitcharcharintintlonglongfloatfloatsignedsignedunsignedunsigned 2 2、也就是、也就是说,当,当charchar型与型与intint型型进行运算行运算时,先自,先自动对charchar型型扩展展为intint型,然后与型,然后与intint型型进行运算,运算行运算,运算结果果为intint型。型。C51C51除了除了支持支持隐式式类型型转换外,外,还可以通可以通过强强制制类型型转换符符“()()”对数据数据类型型进行人行人为的的强强制制转换。 3 3、字符型、字符型(char)(char)、
27、整型、整型( (intint) )和长整型和长整型(long)(long)均有符号型均有符号型(signed)(signed)和无符号型和无符号型(unsigned)(unsigned)两种,如果不是必须,两种,如果不是必须,尽可能选择尽可能选择unsignedunsigned型型,这将会使编译器省却符号位的检测,使生成的程序代,这将会使编译器省却符号位的检测,使生成的程序代码比码比signedsigned类型短得多。类型短得多。 C5l C5l编译器除了能支持以上这些基本数据类型之外,还能支持一编译器除了能支持以上这些基本数据类型之外,还能支持一些复杂的组合型数据类型,如数组类型、指针类型、
28、结构类型、联些复杂的组合型数据类型,如数组类型、指针类型、结构类型、联合类型等这些复杂的数据类型。合类型等这些复杂的数据类型。4.4 4.4 数据的存储类型和存储模式数据的存储类型和存储模式 同同ANSI CANSI C一样,一样,C51C51规定变量必须先定义后使用。规定变量必须先定义后使用。C51C51对变量对变量的进行定义的格式如下:的进行定义的格式如下: 存储种类存储种类 数据类型数据类型 存储器类型存储器类型 变量名表变量名表 其中,存储种类和存储类型是可选项。其中,存储种类和存储类型是可选项。 一、变量的存储种类一、变量的存储种类 按变量的有效作用范围可以将其划分为局部变量和全局变
29、量;按变量的有效作用范围可以将其划分为局部变量和全局变量;还可以按变量的的存储方式为其划分存储种类。还可以按变量的的存储方式为其划分存储种类。 在在C C语言中变量有四种存储种类,即自动语言中变量有四种存储种类,即自动(auto)(auto)、外部、外部(extern)(extern)、静态、静态(static)(static)和寄存器和寄存器(register)(register)。 这四种存储种类与全局变量和局部变量之间的关系如图所示这四种存储种类与全局变量和局部变量之间的关系如图所示 1 1、自动变量(、自动变量(autoauto) 定义一个变量时,在变量名前面加上存储种类说明符定义一个
30、变量时,在变量名前面加上存储种类说明符“auto”auto”,即将该变量定义为自动变量。自动变量是,即将该变量定义为自动变量。自动变量是C C语言中使用最为广泛语言中使用最为广泛的一类变量。的一类变量。 自动变量的作用范围在定义它的函数体或复合语句内部,只有自动变量的作用范围在定义它的函数体或复合语句内部,只有在定义它的函数内被调用,或是定义它的复合语句被执行时,编译在定义它的函数内被调用,或是定义它的复合语句被执行时,编译器才为其分配内存空间,开始其生存期。当函数调用结束返回,或器才为其分配内存空间,开始其生存期。当函数调用结束返回,或复合语句执行结束时,自动变量所占用的内存空间就被释放,变
31、量复合语句执行结束时,自动变量所占用的内存空间就被释放,变量的值当然也就不复存在,其生存期结束。的值当然也就不复存在,其生存期结束。自动变量始终是相对于函自动变量始终是相对于函数或复合语句的局部变量。数或复合语句的局部变量。 一、变量的存储种类一、变量的存储种类 2 2、外部变量、外部变量(extern)(extern) 使用存储种类说明符使用存储种类说明符“extern”extern”定义的变量称为外部变量。定义的变量称为外部变量。 按照缺省规则,凡是在所有函数之前,在函数外部定义的变量按照缺省规则,凡是在所有函数之前,在函数外部定义的变量都是外部变量,定义时可以不写都是外部变量,定义时可以
32、不写externextern说明符。但是,在一个函数说明符。但是,在一个函数体内说明一个已在该函数体外或别的程序模块文件中定义过的外部体内说明一个已在该函数体外或别的程序模块文件中定义过的外部变量时,则必须要使用变量时,则必须要使用externextern说明符。说明符。 一个外部变量被定义之后,它就被分配了固定的内存空间。一个外部变量被定义之后,它就被分配了固定的内存空间。 外部变量的生存期为程序的整个执行时间,即在程序的执行期外部变量的生存期为程序的整个执行时间,即在程序的执行期间外部变量可被随意使用,当一条复合语句执行完毕或是从某一个间外部变量可被随意使用,当一条复合语句执行完毕或是从某
33、一个函数返回时,外部变量的存储空间并不被释放,其值也仍然保留。函数返回时,外部变量的存储空间并不被释放,其值也仍然保留。因此,因此,外部变量属于全局变量外部变量属于全局变量。 C C语言允许将大型程序分解为若干个独立的程序模块文件,各语言允许将大型程序分解为若干个独立的程序模块文件,各个模块可分别进行编译,然后再将它们连接在一起。个模块可分别进行编译,然后再将它们连接在一起。 2 2、外部变量、外部变量(extern)(extern) 在这种情况下,如果某个变量需要在所有程序模块文件中在这种情况下,如果某个变量需要在所有程序模块文件中使用,只要在一个程序模块文件中将该变量定义成全局变量,而在使
34、用,只要在一个程序模块文件中将该变量定义成全局变量,而在其它程序模块文件中用其它程序模块文件中用externextern说明该变量是已被定义过的外部变量说明该变量是已被定义过的外部变量就可以了。就可以了。 函数是可以相互调用的,因此函数都具有外部存储种类的函数是可以相互调用的,因此函数都具有外部存储种类的属性。属性。 定义函数时如果冠以关键字定义函数时如果冠以关键字externextern即将其明确定义为一个外部即将其明确定义为一个外部函数。例如函数。例如extern extern intint func2(char a func2(char a,b)b)。如果在定义函数时省。如果在定义函数时
35、省略关键字略关键字externextern,则隐含为外部函数。如果要调用一个在本程序模,则隐含为外部函数。如果要调用一个在本程序模块文件以外的其它模块文件所定义的函数,则必须要用关键字块文件以外的其它模块文件所定义的函数,则必须要用关键字externextern说明被调用函数是一个外部函数。对于具有外部函数相互调说明被调用函数是一个外部函数。对于具有外部函数相互调用的多模块程序,可用用的多模块程序,可用C51C51编译器分别对各个模块文件进行编译,编译器分别对各个模块文件进行编译,最后最后L51L51连接定位器将它们连接成为一个完整的程序。连接定位器将它们连接成为一个完整的程序。 一、变量的存
36、储种类一、变量的存储种类 3 3、静态变量(、静态变量(staticstatic) 使用存储种类说明符使用存储种类说明符“static”static”定义的变量称为静态变量。静定义的变量称为静态变量。静态变量分为局部静态变量和全局静态变量。态变量分为局部静态变量和全局静态变量。 局部静态变量不象自动变量那样只有当函数调用它时才存在,局部静态变局部静态变量不象自动变量那样只有当函数调用它时才存在,局部静态变量始终都是存在的,但只能在定义它的函数内部进行访问,退出函数之后,变量始终都是存在的,但只能在定义它的函数内部进行访问,退出函数之后,变量的值仍然保持,但不能进行问。量的值仍然保持,但不能进行
37、问。 全局静态变量,它是在函数外部被定义的,作用范围从它的定全局静态变量,它是在函数外部被定义的,作用范围从它的定义点开始,一直到程序结束。义点开始,一直到程序结束。 当一个当一个C C语言程序由若干个模块文件所组成时,全局静态变量始终存在,语言程序由若干个模块文件所组成时,全局静态变量始终存在,但它只能在被定义的模块文件中访问,其数据值可为该文件内的所有函数共享,但它只能在被定义的模块文件中访问,其数据值可为该文件内的所有函数共享,退出该文件后,虽然变量的值仍然保持着,但不能被其它模块文件访问。局部退出该文件后,虽然变量的值仍然保持着,但不能被其它模块文件访问。局部静态变量是一种在两次函数调
38、用之间仍能保持其值的局部变量。有些程序需要静态变量是一种在两次函数调用之间仍能保持其值的局部变量。有些程序需要在多次调用之间仍然保持变量的值,使用自动变量无法实现这一点,使用在多次调用之间仍然保持变量的值,使用自动变量无法实现这一点,使用全局变量有时又会带来意外的副作用,这时就可采用局部静态变量。全局变量有时又会带来意外的副作用,这时就可采用局部静态变量。一、变量的存储种类一、变量的存储种类 4 4、寄存器变量、寄存器变量(register)(register) 为了提高程序的执行效率,为了提高程序的执行效率,C C语言允许将一些使用频率最高的语言允许将一些使用频率最高的那些变量,定义为能够直
39、接使用硬件寄存器的所谓寄存器变量。那些变量,定义为能够直接使用硬件寄存器的所谓寄存器变量。定义一个变量时在变量名前而冠以存储种类符号定义一个变量时在变量名前而冠以存储种类符号“register”register”即将即将该变量定义成为了寄存器变量。该变量定义成为了寄存器变量。 寄存器变量可以被认为是自动变量的一种,它的有效作用范围寄存器变量可以被认为是自动变量的一种,它的有效作用范围也与自动变量相同。也与自动变量相同。 C51C51编译器能够识别程序中使用频率最高的变量,在可能的情编译器能够识别程序中使用频率最高的变量,在可能的情况下,即使程序中并未将该变量定义为寄存器变量,编译器也会自况下,
40、即使程序中并未将该变量定义为寄存器变量,编译器也会自动将其作为寄存器变量处理。动将其作为寄存器变量处理。 因此,用户无须专门声明寄存器变量。因此,用户无须专门声明寄存器变量。 4.4 4.4 数据的存储类型和存储模式数据的存储类型和存储模式二、数据的存储类型二、数据的存储类型 C51C51是面向是面向8XX518XX51系列单片机及硬件控制系统的开发语言,它定系列单片机及硬件控制系统的开发语言,它定义的任何变量必须以一定的存储类型的方式定位在义的任何变量必须以一定的存储类型的方式定位在8XX518XX51的某一存的某一存储区中,否则便没有意义。因此在定义变量类型时,还必须定义它储区中,否则便没
41、有意义。因此在定义变量类型时,还必须定义它的存储类型,的存储类型,C51C51的变量有如下几种存储类型:的变量有如下几种存储类型:存储器类型存储器类型描描述述data直接寻址内部数据存储区直接寻址内部数据存储区,访问变量速度最快访问变量速度最快(128Byte)bdata可为寻址内部数据存储区可为寻址内部数据存储区,允许位与字节混合访问允许位与字节混合访问(16Byte)idata间接寻址内部数据存储区间接寻址内部数据存储区,可访问全部内部地址空间可访问全部内部地址空间(256Byte)pdata分页分页(256Byte)外部数据存储区外部数据存储区,由操作码由操作码MOVXRi访问访问xda
42、ta外部数据存储区外部数据存储区(64KB),由操作码由操作码MOVXDPTR访问访问code程序存储区程序存储区(64KB),由操作码由操作码MOVCA+DPTR访问访问 如果在变量定义时省略了存储器类型标识符,如果在变量定义时省略了存储器类型标识符,C51C51编译器会选编译器会选择默认的存储器类型。默认的存储器类型由择默认的存储器类型。默认的存储器类型由SMALLSMALL、COMPACTCOMPACT和和LARGELARGE存储模式指令决定。存储模式指令决定。1 1)datadata区区 对对datadata区的寻址是最快的,所以应该把使用频率高的变量放在区的寻址是最快的,所以应该把使
43、用频率高的变量放在datadata区,由于空间有限,必须注意使用区,由于空间有限,必须注意使用datadata区,区,datadata区除了包含程区除了包含程序变量外,还包含了堆栈和寄存器组序变量外,还包含了堆栈和寄存器组datadata区。区。 在在SMALLSMALL存储模式下,未说明存储器类型时,变量默认被定位存储模式下,未说明存储器类型时,变量默认被定位在在datadata区。区。2 2)bdatabdata区区 当在当在DATADATA区的位寻址区定义变量,这个变量就可进行位寻址,区的位寻址区定义变量,这个变量就可进行位寻址,并且声明位变量。这对状态寄存器来说十分有用,因为它可以单独
44、并且声明位变量。这对状态寄存器来说十分有用,因为它可以单独使用变量的每一位,而不一定要用位变量名引用位变量。使用变量的每一位,而不一定要用位变量名引用位变量。 3 3)idataidata区区 idataidata区也可以存放使用比较频繁的变量,使用寄存器作为指区也可以存放使用比较频繁的变量,使用寄存器作为指针进行寻址。在寄存器中设置针进行寻址。在寄存器中设置8 8位地址进行间接寻址,与外部存储位地址进行间接寻址,与外部存储器寻址比较,它的指令执行周期和代码长度都比较短。器寻址比较,它的指令执行周期和代码长度都比较短。 4 4)pdatapdata和和xdataxdata区区 在这两个区声明变
45、量和在其他区的语法是一样的,在这两个区声明变量和在其他区的语法是一样的,pdatapdata区只区只有有256B256B,而,而xdataxdata区可达区可达65536B 65536B 5 5)codecode区区 code code区即区即80C5180C51的程序代码区,所以代码区的数据是不可改变的程序代码区,所以代码区的数据是不可改变的,的,80C5180C51的代码区不可重写。一般代码区中可存放数据表,跳转的代码区不可重写。一般代码区中可存放数据表,跳转向量和状态表。向量和状态表。 如果用户不对变量的存贮如果用户不对变量的存贮 类型定义,则编译器承认默认存贮类型定义,则编译器承认默认
46、存贮类型,默认的存贮类型由编译控制命令的存贮的模式部分类型,默认的存贮类型由编译控制命令的存贮的模式部分 决定。决定。三、数据的存储模式三、数据的存储模式 在固定的存贮器地址进行变量参数传递是在固定的存贮器地址进行变量参数传递是C51C51的一个标准特征,的一个标准特征,定义了变量、参数传递区的存贮器模式,也就是默认了变量和参数定义了变量、参数传递区的存贮器模式,也就是默认了变量和参数传递区存贮器类型、无需再对变量和参数传递区的存贮器类型进行传递区存贮器类型、无需再对变量和参数传递区的存贮器类型进行说明。说明。 存贮器模式决定了变量的默认存贮器类型、参数传递区和无明存贮器模式决定了变量的默认存
47、贮器类型、参数传递区和无明确存贮区类型的说明。确存贮区类型的说明。 有三种存贮器模式:有三种存贮器模式:SMALLSMALL、LARGELARGE和和COMPACTCOMPACT存储器模式存储器模式描描述述SMALL参数及局部变量放入可直接寻址的内部数据存储参数及局部变量放入可直接寻址的内部数据存储区(区(128Byte,默认存储器类型是默认存储器类型是DATA)COMPACT参数及局部变量放入分页外部数据存储区(最大参数及局部变量放入分页外部数据存储区(最大256Byte,默认存储类型是默认存储类型是PDATA)LARGE参数及局部变量直接放入外部数据存储器(最大参数及局部变量直接放入外部数
48、据存储器(最大64KB,默认存储器类型为默认存储器类型为XDATA)1 1小小(SMALL)(SMALL)模式模式 所有变量都默认在所有变量都默认在80518051的内部数据存储器中。这和用的内部数据存储器中。这和用datadata显式显式定义变量起到相同的作用。定义变量起到相同的作用。 2 2紧凑紧凑(COMPACT)(COMPACT)模式模式 此模式中,所有变量都默认在此模式中,所有变量都默认在80518051的外部数据存储器的一页中。的外部数据存储器的一页中。 3 3大大(LARGE)(LARGE)模式模式 在大模式下,所有的变量都默认在外部存储器中在大模式下,所有的变量都默认在外部存储
49、器中( (xdataxdata) )。 为了能够直接访问这些特殊功能寄存器为了能够直接访问这些特殊功能寄存器 ,C51C51编译器扩充了关编译器扩充了关键字键字sfrsfr和和sfrl6sfrl6,利用这种扩充关键字可以在,利用这种扩充关键字可以在C C语言源程序中直语言源程序中直接对接对805l805l单片机的特殊功能寄存器进行定义。定义方法如下:单片机的特殊功能寄存器进行定义。定义方法如下: sfr特殊功能寄存器名地址常数特殊功能寄存器名地址常数;例如:sfr P00x80;* 定义IO口P0,其地址为80H *例如:例如:设设C语言源程序为语言源程序为PROR.C,若使程序中的变量类型和
50、参数若使程序中的变量类型和参数传递区限定在外部数据存贮区传递区限定在外部数据存贮区,有两种方法:,有两种方法:方法方法1:在程序的第一句加预处理命令:在程序的第一句加预处理命令#pragmacompact。方法方法2:用:用C51对对PROR.C进行编译时,使用编译控制命令进行编译时,使用编译控制命令:C51PROR.CCOMPACT。四、变量说明举例四、变量说明举例datacharvar;/*字符变量字符变量var定位在片内定位在片内RAM区区charcodeMSG=ENTERPARAMETER:/*字符数组定位在程序存贮区字符数组定位在程序存贮区*/unsignedlongxdataarr
51、ay100;/*无符号长无符号长型数组定位在片外型数组定位在片外RAM区,每区,每元素占元素占4bytes*/floatidatax,y,z;/*实型变量实型变量x,y,z,定位在片内用定位在片内用间址访问的内部间址访问的内部RAM区区*/bitlock;/*位变量位变量Lock定位在片内定位在片内RAM可位寻址区可位寻址区*/unsignedintpdatadimension;/*无符号整型变量无符号整型变量dimension定位在分页的外部定位在分页的外部RAM区区*/unsignedcharxdatavector1044;/*无符号字符型三无符号字符型三维数组,维数组,定位在片外定位在片
52、外RAM区区*/sfrP0=0x80;/*定义定义P0口,地址为口,地址为80H*/charbdataflags;/*字符变量字符变量flags定位在定位在可位寻址内部可位寻址内部RAM区区*/sbitflag0=flags0;/*定义定义flag0为为flags.0如果在变量说明时略去存贮器类型标志符,编译器会如果在变量说明时略去存贮器类型标志符,编译器会自动选择默认的存贮器类型。默认的存自动选择默认的存贮器类型。默认的存贮器类型进一步贮器类型进一步由控制指令由控制指令SMALL、COMPACT和和LARGE限制。限制。例:如果声明例:如果声明charvar,则默认则默认的存贮器模式为的存贮
53、器模式为SMALL,var放在放在data存贮区;存贮区;如果使用如果使用COMPACT模式,模式,var放入放入idata存贮区存贮区;使用使用LARGE模式,模式,var被放入外部存贮区被放入外部存贮区(xdata存贮区存贮区)。一、指针与指针变量一、指针与指针变量 指针指针就是存储单元地址,存储这个地址的变量称为指针变量。就是存储单元地址,存储这个地址的变量称为指针变量。(1 1)指针变量)指针变量 在汇编语言程序中,要取存贮单元在汇编语言程序中,要取存贮单元m m的内容可用的内容可用直接寻址方式直接寻址方式,也可用也可用寄存器间接寻址方式寄存器间接寻址方式 ,如果用,如果用R1R1寄存
54、器指示寄存器指示m m的地址,用的地址,用R1R1就是取就是取m m单元的内容。单元的内容。 相对应的在相对应的在C C语言中可用语言中可用变量名变量名表示取变量的值表示取变量的值( (相当于直接寻相当于直接寻址址) ),也可用另一个变量,也可用另一个变量( (如如P)P)存放存放m m的地址,的地址,P P就相当于就相当于R1R1寄存器寄存器 。用。用* *P P取得取得m m单元的内容单元的内容( (相当于汇编的间接寻址方式相当于汇编的间接寻址方式) )这里这里P P即为即为指指针型变量针型变量。4.5 4.5 指针指针直接寻址直接寻址 间接寻址间接寻址汇编语言汇编语言C C 语言语言汇编
55、语言汇编语言C C 语言语言MOV n,mMOV n,m 传送语句传送语句n=mn=m; 赋值语句赋值语句MOV R1,#mMOV R1,#m; m; m的地址送的地址送R1R1MOV n,R1 MOV n,R1 ;m;m的内容送的内容送n n P=&mP=&m;;/*m;/*m的地址的地址P*/P*/n=*Pn=*P;; /*m; /*m的的内容内容n*/n*/注:注: 汇编语言程序中对符号地址汇编语言程序中对符号地址n n和和m m需用需用EQUEQU伪指令进行地址定义。伪指令进行地址定义。 C C语言应对变量语言应对变量n n、m m和指针变量和指针变量P P需进行类型定义。需进行类型定
56、义。 表中表中& &为为取地址运算符取地址运算符,* *为为取内容运算符取内容运算符。 下面表格表示两种语言将下面表格表示两种语言将m m单元的内容送单元的内容送n n单元的对照语句。单元的对照语句。 (2)(2)指针型变量的类型指针型变量的类型 5151单片机的不同存贮空间,有不同的地址范围,即使对于同一单片机的不同存贮空间,有不同的地址范围,即使对于同一外部数据存贮器,又有用外部数据存贮器,又有用 RiRi分页寻址分页寻址( (RiRi为八位为八位) )和用和用DPTRDPTR寻址寻址(DPTR(DPTR为十六位为十六位) )两种寻址方式。两种寻址方式。 指针是指示变量的地址的,因此,在指
57、针类型的定义中要说明指针是指示变量的地址的,因此,在指针类型的定义中要说明被指的变量的数据类型和存贮类型。同时指针变量本身也是一个变被指的变量的数据类型和存贮类型。同时指针变量本身也是一个变量,有它存放的存贮区和数据长度。即量,有它存放的存贮区和数据长度。即指针变量本身有它的存贮类指针变量本身有它的存贮类型和数据长度,其数据长度是由被指的变量的存贮类型而定的型和数据长度,其数据长度是由被指的变量的存贮类型而定的。 指针变量存储类型:指针变量存储类型:datadata、idataidata、pdatapdata以上均为八位地址指示,所以指针长度为以上均为八位地址指示,所以指针长度为1Byte1B
58、yte。codecode、xdataxdata 这些均为十六位地址指示,所以指针长度为这些均为十六位地址指示,所以指针长度为2Byte2Byte。指针变量存储类型:指针变量存储类型: 如果指针的存储类型缺省,指针定义为通用型指针,如果指针的存储类型缺省,指针定义为通用型指针,表示指针可指向任何存贮空间,表示指针可指向任何存贮空间,此时指针长度为此时指针长度为3 3字节字节。通用型指针的存贮类型编码如下:通用型指针的存贮类型编码如下:表中表中v4.0v4.0、v5.0v5.0表示表示 C51C51的版本的版本第一字节第一字节第二字节第二字节第三字节第三字节存贮类型编码存贮类型编码所指地址的高八位
59、所指地址的高八位所指地址的低八位所指地址的低八位存贮器类型存贮器类型idataidataxdataxdatapdatapdatadatadatacodecode编编 码码(v4.0)(v4.0)1 12 23 34 45 5编编 码码(v5.0)(v5.0)0 00 01 1-2-2-1-1 例如指针变量例如指针变量pxpx值为值为0x021203 (v4.00x021203 (v4.0版版) )或为或为0x001 0x001 203 (v5.0203 (v5.0版版) ) ,即指针指向,即指针指向xdataxdata 区区的的1203H1203H地址单元。地址单元。如:如: char *pd
60、char *pd; /* pd /* pd 定义为通用型指针定义为通用型指针* */ /l指针变量的数据类型:指针变量的数据类型: 同样有同样有charchar、intint、longlong等类型,等类型, 表示指针指向的数表示指针指向的数据的长度是占据的长度是占1 1个单元、个单元、2 2个单元还是个单元还是4 4个单元。个单元。(2)被指数据类型被指数据类型 被指存贮类型被指存贮类型 * *指针变量存贮类型指针变量存贮类型 指针变量指针变量 例如例如long code * xdata px; /*和上面定义等同和上面定义等同*/px为指针型为指针型变量变量被被指指向向的的存存贮贮器器的的
61、数数据据类型为长型类型为长型px指向程指向程存贮器存贮器px自身在外部自身在外部数据存贮器中数据存贮器中longxdatacode*px ;指针变量说明有两种格式:指针变量说明有两种格式:指针变量说明有两种格式:指针变量说明有两种格式:(1) 指针变量存贮类型指针变量存贮类型 被指数据类型被指数据类型 被指存贮类型被指存贮类型 * *指针变量名;指针变量名;( 其中其中其中其中为可选项为可选项为可选项为可选项 )如:如:如:如:(3) (3) 指针变量说明举例指针变量说明举例例如例如charxdata*datapd;/*指针变量指针变量pd指向字符型指向字符型xdata区,自区,自身在身在da
62、ta区,长度区,长度2字节字节*/longxdata*px;/*指针变量指针变量px指向指向long型型xdata区区(被指的数据在被指的数据在xdata区,每个区,每个数据占四个单元,指针自身在默认存贮器数据占四个单元,指针自身在默认存贮器(如不指定编译模式在如不指定编译模式在data区区),指针长度为,指针长度为2个字节个字节*/datacharxdata*pd;/*与上例等效与上例等效*/说明说明:1.”*”号不可少,它表示变量为指针变量。号不可少,它表示变量为指针变量。2.指针变量说明格式中的指针变量说明格式中的为可选项为可选项,如,如被指存贮类型被指存贮类型缺缺省,则指针定义为通用型
63、,如省,则指针定义为通用型,如指针变量存贮类型指针变量存贮类型缺省、指针变量缺省、指针变量则存放在默认存贮区或者在则存放在默认存贮区或者在data区。区。(3) (3) 指针变量说明举例指针变量说明举例二、指向数组的指针变量二、指向数组的指针变量如果用一个变量存放一个数组的地址,这个变量就称为指向数组的指针变量。如果用一个变量存放一个数组的地址,这个变量就称为指向数组的指针变量。数组的起始地址称为数组的起始地址称为数组指针数组指针,一个数组,一个数组a的起始地址用的起始地址用a表示。表示。(1 1)指向数组的指针变量的定义和赋值)指向数组的指针变量的定义和赋值设定义了一个数组设定义了一个数组a
64、5和一个指针变量和一个指针变量ap: char data a5; char data a5; char data * char data *apap; ;仅此两句并不能说明变量仅此两句并不能说明变量ap是指向数组的,还必须将数组的起始地址赋给该是指向数组的,还必须将数组的起始地址赋给该变量:变量:apap=a;=a;/*数组数组a的起始地址赋给指针变量的起始地址赋给指针变量ap*/或或apap=&a0;=&a0;/*意义同上意义同上*/也可以使定义和赋值在一条语句完成:也可以使定义和赋值在一条语句完成:char data *char data *apapa a; 或或 char data *
65、char data * apap=&a0;=&a0;2)利用指向数组的指针变量引用数组元素利用指向数组的指针变量引用数组元素指向数组的指针变量引用数组元素有两种方法:指向数组的指针变量引用数组元素有两种方法:* *( (ap+iap+i) ) 或或 ap(iap(i) ) ,它们等同于它们等同于*(a+i)或或a(i)例例main()main() char a5=11,22,33,44,55;char a5=11,22,33,44,55;char char b,c,db,c,d; ; char *char *apap; ;apap=a;=a;/*ap等于数组等于数组a5的起始地址的起始地址*/
66、b=a+2;b=a+2;/*b等于数组元素等于数组元素a2的地址的地址*/c=ap+3;c=ap+3;/*c等于数组元素等于数组元素a3的地址的地址*/d=*(ap+3);d=*(ap+3);/*d等于数组元素等于数组元素a3的值,的值,即即d=44,等同于,等同于d=a(3)*/ 4.6 C514.6 C51对对SFRSFR、可寻址位、存储器和可寻址位、存储器和I/OI/O口的定义口的定义一、特殊功能寄存器一、特殊功能寄存器SFRSFR定义定义C51对特殊功能寄存器对特殊功能寄存器SFR有两种定义方法有两种定义方法(1)使用特定关键字)使用特定关键字sfr自主形式的定义方式,自主形式的定义方
67、式,sfrsfr 寄存器名寄存器名= =寄存器地址寄存器地址其中寄存器地址必须大写其中寄存器地址必须大写如如sfrsfr SCON=0x98 SCON=0x98;/*串行通信控制寄存器串行通信控制寄存器地址地址98H*/sfrsfr TMOD=0x89 TMOD=0x89;/*定时器模式控制寄存器地址定时器模式控制寄存器地址89H*/sfrsfr ACC=0xe0 ACC=0xe0;/*A累加器地址累加器地址E0H*/sfrsfr P1=0x90 P1=0x90;/*P1端口地址端口地址90H*/定义了以后,程序中就可以直接引用寄存器。定义了以后,程序中就可以直接引用寄存器。(2)使用头文件)
68、使用头文件C51建有头文件建有头文件reg51.h、reg52.h,在该头文件中对在该头文件中对51或或52系系列单片机所有的特殊功能寄存器的进行了列单片机所有的特殊功能寄存器的进行了sfr定义,对特殊功能寄存定义,对特殊功能寄存器的有位名称的可寻址位进行了器的有位名称的可寻址位进行了sbit定义,因此,只要用包含语句定义,因此,只要用包含语句#include就可以直接引用特殊功能寄存器名,或直接引用就可以直接引用特殊功能寄存器名,或直接引用位名称。位名称。要特别注意:在引用时特殊功能寄存器或者位名称必须大写。要特别注意:在引用时特殊功能寄存器或者位名称必须大写。二、对位变量的定义二、对位变量
69、的定义C51对位变量的定义方法有三种:对位变量的定义方法有三种:1.将变量用将变量用bit类型的定义符定义为类型的定义符定义为bit类型类型如如 bit bit mnmn;mnmn为位变量,其值只能是为位变量,其值只能是“0”0”或或“1”1”,其位地址,其位地址C51C51自行安排在可位寻址区的自行安排在可位寻址区的bdatabdata区。区。2.采用采用字节寻址变量字节寻址变量.位位的方法的方法如如bdatabdata intint ibaseibase;/*ibase定义为整型变量定义为整型变量*/sbitsbit mybitmybit=ibase15=ibase15;/*mybit定义
70、为定义为ibase的的D15位位*/这里位是运算符这里位是运算符“”相当于汇编中的相当于汇编中的“”,其后的最大取,其后的最大取值依赖于该位所在的变量的类型,如定义为值依赖于该位所在的变量的类型,如定义为char最大值只能为最大值只能为7。二、对位变量的定义二、对位变量的定义C51对位变量的定义方法有三种:对位变量的定义方法有三种:3.对特殊功能寄存器的位的定义对特殊功能寄存器的位的定义方法方法1:使用头文件及:使用头文件及sbit定义符;多用于无位名的可寻址位。定义符;多用于无位名的可寻址位。如如#include #include sbitsbit P1_1=P11 P1_1=P11;/*P
71、1_1为为P1口的第口的第1位位*/sbitsbit ac=ACC7 ac=ACC7;/*ac定义为累加器定义为累加器A的第的第7位位*/方法方法2:使用头文件:使用头文件reg51.h,再直接用位名称。,再直接用位名称。例如例如#include #include RS1=1 RS1=1; RS0=0RS0=0;方法方法3:用字节地址位表示:用字节地址位表示如如sbitsbit OV=0xD02 OV=0xD02;方法方法4:用寄存器名:用寄存器名.位定义位定义 如如sfrsfr PSW=0xd0 PSW=0xd0; /*/*定义定义PSWPSW地址为地址为d0H*/d0H*/ sbitsbi
72、t CY=PSW7 CY=PSW7; /*CY/*CY为为PSW7*/ PSW7*/ 三、对存贮器和外接三、对存贮器和外接I/OI/O口的绝对地址访问口的绝对地址访问C51对存贮器和外接对存贮器和外接I/O口的绝对地址访问可以通过指针访问,口的绝对地址访问可以通过指针访问,也可以通过函数访问。也可以通过函数访问。1.对存贮器的绝对地址访问对存贮器的绝对地址访问利用绝对地址访问的头文件利用绝对地址访问的头文件absacc.h可对不同的存贮区进行访问。可对不同的存贮区进行访问。该头文件的函数有该头文件的函数有:CBYTE CBYTE (访问访问code区字符型区字符型) DBYTE DBYTE (
73、访问访问data区字符型区字符型)PBYTE PBYTE (访问访问pdata或或I/O)XBYTEXBYTE(访问访问xdata或或I/O)还有还有CWORD、DWORD、PWORD和和XWORD四个函数,它四个函数,它们的访问区域同上,只是访问的类型为们的访问区域同上,只是访问的类型为int型型例:例:#include#include #define com XBYTE#define com XBYTE0x07ff0x07ff那么后面程序那么后面程序com变量出现的地方,就是对地址为变量出现的地方,就是对地址为07ffH外部外部RAM或或I/O口进行访问。口进行访问。例:例:XWORDXW
74、ORD0 0=0x9988=0x9988;即将即将9988H(int类型类型)送入外部送入外部RAM的的0号和号和1号单元。号单元。使用中要注意使用中要注意:absacc.habsacc.h一定要包含进程序。一定要包含进程序。 CBYTECBYTE、DBYTEDBYTE、XBYTEXBYTE等函数名必须大写。等函数名必须大写。2.对外部对外部I/O口的访问口的访问由于单片机的由于单片机的I/O口和外部口和外部RAM统一编址,因此对统一编址,因此对I/O口地址的口地址的访问可用访问可用XBYTE(MOVX DPTR )XBYTE(MOVX DPTR )或或PBYTE(MOVX PBYTE(MOV
75、X RiRi) )进行。进行。4.7 C514.7 C51的运算符及表达式的运算符及表达式一、赋值运算符一、赋值运算符 赋值运算符赋值运算符“=”,在,在C51中,它的功能是将一个数据的值赋给中,它的功能是将一个数据的值赋给一个变量,如一个变量,如x=10。利用赋值运算符将一个变量与一个表达式连接。利用赋值运算符将一个变量与一个表达式连接起来的式子称为赋值表达式,在赋值表达式的后面加一个分号起来的式子称为赋值表达式,在赋值表达式的后面加一个分号“;”就构成了赋值语句,一个赋值语句的格式如下:就构成了赋值语句,一个赋值语句的格式如下:变量变量=表达式;表达式;执行时先计算出右边表达式的值,然后赋
76、给左边的变量。例如:执行时先计算出右边表达式的值,然后赋给左边的变量。例如:x=8+9;/*将将8+9的值赋绐变量的值赋绐变量x*/x=y=5;/*将常数将常数5同时赋给变量同时赋给变量x和和y*/在在C51中,允许在一个语句中同时给多个变量赋值,赋值顺序中,允许在一个语句中同时给多个变量赋值,赋值顺序自右向左。自右向左。4.7 C514.7 C51的运算符及表达式的运算符及表达式二、算术运算符二、算术运算符C51中支持的算术运算符有:中支持的算术运算符有:+加或取正值运算符加或取正值运算符-减或取负值运算符减或取负值运算符*乘运算符乘运算符/除运算符除运算符%取余运算符取余运算符加、减、乘运
77、算相对比较简单,而对于除运算,如相除的两个加、减、乘运算相对比较简单,而对于除运算,如相除的两个数为浮点数,则运算的结果也为浮点数,如相除的两个数为整数,数为浮点数,则运算的结果也为浮点数,如相除的两个数为整数,则运算的结果也为整数,即为整除。如则运算的结果也为整数,即为整除。如25.0/20.0结果为结果为1.25,而,而25/20结果为结果为1。对于取余运算,则要求参加运算的两个数必须为整数,运算结对于取余运算,则要求参加运算的两个数必须为整数,运算结果为它们的余数。例如:果为它们的余数。例如:x=5%3,结果,结果x的值为的值为2。3 关系运算符关系运算符C51中有6种关系运算符: 大于
78、= 大于等于3,结果为真(1),而10= =100,结果为假(0)。注意:关系运算符等于“= =”是由两个“=”组成。4 逻辑运算符逻辑运算符C51有3种逻辑运算符:| 逻辑或& 逻辑与! 逻辑非 关系运算符用于反映两个表达式之间的大小关系,逻辑运算符则用于求条件式的逻辑值,用逻辑运算符将关系表达式或逻辑量连接起来的式子就是逻辑表达式。 逻辑与,格式: 条件式条件式1 & 条件式条件式2 当条件式1与条件式2都为真时结果为真(非0值),否则为假(0值)。逻辑或,格式: 条件式条件式1 | 条件式条件式2 当条件式1与条件式2都为假时结果为假(0值),否则为真(非0值)。逻辑非,格式: !条件式
79、!条件式 当条件式原来为真(非0值),逻辑非后结果为假(0值)。当条件式原来为假(0值),逻辑非后结果为真(非0值)。例如:若a=8,b=3,c=0,则!a为假,a & b为真,b & c为假。5 位运算符位运算符 C51语言能对运算对象按位进行操作,它与汇编语言使用一样方便。位运算是按位对变量进行运算,但并不改变参与运算的变量的值。如果要求按位改变变量的值,则要利用相应的赋值运算。C51中位运算符只能对整数进行操作,不能对浮点数进行操作。C51中的位运算符有:& 按位与| 按位或 按位异或 按位取反 右移【例例4-10】设a=0x45=01010100B,b=0x3b=00111011B,则
80、a&b、a|b、ab、a、a2分别为多少?a&b=00010000b=0x10。a|b=01111111B=0x7f。ab=01101111B=0x6f。a=10101011B=0xab。a2=00001110B=0x0e。6 复合赋值运算符复合赋值运算符 C51语言中支持在赋值运算符“=”的前面加上其它运算符,组成复合赋值运算符。下面是C51中支持的复合赋值运算符:+= 加法赋值 + 减法赋值*= 乘法赋值 /= 除法赋值%= 取模赋值 &= 逻辑与赋值|= 逻辑或赋值 = 逻辑异或赋值= 逻辑非赋值 = 右移位赋值=2相当于x=x2。7 逗号运算符逗号运算符 在C51语言中,逗号“,”是一
81、个特殊的运算符,可以用它将两个或两个以上的表达式连接起来,称为逗号表达式。逗号表达式的一般格式为: 表达式表达式1,表达式,表达式2,表达式,表达式n 程序执行时对逗号表达式的处理:按从左至右的顺序依次计算出各个表达式的值,而整个逗号表达式的值是最右边的表达式(表达式n)的值。例如:x=(a=3,6*3)结果x的值为18。8 条件运算符条件运算符 条件运算符“?:”是C51语言中唯一的一个三目运算符,它要求有三个运算对象,用它可以将三个表达式连接在一起构成一个条件表达式。条件表达式的一般格式为: 逻辑表达式?表达式逻辑表达式?表达式1:表达式:表达式2 其功能是先计算逻辑表达式的值,当逻辑表达
82、式的值为真(非0值)时,将计算的表达式1的值作为整个条件表达式的值;当逻辑表达式的值为假(0值)时,将计算的表达式2的值作为整个条件表达式的值。例如:条件表达式max=(ab)?a:b的执行结果是将a和b中较大的数赋值给变量max。9 指针与地址运算符指针与地址运算符 指针是C51语言中的一个十分重要的概念,在C51中的数据类型中专门有一种指针类型。指针为变量的访问提供了另一种方式,变量的指针就是该变量的地址,还可以定义一个专门指向某个变量的地址的指针变量。为了表示指针变量和它所指向的变量地址之间的关系,C51中提供了两个专门的运算符:* 指针运算符& 取地址运算符 指针运算符“*”放在指针变
83、量前面,通过它实现访问以指针变量的内容为地址所指向的存储单元。例如:指针变量p中的地址为2000H,则*p所访问的是地址为2000H的存储单元,x=*p,实现把地址为2000H的存储单元的内容送给变量x。 取地址运算符“&”放在变量的前面,通过它取得变量的地址,变量的地址通常送给指针变量。例如:设变量x的内容为12H,地址为2000H,则&x的值为2000H,如有一指针变量p,则通常用p=&x,实现将x变量的地址送给指针变量p,指针变量p指向变量x,以后可以通过*p访问变量x。4.8 表达式语句及复合语句表达式语句及复合语句1 表达式语句表达式语句在表达式的后边加一个分号“;”就构成了表达式语
84、句 ,如:a=+b*9;x=8;y=7;+k; 可以一行放一个表达式形成表达式语句,也可以一行放多个表达式形成表达式语句,这时每个表达式后面都必须带“;”号,另外,还可以仅由个分号“;”占一行形成一个表达式语句,这种语句称为空语句。 空语句在程序设计中通常用于两种情况:(1)在程序中为有关语句提供标号,用以标记程序执行的位置。例如采用下面的语句可以构成一个循环。repeat:; ; goto repeat;(2)在用while语句构成的循环语句后面加一个分号,形成一个不执行其它操作的空循环体。这种结构通常用于对某位进行判断,当不满足条件则等待,满足条件则执行。【例例1】下面这段子程序用于读取8
85、051单片机的串行口的数据,当没有接收到则等待,当接收到,接收数据后返回,返回值为接收的数据。#include char getchar()char c;while(!RI); /当接收中断标志位RI为0则等待,当接收中断标志位为1则结束等待。c=SBUF;RI=0;return(c);2 复合语句复合语句 复合语句是由若干条语句组合而成的一种语句,在C51中,用一个大括号“”将若干条语句括在一起就形成了一个复合语句,复合语句最后不需要以分号“;”结束,但它内部的各条语句仍需以分号“;”结束。复合语句的一般形式为:局部变量定义;语句l;语句2; 复合语句在执行时,其中的各条单语句按顺序依次执行
86、,整个复合语句在语法上等价于一条单语句,因此在C51中可以将复合语句视为一条单语句。通常复合语句出现在函数中,实际上,函数的执行部分(即函数体)就是一个复合语句;复合语句中的单语句一般是可执行语句,此外还可以是变量的定义语句(说明变量的数据类型)。在复合语句内部语句所定义的变量,称为该复合语句中的局部变量,它仅在当前这个复合语句中有效。利用复合语句将多条单语句组合在起,以及在复合语句中进行局部变量定义是C51语言的一个重要特征。4.9 C51的输入输出的输入输出在C51语言中,它本身不提供输入和输出语句,输入和输出操作是由函数来实现的。在C51的标准函数库中提供了一个名为“stdio.h”的一
87、般I/O函数库,它当中定义了C51中的输入和输出函数。当对输入和输出函数使用时,须先用预处理命令“#include ”将该函数库包含到文件中。 在C51的一般I/O函数库中定义的I/O函数都是通过串行接口实现,在使用I/O函数之前,应先对MCS-51单片机的串行接口进行初始化。选择串口工作于方式2(8位自动重载方式),波特率由定时器/计数器1溢出率决定。例如,设系统时钟为12MHZ,波特率为2400,则初始化程序如下:SCON=0x52;TMOD=0X20;TH1=0xf3;TR1=1;1 格式输出函数格式输出函数printf()printf()函数的的作用是通过串行接口输出若干任意类型的数据
88、,它的格式如下:printf(格式控制,输出参数表)格式控制是用双引号括起来的字符串,也称转换控制字符串,它包括三种信息:格式说明符、普通字符和转义字符。(1)格式说明符,由“%”和格式字符组成,它的作用是用于指明输出的数据的格式输出,如%d、%f等,它们的具体情况见表4-4。(2)普通字符,这些字符按原样输出,用来输出某些提示信息。(3)转义字符,就是前面介绍的转义字符(表4-2),用来输出特定的控制符,如输出转义字符n就是使输出换一行。输出参数表是需要输出的一组数据,可以是表达式。格式字符数据类型输出格式dint带符号十进制数uint无符号十进制数oint无符号八进制数xint无符号十六进
89、制数,用“af”表示Xint无符号十六进制数,用“AF”表示ffloat带符号十进制数浮点数,形式为-dddd.dddde,Efloat带符号十进制数浮点数,形式为-d.ddddEddg,Gfloat自动选择e或f格式中更紧凑的一种输出格式cchar单个字符s指针指向一个带结束符的字符串p指针带存储器批示符和偏移量的指针,形式为M:aaaa其中,M可分别为:C(code),D(data),I(idata),P(pdata)如M为a,则表示的是指针偏移量2 格式输入函数格式输入函数scanf()()scanf()函数的作用是通过串行接口实现数据输入,它的使用方法与printf()类似,scanf
90、()的格式如下:scanf(格式控制,地址列表)格式控制与printf()函数的情况类似,也是用双引号括起来的一些字符,可以包括以下三种信息:空白字符、普通字符和格式说明。(1)空白字符,包含空格、制表符、换行符等,这些字符在输出时被忽略。(2)普通字符,除了以百分号“%”开头的格式说明符而外的所有非空白字符,在输入时要求原样输入。(3)格式说明,由百分号“%”和格式说明符组成,用于指明输入数据的格式,它的基本情况与printf()相同,具体情况见表4-5。地址列表是由若干个地址组成,它可以是指针变量、取地址运算符“&”加变量(变量的地址)或字符串名(表示字符串的首地址)。格式字符数据类型输出
91、格式dint指针带符号十进制数uint指针无符号十进制数oint指针无符号八进制数xint指针无符号十六进制数f,e,Efloat指针浮点数cchar指针字符sstring指针字符串【例例2】 使用格式输入输出函数的例子#include /包含特殊功能寄存器库#include /包含I/O函数库void main(void) /主函数int x,y; /定义整型变量x和ySCON=0x52; /串口初始化TMOD=0x20;TH1=0XF3;TR1=1;printf(“input x,y:n”); /输出提示信息scanf(“%d%d”,&x,&y); /输入x和y的值printf(“n”);
92、 /输出换行printf(“%d+%d=%d”,x,y,x+y); /按十进制形式输出printf(“n”); /输出换行printf(“%xH+%xH=%XH”,x,y,x+y); /按十六进制形式输出while(1); /结束4.10 C51程序基本结构与相关语句程序基本结构与相关语句1 C51的基本结构的基本结构一顺序结构一顺序结构顺序结构是最基本、最简单的结构,在这种结构中,程序由低地址到高地址依次执行,图4.3给出顺序结构流程图,程序先执行A操作,然后再执行B操作。AB图4.3 顺序结构流程图 选择结构可使程序根据不同的情况,选择执行不同的分支,在选择结构中,程序先都对一个条件进行判
93、断。当条件成立,即条件语句为“真”时,执行一个分支,当条件不成立时,即条件语句为“假”时,执行另一个分支。如图4.4,当条件S成立时,执行分支A,当条件P不成立时,执行分支B。二选择结构二选择结构条件P语句A语句B成立不成立 在C51中,实现选择结构的语句为if/else,if/else if语句。另外在C51中还支持多分支结构,多分支结构既可以通过if和else if语句嵌套实现,可用swith/case语句实现。 在程序处理过程中,有时需要某一段程序重复执行多次,这时就需要循环结构来实现,循环结构就是能够使程序段重复执行的结构。循环结构又分为两种:当(while)型循环结构和直到(do.w
94、hile)型循环结构。(1)当型循环结构当型循环结构如图4-3,当条件P成立(为“真”)时,重复执行语句A,当条件不成立(为“假”)时才停止重复,执行后面的程序。三循环结构三循环结构条件P语句A成立不成立图4.5 当型循环结构 (2)直到型循环结构直到型循环结构如图4-4,先执行语句A,再判断条件P,当条件成立(为“真”)时,再重复执行语句A,直到条件不成立(为“假”)时才停止重复,执行后面的程序。条件P语句A成立不 成立图4.6 直到型循环结构构成循环结构的语句主要有:while、do while、for、goto等。2 if语句if语句是C51中的一个基本条件选择语句,它通常有三种格式:(
95、1)if (表达式) 语句;(2)if (表达式) 语句1; else 语句2;(3)if (表达式1) 语句1;else if (表达式2) (语句2;)else if (表达式3) (语句3;)else if (表达式n-1) (语句n-1;)else 语句n【例例3】 if语句的用法。(1)if (x!=y) printf(“x=%d,y=%dn”,x,y);执行上面语句时,如果x不等于y,则输出x的值和y的值。(2)if (xy) max=x;else max=y;执行上面语句时,如x大于y成立,则把x送给最大值变量max,如x大于y不成立,则把y送给最大值变量max。使max变量得到
96、x、y中的大数。(3)if (score=90) printf(“Your result is an An”);else if (score=80) printf(“Your result is an Bn”);else if (score=70) printf(“Your result is an Cn”);else if (score=60) printf(“Your result is an Dn”);else printf(“Your result is an En”);执行上面语句后,能够根据分数score分别打出A、B、C、D、E五个等级。3 switch/case语句语句if语句
97、通过嵌套可以实现多分支结构,但结构复杂。switch是C51中提供的专门处理多分支结构的多分支选择语句。它的格式如下:switch (表达式)case 常量表达式1:语句1;break;case 常量表达式2:语句2;break;case 常量表达式n:语句n;break;default:语句n+1;说明如下:(1)switch后面括号内的表达式,可以是整型或字符型表达式。(2)当该表达式的值与某一“case”后面的常量表达式的值相等时,就执行该“case”后面的语句,然后遇到break语句退出switch语句。若表达式的值与所有case后的常量表达式的值都不相同,则执行default后面的语
98、句,然后退出switch结构。(3)每一个case常量表达式的值必须不同否则会出现自相矛盾的现象。(4)case语句和default语句的出现次序对执行过程没有影响。(5)每个case语句后面可以有“break”,也可以没有。有break语句,执行到break则退出switch结构,若没有,则会顺次执行后面的语句,直到遇到break或结束。(6)每一个case语句后面可以带一个语句,也可以带多个语句,还可以不带。语句可以用花括号括起,也可以不括。(7)多个case可以共用一组执行语句。【例例4-14】 switch/case语句的用法。对学生成绩划分为AD,对应不同的百分制分数,要求根据不同的
99、等级打印出它的对应百分数。可以通过下面的switch/case语句实现。switch(grade)case A;printf(”90100n”);break;case B;printf(”8090n”);break;case C;printf(”7080n”);break;case D;printf(”6070n”);break;case E;printf(”60n”);break;default;printf(”error”n)4 while语句语句 while语句在C51中用于实现当型循环结构,它的格式如下: while(表达式) 语句; /*循环体*/ while语句后面的表达式是能否循
100、环的条件,后面的语句是循环体。当表达式为非0(真)时,就重复执行循环体内的语句;当表达式为0(假),则中止while循环,程序将执行循环结构之外的下一条语句。它的特点是:先判断条件,后执行循环体。在循环体中对条件进行改变,然后再判断条件,如条件成立,则再执行循环体,如条件不成立,则退出循环。如条件第一次就不成立,则循环体一次也不执行。 【例例5】 下面程序是通过while语句实现计算并输出1100的累加和。#include /包含特殊功能寄存器库#include /包含I/O函数库void main(void) /主函数int i,s=0; /定义整型变量x和yi=1;SCON=0x52; /
101、串口初始化TMOD=0x20;TH1=0XF3;TR1=1;while (i=100) /累加1100之和在s中s=s+i;i+;printf(“1+2+3+100=%dn”,s);while(1);程序执行的结果:1+2+3+100=50505 do while语句语句do while语句在C51中用于实现直到型循环结构,它的格式如下: do 语句; /*循环体*/ while(表达式); 它的特点是:先执行循环体中的语句,后判断表达式。如表达式成立(真),则再执行循环体,然后又判断,直到有表达式不成立(假)时,退出循环,执行do while结构的下一条语句。do while语句在执行时,循
102、环体内的语句至少会被执行一次。【例例4-16】 通过do while语句实现计算并输出1100的累加和。#include /包含特殊功能寄存器库#include /包含I/O函数库void main(void) /主函数int i,s=0; /定义整型变量x和yi=1;SCON=0x52; /串口初始化TMOD=0x20;TH1=0XF3;TR1=1;do /累加1100之和在s中s=s+i;i+;while (i=100);printf(“1+2+3+100=%dn”,s);while(1);程序执行的结果:1+2+3+100=5050 在C51语言中,for语句是使用最灵活、用得最多的循环
103、控制语句,同时也最为复杂。它可以用于循环次数已经确定的情况,也可以用于循环次数不确定的情况。它完全可以代替while语句,功能最强大。它的格式如下:for(表达式1;表达式2;表达式3)语句; /*循环体*/for语句后面带三个表达式,它的执行过程如下:(1)先求解表达式1的值。(2)求解表达式2的值,如表达式2的值为真,则执行循环休中的语句,然后执行下一步(3)的操作,如表达式2的值为假,则结束for循环,转到最后一步。(3)若表达式2的值为真,则执行完循环体中的语句后,求解表达式3,然后转到第四步。(4)转到(2)继续执行。(5)退出for循环,执行下面的一条语句。 在for循环中,一般表
104、达式1为初值表达式,用于给循环变量赋初值;表达式2为条件表达式,对循环变量进行判断;表达式3为循环变量更新表达式,用于对循环变量的值进行更新,使循环变量能不满足条件而退出循环。6 for语句【例例7】 用for语句实现计算并输出1100的累加和。#include /包含特殊功能寄存器库#include /包含I/O函数库void main(void) /主函数int i,s=0; /定义整型变量x和ySCON=0x52; /串口初始化TMOD=0x20;TH1=0XF3;TR1=1;for (i=1;i=100;i+) s=s+i; /累加1100之和在s中printf(“1+2+3+100=
105、%dn”,s);while(1);程序执行的结果:1+2+3+100=5050 在一个循环的循环体中允许又包含一个完整的循环结构,这种结构称为循环的嵌套。外面的循环称为外循环,里面的循环称为内循环,如果在内循环的循环体内又包含循环结构,就构成了多重循环。在C51中,允许三种循环结构相互嵌套。【例例4-18】用嵌套结构构造一个延时程序。void delay(unsigned int x)unsigned char j;while(x-)for (j=0;j125;j+); 这里,用内循环构造一个基准的延时,调用时通过参数设置外循环的次数,这样就可以形成各种延时关系。7 循环的嵌套循环的嵌套 br
106、eak和continue语句通常用于循环结构中,用来跳出循环结构。但是二者又有所不同,下面分别介绍。1break语句语句 前面已介绍过用break语句可以跳出switch结构,使程序继续执行switch结构后面的一个语句。使用break语句还可以从循环体中跳出循环,提前结束循环而接着执行循环结构下面的语句。它不能用在除了循环语句和switch语句之外的任何其它语句中。【例例4-19】下面一段程序用于计算圆的面积,当计算到面积大于100时,由break语句跳出循环。for (r=1;r100) break;printf(“%fn”,area);8 break和和continue语句语句 cont
107、inue语句用在循环结构中,用于结束本次循环,跳过循环体中continue下面尚未执行的语句,直接进行下一次是否执行循环的判定。 continue语句和break语句的区别在于:continue语句只是结束本次循环而不是终止整个循环;break语句则是结束循环,不再进行条件判断。【例例4-20】 输出100200间不能被3整除的数。for (i=100;i=200;i+)if (i%3= =0) continue;printf(“%d ”;i); 在程序中,当i能被3整除时,执行continue语句,结束本次循环,跳过printf()函数,只有能被3整除时才执行printf()函数。2cont
108、inue语句语句 return语句一般放在函数的最后位置,用于终止函数的执行,并控制程序返回调用该函数时所处的位置。返回时还可以通过return语句带回返回值。return语句格式有两种:(1)return;(2)return (表达式); 如果return语句后面带有表达式,则要计算表达式的值,并将表达式的值作为函数的返回值。若不带表达式,则函数返回时将返回一个不确定的值。通常我们用return语句把调用函数取得的值返回给主调用函数。8 return语句4.11 4.11 函数函数1 函数的定义函数的定义函数定义的一般格式如下:函数类型 函数名(形式参数表) reentrantinterru
109、pt musing n形式参数说明局部变量定义函数体前面部件称为函数的首部,后面称为函数的尾部,格式说明:1函数类型函数类型函数类型说明了函数返回值的类型。2函数名函数名函数名是用户为自定义函数取的名字以便调用函数时使用。3形式参数表形式参数表形式参数表用于列录在主调函数与被调用函数之间进行数据传递的形式参数。4reentrant修饰符修饰符 这个修饰符用于把函数定义为可重入函数。所谓可重入函数就是允许被递归调用的函数。函数的递归调用是指当一个函数正被调用尚未返回时,又直接或间接调用函数本身。一般的函数不能做到这样,只有重入函数才允许递归调用。 关于重入函数,注意以下几点:(1)用reentr
110、ant修饰的重入函数被调用时,实参表内不允许使用bit类型的参数。函数体内也不允许存在任何关于位变量的操作,更不能返回bit类型的值。(2)编译时,系统为重入函数在内部或外部存储器中建立一个模拟堆栈区,称为重入栈。重入函数的局部变量及参数被放在重入栈中,使重入函数可以实现递归调用。(3)在参数的传递上,实际参数可以传递给间接调用的重入函数。无重入属性的间接调用函数不能包含调用参数,但是可以使用定义的全局变量来进行参数传递。5interrupt m修饰符修饰符interrupt m是C51函数中非常重要的一个修饰符,这是因为中断函数必须通过它进行修饰。在C51程序设计中,当函数定义时用了inte
111、rrupt m修饰符,系统编译时把对应函数转化为中断函数,自动加上程序头段和尾段,并按MCS-51系统中断的处理方式自动把它安排在程序存储器中的相应位置。 在该修饰符中,m的取值为031,对应的中断情况如下:0外部中断01定时/计数器T02外部中断13定时/计数器T14串行口中断5定时/计数器T2其它值预留。编写MCS-51中断函数注意如下:(1)中断函数不能进行参数传递,如果中断函数中包含任何参数声中断函数不能进行参数传递,如果中断函数中包含任何参数声明都将导致编译出错。明都将导致编译出错。(2)中断函数没有返回值中断函数没有返回值,如果企图定义一个返回值将得不到正确的结果,建议在定义中断函
112、数时将其定义为void类型,以明确说明没有返回值。(3)在任何情况下都不能直接调用中断函数在任何情况下都不能直接调用中断函数,否则会产生编译错误。因为中断函数的返回是由8051单片机的RETI指令完成的,RETI指令影响8051单片机的硬件中断系统。如果在没有实际中断情况下直接调用中断函数,RETI指令的操作结果会产生一个致命的错误。(4)如果在中断函数中调用了其它函数,则被调用函数所使用的寄存器必须与如果在中断函数中调用了其它函数,则被调用函数所使用的寄存器必须与中断函数相同。中断函数相同。否则会产生不正确的结果。(5)C51编译器对中断函数编译时会自动在程序开始和结束处加上相应的内容编译器
113、对中断函数编译时会自动在程序开始和结束处加上相应的内容,具体如下:在程序开始处对ACC、B、DPH、DPL和PSW入栈,结束时出栈。中断函数未加using n修饰符的,开始时还要将R0R1入栈,结束时出栈。如中断函数加using n修饰符,则在开始将PSW入栈后还要修改PSW中的工作寄存器组选择位。(6)C51编译器从绝对地址8m+3处产生一个中断向量,其中m为中断号,也即interrupt后面的数字。该向量包含一个到中断函数入口地址的绝对跳转。(7)中断函数最好写在文件的尾部,并且禁止使用extern存储类型说明。防止其它程序调用。 编写一个用于统计外中断0的中断次数的中断服务程序exter
114、n int x;void int0() interrupt 0 using 1 x+;6using n修饰符修饰符修饰符using n用于指定本函数内部使用的工作寄存器组,其中n的取值为03,表示寄存器组号。 对于using n修饰符的使用,注意以下几点:(1)加入using n后,C51在编译时自动的在函数的开始处和结束处加入以下指令。PUSH PSW ;标志寄存器入栈MOV PSW,#与寄存器组号相关的常量POP PSW ;标志寄存器出栈(2)using n修饰符不能用于有返回值的函数,因为C51函数的返回值是放在寄存器中的。如寄存器组改变了,返回值就会出错。2 函数的调用与声明函数的调用
115、与声明一函数的调用一函数的调用函数调用的一般形式如下: 函数名(实参列表);对于有参数的函数调用,若实参列表包含多个实参,则各个实参之间用逗号隔开。 按照函数调用在主调函数中出现的位置,函数调用方式有以下三种:(1)函数语句。把被调用函数作为主调用函数的一个语句。(2)函数表达式。函数被放在一个表达式中,以一个运算对象的方式出现。这时的被调用函数要求带有返回语句,以返回一个明确的数值参加表达式的运算。(3)函数参数。被调用函数作为另一个函数的参数。二自定义函数的声明二自定义函数的声明在C51中,函数原型一般形式如下: extern 函数类型 函数名(形式参数表); 函数的声明是把函数的名字、函
116、数类型以及形参的类型、个数和顺序通知编译系统,以便调用函数时系统进行对照检查。函数的声明后面要加分号。 如果声明的函数在文件内部,则声明时不用如果声明的函数在文件内部,则声明时不用extern,如果声明的,如果声明的函数不在文件内部,而在另一个文件中,声明时须带函数不在文件内部,而在另一个文件中,声明时须带extern,指,指明使用的函数在另一个文件中。明使用的函数在另一个文件中。【例例23】函数的使用#include /包含特殊功能寄存器库#include /包含I/O函数库int max(int x,int y); /对max函数进行声明void main(void) /主函数int a,
117、b;SCON=0x52; /串口初始化TMOD=0x20;TH1=0XF3;TR1=1;scanf(“please input a,b:%d,%d”,&a,&b);printf(“n”);printf(“max is:%dn”,max(a,b);while(1);int max(int x,int y)int z;z=(x=y?x:y);return(z);【例例24】 外部函数的使用程序serial_initial.c#include /包含特殊功能寄存器库#include /包含I/O函数库void serial_initial(void) /主函数SCON=0x52; /串口初始化TMO
118、D=0x20;TH1=0XF3;TR1=1;程序y1.c#include /包含特殊功能寄存器库#include /包含I/O函数库extern serial_initial();void main(void)int a,b;serial_initial();scanf(“please input a,b:%d,%d”,&a,&b);printf(“n”);printf(“max is:%dn”,a=b?a:b);while(1);3 函数的嵌套与递归函数的嵌套与递归一函数的嵌套一函数的嵌套在一个函数的调用过程中调用另一个函数。C51编译器通常依靠堆栈来进行参数传递,堆栈设在片内RAM中,而片
119、内RAM的空间有限,因而嵌套的深度比较有限,一般在几层以内。如果层数过多,就会导致堆栈空间不够而出错。 【例例25】 函数的嵌套调用#include /包含特殊功能寄存器库#include /包含I/O函数库extern serial_initial();int max(int a,int b)int z;z=a=b?a:b;return(z);int add(int c,int d,int e,int f)int result;result=max(c,d)+max(e,f); /调用函数maxreturn(result);main()int final;serial_initial();f
120、inal=add(7,5,2,8);printf(“%d”,final);while(1);二函数的递归二函数的递归递归调用是嵌套调用的一个特殊情况。如果在调用一个函数过程中又出现了直接或间接调用该函数本身,则称为函数的递归调用。【例26】递归求数的阶乘n!。 在数学计算中,一个数n的阶乘等于该数本身乘以数n-1的阶乘,即n!=n(n-1)!,用n-1的阶乘来表示n的阶乘就是一种递归表示方法。在程序设计中通过函数递归调用来实现。程序如下: 在函数的递归调用中要避免出现无终止的自身调用,应通过条件控制结束递归调用,使得递归的次数有限。下面是一个利用递归调用求n!的例子。#include /包含特
121、殊功能寄存器库#include /包含I/O函数库extern serial_initial();int fac(int n) reentrantint result;if (n= =0)result=1;elseresult=n*fac(n-1);return(result);main()int fac_result;serial_initial();fac_result=fac(11);printf(“%dn”,fac_result);1 结构结构 结构是一种组合数据类型,它是将若干个不同类型的变量结合在一起而形成的一种数据的集合体。组成该集合体的各个变量称为结构元素或成员。整个集合体使用
122、一个单独的结构变量名。 一结构与结构变量的定义一结构与结构变量的定义 结构与结构变量是两个不同的概念,结构是一种组合数据类型,结构变量是取值为结构这种组合数据类型的变量,相当于整型数据类型与整型变量的关系。对于结构与结构变量的定义有两种方法。1先定义结构类型再定义结构变量先定义结构类型再定义结构变量结构的定义形式如下:struct 结构名结构名结构元素表结构元素表;结构变量的定义如下:struct 结构名结构名 结构变量名结构变量名1,结构变量名,结构变量名2,;其中,“结构元素表”为结构中的各个成员,它可以由不同的数据类型组成。在定义时须指明各个成员的数据类型。 4.12 C51构造数据类型
123、构造数据类型例如,定义一个日期结构类型date,它由三个结构元素year、month、day组成,定义结构变量d1和d2,定义如下:struct dateint year;char month,day;struct date d1,d2;2定义结构类型的同时定义结构变量名定义结构类型的同时定义结构变量名这种方法是将两个步骤合在一起,格式如下:struct 结构名结构元素表结构元素表 结构变量名结构变量名1,结构变量名,结构变量名2,;例如对于上面的日期结构变量d1和d2可以按以下格式定义:struct dateint year;char month,day;d1,d2;对于结构的定义说明如下:
124、(1)结构中的成员可以是基本数据类型,也可以是指针或数组,还可以是另一结构类型变量,形成结构的结构,即结构的嵌套。结构的嵌套可以是多层次的,但这种嵌套不能包含其自己。(2)定义的一个结构是一个相对独立的集合体,结构中的元素只在该结构中起作用,因而一个结构中的结构元素的名字可以与程序中的其它变量的名称相同,它们两者代表不同的对象,在使用时互相不影响。(3)结构变量在定义时也可以像其它变量在定义时加各种修饰符对它进行说明。(4)在C51中允许将具有相同结构类型的一组结构变量定义成结构数组,定义时与一般数组的定义相同,结构数组与一般变量数组的不同就在于结构数组的每一个元素都是具有同一结构的结构变量。
125、二结构变量的引用二结构变量的引用结构元素的引用一般格式如下: 结构变量名结构变量名.结构元素名结构元素名 或或 结构变量名结构变量名-结构元素名结构元素名其中,“.”是结构的成员运算符,例如:d1.year表示结构变量d1中的元素year,d2.day表示结构变量d2中的元素day等。如果一个结构变量中结构元素又是另一个结构变量,即结构的嵌套,则需要用到若干个成员运算符,一级一级找到最低一级的结构元素,而且只能对这个最低级的结构元素进行引用,形如d1.time.hour的形式。【例例4-30】输入3个学生的语文、数学、英语的成绩,分别统计他们的总成绩并输出。程序如下:#include /包含特
126、殊功能寄存器库#include /包含I/O函数库extern serial_initial();struct studentunsigned char name10;unsigned int chinese;unsigned int math;unsigned int english;unsigned int total;p13;main()unsigned char i;serial_initial();printf(“input 3 studend name and result:n”);for (i=0;i3;i+)printf(“input name:n”);scanf(“%s”,p
127、1i.name);printf(“input result:n”);scanf(“%d,%d,%d”,&p1i.chinese,&p1i.math,&p1i.english);for (i=0;i3;i+)p1i.total=p1i.chinese+p1i.math+p1i.english;for (i=0;i联合元素联合元素 例如:对于前面定义的联合变量a、b、c中的元素可以通过下面形式引用。a.i;b.j;c.k; 分别引用联合变量a中的float型元素i,联合变量b中的int型元素j,联合变量c中的char型元素k, 3 枚举枚举数据类型是一个有名字的某些整型常量的集合。这些整型常量是该
128、类型变量可取的所有的合法值。枚举定义时应当列出该类型变量的所有可取值。枚举定义的格式与结构和联合基本相同,也有两种方法。先定义枚举类型,再定义枚举变量,格式如下:enum 枚举名 枚举值列表;enum 枚举名 枚举变量列表;或在定义枚举类型的同时定义枚举变量,格式如下:enum 枚举名 枚举值列表枚举变量列表;例如:定义一个取值为星期几的枚举变量d1。enum week Sun,Mon,Tue,Wed,Thu,Fri,Sat;enum week d1;或enum week Sun,Mon,Tue,Wed,Thu,Fri,Sat d1;以后就可以把枚举值列表中各个值赋值给枚举变量d1进行使用了。
129、4.13汇编语言和汇编语言和C语言的混合编程语言的混合编程本节介绍不同的模块,不同的语言相结合的编程方本节介绍不同的模块,不同的语言相结合的编程方法。法。通常情况下以高级语言编写主程序,用汇编语言编通常情况下以高级语言编写主程序,用汇编语言编写与硬件有关的子程序。不同的编译程序写与硬件有关的子程序。不同的编译程序高级语言对高级语言对汇编的调用方法不同,在汇编的调用方法不同,在C51中,是将不同的模块中,是将不同的模块(包包括不同语言的模括不同语言的模块块)分别汇编或编译,再通过连接生成分别汇编或编译,再通过连接生成一个可执行文件。一个可执行文件。C语言程序调用汇编语言程序要注意以下几点:语言程
130、序调用汇编语言程序要注意以下几点:1.被调函数要在主函数中说明,在汇编程序中,要被调函数要在主函数中说明,在汇编程序中,要使用伪指令使使用伪指令使CODE选项有效并声明为可再选项有效并声明为可再定位段类型,定位段类型,并且根据不同情况对函数名作转换,见表并且根据不同情况对函数名作转换,见表4.6。说说明明符符号号名名解解释释voidfunc(void)FUNC无参数传递或不含寄存器参数的无参数传递或不含寄存器参数的函数名不作改变转入目标文件中,函数名不作改变转入目标文件中,名字只是简单的转为大写形式。名字只是简单的转为大写形式。voidfunc(char) _FUNC含寄存器参数的函数名加入含
131、寄存器参数的函数名加入“_”字符前缀以示区别,它表明这类字符前缀以示区别,它表明这类函数包含寄存器内的参数传递。函数包含寄存器内的参数传递。voidfunc(void)reentrant_?FUNC对于重入函数加上对于重入函数加上“_?”字符前字符前缀以示区别,它表明这类函数包缀以示区别,它表明这类函数包含栈内的参数传递。含栈内的参数传递。2.对为其他模块使用的符号进行对为其他模块使用的符号进行PUBLIC声明,对外来符声明,对外来符号进行号进行EXTRN声明。声明。3.参数的传递。参数的传递。在混合语言编程中,关键是入口参数和出口参数的传在混合语言编程中,关键是入口参数和出口参数的传递,递,
132、KeilC编译器可使用寄存器传递参数,也可以使编译器可使用寄存器传递参数,也可以使用固定存贮器或使用堆栈,由于用固定存贮器或使用堆栈,由于8XX51的堆栈深度有的堆栈深度有限,因此多用寄存器限,因此多用寄存器或存贮器传递。用寄存器传递最或存贮器传递。用寄存器传递最多只能传递三个参数,选择固定的寄存器,见表多只能传递三个参数,选择固定的寄存器,见表4.7。表表4.7参数类型参数类型charintlong,float一般指针一般指针第一个参数第一个参数第二个参数第二个参数第三个参数第三个参数R7R5R3R6,R7R4,R5R2,R3R4R7R4R7无无R1,R2,R3R1,R2,R3R1,R2,R
133、3例如例如func1(inta)“a”是第一个参数,在是第一个参数,在R6,R7传递传递,func2(intb,intc,int*d)“b”在在R6,R7中传递,中传递,“c”在在R4,R5中传递,中传递,“d”在在R1,R2,R3中传递。中传递。如果传递参数寄存器不够用,可以使用存贮器传送如果传递参数寄存器不够用,可以使用存贮器传送,通过指针取得参数。通过指针取得参数。汇编语言通过寄存器或存贮器传递参数给汇编语言通过寄存器或存贮器传递参数给C语言程序,语言程序,汇编语言通过寄存器传递汇编语言通过寄存器传递给给C语言的返回值见表语言的返回值见表4.8。返回值返回值寄存器寄存器说明说明bit(u
134、nsigned)char(unsigned)int(unsigned)longFloat指针指针CR7R6,R7R4R7R4R7R1,R2,R3进位标志进位标志高位在高位在R6,低位在低位在R7高位在高位在R4,低位在低位在R732位位IEEE格式,指数和符号位格式,指数和符号位R7R3放存储器类型,高位在放存储器类型,高位在R2,低位低位R1下面通过两个实例说明混合编程的方法及参数传递过程下面通过两个实例说明混合编程的方法及参数传递过程.例例4_10用用P1.0产生周期为产生周期为4ms的方波的方波,同时用同时用P1.1产生产生周期为周期为8ms的方波。的方波。说明:设计三个模块说明:设计三
135、个模块模块一模块一C语言编主程序,使语言编主程序,使P1.1产生周期为产生周期为8ms的方波;的方波;模块二模块二用用C语言编程,使语言编程,使P1.0产生周期为产生周期为4ms的方波;的方波;模块三模块三用汇编语言编写延用汇编语言编写延时时1ms程序。程序。模块一调用模块二获得模块一调用模块二获得8ms方波,方波,模块二调模块三模块二调模块三,向汇编程序传递字符型参数,向汇编程序传递字符型参数(x=2),延时延时2ms。各模块程序如下:各模块程序如下:模块一:模块一:P1.1产生周期为产生周期为8ms的方波的方波#include#defineucharunsignedcharsbitP1-1
136、=P11;voiddelay4ms(void);/*定义定义延时延时4ms函数函数(模块二模块二)*/main()uchari;for(;)P1-1=0;delay4ms();/*调模块二延时调模块二延时4ms*/P1-1=1;delay4ms();/*调模块二延时调模块二延时4ms*/模块二(模块二(delay4ms):使:使P1.0产生周期为产生周期为4ms的方波的方波#include#defineucharunsignedcharsbitP1-0=P10;delaylms(ucharx);/*定义延时定义延时1ms函数函数(模块三模块三)*/voiddelay4ms(void)P1-0=
137、0;delaylms(2);/*调汇编函数调汇编函数(模块三模块三)*/P1-0=1;delaylms(2);/*调汇编函数调汇编函数(模块三模块三)*模块三:模块三:PUBLIC-DELAY1MS;DELAY1MS为为其他其他模块调用模块调用DESEGMENTCODE;定义定义DE段为再定位程序段段为再定位程序段RSEGDE;选择选择DE为当前段为当前段-DELAY1MS:NOPDELA:MOVR1,#0F8H;延时延时LOP1:NOPNOPDJNZR1,LOP1DJNZR7,DELA;R7为为C程序传递程序传递过来的参数过来的参数(x=2)EXIT:RETEND上例可见汇编语言程序从上例可
138、见汇编语言程序从R7中获取中获取C程序传递参数程序传递参数(x=2)。例例2.在汇编程序中比较两数大小,将大数放到指定的存储在汇编程序中比较两数大小,将大数放到指定的存储区,区,由由C程序的主调函数取出。程序的主调函数取出。C语言程序语言程序模块一模块一:#defineucharunsignedcharvoidmax(uchara,ucharb);/*定义汇编函数定义汇编函数*/main()uchara=5,b=35,*c,d;c=0x30;/*c指针变量指向内部指针变量指向内部RAM30H单元单元*/max(a,b);/*调汇编函数,调汇编函数,a,b为传递的参数为传递的参数*/d=*c;/
139、*d存放模块二传递过来的参数存放模块二传递过来的参数*/模块二模块二:汇编语言程序汇编语言程序PUBLIC-MAXMAX为其为其他模块调他模块调DESEGMENTCODE;定义定义DE段为再定位程序段段为再定位程序段RSEGDE;选择选择DE为当前段为当前段MAX:MOVA,R7;取模块一的参数取模块一的参数aMOV30H,R5;取模块一的参数取模块一的参数bCJNEA,30H,TAG1;比较比较a,b的大小的大小TAG1:JCEXITMOV30H,R7;大数存于大数存于30H单元单元EXIT:RETEND可见,可见,C语言程序通过语言程序通过R7和和R5传递字符型参数传递字符型参数a和和b到
140、汇编语言程序,汇编语言程序将返回值到汇编语言程序,汇编语言程序将返回值放在固定放在固定存贮单元,主调函数通过指针取出返回值。存贮单元,主调函数通过指针取出返回值。C语言程序调用汇编程序最多只能传递三个参数,语言程序调用汇编程序最多只能传递三个参数,如果多于三个参数,就需要通过如果多于三个参数,就需要通过存贮存贮qu区传递,这就需区传递,这就需要在要在汇编程序中建立数据段,在下例中汇编程序中建立数据段,在下例中C语言程序向汇语言程序向汇编传递六个参数,汇编程序编传递六个参数,汇编程序*C_CALL.C*#pragmacodesmallexternintafunc(charv_a,charv_b,
141、charv_c,charv_d,charv_e,charv_f);/*外来函数说明外来函数说明voidC_call(void)charv_a=0x11;/*传递参数赋值传递参数赋值*/charv_b=0x18;charv_c=0x33;charv_d=0x44;charv_e=0x55;charv_f=0x98;intdata*aa;/*指针变量指向指针变量指向int型型data区区*/intA_ret;/*存汇编返回结果的变量存汇编返回结果的变量*/aa=0x30;/*置指针置指针*/A_ret=afunc(v_a,v_b,v_c,v_d,v_e,v_f);/*调汇编函数调汇编函数*/*aa
142、=A_ret;/*取汇编返回结果取汇编返回结果*/*aa=(int)0;/*为方便观察改值,强制为方便观察改值,强制0为为int型型*/*aa=A_ret;/*再次观察汇编返回结果再次观察汇编返回结果*/voidmain(void)/*主函数主函数*/chara1,a2,a3;/*为方便观察设为方便观察设a1a2a3*/a1=0;a2=2;a3=3;C_call();a1=1;a2=3;while(1);*AFANC.ASM*PR_AFUNCSEGMENTCODE;名为名为AFUNC段为代码段段为代码段(PR)在在CODE区可再定位,区可再定位,DT_AFUNCSEGMENTDATAOVERL
143、AYABLE;名为名为AFUNC段为数据段段为数据段(DT)在在DATA区区,可再定位,可以覆盖可再定位,可以覆盖PUBLIC ?_afunc?BYTE;公共符号定义公共符号定义PUBLIC _afuncRSEGDT_AFUNC?_afunc?BYTE:;数据段保留参数传递区数据段保留参数传递区v_a:DS1v_b:DS1v_c:DS1v_d:DS1v_e:DS1v_f:DS1RSEGPR_AFUNC_afunc:USING0;程序段程序段MOVA,R7;取取R7中的中的v_aADDA,R5;取取R5中的中的v_bADDA,R3;取取R3中的中的v_cADDA,v_dADDA,v_eADDA,
144、v_fMOVR7,A;和存和存R7,进位存进位存R6MOVA,#0;以便返回以便返回RLCAMOVR6,ARETEND编译连接方法编译连接方法以上各模块可以先分别汇编以上各模块可以先分别汇编(A51)和编译和编译(C51)(选择选择DEBUG编译控制项编译控制项),生成的,生成的.OBJ文件,然后运文件,然后运行行L51将各将各OBJ文件连接,生成一个新的文件。文件连接,生成一个新的文件。在集成环境下的连接调试可以连续进行,比上面在集成环境下的连接调试可以连续进行,比上面方法更为方便,现使用方法更为方便,现使用wave(伟福伟福)的仿真软的仿真软件件ICExplorerW(ICEForWind
145、ows)的编译连接步骤如下:的编译连接步骤如下:1.编辑好各个模块,保存。编辑好各个模块,保存。2.点击文件点击文件/新建项目,弹出项目窗口。新建项目,弹出项目窗口。3.点击项目菜单,选加入模块,此时弹出有文件目录的点击项目菜单,选加入模块,此时弹出有文件目录的对话框,选中要加入刚才编辑好的文对话框,选中要加入刚才编辑好的文件件(模块模块),并打,并打开。此时在项目窗口中可以看到加入的模块文件。开。此时在项目窗口中可以看到加入的模块文件。4.点击项目菜单中的全部编辑,并取名保存项目。于是点击项目菜单中的全部编辑,并取名保存项目。于是系统对加入各模块进行编译,并进行系统对加入各模块进行编译,并进行连连5.编译连接完成会弹出信息窗口,如编译连接有错,信编译连接完成会弹出信息窗口,如编译连接有错,信息窗口将出现错误信息。息窗口将出现错误信息。6.模块连接成功,生成二进制文件模块连接成功,生成二进制文件(.BIN)和十六进制文件和十六进制文件(.HEX)。7.点击跟踪或单步按钮,就可对程序进行跟踪调试,程点击跟踪或单步按钮,就可对程序进行跟踪调试,程序运行到不同模块时,序运行到不同模块时,wave就会弹就会弹出相应的模块源程出相应的模块源程序窗口,显示程序运行情况。序窗口,显示程序运行情况。