《单片机原理及应用系统设计》课件第5章_第1页
《单片机原理及应用系统设计》课件第5章_第2页
《单片机原理及应用系统设计》课件第5章_第3页
《单片机原理及应用系统设计》课件第5章_第4页
《单片机原理及应用系统设计》课件第5章_第5页
已阅读5页,还剩134页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

5.1C51基本语法规则

5.2C51程序设计技巧

5.3汇编语言与C语言的混合编程

5.4C51与汇编语言的对照

5.5C51程序设计小结

习题五第五章C51程序设计语言5.1.1C51数据类型

C51支持的数据类型如表5-1所示。标准C语言和C51相同的数据类型不再详细说明,下面主要解释C51扩展的特殊

数据类型。5.1C51基本语法规则表5-1C51支持的数据类型5.1.2常量与变量

1.常量

常量就是程序运行过程中不能改变值的量,可用在值不必改变的场合,如固定的数据表、字库等。

常量的定义方式有如下几种:

(1)用预定义语句定义常量,如:

#difineFalse0x0;//定义False为0

(2)用code语句定义常量,如:

unsignedintcodea=100;//定义a为无符号int常量100

(3)用const语句定义常量,如:

constunsignedintc=100;//定义c为无符号int常量100

常量的数据类型有整型、浮点型、字符型、字符串型和位标量,这与标准C语言的常量一致。

2.变量

定义一个变量的格式如下:

[存储种类]数据类型[存储器类型]变量名称

表5-2给出了C51编译器能够识别的存储器类型,变量名称的命名规则与标准C语言一致,下面给出了一些变量定义的例子:chardatavar1 ;在data区定义字符型变量var1

intidatavar2 ;在data区定义字符型变量var2

inta=5 ;定义变量a,同时赋以初值5,变量a位

;于由编译模式确定的默认存储区

externfloatidatax,y,z;在idata区定义外部浮点型变量x,y,z表5-2C51能够识别的存储器类型

C51编译器有三种存储模式,具体如下:

(1)小(small)模式

(2)紧凑(compact)模式

(3)大(large)模式

1)全局变量

全局变量是指在程序开始处或各个功能函数的外面定义的变量。全局变量在整个程序的执行过程中都要占用内存单元。在程序开始处定义的全局变量对于整个程序都有效,可供程序中所有函数共同使用。例如:#include〈reg51.h〉 ;头文件,定义单片机片内资源#defineucharunsignedchar ;定义常量uchar=unsignedchar

ucharkey ;定义全局变量key

voidmain()

{

}在各功能函数外面定义的全局变量只对从定义处开始往后的各个函数有效。若一个全局变量不是在程序文件开始处定义的,但又希望在它的定义点之前的函数中引用该变量,这时应在引用该变量的函数中用关键字extern将其说明为外部变量。特别是当一个程序能由多个源程序文件组成时,如果一个程序中需要引用另外一个文件中已经定义的外部变量,必须要使用extern来声明。例如:文件1:

#include〈reg51.h〉

unsignedintarray[10];定义全局变量array

voidfillarray()

voidinit_ser()

{

}

voidmain()

{

}文件2:

externintarray[10];在另外一个文件中引用变量array

voidfillarray()

{

}

2)局部变量

下面给出了一个局部变量声明的例子:

voidfillarray()

{

intarray[10];定义局部array

}5.1.3运算符与表达式

C51的运算符和表达式与标准C语言差别不大,表5-3总结性地给出了运算符及其在表达式中的优先级关系,供读者参考。表5-3C51支持的运算符及其优先级5.1.4程序控制语句

1.表达式语句

表达式语句是一种最基本的语句。C51语言中,在表达式右边加一个分号“;”就构成了表达式语句,下面的语句都是合法的表达式语句:

b=b*10;Count++;

X=A;Y=B;

Page=(a+b)/a-1;在C51语言中有一个特殊的表达式语句,称为空语句,它仅仅由一个分号“;”组成。有时候为了使语法正确,但并不要求有具体的动作,这时就可以采用空语句。例如:

repeat:; //定义标号

gotorepeat; //无条件跳转到标号repeat处

上述程序利用标号repeat和无条件转移指令goto语句构成循环,但在标号repeat处并不需要有具体的动作。空语句在循环中也经常用到。

2.复合语句

若干条表达式语句组合而成的语句称为复合语句,其一般形式为:

