浮点数的存储结构.ppt
第7章 单片机的C语言应用程序设计,7.1 C语言与MCS-517.2 C51数据类型及在MCS-51中的存储方式7.3 C51数据的存储类型与MCS-51存储结构7.4 MCS-51特殊功能寄存器(SFR)的C51定义7.5 MCS-51并行接口的C51定义7.6 位变量的C51定义7.7 C51构造数据类型7.8 模块化程序开发过程7.9 MCS-51内部资源使用的C语言编程7.10 MCS-51片外扩展的C语言编程7.11 频率量测量的C语言编程7.12 MCS-51机间通信的C语言编程7.13 键盘和数码显示人机交互的C语言编程,7.1 C语言与MCS51,用汇编程序设计MCS51系列单片机应用程序时,必须要考虑其存储器结构,尤其必须考虑其片内数据存储器与特殊功能寄存器正确、合理的使用以及按实际地址处理端口数据。用C语言编写MCS51单片机的应用程序,虽然不像用汇编语言那样具体地组织、分配存储器资源和处理端口数据,但在C语言编程中,对数据类型与变量的定义,必须要与单片机的存储结构相关联,否则编译器不能正确地映射定位。用C语言编写单片机应用程序与编写标准的C语言程序的不同之处就在于根据单片机存储结构及内部资源定义相应的C语言中的数据类型和变量,其它的语法规定、程序结构及程序设计方法都与标准的C语言程序设计相同。,用C语言编写的应用程序必须经单片机的C语言编译器(简称C51),转换生成单片机可执行的代码程序。支持MCS51系列单片机的C语言编译器有很多种。如American Automation、Auocet、BSO/TASKING、DUNFIELD SHAREWARE、KEIL/Franklin等。其中KEIL/Franklin以它的代码紧凑和使用方便等特点优于其它编译器。本章是针对这种编译器介绍 MCS51单片机C语言程序设计。,7.2 C51数据类型及在MCS-51中的存储方式,7.2.1 C51的数据类型 Franklin C51编译器具体支持的数据类型有:位型(bit)、无符号字符(unsigned char)、有符号字符(singed char)、无符号整型(unsigned int)、有符号整型(signed int)、无符号长整型(unsigned long)、有符号长整型(signed long)、浮点型(float)和指针类型等。,表7.1 Franklin C51的数据类型,7.2.2 C51数据在MCS-51中的存储方式,位变量(bit):与MCS-51硬件特性操作有关的可以定义成位变量。位变量必须定位在MCS-51单片机片内RAM的位寻址空间中。字符变量(char):字符变量的长度为1 byte即8位。这很合适MCS-51单片机,因为MCS-51单片机每次可处理8位数据。对于无符号变量(unsigned char)的值域范围是0255。对于有符号字符变量(signed char),最具有重要意义的位是最高位上的符号标志位(msb)。此位为1代表负,为0代表正。有符号字符变量和无符号字符变量在表示0127的数值时,其含义是一样的,都是00 x7F。负数一般用补码表示,即用11111111表示-1,用11111110表示-2。当进行乘除法运算时,符号问题就变得十分复杂,而C51编译器会自动地将相应的库函数调入程序中来解决这个问题。,整型变量(int):整型变量的长度为16位。与8080和8086 CPU系列不同,MCS-51系列单片机将int型变量的高位字节数存放在低地址字节中,低位字节数存放在高地址字节中。有符号整型变量(signed int)也使用msb位作符号标志位,并使用二进制补码表示数值。可直接使用几种专用的机器指令来完成多字节的加、减、乘、除运算。整型变量值0 x1234以图7.1所示的方式存放在内存中。,图7.1 整型数的存储结构,+0+1,地址,+0+1+2+3,地址,图7.2 长整型变量的存储结构,浮点型变量(float):浮点型变量为32位,占4个字节,许多复杂的数学表达式都采用浮点变量数据类型。应用符号位表示数的符号,用阶码和尾数表示数的大小。用它们进行任何数学运算都需要使用由编译器决定的各种不同效率等级的库函数。Franklin C51的浮点变量数据类型的使用格式与IEEE-754标准有关,具有24位精度,尾数的高位始终为1,因而不保存,位的分布如下:1位符号位。8位指数位。23位尾数。,符号位是最高位,尾数为低23位,内存中按字节存储顺序如下:,其中,S为符号位,1表示负,0表示正;E为阶码;M为23位尾数,最高位为1。浮点变量值-12.5的十进制为:0 xC1480000,它按图7.3所示方式存于内存中。,+0+1+2+3,地址,图7.3 浮点数的存储结构,在编程时,如果只强调运算速度而不进行负数运算时,最好采用无符号(unsigned)格式。无符号字符类型的使用:无论何时,应尽可能使用无符号字符变量,因为它能直接被MCS-51所接受。基于同样的原因,也应尽量使用位变量。有符号字符变量虽然也只占用一个字节,但需要进行额外的操作来进行测试代码的符号位。这无疑会降低代码效率。,使用简化形式定义数据类型。其方法是在源程序开头使用#define语句自定义简化的类型标识符。例如:#define uchar unsigned char#define uint unsigned int 这样,在编程中,就可以用uchar代替unsigned char,用uint代替unsigned int来定义变量。,7.3 C51数据的存储类型与MCS-51存储结构,表 7.2 C51存储类型与MCS-51存储空间的对应关系,表7.3 C51存储类型及其数据长度和值域,带存储类型的变量的定义的一般格式为 数据类型 存储类型 变量名,带存储类型的变量定义举例:char data var1;bit bdata flags;float idata x,y,z;unsigned int pdata var2;unsigned char vector34;,表 7.4 存储模式说明,7.4 MCS-51特殊功能寄存器(SFR)的C51定义,MCS-51单片机中,除了程序计数器PC和4组工作寄存器组外,其它所有的寄存器均为特殊功能寄存器(SFR),分散在片内RAM区的高128字节中,地址范围为80H0FFH。SFR中有11个寄存器具有位寻址能力,它们的字节地址都能被8整除,即字节地址是以8或0为尾数的。为了能直接访问这些SFR,Franklin C51提供了一种自主形式的定义方法,这种定义方法与标准C语言不兼容,只适用于对MCS-51系列单片机进行C语言编程。特殊功能寄存器C51定义的一般语法格式如下:sfr sfr-name=int constant;,sfr是定义语句的关键字,其后必须跟一个MSC-51单片机真实存在的特殊功能寄存器名,=后面必须是一个整型常数,不允许带有运算符的表达式,是特殊功能寄存器sfr-name的字节地址,这个常数值的范围必须在SFR地址范围内,位于0 x800 xFF。例如:sfr SCON=0 x98;/*串口控制寄存器地址98H*/sfr TMOD=0 x89;/*定时器/计数器方式控制寄存器地址89H*/,MCS-51系列单片机的特殊功能寄存器的数量与类型不尽相同,因此建议将所有特殊的sfr定义放入一个头文件中,该文件应包括MCS-51单片机系列机型中的SFR定义。C51编译器的reg51.h头文件就是这样一个文件。在新的MCS-51系列产品中,SFR在功能上经常组合为16位值,当SFR的高字节地址直接位于低字节之后时,对16位SFR的值可以直接进行访问。例如52子系列的定时器/计数器2就是这种情况。为了有效地访问这类SFR,可使用关键字sfr16来定义,其定义语句的语法格式与8位SFR相同,只是=后面的地址必须用16位SFR的低字节地址,即低字节地址作为sfr16的定义地址。,例如:sfr16 T2=0 xCC/*定时器/计数器2:T2低8位地址为0CCH,T2高8位地址为0CDH*/这种定义适用于所有新的16位SFR,但不能用于定时器/计数器0和1。对于位寻址的SFR中的位,C51的扩充功能支持特殊位的定义,像SFR一样不与标准C兼容,使用sbit来定义位寻址单元。,第一种格式:sbit bit-name=sfr-nameint constant;sbit是定义语句的关键字,后跟一个寻址位符号名(该位符号名必须是MCS-51单片机中规定的位名称),=后的sfr-name必须是已定义过的SFR的名字,后的整常数是寻址位在特殊功能寄存器sfr-name中的位号,必须是07范围中的数。例如:sfr PSW=0 xD0;/*定义PSW寄存器地址为D0H*/sbit OV=PSW2;/*定义OV位为PSW.2,地址为D2H*/sbit CY=PSW7;/*定义CY位为PSW.7,地址为D7H*/,第二种格式:sbit bit-name=int constantint constant;=后的int constant为寻址地址位所在的特殊功能寄存器的字节地址,符号后的int constant为寻址位在特殊功能寄存器中的位号。例如:sbit OV=0XD02;/*定义OV位地址是D0H字节中的第2位*/sbit CY=0XD07;/*定义CY位地址是D0H字节中的第7位*/,第三种格式:sbit bit-name=int constant;=后的int constant为寻址位的绝对位地址。例如:sbit OV=0XD2;/*定义OV位地址为D2H*/sbit CY=0XD7;/*定义CY位地址为D7H*/特殊功能位代表了一个独立的定义类,不能与其它位定义和位域互换。,7.5 MCS-51并行接口的C51定义,MCS-51系列单片机并行I/O接口除了芯片上的4个I/O口(P0 P3)外,还可以在片外扩展I/O口。MCS-51单片机I/O口与数据存储器统一编址,即把一个I/O口当作数据存储器中的一个单元来看待。使用C51进行编程时,MCS-51片内的I/O口与片外扩展的I/O可以统一在一个头文件中定义,也可以在程序中(一般在开始的位置)进行定义,其定义方法如下:对于MCS-51片内I/O口按特殊功能寄存器方法定义。例如:sfr P0=0 x80;/*定义P0口,地址为80H*/sfr P1=0 x90;/*定义P1口,地址为90H*/,对于片外扩展I/O口,则根据硬件译码地址,将其视作为片外数据存储器的一个单元,使用#define语句进行定义。例如#include#define PORTA XBYTE 0 xFFC0 absacc.h是C51中绝对地址访问函数的头文件,将PORTA定义为外部I/O口,地址为 FFC0H,长度为8位。一旦在头文件或程序中对这些片外I/O口进行定义后,在程序中就可以自由使用变量名与其实际地址的联系,以便使程序员能用软件模拟MCS-51的硬件操作。,(1)位变量C51定义。使用C51编程时,定义了位变量后,就可以用定义了的变量来表示MCS-51的位寻址单元。位变量的C51定义的一般语法格式如下:位类型标识符(bit)位变量名;例如:bit direction_bit;/*把direction_bit定义为位变量*/bit look_pointer;/*把look_pointer定义为位变量*/,7.6 位变量的C51定义,(2)函数可包含类型为bit的参数,也可以将其作为返回值。例如:bit func(bit b0,bit b1)/*变量b0,b1作为函数的参数*/return(b1);/*变量b1作为函数的返回值*/注意,使用(#pragma disable)或包含明确的寄存器组切换(using n)的函数不能返回位值,否则编辑器将会给出一个错误信息。,(3)对位变量定义的限制。位变量不能定义成一个指针,如不能定义:bit*bit_pointer。不存在位数组,如不能定义:bit b_array。在位定义中,允许定义存储类型,位变量都被放入一个位段,此段总位于MCS-51片内的RAM区中。因此,存储类型限制为data和idata,如果将位变量的存储类型定义成其它存储类型都将编译出错。,例1 先定义变量的数据类型和存储类型:bdata int ibase;/*定义ibase为bdata整型变量*/bdata char bary4;/*bary4定义为bdata字符型数组*/然后可使用sbit定义可独立寻址访问的对象位:sbit mybit0=ibase0;/*mybit0定义为ibase的第0位*/sbit mybit15=ibase15;/*mybit0定义为ibase的第15位*/sbit Ary07=bary07;/*Ary07定义为abry0的第7位*/sbit Ary37=bary37;/*Ary37定义为abry3的第7位*/,对象ibase和bary也可以字节寻址:ary37=0;/*bary3的第7位赋值为0*/bary3=a;/*字节寻址,bary3 赋值为a*/sbit定义要位寻址对象所在字节基址对象的存储类型为bdata,否则只有绝对的特殊位定义(sbit)是合法的。操作符后的最大值依赖于指定的基类型,对于char/uchar而言是07,对于int/uint而言是015,对于long/ulong而言是031。,7.7 C51构造数据类型,1基于存储器的指针 基于存储器的指针以存储器类型为参量,它在编译时才被确定。因此,为指针选择存储器的方法可以省掉,以便这些指针的长度为一个字节(idata*,data*,pdata*)或2个字节(code*,xdata*)。编译时,这类操作一般被行内(inline)编码,而无需进行库调用。基于存储器的指针定义举例:char xdata*px;,在xdata存储器中定义了一个指向字符型(char)的指针变量px。指针自身在默认存储区(决定于编译模式),长度为2个字节(值为00 xFFFF)。char xdata*data pdx;除了明确定义指针位于MCS-51内部存储区(data)外,其它与上例相同,它与编译模式无关。data char xdata*pdx;,struct time char hour;char min;char sec;struct time xdata*pxtime;在结构struct time中,除了其它结构成员外,还包含有一个具有和struct time相同的指针pxtime,time位于外部数据存储器(xdata),指针pxtime具有两个字节长度。,struct time idata*ptime;这个声明定义了一个位于默认存储器中的指针,它指向结构time,time位于idata存储器中,结构成员可以通过MCS-51的R0或R1 进行间接访问,指针ptime为1个字节长。ptimepxtimehour=12;使用上面的关于struct time和struct idata*ptime的定义,指针pxtime被从结构中间接调用,它指向位于xdata存储器中的time结构。结构成员hour被赋值为12。,2一般指针 一般指针包括3个字节:1个字节存储类型和2个字节偏移地址,即,其中,第一字节代表了指针的存储器类型,存储器类型编码如下:,例如,以xdata类型的0 x1234地址为指针可以表示如下:,当用常数作指针时,必须注意正确定义存储器类型和偏移量。例如,将常数值0 x41写入地址为0 x8000的外部数据存储器。#define XBYTE(char*)0 x20000L)XBYTE0 x8000=0 x41;其中,XBYTE被定义为(char*)0 x20000L,0 x20000L为一般指针,其存储类型为2,偏移量为0000H,这样XBYTE成为指向xdata零地址的指针。而XBYTE8000则是外部数据存储器的0 x8000绝对地址。,7.8 模块化程序开发过程,图7.4 程序开发过程,7.8.1 混合编程,1命名规则,表7.5 函数名的转换,例2 用汇编语言编写函数toupper,参数传递发生在寄存器R7中。UPPER SEGMENT CODE;程序段PUBLIC _TOUPPER;入口地址PSEG UPPER;程序段_TOUPPER:MOV A,R7;从R7中取参数 CJNE A,#a,$+3 JC UPPERET CJNE A,#z+1,$+3 JNC UPPERET CLR ACC,5 UPPERET:MOV R7,A;返回值放在R7中 RET;返回到C,2参数传递规则,表7.6 参数传递的寄存器选择,func1(int a)a是第一个参数,在R6,R7中传递。func2(int b,int c,int*d)b是第一个参数,在R6,R7中传递;c是第二个参数,在R4,R5中传递;d是第三个参数,在R1,R2,R3中传递。func3(long e,long f)e是第一个参数,在R4R7中传递;f是第二个参数,不能在寄存器中传递,只能在参数传递段中传递。func4(float g,char h)g是第一个参数,在R4R7中传递;h是第二个参数,必须在参数传递段中传递。,表7.7 函数返回值的寄存器,在汇编子程序中,当前选择的寄存器组及寄存器ACC、B、DPTR和PSW都可能改变。当被C调用时,必须无条件地假设这些寄存器的内容已被破坏。如果已在连接/定位程序时选择了覆盖,那么每个汇编子程序包含一个单独的程序段是必要的,因为在覆盖过程中,函数间参量通过子程序各自的段参量计算。汇编子程序的数据区甚至可包含在覆盖部分中,但应注意下面两点:(1)所有段名必须以C51类似的方法建立。(2)每个有局部变量的汇编程序必须指定自己的数据段,这个数据段只能为其它函数访问作参数传递用。所有参数一个接一个被传递,由其它函数计算的结果保存入栈。,7.8.2 覆盖和共享,1覆盖 单片机片内存储空间有限,连接器/定位器通常重新启用程序不再用的位置。这就是说,若一个程序不再调用,也不由其它程序调用(甚至间接调用),那么在其它程序执行完之前,这个程序不再运行。这个程序的变量可以放在与其它程序完全相同的RAM空间,很像可重用的寄存器。这种技术就是覆盖。在汇编中直接通过手工完成的这些空间分配,C语言中可以由连接器自动管理。若有几个不相关联的程序时,它可以使RAM单元比手工考虑要用的少。,2共享1)共享变量,2)共享函数/子程序 C中函数若是全局的(公用的),可以放在调用的函数之后。若函数是模块专用的,它可以定义为静态函数,这样它不能被其它模块调用。C语言的ANSI标准建议所有函数在主函数前要有原型(进行说明),然后实际函数可在主函数之后或其它模块中。这符合自顶向下编程的概念。汇编语言中,子程序使用标号可在给定模块的任何位置。汇编器首先扫描得到所有的符号名,然后值就可填入LCALL或LJMP。一个模块或另一模块共享子程序,一个使用PUBLIC而另一个使用EXTERN。当指定为EXTERN,符号类型(CODE,DATA,XDATA,IDATA,BIT或NUMBER)必须特别加以指定,以便连接器可以确定放在一起的正确类型。,7.8.3 库和连接器/定位器 1.库,表7.9 Franklin C51的编译库,2连接器/定位器 1)组合程序模块 将几个不同程序模块组合为一个模块,并自动从库中挑选模块嵌入目标文件。输入文件按命令行中出现的顺序处理。通常的程序模块是由C51编译器或A51宏汇编生成的可重入的目标文件。,2)组合段 将具有相同段名的可重定位段组合成单一的段。在一个程序模块中定义的一个段成为部分段。一个部分段在源文件中以下列形式指定:(1)名字 每个重定位段有一个名字,它可与来自其它模块的同名的可重定位段组合。绝对段没有名字。(2)类型 类型表明段所属的地址空间CODE,XDATA,DATA或BIT。,(3)定位方式 可重定位段的定位方式有PAGE,INPAGE,INBLOCK,BITADD RESSABLE或UNIT。INPAGE表明段必须放入一页(高8位地址相同)中以使用短转移和调用指令。INBLOCK段应使用ACALL,必须放在2048字节块中。因为没有连接器可以灵活地判知调用和转移是否在块内。可重定位的其它限制是:PAGE-不能超过256字节;BITADDRESSABLE-必须放在可位寻址的内部RAM空间;UNIT-允许段从任意字节开始(对位变量是位)。(4)长度 一个段的长度。,(5)基址 段的首址。对于绝对段,地址由汇编器赋予,对于可重定位段,地址由L51决定。在处理程序模块时,L51自动产生段表(MAP),该表包含了每个段的类型、基址、长度、可重定位性和名字。L51自动将所具有相同名字的所有部分段组合到单一可重定位段中。例如,三个程序模块包含字段VAR,在组合时,三个段的长度相加,从而组合段的长度也增加了。对组合段有下列规则:所有具有相同名的部分段必须有相同类型(CODE,DATA,IDATA,XDATA或BIT)。组合段的长度不能超过存储区的物理长度。每个组合的部分段的定位方法也必须相同。绝对段相互不组合,它们被直接拷贝到输出文件。,3)存储器分配,表7.10 MCS-51系列的物理存储区,4)采用覆盖技术使用数据存储器 通过采用一定的覆盖技术,MCS-51系列少量的片内数据存储器可由L51有效地使用。由C51编译器或是A51汇编器生成的参数和局部变量(若使用它们的函数不相互调用)可在存储器中覆盖。这样,所用的存储器得到相当程度地减少。为完成数据覆盖,L51分析所有不同函数间的调用,使用该信息可以确定哪个数据和位段可被覆盖。使用控制参数OVERLAY和NOOVERLAY可允许或禁止覆盖。OVERLAY是默认值,用它可产生非常紧凑的数据区。,5)决定外部参考地址 具有相同名的外部符号(EXTERN)和公用符号(PUBLIC)被确定后,外部符号指向其它模块中的地址。一个已声明的外部符号用具有相同名字的功用符号确定,外部参考地址由其公共参考地址确定。这还与类型(DATA,IDATA,XDATA,CODE,BIT或NUMBER)有关,如果类型不符或未发现外部符号参考地址的公用符号,则会产生错误。公用符号的绝对地址在段定位后决定。,6)绝对地址计算 定义绝对地址并计算可重定位段的地址。在段分配和外部公用参考地址处理完后,程序模块中所有可重定位地址和外部地址要进行计算,此时生成的目标文件中的符号信息(DEBUG)被改变以反映新的值。,7)产生绝对目标文件 可执行程序以绝对目标格式产生。该绝对目标文件可包含附加的符号信息(DEBUG),从而使符号调试成为可能。符号信息可用参数NODEBUGSYMBOLS,NODEBUGPUBLICS和NODEBUGLINES禁止。输出文件是可执行的,并可由仿真器装入调试或被OHS51翻译为Intel HEX格式文件以供EPROM固化。,8)产生映像文件 产生一个反映每个处理步骤的映像文件,它显示有关连接/定位过程的信息和程序符号,并包含一个公用和外部符号的交叉参考报告。映像文件包含下列信息:文件名和命令行参数。模块的文件名和模块名。一个包含段地址、类型、定位方法和名字的存储器分配表。该表可在命令行中用NOMAP参考禁止。,段和符号的所有错误列表。列表文件末尾显示出所有出错的原因。一个包含输入文件中符号信息的符号表。该信息由MODULES,SYMBOLS,PUBLICS和LINES名组成,LINES是C编译器产生的行号。符号信息可用参数NOSYMBOLS,NOPUBLICS和NOLINES完全或部分禁止。一个按字母顺序排列的有关所有PUBLIC和EXTERN符号的交叉参考报告,其中显示出符号类型和模块名。第一个显示的模块名是定义了PUBLIC符号的模块,后面的模块名是定义了EXTERN符号的模块。在命令行输入参数IXREF可产生此报告。在连接器/定位器运行期间检测到的错误同时显示在屏幕和文件尾部。,7.8.4 程序优化,以下选择对提高程序效率有很大影响:(1)尽量选择小存储模式以避免使用MOVX指令。(2)使用大模式(COMPACT/LARGE)应仔细考虑要放在内部数据存储器的变量要求是经常用的或是用于中间结果的。访问内部数据存储器要比访问外部数据存储器快得多。内部RAM由寄存器组、位数据区和其它用户用“data”类型定义的变量共享。由于内部RAM容量的限制(128256字节,由使用的单片机决定),必须权衡利弊以解决访问效率和这些对象的数量之间的矛盾。,(3)要考虑操作顺序,完成一件事后再做一件事。(4)注意程序编写细则。例如,若使用for(;)循环,DJNZ指令比CJNE指令更有效,可减少重复循环次数。(5)若编译器不能使用左移和右移完成乘除法,应立即修改,例如,左移为乘2。(6)用逻辑AND/&取模比用MOD/%操作更有效。(7)因计算机基于二进制,仔细选择数据存储器和数组大小可节省操作。,(8)尽可能使用最小的数据类型,MCS-51系列是8位机,显然对具有char类型的对象的操作比int或long类型的对象的操作要方便得多。(9)尽可能使用unsigned数据类型。MCS-51系列CPU并不直接支持有符号数的运算。因而C51编译器必须产生与之相关的更多的程序代码以解决这个问题。(10)尽可能使用局部函数变量。编译器总是尝试在寄存器里保持局部变量。这样,将循环变量(如for和while循环中的计数变量)说明为局部变量是最好的。使用unsigned char/int的对象通常能获得最好的结果。,7.9 MCS-51内部资源使用的C语言编程,7.9.1 中断应用的C语言编程 C51编译器支持在C源程序中直接开发中断程序。中断服务程序是通过按规定语法格式定义的一个函数。中断服务程序的函数定义的语法格式如下:返回值 函数名(参数)interrupt musing n,表7.11 MCS-51中断源编号,using n 选项用于实现工作寄存器组的切换,n是中断服务子程序中选用的工作寄存器组号(0 3)。在许多情况下,响应中断时需保护有关现场信息,以便中断返回后,能使中断前的源程序从断点处继续正确地执行下去。这在MCS-51单片机中,能很方便地利用工作寄存器组的切换来实现。即在进入中断服务程序前的程序中使用一组工作寄存器,进入中断服务程序后,由using n切换到另一组寄存器,中断返回后又恢复到原寄存器组。这样互相切换的两组寄存器中的内容彼此都没有被破坏。,图 7.5 扩展多个中断源,在中断服务程序中仅设置标志,并保存I/O口输入状态。Franklin C51编译器提供定义特定MCS-51系列成员的寄存器头文件。MCS-51头文件为reg51.h。C51程序如下:,#include unsigned char status;bit flag;void service_int1()interrupt 2 using 2/*INT1中断服务程序,使用第2组工作寄存器*/flag=1;/*设置标志*/status=p1;/*存输入口状态*/void main(void)IP=0 x04;/*置INT1为高优先级中断*/IE=-0 x84;/*INT1开中断,CPU开中断*/,for(;)if(flag)/*有中断*/switch(status)/*根据中断源分支*/case 0:break;/*处理IN0*/case 1:break;/*处理IN1*/case 2:break;/*处理IN2*/case 3:break;/*处理IN3*/default:;flag=0;/*处理完成清标志*/,7.9.2 定时器/计数器(T/C)应用的C语言编程,例4 设单片机的fosc=12 MHz晶振,要求在P1.0脚上输出周期为2 ms的方波。周期为2 ms的方波要求定时时间隔1 ms,每次时间到P1.0取反。机器周期=12/fosc=1 s 需计数次数=1000/(12/fosc)=1000/1=1000 由于计数器是加1计数,为得到1000个计数之后的定时器溢出,必须给定时器置初值为-1000(即1000的补数)。,(1)用定时器0的方式1编程,采用查询方式,程序如下:#include sbit P1_0=P10;void main(void)TMOD=0 x01;/*设置定时器1为非门控制方式1*/TR0=1;/*启动 T/C0*/for(;)TH0=-(1000/256);/*装载计数器初值*/TL0=-(1000%256);do while(!TF0);/*查询等待TF0置位*/P1_0=!P1_0;/*定时时间到P1.0反相*/TF0=0;/*软件清 TF0*/,(2)用定时器0的方式1编程,采用中断方式。程序如下:#include sbit P1_0=P10;void time(void)interrupt 1 using 1/*T/C0中断服务程序入口*/P1_0=!P1_0;/*P1.0取反*/TH0=-(1000/256);/*重新装载计数初值*/void main(void)TMOD=0 x01;/*T/C0工作在定时器非门控制方式1*/P1_0=0;TH0=-(1000/256);/*预置计数初值*/TL0=-(1000%256);EA=1;/*CPU中断开放*/ET0=1;/*T/C0中断开放*/TR0=1;/*启动T/C0开始定时*/do while(1);/*等待中断*/,例5 采用10 MHz晶振,在P1.0脚上输出周期为2.5 s,占空比20%的脉冲信号。10 MHz晶振,使用定时器最大定时几十毫秒。取10 ms定时,周期2.5 s需250次中断,占空比20%,高电平应为50次中断。10 ms定时,晶振fosc=10 MHz。需定时器计数次数=1010310/12=8333,#include#define uchar unsigned charuchar period=250;uchar high=50;,timer0()interrupt 1 using 1/*T/C0中断服务程序*/TH0=-8333/256;/*重置计数值*/TL0=-8333%256;if(+time=high)P1=0;/*高电平时间到变低*/else if(time=period)/*周期时间到变高*/time=0;P1=1;,main()TMOD=0 x01;/*定时器0方式1*/TH0=-8333/256;/*预置计数初值*/TL0=-8333%256;EA=1;/*开CPU中断*/ET0=1;/*开T/C0中断*/TR0=1;/*启动T/C0*/do while(1);,图7.6 中断服务程序流程图,产生一个占空比变化脉冲信号的程序,它产生的脉宽调制信号用于电机变速控制。,#include#define uchar unsigned char#define uint unsigned int unchar time,status,percent,period;bit one_round;uint oldcount,target=500;void pulse(void)interrupt 1 using 1/*T/C0中断服务程序*/TH0=-833/256;/*1ms-10 MHz*/,TL0=-833%256;ET0=1;if(+time=percent)P1=0;else if(time=100)time=0;P1=1;void tachmeter(void)interrupt 2 using 2/*外中断1服务程序*/union unit word;struct uchar hi;uchar lo;byte;,newcount;newcount.byte.hi=TH1;newcount.byte.lo=TL1;period=newcount.word-oldcount;/*测得周期*/oldcount=newcount.word;one_round=1;/*每转一圈,引起中断,设置标志*/void main(void)IP=0 x04;/*置INT1为高位优先级*/TMOD=0 x11;/*T0,T1 16位方式*/TCON=0 x54;/*T0,T1运行,IT1边沿触发*/,TH1=0;TL1=0;/*设置初始计数值*/IE=0 x86;/*允许中断EX1,ET0*/for(;)if(one_round)/*每转一圈,调整*/if(period 0)-percent;/*占空比减*/one_round=0;,7.9.3 串行口使用的C语言编程,例6 单片机fosc=11.0592 MHz,波特率为9600,各设置32字节的队列缓冲区用于发送接收。设计单片机和终端或另一计算机通信的程序。单片机串行口初始化成9600波特,中断程序双向处理字符,程序双向缓冲字符。背景程序可以放入和提取在缓冲区的字符串,而实际传入和传出SBUF的动作由中断完成。Loadmsg 函数加载缓冲数组,标志发送开始。缓冲区分发(t)和收(r)缓冲,缓冲区通过两种指示(进in和出out)和一些标志(满full,空empty,完成done)管理。队列缓冲区32字节接收缓冲(r_buf)区满,不能再有字符插入。当t_in=t_out,发送缓冲区(t_buf)空,发送中断清除,停止UART请求。具体程序如下:,#include#define uchar unsigned char uchar xdata r_buf32;/*item1*/uchar xdata t_buf32;uchar r_in,r_out,t_in,t_done;/*队列指针*/bit r_full,t_empty,t_done;/*item2*/code uchar m=this is a test program rn;serial()interrupt 4 using 1/*item3*/if(RI&r_full)r_bufr_in=SBUF;RI=0;,r_in=+r_in&ox1f;if(r_in=r_out)r_full=1;else if(TI&t_empty)SBUF=t_buf t_out;TI=0;t_out=+t_out&0 x1f;i f(t_out=t_in)t_empty=1;else if(TI)TI=0;t_done=1;,void loadmsg(uchar code*msg)/*item4*/while(*msg!=0)&(t_in+1)t_out)&0 x1f)!=0)/*测试缓冲区满*/t_ buf t_in=*msg;msg+;t_in=+t_in&0 x1f;if(t_done)TI=1;t_empty=t_done=0;/*完成重新开始*/,void process(uchar ch)return;/*item5*/*用户定义*/void processmsg(void)/*item6*/while(r_out+1)r_in)!=0)/*接收非缓冲区*/process(r_buf r_out);r_out=+r_out&0 x1f;,main()/*item7*/TMOD=0 x20;/*定时器1方式2*/TH1=0 xfd;/*9600波特11.0592 MHz*/TCON=0 x40;/*启动定时器1*/SCON=0 x50;/*允许接收*/IE=0 x90;/*允许串行口中断*/t_empty=t_done=1;r_full=0;r_out=t_in=0;r_ in=1;/*接收缓冲和发送缓冲置空*/for(;)loadmsg(&m);processmsg();,item1:背景程序放入和提取字符队列缓冲区。item2:缓冲区状态标志。item3:串行口中断服务程序,从RI,TI判别接收或发送中断,由软件清除。判别缓冲区状态(满full,空empty)和全部发送完成(done)。ite