nullnull第五章 单片机C51程序设计*第五章 单片机C51程序设计5.1、C51程序设计基础
5.2、C51的数据类型及其在51单片机中的存储方式
5.3、硬件资源访问
5.4、C51的运算符
5.5、C51的指针
5.6、C51 的
函数
excel方差函数excelsd函数已知函数 2 f x m x mx m 2 1 4 2拉格朗日函数pdf函数公式下载
5.7、C51的流程控制
5.8、C51编程实例
5.1 C51程序设计基础
*5.1 C51程序设计基础
汇编语言程序可以高效率利用计算机资源,目标程序占用内存少,执行速度快,适合于自动测控系统反应快速、结构紧凑的要求。
C语言程序容易掌握,通用性好,但编译程序系统开销大,目标程序占用内存多,且执行时间比较长,多用于科学计算、工业设计、企业管理。实际应用中,汇编语言常与C语言配合使用。使用C语言来进行51内核单片机的程序设计,即C51程序
设计 。5.1.1 C51语言特点*5.1.1 C51语言特点用C51编程需要根据单片机存储器结构及内部资源,定义相应的数据类型和变量,按照C51所包含的数据类型、变量存储模式、输入/输出处理、函数等方面的格式来编写C语言应用程序。
其它的语法规定、程序结构及程序设计方法,都与ANSI C相同。
用C51语言编写的应用程序必须经单片机的C语言编译器,转换生成单片机可执行的代码程序。本章是针对德国KEIL公司的C51编译器介绍C51程序设计。
C51与汇编语言相比,有如下优点:*C51与汇编语言相比,有如下优点:C51语言程序具有规范的结构,可由不同的函数组成,用这种方式编写的程序很容易被移植;
运算符和关键字用接近于自然语言的方式表示,改善了程序的可读性;
提供了包含很多数学函数及其他大量标准子程序的函数库,具有较强的数据处理能力,开发效率高;
C语言程序基本上不作修改或者进行简单的修改,就可方便地移植到其他类型的单片机上;
总之,用C语言进行单片机程序设计是单片机开发和应用的必然趋势,在进行大型、复杂的单片机应用系统开发时都通过C语言来设计程序。 5.1.2 C51程序结构*5.1.2 C51程序结构C51源程序由一个或者多个源文件组成,每个源文件扩展名都命名为“*.c” 。
每个C51程序都由一个或多个函数组成,其中有且只有一个main()函数。程序从main()函数开始执行,在main()函数中可以调用库函数和用户定义的函数。C51中还有中断函数,运行每一个函数都执行一个特定的任务来实现整个程序的功能。
C51源程序中含有预处理命令、语句、说明等,说明和语句以分号(;)结尾,预处理命令后不加分号。
程序中可以用“/*…注释…..*/”或“//…注释…”的形式对C程序中的任何部分进行注释,用于说明程序段的功能,增加程序的可读性。
null*C51程序的结构一般如下:
#include
int fun1(形参); //函数声明
char fun2(形参);
unsigned char x,y,z; //定义全局变量
……
void main( ) //主函数定义
{
主函数体……
}
int fun1(形参)//功能函数定义
{
函数体……
}
char fun2(形参)//功能函数定义
{
函数体……
}5.1.3 C51的字符集、标识符与关键字*5.1.3 C51的字符集、标识符与关键字1.C51的字符集 C51语言的字符集由大小写英文字母、数字、下划线和
空格等特殊字符构成。 2.C51标识符 标识符在源程序中用来标识某个对象的名字,由程序员定
义,用做变量名、函数名和类型名等。
标识符可以由字母、数字和下划线组成,但必须由字母或
者下划线开头。
标识符的定义不能与关键字及系统预先定义的标准标识符
(如标准函数)同名,最好“见名知意”。
C51程序中标识符区分字母的大小写。 null*3.关键字 关键字是C51已定义的具有固定名称和特定含义的特殊标
识符,又称保留字。标准C语言中规定的关键字有32个。
C51编译器除了支持ANSI C标准的关键字外,还扩展了适
应51内核单片机要求的关键字。
C51扩展关键字如下:
bdata data idata pdata xdata code bit sbit sfr
sfr16 _at_ reentrant interrupt using volatile 5.2 C51的数据类型及其在51单片机中的存储方式*5.2 C51的数据类型及其在51单片机中的存储方式数据类型是指数据的存储格式。无论哪种数据都存放在存储单元中,每一个数据究竟要占用几个单元,在编译时都要提供给编译系统。
C51的数据类型与标准C语言的数据类型基本相同,但其中char型与short型相同,float型与double型相同。
C51中还有专门针对51内核单片机的特殊功能寄存器型和位类型。5.2.1 C51的数据类型 C51具体支持的基本数据类型及其长度、值域如表所示:* C51具体支持的基本数据类型及其长度、值域如表所示:null*1、字符型char
长度为1个字节。默认为signed char。
对于signed char,其字节的最高位为符号位,“0”表示正数,“1”表示负数,补码表示,所能表示的数值范围是-128~+127。
对于unsigned char,其取值范围为0~255。也可以存放西文字符,在计算机内部用ASCII码存放。 2、整型int
长度为2个字节,默认为signed int。
对于signed int,用于存放2字节带符号数,补码表示,数的范畴为-32768~+32767。
对于unsigned int,用于存放2字节无符号数,数的范围为0~65535。null*3、长整型long
长度为4个字节,默认为signed long;
对于signed long,用于存放4字节带符号数,补码表示,数的范畴为-2147483648~+2147483647。
对于unsigned long,用于存放4字节无符号数,数的范围为0~4294967295。 长整型数据与整型数据的存储结构都是高位字节数存
放在低地址单元中,低位字节数存放在高地址单元中。
例如,长整型变量的值为0x12345678
时,在内存中的存放形式如右图所示。null*4、浮点型float
长度为4个字节。
用浮点型变量进行任何数学运算都需要使用由编译器决定的各种不同效率等级的库函数。
Keil C51的浮点型变量数据类型是IEEE-754标准的单精度浮点型数据,转换成十进制数后最多具有7位有效数字。
4个字节中包含指数和尾数两部分,尾数的最高位始终为1,因而不保存。具体分布为:1位符号位,8位阶码位,23位尾数,如下图所示。 null* 符号位S:1表示负数,0表示正数。
阶码E:共8位,是以2为底的指数再加上127,这样可以避免出现负的阶码值。如,实际阶码-126用1表示,实际阶码0用127表示,即实际阶码数加上127得到阶码的值。阶码E的正常取值范围是1~254,而对应的指数实际取值范围为-126~+127。
尾数:M为其小数部分,共23位,其整数部分隐含为“1”。
一个浮点数的取值范围:null*例如:浮点数123.25
其二进制数01111011.01B=1.11101101×2+110;
8位阶码E为6+127=133=10000101B;
23位尾数值为11101101000000000000000B;
32位浮点数表示形式为:
0100 0010 1111 0110 10000000 00000000B;
在内存中的存放格式如下表所示:null*5.位类型
位类型是C51中扩充的数据类型,用于访问51内核单片机中的可位寻址单元;
包括bit型和sbit型,它们在内存中只占1个二进制位,其值可以为“0”或者“1”。利用它们可以定义一个位变量,但不能定义位指针,也不能定义位数组。
两种位类型变量的区别在于,用bit定义的位变量,其地址由C51编译器编译时予以安排,而用sbit定义位变量时必须同时定义其位地址,在C51编译器编译时,其位地址不可变化。null*6.特殊功能寄存器型
也是C51中扩充的数据类型,用于访问51内核单片机中的特殊功能寄存器中的数据 ;
包括sfr和sfr16两种类型;
sfr为字节型特殊功能寄存器类型,占用1字节单元,利用它可以访问51内核单片机中所有的特殊功能寄存器;
sfr16为双字节型特殊功能寄存器类型,占用2字节单元,利用它可以访问单片机中2字节的特殊功能寄存器。 null*5.2.2 数据的存储器类型 C51应用程序中使用的任何数据必须以一定的存储器类型
定位于单片机的相应存储区域中。
51内核单片机中程序存储器与数据存储器严格分开。数据
存储器又分为片内、片外两个独立的寻址空间,特殊功能寄存
器与片内RAM统一编址。
使用汇编指令访问这些存储器时,通常使用不同的指令即
可。而在C51中访问这些存储器时,是通过定义不同存储类型
的变量,以说明该变量的存储器位置。 null*C51编译器支持的存储器类型如下表所示:null*5.2.3 常量和变量数据有常量和变量之分。1.常量
常量就是在程序运行过程中其值不能被改变的量,可以为字符,字符串,转义字符,十进制数或十六进制数(用0x打头表示)、浮点数等直接常量,也可以定义符号型常量,定义方式如下:
用宏定义语句定义常量,例如:
#define PI 3.1415926 //定义PI为常量3.1415926
用const 语句定义常量,例如:
const unsigned char xx=0x98;//定义xx为无符号char型常量0x98null*表5-5常用的转义字符
2.变量*2.变量在程序运行过程中其数值可以改变的量叫变量;
变量由变量名和变量值构成;
变量名是存储单元地址的符号表示;
变量的值是该地址单元存放的内容;
一个变量一旦被定义,编译系统就会自动为它安排存储单元;
要在程序中使用变量必须先用标识符作为变量名,并指出所用的数据类型和存储器类型,这样编译系统才能为变量分配相应的存储空间。
变量的定义格式如下:
[存储种类] 数据类型 [存储器类型] 变量名表;举例说明变量定义相关情况:*举例说明变量定义相关情况:char data varl; /*在片内RAM低128B范围内定义用直接寻址方式访问的字符型变量var1*/
int idata var2; //在片内RAM256B范围内定义用间接寻址方式访问的整型变量var2
auto unsigned long data var3;/*在片内RAM128B范围内定义用直接寻址方式访问的自动无符号长整型变量var3*/
extern float xdata var4; /*在扩展RAM空间范围内定义用间接寻址方式访问的外部实型变量var4*/
int code var5; //在ROM空间定义整型变量var5
unsigned char bdata var6; /*在片内RAM位寻址区20H-2FH单元范围内定义无符号字符型变量var6*/5.2.4 存储模式*5.2.4 存储模式存储模式决定变量的默认存储器类型和参数传递区。
若定义变量时指定了存储器类型,编译程序按要求为其分配存储空间,这种情况可以不必再指定存储模式;若未指定存储器类型,编译程序按照存储模式为变量选择默认的存储器类型和参数传递区。
C51有三种存储模式:SMALL、COMPACT和LARGE,以适应不同规模的程序。如果没有说明,编译程序默认使用SMALL模式。
C51的数据存储模式见表5-6。null*表5-6 C51的存储模式1.C51编译器3种存储模式的优缺点*1.C51编译器3种存储模式的优缺点SMALL:所有变量默认存放于片内RAM中,这时的变量访问速度最快、效率最高,但由于空间有限,只适用于小程序。
COMPACT:所有变量默认存放于片内扩展RAM或片外RAM区低256B中,对于片外RAM,具体哪一页可由P2口指定。优点是空间比SMALL大,但由于必须使用间接寻址方式访问变量,所以速度较SMALL慢,但比LARGE模式快。
LARGE:所有变量默认存放于片内扩展RAM或片外RAM中,最多可有64KB。空间最大,但用数据指针DPTR来寻址变量,速度较慢。产生的机器码比SMALL和COMPACT模式都多。
由于各种存储模式在访问效率、代码长度、变量总长度等方面各有优缺点,现在常用的C51编译程序通常支持混合模式。即不管存储模式如何,把经常使用的变量存放于片内RAM,大块数据则存放于扩展RAM而将其指针存放于片内RAM中。2.数据存储模式设定*2.数据存储模式设定 数据存储模式的设定有两种方式,即使用预处理命令或使用编译控制命令。
使用预处理命令设定数据存储模式
使用预处理命令设定数据存储模式时,只需要在程序的开头处加预处理命令即可。例如:
#pragma compact //设定数据存储模式为紧凑编译模式
使用编译控制命令设定数据存储模式
使用编译控制命令设定数据存储模式,是在用C51编译程序对C51源程序进行编译时,使用编译控制命令。例如:
C51 file1.C LARGE //设定源程序file1.c中的数据存储模式为大编译模式
函数的存储模式可通过在函数定义时后面带存储模式说明。null*
#pragma small //变量的存储模式为SMALL
char x,y; //两个变量x,y的存储器类型为data
static char m,n; //两个静态变量m,n的存储器类型为data
#pragma compact //变量的存储模式为COMPACT
char k; //变量k的存储器类型为pdata
int xdata z; //变量z的存储器类型为xdata
int func1(int x1,int y1) large //函数的存储模式为LARGE
{
return(x1+y1); //形参x1和y1的存储器类型为xdata型
}
int func2(int x2,int y2) //函数的存储模式默认为SMALL
{
return(x2-y2); //形参x2和y2的存储器类型为data
}【例5-1】变量和函数的存储模式设置。5.3 硬件资源访问*5.3 硬件资源访问 51内核单片机中,除了程序计数器PC和8个通用工作寄存器之外,其它所有的寄存器均为特殊功能寄存器(SFR)。它们分散在片内RAM区的高128B(0x80-0xFF),为了对它们进行直接访问,C51编译器利用扩展数据类型sfr和sfr16对这些特殊功能寄存器进行定义。 5.3.1 C51对特殊功能寄存器的定义null*
例如:
sfr PSW=0xd0; //程序状态字寄存器地址0D0H
sfr SCON=0x98; //串行口控制寄存器地址98H
sfr TMOD=0x89; //定时/计数器模式寄存器地址89H
sfr P1=0x90; //P1端口地址90H
1.用sfr定义8位特殊功能寄存器sfr 特殊功能寄存器名=特殊功能寄存器地址常数定义之后,在程序中就可以直接引用寄存器名。null*
sfr PORT_ZERO=0X80;
sfr ACC= 0XE0;
sfr NEW_CON=0XFF;
sfr C_REG=0X22; 【例5-2】在基于STC12C5A60S2单片机的C51语言编程中,
使用了下面特殊功能寄存器定义的语句,指出哪些是合法的,
哪些是有效的,哪些是非法的。//合法有效,PORT_ZERO
相当于P0口的别名
//合法有效,A寄存器的定义
//合法无效,该地址无实际SFR//不合法,地址不在允许范围内2.用sfr16定义16位特殊功能寄存器*2.用sfr16定义16位特殊功能寄存器当16位寄存器的高8位地址紧排在低8位地址之后时,就可以定义一个16位特殊功能寄存器变量。
定义格式为:
sfr16 特殊功能寄存器名=16位特殊功能寄存器中低8位的地址
例如:
sfr16 DPTR=0x82;/*指定DPTR寄存器地址为0x82,其中DPL的地址为0x82,DPH的地址为0x83*/
注意:这种定义适用于所有新的16位SFR,但不能用
于定时/计数器T0和定时/计数器T1的定义。3.说明
*3.说明
定义特殊功能寄存器中的地址必须在0x80-0xff范围内;
定义特殊功能寄存器,必须放在函数外面作为全局变量;
用sfr或sfr16每次只能定义一个特殊功能寄存器;
用sfr或sfr16定义的变量必须是真实的特殊功能寄存器,其地址是固定的,不能像一般变量那样随便定义。 5.3.2 C51对位变量的定义 * 5.3.2 C51对位变量的定义 1.特殊功能寄存器中特定位的定义
如果特殊功能寄存器的字节地址能被8整除,则这样的特殊功能寄存器具有位寻址功能,可以用关键字sbit来定义特定位。定义方法有多种,分别如下:
用寄存器名位定义
sfr PSW=0xd0; //定义PSW地址为D0H
sbit CY=PSW^7; //CY为PSW.7
用字节地址位表示
sbit CY=0xd0^7; //定义CY为字节地址0xd0单元的第7位,即PSW.7
直接用位地址表示
sbit CY=0xd7; //定义CY位地址为0xd7,即PSW.7null*使用头文件,再直接用位名称
#include
TR0=1;EA=1;TF0=0;//在文件中已定义了TR0、EA、
TF0……等位
使用头文件及sbit定义符,用于无位名称的可位寻址位。
#include
sbit P1_0=P1^0;
sbit a_7=ACC^7;
由于在51内核单片机产品中特殊功能寄存器的数量和类型
不尽相同,C51建立了一个头文件reg51.h(增强型为reg52.h),STC系列单片机的头文件有STC90.h、STC10.h、STC11.h、STC12C5A.h、STC12C52.h、STC12C54.h、STC12C56.h、STC15.h、STC89.h等。null* 在这些头文件中对相应系列的所有特殊功能寄存器进行
了sfr定义,对特殊功能寄存器中有位名称并且可位寻址位进行了sbit定义,只要使用了#include< STC12C5A.h >或#include等包含语句,就可以直接引用该文件中已经定义过的特殊功能寄存器名及位名称。
注意:在引用特殊功能寄存器名及位名称时必须大写。
我们也可以用自己喜欢的名字写定义文件,使用时只要用包含语句#include“****.h”就可以直接引用已定义的特殊功能寄存器名和位名称。 null*程序如下:
#include< STC12C5A.h>
sbit P1_0=P1^0;
void main( )
{
P1_0=0;
while(1) ;
}【例5-3】利用STC12C5A60S2单片机P1口的P1.0 引脚接一
只LED灯,如图5-2所示。要求一直点亮。 2.对一般位变量的定义
*2.对一般位变量的定义
在C51中,经常用位变量标记某些事件的状态,如标记某电机的停与转等。
位变量的地址位于片内RAM的可位寻址区(20H-2FH单元),用户通过位类型符定义位变量。
格式如下:
bit 位变量名[=初值];//其地址可由C51编译器编译时自动分配
sbit 位变量名=位地址;//定义时直接确定其地址
在格式中可以加上各种修饰,但注意存储器类型只能是bdata、data、idata。只能是片内RAM的可位寻址区,严格来说只能是bdata。null*
bit data a1;
bit bdata a2;
bit xdata a4; 【例5-4】判断以下定义是否正确。 //正确,a1的位地址由C51编译器自行安排在bdata区//正确,a2的位地址由C51编译器自行安排在bdata区
//错误【例5-5】用 sbit定义一般位变量。 char bdata temp; //在位寻址区定义一个字符型变量temp
sbit bit7=temp^7; //bit7定义为temp的第7位
bit7=0; //位寻址,将temp的第7位置03.说明*3.说明bit型位变量与其它变量一样,可以作为函数的形参,也可以作为函数的返回值;
位变量不能定义指针,不能定义数组。
用sbit定义的位变量,必须能够按位操作,而不能够对无位操作功能的位定义位变量。
用sbit定义位变量,必须放在函数外面作为全局位变量,而不能在函数内部定义。
用sbit每次只能定义一个位变量。
对其它模块定义的位变量(bit型或 sbit型)的引用声明,都使用bit。null*【例5-6】指出下面程序中各语句的作用。 #include< STC12C5A.h>
unsigned char bdata data1;//在位寻址区定义字节变量data1
sbit lsb =data1^0; //定义位变量lsb为data1的最低位
sbit msb=data1^7; //定义位变量msb为data1的最高位
void main(void)
{
bit flag=1; //定义局部位变量flag,并赋初值为1
data1=‘A’; //给字节变量data1赋值
while(1)
{
flag=!flag; //位变量flag取反
msb=flag; //将flag的值赋给msb
lsb=flag; //将flag的值赋给lsb
P1=data1; //将data1变量通过P1口输出
}
}5.3.3 C51对存储器和外部I/O接口的绝对地址访问*5.3.3 C51对存储器和外部I/O接口的绝对地址访问在具有外部I/O接口电路的应用中,外部接口电路的地址是由硬件电路确定的,以变量形式对其访问时,必须事先指定变量在系统中的绝对地址;也有一些变量需要明确在存储器中的具体地址,而不是让编译程序自行分配。
对这些变量的访问就是对绝对地址访问。
下面介绍使用宏定义、关键字_at_以及指针来访问绝对地址。 1.使用宏定义访问绝对地址*1.使用宏定义访问绝对地址这些宏定义的函数原型所在头文件为absacc.h。
定义绝对地址变量的格式如下:
#include
#define 变量名 CBYTE[绝对地址]
#define 变量名 DBYTE[绝对地址]
#define 变量名 PBYTE[绝对地址]
#define 变量名 XBYTE[绝对地址]
#define 变量名 CWORD[绝对地址]
#define 变量名 DWORD[绝对地址]
#define 变量名 PWORD[绝对地址]
#define 变量名 XWORD[绝对地址]1.使用宏定义访问绝对地址*1.使用宏定义访问绝对地址 #define 变量名 CBYTE[绝对地址]CBYTE指定绝对地址在单片机程序存储器中的字节变量;1.使用宏定义访问绝对地址*1.使用宏定义访问绝对地址 #define 变量名 CBYTE[绝对地址]
#define 变量名 DBYTE[绝对地址] DBYTE指定绝对地址在单片机片内RAM中字节变量;
1.使用宏定义访问绝对地址*1.使用宏定义访问绝对地址 #define 变量名 CBYTE[绝对地址]
#define 变量名 DBYTE[绝对地址]
#define 变量名 PBYTE[绝对地址] PBYTE指定绝对地址在单片机片内扩展RAM或片外RAM低256B中的字节变量;1.使用宏定义访问绝对地址*1.使用宏定义访问绝对地址 #define 变量名 CBYTE[绝对地址]
#define 变量名 DBYTE[绝对地址]
#define 变量名 PBYTE[绝对地址]
#define 变量名 XBYTE[绝对地址] XBYTE指定绝对地址在单片机片内扩展RAM或片外RAM中的字节变量;1.使用宏定义访问绝对地址*1.使用宏定义访问绝对地址 #define 变量名 CBYTE[绝对地址]
#define 变量名 DBYTE[绝对地址]
#define 变量名 PBYTE[绝对地址]
#define 变量名 XBYTE[绝对地址]
#define 变量名 CWORD[绝对地址] CWORD指定绝对地址在单片机程序存储器中的字变量,该字变量的低地址为中括号中的数乘2;1.使用宏定义访问绝对地址*1.使用宏定义访问绝对地址 #define 变量名 CBYTE[绝对地址]
#define 变量名 DBYTE[绝对地址]
#define 变量名 PBYTE[绝对地址]
#define 变量名 XBYTE[绝对地址]
#define 变量名 CWORD[绝对地址]
#define 变量名 DWORD[绝对地址] DWORD指定绝对地址在单片机片内RAM中的字变量,该字变量的低地址为中括号中的数乘2;1.使用宏定义访问绝对地址*1.使用宏定义访问绝对地址 #define 变量名 CBYTE[绝对地址]
#define 变量名 DBYTE[绝对地址]
#define 变量名 PBYTE[绝对地址]
#define 变量名 XBYTE[绝对地址]
#define 变量名 CWORD[绝对地址]
#define 变量名 DWORD[绝对地址]
#define 变量名 PWORD[绝对地址] PWORD指定绝对地址在单片机片内扩展RAM或片外RAM低256B中的字变量,该字变量的低地址为中括号中的数乘2;1.使用宏定义访问绝对地址*1.使用宏定义访问绝对地址 #define 变量名 CBYTE[绝对地址]
#define 变量名 DBYTE[绝对地址]
#define 变量名 PBYTE[绝对地址]
#define 变量名 XBYTE[绝对地址]
#define 变量名 CWORD[绝对地址]
#define 变量名 DWORD[绝对地址]
#define 变量名 PWORD[绝对地址]
#define 变量名 XWORD[绝对地址] XWORD指定绝对地址在单片机片内扩展RAM或片外RAM中的字变量,该字变量的低地址为中括号中的数乘2;null*CBYTE、DBYTE、PBYTE、XBYTE、CWORD、DWORD、PWORD和XWORD为8个宏名。
也可以不使用宏定义的方法,直接使用这些宏名进行绝对地址的访问。
访问形式如下:
宏名[绝对地址]
【例5-7】使用宏定义的方法访问片外并行I/O口或存储单元。 #include
#define PA XBYTE[0xfffe] /*PA定义为地址为0xfffe的字节变量 */
void main()
{
PA=0xFF;/*将数据FFH写入地址为0xfffe的I/O端口或存储单元*/
}【例5-8】使用宏名访问存储器。*【例5-8】使用宏名访问存储器。#include //将绝对地址头文件包含在文件中
#define uchar unsigned char
#define uint unsigned int
void main(void)
{ uchar var1;
uint var2;
var1=XBYTE[0x0003];
var2=XWORD[0x0002]; /*读片内扩展RAM的0004H-0005H字单元的数据赋给变量var2*/
XBYTE[0x0003]=0x7f;
XWORD[0x0002]=0x7fff; /*将数据0x7fff写入片内扩展RAM的0004H-0005H地址中*/
while(1);
}2.使用关键字_at_访问绝对地址*2.使用关键字_at_访问绝对地址 [存储器类型] 数据类型 变量名 _at_ 地址常数;
存储器类型为data、bdata、idata、pdata等;
地址常数用于指定变量的绝对地址,必须位于有效的存储器空间之内;这种变量定义时不能初始化。说明:*说明:不能对code型变量绝对定位;
绝对地址变量必须是全局变量,不能在函数中对变量绝对定位;
绝对地址变量多用于I/O端口,一般情况下不对变量作绝对定位;
函数和bit型变量不能使用_at_绝对定位。【例5-9】使用关键字_at_实现绝对地址的访问 。 #define uchar unsigned char
#define uint unsigned int
data uchar x1 _at_ 0x40;
xdata uint x2 _at_ 0x2000;
void main(void)
{ x1=0xff; x2=0x1234;
......
}3.使用指针访问绝对地址*3.使用指针访问绝对地址【例5-10】通过指针实现绝对地址的访问。
#define uchar unsigned char
#define uint unsigned int
void func( )
{
uchar data var1;
uchar pdata *dp1;
uint xdata *dp2;
uchar data *dp3;
dp1=0x30;
dp2=0x1000;
*dp1=0xff;
*dp2=0x1234;
dp3=&var1;
*dp3=0x20;
}5.4 C51 的运算符*5.4 C51 的运算符1.赋值运算符
赋值运算符的作用是将“=”右边的数据(常量或表达式)赋给左边的变量。
2.算术运算符
基本算术运算符有:+ (加或正号)、-(减或负号)、 *(乘号)、/ (除号)、%(求余)。
优先级为:先乘除,后加减,先括号内,再括号外。
3.关系运算符
关系运算符有:<(小于)、 <=(小于等于)、> (大于)、>=(大于等于)、==(相等)、!=(不相等)
优先级:前四个高,后两个低。null*4.逻辑运算符
逻辑运算符共有三种:!(逻辑非)、&&(逻辑与)、||(逻辑或),逻辑表达式和关系表达式都是以0代表假,以1代表真。
5.位操作运算符
C51支持的位操作运算符有:&(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(位左移)、>>(位右移)
需要说明的是:无符号数的右移运算是高端移入0,低端移出舍掉。有符号数的右移运算是高端移入操作数的符号位,低端移出位被舍掉。
例如:signed char a=-2,b; b=a>>1;
执行结果为b=0xff。因为a为负数,以其补码形式出现,即a=0xfe,右移一位,各位依次右移一位,最高位移入符号位1,最低位移出舍掉,则b为0xff。6.自增、自减运算符*6.自增、自减运算符自增(++)、自减(--)运算符的作用是使变量值加1或减1。
包括前置和后置两种形式:
前置 ++i,- -i (先执行i+1或i-1,再使用i值)
后置 i++,i-- (先使用i值,再执行i+1或i-1)
例如: j=3; k=++j; //执行后k=4,j=4
j=3; k=j++; //执行后k=3,j=4null*7.复合赋值运算符
复合赋值运算符有+= -= *= /= %= <<= >>= &= ^= |=
例如:a+=3相当于a=a+3,a*=b相当于a=a*b;
8.条件运算符
一般形式: 表达式1 ? 表达式2 : 表达式3
功能:首先判断表达式1是否成立,如果表达式1成立,则整个表达式的值为表达式2的值,否则为表达式3的值。
例如:(x%2==1)?1:0; //首先判断x%2==1是否成立,即x如果是奇数则结果为1,否则结果为0
9.逗号运算符
形式:表达式1,表达式2,……表达式n
逗号表达式的值等于表达式n的值 null*10.对指针操作的运算符
&――取地址运算符,*――取内容运算符
例如:
unsigned char a,b,*p;
p=&b; //取变量b的地址赋给指针变量p
a=*p; //取指针变量p所指向的地址中的内容(即变量b的内容)赋给变量a
表5-7列出了C语言中运算符的优先级及结合规则。null*5.5 C51的指针*5.5 C51的指针指针是C语言中的一个重要概念。正确地使用指针类型数据,可以有效地表示复杂的数据结构,可以动态地分配存储器,直接处理内存地址,使程序精简、灵活、高效。
这里介绍指针的基本概念,以及C51相对标准C语言对指针的扩展。
变量的指针就是该变量的地址,专门用于提供另一个变量地址的变量就是指针变量。
指针变量必须先定义后使用。指针变量定义的一般形式为:
数据类型 [存储器类型1] * [存储器类型2] 标识符;null*“数据类型”是指该指针变量所指向对象的数据类型;
“存储器类型1”和“存储器类型2”是可选项,
“存储器类型1”是指针变量所指向的数据存储空间区域,
“存储器类型2”是指针变量本身所在的存储器的空间区域。
“标识符”是所定义的指针变量名。
根据是否有“存储器类型1”选项,C51中指针变量可以分为通用指针变量和存储器专用指针变量。数据类型 [存储器类型1] * [存储器类型2] 标识符;1.通用指针变量
*1.通用指针变量
不选用“存储器类型1”选项的指针变量称为通用指针变量;
其声明和标准C语言一样。例如:
char *s; // s为指向char型数据的指针变量
int * xdata p; //p为指向int型数据的指针变量,而p本 身放于xdata区
通过它可以很方便地访问存储空间任何位置的变量,因而许多库函数都使用通用指针变量。它的缺点是速度慢。
通用指针变量需要3个字节来存储。第一个字节用来表示存储器类型,存储器类型的编码值如表5-8所示。第二个字节是指针的高字节,第三个字节是指针的低字节。
表5-8 存储器类型的编码值2.存储器专用指针变量 *2.存储器专用指针变量 选用“存储器类型1”选项的指针变量称为存储器专用指针变量。
通过该类指针变量,只能够访问规定的存储空间区域。
当存储器类型为idata、data、pdata时指针变量长度为1个字节,当存储器类型为xdata 或code时指针变量长度为2个字节。
使用存储器专用指针变量比通用指针变量的效率高,速度快,但必须保证指针变量不指向所声明的存储区以外的地方,否则会产生错误。 【例5-11】指出下列指针定义的作用。 *【例5-11】指出下列指针定义的作用。 char data * p1; /*p1指向data区的字符型数据,p1本身存放于默认存储区,该指针变量在内存中占1个字节*/
unsigned char pdata *xdata p2; /*p2指向pdata区的无符号字符型数据,p2本身存放于xdata区,该指针变量在内存中占1个字节*/
unsigned int code *data p3; /*p3指向code区的无符号整型数据,p3本身存放于data区,该指针变量在内存中占2个字节*/5.6 C51的函数*5.6 C51的函数1.C51函数的定义
一般定义格式如下:
返回值类型 函数名(形参表)[reentrant] [interrupt m] [using n]
{ 局部变量定义
执行语句
}
reentrant:表示该函数为可重入函数。所谓可重入函数,就是允许被递归调用的函数。是C51定义的关键字。
interrupt:是C51定义的关键字,中断函数必须通过它进行修饰。m代表中断号。
using:是定义工作寄存器组所用的关键字。n为工作寄存器组号。2.函数的调用*2.函数的调用函数调用的一般形式如下:
函数名(实参列表)
对于有参数的函数调用,若实参列表包含多个实参,则各个实参之间用逗号隔开。
按照函数调用在主调函数中出现的位置,函数调用方式有以下三种:
(1)函数语句。把被调用函数作为主调用函数的一个语句(2)函数表达式。函数被放在一个表达式中,以一个运算对象的方式出现。这时的被调用函数要求带有返回语句,以返回一个明确的数值参加表达式的运算。
(3)函数参数。被调用函数作为另一个函数的参数。3.自定义函数的声明*3.自定义函数的声明函数必须先定义后使用,如果函数的定义在调用语句之后或调用的函数不在本文件内部而在其他文件中,则在调用之前需对函数进行声明。
在C51中,函数原型一般形式如下:
[extern] 函数类型 函数名(形式参数表);
函数的声明是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便调用函数时系统进行对照检查。函数的声明后面要加分号。
如果声明的函数在文件内部,则声明时不用extern,如果声明的函数不在文件内部,而在另一个文件中,声明时须带extern,指明使用的函数在另一个文件中。4.可重入函数*4.可重入函数可重入函数是一种可以在函数体内间接调用其自身的函数。对于一个函数,如果需要递归调用,应将它定义为可重入函数;非中断服务程序和中断服务程序中都要调用,同样应将它定义为可重入函数。一般的函数不能这样做。
使用reentrant修饰符时需注意以下几点:
(1)用reentrant修饰的可重入函数被调用时,实参表内不允许使用bit类型的参数。函数体内也不允许出现任何关于位变量的操作,更不能返回bit类型的值。
(2)编译时,系统为重入函数在内部或扩展存储器中建立一个模拟堆栈区,称为重入栈。重入函数的局部变量及参数被放在重入栈中,使重入函数可以实现递归调用。5.寄存器组的定义*5.寄存器组的定义修饰符using n用于指定本函数内部使用的工作寄存器组,其中n的取值为0-3,表示寄存器组号,对应第0组到第3组。
当函数有返回值时,不能使用该属性,因为返回值是存于寄存器中,函数返回时要恢复原来的寄存器组,导致返回值错误。 【例5-12】有一个延时函数,在程序中多处被调用,包括中断
服务程序。请将其定义为可重入函数。 void delay( ) reentrant
{
int i;
for(i=0;i<20000;i++);
}6.中断函数*6.中断函数中断函数也称作中断服务程序,是CPU在响应中断后要执行的一段程序。
编写中断函数时,只需关心中断类型号和寄存器组的选择,编译程序会自动安排中断函数在存储器中的位置及中断返回地址的入栈及出栈任务。
C51支持32个中断源,即m的取值为0-31,中断入口地址与中断号m的关系:
中断入口地址=3+8×m编写51中断函数注意如下:*编写51中断函数注意如下:中断函数不能进行参数传递。
中断函数没有返回值,返回值类型应为void类型。
在任何情况下都不能直接调用中断函数。
如果在中断函数中调用了其它函数,则被调用函数所使用的寄存器必须与中断函数相同。
在中断函数中调用其它函数,被调函数最好设置为可重入的。
C51编译器对中断函数编译时会自动在程序开始和结束处加上相应的内容,具体如下:在程序开始处对ACC、B、DPH、DPL和PSW入栈,结束时出栈。中断函数未加using n修饰符的,开始时还要将R0-R1入栈,结束时出栈。如中断函数加using n修饰符,则在开始将PSW入栈后还要修改PSW中的工作寄存器组选择位。
中断函数最好写在文件的尾部,并且禁止使用extern存储类型说明。7.内部函数*【例5-13】编写统计定时/计数器T1中断次数的函数。 unsigned int interruptcnt; //中断次数计数器
void timer1( ) interrupt 3 using 2
{
interruptcnt++;
}7.内部函数C51软件包的库包含许多标准的应用程序,也称为库函数,如果要使用某一库函数,必须在源程序中用预处理命令定义与该函数相关的头文件。下面介绍头文件为intrins.h的内部函数。
内部函数也称为本征函数,共有9种函数,数目虽少,但都常用。(1)循环左移内部函数*(1)循环左移内部函数函数名:_crol_
函数原型:unsigned char _crol_( unsigned char a, unsigned char n);
函数功能:将无符号字符型变量a,循环左移n位。
函数名:_irol_
函数原型:unsigned int _irol_( unsigned int a, unsigned int n);
函数功能:将无符号整型变量a,循环左移n位。
函数名:_lrol_
函数原型:unsigned long _lrol_( unsigned long a, unsigned long n);
函数功能:将无符号长整型变量a,循环左移n位。null*以上三种函数的不同之处在于变量的类型和返回值的类型不一样。它们在执行循环左移时都是最高位循环到最低位,与汇编指令RL A相似。
例如: P1=0xEE;
P1=_crol_(P1,1);
P1的初始状态为11101110,执行1次循环左移语句之后P1的状态为11011101,执行2次后P1变成10111011。(2)循环右移内部函数*(2)循环右移内部函数函数名:_cror_
函数原型:unsigned char _cror_( unsigned char a, unsigned char n);
函数功能:将无符号字符型变量a,循环右移n位。
函数名:_iror_
函数原型:unsigned int _iror_( unsigned int a, unsigned int n);
函数功能:将无符号整型变量a,循环右移n位。
函数名:_lror_
函数原型:unsigned long _lror_ ( unsigned long a, unsigned long n);
函数功能:将无符号长整型变量a,循环右移n位。null*以上三种函数的不同之处在于变量的类型和返回值的类型不一样。它们在执行循环右移时都是最低位循环到最高位,与汇编指令RR A相似。
例如: P1=0xEE;
P1=_cror_(P1,1);
P1的初始状态为11101110,执行1次循环右移语句之后P1的状态为01110111,执行2次后P1变成10111011
(3)其他内部函数
函数名:_nop_
函数原型:void _nop_ ( void);
函数功能:产生一条NOP空指令,执行一次空操作。null*函数名:_testbit_
函数原型:bit _testbit_ ( bit x);
函数功能:产生一条JBC指令。该函数测试一个位,如果该位为1,则将该位清0,并且返回值为1,否则返回值为0。
例如:
if(_testbit_(RI)) a=SBUF;//若RI=1,则清0,并读SBUF
函数名:_chkfloat_
函数原型:unsigned char _chkfloat_ ( float x);
函数功能:检查浮点型变量x的状态,返回值为无符号字符型数据,其值可以为0,1,2,3,4。返回值意义:*返回值意义:0——标准浮点数;
1——浮点数0;
2——+INF正溢出;
3——-INF负溢出;
4——NaN 该数为非法数据的错误状态。【例5-14】编写程序段,若位变量flag值为1,则8位数据data8
右移两位并将flag清0;否则左移3位。 程序段如下:
if(_testbit_(flag))
data8=_cror_(data8,2);
else
data8=_crol_(data8,3);null*【例5-15】_chkfloat_函数的应用举例。 #include
void exm(float f1,floatf2 )
{
float f3;
f3=f2*f1;
switch(_chkfloat_(f3))
{
case 0: printf(“result is a number\n”);break;
case 1: printf(“result is a zero\n”);break;
case 2: printf(“result is +INF \n”);break;
case 3: printf(“result is -INF \n”);break;
case 4: printf(“result is NaN\n”);break;
}
}8.C51的输入/输出函数*8.C51的输入/输出函数C51语言本身未提供输入和输出语句,输入和输出操作是通过函数实现的。
在C51的函数库中提供了一个名为“stdio.h”的标准I/O函数库,其中包含了输入输出函数。当使用输入和输出函数时,需先用预处理命令“#include”将该函数包含到文件中。
本小节主要介绍格式输入及输出函数scanf()和printf(),其它函数说明见附录B。 格式输出函数printf *格式输出函数printf 函数功能:通过单片机的串行口输出若干任意类型的数据。
格式: printf(格式控制,输出参数表)
格式控制是用双引号括起来的字符串,它包括三种信息:格式说明符、普通字符、转义字符。
1)格式说明符:由百分号“%”和格式字符组成,其作用是指明输出数据的格式,如%d、%c、%s等,详细情况见表5-9。null*表5-9 printf()函数的格式字符
格式输出函数printf *格式输出函数printf 函数功能:通过单片机的串行口输出若干任意类型的数据。
格式: printf(格式控制,输出参数表)
格式控制是用双引号括起来的字符串,它包括三种信息:格式说明符、普通字符、转义字符。
1)格式说明符:由百分号“%”和格式字符组成,其作用是指明输出数据的格式,如%d、%c、%s等,详细情况见表5-9。
2)普通字符:这些字符按原样输出,主要用来输出一些提示信息。
3)转义字符:由“\”和字母或字符组成,它的作用是输出特定的控制符,如转义字符\n的含义是输出换行,详细情况见表5-5。 null*表5-5常用的转义字符
举例用printf函数输出(假设y已经定义并赋值):*举例用printf函数输出(假设y已经定义并赋值):printf(“x=%d”,36) ;//从串行口输出x=36
printf(“y=%d”,y) ;//从串行口输出y=y变量的值
printf(“c1=%c,c2=%c”,‘A’,‘B’) ; //从串行口输出 //c1=A,c2=B
printf(“%s\n”,“OK,Send data begin!”) ;//从串行口输出 /