{

局部变量定义

语句1;

语句2;

语句n;

}复合语句通常出现在函数中。下面给出了一个复合语句短句的简单例子:

{

intx1,y1; //定义局部变量

x1=5;

y1=8;

printf(″%d%d″,x1,y1); //打印x1,y1

}

3.条件语句

C51语言提供了三种形式的条件语句:

(1)基本形式,其语法结构为:

if(表达式)语句:

语义:如果表达式的值为真,则执行其后的语句,否则不执行该语句。

(2)if-else形式,其语法结构为:

if(表达式)语句1;

else语句2;

语义:如果表达式的值为真,则执行语句1,否则执行语句2。

(3)if-else-if形式,其语法结构为:

if(表达式1)语句1;

elseif(表达式2) 语句2;

elseif(表达式3) 语句3;

elseif(表达式m) 语句m;

else 语句n;

语义:依次判断表达式的值,当出现某个值为真时,则执行其对应的语句。然后跳到整个if语句之外继续执行程序。如果所有的表达式均为假,则执行语句n。下面给出了一个简单的条件语句应用的例子:

voidmain()

{

Inta,b; //定义变量

Printf(″inputtwonumbers:″)

Scanf(″%d%d″,&a,&b);//从外部输入两个数据

If(a>b)printf(″max=%d\n″,a) //比较大小,并

//打印最大值

elseprintf(″max=%d\n″,b);

}

4.开关语句

开关语句是用关键字switch构成的,它的一般形式如下:

switch(表达式)

{

case常量表达式1;语句1;

break;

case常量表达式2;语句2;

break;

case常量表达式n;语句n;

break;

default:语句n+1;

}

语义:计算表达式的值,并逐个与其后的常量表达式的值相比较,当表达式的值与某个常量表达式的值相等时,则执行case后面的语句,再执行break(间断语句)语句,跳出switch语句。如果case后没有和条件相等的值时就执行default后的语句。当要求没有符合的条件时不做任何处理,则不写default语句。

使用switch语句时要注意,在case后的各常量表达式的值不能相同,否则会出现错误。在case后,允许有多个语句,可以不用“{}”括起来。各case和default子句的先后顺序可

以变动,而不会影响程序执行结果。voidmain()

{

inta;

printf(″inputintegernumber:″);

scanf(″%d″,&a); //从外部输入一个数据

switch(a)

{

case1:printf(″Monday\n″);break;

//如果该数据为1,打印星期一,退出switch语

case2printf(″Tuesday\n″);break;

//如果该数据为2,打印星期二,退出switch语句

case3:printf(″Wednesday\n″);break;

//如果该数据为3,打印星期三,退出switch语句

case4:printf(″Thursday\n″);break;

//如果该数据为4,打印星期四,退出switch语句

case5:printf(″Firday\n″);break;

//如果该数据为5,打印星期五,退出switch语句

case6:printf(″Saturday\n″);break;

//如果该数据为6,打印星期六,退出switch语句

case1:printf(″Sunday\n″);break;

//如果该数据为7,打印星期日,退出switch语句

default:printf(″error\n″); //否则,打印出错信息

}

}

5.循环语句

提供了四种循环语句:

(1)while语句,其语法结构为:

while(表达式)语句;

(2)do-while语句,其语法结构为:

do

语句;

while(表达式);

(3)for语句,for语句是C51语言所提供的功能更强,使用更广泛的一种循环语句,其语法结构为:

for(表达式1;表达式2;表达式3)

语句;

下面给出了一个循环语句应用的简单例子,完成1~10的累加并通过串口显示:

#includde〈AT89X51.H〉

#include〈stdio,h〉

intcount(void);//声明函数

voidmain(void){

unsignedinttemp;

SCON=0x50; //串口方式1,允许接收TMOD=0x20;定时器1定时方式2

TCON=0x40; //设置定时器1开始计数

TH1=0xE8; //晶振11.0592MHz,1200波特率,TL1=0xE8;

TI=1;

TR1=1; //启动定时器

Temp=Count();

printf(11-10SUM=%d\n11,temp);//显示

while(1);

}

intcount(void)

{

unsignedintI,SUM;

for(I=1;I<=10;I++)

{

SUM=I+SUM; //累加

}

return(SUM);

}

6.转移语句

(1)goto语句。goto语句也称为无条件转移语句,其语法结构为:

goto语句标号;

