Keil C51程序设计
转载地址:http://www.dwenzhao.cn/profession/mcu/mcu51keilc.html
Keil C51是一种专为8051系列单片机设计的C编译器,支持符合ANSI标准的C语言进行程序设计,同时针对8051系列单片机自身特点做了一些特殊扩展。
1. Keil C51程序设计基本语法:
1)Keil C51程序的一般结构:
C51程序由一个或多个函数构成,其中至少应包含一个主函数main()。程序执行时,一定是从main()函数开始,调用其他函数后又返回main()函数,被调用函数如果位于主调函数前面则可直接调用,否则要先说明后调用,函数之间可以互相调用。C51程序的一般结构如下:
预处理命令 //用于包含头文件等
全局变量说明; //全局变量可被本程序的所有函数引用
函数1说明;
... ...
函数n说明;
/*主函数*/
main(){
局部变量说明; //局部变量只能在所定义的函数内部引用
执行语句;
函数调用(形式参数表);
}
/*其他函数定义*/
函数1(形式参数定义){
局部变量说明; //局部变量只能在所定义的函数内部引用
执行语句;
函数调用(形式参数表);
}
... ...
函数n(形式参数定义){
局部变量说明; //局部变量只能在所定义的函数内部引用
执行语句;
函数调用(形式参数表);
}
由此可见,C51是由函数组成的,函数之间可以相互调用,但main()函数只能调用其他功能函数,不能被其他函数调用。其他功能函数,可以是C51编译器提供的库函数,也可以是用户按需要自行编写的。不管main()函数处于程序中什么位置,程序总是从main()开始执行。
编写C51程序的注意点:
①函数以“{”开始,以“}”结束,二者必须成对出现,它们之间的部分为函数体。
②C51程序没有行号,一行内可以书写多条语句,一条语句也可以分写在多行上。
③每条语句最后必须以一个分号“;”结尾,分号是C51程序的必要组成部分。
④每个变量必须先定义后引用。函数内部定义的变量为局部变量,又称内部变量,只有在定义它的那个函数之内才能够使用。在函数外部定义的变量为全局变量,又称外部变量,在定义它的那个程序文件中的函数都可以使用它。
⑤对程序语句的注释必须放在双斜杠“//”之后,或者放在“/*......*/”之内。
2)数据类型:
C51数据类型可分为基本数据类型和复杂数据类型,复杂数据类型由基本数据类型构造而成。基本数据类型有char(字符型)、int(整型)、long(长整型)、float(浮点型)和*(指针型)。下表列出了Keil C51编译器能够识别的数据类型:
数据类型 |
长度 |
值域 |
unsigned char |
单字节 |
0~255 |
signed char |
单字节 |
-128~+127 |
unsigned int |
双字节 |
0~65536 |
signed int |
双字节 |
-32768~+32767 |
unsigned long |
四字节 |
0~4294967295 |
signed long |
四字节 |
-2147483648~+2147483647 |
float |
四字节 |
±1.175494E-38~±3.402823E+38 |
* |
1~3字节 |
对象的地址 |
bit |
位 |
0或1 |
sfr |
单字节 |
0~255 |
sfr16 |
双字节 |
0~65536 |
sbit |
位 |
0或1 |
Keil C51编译器除了支持基本数据类型之外,还支持扩充数据类型:
①bit:位类型,可以定义一个位变量,但不能定义位指针,也不能定义位数组。
②sfr:特殊功能寄存器,可以定义8051单片机的所有内部8位特殊功能寄存器。sfr型数据占用一个内存单元,其取值范围是0~255。
③sfr16:16位特殊功能寄存器,它占用两个内存单元,取值范围0~65535,可以定义8051单片机内部的16位特殊功能寄存器。
④sbit:可寻址位,可以定义8051单片机内部RAM中的可寻址位或特殊功能寄存器中的可寻址位。
示例:采用如下语句可以将8051单片机P0口地址定义为80H,将P0.1位定义为FLAG1。
sfr P0=80H;
sbit FLAG1=P0^1;
3)常量、变量及其存储模式:
常量包括整型变量、浮点型变量、字符型常量及字符串常量等。
变量,是一种在程序执行过程中其值能不断变化的量。在使用一个变量之前,必须先进行定义,用一个标识符作为变量名并指出它的数据类型和存储类型,以便编译系统为它分配相应的存储单元。在C51中对变量进行定义的格式如下:
[存储种类] 数据类型 [存储器类型] 变量名表;
其中,“存储种类”和“存储器类型”是可选项。变量的存储种类有4种:auto自动、extern外部、static静态和register寄存器。定义变量时如果省略存储种类选项,则该变量将为自动变量。
Keil C51编译器还允许说明变量的存储器类型,使之能够在8051单片机系统内准确地定位。下表列出了Keil C51编译器所能识别的存储器类型:
存储器类型 |
说明 |
DATA |
直接寻址的片内数据存储器(128B),访问速度最快 |
BDATA |
可位寻址的片内数据存储器(16B),允许位与字节混合访问 |
IDATA |
间接访问的片内数据存储器(256B),允许访问全部片内地址 |
PDATA |
分页寻址的片外数据存储器(256B),用MOVX @Ri指令访问 |
XDATA |
片外数据存储器(64KB),用MOVX @DPTR指令访问 |
CODE |
程序存储器(64KB),用MOVC @A+DPTR指令访问 |
下面是一些变量定义的示例:
char data var1; //在DATA区定义字符型变量var1
int idata var2; //在IDATA区定义整型变量var2
char code text[]=”ENTER PARAMETER”; //在CODE区定义字符串数组text[]
long xdata array[100]; //在XDATA区定义长整数数组变量array[100]
extern float idata x,y,z; //在IDATA区定义外部浮点型变量x, y, z
char bdata flags; //在BDATA区定义字符型变量flags
sbit flag0=flags^0; //在BDATA区定义可位寻址变量flag0
sfr P0=ox80; //定义特殊功能寄存器P0
定义变量时如果省略“存储器类型”选项,则按编译时使用的存储器模式SMALL、COMPACT或LARGE来规定默认存储器类型,确定变量的存储器空间。函数中不能采用寄存器传递的参数变量的过程变量也保存在默认的存储器空间中。Keil C51编译器的3种存储器模式对变量的影响如下:
①SMALL:变量被定义在8051单片机片内数据存储器中,对这种变量的访问速度最快。另外,所有的对象,包括堆栈,都位于片内数据存储器中,实际的堆栈长度取决于不同函数的嵌套深度。
②COMPACT:变量被定义在分页寻址的片外数据存储器中,每一页片外数据存储器的长度为256B。这时对变量的访问是通过寄存器间接寻址(MOVX @Ri)进行的,堆栈位于8051单片机片内数据存储器中。采用这种模式的同时,必须适当改变启动配置文件STARTUP.A51中的参数PDATASTART和PDATALEN;在用BL51进行连接时,还必须采用连接控制命令“PDATA”对P2口地址进行定位,这样才能确保P2口为所需要的高8位地址。
③LARGE:变量被定义在片外数据存储器中(最大可达64KB),通常使用数据指针(DPTR)来间接访问变量(MOVX @DPTR)。这种访问数据的方法效率不高,尤其是对于2个以上字节的变量。
Keil C51编译器在不同编译模式下的存储器类型:
编译模式 |
存储器类型 |
SMALL |
DATA |
COMPACT |
PDATA |
LARGE |
XDATA |
4)运算符与表达式:
Keil C51对数据有很强的表达能力,具有十分丰富的运算符。运算符就是完成某种特定运算的符号,表达式则是由运算符及运算对象所组成的具有特定含义的算式。
运算符,按其在表达式中所起的作用,可分为赋值运算符、算术运算符、增量与减量运算符、关系运算符、逻辑运算符、位运算符、复合赋值运算符、逗号运算符、条件运算符、指针和地址运算符、强制类型转换运算符等。
⑴赋值运算符:
在C51程序中,符号“=”称为赋值运算符,作用是将一个数据的值赋给一个变量。
⑵算术运算符:
C语言中的算术运算符有+、-、*、/(除)和%(取余)运算符。其中加、减和乘法都符合一般的算术运算规则,但除法和取余有所不同。如果两个整数相除,其结果为整数,舍去小数部分;如果是两个浮点数相除,其结果为浮点数。取余运算要求两个运算对象均为整形数据。
⑶增量和减量运算符:
C51还提供有一种特殊的运算符,即++(增量)和--(减量)运算符,作用分别是对运算对象作加1和减1运算。增量和减量运算符只能用于变量,不能用于常数或表达式,在使用中还要注意运算符的位置。例如,++i与i++的意义不同,前者为在使用i前先使i加1,而后者则是在使用i之后再使i的值加1。
⑷关系运算符:
C语言中有以下6种关系运算符:>、<、>=、<=、= =、!=。用关系运算符将两个表达式连接起来即称为关系表达式。
⑸逻辑表达式:
C51中有以下3种逻辑运算符:||(逻辑或)、&&(逻辑与)、!(逻辑非)。用逻辑运算符将关系表达式或逻辑量连接起来即称为逻辑表达式。
关系运算符和逻辑运算符通常用来判别某个条件是否满足,关系运算和逻辑运算的结果只有0和1两种值。当所指定的条件满足时结果为1,条件不满足时结果为0。
⑹位运算符:
C51中共有以下6种位运算符:~(按位取反)、<<(左移)、>>(右移)、&(按位与)、^(按位异或)、|(按位或)。为运算符的作用是按位对变量进行运算。位运算符不能用来对浮点型数据进行操作。
⑺复合赋值运算符:
在赋值运算符“=”的前面加上其他运算符,就构成了复合赋值运算符。C51中共有以下10种复合赋值运算符:+=(加法赋值)、-=(减法赋值)、*=(乘法赋值)、/=(除法赋值)、%=(取模赋值)、<<=(左移位赋值)、>>=(右移位赋值)、&=(逻辑与赋值)、|=(逻辑或赋值)、^=(逻辑异或赋值)、~=(逻辑非赋值)。
复合赋值运算,首先对变量进行某种操作,然后将运算的结果再赋给该变量。采用复合赋值运算符,可以使程序简化,同时还可以提高程序的编译效率。
⑻逗号运算符:
在C51程序中,逗号“,”是一种特殊的运算符,用逗号运算符连接起来的两个(或多个)表达式,称为逗号表达式。在程序运行时,对逗号表达式的处理是从左至右依次计算出各个表达式的值,而整个逗号表达式的值是最右边表达式(即表达式n)的值。
⑼条件运算符:
条件运算符“?:”是C51中唯一的三目运算符,它要求有3个运算对象,用它可以将3个表达式连接构成一个条件表达式。条件表达式的一般形式如下:
逻辑表达式 ? 表达式1 : 表达式2
其功能是:首先计算逻辑表达式,当值为真(非0值)时,将表达式1的值作为整个条件表达式的值;当逻辑表达式的值为假(0值)时,将表达式2的值作为整个条件表达式的值。
例如,条件表达式max=(a>b)?a:b的执行结果是将a和b中的较大者赋值给变量max。另外,条件表达式中逻辑表达式的类型可以与表达式1和表达式2的类型不一样。
⑽指针和地址运算符:
C51中专门规定了一种指针类型的数据,变量的指针就是该变量的地址,还可以定义一个指向某个变量的指针变量。C51提供了以下两个专门的运算符:*(取内容)、&(取地址)。
取内容和取地址运算的一般形式如下:
变量=*指针变量
指针变量=&目标变量
取内容运算的含义是将指针变量所指向的目标变量的值赋给左边的变量;取地址运算的含义是将目标变量的地址赋给左边的变量。需要注意,指针变量中只能存放地址(即指针型数据),不要将一个非指针类型的数据赋值给一个指针变量。示例:
char data *p //定义指针变量
p=30H //给指针变量赋值,30H为8051片内RAM地址
⑾C51对存储器和特殊功能寄存器的访问:
由于8051单片机存储器结构自身的特点,C51提供了利用库函数中的绝对地址访问头文件“absacc.h”来访问不同区域的存储器以及片外扩展I/O端口的方法。
在“absacc.h”头文件中进行了如下宏定义:
CBYTE[地址] (访问CODE区char型)
DBYTE[地址] (访问DATA区char型)
PBYTE[地址] (访问PDATA区或I/O端口char型)
XBYTE[地址] (访问XDATA区或I/O端口char型)
CWORD[地址] (访问CODE区int型)
DWORD[地址] (访问DATA区int型)
PWORD[地址] (访问PDATA区或I/O端口int型)
XWORD[地址] (访问XDATA区或I/O端口int型)
下面的语句是向片外扩展端口地址7FFFH写入一个字符型数据:
XBYTE[0x7FFF]=0x80;
下面的语句是将int型数据0x9988送入外部RAM单元0000H和0001H:
XWORD[0]=0x9988;
如果采用如下语句定义一个D/A转换器端口地址:
#define DAC0832 XBYTE[0x7FFF]
那么程序文件中所有出现DAC0832的地方,就是对地址为0x7FFFH的外部RAM单元或I/O端口进行访问。
8051单片机具有100多个品种,为了方便访问不同品种单片机内部特殊功能寄存器,C51提供了多个相关头文件,如reg51.h、reg52.h等。在头文件中,对单片机内部特殊功能寄存器及其可寻址位进行了定义,编程时只要根据所采用的单片机,在程序文件的开始处用文件包含处理命令“#include”将相关头文件包含进来,然后就可以直接引用特殊功能寄存器了。例如,下面语句完成的8051定时方式寄存器TMOD的赋值:
#include <reg51.h>
TMOD=0x20;
⑿强制类型转换运算符:
C语言中的圆括号“()”也可作为一种运算符使用,这就是强制类型转换运算符,它的作用是将表达式或变量的类型强制转换成为所指定的类型。
数据类型转换分为隐式转换和显式转换。隐式转换是在对程序编译时由编译器自动处理的,并且只有基本数据类型(即char、int、long和float)可以进行隐式转换,其他数据类型不能进行隐式转换,这种情况下就必须利用强制类型转换运算符来进行显式转换。强制类型转换运算符的一般使用形式如下:
(类型)=表达式
显式强制类型转换在给指针变量赋值时特别有用。例如,预先在8051单片机的片外数据存储器(XDATA)中定义了一个字符型指针变量px,如果想给这个指针变量赋一初值0xB000,可以写成px=(char xdata *)0xB00
2. C51程序的基本语句:
C51提供了十分丰富的程序控制语句。
1)表达式语句:
表达式语句是最基本的一种语句。在表达式的后面加一个分号“;”,就构成了表达式语句。表达式语句也可以仅由一个分号“;”组成,这种语句称为空语句。当程序在语法上需要有一个语句,但在语义上并不要求有具体的动作时,便可以采用空语句。
2)复合语句:
复合语句,是由若干条语句组合而成的一种语句,是用“{}”将若干条语句组合在一起而形成的功能块,内部的各条单语句需以“;”结束。复合语句的一般形式如下:
{
局部变量定义;
语句1;
语句2;
... ...
语句n;
}
复合语句在执行时,其中各条语句依次顺序执行。整个复合语句在语法上等价于一条单语句。复合语句允许嵌套,即在复合语句内部还可以包含其他的复合语句。实际上,函数的执行部分(函数体)就是一个复合语句。复合语句中的单语句一般是可执行语句,也可以是变量定义语句。在复合语句内所定义的变量,称为该复合语句中的局部变量,仅在当前复合语句中有效。
3)条件语句:
条件语句,又称为分支语句,是用关键字“if”构成的。C51提供了3种形式的条件语句:
①if (表达式) 语句
含义是:若条件表达式的结果为真(非0值),执行后面的语句;反之,若条件表达式的结果为假(0值),不执行后面的语句。其中的语句也可以是复合语句。
②if (表达式) 语句1
else 语句2
含义是:若条件表达式的结果为真(非0值),执行语句1;反之,若条件表达式的结果为假(0值),执行语句2。其中的语句都可以是复合语句。
③if (条件表达式1) 语句1
else if (条件表达式2) 语句2
else if (条件表达式3) 语句3
... ...
else if (条件表达式m) 语句m
else 语句n
这种条件语句常用来实现多方向条件分支。当分支较多时,会使条件语句的嵌套层次太多,程序冗长,可读性降低。
4)开关语句:
开关语句也是一种用来实现多方向条件分支的语句。开关语句直接处理多分支选择,使程序结构清晰,使用方便。开关语句是用关键字switch构成的,一般形式如下:
switch (表达式)
{
case 常量表达式1:语句1;
break;
case 常量表达式2:语句2;
break;
... ...
case 常量表达式n:语句n;
break;
default:语句d;
}
开关语句的执行过程是:将switch后面的表达式的值与case后面各个常量表达式的值逐个进行比较,若遇到匹配时,就执行相应case后面的语句,然后执行break语句。若无匹配的情况,则只执行default后面的语句d。break语句又称间断语句,功能是中止当前语句的执行,使程序跳出switch语句。
5)循环语句:
实际应用中,很多地方要用到循环控制,如对于某种操作需要反复进行多次等。在C51程序中,用来构成循环控制的语句有while、do-while、for以及goto等形式的语句。
①采用while构成的循环结构:
while (表达式) 语句;
含义是:当条件表达式的结果为真(非0值)时,程序就重复执行后面的语句,一直执行到条件表达式的结果变为假(0值)为止。这种循环结构是先检查条件表达式所给出的条件,再根据检查结果决定是否执行后面的语句。如果条件表达式的结果一开始就为假,则后面的语句一次也不会被执行。这里的语句可以是复合语句。
②采用do-while构成的循环结构:
do 语句 while (表达式);
含义是:当条件表达式的值为真(非0值)时,则重复执行循环体语句,直到条件表达式的值变为假(0值)为止。这种循环结构是先执行给定的循环语句,然后再检查条件表达式的结果,任何条件下循环体语句至少会被执行一次。
③采用for构成的循环结构:
for ([初值设定表达式];[循环条件表达式];[更新表达式]) 语句;
for语句的执行过程是:先计算初值表达式的值作为循环控制变量的初值,再检查循环条件表达式的结果,当满足条件时就执行循环体语句并计算更新表达式,然后再根据更新表达式的计算结果来判断循环条件是否满足... ...一直进行到循环条件表达式的结果为假(0值)时退出循环体。
6)goto、break、continue语句:
①goto语句:
是一个无条件转向语句,它的一般形式如下:
goto 语句标号;
其中,语句标号是一个带冒号“:”的标识符。将goto语句与if语句一起使用,可以构成一个循环语句,但更常见的是采用goto语句来跳出多重循环。需要注意,只能用goto语句从内层循环跳到外层循环,而不允许从外层循环跳到内层循环。
②break语句:
break语句也用于跳出循环语句,只能用于开关语句和循环语句中,是一种具有特殊功能的无条件转移语句。对于多重循环的情况,break语句只能跳出它所处的那一层循环。
③continue语句:
continue语句是一种中断语句,功能是中断本次循环,通常和条件语句一起用在由while、do-while和for语句构成的循环结构中,也是一种具有特殊功能的无条件转移语句。但与break语句不同,continue语句并不跳出循环体,而只是根据循环控制条件确定是否继续执行循环语句。
7)返回语句:
返回语句用于终止函数的执行,并控制程序返回到调用该函数时所处的位置。返回语句有两种形式:
return (表达式);
return;
如果return语句后面带有表达式,则要计算表达式的值,并将表达式的值作为该函数的返回值。若使用不带表达式的形式,则被调用函数返回主函数时函数值不确定。一个函数的内部,可以含有多个return语句,但程序仅执行其中的一个return语句而返回主调用函数。一个函数的内部也可以没有return语句,在这种情况下,当程序执行到最后一个界限符“}”处时,就自动返回主调函数。
3. 函数:
从用户的角度看,有两种函数,即标准库函数和用户自定义函数。标准库函数是Keil C51编译器提供的,可以直接调用;用户自定义函数是用户根据自己的需要编写的,必须先进行定义之后才能调用。
1)函数的定义:
函数定义的一般形式如下:
函数类型 函数名(形式参数表)
{
局部变量定义;
函数体语句;
}
其中,函数类型说明了自定义函数返回值的类型;函数名是用标识符表示的自定义函数的名字;形式参数表中则列出了主调用函数与被调用函数之间传递的数据,形式参数的类型必须加以说明。局部变量是对在函数内部使用的局部变量进行定义;函数体语句是为完成该函数的特定功能而设置的各种语句。
ANSI C标准,允许在形式参数表中对形式参数的类型进行说明。如果定义的是无参函数,可以没有形式参数表,但圆括号“()”不能省略。
2)函数的调用:
所谓函数调用,就是在一个函数体中引用另外一个已经定义了的函数,前者称为主调函数,后者称为被调用函数。C51程序中,函数是可以互相调用的。函数调用的一般形式为:
函数名 (实际参数表)
其中,函数名指出被调用的函数;实际参数表中可以包括多个实际参数,各个参数之间用逗号“,”分开。实际参数的作用是将它的值传递给被调用函数中的形式参数。需要注意,函数调用中的实际参数与函数定义中的形式参数必须在个数、类型及顺序上严格保持一致,以便将实际参数的值正确地传递给形式参数,否则在函数调用时会产生意想不到的结果。如果调用的是无参函数,则可以没有形式参数表,但圆括号“()”不能省略。
C51中可以采用3种方式完成函数的调用:
①函数语句:
在主调函数中,将函数调用作为一条语句。这是无参调用,不要求被调用函数返回一个确定的值,只要求它完成一定的操作。
②函数表达式:
在主调函数中,将函数调用作为一个运算对象直接出现在表达式中,这种表达式称为函数表达式。这种函数调用方式通常要求被调用函数返回一个确定的值。
③函数参数:
在主调函数中,将函数调用作为另一个函数调用的实际参数。这种在调用一个函数的过程中又调用了另外一个函数的方式,称为嵌套函数调用。
3)函数的说明:
与使用变量一样,在调用一个函数之前(包括标准库函数),必须对该函数的类型进行说明,即“先说明,后调用”。如果调用的是库函数,一般应在程序的开始处用预处理命令#include将有关函数说明的头文件包含进来。
如果调用的是用户自定义函数,而且函数与调用它的主调函数在同一个文件中,一般应该在主调用函数中对被调用函数的类型进行说明。函数说明的一般形式如下:
类型标识符 被调用的函数名(形式参数表);
其中,类型标识符说明了函数的返回值的类型;形式参数表说明了各个形式参数的类型。
需要注意,函数定义与函数说明是完全不同的,书写方式上也有差别。函数定义时,被定义函数名的圆括号后面没有分号“;”,函数定义还未结束,后面应接着写被定义的函数体部分;而函数说明结束时,在圆括号的后面需要有一个分号“;”作为结束标志。
4)中断服务函数:
C51支持在C语言源程序中直接编写8051单片机的中断服务函数程序,一般形式为:
函数类型 函数名(形式参数表) [interrupt n] [using n]
关键字interrupt后面的n是中断号,n的取值范围为0~31.编译器从8n+3处产生中断向量,具体的中断号n和中断向量取决于8051系列单片机芯片的型号。常用的中断源与中断向量表如下表:
中断号n |
中断源 |
中断向量8n+3 |
0 |
外部中断0 |
0003H |
1 |
定时器0 |
000BH |
2 |
外部中断1 |
0013H |
3 |
定时器1 |
001BH |
4 |
串行口 |
0023H |
8051系列单片机可以在片内PAM中使用4个不同的工作寄存器组,每个寄存器组中包含8个工作寄存器(R0~R7)。C51编译器扩展了一个关键字using,专门用来选择8051单片机中不同的工作寄存器组。using后面的n是一个0~3的常整数,分别选中4个不同的工作寄存器组。在定义一个函数时,using是一个选项,如果不用该选项,则由编译器自动选择一个寄存器组做绝对寄存器组访问。
编写8051单片机中断函数时应遵循以下规则:
①中断函数不能进行参数传递,也没有返回值,因此一般定义为void型。
②在任何情况下都不能直接调用中断函数,否则会产生编译错误。
③如果中断函数中调用了其他函数,则被调用函数所使用的寄存器组必须与中断函数相同。
④C51编译器从绝对地址8n+3处产生一个中断向量,其中n为中断号,该向量包含一个到中断函数入口地址的绝对跳转。
5. Keil C51编译器对ANSI C的扩展:
1)存储器类型:
8051单片机的存储器空间可分为片内外统一编址的程序寄存器ROM、片内数据存储器RAM和片外数据存储器RAM。
①C51编译器对ROM存储器提供了存储器类型标识符code,用户的应用程序代码以及各种表格常数被定为在code空间。
②数据存储器RAM用于存放各种变量,通常应尽可能将变量放在片内RAM中以加快操作速度。C51编译器对片内RAM提供了3种存储器类型标识符,即data、idata、bdata。data地址范围为0x00~0x7f,位于data空间的变量以直接寻址方式操作,速度很快;idata地址范围为0x00~0xff,位于idata空间的变量以寄存器间接寻址方式操作,速度略慢于data空间;bdata地址范围为0x20~0x2f,位于bdata空间的变量,除了可以进行直接寻址或间接寻址之外,还可以进行位寻址操作。
③片外数据RAM简称XRAM,C51提供了xdata和pdata两个存储器类型标识符。xdata空间地址范围为0x0000~0xffff,位于xdata空间的变量以MOVX @DPTR方式寻址,可以操作整个64KB地址范围内的变量,但这种方式速度最慢;pdata空间又称为片外分页XRAM空间,它将地址0x0000~0xffff均匀地分成256页,每页地址都为0x00~0xff,位于pdata空间的变量以MOVX @R0、MOVX @R1方式寻址。实际上,XRAM空间并非全部用于存放变量,用户扩展的I/O接口也位于XRAM地址范围之内。有些新型的80C51单片机还提供有片内XRAM,其操作方式与传统的XRAM相同,但一般要先对相应的特殊功能寄存器(SFR)进行配置之后才能使用。
一些新型的8051单片机能够进行大容量存储器扩展,如到8MB甚至16MB的code和xdata存储器空间。C51编译器针对这种大容量扩展存储器定义了far和const far两种存储器类型,分别用以操作这种扩展的片外RAM和片外ROM空间。
对于传统的8051单片机,如果具有可以映射到xdata的附加存储器空间,或者提供了一种地址扩展特殊功能寄存器,则可以根据具体硬件电路通过修改配置文件XBANKING.A51来使用far和const far类型的变量。需要注意,在使用far和const far存储器类型时,必须采用LX51扩展连接定位器,同时还必须采用OMF2格式的目标文件。
下表列出了Keil C51编译器能够识别的存储器类型:
存储器类型 |
说明 |
code |
程序存储器(64KB),用MOVC @A+DPTR指令访问 |
data |
直接寻址的片内数据存储器(128B),访问速度最快 |
idata |
间接访问的片内数据存储器(256B),允许访问全部片内地址 |
bdata |
可位寻址的片内数据存储器(16B),允许位与字节混合访问 |
xdata |
片外数据存储器(64KB),用MOVX @DPTR指令访问 |
pdata |
分页寻址的片外数据存储器(256B),用MOVX @R0、MOVX @R1指令访问 |
far |
高达16MB的扩展RAM和ROM,专用芯片扩展访问或用户自定义子程序进行访问 |
2)编译模式:
如果定义变量时没有明确指出具体的存储器类型,则按C51编译器采用的编译模式来确定变量的默认存储器空间。Keil C51编译器控制命令SMALL、COMPACT、LARGE对变量存储空间的影响如下:
①SMALL:所有变量都被定义在8051单片机的片内RAM中,对这种变量的访问速度最快。另外,堆栈也必须位于片内RAM中,实际的堆栈长度取决于不同函数的嵌套深度。采用SMALL编译模式,与定义变量时指定data存储器类型具有相同的效果。
②COMPACT:所有变量被定义在分页寻址的片外XRAM中,每一页片外XRAM的长度都为256B。这时对变量的访问是通过寄存器间接寻址(MOVX @R0、MOVX @R1)进行的,变量的低8位地址由R0或R1确定,变量的高8位地址由P2口确定。采用这种模式时,必须适当改变配置文件STARTUP.A51中参数PDATASTART和PDATALEN;同时还必须在编译器菜单“option选项/BL51 Locator标签栏/Pdata”打开的对话框中键入合适的地址参数,以确保P2口能输出所需要的高8位地址。而堆栈则位于8051单片机片内数据存储器中。采用COMPACT编译模式,与定义变量时指定pdata存储器类型具有相同的效果。
③LARGE:所有变量被定义在片外XRAM中(最大可达64KB),通常使用数据指针(DPTR)来间接访问变量(MOVX @DPTR)。这种编译模式对数据访问的效率最低,而且将增加程序的代码长度。采用LARGE编译模式与定义变量时指定xdata存储器类型具有相同的效果。
3)数据类型bit、sbit、sfr、sfr16:
Keil C51编译器支持标准C语言的数据类型,另外还根据8051单片机的特点扩展了数据类型,如bit、sbit、sfr、sfr16。
①bit:所有bit类型的变量都被定位在8051片内RAM的可位寻址区。由于8051单片机的可位寻址区只有16个字节长,所以在某个范围内最多只能声明128个bit类型变量。声明bit类型变量时,可以带有存储器类型data、idata或bdata。对于bit类型变量有如下限制:如果在函数中采用预处理命令“#pragma disable”禁止了中断,或者在函数声明时采用关键字“using n”明确进行了寄存器组切换,则该函数不能返回bit类型的值,否则C51在进行编译时会产生编译错误;另外,不能定义bit类型指针,也不能定义bit类型数组。
②sbit:sbit用于定义可独立寻址访问的位变量。C51编译器提供了一个存储器类型bdata,带有bdata存储器类型的变量被定为在8051单片机片内RAM的可位寻址区。带有bdata存储器类型的变量可以进行字节寻址,也可以进行位寻址,因此对bdata变量可用sbit指定其中任一位为可位寻址变量。需要注意,采用bdata及sbit所定义的变量都必须是全局变量,并且采用sbit定义可位寻址变量时要求基址对象的存储器类型为bdata。
例如,可先定义变量的数据类型和存储器类型:
int bdata ibase; //定义ibase为bdata整型变量
char bdata bary[4]; //定义bary[4]为bdata字符型数组
然后使用sbit定义可位寻址变量:
sbit mybit0= ibase^0; //定义mybit0为ibase的第0位
sbit mybit15= ibase^15; //定义mybit15为ibase的第15位
sbit Ary07= bary[0]^7; //定义Ary07为 bary[0]的第7位
sbit Ary37= bary[3]^7; //定义Ary37为 bary[3]的第7位
操作符“^”后面的数值范围取决于基址变量的数据类型,对于char型是0~7,对于int型是0~15,对于long型是0~31。bdata变量ibase和bdata数组bary[4]可以进行字或字节寻址,sbit变量可以直接操作可寻址位。例如:
ibase=-1; //字寻址,对ibase赋值为-1
bary[3]=’a’; //字节寻址,对bary[3]赋值为’a’
Ary37=0; //清0 bary[3]的第7位
mybit15=1; //置1 ibase的第15位
对bdata变量可以向对data变量一样处理,所不同的
- 上一篇: C51简介及Keil的使用
- 下一篇: Keil C51使用详解V1.0