单片机ppt课件单片机的C语言编程.ppt
第10章 单片机的C语言编程,单片机原理、接口及应用,内 容 提 要, C51程序结构 C51的数据类型 数据的存贮类型和存贮模式 C51对SFR、可寻址位、存储器和I/O口的定义 C51的运算符 函 数 C语言编程实例 单片机资源的C语言编程实例 汇编语言和C语言的混合编程 C语言函数库的管理与使用 小结,51系列单片机支持三种高级语言,即PL/M,C和BASIC。8052单片机内固化有解释BASIC语言 ,BASIC语言适用于简单编程而对编程效率运行速度要求不高的场合;PL/M是一种结构化的语言,很象PASCAL,PL/M 编译器好象汇编器一样产生紧凑的机器代码,可以说是高级汇编语言,但它不支持复杂的算术运算,无丰富库函数支持,学习PL/M无异于学习一种新的语言。C语言是一种通用的程序设计语言,其代码率高,数据类型及运算符丰富,并具有良好的程序结构,适用于各种应用的程序设计,是目前使用较广的单片机编程语言。,单片机的C语言采用C51编译器(简称C51)。由C51产生的目标代码短、运行速度高、所需存储空间小、符合C语言的ANSI标准,生成的代码遵循Intel目标文件格式,而且可与A51汇编语言或PL/M51语言目标代码混和使用。,应用C51编程具有以下优点: (1)C51管理内部寄存器和存贮器的分配,编程时,无需考虑不同存储器的寻址和数据类型等细节问题; ()程序由若干函数组成,具有良好的模块化结构;()有丰富的子程序库可直接引用,从而大大减少用户编程的工作量。()C语言和汇编语言可以交叉使用. 汇编语言程序代码短、运行速度快、但复杂运算编程耗时。如果用汇编语言编写与硬件有关的部分程序,用C语言编写与硬件无关的运算部分程序,充分发挥两种语言的长处,可以提高开发效率。,10.1 C51程序结构,同标准C一样,C51的程序由一个个函数组成,这里的函数和其他语言的“子程序”或“过程 ”具有相同的意义。其中必须有一个主函数main(),程序的执行从main()函数开始,调用其 他函数后返回主函数main(),最后在主函数中结束整个程序而不管函数的排列顺序如何。,C语言程序的组成结构如下所示:,全局变量说明 /*可被各函数引用*/ main() /*主函数*/ 局部变量说明 /*只在本函数引用*/执行语句(包括函数调用语句) fun1(形式参数表) /*函数1*/ 形式参数说明,局部变量说明 执行语句(包括调用其他函数语句)funn(形式参数表) /*函数n*/形式参数说明 局部变量说明 执行语句,C语言的语句规则: 1. 每个变量必须先说明后引用,变量名英文大小写是有差别的。 2. C语言程序一行可以书写多条语句,但每个语句必须以“;”结尾,一个语句也可以多行书写为好。 3. C语言的注释用/*/表示。 4. “”花括号必须成对,位置随意,可在紧挨函数名后,也可另起一行,多个花括号可以同行书写,也可逐行书写,为层次分明,增加可读性,同一层的“”花括号对齐,采用逐层缩进方式书写。,10.2 C51的数据类型,C51的数据有常量和变量之分。 常量在程序运行中其值不变的量,可以为字符,十进制数或十六进制数(用0 x表示)。 常量分为数值型常量和符号型常量,如果是符号型常量,需用宏定义指令(#define)对其进行定义(相当于汇编的“EQU”伪指令)如: #define PI 3.1415 那么程序中只要出现PI的地方,编译程序都译为3.1415。 变量在程序运行中其值可以改变的量。 一个变量由变量名和变量值构成,变量名即是存贮单元地址的符号表示,而变量的值就是该单元存放的内容。定义一个变量,编译系统就会自动为它安排一个存贮单元,具体的地址值用户不必在意。,10.2.1 C51变量的数据类型,无论哪种数据都是存放在存贮单元中的,每一个数据究竟要占用几个单元(即数据的长度)都要提供给编译系统,正如汇编语言中存放数据的单元要用DB或DW伪指令进行定义一样,编译系统以此为根据预留存贮单元,这就是定义数据类型的意义.C51编译器支持数据类型见表10.1。,表10-1 C51的数据类型,对表10.1作如下说明: 1. 字符型(char)、整型(int)和长整型(long)均有符号型(signed)和无符号型(unsigned)两种,如果不是必须,尽可能选择unsigned型,这将会使编译器省却符号位的检测,使生成的程序代码比signed类型短得多。 2. 程序编译时,C51编译器会自动进行类型转换,例如将一个位变量赋值给一个整型变量时,位型值自动转换为整型值;当运算符两边为不同类型的数据时,编译器先将低级的数据类型转换为较高级的数据类型,运算后,运算结果为高级数据类型。 3. 51单片机内部数据存贮器的可寻址位(20H2FH)定义为bit型,而特殊功能寄存器的可寻址位(即地址为X0H和X8H的SFR的各位)只能定义为sbit类型。,10.2.2 关于指针型数据,(1)关于指针型变量 在汇编语言程序中,要取存贮单元m的内容可用直接寻址方式,也可用寄存器间接寻址方式 ,如果用R1寄存器指示m的地址,用R1取m单元的内容。相对应的在C语言中用变量名表示取变量的值(相当于直接寻址),也可用另一个变量(如P)存放m的地址,P就相当于R1寄存器 。用*P取得m单元的内容(相当于汇编的间接寻址方式)这里P即为指针型变量。下面表格表示两种语言将m单元的内容送n单元的对照语句。,注: 上表省略了汇编语言程序中对符号地址n和m用EQU伪指令进行具体地址定义的 语句以及C语言对变量n、m和指针变量P进行类型定义的语句,实际程序设计中,此步是不可 缺少的。表中&为取地址运算符,*为取内容运算符。,表1O-2 汇编语言和C语言的对照,(2)指针型数据的类型 由于C51是结合51单片机硬件的,51单片机的不同存贮空间,有不同的地址范围,即使对于同一外部数据存贮器,又有用Ri分页寻址(Ri为八位)和用DPTR寻址(DPTR为十六位)两种寻址方式,而指针本身也是一个变量,有它存放的存贮区和数据长度。因此,在指针类型的定义中要说明:被指的变量的数据类型和存贮类型;指针变量本身的数据类型(占几个字节)和存贮类型(即指针本身存放在什么存贮区)。 例如类型定义为data或idata,表示指针指示内部数据存贮器;而pdata表示指针指向外部数据存贮器,用Ri间址。以上均为八位地址;而类型code/xdata表示指针指向外部程序存贮器或外部数据存贮器指针,本身(即被指 示地址)应为十六位长度。如果想使指针能适用于指向任何存贮空间,则可以定义指针为通用型,此时指针长度为3字节,第一字节表示存贮器类型编码,第二、三字节分别表示所指 地址的高位和低位。第一字节表示的存贮器类型编码见表10-3:,表10-3 通用型指针的存贮类型编码,10.3 数据的存贮类型和存贮模式,10.3.1数据的存贮类型 C51是面向8XX51系列单片机及硬件控制系统的开发语言,它定义的任何变量必须以一定的存贮类型的方式定位在8XX51的某一存贮区中,否则便没有意义。因此在定义变量类型时,还必须定义它的存贮类型,C51的变量的存贮类型如表10-4所示:,表10-4 C51的变量的存贮类型,访问内部数据存贮器(idata)比访问外部数据存贮器(xdata)相对要快一些,因此,可将经常使用的变量置于内部数据存贮器中,而将较大及很少使用的数据变量置于外部数据存贮器中。例如定义变量x语句:data char x (等价于char data x)。如果用户不对变量的存贮类型定义,则编译器承认默认存贮类型,默认的存贮类型由编译控制命令的存贮的模式部分决定。,10.3.2 存贮器模式,存贮器模式决定了变量的默认存贮器类型、参数传递区和无明确存贮区类型的说明。C51的存贮器模式有SMALL、LARGE和COMPACT(见表10-5)。 在固定的存贮器地址进行变量参数传递是C51的一个标准特征,在SMALL模式下参数传递是在内部数据存贮区中完成的。LARGE和COMPACT模式允许参数在外部存贮器中传递。C51同时也支持混 合模式,例如在LARGE模式下生成的程序可将一些函数分页放入SMALL模式中从而加快执行速度。,例如设C语言源程序为PROR.C,若使程序中的变量类型和参数传递区限定 在外部数据存贮区 ,有两种方法: 方法1:用C51对PROR.C进行编译时,使用命令C51 PROR.C COMPACT。 方法2:在程序的第一句加预处理命令 #pragma compact,表10-5 存贮器模式,10.3.3 变量说明举例,data char var; /*字符变量var定位在片内数据存贮区*/ char code MSG=PARAMETER:; /*字符数组MSG 定位在程序存 贮区*/unsigned long xdata array100; /*无符号长型数组定位在片外RAM区,每元素占4bytes*/ float idata x,y,z; /*实型变量x,y,z,定位在片内用间址访问的内部 RAM区*/ bit lock; /*位变量Lock定位在片内RAM可位寻址区*/unsigned int pdata sion; /*无符号整型变量sion定位在分页的外部RAM*/unsigned char xdata vector1044 /*无符号字符型三维数组, 定位在片外RAM区*/ sfr P0=0 x80; /*定义P0口,地址为80H*/char bdata flags; /*字符变量flags定位在可位寻址内部RAM区*/ sbit flag0=flags0; /*定义flag0为flags.0 */,如果在变量说明时略去存贮器类型标志符,编译器会自动选择默认的存贮器类型。默认的存贮器类型由控制指令SMALL、COMPACT和LARGE限制。例如如果声明char var,则默认的存贮器模式为SMALL,var放在data存贮区;如果使用COMPACT模式,var放入idata存贮区 ;在使用LARGE模式的情况下,var被放入外部数据存贮区(xdata存贮区)。 10.3.4指针变量说明举例long xdata *px; /*指针px指向long型xdata区(每个数据占四个单元,指针自身在默认存贮器(如不指定编译模式在data区),指针长度为2个字节*/char xdata *data pd;/*指针pd指向字符型xdata区,自身在data区,长度2字 节*/data char xdata *pd; /*与上例等效*/ data int *pn;(和int *data pn及intr*pn等效) /*定义一个类型为int型的通用型指针,指针自身在data区长度为3字节*/,在上例的指针声明中包含如下几个内容:1) 1) 指针变量名(如px)前面冠以“*”,表示px为指针型变量,此处*不带取内容之意。 2) 指针指向的存贮类型,即指向哪个存贮区,它决定了指针本身的长度(见表10.1)。存贮类型声明的位置在数据类型和指针名(如*px)之间,如无次项声明,则此指针型变量为通用型。 3) 3) 指针指向的存贮区的数据类型,即被指向的存贮区以多少个单元作一个数据单位,当程序通过指针对该区操作时,将按此规定的单元个数的内容作为一个数据操作。 4) 指针变量自身的存贮类型,即指针处于什么区与自身的长度无关,该声明可位于声明语句的开头,也可在“*”和变量名之间。此项由编译模式放在默认区,如无规定编译模式,通常在data区。,10.4 C51对SFR、可寻址位、存储器和I/O口的定义,10.4.1 特殊功能寄存器SFR定义 C51提供了一种自主形式的定义方式,使用特定关键字sfr 如 sfr SCON=0 x98; /*串行通信控制寄存器地址98H*/ sfr TMOD=0 x89; /*定时器模式控制寄存器地址89H*/ sfr ACC=0 xe0; /*A累加器地址E0H*/ sfr P1=0 x90; /*P1端口地址90H*/ 定义了以后,程序中就可以直接引用寄存器名。 C51也建立了一个头文件reg51.h (增强型为reg52.h),在该文件中对所有的特殊功能寄存器的进行了sfr定义, 对特殊功能寄存器的有位名称的可寻址位进行了sbit定义,因此,只要用包含语句#include,就可以直接引用特殊功能寄存器名,或直接引用位名称。要特别注意:在引用 时特殊功能寄存器或者位名称必须大写。,10.4.2 对位变量的定义,C51对位变量的定义有三种方法: 1. 将变量用bit类型的定义符定义为bit类型: 如 bit mn; mn为位变量,其值只能是“0”或“1”,其位地址C51自行安排在可位寻址区的bdata区。 2. 采用字节寻址变量.位的方法: 如 bdata int ibase; /*ibase定义为整型变量*/ sbit mybit=ibase15; /*mybit定义为ibase的D15位*/ 这里位是运算符“”相当于汇编中的“”,其后的最大取值依赖于该位所在的字节寻址变量的定义类型,如定义为char最大值只能为7。,3. 对特殊功能寄存器的位的定义 方法1:使用头文件及sbit定义符;多用于无位名的可寻址位。 例如 #include sbit P1-1=P11; /*P1-1为P1口的第1位*/ sbit ac=ACC7; /*ac定义为累加器A的第7位*/ 方法2:使用头文件reg51.h,再直接用位名称。 例如 #include RS1=1; RS0=0; 方法3:用字节地址位表示 例如 sbit OV=0 xD02; 方法4:用寄存器名.位定义 例如 sfr PSW=0 xd0; /*定义PSW地址为d0H*/ sbit CY=PSW7; /*CY为PSW7*/,10.4.3 C51对存贮器和外接I/O口的绝对地址访问,1.对存贮器的绝对地址访问 利用绝对地址访问的头文件absacc.h可对不同的存贮区进行访问。该头文件的函数有: CBYTE (访问code区字符型) DBYTE (访问data区字符型) PBYTE (访问pdata或I/O区字符型) XBYTE (访问xdata或I/O区字符型) 还有CWORD、DWORD、PWORD和XWORD四个函数,它们的访问区域同上,只是访问的类型为int 型。例10-1 #include #define com XBYTE0 x07ff 那么后面程序com变量出现的地方,就是对地址为07ffH的外部RAM或I/O口进行访问。,例10-2 XWORD0=0 x9988; 即将9988H(int类型)送入外部RAM的0号和1号单元。 使用中要注意:absacc.h一定要包含进程序,XBYTE必须大写。 2.对外部I/O口的访问 由于单片机的I/O口和外部RAM统一编址,因此对I/O口地址的访问可用XBYTE (MOVX DPTR )或PBYTE (MOVX Ri)进行。例10-3 XBYTE0Xefff=0 x10;将10H输出到地址为EFFFH端口,10.5 C51的运算符,1赋值运算符: 将“”的右边的值赋值给左边的变量.2. C51的算术运算符: (加或正号); (减或负号);* (乘号); / (除号); % (求余)优先级为:先乘除,后加减,先括号内,再括号外3. C51的关系运算符有六种:(小于); (大于); = (小于等于); = (大于等于); =(相等);!= (不相等)优先级:前四个高,后二个“=”和“!=”级别低。,4C51的逻辑运算符有三种: 逻辑表达式和关系表达式的值相同,以0代表假,以1代表真。以上三种运算的优先级见图10.1。 5C51的按位操作的运算符有六种:&(按位与);(按位或); (按位异或); (位取反); (位右移 ) (注:补零移位)例1. a=0 xf0H; 表达式a=a值为0FH例2. a=0 xea; 表达式a2值为A8H,即a值左移两位,移位后空白位补0。 6. 自增、自减运算符: +i,-i (在使用i之前,先使i值加1,减1) i+,i- (在使用i之后,再使i值加1.,减1) 例设i原值为5 j=+i 则j值为6,i值也为6 j=i+ 则j值为5,i值为6,| 非算术运算关系运算&和|= 赋值运算,图10-1 运算符的优先级,7复合赋值运算符:+=;=;*=;/=;%=;=;&=;=;|=。例:a+=b相当于a=a+b。a=7. 相当于a=a 7.。 8. 对指针操作的运算符: &取地址运算 *间址运算符例 a=&b;取b变量的地址送变量a c=*b;将以b的内容为地址的单元的内容送c这里要注意: “&”与按位与运算符的差别,如果“&”为“与”,&的两边必须为变量或常量; “*”与指针定义时指针前的“*”的差别。如char *pt,这里的“*”只表示pt为指针变量,不代表间址取内容的运算。,10.6 函 数,C语言程序由函数组成,下面介绍函数的要点。10.6.1 函数的分类及定义 从用户使用角度划分,函数分为库函数和用户自定义函数。 库函数是编译系统为用户设计的一系列标准函数(见本书附录二),用户只需调用,而无需自己去编写这些复杂的函数,如前面所用到的头文件reg51.h、absacc.h等,有的头文件中包括一系列函数,要使用其中的函数必须先使用#include包含语句,然后才能调用。用户自定义函数是用户根据任务编写的函数从参数形式上函数分为无参函数和有参函数。有参函数即是在在调用时,调用函数用实际参数代替形式参数,调用完返回结果给调用函数。,10.6.2 函数的定义,函数以“”开始,以“”结束。 无参函数的定义:返回值类型 函数名() 函数体语句 如果函数没有返回值,可以将返回值类型设为void。 有参函数的定义: 返回值类型 函数名(形式参数表列) 形式参数类型说明 函数体语句 return(返回形参名) 也可以这样定义 返回值类型 函数名(类型说明 形式参数表列) 函数体语句 return(返回参形名) 其中形式参数表列的各项要用“,”隔开,通过return语句将需返回的值返回给调用函数。,10.6.3 函数的调用,函数调用的形式为:函数名(实际参数表列); 实参和形参的数目相等类型一致,对于无参函数当然不存在实际参数表列。 函数的调用方式有三种: 函数调用语句:即把被调函数名作为调用函数的一个语句;如fun1()。 被调函数作为表达式的运算对象,如 result=2*get(a,b) 此时get函数中的a,b应为实参,其以返回值参予式中的运算。 被调函数作为另一个数的实际参数 如 m=max(a,get(a,b);函数get(a,b)作为函数max()的一个实际参数。,10.6.4对被调函数的说明,如果被调函数出现在主调函数之后,在主调函数前应对被调函数作以说明,形式为: 返回值类型 被调函数名(形参表列); 如果被调函数出现在主调函数之前,可以不对被调函数说明。下面以一个简单例子来说明int fun1(a,b) int a,b; int c; c=a+b; return(c); main() int d,0u=3,v=2;d=2*fun(u,v); 上例被调函数在主调函数前,不用说明。,int fun1(a,b); main() int d,u=3,v=2; d=2*fun1(u,v); int fun1(a,b); int a,b; int c; c=a+b; return(c); 上例中被调函数在主调函数后,在前面对被调函数进行说明。,10.7 C语言编程实例,为了使C语言的编程方法和汇编语言的编程方法有一个对比,本节采用3.1节的例题。由于C51编译器是针对单片机的,因此ANSI C中的scanf和printf等对PC电脑的键盘和监视器的输入、输出语句无效。运算的数据可以通过变量置入或取出,这时C51会自动安排使用的存贮单元。当然也可以用户自行通过具体的内存地址置入数据或从特定地址取出数据,这就少不了要会观察具体地址的内容或改变该地址的内容,C语言的编程上机调试见本教材的实验部分。下面通过一个例子说明C语言程序编译后生成的机器代码及对应的反汇编程序,从中引出一 些道理。,10.7.1 顺序程序的设计,例105 完成1980524503的编程 分析:两个乘数比较大,其积更大,采用unsigned long类型,设乘积存放在外部数据存贮器0号开始的单元。程序如下: main() unsigned long xdata *p; /*设定指针p指向类型为unsigned long的外部RAM区*/ unsigned long a=19805; /* 设置a为unsigned long类型,并赋初值 */ unsigned long b=24503,c; /*设置b和积为unsigned long类型,并赋初值 */ p=0; /*设地址指向0号单元*/ c=a*b; *p=c; /*积存入外部RAM 0号单元*/ ,上机通过WAVE软件仿真调试,在变量观察窗口看到运算结果c=48528195,即为乘积的十进制数。观察XDATA区(外部RAM)的0000H0003H单元分别为1C EC D0 7B,即存放的为乘积的十六进制数。 观察DATA区(内部RAM区): 地址 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 1C EC D0 7B 00 00 4D 5D 00 00 5F B7 C变量(积) a变量 b变量 可见定义为unsigned long类型,给每个变量分配四个单元,如果定义类型不对,将得不到 正确的结果。 对于复杂的运算通常采用查表的方法。如同汇编程序设计一样,在程序存贮器建立一张表, 在C语言中表格定义为数组,表内数据(元素)的偏移量表现为下标。数组的使用如同变量一样,要先进行定义:说明数组名、维数、数据类型和存贮类型,在定义数组的同时,还可以给数组各元素赋初值。通过下例说明C51数组的定义方法和用C语言编查表程序的方法。,例106 片内RAM 20H单元存放着一个005H的数,用查表法,求出该数的平方值放入内部RAM 21H单元。 main() char x,*p char code tab6=0,1,4,9,16,25;p=0 x20; x=tab*p; p+; *p=x; ,10.7.2 循环程序的设计,C语言的循环语句有以下几种形式1.while(表达式)语句; 其中表达式为循环条件,语句为循环体,当表达式值为真(值为非0),重复执行“语句”。语句可只一条以“;”结尾;可以多条组成复合语句,复合语句必须用括起;也可以没有语句,通常用于等待中断,或查询。2.do语句;while(表达式) 表达式为真执行循环体“语句”,直至表达式为假,退出循环执行下一个语句。 3.for(表达式1;表达式2;表达式3;)语句; 其中语句为循环体。执行过程是:执行表达式1后进入循环体,如表达式2为假,按表达式3修改变量,再执行循环体,直到表达式2为 真. 语句中的表达式可以省其中任一项甚至全部,但二个分号不可省,如for(;语句;为 无限循环,for(i=4;i+)语句i从4开始无限循环,for (;i100; )相当于while(i100),例10-7 while(P1 本程序完成0+1+2+10的累加,执行后sum=55,例10-9 将例10-8改用for语句编程 main int sum=0,i; for(i=0;i10;i+) sun+=i;,10.7.3 分支程序的设计,C语言的分支选择语句有以下几种形式: 1. if(表达式)语句;句中表达式为真执行语句,否则执行下一条语句。当花括号中的语句不只一条,花括号不能省。 2. if(表达式)语句1;else语句2;句中表达式为真执行语句1,否则执行语句2为了能无论哪种情况, 执行完后都执行下一条语句。if语句可以嵌套。 3.switch(表达式) case 常量表达式1:语句1;break;case 常量表达式2:语句2;break; case 常量表达式n:语句n;break; default:语句n+1;,说明:语句先进行表达式的运算,当表达式的值与某一case后面的常量表 达式相等,就执行它后面的语句。当case语句后有break语句时,执行完这一case语句后,跳出switch语句,当case 后面无break语句,程序将执行下一条case语句。 如果case中常量表达式值和表达式的值都不匹配,就执行default后面的语句。 如果无default语句就退出switch语句。 default的次序不影响执行的结果,也可无此语句。 case语句适于多分支转移的情况下使用。,例10-10 片内RAM的20H单元存放一个有符号数x,函数y与x有如下关系式: x x0 y= 20H x=0 x+5 x0 设y存放于21H单元,程序如下main() char x,*p,*y;p=0 x20; y=0 x21; for(;) x=*p; if(x0)*y=x; if(x0)*y=x+5; if(x=0)*y=0 x20; 程序中为观察不同数的执行结果,采用了死循环语句for(;),上机调试时退出死循环可用Ctrl+C。,例10-11 有两个数a和b,根据R3的内容转向不同的处理子程序:r3=0,执行子程序pr0(完成两数相加) r3=1,执行子程序pr1(完成两数相减) r3=2,执行子程序pr2(完成两数相乘) r3=3,执行子程序pr3(完成两数相除) 分析: C语言中的子程序即为函数,因此需编四个处理的函数,如果主函数在前,主函数要对子函数进行说明;如果子函数在前,主函数无须对子函数说明,但是无论子、主函数的顺序如何,程序总是从主函数开始执行,执行到调用子函数就会转到子函数执行. 在C51编译器中通过头文件reg51.h可以识别特殊功能寄存器,但不能识别R0R7通用寄存器,因此R0R7只有通过绝对地址访问识别,程序如下:,#include #define r3 DBYTE0 x03 int c,c1,a,b; pr0() c=a+b; pr1() c=a-b; pr2() c=a*b; pr3() c=a/b; main() a=90;b=30;,for (;) switch(r3) case 0: pr0();break; case 1: pr1();break; case 2: pr2();break; case 3: pr3();break; c1=56; ,在上述程序中,为便于调试观察,加了C1=56语句,并使用了死循环语句for(;),用Ctrl+C 可退出死循环。,10.8 单片机资源的C语言编程实例,例10-12 在3.1节曾用汇编语言完成了外部RAM的000EH单元和000FH单元的内容交换,现改用C语言编程。C语言对地址的指示方法可以采用指针变量,也可以引用absacc.h头文件作绝对地址访问,下面采用绝对地址访问方法。#include main() char c; for(;) c=XBYTE14; XBYTE14=XBYTE15; XBYTE15=c; ,程序中为方便反复观察,使用了死循环语句for(;)只要用Ctrl+C即可退出死循环。上面程序通过编译,生成的机器代码和反汇编程序如下:,0000 020014 LJMP 0014H 0003 90000E MOV DPTR,#000EH 0006 E0 MOVX A,DPTR0007 FF MOV R7,A0008 A3 INC DPTR 0009 E0 MOVX A,DPTR000A 90000E MOV DPTR,#000EH 000D F0 MOVX DPTR,A 000E A3 INC DPTR 000F EF MOV A,R7 0010 F0 MOVX DPTR,A,0011 80F0 SJMP 0003H 0013 22 RET 0014 787F MOV R0,#7FH 0016 E4 CLR A 0017 F6 MOV R0,A 0018 D8FD DJNZ R0,0017H 001A 758107 MOV SP,#07H 001D 020003 LJMP 0003H,例中可见: 一进入C语言程序,首先执行初始化,将内部RAM的07FH 128个单元清零,然后置SP为07H(视变量多少不同,SP置不同值,依程序而定),因此如果要对内部RAM置初值,一定要在执行了一条C语言语句后进行。 C语言程序设定的变量,C51自行安排寄存器或存贮器作参数传递区,通常在R0R7(一组或两组,视参数多少定),因此,如果对具体地址置数据,应避开这些R0R7的地址。 如果不特别指定变量的存贮类型,通常被安排在内部RAM中。,10.8.2 并行口及键盘的C语言编程,例10-13 用P1.0输出1KHz和500Hz的音频信号驱动扬声器,作报警信号,要求1KHz信号响100ms,500Hz信号响200ms,交替进行,P1.7接一开关进行控制,当开关合上,响报警信号,当开关断Kk告警信号停止,编出程序.分析 500Hz信号周期为2ms,信号电平为每1ms变反一次.1KHz信号周期为1ms,信号电平每500s变反一次。用C语言编程如下:,#includesbit P10=P10;sbit P17=P17;main()unsigned char i, j;while(1) while(P17=0)for(i=1;i=150;i+) /*控制音响时间*/ P10=P10;for(j=0;j=50;j+); /*延时完成信号gou周期时间*/for(i=1;i=100;i+) /*控制音响时间*/ P10=P10; for(j=0;j=100;j+); /*延时,完成信号周期时间*/ ,例10-14 在 下图中8XX51接有五个共阴极数码管的动态显示接口电路,开关打向位置“1”时,显示“12345”字样,当开关打向“2”时,显示HELLO字样,C语言编程程序清单如下。,图10-7 接五个共阴极数码管的动态显示接口,用C语言完成上述功能编程#include#define uint unsigned int#deefine uchar unsigned charsbitP17=P17;main ( )uchar code tab15=0 x86,0 xdb,0 xcf,0 xe6,0 xed ; /*“15”的字形码,因P1.7接的开关,最高位送的“1”*/ uchar code tab25=0 xf8,0 xf9,0 xb8,0 xb8,0 x,bf; /*“HELLO”的段码 “1”*/uchar i;unit j;while(1) p3=0 x011 for(i=0;i+) if(p17=1)P1=tab1i; elseP1=tab2i; P3=1;for(j=0;j=25000;j+);,例10-15 以P1.0P1.3作输出线,以P1.4P1.7作输入线,如图4-6所示。,C语言编程程序清单如下:#include#define uchar unsigned char#define uint unsigned intvoid dlms (void);uchar kbscan(void); / * 函数说明 * /void main (void0;uchar key;while (1) key=kbscan( ); / * 键盘扫描函数,返回键码送key保存 * / dlms( ); void dlms (void) /* 延时 * /uchar i; for (i=200;i0;i- -) ,图10-8 44矩阵键盘,uchar kbscan(void) / * 键盘扫描函数 * /uchar sccode,recode;P1=0 xf0; / *P1.0P1.3发全0,P1.4P1.7输入 * / if ( (P1 / * 无键按下,返回值为0 * / ,10.8.3 C51中断程序的编制,C51使用户能编写高效的中断服务程序,编译器在规定的中断源的矢量地址中放入无条件转移指令,使CPU响应中断后自动地从矢量地址跳转到中断服务程序的实际地址,而无需用户去安排。中断服务程序定义为函数,函数的完整定义如下。返回值 函数名(参数)模式再入interrupt nusing m 其中必选项 interrupt n表示将函数声明为中断服务函数,n为中断源编号,可以是031间的整数 ,不允许是带运算符的表达式,n通常取以下值:0 外部中断0;1 定时器/计数器0溢出中断2 外部中断1;3 定时器/计数器1溢出中断4 串行口发送与接收中断5 定时器/计数器2中断,各可选项的意义如下:using m 定义函数使用的工作寄存器组,m的取值范围为03,可缺省。它对目标代码的影响是:函数入口处将当前寄存器保存,使用 m 指定的寄存器组,函数退出时 原寄存器组恢复。选不同的工作寄存器组,可方便实现寄存器组的现场保护。再入属性关键字reentrant将函数定义为再入的,在C51中,普通函数(非再入的)不能递归调用, 只有再入函数才可被递归调用。中断服务函数不允许用于外部函数,它对目标代码影响如下:当调用函数时,SFR中的ACC、B、DPH、DPL和PSW当需要时入栈。如果不使用寄存器组切换,中断函数所需的所有工作寄存器Rn都入栈。函数退出前,所有工作寄存器都出栈。函数由“RETI”指令终止。下面示例说明C语言的编程方法。,例10-15 对10.2.3的例10-4(见图)要求每中断一次,发光二极管显示开关状态 用C语言编程 #include int0() interrupt 0 /*INT0中断函数*/ P1=0 x0f; /*输入端先置1,灯灭*/ P=4; /* 读入开关状态,并左移四位, 使开关反映在发光二极管上*/ main() EA=1; /*开中断总开关*/EX0=1; /*允许INT0中断*/ IT0=1; /*下降沿产生中断*/while(1); /*等待中断*/ ,例10-16 记录并显示中断次数用C语言编程,可有两种编程方法。法1:在主程序中判断中断次数,程序如下:#includechar i;code char tab16=0 x3f,0 x06,0 x5b,0 x4F,0 x66,0 x6d,0 x7d,0 x07,0 x7f,0 x6f,0 x77,0 x7c,0 x39,0 x5e,0 x79,0 x71;int() interrupt 2 i+; /*计中断次数*/ P1=tabi; /*查表,次数送显示*/ main() EA=1; EX1=1; IT1=1;ap5: P1=0 x3f /*显示“0”*/for(i=0;i16;); /*当i小于16等待中断*/ goto ap5; /*当i=16重复下一轮16次中断*/ ,法2:在中断程序中判断中断次数:#includechar i;code char tab16=0 x3f,0 x06,0 x5b,0 x4F,0 x66,0 x6d,0 x7d,0 x07, 0 x7f,0 x6f,0 x77,0 x7c,0 x39,0 x5e,0 x79,0 x71;int() interrupt 1 i+ if(i16)P1=tabi; elsei=0;P1=0 x3f; main() EA=1; EX1=1; IT1=1; P1=0 x3f; while(1); /*等待中断*/ ,10.8.4 定时/计数器的C语言编程,例10-17 在P1.7端接一个发光二极管LED,要求利用定时控制使LED亮一秒灭一秒周而复始,设fo sc=6MHz。分析 T0定时100ms初值=100103/2=50000,即初值为-50000。T1计数5个脉冲工作于方式2,计数初值为-5,T0和T1均采用中断方式。程序如下:#include reg51.hsbit P1_0=P10;sbit P1_7=P17;timer0() inte