(2)break语句。break语句只能用在switch语句或循环语句中,其语法结构为:

break;

(3)continue语句。continue语句只能用在循环体中,其语法结构为:

continue;

(4)return语句。return语句是返回语句,其语法结构为:

return(表达式);

return;5.1.5函数

1.函数的调用

(1)函数语句。在主调用函数中直接将被调用函数作为一个语句。如:

Print(″HelloWord!\n″);

(2)函数表达式。这种调用方式是将被调用函数作为一个表达式,这种表达式称为函数表达式,如:

c=power(x,n)+power(y,m);

(3)函数参数。这种参数调用方式是指将被调用函数作为另一个函数的实际参数,如:

M=jiec(jiec(3));

2.被调用函数的说明

与使用变量一样,在调用一个函数之前,包括标准库函数,必须对该函数的类型进行说明,即“先说明,后调用”。如果调用的是标准库函数,一般应在程序的开始处用预处理命令#include将有关函数说明的头文件包含进来,如:

include〈reg51.h〉如果调用的是用户自定义函数,而且该函数与调用它的主调用函数在同一个文件中,一般应在主调用函数中对被调用函数的类型进行说明。函数说明的一般形式为:

类型标识符函数的名称(形式参数表);

其中,“类型标识符”说明了函数返回值的类型,“形式参数表”说明了各个形式参数的类型。

例如:

intcount(void);

3.函数的返回值

在调用函数过程中,经常希望得到一个从被调用函数中带回来的值,这就是函数的返回值。

4.参数的传递

(1)数值传递

(2)地址传递

5.重入函数

重入函数声明的关键字为reentrant,重入函数的声明的格式为:

函数类型函数名(形式参数表)reentrant下面给出了一个简单重入函数定义的例子:

charmax(chara,charb,charc)reentrant

{

charmax_data;

max_data=(a>b)?a:b; //将a,b两数中的最大

//值赋给max_data

max_data=(max_data>c)?max_data:c; //将a,b,c三数中的

//最大值赋予maxdata

return(max_data); //返回最大值

}

6.中断服务函数

中断服务函数声明的关键字为interrupt,声明的格式为:

函数类型函数名(形式参数)interruptn[usingn]

其中,关键字interrupt后面的n是中断号,n的取值范围为0~31。编译器从8n+3处产生中断向量,具体的中断号n和中断向量取决于8051系列单片机芯片的型号,常用中断源和中断向量。如表5-4所示。表5-4单片机常用中断号与中断向量下面给出了一个简单的中断服务函数的例子:

unsignedintg_wTCount;

unsignedcharg_bySecond;

voidtimer0(void)interrupt1using1

