资源描述
项 目 二 霓 虹 灯 控 制 系 统 项 目 二 霓 虹 灯 控 制 系 统 2.1 项目说明 2.2 基础知识 2.3 项目实施 2.4 项目评价 2.5 拓展与提高 项 目 二 霓 虹 灯 控 制 系 统 2.1 项 目 说 明 项目任务霓虹灯为美化城市夜景作出了不可磨灭的贡献。本项目的任务是利用51单片机驱动8个发光二极管来模拟霓红灯控制系统。 知识培养目标(1) 掌握C51变量的定义以及运算符的应用。(2) 掌握延时的实现及其应用。(3) 掌握基本程序的设计方法。(4) 掌握C51库函数的使用。 项 目 二 霓 虹 灯 控 制 系 统 (5) 掌握数组的定义及其应用。(6) 掌握字节寻址与位寻址的应用。 能力培养目标(1) 培养单片机控制系统的硬件分析与设计能力。(2) 培养元器件的计算与选择能力。(3) 培养C51的程序设计能力。(4) 培养分析问题与解决问题的能力。(5) 培养团队协作能力。 项 目 二 霓 虹 灯 控 制 系 统 2.2 基 础 知 识2.2.1 C51变量1. 变量的定义在项目一中,我们对C51中的变量进行了简要的说明,每个变量要有一个变量名,变量的数据类型不同,占用的存储单元数也不一样。在使用前必须对变量进行定义,完整的变量定义形式要指出变量的数据类型和存储类型,以便编译系统为它分配相应的存储单元。定义的一般形式为存储种类 数据类型说明符 存储类型 变量名l=初值, 变量 名2=初值; 项 目 二 霓 虹 灯 控 制 系 统 1) 数据类型说明符在定义变量时,必须通过数据类型说明符指明变量的数据类型,也就是规定了变量在存储器中占用的字节数。数据类型说明符可以是基本数据类型说明符,也可以是组合数据类型说明符,还可以是用typedef或#define定义的类型别名。例如:typedef unsigned int WORD;#define BYTE unsigned charBYTE A=0 x34; WORD a2=0 x3534; 项 目 二 霓 虹 灯 控 制 系 统 2) 变量名变量名是C51为了区分不同变量为变量取的名称。在C51中规定变量名由字母、数字和下画线三种字符组成,且规定变量名第一个字符必须是字母或下画线。变量名有普通变量名和指针变量名两种,它们的区别是指针变量名前面要带“*”号。3) 存储种类存储种类是指变量在程序执行过程中的作用域。C51变量的存储种类有四种,分别是自动(auto)、外部(extern)、静态(static)与寄存器(register)。 项 目 二 霓 虹 灯 控 制 系 统 (1) auto:使用auto定义的变量称为自动变量,其作用范围在定义它的函数体或复合语句内部。当定义它的函数体或复合语句执行时,C51才为该变量分配内存空间,结束时释放占用的内存空间。自动变量一般分配在内存的堆栈空间中。当定义变量时,如果省略存储种类,则该变量默认为自动(auto)变量。用自动变量能最有效地使用51单片机内存。由于51单片机访问片内RAM速度最快,通常将函数体内和复合语句中使用频繁的变量存放在片内RAM中,且定义为自动变量,可有效地利用片内有限的RAM资源。 项 目 二 霓 虹 灯 控 制 系 统 (2) extern:使用extern定义的变量称为外部变量。在一个函数体内,要使用一个已在该函数体外或其他程序中定义过的外部变量时,该变量在该函数体内要用extern说明。外部变量被定义后分配固定的内存空间,在程序的整个执行时间内都有效,直到程序结束才释放。通常将多个函数或模块共享的变量定义为外部变量。外部变量是全局变量,在程序执行期间一直占有固定的内存空间。当片内RAM资源紧张时,不建议将外部变量放在片内RAM。 项 目 二 霓 虹 灯 控 制 系 统 (3) static:使用static定义的变量称为静态变量。它又分为内部静态变量和外部静态变量。在函数体内部定义的静态变量为内部静态变量,它在对应的函数体内有效,一直存在,但在函数体外不可见。这样不仅使变量在定义它的函数体外被保护,还可以实现变量离开函数时值不被改变。外部静态变量是在函数外部定义的静态变量,它在程序中一直存在,但在定义的范围之外是不可见的。如在多文件或多模块处理时,外部静态变量只在文件内部或模块内部有效。(4) register:使用register定义的变量称为寄存器变量。它定义的变量存放在CPU内部的寄存器中,处理速度快,但数目少。C51编译器编译时能自动识别程序中使用频率最高 的变量,并自动将其作为寄存器变量,用户无须专门声明。 项 目 二 霓 虹 灯 控 制 系 统 4) 存储类型存储类型是用于指明变量存放在单片机的哪个存储器中。存储类型与存储种类完全不同,存储类型指明该变量在单片机内所处的存储空间。如果在变量定义时省略了存储类型标识符,C51编译器会选择默认的存储类型。默认的存储类型由SMALL、COMPACT和LARGE存储模式来决定。C51编译器能识别的存储类型如表2-1所示。 项 目 二 霓 虹 灯 控 制 系 统 表2-1 C51编译器能识别的存储类型 项 目 二 霓 虹 灯 控 制 系 统 (1) data区:对data区的访问是最快的,所以应该把使用频率高的变量放在data区,由于空间有限,必须有效使用data区,data区除了包含程序变量外,还包含了堆栈和寄存器组。例如:unsigned char data system_status=0; float data outp_value;unsiged char data new_var; 项 目 二 霓 虹 灯 控 制 系 统 在SMALL存储模式下,当未说明存储类型时,变量默认被定位在data区。标准变量和用户自定义变量都可以存储在data区,只要不超过data区的范围。因为C51使用默认的寄存器组传递参数,至少失去了8 B。另外要定义足够大的堆栈空间,当内部堆栈溢出时,程序会产生莫名其妙的错误,实际原因是51系列单片机没有硬件报错机制,堆栈溢出只能以这种方式表示出来。(2) bdata区:在片内RAM的位寻址区(bdata区)定义变量,这个变量就可进行位寻址,并且声明位变量。这对状态寄存器来说十分有用,因为这样可以单独使用变量的某一位,而不一定要用位变量名引用位变量。下面是一些在bdata区中 声明变量和使用位变量的例子。 项 目 二 霓 虹 灯 控 制 系 统 unsigned char bdata status_byte;unsigned int bdata status_word;sbit stat_flag=status_byte4;if(status_word15) stat_flag=1;编译器不允许在bdata区中定义float和double类型的变量。 项 目 二 霓 虹 灯 控 制 系 统 (3) idata区:idata区可以存放使用比较频繁的变量,使用寄存器作为指针进行寻址。在寄存器中设置8位地址进行间接寻址,与外部存储器寻址比较,它的指令执行周期和代码长度都比较短。例如:unsigned char idata system_status=0;float idata outp_value; 项 目 二 霓 虹 灯 控 制 系 统 (4) pdata和xdata区:在这两个区声明变量和在其他区的语法是一样的,pdata区只有256 B,而xdata区可达64 KB。例如:unsigned char xdata system_status=0; float pdata outp_value;对pdata和xdata的操作是相似的,对pdata区寻址比对xdata区寻址要快,因为对pdata区寻址只需要装入8位地址,而对xdata区寻址需装入16位地址。所以尽量把外部数据存储在pdata区中,汇编语言中对pdata和xdata寻址要使用MOVX指令,需要2个机器周期。 项 目 二 霓 虹 灯 控 制 系 统 (5) code区:code区即51单片机的程序存储器,所以存入的数据是不可以改变的,即不可重写。程序存储器除了存放用户编写的程序代码外,还可存放数据表、跳转向量和状态表,对code区的访问和对xdata区的访问的时间是一样的,代码区中的变量必须在编译时初始化,否则就得不到想要的值,下面是代码区的声明例子。unsigned int code unit_id2=0 x1234, 0 x89ab;unsigned char code uchar_data =0 x00,0 x01,0 x02,0 x03,0 x04,0 x05,0 x06,0 x07; 项 目 二 霓 虹 灯 控 制 系 统 2. 变量的存储模式变量的存储模式确定了变量在内存中的地址空间,C51编译器允许采用小编译模式SMALL、紧凑编译模式COMPACT、大编译模式LARGE三种存储模式。SMALL模式下,变量存放在51单片机的内部RAM中;COMPACT和LARGE模式下,变量存放在51单片机的外部RAM中。同样一个函数的存储模式确定了函数的参数和局部变量在内存中的地址空间,SMALL模式下,函数的参数和局部变量存放在51单片机的内部RAM中;COMPACT和LARGE模式下,函数的参数和局部变量存放在51单片机的外部RAM中。例如: 项 目 二 霓 虹 灯 控 制 系 统 #pragma small /存储模式为SMALLunsigned char data i,j,k;int xdata m, n;unsigned char a=0 x99,b=0 x88;unsigned char xdata ram128;unsigned int func1(int i, int j) large return(i+j);unsigned int func2(int i, int j) return(i-j); 项 目 二 霓 虹 灯 控 制 系 统 由于是SMALL模式,故a、b、i、j、k都存储在片内数据存储器中。不同的存储类型访问速度是不一样的,如:unsigned char data var1;unsigned char pdata var1;unsigned char xdata var1; 在SMALL模式下,var1被定位在DATA区,经C51编译器编译后,采用内部RAM直接寻址方式访问速度最快;在COMPACT模式下,var1被定位在pdata区,经C51编译器编译后,采用外部RAM间接寻址方式访问速度较快;在LARGE模式下,var1被定位在xdata区,经C51编译器编译后,采用外部RAM间接寻址方式访问速度最慢。为了提高 项 目 二 霓 虹 灯 控 制 系 统 系统运行速度,建议在编写源程序时,把存储模式设定为SMALL,再在程序中对xdata、pdata和idata等类型变量进行专门声明。定义变量时也可以省略存储类型,省略时C51编译器将按存储模式选择存储类型。单击图2-1中所圈图标或选择“ProjectOptions for、Target Target1”,出现如图2-1所示窗口,单击第二个选项“Target”,“Memory Model”用于选择数据存储模式,“Code Rom Size”用于选择程序存储模式,选择好后,单击“确定”按钮。 项 目 二 霓 虹 灯 控 制 系 统 图2-1 C51编译器存储模式 项 目 二 霓 虹 灯 控 制 系 统 3. 特殊功能寄存器变量51系列单片机片内有许多特殊功能寄存器,通过这些特殊功能寄存器可以管理与控制51系列单片机的定时器、计数器、串口、I/O及其他功能部件,每一个特殊功能寄存器都占据片内RAM中的一个或两个字节。在C51中,允许用户对这些特殊功能寄存器进行访问,访问时需通过sfr或sfr16类型说明符进行定义,定义时需指明它们在片内RAM中对应单元的地址。一般形式为sfr/sfr16特殊功能寄存器名称=字节地址; 项 目 二 霓 虹 灯 控 制 系 统 sfr用于对51系列单片机中单字节(8位)的特殊功能寄存器进行定义,sfr16用于对双字节(16位)特殊功能寄存器(DPTR)进行定义。特殊功能寄存器名一般用大写字母表示,地址一般采用直接地址形式,如:sfr PSW=0 xD0; /定义程序状态字PSW的地址为D0Hsfr TMOD=0 x89; /定义定时/计数器方式寄存器TMOD的地址为89Hsfr P1=0 x90; /定义P1端口的地址为90H 项 目 二 霓 虹 灯 控 制 系 统 4. 位变量在C51中,允许用户通过位类型符定义位变量。关键字有bit和sbit两个。bit用于定义一般的可进行位处理的位变量,位地址由编译器在编译时分配,位地址位于片内RAM中的20H27H单元。它的一般形式为bit 位变量名;在格式中可以加上各种修饰,但严格来说只能是bdata,如: bit bdata a1;而bit pdata a3是错误的。 项 目 二 霓 虹 灯 控 制 系 统 sbit用于定义可位寻址特殊功能寄存器中的某一位,定义时需指明其位地址,可以是直接位地址、特殊功能寄存器字节地址值带位号或特殊功能寄存器名带位号。一般形式为sbit 位地址名=位地址;如位地址为直接位地址,其取值范围为0 x7F0 xFF中可位寻址的位地址;如采用特殊功能寄存器名称带位号时,需在定义位地址之前用sfr/sfr16对特殊功能寄存器进行定义,且字节地址与位号之间、特殊功能寄存器名称与位号之间一般用“”作间隔。如: 项 目 二 霓 虹 灯 控 制 系 统 sbit led1=0 x80; /直接位地址sbit led1=0 x800; /特殊功能寄存器字节地址值带位号 sbit led1=P00; /特殊功能寄存器名称带位号 这三条指令都可以将P0口最低位的位地址定义为led1。 项 目 二 霓 虹 灯 控 制 系 统 5. 局部变量与全局变量局部变量是指在在函数内部定义的变量;全局变量是指在函数外部定义的变量,也称外部变量。全局变量和局部变量体现了变量能被有效引用的范围,即变量的作用域。它们的区别:局部变量只在当前函数中有效,即当该函数被调用时,为函数内定义的变量分配存储单元,在该函数执行完后,在它内部定义的所有变量将自动销毁,分配的存储单元将自动释放,当下次再被调用时,编译器重新为其分配新的存储单元;而编译器为全局变量分配存储单元后,它将永远占据这些存储单元。 项 目 二 霓 虹 灯 控 制 系 统 1) 局部变量的作用域注意事项(1) 主函数中定义的变量也只能在主函数中使用,不能在其他函数中使用;同时,主函数中也不能使用其他函数中定义的变量,因为主函数也是一个函数,它与其他函数是平行关系。这一点与其他语言不同,应予以注意。(2) 形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。(3) 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,相互间不干扰,也不会发生混淆。(4) 在复合语句中也可定义变量,其作用域只在复合语 句范围内。 项 目 二 霓 虹 灯 控 制 系 统 2) 全局变量的作用域注意事项(1) 全局变量可以被本文件中所有函数共用,它的作用范围是从定义变量的位置开始到本源文件结束。(2) 全局变量定义在使用它的函数之后,当在该函数中使用全局变量时,应作全局变量说明,只有在函数内经过说明的全局变量才能使用,全局变量的说明符为extern。但在一个函数之前定义的全局变量,在该函数内使用时可不再加以说明。(3) 在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。 项 目 二 霓 虹 灯 控 制 系 统 (4) 外部变量可加强函数模块之间的数据联系,但是又使函数要依赖这些变量,因而使得函数的独立性降低,从模块化程序设计的观点来看这是不利的,因此在不必要时尽量不要使用全局变量。我们知道单片机内部的数据存储器容量有限,51系列单片机供用户使用的只有128 B,如果定义unsigned int类型变量,一个变量是两个字节,最多只能定义64个;定义unsigned char类型变量,一个变量为一个字节,最多也只能定义128个。当源程序较长,控制功能较复杂时,会遇到内存不够用的情况,因此用C51编程,虽然降低了对单片机硬件知识的要求,但也要在刚开始学习时养成良好的思考习惯,牢记单片机内部的可用资源,从节省片内RAM的角度出发,能用局部变量的就不用全局变量,能用 unsigned char类型的就不用unsigned int类型。 项 目 二 霓 虹 灯 控 制 系 统 2.2.2 C51的运算符与表达式C语言中运算符和表达式数量之多,应用之灵活,是其他高级语言所没有的。C51中常用运算符主要有算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符。这些运算符按其所在表达式中参与运算的操作数的个数可分为单目运算符、双目运算符和三目运算符。(1) 算术运算符:用于各类数值运算,包括加(+)、减(-)、乘(*)、除(/运算)、求余(%)、自增(+)、自减(-),共7种运算。 项 目 二 霓 虹 灯 控 制 系 统 (2) 关系运算符:用于各类比较运算,包括大于()、小于(=)、小于等于(=)和不等于(!=),共6种运算。 (3) 逻辑运算符:用于各类逻辑运算,包括与(”、“a=b=c=5;”都是赋值语句。在项目一中我们已使用过了。如果赋值运算符两边的数据类型不相同,系统将自动进行类型转换,即把赋值号右边的类型换成左边的类型。具体规定如下: 项 目 二 霓 虹 灯 控 制 系 统 (1) 实型量赋予整型变量时,舍去小数部分。如x为unsigned char 型,在执行x=4.56时,只将整数部分赋给x,即x=4。(2) 整型量赋予实型变量时,数值不变,但将以浮点形式存放,即增加小数部分(但小数部分值为0)。(3) 字符型量赋予整型变量,由于字符型量为一个字节,而整型量为两个字节,故将字符型的值存放到整型量的低八位中,高八位为0。(4) 整型量赋予字符型变量,只把低八位赋予字符型变量。 项 目 二 霓 虹 灯 控 制 系 统 3. 关系运算符和关系表达式关系运算是双目运算,用于比较两个操作数的大小。C51提供了6种关系运算符,即小于()、小于等于()、大于等于(=)、等于(=)、不等于(!=)。这6个关系运算符分为两个优先级,前四种高于后两种。将两个表达式(可以是算术表达式、赋值表达式、关系表达式等)用关系运算符连接起来就构成了关系表达式。关系表达式的值是逻辑值“真”或“假”。但是C51中没有逻辑型变量和常量,也没有专门的逻辑值,故以“非0”代表“真”,以“0”代表“假”。当关系表达式成立时,表达式的值为真,否则表达式的值为假。如53,则该表达式为真, 即该关系表达式的值为1;8=12,该表达式的值为0,即为假。 项 目 二 霓 虹 灯 控 制 系 统 算术、关系、赋值这三类运算符的优先级是由高到低,例如a=bc,该表达式等效于a=(bc+d(a=3)(b=5)(ab)(bc)如a=3、b=4、c=5、d=6,则这三个关系表达式的值分别为0、1、0。注意:不要误将关系运算符“=”写作赋值运算符“=”。 项 目 二 霓 虹 灯 控 制 系 统 4. 逻辑运算符和逻辑表达式 逻辑运算用于判断运算对象的逻辑关系,运算对象为关系表达式或逻辑量。C51提供了逻辑非(!)、逻辑与( b=ba; a=ab;(2) 取反。某数与0 xFF按位异或时,相当于对该数取反。例如,0 x880 xFF=0 x77。 项 目 二 霓 虹 灯 控 制 系 统 4) 按位非按位非运算符“”为单目运算符,具有右结合性。其功能是对参与运算的操作数的各二进位按位求反。如:11000011=00111100按位非的应用:按位非主要用于求取某一二进制数的反码,或和其他运算结合使用。 项 目 二 霓 虹 灯 控 制 系 统 5) 左移左移运算符“”是双目运算符,其功能是把“”左边的操作数的各二进位全部左移若干位,由“”右边的操作数指定移位的位数,移位时将溢出的高位丢弃,低位补0。如:a=11000011,执行 a2=00001100。左移的应用:左移1位相当于该数乘以2,左移2位相当于该数乘以4,左移n位相当于该数乘以2n。但此结论只适用于该数左移时被溢出舍弃的高位中不包含1的情况。例如:a”是双目运算符,其功能是把“”左边的操作数的各二进位全部右移若干位,“”右边的操作数指定移动的位数。移到右端的低位被舍弃,对于无符号数,高位补0;对于有符号数,算术移位是在左边用符号位填补,逻辑移位则是在左边补0。例如:a=00110011,执行 a2=00001100。右移的应用:右移1位相当于该数除2,右移2位相当于该数除4,右移n位相当于该数除2n。但此结论只适用于该数右移时被舍弃的低位中不包含1的情况。设a为unsigned uchar类型,且a=12,表示把000001100右移2位为00000011(十进制3),即a2=3。对于左边移出的空 位补入0。 项 目 二 霓 虹 灯 控 制 系 统 6. 复合赋值运算符在赋值运算符“=”的前面加上其他运算符,组成复合赋值运算符。C51中支持的复合赋值运算符有:+=加法赋值;-=减法赋值;*=乘法赋值;/=除法赋值;%=取余赋值;”组成。其一般形式为表达式; 项 目 二 霓 虹 灯 控 制 系 统 执行表达式语句就是计算表达式的值。例如:x=y+z;是利用赋值语句将y+z保存到x变量中;又如y+z;是合法的加法表达式,但计算结果不能保留,无实际意义;i+;是自增语句,i值增1。a=3与a=3;是不一样的。不加分号,为赋值表达式,不是语句;加分号后才是赋值语句。对赋值语句的使用要注意以下几点:(1) 由于在赋值符“=”右边的表达式也可以又是一个赋值表达式,因此形式为 变量=(变量=表达式); 项 目 二 霓 虹 灯 控 制 系 统 是成立的,形成嵌套的情形。其展开之后的一般形式为变量=变量=表达式;例如:a=b=c=d=e=5;按照赋值运算符的右结合性,实际上等效于:e=5;d=e;c=d;b=c;a=b;(2) 在变量定义中给变量赋初值是变量定义的一部分,赋初值后的变量与其后的其他同类变量之间仍必须用逗号间隔,而赋值语句则必须用分号结尾。(3) 在变量定义中,不允许连续给多个变量赋初值。如int a=b=c=5是错误的,必须写为int a=5,b=5,c=5; 而赋值语句允许连续赋值。 项 目 二 霓 虹 灯 控 制 系 统 (4) 注意赋值表达式和赋值语句的区别。赋值表达式是一种表达式,它可以出现在任何允许表达式出现的地方,而赋值语句则不能。如:if(x=y+5)0)z=x; 此语句是合法的,语句的功能是,若表达式x=y+5大于0,则z=x。而下述语句是非法的:if(x=y+5;)0)z=x;因为=y+5;是语句,不能出现在表达式中。 项 目 二 霓 虹 灯 控 制 系 统 2) 函数调用语句将函数调用作为一个语句。函数调用的一般形式为函数名(实际参数表);执行函数调用语句就是调用函数体并把实际参数赋予函数定义中的形式参数,然后执行被调函数体中的语句,求取函数值;调用无参函数时,无需实际参数表。 项 目 二 霓 虹 灯 控 制 系 统 3) 复合语句把多个语句用括号“”括起来组成一条复合语句。在程序中应把复合语句看成是单条语句,而不是多条语句,例如:x=y+z;a=b+c;z=x/c;复合语句内的各条语句都必须以分号“;”结尾,在括号“”后不能加分号。 项 目 二 霓 虹 灯 控 制 系 统 4) 控制语句控制语句用于控制程序的流程,实现程序的各种结构方式。控制语句共有九种,它们是: if() else 条件语句 switch 多分支选择语句 for() 循环语句 while() 循环语句 do while() 循环语句 continue 结束本次循环语句 break 中止执行swith或循环语句 goto 转向语句 return 函数的返回语句 项 目 二 霓 虹 灯 控 制 系 统 括号“()”中表示条件,“”表示内部语句。如:if(a45) b=x*y;c=x/y;elseb=c=0;5) 空语句只有分号“;”组成的语句才称为空语句。空语句是什么也不执行的语句,在程序中空语句可用来作循环体或内部语句。如“while(1);”就是用空语句作循环体,其作用是无数 遍执行空语句。 项 目 二 霓 虹 灯 控 制 系 统 2. for语句for语句是C语言所提供的功能强大且使用广泛的一种循环语句。其一般形式为for(表达式1;表达式2;表达式3) 循环体语句;表达式1用于给循环变量赋初值,一般为赋值表达式,也允许在for语句外给循环变量赋初值;表达式2是循环结束条件,可以是关系表达式或逻辑表达式;表达式3用于修改循环变量的值,一般是赋值语句;循环体语句可以为空语句。 项 目 二 霓 虹 灯 控 制 系 统 表达式13都可以是逗号表达式,即每个表达式都可由多个表达式组成;三个表达式都是任选项,都可以省略,但分号不能省略。for语句的执行过程是:第一步:计算表达式1。第二步:计算表达式2,若值为真(非0),则执行循环体语句,然后执行第三步;否则结束for语句,不再执行循环体和第三步,接着执行for的下一条语句。图2-2 for语句执行流程图第三步:计算表达式3。第四步:转至第二步重复执行。在整个for语句的执行过程中,表达式1只计算一次,表达式2和表达式3则可能计算多次;循环体可能执行多次,也可能一次 都不执行;for语句的执行过程如图2-2所示。 项 目 二 霓 虹 灯 控 制 系 统 图2-2 for语句执行流程图 项 目 二 霓 虹 灯 控 制 系 统 3. do-while语句do-while语句也是C51中常用的循环语句,它的一般形式为do语句;while(表达式);说明:“表达式”为循环条件,“语句”为循环体。 项 目 二 霓 虹 灯 控 制 系 统 do-while语句的执行过程是先执行循环体语句一次,再判别表达式的值,若为真(非0),则继续执行循环体语句;若为假,则终止循环。流程图如图2-3所示。do-while语句和while语句的区别在于do-while是先执行后判断,因此do-while至少要执行一次循环体;而while是先判断后执行,如果条件不满足,则一次循环体语句也不执行。while语句和do-while语句可以相互替换。 项 目 二 霓 虹 灯 控 制 系 统 图2-3 do-while语句的执行流程图 项 目 二 霓 虹 灯 控 制 系 统 例1 用do-while语句与while语句分别执行2000次空语句。解:源程序1void delay() unsigned int i=0; do i+; while(i2000); 项 目 二 霓 虹 灯 控 制 系 统 源程序2void delay() unsigned int i=0; while(i2000) i+; 项 目 二 霓 虹 灯 控 制 系 统 2.2.4 C51函数1. C51函数的分类C51源程序是由一个或多个函数组成的,函数是C51源程序的基本模块,通过对函数的调用实现特定的功能。C51语言不仅提供了许多库函数,还允许用户自己定义函数,用户可把自己的算法编成一个个相对独立的函数模块,然后用调用的方法来使用这些函数。由于采用了函数结构,C51易于实现结构化程序设计,使程序的层次结构更为清晰,便于程序的编写、阅读和调试。在C51中可以从不同的角度对函数进行分类。 项 目 二 霓 虹 灯 控 制 系 统 1) 从函数定义的角度分类(1) 库函数:由C51系统提供,用户无须定义,也不必在程序中作类型说明,只需在程序前包含该函数原型的头文件就可以在程序中直接调用。(2) 用户定义函数:由用户按需要写的函数。对于用户自定义函数,不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。 项 目 二 霓 虹 灯 控 制 系 统 2) 从有无返回值的角度分类(1) 有返回值函数:此类函数被调用后将向调用者返回一个执行结果,称为函数返回值,如数学函数即属于此类函数。由用户定义的有返回值函数必须在函数定义和函数说明中明确返回值的类型。(2) 无返回值函数:此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。由于函数无须返回值,因此用户在定义此类函数时可指定其为空类型,空类型的说明符为void。 项 目 二 霓 虹 灯 控 制 系 统 3) 从主调函数和被调函数之间数据传送的角度分类(1) 无参函数:函数定义、函数说明及函数调用中均不带参数。主调函数和被调函数之间不进行参数传送。此类函数通常用来完成一组指定的功能,可以返回或不返回函数值。(2) 有参函数:也称为带参函数。在函数定义及函数说明时都有参数,称为形式参数(简称为形参);在函数调用时也必须给出参数,称为实际参数(简称为实参)。在调用有参函数时,主调函数将把实参的值传送给形参,供被调函数使用。 项 目 二 霓 虹 灯 控 制 系 统 在C51中,所有的函数定义,包括主函数main在内,都是平行的,也就是说,在一个函数的函数体内,不能再定义另一个函数,即不能嵌套定义。但是函数之间允许相互调用,也允许嵌套调用,习惯上把调用者称为主调函数,函数还可以自己调用自己,称为递归调用。main函数是主函数,它可以调用其他函数(不包括中断函数),而不允许被其他函数调用。因此,C51程序的执行总是从main函数开始的,完成对其他函数的调用后再返回到main函数,最后由main函数结束整个程序执行。一个C51源程序必须有,也只能有一个主函数main。 项 目 二 霓 虹 灯 控 制 系 统 2. 函数的定义与调用1) 无参函数的定义与调用(1) 无参函数的定义。无参函数定义的一般形式为类型说明符 函数名() 类型说明语句;语句; 项 目 二 霓 虹 灯 控 制 系 统 类型说明符和函数名称为函数头,类型说明符指明了该函数的类型,函数的类型实际上是函数返回值的类型,而无参函数多数没有返回值,可定义为void类型;函数名是由用户定义的标识符,函数名后有一个空括号,其中无参数,但括号不可少。中的内容称为函数体,在函数体中也有类型说明,这是对函数体内部所用到的变量的类型说明。在C51中,如果有一些语句多次用到,而语句的内容都相同时,就可以将这些语句写成一个函数,在主函数中通过调用该函数来使用这些语句。(2) 无参函数的调用。在C51中,无参函数调用的一般形式为 函数名(); 项 目 二 霓 虹 灯 控 制 系 统 2) 有参函数的定义与调用(1) 有参函数的定义。有参函数定义的一般形式为类型说明符 函数名(形参类型1 参数1,形参类型n 参数n)类型说明语句;语句; 项 目 二 霓 虹 灯 控 制 系 统 函数名后“()”中定义的参数称为形式参数,它们可以是各种类型的变量,各参数之间用逗号间隔;在函数调用时,主调函数将赋予这些形式参数以实际数值,这些实际的数值称为实参。形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用;实参出现在主调函数中,进入被调函数后,实参变量也不能使用;形参和实参的功能是作数据传送,当发生函数调用时,主调函数把实参的值传送给被调函数的形参,从而实现主调函数向被调函数的数据传送。函数的形参和实参还具有以下特点: 项 目 二 霓 虹 灯 控 制 系 统 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此形参只有在函数内部有效,函数调用结束返回主调函数后则不能再使用该形参变量。 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值、输入等办法使实参获得确定值。 实参和形参在数量、类型和顺序上应严格一致,否则会发生“类型不匹配”的错误。 项 目 二 霓 虹 灯 控 制 系 统 函数调用中发生的数据传送是单向的,即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值会发生改变,而实参中的值不会产生变化。(2) 有参函数的调用。在C51中,有参函数调用的一般形式为函数名(实际参数表);实际参数表中的参数可以是常数、变量或其他构造类型数据及表达式。各实参之间用逗号分隔。函数的调用方式灵活,常用的还有函数表达式和函数实参两种方式。 项 目 二 霓 虹 灯 控 制 系 统 函数表达式。函数作为表达式中的一部分出现在表达式中,函数返回值参与表达式的运算。当采用这种调用方式时,被调用的函数必须有返回值。例如max(x,y)为求x,y之间的最大值,z=max(x,y)是一个赋值表达式,把调用max函数的返回值赋予变量z。 函数实参。函数作为另一个函数调用的实际参数出现,这种情况是把该函数的返回值作为实参进行传送,因此被调函数也必须要有返回值。 项 目 二 霓 虹 灯 控 制 系 统 3. 函数的声明在C51中,除主函数外的其他函数在定义时可以写在主函数的前面,也可以写在主函数的后面,但是不可以写在主函数的内部。当函数写在主函数的后面时,必须要在主函数之前对函数进行声明,声明的作用是为了编译器在编译主函数过程中,当遇到函数调用时,知道有这样一个函数存在,才能够根据它的类型和参数等信息为它分配必要的存储空间。函数声明语句的一般形式为无参函数:类型说明符 函数名(); 如:void delay500ms(); 有参函数:类型说明符 函数名(类型1, 类型n ); 如:void delay10ms(uchar); 项 目 二 霓 虹 灯 控 制 系 统 可以省略函数声明的几种情况:(1) 当被调函数的函数定义出现在主调函数之前时,在主调函数中可以不对被调函数再作说明而直接调用。 (2) 所有函数定义之前,在函数外预先说明各个函数的类型,这样则在以后的各主调函数中,可不再对被调函数作说明。例如:char str(int a); /声明有参函数strvoid f(); /声明无参函数fmain() 项 目 二 霓 虹 灯 控 制 系 统 char str(int a)void f()其中第1、2行对str函数和f函数预先作了声明,因此在以后各函数中无须对str和f函数再作声明就可以直接调用了。(3) 对库函数的调用不需要再作声明,但在源程序的开始处须用“#include”命令包含所需的头文件。 项 目 二 霓 虹 灯 控 制 系 统 4. 函数的值有返回值的函数被调用后,会向主调函数返回一个数值,称为函数的值或称为函数返回值。关于函数的值有以下一些说明:(1) 函数的值只能通过return语句返回主调函数。return语句的一般形式为return表达式; 或return (表达式); 项 目 二 霓 虹 灯 控 制 系 统 该语句的功能是计算表达式的值,并返回给主调函数。在函数中允许有多个return语句,但每次调用只能有一个return语句被执行,因此只能返回一个函数值。(2) 函数值的类型和函数定义中函数的类型应保持一致,如果两者不一致,则以函数类型为准,自动进行类型转换。 项 目 二 霓 虹 灯 控 制 系 统 2.2.5 一个发光二极管的闪烁1. 闪烁原理控制一个发光二极管点亮延时熄灭延时点亮,就会形成闪烁的效果,如图2-4所示流程图。闪烁的效果与发光二极管点亮、熄灭的时间有关,如果时间太短,人的眼睛无法分辨;若时间太长,发光二极管闪烁的速度会太慢,而影响效果。因此发光二极管点亮或熄灭的时间一般控制在100 ms1 s之间。点亮与熄灭的时间不需要特别准确,可以用延时语句或延时函数来实现,称为软件延时。软件延时的原理就是让单片机重复执行没有意义的空语句来实现时间的推移,一般由循环语 句构成。在延时时间要求特别精确时可用定时/计数器来实现。 项 目 二 霓 虹 灯 控 制 系 统 图2-4 发光二极管闪烁流程图 项 目 二 霓 虹 灯 控 制 系 统 2for语句实现延时1) 由for构成的延时语句C51中的循环语句均可实现延时,但for语句用得最多。由for构成的延时语句为unsigned int i;for(i=0;i1827;i+);先定义了一个unsigned char类型的变量i,用于控制for的循环次数,表达式1给i赋初值0,表达式2是循环结束语句,判断i是否小于1827,表达式3通过i自增1修改i,循环体为空语句,什么也不需要做。当变量i从0递增至1826时,条件i1827为真,执行空语句,共1827次;当i递增至1827时, 条件i1827为假,退出for循环。 项 目 二 霓 虹 灯 控 制 系 统 由C51编写的延时语句不能精确地计算出延时时间,通过KEIL C仿真后可知当晶振为12 MHz,for循环1827次时,约延时10 ms。需要修改延时时间时,以1827为基准进行调整,或者由for语句的嵌套来实现更长时间的延时,不必每次都进行仿真。如果要延时1 s,可以以10 ms为基准,采用双重循环来实现。由于1 s=10010 ms,内层循环实现10 ms延时,外层再循环100次,就可以达到延时1 s的要求。1 s的延时语句为 unsigned int i,j;for(i=0;i100;i+)for(j=0; j1827;j+); 项 目 二 霓 虹 灯 控 制 系 统 定义i、j两个变量用于控制双层循环的次数。第一个for语句的后面没有分号,它的循环体就是第二个for语句;第二个for语句的循环体是空语句,因此第二个for语句为内层循环,由变量j控制循环1827次,延时10 ms;第一个for语句为外层循环,由变量i控制循环100次,延时1 s。执行时,第一个for语句的i每加一次,第二个for语句的j就需要加1827次,因此两重循环共执行了1001827次,约延时1 s。若需要更长的时间时可以改变外层for循环的循环次数或增加嵌套的次数。 项 目 二 霓 虹 灯 控 制 系 统 2) 应用举例例2 编程使图1-18中发光二极管LED0闪烁,点亮和熄灭的时间均为1 s。解:只要单片机上电,发光二极管LED0就能不停地闪烁,即无休止地点亮1 s、熄灭1 s。要求控制一个LED闪烁时,字节寻址与位寻址均可实现,本例中采用位寻址。源程序#include /包含51系列单片机的头文件#define uchar unsigned char #define uint unsigned intsbit LED0=P20; /定义P2.0为LED0main() /主函数 项 目 二 霓 虹 灯 控 制 系 统 uint i,j; /定义局部变量i、j,用于延时while(1) / while死循环,无数遍点亮、熄灭LED0LED0=0; /位寻址,点亮LED0 for(i=0;i100;i+) /延时1 sfor(j=0; j1827;j+); /分号必须有LED0=1; /位寻址,熄灭LED0for(i=0;i100;i+) /延时1 sfor(j=0; j1827;j+); /分号必须有 项 目 二 霓 虹 灯 控 制 系 统 主函数中先定义了i、j两个变量,在函数内部定义的变量为局部变量,然后执行while循环,由于while的表达式是1,永远为真,形成死循环,因此CPU将一直执行while的循环体语句。在while的循环体中,先点亮LED0,延时1 s,后熄灭,延时1 s,编译后下载至单片机就可以观察到LED0闪烁的效果。 项 目 二 霓 虹 灯 控 制 系 统 例3 编程使图1-18中发光二极管LED0闪烁,点亮和熄灭的时间均为500 ms。解:源程序#include /包含51系列单片机的头文件#define uchar unsigned char#define uint unsigned intsbit LED0=P20; /定义P2.0为LED0/*延时函数*/void delay500 ms() /延时500 ms 项 目 二 霓 虹 灯 控 制 系 统 uint i,j; /定义局部变量i、j,用于延时for(i=0;i50;i+) /外层for语句循环50次for(j=0; j1827;j+); /内层for语句约延时10 ms,分号必须有/*主函数*/main() while(1) / while死循环,无数遍点亮、熄灭LED0 项 目 二 霓 虹 灯 控 制 系 统 LED0=0; /位寻址,点亮LED0delay500ms(); /调用延时函数,延时500 msLED0=1; /位寻址,熄灭LED0delay500ms(); /调用延时函数,延时500 ms“/* */”也是C51的注释符,可以注释多行,而“/”只能注释一行。 项 目 二 霓 虹 灯 控 制 系 统 由于关键字unsigned char、unsigned int较长,使用时会很麻烦,英文不好时也容易写错,这时可用#define命令给它们重新起一个比较简单的新名字,如:uchar、uint,当然也可以是其他名字,在后续程序中可直接用这个新名字定义变量,如:uint i,j;。源程序中,在主函数之前定义了无参延时函数void delay500ms(),“void”表示该函数执行完后不需要返回任何数值,是一个无返回值的函数;“delay500ms”是延时函数的名字,表示它是一个延时500 ms的函数,函数名可以任意起,但是要注意两点:一是不能和C51中的关键字相同,二 项 目 二 霓 虹 灯 控 制 系 统 是要做到见名知义;函数名后的括号中没有写任何参数,表示它是一个无参函数。延时函数delay500ms()的函数体内定义了i、j两个变量用于控制循环次数,它们均为局部变量。在主函数中需要延时500 ms时,可以通过语句“delay500ms();”调用延时函数,CPU转去执行延时函数,从而实现延时功能,这样会使主函数看起来条理清晰,增强了程序的可读性。 项 目 二 霓 虹 灯 控 制 系 统 例4 编程使图1-18中的发光二极管LED0闪烁,点亮500 ms、熄灭300 ms。解:该题目要求发光二极管点亮与熄灭的时间不相同,如果编写无参延时函数,需将无参延时函数的时间设置为100 ms,调用3次实现300 ms的延时,调用5次实现500 ms的延时,方可实现题目要求。最好的方法是编写有参延时函数,在调用时给以不同的实参,这样就可以实现不同时间的延时了。 项 目 二 霓 虹 灯 控 制 系 统 项 目 二 霓 虹 灯 控 制 系 统 main()while(1) / while死循环,无数遍点亮、熄灭LED0 LED0=0; /位寻址,点亮LED0delay10ms(50 ); /实参为50,延时500 msLED0=1; /位寻址,熄灭LED0delay10ms(30 ); /实参为30,延时300 ms 项 目 二 霓 虹 灯 控 制 系 统 由“void delay10ms(uchar a)”定义一个有参延时函数,括号中的“uchar a”表示a是该函数的一个形式参数,用a来控制函数体内第一个for语句的循环次数,两个for语句共执行a1827次,约延时a10 ms;在主函数中调用该函数时,需要对应给出一个具体的数据,即实参,当执行被调函数时,将函数体中的所有形参用实参代替。主函数中通过调用语句“delay10 ms (50);”及“delay10ms (30);”调用delay10 ms函数,并将实参50或30传送给delay10 ms中的形参a,执行延时函数后可分别获得500 ms及300 ms的延时,改变实参可以方便地改变延时时间。 项 目 二 霓 虹 灯 控 制 系 统 延时函数是单片机编程中常用的一个函数,除了由for语句构成外,也可以由while语句构成;循环变量的修改不仅可以用自增,也可以用自减。3) 思考编写实现下述要求的源程序。(1) 分别用前述方法实现300 ms延时。(2) 用位寻址使P2.2端口所接发光二极管闪烁,点亮、熄灭时间自定。(3) 用字节寻址使P2.3、P2.4端口所接的两个发光二极管同时闪烁,点亮、熄灭时间自定。 项 目 二 霓 虹 灯 控 制 系 统 2.2.6 流水灯流水灯是霓虹灯中最简单的一种闪烁效果,要求每次只点亮一个发光二极管,轮流点亮所有的发光二极管,点亮时间为500 ms;轮流点亮所有发光二极管时,既可从高位至低位,也可从低位至高位,还可在高位与低位之间往复。硬件电路如图1-18所示。从程序流程的角度来看,程序可以分为顺序结构、分支结构、循环结构三种形式,如图2-5所示。分支结构与循环结构形式多样,图2-5(b)、(c)所示的分支结构与循环结构只是其中常见的一种。这三种基本结构可以组成各种复杂程序,它们不是绝对独立的,常常是相互融合的,从前述源程序中 就可以清楚地看到这一点。 项 目 二 霓 虹 灯 控 制 系 统 图2-5 三种程序结构 项 目 二 霓 虹 灯 控 制 系 统 1. 顺序结构顺序结构是最简单的程序结构,执行时单片机按照程序中指令的顺序逐条执行。在实现复杂的功能时,顺序结构常常用于实现一些基本功能或用作循环结构的循环体。例5 采用顺序结构使P2口所接的8个发光二极管形成流水灯。解:用顺序结构实现流水灯时,只需要按照点亮的次序将代码发送至I/O口即可。从LED0LED7依次点亮,当最高位的LED7点亮后,又从LED0重新开始,流程图如图2-6所示。 项 目 二 霓 虹 灯 控 制 系 统 图2-6 流水灯流程图(顺序结构) 项 目 二 霓 虹 灯 控 制 系 统 流水灯在点亮一个LED的同时,要使其他7个LED熄灭,用字节寻址较为方便。从低位至高位轮流点亮LED时,P2口所需代码如表2-2所示。 项 目 二 霓 虹 灯 控 制 系 统 表2-2 流水灯代码 项 目 二 霓 虹 灯 控 制 系 统 从表2-2可知,由于每次只能点亮一个LED,因此每个代码中都只有一个低电平。源程序#include /包含51系列单片机的头文件#define uchar unsigned char#define uint unsigned int/*延时函数*/void delay10ms(uchar a) /定义有参延时函数,延时时间为a10 ms uint i,j; /定义局部变量i、j,用于延时 for(i=0;ia;i+)for(j=0; j1827;j+); /分号必须有 项 目 二 霓 虹 灯 控 制 系 统 /*主函数*/main() while(1) / while死循环,无数遍点亮LED0LED7 P2=0 xfe; /字节寻址,点亮LED0delay10ms(50); /延时500 msP2=0 xfd; /字节寻址,点亮LED1delay10ms(50); /延时500 msP2=0 xfb; /字节寻址,点亮LED2delay10ms(50); /延时500 ms P2=0 xf7; /字节寻址,点亮LED3delay10ms(50); /延时500 msP2=0 xef; /字节寻址,点亮LED4 项 目 二 霓 虹 灯 控 制 系 统 delay10ms(50); /延时500 msP2=0 xdf; /字节寻址,点亮LED5delay10ms(50); /延时500 msP2=0 xbf; /字节寻址,点亮LED6delay10ms(50); /延时500 msP2=0 x7f; /字节寻址,点亮LED7delay10ms(50); /延时500 ms 项 目 二 霓 虹 灯 控 制 系 统 主函数由while语句构成死循环,在while的循环体中顺序向P2口发送代码,使LED0LED7轮流点亮,由于while(1)的表达式永远为真,因此当LED7点亮后又重新从LED0开始点亮。上述源程序之所以称为顺序结构,就是指在while的循环
展开阅读全文