第3章 KeilC语言及其程序设计21Word文档格式.docx
《第3章 KeilC语言及其程序设计21Word文档格式.docx》由会员分享,可在线阅读,更多相关《第3章 KeilC语言及其程序设计21Word文档格式.docx(17页珍藏版)》请在冰豆网上搜索。
i<
count;
i++)//如果i<
count,则i加1
{//在时钟频率为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>
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;
break;
…………
default:
注意:
每个switch分支必须有一个break语句,否则程序并不能跳出switch,就会继续执行case后面的case语句。
2.循环结构程序
循环语句有以下三种。
(1)for循环
格式为:
for(循环体初始化;
循环体执行条件;
循环体执行后操作)
{循环体}
花括号{}中为循环体内容。
(2)while循环
格式为:
while(循环体执行条件)
{
花括号{}中为循环体内容。
}
(3)dowhile循环
do{}花括号{}中为循环体内容
前两种循环是先判断循环条件是否满足,才决定循环体是否执行;
而“dowhile循环”是在执行完循环体后再判断条件是否满足,再决定循环体是否继续执行。
三种循环中,经常使用的是for语句和while语句。
下面来说明for语句的应用。
【例】求1到100之间整数的和
程序如下:
#include<
stdio.h>
main()
{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文件中。
使用时须用预处理命令把该头文件包含到文件中,形式为:
absacc.h>
。
其中:
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】绝对地址对存储单元的访问。
//将绝对地址头文件包含在文件中
//将寄存器头文件包含在文件中
#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转换
......
while
(1);
在上面程序中,其中XBYTE[0x0005]就是以绝对地址方式访问的片外RAM0005字节单元;
XWORD[0x0002]就是以绝对地址方式访问的片外RAM0002字单元。
3.2.7使用C51扩展关键字_at_
使用_at_对指定的存储器空间的绝对地址进行访问,一般格式如下:
[存储器类型]数据类型说明符变量名_at_地址常数;
其中,存储器类型为data、bdata、idata、pdata等C51能识别的数据类型,如省略则按存储模式规定的默认存储器类型确定变量的存储器区域;
数据类型为C51支持的数据类型。
地址常数用于指定变量的绝对地址,必须位于有效的存储器空间之内;
使用_at_定义的变量必须为全局变量。
【例】通过_at_实现绝对地址的访问。
#defineuintunsignedint//定义符号uint为数据类型符unsignedint
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
0003H
1
定时器0中断
000BH
2
外部中断1
0013H
3
定时器1中断
001BH
4
串行口中断
0023H
其他值(5~31)
预留
(8×
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公