{

g_wTCount++

if(g_wTCount>=20 //如果计数到20

{

g_bySecond++; //另一个计数器

g_wTCount=0; //计数器清零

}

}5.1.6指针

图5-1形象地给出看了指针变量和它所指向的变量之间的关系。图5-1指针变量和它所指向的变量

2.指针变量的定义

指针变量的定义与一般变量的定义类似,其一般形式如下:

数据类型[存储器类型1]*[存储器类型2]标识符;

(1)通用指针变量:不选用“存储器类型1”选项的指针变量称为通用指针变量,其声明和标准C语言中一样。如:

char*s;

int*numptr;

long*state;

(2)存储器指针变量:选用“存储器类型1”选项的指针变量称为存储器指针变量,如:

chardata*str;

intxdata*numtab;

longcode*powtab;下面的示例程序说明了使用不同的指针在代码长度、占用数据空间和运行时间上的不同:

Description IdataPointer XdataPointer

GenericPointer

C源程序 idata*ip; charxdata*xp;

char*p;

charval; charval;

charval;

val=*ip; val=*xp;

val=*xp;

编译后的代码 MOVR0,ip MOVDPL,xp+1

MOVR1,p+2

MOVval,@R0 MOVDPH,xpMOVR2,p+1

MOVA,@DPTRMOVR3,p

MOVval,ACALLCLDPTR

指针大小 1byte 2byte

3byte

代码长度 4byte 9byte

11byte+librarycall

执行时间 4cycles 7cycles 13cycles由于许多库函数会使用通用指针,但为了提高效率,用户在程序中其自定义的函数可能会使用存储器指针。在不同指针的函数之间进行调用时,往往要进行指针的转换。指针的转换可以用类型转换的直接程序代码来强迫转换,或在编译器内部强制转换。C51编译器指针转换的规则如表5-5所示。表5-5通用指针与存储器指针之间的转换

3.指针变量的操作

(1)取地址运算符&:该运算符用来取变量的地址。如取变量a地址的指令为&a。

(2)间接访问运算符*:该运算符用来间接访问变量。如*p为指针变量p所指向的变量。

指针变量经过定义后可以像其他基本变量一样引用,下面的例子给出了指针变量基本操作:

inti,x,y,*pi,*px //定义指针变量

pi=&i;

//指针变量赋值将变量i的地址赋

//给指针变量pi,使pi指向i

*pi+=1 //指针变量的运算,等价于i+=1

下面是一个指针应用的简单例子:

#include〈reg51.H〉

#inculde〈stdio.h〉

voidmain(void)

{

intx,y; //定义一般变量

int*p,*p1,*p2; //定义指针变量

printf(″inputxandy:\n″);

sacnf(″%d%d,&x,&y″); //输入两个数

p1=&x;p2=&y;

if(x<y)

{

p=p1;p1=p2;p2=p; //将大的数放在指针p1指向的变量,小

//的数放在p2指向的变量

printf(″max=%d,min=%d,\n″,*p1,*p2);//打印结果

while(1);

}5.1.7构造数据类型

1.数组

数组是一组有序数据的集合,数组中的每一个数据都属于同一种数据类型。数组中的各个元素可以用数组名和下标来唯一地确定。按照维数,数组可以分为一维数组和多维数组。在C51中数组必须先定义,然后才能使用。一维数组的定义形式如下:

数据类型数组名[常量表达式];定义多维数组时,只要在数组名后面增加相应于维数的常量表达式即可,其一般形式如下:

数据类型数组名[常量表达式1]…[常量表达式N];

下面给出了几个数组定义的例子:

unsignedintxcount[10];

//定义一堆无符号整型数组,有10个数据单元

charinputstring[5];

//定义一维字符型数组,有5个数据单元

floatoutnum[10],[10];

//定义二维浮点型数组,有100个数据单元

2.结构

结构是由基本数据类型构成的,并用一个标识符来命名的各种变量的组合。结构中可以使用不同的数据类型。结构也是一种数据类型,可以使用结构变量,因此,像其他类型的变量一样,在使用结构变量时要先对其定义。定义结构变量的一般格式为:

struct结构名

{

数据类型变量名;

数据类型变量名;

}

结构变量;构成结构的每一个类型变量称为结构成员,它像数组的元素一样,但数组中元素是以下标来访问的,而结构是按变量名字来访问成员的。下面的例子定义了一个结构名为string的结构变量person:

structstring

{

charname[8];

intage;

charsex[2];

chardepart[20];

floatwage1,wage2,wage3,wage4,wage5;

}

person;结构是一个新的数据类型,因此结构变量也可以像其他类型的变量一样赋值、运算,不同的是结构变量以成员作为基本变量。结构成员的表示方式为:

结构变量.成员名

如果将“结构变量.成员名”看成一个整体,则这个整体的数据类型与结构中该成员的数据类型相同,这样就可像前面所讲的变量那样使用它们。如对上面例子中的年龄进行赋值:

person.age=20;

3.联合

联合也是一种新的数据类型,它是一种特殊形式的变量。联合说明和联合变量定义与结构十分相似。其形式为:

union联合名

数据类型成员名;

数据类型成员名;

}

联合变量名;联合表示几个变量共用一个内存位置,在不同的时间保存不同的数据类型和不同长度的变量。下面的例子定义了一个联合名为a_bc的联合变量lgc:

uniona_bc

{

inti;

charmm;

}

lgc同样联合变量也可以定义成数组或指针,但当定义为指针时,此时联合访问成员可表示成:

联合名_成员名

另外,联合可以出现在结构内,它的成员也可以是结构。例如:

struct

{

intage;

char*addr;

union

{

inti;

char*ch;

}

x;

}

y[10];

4.枚举

枚举是一个被命名的整型常数的集合,枚举的说明与结构的联合相似,其形式为:

enum枚举名

{

标示符[=整型常数],

标示符[=整型常数],

标示符[=整型常数],

}

枚举变量;若果枚举没有初始化,即省掉“=整型常数”时,则从第一个标示符开始,顺次赋给标示符0,1,2,…。但当枚举中的某个成员赋值后,其后的成员按依次加一的规则确定其值。例如下列枚举:

enumstring{x1,x2,x3,x4}x;

说明后,x1,x2,x3,x4的值分别为0,1,2,3,当定义改变成:

enumstring

{

x1,

x2=0,

x3=50,

x4,

}

x;5.1.8C51位操作及其表达式

C51提供了如下位操作运算符:

& 按位与

| 按位或

^ 按位异或

~ 按位取反

<< 位左移

>> 位右移

1.“按位与”运算符“&”

运算规则:参加运算的两个运算对象,若两者相应的位都为1,则该位结果值为1,否则为0。即

0&0=0

0&1=0

1&0=0

1&1=1

【例5-1】若a=54H=010100B,b=3BH=00111011B,

则表达式c=a&b的值为10H,即

a:01010100

b:&00111011

(10H)

c:=0010000

2.“按位或”运算符“|”

运算规则:参加运算的两个运算对象,若两者相应的位置中只要有一个为1,则该位结果为1。即

0|0=0

0|1=1

1|0=1

1|1=1

【例5-2】若a=30H=00110000B,b=0FH=00001111B,

则表达式c=a|b的值为3FH,即

A:00110000

B:|

00001111

(3FH)

C=00111111

3.“按位异或”运算符“^”

运算规则:参加运算的两个运算对象,若两者相应的位置相同,则结果为0;若两者相应的位置相异,则结果为1。即

0^0=0

0^1=1

1^0=1

1^1=0

【例5-3】若a=A5H=10100101,b=37H=00110111,

则表达式c=a^b的值为92H,即

a:10100101

b:00110111

(92H)

10010010

4.“按位取反”运算符“~”

~是一个单目运算符,用来对一个二进制数按位进行取反,即0变1,1变0。

【例5-4】若a=FOH=11110000B,则表达式a=~a值为0FH,即

a:11110000

~

(0FH)

00001111

5.“位左移”和“位右移”运算符“<<”和“>>”

位左移运算符<<和位右移运算符>>,用来将一个数各二进制位的全部左移或右移若干位,移位后,空白为补0,而溢出位舍弃。

【例5-5】若a=FAH=111010101B,则表达式a=a<<2将a值左移两位,其结果为A8H,即

【例5-6】若a=11000011B=E3H,将a值右循环位移2位。

a值右循环位移n位,即将a中原来左边(8-n)位右移n位,而将原来右边的n位移到最左面n位。

上述问题可以通过下列步骤来实现:

(1)将a的右端n位先放到b的高n位中,即

b=a<<(8-n);

(2)将a的右端n位,其左边高n位补0,即

c=a>>n;

(3)将b、c进行或运算,即

a=c|b;

故对a进行循环右移2位的程序可这样编写:

main()

{

unsignedchara=0xc3,b,c;

intn=2;

b=a<<(8-n);

c=a>>n;

a=c|b;

}

结果:循环右移前a=11000011

循环右移后a=11110000

【例5-7】分析下面程序段的作用。

#definePORTAXBYTE[0XFFC0]

voidmain()

{…

PORTA=(PORTA&0xbf)|0x4;

}5.1.9自增减运算符、复合运算符及其表达式

1.自增减运算符

【例5-8】若i值原来为5,则

j=++i:j值为6,i值也为6

j=i++:j值为5,i值为6

【例5-9】若i原值为3,则表达式k=(++i)+(++i)+(++i)的值为18。这是因为,++i最先执行,先对表达式进行扫描,对i进行3次自加(++i),此时i=6,然后执行k=6+6+6=18,故k值为18。

而表达式k=(i++)+(i++)+(i++)其结果是:k值为9,而i值为6。因为先对i进行3次相加,再执行3次i的自加。

注意:(1)自增运算符(++)和自减运算符(--)只能用于变量而不能用于常量表达式。

(2)(++)和(--)的结合方向是“自右向左”。

【例5-10】-i++相当于-(i++),假如i的原值为3,则表达式k=-i++的结果k值为-3,而i值为4。

2.复合运算符及其表达式

凡是双目运算符,都可以与赋值运算符“=”一起组成复合赋值运算符。C51共提供了10种复合赋值运算符。即

+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=

采用这种复合赋值运算的目的是简化程序,提高C程序编译效率。如:a+=b 相当于 a=a+b

a-=b 相当于 a=a-b

a*=b 相当于 a=a*b

a/=b 相当于 a=a/b

a%=b 相当于 a%b

a<<=8 相当于 a=a<<8

a>>8 相当于 a=a>>8

5.2.1存取AT89S52单片机特殊功能寄存器

单片机AT89S52内部有一些特殊功能寄存器(SFR),如P0、P1其对应的内存地址分别为80H、90H、A0H、B0H……在编写汇编程序时可以对端口1做输出控制:

MOVA,#0

MOVP1,A5.2C51程序设计技巧这两条指令将端口1全部设为低电位。对于C51语言,则可以用以下方式来控制:

#include″AT89S52reg.h″

P1=0;

只要将定义文件AT89S521reg.h包括进来即可。因为头文件中已对AT89S52内部各个专用寄存器做了定义,其内容如下:/*

*AT89S52internalregisterdehnitionsforDDSMICRO-C/51

*

*Copyright1991-1994DaveDunfield

*Allrightsreserved.

*/

externalregisterunsignedcharPSW,SP,DPH,DPL,P0,P1,P2,P3,IP,IE,TMOD,TCON,TH0,TL0,TH1,TL1,SCON,SBUF,PCON,

T2CON,TH2,TL2,RCAP2H,RCAP2L;

若想从端口1中读取数据至变量in中,可以用如下指令:

#include“AT89S52.reg.h”

charin;

in=P15.2.2位的控制

AT89S52内部有一些特殊功能寄存器,其中可以分别做位的控制,如P0、P1、P2、P3等。例如,将端口1位7设为高电位。可以使用汇编语言指令:

setbP1.7

将其设为低电位可以使用指令:

clrP1.7

将位取反可以使用指令:

cplP1.7

而在C51中则可以使用如下指令:#include"AT89S52io.h" /*一般I/O定义*/

#include"AT89S52lbit.h" /*位设定/取消宏定义*/

#include"AT89S52reg.h" /*8051寄存器定义*/

setbit(P1.7), /*设为高电位*/

clrbit(P1.7); /*设为低电位*/

cplbit(P1.7); /*位取反*/上述3指令在AT89S52bit.h定义中已用宏处理了。其内容如下:

/*

*MacrostoallowdirectaccesstotheI/ObitsoftheAT89S52

*internalregistersfromDDSMICRO-C/51.

*REQUIRESTHEEXTERNALPREPROCESSOR(MCP)!

*

*Copyright1991-1994DaveDunfield

*Allrightsreserved.

*/

#definesetbit(bit)asm{/*MacrotosetaI/Obit*/

SETBbit

}#defineclrbit(bit)asm{/*MacrotoclearanI/Obit*/

