第3章 KeilC语言及其程序设计21.docx
《第3章 KeilC语言及其程序设计21.docx》由会员分享,可在线阅读,更多相关《第3章 KeilC语言及其程序设计21.docx(17页珍藏版)》请在冰豆网上搜索。
第3章KeilC语言及其程序设计21
(3)片外程序存储器
code:
外部程序存储器的64KB空间。
程序存储区用来存放程序代码、数据及表格(数据及表格中的数据是不变的)。
程序的代码(CODE)存储区是只读的,不能写入。
硬件决定最多可能有64KB的程序存储区。
【因为是16位】
用code标识符来访问片内、片外统一编址的程序存储区,寻址范围为0~65535。
对单片机编程,正确地定义数据类型以及存储类型,是所有编程者在编程前都需要首先考虑的问题。
在资源有限的条件下,如何节省存储单元并保证运行效率,是对开发者的一个考验。
只有对C51中的各种数据类型以及存储类型非常熟练的掌握,才能运用自如。
定义变量类型应考虑如下问题:
程序运行时该变量可能的取值范围,是否有负值,绝对值有多大,以及相应需要的存储空间大小。
在够用的情况下,尽量选择8位即一个字节的char型,特别是unsigedchar。
对于51系列这样的定点机而言,浮点类型变量将明显增加运算时间和程序长度,如果可以的话,尽量使用灵活巧妙的算法来避免浮点变量的引入。
定义数据的存储类型通常遵循如下原则:
只要条件满足,尽量选择内部直接寻址的存储类型data,然后选择idata即内部间接寻址。
对于那些经常使用的变量要使用内部寻址。
在内部数据存储器数量有限或不能满足要求的情况下才使用外部数据存储器。
选择外部数据存储器可先选择pdata类型,最后选用xdata类型。
需指出,扩展片外存储器,原理上虽很简单,但在实际开发中,很多时候,会带来不必要的麻烦,如可能降低系统稳定性、增加成本、拉长开发和调试周期等,推荐充分利用片内存储空间。
另外,通常的单片机应用都是面对小型的控制,代码比较短,对于程序存储区的大小要求很低,常常是片内RAM很紧张而片内FlashROM很富裕,因此如果实时性要求不高,可考虑将一些子函数的常量数据做成数据表,放置在程序存储区,当程序运行时,进入子函数动态调用下载至RAM即可,退出子函数后立即释放该内存空间。
3.2.3一个简单的C51程序
一个C51源程序是由一个个模块化的函数所构成,函数是指程序中的一个模块,main()函数为程序的主函数,其他若干个函数可以理解为一些子程序。
一个C51源程序无论包含了多少函数,它总是从main()函数开始执行,不论main()函数位于程序的什么位置。
程序设计者就是编写一系列的函数模块,并在需要的时候调用这个函数,实现程序所要求的功能。
1.C51程序与函数
下面通过一个简单C51程序,认识C51程序与函数。
【例3-1】在AT89S51的P1.0脚接有一只发光二极管,二极管的阴极接P1.0脚,阳极通过限流电阻接+5V,现在让发光二极管每隔800ms闪灭,占空比为50%。
已知单片机时钟晶振为12MHz,即每个机器周期1μs,采用软件延时的方法,参考程序如下:
#include//包含reg51.h头文件
sbitP10=P1^0;//定义位变量P1.0,也可使用sbitP10=0x90
voiddelay(unsignedintcount)//延时函数Delay(),count是形式参数
{//两个花括号之间为函数Delay()的函数体
unsignedinti,j;//定义变量i,j
for(i=0;i{//在时钟频率为12MHz时,循环120次,大约为1msfor(j=0;j<120;j++)//如果j<120,则j加1}//unsignedcharj;可以节约一个单元空间}voidmain(void)//主函数main(){while(1)//主程序轮询{P10=1;//P1.0输出高电平,发光二极管灭delay(800);//将实际参数800传递给形式参数i,延时800msP10=0;//P1.0输出低电平,发光二极管亮delay(800)//将实际参数800传递给形式参数i,延时800ms}}【】voiddelay(unsignedintcount)改为voiddelay(unsignedcharcount)否?下面对程序进行简要说明。程序的第1行是“文件包含”,是将另一个文件“reg51.h”的内容全部包含进来。文件“reg51.h”包含了51单片机全部的特殊功能寄存器的字节地址及大多数可寻址位的位地址定义。程序包含reg51.h的目的就是为了使用P1这个符号,即通知程序中所写的P1是指AT89S51的P1端口,而不是其他变量。打开reg51.h文件可以看到“sfrP1=0x90;”,即定义符号P1与地址0x90对应,而P1口的地址就是0x90。虽然这里的“文件包含”只有一行,但C编译器在处理的时候却要处理几十行或几百行。程序的第2行用符号P10来表示P1.0引脚。在C51中,如果直接写“P1.0”编译器并不能识别,而且P1.0也不是一个合法的C51语言程序变量名【因为在reg51.h中,没有定义,需要用户自己定义】,所以必须给它起一个另外的名字,这里起的名字是P10,可是P10是否就是P1.0呢,所以必须给它们建立联系,这里使用了C51的关键字“sbit”来进行定义。第3行~第8行对函数delay进行了事先定义,只有这样,才能在主程序中被主函数main()调用。自行编写的函数delay()的用途是软件延时,调用时使用的这个“800”被称为“实际参数”,以延时800ms的时间。注意,若delay()的定义写在main函数的后面,则需要先作出声明,否则编译无法通过,因为编译到main函数中的delay()语句时,找不到相应的函数体。main为“主函数”,每一个C语言程序有且只有一个主函数,主函数后面一定有一对花括号“{}”,在花括号里面书写该函数的代码行。2.用户自定义函数与库函数用户自定义函数用户根据自己需要所编写的函数。如例3-1中的delay函数。编写时,需要注意以下几点。函数的首部(函数的第1行),包括函数名、函数类型、函数属性、函数参数(形式参数)名、参数类型。例如:voidDelay(unsignedinti)函数体,即函数首部下面的花括号“{}”内的部分。如果一个函数体内有多个花括号,则最外层的一对“{}”为函数体的范围。C51区分大小写,例如Delay与delay,编译时是不同的两个名称。每个语句最后必须有一个分号,分号是C语句的必要组成部分。从函数的定义的形式上划分可以有三种形式:无参数函数、有参数函数和空函数。(1)无参数函数此种函数在被调用时,既无参数输入,也不返回结果给调用函数,只是为完成某种操作而编写的。(2)有参数函数调用此种函数时,必须提供实际的输入函数,必须说明与实际参数一一对应的形式参数,并在函数结束时返回结果,供调用它的函数使用。(3)空函数函数体内无语句,是空白的。调用空函数时,什么工作也不做,不起任何作用。定义空函数的目的,是为以后程序功能的扩充。程序最初设计时,往往只涉及最基本的功能模块的函数,其他模块的功能函数可以在以后补上。因此先将非基本模块的功能函数定义成空函数,用一个空语句“;”占好位置,并写好注释,以后再用一个编好的函数代替它。3.函数调用程序设计者的任务就是编写一系列的用户自定义函数模块,并在需要的时候调用这些函数以及库函数,实现程序所要求的功能。调用格式为:函数名(实际参数1,实际参数2,…)例如,例3-1中主函数main()里的子函数调用语句“delay(800);”,其中800为实际参数。3.2.4C51的运算符在程序中实现运算,要熟悉常用的运算符。本节对C51中用到的标准C运算符进行回顾,为C51的程序设计打下基础。1.算术运算符如表3-3所示。表3-3算术运算符及其说明符号说明+加法运算-减法运算*乘法运算/除法运算【商】%取模运算【余数】++自增1――自减1对于“/”和“%”这两个符号都涉及除法运算,但“/”运算是取商,而“%”运算为取余数。例如“5/3”的结果(商)为1,而“5%3”的结果为2(余数)。表3-3中的自增和自减运算符是使变量自动加1或减1,自增和自减运算符放在变量前和变量之后是不同的。++i,--i:在使用i之前,先使i值加(减)1。i++,i--:在使用i之后,再使i值加(减)1。例如:若i=4,则执行x=++i时,先使i加1,再引用结果,即x=5,运算结果为i=5,x=5。再如:若i=4,则执行x=i++时,先引用i值,即x=4,再使i加1,运算结果为i=5,x=4。2.逻辑运算符逻辑运算符及其说明如表3-4所示。表3-4逻辑运算符及其说明符号说明&&逻辑与||逻辑或!逻辑非3.关系运算符判断两个数之间的关系。关系运算符及其说明如表3-5。表3-5关系运算符及其说明符号说明>大于<小于>=大于或等于<=小于或等于==等于!=不等于4.位运算C51中的位运算符及其说明如表3-6所示。表3-6位运算其说明符号说明&按位与Ι按位或^按位异或~按位取反<<左移>>右移【例】设a=0x54=01010100B,b=0x3b=00111011B,则a&b、a|b、a^b、~a、a<<2、b>>2分别为多少?a=0x54=01010100Bb=0x3b=00111011Ba&b=00010000b=0x10a|b=01111111B=0x7fa^b=01101111B=0x6f~a=10101011B=0xaba<<2=01010000B=0x50【a<<=2】b>>2=00001110B=0x0e【b>>=2】5复合赋值运算符C51语言中支持在赋值运算符“=”的前面加上其它运算符,组成复合赋值运算符。下面是C51中支持的复合赋值运算符:+=加法赋值-+减法赋值*=乘法赋值/=除法赋值%=取模赋值&=逻辑与赋值|=逻辑或赋值^=逻辑异或赋值~=逻辑非赋值>>=右移位赋值<<=左移位赋值复合赋值运算的一般格式如下:变量复合运算赋值符表达式它的处理过程:先把变量与后面的表达式进行某种运算,然后将运算的结果赋给前面的变量。其实这是C51语言中简化程序的一种方法,大多数二目运算都可以用复合赋值运算符简化表示。例如:a+=6相当于a=a+6;a*=5相当于a=a*5;b&=0x55相当于b=b&0x55;x>>=2相当于x=x>>2。3.2.5C51的分支与循环程序结构程序结构上可把程序分为三类,即顺序、分支和循环结构。顺序结构是程序的基本结构,程序自上而下,从main()的函数开始一直到程序运行结束,程序只有一条路可走,没有其他的路径可以选择。顺序结构比较简单和便于理解,这里介绍分支结构和循环结构。1.分支结构程序(1)只有两条分支的时候用If(条件){分支1}else{分支2}(2)分支较多时在分支较多时的情况下使用switch语句。switch(){case():语句;break;case():语句;break;…………default:语句;break;}注意:每个switch分支必须有一个break语句,否则程序并不能跳出switch,就会继续执行case后面的case语句。2.循环结构程序循环语句有以下三种。(1)for循环格式为:for(循环体初始化;循环体执行条件;循环体执行后操作){循环体}花括号{}中为循环体内容。(2)while循环格式为:while(循环体执行条件){花括号{}中为循环体内容。}(3)dowhile循环格式为:do{}花括号{}中为循环体内容while(循环体执行条件)前两种循环是先判断循环条件是否满足,才决定循环体是否执行;而“dowhile循环”是在执行完循环体后再判断条件是否满足,再决定循环体是否继续执行。三种循环中,经常使用的是for语句和while语句。下面来说明for语句的应用。【例】求1到100之间整数的和程序如下:#include#includemain(){unsignedintnVar1,nsum;//unsignedcharnsum;unsignedintnVar1;能节省一个单元for(nVar1=0,nSum=1;nSum<=100;nSum++)nVar1+=nSum;/*累加求和*/while(1);}关于循环,需说明的是,在无操作系统的控制器和处理器上运行的程序,主体通常采用轮询方式,即把所有的操作包含在一个while(1){}中,如例3-1。这样的无限循环在面向通用计算机的软件设计中是不被允许的,然而嵌入式系统软件设计中,则由于其硬件构成和使用需求,常常采用这种无限循环。3.2.6绝对地址访问使用C51运行库中预定义宏C51编译器提供了一组宏定义来对51系列单片机的code、data、pdata和xdata空间进行绝对寻址。规定只能以无符号数方式访问,定义了8个宏定义,其函数原型如下:#defineCBYTE((unsignedcharvolatilecode*)0)#defineDBYTE((unsignedcharvolatiledata*)0)#definePBYTE((unsignedcharvolatilepdata*)0)#defineXBYTE((unsignedcharvolatilexdata*)0)#defineCWORD((unsignedintvolatilecode*)0)#defineDWORD((unsignedintvolatiledata*)0)#definePWORD((unsignedintvolatilepdata*)0)#defineXWORD((unsignedintvolatilexdata*)0)这些函数原型放在absacc.h文件中。使用时须用预处理命令把该头文件包含到文件中,形式为:#include。其中:CBYTE以字节形式对code区寻址,DBYTE以字节形式对data区寻址,PBYTE以字节形式对pdata区寻址,XBYTE以字节形式对xdata区寻址,CWORD以字形式对code区寻址,DWORD以字形式对data区寻址,PWORD以字形式对pdata区寻址,XWORD以字形式对xdata区寻址。访问形式如下:宏名[地址]宏名为CBYTE、DBYTE、PBYTE、XBYTE、CWORD、DWORD、PWORD或XWORD。地址为存储单元的绝对地址,一般用十六进制形式表示。【例4-7】绝对地址对存储单元的访问。#include//将绝对地址头文件包含在文件中#include//将寄存器头文件包含在文件中#defineucharunsignedchar//定义符号uchar为数据类型符unsignedchar#defineuintunsignedint//定义符号uint为数据类型符unsignedintvoidmain(void){ucharvar1;uintvar2;var1=XBYTE[0x0005];//XBYTE[0x0005]访问片外RAM的0005字节单元var2=XWORD[0x0002];//XWORD[0x0002]访问片外RAM的0002字单元xval=XBYTE[0x0002];//把外部存储区地址0x0002的数据存入变量xval中XWORD[0x0002]=0x2000;//把0x2000送到外部存储区地址为0x0002的单元#defineDAC0832XBYTE[0x7fff]//定义DAC0832的端口地址DAC0832=0x80;//启动一次D/A转换......while(1);}在上面程序中,其中XBYTE[0x0005]就是以绝对地址方式访问的片外RAM0005字节单元;XWORD[0x0002]就是以绝对地址方式访问的片外RAM0002字单元。3.2.7使用C51扩展关键字_at_使用_at_对指定的存储器空间的绝对地址进行访问,一般格式如下:[存储器类型]数据类型说明符变量名_at_地址常数;其中,存储器类型为data、bdata、idata、pdata等C51能识别的数据类型,如省略则按存储模式规定的默认存储器类型确定变量的存储器区域;数据类型为C51支持的数据类型。地址常数用于指定变量的绝对地址,必须位于有效的存储器空间之内;使用_at_定义的变量必须为全局变量。【例】通过_at_实现绝对地址的访问。#defineucharunsignedchar//定义符号uchar为数据类型符unsignedchar#defineuintunsignedint//定义符号uint为数据类型符unsignedintvoidmain(void){dataucharx1_at_0x40;//在data区中定义字节变量x1,它的地址为40Hxdatauintx2_at_0x2000;//在xdata区中定义字变量x2,它的地址为2000Hx1=0xff;x2=0x1234;......while(1);}3.2.7C51中断服务函数的定义由于标准C没有处理单片机中断的定义,为直接编写中断服务程序,C51编译器对函数的定义进行了扩展,增加了一个扩展关键字interrupt,使用该关键字可以将一个函数定义成中断服务程序。由于C51编译器在编译时对声明为中断服务程序的函数自动添加了相应的现场保护、阻断其他中断、返回时恢复现场等处理的程序段,因而在编写中断服务函数时可不必考虑这些问题,减轻了用汇编语言编写中断服务程序的繁琐程度,而把精力放在如何处理引发中断请求的事件上。中断服务函数的一般形式为:void函数名(void)interruptn[usingn]在函数声明时,用“interruptn”语句,可以把所声明的函数定义为一个中断服务程序。从定义中可以看出,中断函数必须是无参数、无返回值的函数。关键字interrupt后面的n是中断号,对于AT89S51,取值为0~4,编译器从8×n+3处产生中断向量。AT89S51中断源对应的中断号和中断向量见表3-3。表3-3中断号n和中断向量中断号n中断源中断向量(8×n+3)0外部中断00003H1定时器0中断000BH2外部中断10013H3定时器1中断001BH4串行口中断0023H其他值(5~31)预留(8×n+3) AT89S51在内部RAM中有4个工作寄存器区,每个寄存器区包含8个工作寄存器(R0-R7)。C51扩展了一个关键字using,专门用来选择AT89S51的4个不同的工作寄存器区。在定义一个函数时,using是一个选项,如果不选用该项,则由编译器选择一个寄存器区作为绝对寄存器区访问。例如:unsignedintinterruptcnt;unsignedcharsecond;voidtimer0(void)interrupt1using2//定时0中断服务程序{if(++interruptcnt==4000){second++;Interruptcnt=0;}} 关键字using对函数目标代码的影响:在中断函数的入口处将当前工作寄存器区内容保护到堆栈中,函数返回前将被保护的寄存器区的内容从堆栈中恢复。使用关键字using在函数中确定一个工作寄存器区时必须小心,要保证工作寄存器区切换都只在指定的控制区域中发生,否则将产生不正确的函数结果。还要注意,带using属性的函数原则上不能返回bit类型的值,且关键字using和关键字interrupt都不允许用于外部函数,另外也都不允许有一个带运算符的表达式。例如,外中断1(/int1)的中断服务函数书写如下:voidint1(void)interrupt2using0//中断号n=2,选择0区工作寄存器区编写AT89S51中断程序时,应遵循以下规则:(1)中断函数没有返回值,如果定义了一个返回值,将会得到不正确的结果。因此建议在定义中断函数时,将其定义为void类型,以明确说明没有返回值。(2)中断函数不能进行参数传递,如果中断函数中包含任何参数声明都将导致编译出错。(3)在任何情况下都不能直接调用中断函数,否则会产生编译错误。(4)如果在中断函数中再调用其他函数,则被调用的函数所使用的寄存器区必须与中断函数使用的寄存器区不同。(5)C51编译器对中断函数编译时会自动在程序开始和结束处加上相应的内容,具体如下:在程序开始处对ACC、B、DPH、DPL和PSW入栈,结束时出栈。中断函数未加usingn修饰符的,开始时还要将R0~R1入栈,结束时出栈。如中断函数加usingn修饰符,则在开始将PSW入栈后还要修改PSW中的工作寄存器组选择位。(6)C51编译器从绝对地址8m+3处产生一个中断向量,其中m为中断号,也即interrupt后面的数字。该向量包含一个到中断函数入口地址的绝对跳转。(7)中断函数最好写在文件的尾部,并且禁止使用extern存储类型说明。防止其它程序调用。3.4C51的集成开发环境KeilµVision3介绍C51程序开发是在KeilµVision3开发环境下进行,首先介绍该开发环境。3.4.1集成开发环境KeilµVision3简介KeilSoftware公司推出的KeilµVision3是一款基于Windows的软件平台,它是一种用于51单片机的集成开发环境(IDE—IntergratedDevelopmentEviroment)。µVision3提供了对基于8051内核的各种型号单片机的支持,完全兼容先前的KeilµVision2版本。目前当前较新的版本为KeilC51V8.08a。开发者可购买KeilµVision3软件,也可到Keilsoftware公
{//在时钟频率为12MHz时,循环120次,大约为1ms
for(j=0;j<120;j++)//如果j<120,则j加1
}//unsignedcharj;可以节约一个单元空间
}
voidmain(void)//主函数main()
{
while
(1)//主程序轮询
P10=1;//P1.0输出高电平,发光二极管灭
delay(800);//将实际参数800传递给形式参数i,延时800ms
P10=0;//P1.0输出低电平,发光二极管亮
delay(800)//将实际参数800传递给形式参数i,延时800ms
【】voiddelay(unsignedintcount)改为voiddelay(unsignedcharcount)否?
下面对程序进行简要说明。
程序的第1行是“文件包含”,是将另一个文件“reg51.h”的内容全部包含进来。
文件“reg51.h”包含了51单片机全部的特殊功能寄存器的字节地址及大多数可寻址位的位地址定义。
程序包含reg51.h的目的就是为了使用P1这个符号,即通知程序中所写的P1是指AT89S51的P1端口,而不是其他变量。
打开reg51.h文件可以看到“sfrP1=0x90;”,即定义符号P1与地址0x90对应,而P1口的地址就是0x90。
虽然这里的“文件包含”只有一行,但C编译器在处理的时候却要处理几十行或几百行。
程序的第2行用符号P10来表示P1.0引脚。
在C51中,如果直接写“P1.0”编译器并不能识别,而且P1.0也不是一个合法的C51语言程序变量名【因为在reg51.h中,没有定义,需要用户自己定义】,所以必须给它起一个另外的名字,这里起的名字是P10,可是P10是否就是P1.0呢,所以必须给它们建立联系,这里使用了C51的关键字“sbit”来进行定义。
第3行~第8行对函数delay进行了事先定义,只有这样,才能在主程序中被主函数main()调用。
自行编写的函数delay()的用途是软件延时,调用时使用的这个“800”被称为“实际参数”,以延时800ms的时间。
注意,若delay()的定义写在main函数的后面,则需要先作出声明,否则编译无法通过,因为编译到main函数中的delay()语句时,找不到相应的函数体。
main为“主函数”,每一个C语言程序有且只有一个主函数,主函数后面一定有一对花括号“{}”,在花括号里面书写该函数的代码行。
2.用户自定义函数与库函数
用户自定义函数
用户根据自己需要所编写的函数。
如例3-1中的delay函数。
编写时,需要注意以下几点。
函数的首部(函数的第1行),包括函数名、函数类型、函数属性、函数参数(形式参数)名、参数类型。
例如:
voidDelay(unsignedinti)
函数体,即函数首部下面的花括号“{}”内的部分。
如果一个函数体内有多个花括号,则最外层的一对“{}”为函数体的范围。
C51区分大小写,例如Delay与delay,编译时是不同的两个名称。
每个语句最后必须有一个分号,分号是C语句的必要组成部分。
从函数的定义的形式上划分可以有三种形式:
无参数函数、有参数函数和空函数。
(1)无参数函数
此种函数在被调用时,既无参数输入,也不返回结果给调用函数,只是为完成某种操作而编写的。
(2)有参数函数
调用此种函数时,必须提供实际的输入函数,必须说明与实际参数一一对应的形式参数,并在函数结束时返回结果,供调用它的函数使用。
(3)空函数
函数体内无语句,是空白的。
调用空函数时,什么工作也不做,不起任何作用。
定义空函数的目的,是为以后程序功能的扩充。
程序最初设计时,往往只涉及最基本的功能模块的函数,其他模块的功能函数可以在以后补上。
因此先将非基本模块的功能函数定义成空函数,用一个空语句“;”占好位置,并写好注释,以后再用一个编好的函数代替它。
3.函数调用
程序设计者的任务就是编写一系列的用户自定义函数模块,并在需要的时候调用这些函数以及库函数,实现程序所要求的功能。
调用格式为:
函数名(实际参数1,实际参数2,…)
例如,例3-1中主函数main()里的子函数调用语句“delay(800);”,其中800为实际参数。
3.2.4C51的运算符
在程序中实现运算,要熟悉常用的运算符。
本节对C51中用到的标准C运算符进行回顾,为C51的程序设计打下基础。
1.算术运算符
如表3-3所示。
表3-3算术运算符及其说明
符号
说明
+
加法运算
-
减法运算
*
乘法运算
/
除法运算【商】
%
取模运算【余数】
++
自增1
――
自减1
对于“/”和“%”这两个符号都涉及除法运算,但“/”运算是取商,而“%”运算为取余数。
例如“5/3”的结果(商)为1,而“5%3”的结果为2(余数)。
表3-3中的自增和自减运算符是使变量自动加1或减1,自增和自减运算符放在变量前和变量之后是不同的。
++i,--i:
在使用i之前,先使i值加(减)1。
i++,i--:
在使用i之后,再使i值加(减)1。
若i=4,则执行x=++i时,先使i加1,再引用结果,即x=5,运算结果为i=5,x=5。
再如:
若i=4,则执行x=i++时,先引用i值,即x=4,再使i加1,运算结果为i=5,x=4。
2.逻辑运算符
逻辑运算符及其说明如表3-4所示。
表3-4逻辑运算符及其说明
&&
逻辑与
||
逻辑或
!
逻辑非
3.关系运算符
判断两个数之间的关系。
关系运算符及其说明如表3-5。
表3-5关系运算符及其说明
>
大于
<
小于
>=
大于或等于
<=
小于或等于
==
等于
=
不等于
4.位运算
C51中的位运算符及其说明如表3-6所示。
表3-6位运算其说明
&
按位与
Ι
按位或
^
按位异或
~
按位取反
<<
左移
>>
右移
【例】设a=0x54=01010100B,b=0x3b=00111011B,则a&b、a|b、a^b、~a、a<<2、b>>2分别为多少?
a=0x54=01010100B
b=0x3b=00111011B
a&b=00010000b=0x10
a|b=01111111B=0x7f
a^b=01101111B=0x6f
~a=10101011B=0xab
a<<2=01010000B=0x50【a<<=2】
b>>2=00001110B=0x0e【b>>=2】
5复合赋值运算符
C51语言中支持在赋值运算符“=”的前面加上其它运算符,组成复合赋值运算符。
下面是C51中支持的复合赋值运算符:
+=加法赋值-+减法赋值
*=乘法赋值/=除法赋值
%=取模赋值&=逻辑与赋值
|=逻辑或赋值^=逻辑异或赋值
~=逻辑非赋值>>=右移位赋值
<<=左移位赋值
复合赋值运算的一般格式如下:
变量复合运算赋值符表达式
它的处理过程:
先把变量与后面的表达式进行某种运算,然后将运算的结果赋给前面的变量。
其实这是C51语言中简化程序的一种方法,大多数二目运算都可以用复合赋值运算符简化表示。
a+=6相当于a=a+6;a*=5相当于a=a*5;b&=0x55相当于b=b&0x55;x>>=2相当于x=x>>2。
3.2.5C51的分支与循环程序结构
程序结构上可把程序分为三类,即顺序、分支和循环结构。
顺序结构是程序的基本结构,程序自上而下,从main()的函数开始一直到程序运行结束,程序只有一条路可走,没有其他的路径可以选择。
顺序结构比较简单和便于理解,这里介绍分支结构和循环结构。
1.分支结构程序
(1)只有两条分支的时候用
If(条件){分支1}
else{分支2}
(2)分支较多时
在分支较多时的情况下使用switch语句。
switch()
case():
语句;
break;
…………
default:
注意:
每个switch分支必须有一个break语句,否则程序并不能跳出switch,就会继续执行case后面的case语句。
2.循环结构程序
循环语句有以下三种。
(1)for循环
格式为:
for(循环体初始化;循环体执行条件;循环体执行后操作)
{循环体}
花括号{}中为循环体内容。
(2)while循环
while(循环体执行条件)
(3)dowhile循环
do{}花括号{}中为循环体内容
前两种循环是先判断循环条件是否满足,才决定循环体是否执行;而“dowhile循环”是在执行完循环体后再判断条件是否满足,再决定循环体是否继续执行。
三种循环中,经常使用的是for语句和while语句。
下面来说明for语句的应用。
【例】求1到100之间整数的和
程序如下:
#include
main()
{unsignedintnVar1,nsum;
//unsignedcharnsum;unsignedintnVar1;能节省一个单元
for(nVar1=0,nSum=1;nSum<=100;nSum++)
nVar1+=nSum;/*累加求和*/
(1);
关于循环,需说明的是,在无操作系统的控制器和处理器上运行的程序,主体通常采用轮询方式,即把所有的操作包含在一个while
(1){}中,如例3-1。
这样的无限循环在面向通用计算机的软件设计中是不被允许的,然而嵌入式系统软件设计中,则由于其硬件构成和使用需求,常常采用这种无限循环。
3.2.6绝对地址访问
使用C51运行库中预定义宏
C51编译器提供了一组宏定义来对51系列单片机的code、data、pdata和xdata空间进行绝对寻址。
规定只能以无符号数方式访问,定义了8个宏定义,其函数原型如下:
#defineCBYTE((unsignedcharvolatilecode*)0)
#defineDBYTE((unsignedcharvolatiledata*)0)
#definePBYTE((unsignedcharvolatilepdata*)0)
#defineXBYTE((unsignedcharvolatilexdata*)0)
#defineCWORD((unsignedintvolatilecode*)0)
#defineDWORD((unsignedintvolatiledata*)0)
#definePWORD((unsignedintvolatilepdata*)0)
#defineXWORD((unsignedintvolatilexdata*)0)
这些函数原型放在absacc.h文件中。
使用时须用预处理命令把该头文件包含到文件中,形式为:
#include。
其中:
CBYTE以字节形式对code区寻址,DBYTE以字节形式对data区寻址,PBYTE以字节形式对pdata区寻址,XBYTE以字节形式对xdata区寻址,CWORD以字形式对code区寻址,DWORD以字形式对data区寻址,PWORD以字形式对pdata区寻址,XWORD以字形式对xdata区寻址。
访问形式如下:
宏名[地址]
宏名为CBYTE、DBYTE、PBYTE、XBYTE、CWORD、DWORD、PWORD或XWORD。
地址为存储单元的绝对地址,一般用十六进制形式表示。
【例4-7】绝对地址对存储单元的访问。
#include//将绝对地址头文件包含在文件中
#include//将寄存器头文件包含在文件中
#defineucharunsignedchar//定义符号uchar为数据类型符unsignedchar
#defineuintunsignedint//定义符号uint为数据类型符unsignedint
voidmain(void)
ucharvar1;
uintvar2;
var1=XBYTE[0x0005];//XBYTE[0x0005]访问片外RAM的0005字节单元
var2=XWORD[0x0002];//XWORD[0x0002]访问片外RAM的0002字单元
xval=XBYTE[0x0002];//把外部存储区地址0x0002的数据存入变量xval中
XWORD[0x0002]=0x2000;//把0x2000送到外部存储区地址为0x0002的单元
#defineDAC0832XBYTE[0x7fff]//定义DAC0832的端口地址
DAC0832=0x80;//启动一次D/A转换
......
(1);
在上面程序中,其中XBYTE[0x0005]就是以绝对地址方式访问的片外RAM0005字节单元;XWORD[0x0002]就是以绝对地址方式访问的片外RAM0002字单元。
3.2.7使用C51扩展关键字_at_
使用_at_对指定的存储器空间的绝对地址进行访问,一般格式如下:
[存储器类型]数据类型说明符变量名_at_地址常数;
其中,存储器类型为data、bdata、idata、pdata等C51能识别的数据类型,如省略则按存储模式规定的默认存储器类型确定变量的存储器区域;数据类型为C51支持的数据类型。
地址常数用于指定变量的绝对地址,必须位于有效的存储器空间之内;使用_at_定义的变量必须为全局变量。
【例】通过_at_实现绝对地址的访问。
dataucharx1_at_0x40;//在data区中定义字节变量x1,它的地址为40H
xdatauintx2_at_0x2000;//在xdata区中定义字变量x2,它的地址为2000H
x1=0xff;
x2=0x1234;
3.2.7C51中断服务函数的定义
由于标准C没有处理单片机中断的定义,为直接编写中断服务程序,C51编译器对函数的定义进行了扩展,增加了一个扩展关键字interrupt,使用该关键字可以将一个函数定义成中断服务程序。
由于C51编译器在编译时对声明为中断服务程序的函数自动添加了相应的现场保护、阻断其他中断、返回时恢复现场等处理的程序段,因而在编写中断服务函数时可不必考虑这些问题,减轻了用汇编语言编写中断服务程序的繁琐程度,而把精力放在如何处理引发中断请求的事件上。
中断服务函数的一般形式为:
void函数名(void)interruptn[usingn]
在函数声明时,用“interruptn”语句,可以把所声明的函数定义为一个中断服务程序。
从定义中可以看出,中断函数必须是无参数、无返回值的函数。
关键字interrupt后面的n是中断号,对于AT89S51,取值为0~4,编译器从8×n+3处产生中断向量。
AT89S51中断源对应的中断号和中断向量见表3-3。
表3-3中断号n和中断向量
中断号n
中断源
中断向量(8×n+3)
0
外部中断0
0003H
1
定时器0中断
000BH
2
外部中断1
0013H
3
定时器1中断
001BH
4
串行口中断
0023H
其他值(5~31)
预留
(8×n+3)
AT89S51在内部RAM中有4个工作寄存器区,每个寄存器区包含8个工作寄存器(R0-R7)。
C51扩展了一个关键字using,专门用来选择AT89S51的4个不同的工作寄存器区。
在定义一个函数时,using是一个选项,如果不选用该项,则由编译器选择一个寄存器区作为绝对寄存器区访问。
unsignedintinterruptcnt;
unsignedcharsecond;
voidtimer0(void)interrupt1using2//定时0中断服务程序
if(++interruptcnt==4000)
{second++;
Interruptcnt=0;
关键字using对函数目标代码的影响:
在中断函数的入口处将当前工作寄存器区内容保护到堆栈中,函数返回前将被保护的寄存器区的内容从堆栈中恢复。
使用关键字using在函数中确定一个工作寄存器区时必须小心,要保证工作寄存器区切换都只在指定的控制区域中发生,否则将产生不正确的函数结果。
还要注意,带using属性的函数原则上不能返回bit类型的值,且关键字using和关键字interrupt都不允许用于外部函数,另外也都不允许有一个带运算符的表达式。
例如,外中断1(/int1)的中断服务函数书写如下:
voidint1(void)interrupt2using0//中断号n=2,选择0区工作寄存器区
编写AT89S51中断程序时,应遵循以下规则:
(1)中断函数没有返回值,如果定义了一个返回值,将会得到不正确的结果。
因此建议在定义中断函数时,将其定义为void类型,以明确说明没有返回值。
(2)中断函数不能进行参数传递,如果中断函数中包含任何参数声明都将导致编译出错。
(3)在任何情况下都不能直接调用中断函数,否则会产生编译错误。
(4)如果在中断函数中再调用其他函数,则被调用的函数所使用的寄存器区必须与中断函数使用的寄存器区不同。
(5)C51编译器对中断函数编译时会自动在程序开始和结束处加上相应的内容,具体如下:
在程序开始处对ACC、B、DPH、DPL和PSW入栈,结束时出栈。
中断函数未加usingn修饰符的,开始时还要将R0~R1入栈,结束时出栈。
如中断函数加usingn修饰符,则在开始将PSW入栈后还要修改PSW中的工作寄存器组选择位。
(6)C51编译器从绝对地址8m+3处产生一个中断向量,其中m为中断号,也即interrupt后面的数字。
该向量包含一个到中断函数入口地址的绝对跳转。
(7)中断函数最好写在文件的尾部,并且禁止使用extern存储类型说明。
防止其它程序调用。
3.4C51的集成开发环境KeilµVision3介绍
C51程序开发是在KeilµVision3开发环境下进行,首先介绍该开发环境。
3.4.1集成开发环境KeilµVision3简介
KeilSoftware公司推出的KeilµVision3是一款基于Windows的软件平台,它是一种用于51单片机的集成开发环境(IDE—IntergratedDevelopmentEviroment)。
µVision3提供了对基于8051内核的各种型号单片机的支持,完全兼容先前的KeilµVision2版本。
目前当前较新的版本为KeilC51V8.08a。
开发者可购买KeilµVision3软件,也可到Keilsoftware公
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1