1
第 6章 单片机的 C51程序设计
本章基本要求:在 ANSI C或 Turbo C的基础上学习 C51程序设计,应重点掌握 Turbo C和
C51 的主要区别,掌握 C51 的数据类型和存储类型,C51 增删的关键字,C51 的基本运算,
C51流程控制语句以及 C51中断函数等内容。了解 C51的常用库函数和典型的 C51程序设计
实例,为使用 C51开发单片机应用系统奠定基础。
6.1 C51数据类型及存储类型
6.1.1 C51的数据类型
C51 和 ANSI C 的数据类型基本类似,大体可以分为基本数据类型、构造数据类型、指针
类型和空类型等。为了充分利用 MCS-51单片机的资源特点,C51在 ANSI C的数据类型基础上
增设了位型变量,取消了布尔变量,其实两者的使用方法基本类似。C51的基本数据类型如
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
6.1所示。
表 6.1 C51的数据类型
数据类型 长度(bit) 长度(Byte) 值域范围
bit或 sbit 1 —— 0,1
unsigned char 8 1 0~255
Signed char 8 11 -128~+127
unsigned int 16 2 0~65535
signed int 16 2 -32768~+32767
unsigned long 32 4 0~4294967295
Signed long 32 4 -2147483648~+2147483647
float 32 4 ±1.176E-38~±3.40E+38(相当于 6位有效数字)
double 64 8 ±1.176E-38~±3.40E+38(相当于 10位有效数字)
6.1.2 C51的数据存储类型
C51允许将变量或常量定义成不同的存储类型,C51编译器允许的存储类型主要包括 data,
bdata、idata、pdata、xdata 和 code 等,它们和单片机的不同存储区相对应。C51 存储类型与
MCS-51单片机实际存储空间的对应关系如表 6.2所示。
表 6.2 C51存储类型与MCS-51单片机存储空间的对应关系
存储类型 长度(bit) 长度(Byte) 值域范围 与存储空间的对应关系
data 8 1 0~255 直接寻址片内低 128字节片内数据 RAM
bdata 8 1 32~47 按位或字节寻址片内 RAM的 20H~2FH地址空间
idata 8 1 0~255 间接寻址片内数据 RAM的 00H~FFH地址空间
pdata 8 1 0~255 分页寻址 256字节片外 RAM,对应MOVX @Ri
xdata 16 2 0~65535 寻址 64K字节片外 RAM,对应MOVX @DPTR
code 16 2 0~65535 寻址 64K程序 ROM,对应MOVC @DPTR
单片机访问片内 RAM比访问片外 RAM相对快一些,鉴于此,应当将使用频繁的变量置
2
于片内数据存储器,即采用 data、bdata或 idata存储类型;而将容量较大的或使用不怎么频繁
的那些变量置于片外 RAM,即采用 pdata或 xdata存储类型;常量只能采用 code存储类型。
变量存储类型定义举例(C51支持 ANSI C和 C++的注释方法):
① char data var1;/*字符变量 var1被定义为 data型,被分配在片内 RAM中*/
② bit bdata flags;//位变量 flags被定义为 bdata型,定位在片内 RAM中的位寻址区
③ float idata x,y,z;//浮点型变量 x、y和 z被定义为 idata存储类型,定位在片内 RAM
中,并只能用间接寻址 方式进行访问。
④ unsigned int pdata dimension;//无符号整型变量 dimension被定义为 pdata型,定位在
片外数据存储区,相当于用MOVX @Ri访问。
⑤ unsigned char xdata vector[10][4][4];//无符号字符型三维数组变量 vector[10][4][4]被
定义为 xdata存储类型,定位在片外 RAM中,占据 10×4×4=160个字节。
如果在变量定义时略去存储类型标志符,编译器会自动默认存储类型。默认的存储类型
进一步由 SMALL、COMPACT和 LARGE存储模式指令限制。例如,若表明 char var1在 SMALL
存储模式下,var1被定位在 data存储区;在 COMPACT模式下,var1被定位在 idata存储区,
在 LARGE模式下,var1被定位在 xdata存储区中。存储模式的有关说明如表 6.3所示。
表 6.3 存储模式及说明
存储模式 说 明
SMALL
参数及局部变量放入可直接寻址的片内存储器(最大 128字节,默认存储类型是 data),因此访问
十分方便。另外所有对象,包括栈,都必须嵌入片内 RAM。栈长很关键,因为实际栈长依赖于不
同函数的嵌套层数
COMPACT
参数及局部变量放入分页片外存储区(最大 256 字节,默认的存储类型是 pdata),通过寄存器 R0
和 R1间接寻址,栈空间位于内部数据存储区中
LARGE
参数及局部变量直接放入片外数据存储区(最大64KB,默认存储类型为xdata),使用数据指针DPTR
来进行寻址。用此数据指针访问的效率较低,尤其是对二个或多个字节的变量,这种数据类型的访
问机制直接影响代码的长度,另一个不便之处在于这种数据指针不能对称操作
6.1.3 C51对单片机主要资源的定义
MCS-51通过其特殊功能寄存器(SFR)实现对其主要资源的控制。MCS-51单片机有 21
个 SFR,有的单片机还有更多的 SFR,它们分布在片内 RAM的高 128字节中,其地址能够被
8整除的 SFR一般可以进行位寻址。对 SFR只能用直接寻址方式访问。C51 允许通过使用关
键字 sfr、sbit 或直接引用编译器提供的头文件来实现对 SFR 的访问,但对于片外 RAM 或扩
展 I/O的直接访问只能由用户实现。
(1) 使用关键字定义 SFR
SFR及其可位寻址的位是通过关键字 sfr和 sbit来定义的,这种方法与
标准
excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载
C不兼容,只
适用于 C51。
sfr或 sbit应用实例:
sfr PSW=0xD0; //定义程序状态字 PSW的地址为 D0H
sfr TMOD=0x89; //定义定时器/计数器方式控制寄存器 TMOD的地址为 89H
sfr P1=0x90; //定义 P1口的地址为 90H
sbit CY=0xD7; //定义进位标志 CY的地址为 D7H
sbit AC=0xD0^6; //定义辅助进位标志 AC的地址为 D6H
sbit RS0=0xD0^3; //定义 RS0的地址为 D3H
3
注意:用 sfr和 sbit定义的 SFR必须位于函数外,一般放在程序的开头。
(2) 通过头文件访问 SFR
编译器给出的头文件已经给出了常用 MCS-51 单片机中的 SFR 及其可位寻址位的定义。
比如 Keil C将这些头文件按单片机的不同生产公司、不同型号分别存在 Keil C的 INC子目录
下,在程序中只需直接引用这些头文件即可实现对 SFR的访问和控制。
头文件引用实例:
#include “aduc812.h” //使用的单片机为 ADI公司的 Aduc812
main()
{
TL0=0xb0; //访问定时器 0,设置时间常数
TH0=0x3c;
TR0=1; //启动定时器 0
⋯⋯
}
(3) 扩展 I/O端口或片外 RAM的直接访问
扩展的片外 RAM或 I/O端口需要用户自己先定义后才能在语句或函数中使用,其方法如
下:
#include
//定义 XBYTE为一个指针,指向外部数据 RAM的零地址单元
#define PA XBYTE[0xffec] //将 PA定义为外部 I/O口,地址为 oxffec
main()
{
PA=0x3A; //将数据 3AH写入地址为 0xffec的存储单元或 I/O端口
}
(4) 定义和使用位变量
C51使用关键字“bit”来定义位变量,这类变量的值是一个二进制位,其定义和使用方法
如下:
bit lock; //将 lock定义为位变量
bit dirention;//将 direction定义为位变量
函数也可以有 bit类型的参数,也可以有 bit类型的返回值。
例如:
bit func(bit b0,bit b1)
{
bit a;
⋯⋯
return a;
}
使用禁止中断宏命令#progma disable或指定明确的寄存器切换(using n)的函数不能返回
位变量值。
定义和使用位变量有以下限制:
不能定义位变量的指针,如:bit *bitno;
不能定义位数组,如:bit bitarray[10];
4
位变量的存储类型只能是 bdata。
在程序设计时,对于可位寻址的对象,即可以字节寻址又可以位寻址的变量,则其存储
类型只能是 bdata。使用时,先说明字节变量的数据类型和存储类型。
例如:
int bdata a; //整型变量 a定位在片内数据存储区中的可位寻址区
char bdata b[4];//字符数组 b定位在片内数据存储区中的可位寻址区
然后,再使用 sbit关键字定义其中可独立寻址的位变量。
例如:
sbit a0=a^0; //定义 a0为 a的第 0位
sbit a12=a^12; //定义 a12为 a的第 12位
sbit b03=b[0]^3; //定义 b03为 b[0]的第 3位
sbit b36=b[3]^6; //定义 b36为 b[3]的第 6位
sbit 定义要求基址对象的存储类型为 bdata,使用 sbit 类型位变量时,基址变量和其对应
的位变量的说明必须在函数外部进行。
6.2 C51的基本运算
C51 的基本运算类似于 ANSI C,主要包括算数运算、关系运算、逻辑运算、位运算和赋
值运算及其表达式等。
6.2.1 C51的算数运算
(1) 基本的算术运算符
C51最基本的算术运算符有以下五种:
+ (加法运算符)
- (减法运算符)
* (乘法运算符)
/ (除法运算符)
% (模运算或取余运算符)
除法运算符两侧的操作数可为整数或浮点数;取余运算符两侧的操作数均为整型数据,
所得结果的符号与左侧操作数的符号相同。
(2) 自增、自减运算符
++为自增运算符,--为自减运算符。
例如:++j、j++、--i、i--
++和--运算符只能用于变量,不能用于常量和表达式。++j表示先加 1,再取值;
j++表示先取值,再加 1。自减运算也是如此。
(3) 算术表达式和运算符的优先级与结合性
用算术运算符和括号将运算对象连接起来的式子称为算术表达式。其中的运算对象包括
常量、变量、函数、数组、结构等。例如:(2a+3b)*c/d。
C51 规定了算术运算符的优先级和结合性为:先乘除和取
模,后加减,括号最优先。
如果一个运算符两侧的数据类型不同,则必须通过数据类
型转换将数据转换成同种类型。转换方式有两种。其一是自动
自动类型转换,即在程序编译时,由 C 编译器自动进行数据类
型转换,转换规则如图 6.1 所示。当参与运算的操作数类型不
同时,先将精度较低的数据类型转换成较高的数据类型,运算
图 6.1 C51数据类型转换
5
结果为精度较高的数据类型。其二使用强制类型转换运算符,语句形式为:(类型名)(表达
式)。
例如:
int a,b; //a为整数
(double)(a+b) //将 a+b强制转换成 double类型
6.2.2 C51的关系运算
(1) 关系运算符及其优先级
关系运算又称为比较运算,C51提供了以下六种关系运算符:
< (小于)
<= (小于等于)
> (大于)
>= (大于等于)
== (等于)
!= (不等于)
其中:<、<=、>、>=这四个运算符的优先级相同,处于高优先级;==和!=这
两个运算符的优先级相同,处于低优先级。此外,关系运算符的优先级低于算术运算符的优
先级,而高于赋值运算符的优先级。
(2) 关系表达式
用关系运算符将运算对象连接起来的式子称为关系表达式。如:a>b、(a=3)<(b=2)等。
关系表达式的值为逻辑值,其结果只能取真和假两种值。C51中用 1表示真,用 0表示假。
例如有关系表达式 a>=b,若 a的值是 4,b的值是 3,则给定关系满足,关系表达式的值
为 1,即逻辑真;若 a的值是 2,则给定关系不成立,系表达式的值为 0,即逻辑假。
6.2.3 C51的逻辑运算
(1) 逻辑运算符及其优先级
逻辑运算是对变量进行逻辑与运算、或运算、及非运算。C51提供三种逻辑运算符。如下:
&& (逻辑与)
|| (逻辑或)
! (逻辑非)
其中:非运算的优先级最高,而且高于算术运算符;或运算的优先级最低,低于关系运
算符,但高于赋值运算符。
(2) 逻辑表达式
用逻辑运算符将运算对象连接起来的式子称为逻辑表达式。运算对象可以是表达式或逻
辑量,而表达式可以是算术表达式、关系表达式或逻辑表达式。逻辑表达式的值也是逻辑量,
即真或假。对于算术表达式,其值若为 0,则认为是逻辑假;若不为 0,则认为是逻辑真。
逻辑表达式并非一定完全被执行,仅当必须要执行下一个逻辑运算符才能确定表达式的
值时,才执行该运算符。
例如:a&&b&&c
若 a的值为 0,则不需判断 b和 c的值就可确定表达式的值为 0。
6.2.4 C51的位运算
位运算的操作对象只能是整型和字符型数据,不能是实型数据。C51提供以下六种位运算。
6
& (按位与),两个字符或整数按位进行逻辑与运算,如 var3=var1 & bvar2
| (按位或),两个字符或整数按位进行逻辑或运算,如 var3=var1 | bvar2
^ (按位异或),两个字符或整数按位进行逻辑异或运算,如 var3=var1^bvar2
~ (按位取反)两个字符或整数按位进行逻辑非运算,如:var1=~var2
<< (左移),字符或整数按位进行逻辑左移运算,如:var1=var<<2
>> (右移),两个字符或整数按位进行逻辑右移运算,如:var1=var2>>5
这些位运算和汇编语言中的位操作指令十分类似。位操作指令是 MCS-51系列单片机的重
要特点,所以位运算在 C51控制类程序设计中的应用比较普遍。
6.2.5 C51的赋值运算
(1) 赋值运算符
赋值符号“=”完成的操作即为赋值运算,它是右结合性,且优先级低最低。
(2) 赋值表达式
将一个变量与表达式用赋值号连接起来就构成赋值表达式。形式如下:
变量名=表达式
赋值表达式中的表达式包括变量、算术运算表达式、关系运算表达式、逻辑运算表达式
等,甚至可以是另一个赋值表达式。赋值过程是将“=”右边表达式的值赋给“=”左边的一
个变量,赋值表达式的值就是被赋值变量的值。
例如:
a=(b=4)+(c=6),该表达式的值为 10,变量 a的值为 10。
(3) 赋值的类型转换规则
在赋值运算中,当“=”两侧的类型不一致时,系统自动将右边表达式的值转换成左侧变
量的类型,再赋给该变量。转换规则如下:
① 实型数据赋给整型变量时,舍弃小数部分。
② 整型数据赋给实型变量时,数值不变,但以 IEEE浮点数形式存储在变量中。
③ 长字节整型数据赋给短字节整型变量时,实行截断处理。如将 long 型数据赋给 int
型变量时,将 long 型数据的低两字节数据赋给 int 型变量,而将 long 型数据的高两字节的
数据丢弃。
④ 短字节整型数据赋给长字节整型变量时,进行符号扩展。如将 int 型数据赋给 long
型变量时,将 int 型数据赋给 long 型变量的低两字节,而将 long 型变量的高两字节的每一
位都设为 int型数据的符号值。
(4) 复合赋值运算符
赋值符号前加上其他运算符构成复合运算符。C51提供以下十种复合运算符。
+=,-=,*=,/=,%=,﹠=,︱=,^=,<<=,>>=。
例如:
a += b; //等价于 a = (a+b)
x *= a+b;//等价于 x = (x*(a+b))
a &= b; //等价于 a = (a&b)
a <<= 4; //等价于 a = (a<<4)
6.3 C51的构造数据类型
C51 的构造数据类型主要有数组、指针和结构等。在单片机系统中,数组的应用比较广
泛,指针则次之,结构用的相对较少,这和单片机系统的要求以及用户的程序设计习惯有一
定的关系。
7
6.3.1 数组
数组是同类型数据的一个有序集合。数组用一个名字来标识,称为数组名。数组中各元
素的顺序用下标表示,下标为 n 的元素可以表示为数组名[n]。改变[ ]中的下标就可以访问数
组中所有的元素。
(1) 一维数组
由具有一个下标的数组元素组成的数组称为一维数组,定义一维数组的一般形式如下:
类型说明符 数组名[元素个数];
其中,数组名是一个标识符,元素个数是一个常量表达式,不能是含有变量的表达式。
例如:
int demo1[10];//定义一个数组名为 demo1的数组,数组包含 10个整型的元素
在定义数组时可以对数组进行整体初始化,若定义后想对数组赋值,则只能对每个元素
分别赋值。
例如:
int a[5]={1,2,3,4,5};//给全部元素赋值,a[0]=1,a[1]=2,a[2]=3,a[3]=4,a[4]=5
int b[6]={1,2,6};//给部分元素赋值,b[0]=1,b[1]=2,b[2]=6,b[3]=b[4]=b[5]=0
(2) 二维数组或多维数组
数组的下标具有两个或两个以上,则称为二维数组或多维数组。定义二维数组的一般形
式如下:
类型说明符 数组名[行数] [列数];
其中,数组名是一个标识符,行数和列数都是常量表达式。
例如:
float demo2[3][4];//demo2数组有 3行 4列共 12个实型元素
二维数组也可以在定义时进行整体初始化,也可以在定义后单个地进行赋值。
例如:
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};//全部初始化
int b[3][4]={{1,2,3,4},{5,6,7,8},{}};//部分初始化,未初始化的个元素为 0
(3) 字符数组
若一个数组的元素是字符型的,则该数组就是一个字符数组。
例如:
char a[12]={“Chong Qing”};//字符数组
char add[3][6]={"weight","height","width"}; //字符串数组
6.3.2 指针
C51支持“基于存储器”的指针和“一般”指针。当定义一个指针变量时,若未给出它所
指向的对象的存储类型,则该指针变量被认为是一般指针;反之若给出了它所指对象的存储
类型,则该指针被认为是基于存储器的指针。
(1) 基于存储器的指针
在定义一个指针时,若给出了它所指对象的存储类型,则该指针是基于存储器的指针。
例如:
char xdata *px; // px指向一个存在片外 RAM的字符变量
// Px本身在默认的存储器中(由编译模式决定),占用 2字节
char xdata *data py; // px指向一个存在片外 RAM的字符变量
// Py本身在 RAM中,与编译模式无关,占用 2字节
8
(2) 一般指针
在函数的调用中,函数的指针参数需要用一般指针。一般指针的说明形式如下:
数据类型 *指针变量
如:char *pz;
这里没有给出 pz所指变量的存储类型,pz处于编译模式默认的存储区,长度为 3字节,
其格式如表 6.4所示。存储类型由编译模式决定,不同的存储区域的编码值如表 6.5所示。
表 6.4 一般指针占用的 3字节及其分配
地址 +0 +1 +2
内容 存储类型的编码 高位地址偏移量 低位地址偏移量
表 6.5 存储类型的编码值
存储类型 idata xdata pdata data code
编码值 1 2 3 4 5
在使用常量指针时,必须正确定义存储类型和偏移量。
例如:要将数值 0x55写入地址为 0x8000的片外 RAM单元,则可编写如下语句:
#include
XBYTE[0x8000]=0x41;//XBYTE是一个指针,头文件 absacc.h已对其进行了定义
#define XBYTE ((unsigned char *) 0x2000L);
XBYTE被定义为(unsigned char *) 0x2000L,是一般指针,其存储类型为 2,即 xdata型,偏移
量是 0000,这样,XBYTE成为指向片外 RAM零地址单元的指针。而 XBYTE[8000]则表示地
址为 0x8000的片外 RAM单元。
6.3.3 结构
根据不同程序员的使用习惯,也可以使用 C51提供的结构。
(1) 结构类型的定义
结构类型的定义如下:
struct 结构名
{
结构成员说明;//和定义基本数据类型相似
};
例如,一个名为 date的结构类型可以定义如下:
struct date
{
int month;
int day;
int year;
}
(2) 定义结构的变量
结构的变量可以在定义结构时进行定义,也可以先定义结构类型,在定义该结构的变量。
例如:
9
date date1,date2;//先定义结构 date,在定义其结构的变量 date1和 date2。
struct student
{
int no;
char name[20];
int grade;
} wangxiao,liping;//定义结构时即定义结构的变量 date1和 date2。
(3) 结构类型变量的引用
使用成员运算符“·”实现对结构成员的引用。
例如:
date1.year = 2003;
date1.month = 12;
date1.day = 25;
C51还提供了其他的构造数据类型,但在单片机系统中使用得比较少,次序从略。
6.4 C51流程控制语句
和 ANSI C的程序结构类似,C51程序结构也可以分为顺序结构、选择结构或分支结构、
循环结构三种基本类型。本节对选择或分支结构、循环结构用到的 C51 流程控制语句作简要
的介绍。
6.4.1 选择控制语句
C51的选择语句主要有 if语句和 switch/case语句。
(1) if语句
C51提供三种形式的 if语句:
① if(表达式){语句;}
② if(表达式){语句 1;} else {语句 2;}
③ if(表达式 1){语句 1;}
else if(表达式 2){语句 2;}
else if(表达式 3){语句 3;}
此外,如果一个 if语句中又含有一个或多个 if语句,则称为 if语句嵌套。在 if语句嵌套
中应注意 if与 else的对应关系,else总是与它前面最近的一个 if语句相对应。
例 6.1 某浮点数的范围在 0.000~9999之间,试编写一个函数返回浮点数的小数点位置。
解 此
题
快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题
的基本思路是根据浮点数的 4 种取值范围给出 4 种不同的返回值,可以约定当
定浮点数的大小在 0.000~9.999、10.00~99.99、100.0~999.9、1000~9999之间时,分别
返回 0、1、2和 3。参考程序如下:
int ftochar(float valp)
{
int dotno=0;
if(valp<10.0) dotno=0;
else if((valp>=10.0)&&(valp<100.0)) dotno=1;
else if((valp>=100.0)&&(valp<1000.0)) dotno=2;
else if(valp>=1000.0) dotno=3;
return dotno;
10
}
根据小数点的位置,即可在实际的单片机系统中显示出浮点数或小数。
(2) switch/case语句
switch/case是 C51的多分支选择语句,它的一般形式如下:
switch(表达式)
{
case 常量表达式 1:语句 1; break;
case 常量表达式 2:语句 2; break;
⋯⋯
case 常量表达式 n:语句 n; break;
default :语句 n+1;
}
switch括号中的表达式的值与某一 case后面的常量表达式的值相同时,就执行它后面的
语句(可以是复合语句),遇到 break语句则退出 switch语句。若所有的 case中的常量表达式
的值都没有与表达式的值相匹配时,就执行 default后面的语句。每一个 case的常量表达式必
须是互不相同的,否则将出现混乱局面。各个 case和 default出现的次序,不影响程序的执行
结果。如果在 case语句中遗忘了 break语句,则程序执行了本行之后,不会按规定退出 switch
语句,而是将执行后续的 case语句。
例 6.2 AT89C51单片机的 P1.0和 P1.1引脚接有两只按键,其 4种逻辑组合分别点亮由
P2.0~P2.3控制的 4只 LED(高电平点亮),试编程实现此功能。
解 参考程序如下
#include
void main()
{
char a;
do
{
a=P1;
a=a&0x03; //屏蔽高 6位
P2=P2&0xf0;
switch (a)
{
case0:P2=P2|0x01;break;
case1:P2=P2|0x02;break;
case2:P2=P2|0x04;break;
case3:P2=P2|0x08;
}
}while(1);
}
6.4.2 循环语句
循环程序主要有“当型”循环和“直到型”循环两种,C51对此提供了 4种实现方法。
11
(1) 基于 if和 goto构成的循环
采用 if和 goto可以构成“当型”循环程序,其格式如下:
loop:if(表达式)
{
语句;
goto loop;
}
loop是语句标号,或称为标识符,原则上任何一条语句都可以有标号,标号和语句用“:”
号分开。
采用 if和 goto也可以构成“直到型”循环程序,其格式如下:
loop:{
语句;
if(表达式)goto loop;
}
goto语句为无条件转向语句,它的一般形式是:
goto 语句标号;
按照软件工程的有关思想,在程序设计中应避免使用或尽量少使用 goto,以提高程序的
可读性。
(2) 基于 while语句构成的循环
while语句只能用来实现“当型”循环,其一般格式如下:
while(表达式)
{
语句;//可以是复合语句
}
while 语句首先计算表达式的值;若其值为非 0,则执行内嵌语句,若其值为 0,则退出
while循环。
(3) 基于 do-while语句构成的循环
do-while语句只能用来实现“直到型”循环,其一般格式是:
do
{
语句;//可以是复合语句
} while (表达式);
do-while语句的特点是先执行内嵌的语句;再计算表达式,如果表达式的值为非 0,
则继续执行内嵌的语句,直到表达式的值为 0时结束循环。
例 6.3 实型数组 sample 存有 10 个采样值,编写一个函数返回其平均值(即平均值滤波
程序)。
解 参考程序如下:
float avg(float *sample)
{
float sum=0;
char no=0;
12
do
{
sum += sample[no];
no++;
} while(no<10);
return (sum/10);
}
(4) 基于 for语句构成的循环
for语句的一般形式为:
for (表达式 1;表达式 2;表达式 3)
{
语句;
}
它的执行过程是:首先求解表达式 1;其次求解表达式 2,若其值非 0,则执行内嵌语句;
否则退出循环;最后求解表达式 3,并回到第 2步。
在 for 语句中,可以没有表达式 1、表达式 2 或表达式 3,若三个表达式都没有,则相当
于一个死循环。
例 6.4 求自然数 1~100的累加和,并用 printf( )函数通过单片机的串口显示在终端上。
解 参考程序如下:
#include
#include
int getsum (void);
main( )
{
SCON = 0x50; // 如用 Keil C进行模拟调试或使用 printf( ),必须初始化 SCON
TMOD = TMOD|0x20; // 定时器 T1工作在方式 2,用作波特率控制
TH1 = 0xfd; // 9600bps对应 T1的时间常数为 0xfd
TL1 =0xfd;
TR1 = 1; // 启动 T1
TI = 1; //启动发送,以发送第一个字符
printf("%d\n",getsum());
do{ }while(1);
}
int getsum (void)
{
int sum=0;
int n;
for (n=1;n<=100;n++)
{
sum=sum+n;
}
return sum;
13
}
6.4.3 C51的中断控制
C51 编译器支持在 C51 源程序中直接开发中断过程或中断函数,但中断函数是由中断系
统自动调用的。用户在主程序或函数中一般不能调用中断函数,否则容易导致混乱。中断函
数的定义如下:
返回值 函数名 interrupt n using r
其中,interrupt和 using为关键字,n为中断源的编号,n为 0~5分别对应外部中断 0、
定时器/计数器 0中断、外部中断 1、定时器/计数器 1中断、串口中断和定时器/计数器 2中断。
对于中断源超过 6个的MCS-51及其兼容单片机则依此类推。r为工作寄存器组,其取值范围
为 0~3,若选择了寄存器组,则按程序员的安排进行编译,否则由编译器自动分配。大多数
情况下都可以选择自动分配。
例 6.5 设 Aduc812 的时钟频率为 12MHz,利用定时中断在其 P1.0 引脚输出周期为 2 秒
的方波。
解 参考程序如下:
#include
int t0int_no = 0;
sbit p11 = P1^0;
main( )
{
TMOD = 0x1; //定时器 T0安方式 1工作
TH0 = 0x3c; //每隔 50ms产生 1次中断
TL0 = 0xb0;
IE = 0x82; //开放中断
TR0 = 1;
p11 = 0; //初始值为低电平
do{ } while(1); //死循环,等价于汇编语言的 SJMP $
}
void time0_int ( void ) interrupt 1
{
TH0 = 0x3c; //重置时间常数
TL0 = 0xb0;
t0int_no++; //对中断次数进行累计,中断 20次则对 P1.0进行一次取反操作
if( t0int_no >= 20 )
{
t0int_no = 0;
p11= ~p11;
}
}
6.5 C51函数
6.5.1 函数的分类与定义
(1) 函数的分类
14
从 C 语言程序的结构上划分,C 语言函数分为主函数 main( )和普通函数两种,而对于
普通函数,从不同的角度或以不同的形式又可以分为标准库函数和用户自定义函数。
标准库函数是由 C编译系统提供的库函数,在 C编译系统中将一些独立的功能模块编写
成公用函数,并将它们集中存放在系统的函数库中,供程序设计时使用,称之为标准库函数。
附录 2给出了常用的 C51库函数的简要说明。
用户自定义函数是用户根据自己的需要而编写的函数。从函数定义的形式上可以将其划
分无参数函数、有参数函数和空函数。
无参数函数被调用时,既无参数输入,也不返回结果给调用函数,它是为完成某种操作
而编写的函数。有参数函数在被调用时,必须提供实际的输入参数,必须说明与实际参数一
一对应的形式参数,并在函数结束时返回结果供调用它的函数使用。定义空函数的目的是为
了以后程序功能的扩充。
(2) 函数的定义
函数定义的一般形式为:
返回值类型 函数名(形式参数列表)
{
函数体;
}
返回值类型可以是基本数据类型(int、char、float、double等)及指针类型。当函数没有
返回值时,则使用标识符 void 进行说明。若没有指定函数的返回值类型,默认返回值则为整
型类型。一个函数只能有一个返回值,该返回值是通过函数中的 return语句获得的。
函数名必须是一个合法标识符。
形式参数(简称形参)列表包括了函数所需全部参数的定义,形式参数可以是基本数据
类型的数据、指针类型数据、数组等。在没有调用函数时,函数的形参和函数内部的变量未
被分配内存单元,即它们是不存在的。
函数体由两部分组成:函数内部变量定义和函数体其他语句。
各函数的定义是独立的,函数的定义不能在另一个函数的内部。
6.5.2 函数的调用
函数调用的一般形式为:
函数名 ( 实际参数列表 );
在一个函数中需要用到某个函数的功能时,就调用该函数。调用者称为主调函数,被调
用者称为被调函数。若被调函数是有参函数,则主调函数必须把被调函数所需的参数传递给
被调函数。传递给被调函数的数据称为实际参数(简称实参),必须与形参在数量、类型和顺
序上都一致。实参可以是常量、变量和表达式;实参对形参的数据传递是单向的,即只能将
实参传递给形参。
和 ANSI C相似,C51也支持函数的嵌套调用和递归调用,也通过指向函数的指针变量来
调用函数,读者可以自行参考相关书籍,此处从略。
6.6 C51应用编程实例
例 6.6 在例 6.3基础上,要求滤波函数能去掉实型数组 sample中的最大值和最小值,返
回余下 8个实型数的平均值,并通过主程序调用该函数打印出结果。
解 参考出现如下:
15
#include
#include
float demo[ ]={67.5,2.0,3.0,4.0,5.0,30.6,7.0,8.0,9.0,10.0};
float avg2(float*);
main()
{
SCON = 0x50; // 初始化 SCON,用串口支持 printf( )函数,参考附录 2
TMOD = TMOD|0x20; // 定时器 T1工作在方式 2,用作波特率控制
TH1 = 0xfd; // 9600bps对应 T1的时间常数为 0xfd
TL1 =0xfd;
TR1 = 1; // 启动 T1
TI = 1; //启动发送,以发送第一个字符
printf("%f\n",avg2(demo));
do{ }while(1);
}
float avg2(float *sample)
{
float max,min,sum;
char no=1;
max=min=sum=sample[0];
for(no=1;no<10;no++)
{
if(max < sample[no]) max=sample[no];
if(min > sample[no]) min=sample[no];
sum += sample[no];
}
return ((sum-max-min)/8);
}
例 6.7 如图 6.2所示,单片机扩展可编程接口芯片 8155,8155PA口控制 8只发光二极管,
要求 8只 LED形成走马灯,每位点亮的时间为 1秒。
图 6.2 例 6.7接口
电路
模拟电路李宁答案12数字电路仿真实验电路与电子学第1章单片机复位电路图组合逻辑电路课后答案
图
16
解 由图可知,8155的端口地址如下:
命令口地址(COM8155):8000H
PA口地址(PA8155):8001H
PB口地址(PB8155):8002H
PC口地址(PC8155):8003H
在本例只用到命令口和 PB口,把 8155PB口定义成输出即可,于是命令字为 02H,参考
程序如下:
#include
#include
#define COM8155 XBYTE[0x8000]
#define PB8155 XBYTE[0x8002]
int t0int_no=0;
main()
{
TMOD=0x1;
TH0=0x3c;
TL0=0xb0;
IE=0x82;
TR0=1;
p11=0;
do{ }while(1);
}
void time0_int(void) interrupt 1
{
TH0=0x3c;
TL0=0xb0;
t0int_no++;
if(t0int_no>=20)
{
t0int_no=0;
PB8155=disp_word;
disp_word= disp_word<<1;
if( disp_word = =0) disp_word = 0x01;
}
}
注意:本题图中的 LED直接由 8155 PB口驱动,这对于小电流 LED是可以的,对于驱动
电流比较大的 LED,应增加相应的驱动电路。
例 6.8 Aduc812单片机和 4位数码管的接口电路如图所示,试编写数码管动态显示程序。
解 基本思路:动态显示的基本原理是让 4只数码管轮流导通,为了提高亮度,每只数码
管的导通时间应占整个扫描周期的 1/4,即每时每刻都有一只数码管工作,整个扫描周期应小
17
于或等于 20ms。软件延时实现的数码管动态显示无法让 4只数码管的工作时间等于扫描周期,
总是小于扫描周期,所以无法达到应有的亮度。实际的单片机系统不可能采用软件延时实现
数码管的动态显示,而是采用定时中断实现的。每中断一次扫描 1位数码管,中断 4次即为 1
个扫描周期,在中断间隔时间内,该位数码管始终点亮。
参考程序如下:
#include
unsigned char data disp_buffer[4]={12,0,0,0};
unsigned char dispno=0;
unsigned char data disp_buffer[4]={1,2,3,4};
unsigned char code segment_table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
sbit ledb3=P3^4;
sbit ledb2=P3^5;
sbit ledb1=P3^6;
sbit ledb0=P3^7;
main()
{
TMOD=0x1;
TH0=0xee; //设时钟为 11.0592MHz,每 5ms中断 1次
TL0=0x00;
IE=0x82;
TR0=1;
p11=0;
do{ }while(1);
}
void time0_int(void) interrupt 1
{
图 6.3 例 6.8接口电路图
18
TH0=0xee;
TL0=0x00;
P0=0x00;
P3=P3|0xf0;
P0=segment_table[disp_buffer[dispno]]; //根据显示内容获得其段码,4只数码管的显示内容
//存放在 disp_buffer数组中
switch(dispno)
{
case 0: ledb3=0; break; //当前中断已经扫描到第 0位数码管
case 1: ledb2=0; break; //当前中断已经扫描到第 1位数码管
case 2: ledb1=0; break; //当前中断已经扫描到第 2位数码管
case 3: ledb0=0; break; //当前中断已经扫描到第 3位数码管
default: break;
}
dispno++;
if(dispno>3) dispno=0; //当第 3位数码管扫描后,下次应扫描第 0位数码管
}
例 6.9 在例 6.1的基础上,同时返回浮点数对应的 4位单字符和小数点位置。
解 为了便于单片机系统的显示,往往需要将待显示的浮点数拆分成单个的字符,在将
这些字符显示在数码管或 LCD上,并根据小数点的位置编号,显示出小数点。下列函数能够
将大小范围在 0.000~9999的浮点数拆分成单字符,并以指针的形式返回,其中前 4个元素为
拆分出的 4个单字符,第 5个元素为小数点编号。
unsigned char * float_char(float valp)
{
char idata disp[5]; //存放浮点数对应的 4个单字符和小数点位置编号
if(valp<10.0)
//是 0.000~9.999之间的浮点数,乘以 1000取整后为 4位整数,小数点位置号为 0
{ disp[4]=0;
valp *=1000.0;
}
else if((valp>=10.0)&&(valp<100.0))
//是 10.00~99.99之间的浮点数,乘以 100取整后为 4位整数,小数点位置号为 1
{
disp[4]=1;
valp *=100.0;
}
else if((valp>=100.0)&&(valp<1000.0))
//是 100.0~999.9之间的浮点数,乘以 10取整后为 4位整数,小数点位置号为 2
{
disp[4]=2;
valp *=10.0;
}
19
else if(valp>=1000.0) disp[4]=3; //是 1000~9999之间的浮点数,小数点位置号为 3
disp[0]=(int)valp/1000; //千位
disp[1]=((int)valp%1000)/100; //百位
disp[2]=(((int)valp%1000)%100)/10; //十位
disp[3]=(((int)valp%1000)%100)%10; //个位
return disp; //返回一个指针
}
例 6.10 Aduc812的 ADC6通道接有一个 0~5V的电压信号,试编程返回该通道的输入
电压值。
解 通过控制寄存器 ADCCON1、ADCCON2 和 ADCCON3,可以实现 Aduc812内部集
成 A/D转换器的采样控制,关于寄存器介绍,请读者参考第 4章。
参考程序如下:
#include
float adc(void)
main( )
{
ADCCON1=0x7c; //ADC正常工作,8分频,9个时钟周期,禁止定时器触发和外触发
ADCCON2=(ADCCON2&0xf0)|0x06; //通道号为 6,选择单次转换
SCONV=1; //启动 A/D转换
for(;;)
{
printf(“%f\n”,adc( ));
}
}
float adc(void)
{
char status;
float ftemp;
do {
status=ADCCON3&0x80; //转换结束时,ADCCON3的最高位为低电平
} while(status!= 0);
ftemp=(ADCDATAH&0x0f)*256+ADCDATAL; //读 A/D转换结果
ftemp=ftemp*5.0 /4095.0; //转换为电压值
SCONV=1; //启动下次转换
return ftemp;
}
例 6.11 Aduc812内部集成 D/A转换器的输出程序设计。
解 Aduc812有 2路 12位的 D/A转换器,其寄存器介绍可参考第 4章,参考程序如下:
void dac(int dac1,int dac2)
{
DACCON=0x7f;
DAC0H=(dac1/256)&0x0f; //必须先写高位,后写低位
20
DAC0L=dac1%256;
DAC0H=(dac2/256)&0x0f; //必须先写高位,后写低位
DAC0L=dac2%256;
}
本章小结
本章介绍 C51的基本内容和程序设计方法,主要内容包括 C51数据类型和存储类型、构
造数据类型、基本运算、流程控制语句、函数和程序设计实例。充分重视 C51的区别和联系,
是学习好 C51 的关键,因此读者在学习本章之前,应有一定的 C 语言知识。C51 和 A