CLRbit

}

Definecplbit(bit)asm{/*MacrotocomplemetanI/Obit*/

CPLbit

}5.2.3中断子程序的设计

在有关中断的头文件AT89S52INT.H中,有关于中断向量的执行地址,如下所示:/*

*Common8025interruptvectoradresses

(Mustbedecimal)

*/

defineIEO3/*Externalinterrupt0*/

#defineTFO11/*Timer0rollover*/

defineIE119/*Externalinterrupt1*/

defineTF127/*Timer1rollover*/

#defineSER35/*SerialportRXorTX*/

#defineTF243/*Timer2rollover*/

defineEXF243/*Timer2externalflag*/若中断程序写为如下的程序结构:

INTERRUPT(-TF0-)t0–isr()

{

程序内容

}

【例5-11】以计时器0模式1做每隔5ms的计时中断,即每隔5ms执行一次中断服务程序t0-isr()。

#include″AT89S52i0.h″

#include″AT89S52reg.h″

#include″AT89S52bit.h″

#include″AT89S52lint.h″

#dehneHI238/*5msrun1timeISR*/

#defineLO0

INTERRUPT(-TF0-)t0–isr()

{

TH0=HI;

TL0=LO;

}

main()

{

TMOD=0x01;

TH0=HI;

TL0=LO;

IE=0x82;

TCON=0x10;

}5.2.4内存应对式I/O

