§3-1 C语言与MCS-51
一、C语言特点 C语言是一种编译型程序设计语言,它兼顾了多种高级语言的特点,并具备汇编语言的某些特点,用C语言进行程序设计已经成为软件开发的一个主流。单片机系统的开发也适应了这个潮流。与汇编语言相比,用C语言开发单片机具有如下特点:
1. 开发速度优于汇编语言;
2. 软件的可读性和可维护性显著改善;
3. 提供了库函数包含许多标准子程序,具有较强的数据处理能力;
4. 关键字及控制转移方式更接近人的思维方式;
5. 方便进行多人联合开发,进行模块化软件设计;
6. C语言本身并不依赖于机器硬件系统,移植方便;
7. 适合运行嵌入式实时操作系统;
对于MCS-51单片机的C语言:
1. 针对8051的特点对标准的C语言进行扩展。
2. 对单片机的指令系统不要求十分了解,只要对8051单片机的存储结构了初步了解,就可以编写出应用软件。
3. 寄存器的分配、不同存储器的寻址及数据类型等细节由编译器管理。
C语言编写的应用程序必须经单片机的C语言编译器(简称C51)转换生成单片机可执行代码程序。支持MCS-51系列单片机的C语言编译器有很多种。如American AutomationAuoectBso/TaskingKEIL等等。其中德国KEIL公司的C51编译器在代码生成方面领先,可产生最少代码,它支持浮点和长整数、重入和递归,使用非常方便。本章针对这种被广泛应用的KEIL C51编译器,介绍MCS-51单片机C语言的程序设计。
二、C51程序的开发过程
C语言编写单片机应用程序和编写标准的C语言程序的不同之处,在于根据单片机的存储结构及内部资源定义C语言中的数据类型和变量,其他的语法规定、程序结构及程序设计方
法与标准的C语言相同,所以在后面的几节中主要介绍如何定义C51中的变量的数据类型、存储类型、特殊功能寄存器以及中断函数,与标准C相同的部分就不再 述。
C51的开发过程和用其它语言包括汇编语言开发没有什么不同,其开发流程见下图:
1:如图所示,P1口连接8只发光二极管,要求每隔0.5秒移动一次,当P2.0为高时,发光二极管左移,否则右移。
#include <reg51.h> //标准的8051头文件,定义了所有的SFR
#include<intrins.h> // 内部函数包含到程序中,
#define uchar unsigned char
#define uint unsigned int
sbit KEY =P2^0; //定义P2.0为开关输入
void delay(void) //软件延时函数
{ uint t;
for(t=0;t<30000;t++); //空循环延时,大约0.5秒左右
}
void main(void)
{ uchar data led;
led=0xfe; //低电平点亮发光管,初始值对应最低位P1.0为低,即L0点亮
while(1)
{ P1=led; //led送到P1
delay(); //延时0.5
if(KEY) //如果KEY1(开关断开),则变量led循环左移一位
led=_crol_(led,1);
else
led=_cror_(led,1); //否则led循环右移一位
}
}
这是一个完整的C51源程序,注意主函数中由while(1)所构成的死循环,因为单片机中没有其它软件,也就没有PCC语言中所谓的退到DOSWINDOWS的概念,如果程序中没有这样的死循环,程序执行完最后一条语名,随后的结果将不可预计。
§3-2数据与数据类型
无论我们学习哪一种语言,首先遇到的是数据类型, C51共有以下几种数据类型: 
位型:bit
字符型:char
整型:int
基本类型 长整型:long
 浮点型:float
双精度浮点型:double
 数组类型: array
数据类型 构造类型 结构体类型: struct
共用体 : union
指针类型 枚举: enum
空类型
KEIL C51所支持的基本数据说型说明如下:

数据类型
长度(bit
长度(Byte
值域范围
bit
1
 
0,1
unsigned char
8
1
0255
signed char
8
1
-128 127
unsigned int
16
2
0 65535
signed
16
2
-32768 32767
unsigned long
32
4
04 294 967 295
uigned long
32
4
union是什么类型
-21474836482147483647
float
32
4
±1.176E-38~±3.40E+38
double
64
8
±1.176E-38~±3.40E+38
一般指针
24
3
存储类型(1字节)偏移量(2字节)
有了这些数据类型,我们用变量去描述一个现实中的数据时,就应按需选择变量类型。对于C51来讲,不管采用哪一种数据类型,虽然源程序看起来是一样的,但最终形成的目标代码却大相径庭,其效率相并非常大。
例如:当我们去表示时间量秒的时候,虽然可用unsigned int类型甚到double类型,但由于秒的取值范围是059,所以采用unsigned char就够了。这样不仅节省了存贮空间,而且还可以提高程序的运行速度。因此我们在编程时应按照变量可能的取值范围、精度要求去选择恰当的数据类型。
另外,如果不涉及负数运算,要尽量采用无符号类型,这样可以提高编译后目标代码的效率。我们编程时最常用到的时无符号数运算,因此为了编程时书写的方便,我们可以采用简化的缩写形式来定义变量的数据类型。其方式是在源程序的开始处加上下面两条语句:
#define uchar unsigned char
#define uint unsigned int
这样在定义变量时,就可以使用 uchar uint 来代替 unsigned charsigned char
§3-3 C51的数据存贮类型与8051存贮器结构
8051系列单片机将程序存贮器(ROM)和数据存贮器(RAM)分开,并有各自的寻址机构和寻址方式。8051单片机在物理上有四个存贮空间:
片内程序存贮器空间:0000—0FFF
片外程序存贮器空间:1000—FFFF/EA=1 0000—FFFFEA=0
片内数据存贮器空间:
1. 1F:通用工作寄存器区
20—2F:位寻址空间
30—7F:用户RAM
80—FF:特殊功能寄存器区
片外数据存贮器空间:0000—FFFF
我们采用汇编编语言编程时,是按地址去读写指定的存储单元的,用不同的指令去表示不同的存储空间,例如:MOV指令访问片内数据存储器,MOVX指令访问片外数据存储器,MOVC指令访问程序存储器。而在C51中直接使用变量名去防问存储单元,而无需关心变量的存放地址,程序的可读性大大增加了。但变量放在哪一个存贮空间呢?这对最终目标代码
的效率影响很大。因此在编程时除了说明变量的数据类型外,还应说明变量所在的存储空间即存储类型。
C51将变量、常量定义成不同的存贮类型,以完全支持8051单片机的存贮器结构。
C51 存储类型与8051存储空间的对应关系

存贮类型
与存贮空间的对应关系
data
直接寻址片内数据存贮区,速度快(00—7F
bdata
可位寻址片内数据存贮区,允许位/字节混合访问(20—2F
idata
间接寻址片内数据存贮区,可访问全部RAM空间(00—FF)由MOV @Ri访问
pdata
分页寻址片外数据存贮区(256字节),用MOVX @Ri访问
xdata
片外数据存贮区(64字节),用MOVX @DPTR访问
code
代码存贮区(64K),由MOVC @DPTR访问
C51中变量定义的格式:
数据类型 [存储类型] 变量名1 [,变量名2]……[,变量名n]
例:
char data temp; //字符变量temp,定位在片内数存贮区,用直接寻址方式访问。
bit bdata flags; //位变量flags,定位在可位寻址片内数据存贮区。
uchar bdata speed; //无符号字符变量speed ,定位在可位寻址片内数据存贮区
uchar idata len; //无符号字符变量len ,定位在片内数据存贮区,用间接寻址
uchar code seg[ ]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d};
//定义无符号字符型数组seg,共有10个元表,存放在程序存储器中。
说明: 选择变量的存储类型时,可按以下的原则:
通常将一些固定不变的参数或表格放在程序存储器中,即存储类型设为code。例如上面所定义的seg数组就是用来存放LED数码管的字段码的。
访问片内数据存储器(存储类型为data/bdata/idata)比访问片外数据存储器的速度要快,因此对一些使用频率较高的变量或者对速度要求较高的程序中的变量可选择片内数据存储器,而将一些不常使用的变量放存片外数据存储器(存储类型为pdata/ xdata)中。当然如果系统中所用的变量较少,片内数据存储器的空间足够应付时,就无需使用片外数据存储器了
对于52子系列的单片机,其内部RAM256字节,其高128字节的地址从80HFFH,正好与特殊功能寄存器地址重叠,两者通过寻址方式加以区别,对于52子系列的高128字节的RAM,必须用R0R1寄存器间接寻址方式访问,而对特殊功能寄存器只能用直接寻址方式访问。也就是说如果希望在C51中对80H0FFH之间的数据存储器进行读写,可以将变量的存储类型定义为idata
通常将位变量或希望位与字节混合访问的变量的存储类型设置为bdata。但要注意,不能定义指向指向位型的指针,也不能定义位数组。例如:下面两条定义语名时错误的。
bit bdata *pb;
bit bdata status[8];
如果变量定义时省去存贮类型说明,编译时会自动选择默认的存储类型,而默认的存贮类型由存贮模式确定。在C51中有SMALLCOMPACTLARGE三种存储模式,在KEIL环境中,可以通过目标工具选项设置选择所需的存储模式。下面分别对这三种模式进行说明:
存贮模式作为编译选项如下表所示:

存贮模式
说明
 
SMALL
参数和局部变量放入可直接寻址的内部数据存贮器(最大128字节,默认的存储类型为DATA),速度快,访问方便。所用堆栈在片内RAM
CPMPACT
参数和局部变量放入分页外部数据存贮器(最大256字节,默认的存储类型为PDATA),通过MOVX @Ri指令间接寻址,所用堆栈在片内RAM
 
LARGE
参数和局部变量直接放入外部数据存贮器(最大64KB,默认的存储类型为XDATA),通过MOVX @DPTR指令进行访问,所形成的目标代码效率低。
例如,有一程序如下:
#define uchar unsigned char
void main(void)
{ uchar b; //由于省去了存储类型,C51会根据当前的选择存贮模式来确定b的存储类型。
b=12;
}
如果当前的存贮模式为SMALL,编译后所形成的目标代码为:
MOV 08H#0CH ;这里08H为变量b在片内RAM的地址,#0CH即为12
如果当前的存贮模式为CPMPACT,编译后所形成的目标代码为:
MOV R0#00H ;这里00H为变量b在片外RAM的页面地址,#0CH即为12
MOV A#0CH
MOVX @R0A
如果当前的存贮模式为LARGE,编译后所形成的目标代码为:
MOV DPTR#0000H ;这里0000H为变量b在片外RAM的地址,#0CH即为12
MOV A#0CH
MOVX @DPTRA
由此可见,在存储类型缺省的情况下,由当前的存贮模式确定变量的存储类型,初学者使用时一定要小心!例如,你的单片机系统并没有扩充外部数据存贮器,当前的存储模式为LARGE,而且一些变量又没有选择存储类型,这样在调试时会出现一些意想不到的错误。

教学目的:掌握8051特殊功能寄存器、并行接口、位变量的C51定义,C51 内部函数及常用的宏。
教学难点:可位寻址对象的定义及实际应用中的技巧,关键字sbitbit的差别。