【例5-12】将外部RAM从2000H起的连续100字节全部填为“55H”。

程序如下:

#defineadd(char*)0x2000

test–ext-ram()

inti;

char*pt;

pt=add;

for(i=0;i<100;i++)

*pt++=0x55

【例5-13】从AT89S52单板上8255端口A送出数据“55H”。

程序如下:

#definepa(*(char*)0x8000)

definepb(*(char*)0x8001)

definepc(*(char*)0x8002)

definecr(*(char*)0x8003)

text-8255()

{

cr=0x80;

pa=0x55;

}5.2.5C51程序设计举例

1.“求和”的C51程序设计

【例5-14】已知x=10,y=20,计算z=x+y的结果。

main() /*主函数名*/

{ /*主函数体开始*/

intx,y,z; /*主函数的内部变量类型说明*/

x=10:y=20; /*变量赋值*/

z=x+y; /*计算z=x+y的值*/

} /*程序结束*/

2.求最大值的C51程序设计

【例5-15】求最大值。

#include〈stdio.h〉 /*预处理命令*/

include〈reg51.h〉

main() /*主函数名*/

{ /*主函数体开始*/

inta,A,c; /*主函数的内部变量类型说明*/

intmax(intx,inty); /*功能函数max及其形式参数说明*/

SCON=0x52; /*8051单片机串行口初始化*/

TMOD=0x20;

TCON=0x0F3

scanf(″%d%d,&a,&A″)/*输入变量a和A的值*/

c=max(a,A); /*调用max函数*/

printf(″max=%d″,c) /*输出变量c的值*/

} /*主程序结束*/

intmax(intx,inty) /*定义max函数,x、y为形式参数*/

{ /*max函数体开始*/

intz; /*max函数内部变量类型说明*/

if(x>y)z=x /*计算最大值*/

elsez=y;

return(z);/*将计算得到的最大值z返回到调用处*/

} /*max函数结束*/

C51本身提供有“asm”语句,有以下两种格式。

格式1:

asm″汇编语言指令″;

在此格式中,双引号内的所有指令将全部送到汇编语言编译器。注意,在语句的最后要加上分号,作为语句的结束,如同一般的C语言程序。

格式2:

asm{

汇编语言指令

};5.3汇编语言与C语言的混合编程下面举一个在C语言程序设计中加入汇编语言语句的实例。

#include"AT89S52io.h"

#include"AT89S52reg.h"

#include"AT89S52bit"

/**/

text-asm0.

{

asm"nop";

asm"nop";

asm"clrP1.7"

}

/**/

text-asml()

{

asm{

NOP

NOP

CPLPI.7

}

}

/**/

main()

{

test-asm()

test-asm()

}5.3.1C51和A51接口所涉及的几个主要问题

1.C51函数名的转换及其命名规则

C51中函数名的转换规则如表5-6所示。表5-6C51函数名的转换规则

2.C51函数及其相关段的命名规则

依赖于所使用的存储器模式,这些段的段名按表5-7所列规则命名,在相互调用时,汇编语言必须服从C51有关段名的命名规则。表5-7各种存储器模式下函数相关段名的命名规则

3.C51函数的参数传递规则

表5-8是利用寄存器传递参数的规则。表5-8C51中的参数传递当函数具有返回值时,也需传递参数,这种返回值参数的传递均是通过CPU内部寄存器完成,其传递规则如表5-9所示。表5-9函数返回使用的寄存器下面给出了一个C51程序及其编译后的程序清单,从中可以更直观地理解C51的函数名转换规则、段命名规则及参数传递规则。

/*

文件名:ASM.c

功能:计算x/y

*/

#Include<reg51.h>

#deinfineucharunsignedchar

ucharfunc(ucharx,uchary); /*函数func原型声明*/

voidmain(void) /*主函数*/

{

func(0x12,0x34); /*调用函数func*/

}

ucharfunc(ucharx,uchary) /*函数func*/

{

return(x/y) /*计算x/y并返回结果*/

}

;********************************

;文件名;ASM.SRC

;说明:此文件是ASM.C编译后的汇编输出文件(限于篇幅,有所省略)

;********************************

?PR?main?ASMSEGMENTCODE /*主函数main代码段声明*/

?PR?_func?ASMSEGMENTCOME /*函数func代码段声明*/

PUBLIC_func /*公开函数名以便可被其他模块调用*/

PUBLICmain

RSEG?PR?main?ASM

main /*主函数代码段起始*/

;{

;func(0x12,0x34);

MOVR7,#02H /*R7传递第一个char参数*/

MOVR5,#034H /*R5传递第二个char参数*/

LCALI_func /*调用函数func*/

;}

RET /*返回*/

;ucharfunc(ucharx,uchary)

RSEG?PR?_func?ASM_func; /*函数func代码段起始*/

;{

;return(x/y);

MOVA,R7; /*计算x/y*/

MOVB,R5

DIVAB

MOVR7,A /*结果经R7返回*/

;}

RET /*返回*/

END /*结束*/5.3.2C51程序中嵌入汇编

(1)通过预编译指令“#pragmaasm”和“#pragma.asm”在C语言代码中插入汇编语言代码,如:

#include〈reg51.h〉

voidmain(void)

{

P2=1;

#pragmaasm//从此处插入汇编代码

MOVR7,#10

DEL:MOVR6,#20

DJNZR6,$

DJNZR7,DEL

#pragmaendasm//结束汇编代码

P2=0;

}

(2)在project窗口中包含汇编代码的C文件上单击右键,选择“Optionsfor…”选项,单击右边的“GenerateassemblerSRCFile”和“AssembleSRCFile”,使复选框由灰色变成黑色状态,设置完成之后的对话框如图5-2所示。

图5-2SRC选项设置完成之后的对话框

(3)根据选择的编译模式,把相应的库文件(如Small模式时是Keil\C51\Lib\C51S.Lib)加入工程中,该文件必须作为工程的最后文件。如果没有做这一步,编译时会出现如下警告:

“UNRESOLVEDEXTERNALSYMBOL”。

(4)编译,即可生成目标代码。5.3.3C51与汇编函数的相互调用

1)先用C语言编写所有的代码,包括需要汇编语言实现的部分。这里所选的例子就是前面讨论的单片机模拟节能路灯的例子,延时子程序用汇编语言编写,其余程序用C语言编写。

则程序清单如下:/*

文件名:main.c

功能:模拟节能路灯的控制过程主程序

*/

#include〈AT89X51.H〉

SbitK1=P3^0;

SbitL1=P1^0;

externvoiddelay02s(void);//延时0.2s子程序

voidmain(void)

{

while(I)

{

if(K1==0){

L1=0; //点亮发光二极管

delay02s(); //调用延时子程序

L1=1; //熄灭发光二极管

}

else

{

L1=1; //熄灭发光二极管

}

}

}/*

文件名:delay.c

功能:延时0.2s

*/

delay02s(void)

{

unsignedchari,j,k;

for(i=20;i>0;i--)

for(j=20;j>0;j--)

for(k=248;k>0;k--);

}

(2)建立项目文件,将main.c和delay.c都包含进项目。

(3)在project窗口中包含汇编代码的.c文件上单击右键,选择“Optionsfor…”选项,单击右边的“GenerateassemblerSRCFile”和“AssembleSRCFile”,使复选框由灰色变成黑色(有效)状态。本例是在delay.c文件上进行此操作。

(4)根据选择的汇编模式,把相应的库文件(Small模式时,是Keil\C51\Lib\C51S.Lib)加入工程,该文件必须作为工程的最后文件。

(5)编译这个项目后将会产生一个delay.SRC的文件,将这个文件名改为delay.A51。为了让用户更直观地认识C程序经编译器编译之后的情况,下面给出了由编译器产生的不做任何修改的delay.a51的清单:

;\delay.SRCgeneratedfrom:delay.c

;COMPILERINVOKEDBY:

;d:\keil\C51\BIN\C51.EXEdelay.cBROWSEDEBUGOBJECTEXTENDSRC(.\delay.SRC)

NAMEDELAY

?PR?delay02s?DELAYSEGMENTCODE

PUBLICdelay02S

;/*

;文件名:delay.c

;功能:延时0.2s

;*/

;voiddelay02s(void)

RSEG?PR?delay02s?DELAY

Delay02s

USING0

;SOURCELINE#5

;{

;SOURCELINE#6

;unsignedchari,j,k;

;for(i=20;i>0;i--)

;SOURCELINE#8;

;Variable′i?040′assignedtoRegister′R7′

MOVR7,#014H

?Cooo1:

;for(j=20;j>0;j--)

;SOURCELINE#9

;Variable′j?041′assignedtoRegister′R6′

MOVR6,#014H

?C0001:

;for(k=248;k>0;k--)

;SOURCELINE#10

;Variable′k?042′assignedtoregister′R5′

MOVR5,#0F8H

?C0007

DJNZR5,?C0007

?C0006

DJNZR6,?C0004

?C0003

DJNZR7,?C0001

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论