2.3 任务5 开关控制LED循环点亮

工作任务

用P3.0作输入接开关SW,P1口作输出接8个LED,通过开关SW控制LED循环点亮。开关SW合上,LED循环点亮,开关SW打开,LED停止循环点亮。

2.3.1 开关控制LED循环点亮电路设计

开关控制LED循环点亮电路比LED循环点亮控制电路(见图2-1)多一个开关电路部分,其他都一样。开关SW一端接到单片机的P3.0引脚上,另一端接地,当开关SW合上时P3.0引脚就接到了低电平。开关控制LED循环点亮电路设计如图2-16所示。

运行Proteus软件,新建“开关控制LED循环点亮”设计文件。按图2-16所示,放置并编辑AT89C52、CRYSTAL、CAP、CAP-ELEC、RES、LED-RED和SWITCH等元器件。设计完成开关控制LED循环点亮电路后,进行电气规则检测。

2.3.2 开关控制LED循环点亮程序设计

与LED循环点亮控制程序相比,本程序的关键是如何用开关控制LED循环点亮。根据任务要求,用P3.0作输入接开关SW,通过开关SW控制LED循环点亮。开关SW合上,P3.0为低电平,LED循环点亮;开关SW打开,P3.0为高电平,LED停止循环点亮。

图2-16 开关控制LED循环点亮电路

开关控制LED循环点亮程序如下:

#include <reg52.h>         //包含reg52.h头文件 
sbit SW=P3^0;              //符号SW表示 P3.0 引脚 
void  Delay()              //延时函数 
{ 
  unsigned char i, j; 
      for (i=0;i<255;i++) 
         for (j=0;j<255;j++); 
} 
void main() 
{ 
   unsigned char i; 
   unsigned char temp;    
   P1 = 0xff;                     //十六进制全1,熄灭所有LED 
   while(1) 
   { 
     temp = 0x01;                 //第一位为1,即初始控制码为0x01 
     for (i=0;i<8;i++) 
     { 
        if(SW==0)                 //SW 若合上,P3.0 为低电平,LED 循环点亮 
        { 
          P1 = ~ temp;            //temp 值取反送P1口 
          Delay(); 
          temp = temp << 1 ;      //temp 值左移一位,获得下一个控制码 
         }  
      } 
   } 
 } 

开关控制LED循环点亮程序设计好以后,打开“开关控制LED循环点亮”Proteus电路,加载“开关控制LED循环点亮.hex”文件,进行仿真运行,观察开关控制LED循环点亮是否与设计要求相符。

2.3.3 C51数据类型

C51定义了标准C语言的所有数据类型,同时对标准C语言进行了扩展,更加注意对系统资源的合理利用,如表2-5所示。

表2-5 C51基本数据类型

1. C51基本数据类型

标准C语言中的基本数据类型有char、int、short、long、float和double,而在C51编译器中,int和short相同,float和double相同。

(1)char字符类型

char字符类型的长度是一个字节(8位),通常用于定义处理字符数据的变量或常量。这很适合MCS-51单片机,因为MCS-51单片机每次可处理8位数据。char类型分无符号字符类型unsigned char和有符号字符类型signed char,默认为signed char类型。

unsigned char类型用字节中所有的位来表示数值,数值范围是0~255。常用于处理ASCII字符或小于等于255的整型数。signed char类型具有重要意义的位是最高位的符号标志位,“0”表示正数,“1”表示负数,负数用补码表示,数值范围是-128~+127。正数的补码与原码相同,负数的补码等于它的原码按位取反后加1(符号位不变)。

例如:

unsigned char a;     //定义变量a为无符号字符类型unsigned char 
char b;              //定义变量b为有符号字符类型signed char 

(2)int整型

int整型的长度为两个字节(16位),用于存放一个两字节数据。MCS-51系列单片机将int型变量的高位字节存放在低地址字节中,低位字节存放在高地址字节中。int整型分有符号整型数signed int和无符号整型数unsigned int,默认为signed int类型。

unsigned int表示的数值范围是0~65535。signed int表示的数值范围是-32768~+32767,字节中最高位表示数据的符号,“0”表示正数,“1”表示负数。

例如:

unsigned int x;      //定义变量x为无符号整型数unsigned int 
int y;               //定义变量y为有符号整型数signed int 

(3)long长整型

long长整型长度为4个字节(32位),用于存放一个四字节数据。分有符号长整型signed long和无符号长整型unsigned long,默认为signed long类型。

unsigned long表示的数值范围是0~4294967295。signed int表示的数值范围是-2147483648~+2147483647,字节中最高位表示数据的符号,“0”表示正数,“1”表示负数。

(4)float浮点型

float浮点型长度为4个字节(32位)。在十进制中具有7位有效数字,许多复杂的数学表达式都采用浮点变量数据类型组成。

(5)*指针型

指针型本身就是一个变量,在这个变量中存放的是指向另一个数据的地址。指针变量要占据一定的内存单元,在C51中,它的长度一般为1~3个字节。

2. C51扩展的数据类型

为了更加有效地利用单片机的硬件资源,C51对标准C语言进行了扩展,增加了如下几个特殊的数据类型。

(1)bit位变量

可以将与MCS-51硬件特性操作相关的变量定义成位变量。位变量必须定位在MCS-51单片机内部RAM的位寻址空间中。但不能定义位指针,也不能定义位数组。bit位变量的值就是一个二进制位,不是0就是1,类似True和False。

例如:

bit flag;        //flag 为bit 位变量,其值是0 或1 

(2)sfr特殊功能寄存器

为了能直接访问特殊功能寄存器SFR,C51提供了一种特殊形式的定义方法,这种定义方法与标准C语言不兼容,只适用于对MCS-51系列单片机进行C语言编程。sfr占用一个字节,数值范围为0~255。利用它可以访问51单片机内部的所有特殊功能寄存器。C51定义特殊功能寄存器的一般语法格式如下:

sfr  特殊功能寄存器名=特殊功能寄存器的字节地址; 

例如:

sfr P1 = 0x90; 

定义了P1为P1端口在内部的寄存器,在后面的语句中我们可以用P1=0xff(对P1端口的所有引脚置高电平)之类的语句来操作特殊功能寄存器。

又如:

sfr  SCON=0x98;      //串口控制寄存器,地址为0x98 
sfr  TMOD=0x89;      //定时器/计数器方式控制寄存器,地址为0x89 

注意

“sfr”是定义语句的关键字,其后必须跟一个MSC-51单片机真实存在的特殊功能寄存器名,“=”后面必须是一个整型常数,不允许是带有运算符的表达式,代表特殊功能寄存器的字节地址,这个常数值必须在SFR地址范围内,位于0x80~0xFF。

(3)sfr16 16位特殊功能寄存器

sfr16占用两个字节,数值范围为0~65535。sfr16和sfr一样用于操作特殊功能寄存器,所不同的是它操作占用两个字节的寄存器,如52子系列的定时器/计数器2。

在许多新的MCS-51系列单片机中,有时会使用两个连续地址的特殊功能寄存器来指定一个16位的值。为了有效地访问这类SFR,可使用关键字“sfr16”来定义,16位SFR定义语句的语法格式与8位SFR相同,只是“=”后面的地址必须用16位SFR的低字节地址作为“sfr16”的定义地址。

例如:

sfr16  T2 = 0xCC    //定时器/计数器2:T2低8位地址为0xCC,T2高8位地址为0xCD 

注意

这种定义适用于所有新的16位SFR,但不能用于定时器/计数器0和1。

(4)sbit可寻址位

C51的扩充功能支持对特殊位的定义。与SFR定义一样,关键字“sbit”用于定义某些特殊位,利用它可以访问芯片内部的RAM中的可寻址位或特殊功能寄存器中的可寻址位。如先前我们定义:

sfr P1 = 0x90; 

因P1端口的寄存器是可位寻址的,所以我们可以定义:

sbit P1_1 = P1^1;    //P1_1为P1中的P1.1 引脚 

这样在以后的程序语句中,我们就可以用P1_1来对P1.1引脚进行读写操作了。C51关键字sbit的用法有3种格式:

第1种格式:

sbit  bit-name = sfr-name^int constant; 

其中,“bit-name”是一个寻址位符号名,该位符号名必须是MCS-51单片机中规定的位名称,“sfr-name”必须是已定义过的SFR的名字,“^”后的整常数是寻址位在特殊功能寄存器“sfr-name”中的位号,必须是0~7范围中的数。

例如:

sfr   PSW=0xD0;        //定义PSW 寄存器地址为0xD0 
sbit  OV=PSW^2;        //定义OV位为PSW.2,地址为0xD2 
sbit  CY=PSW^7;        //定义CY位为PSW.7,地址为0xD7 

第2种格式:

sbit  bit-name = int constant^int constant; 

其中,“=”后的int constant为寻址位所在的特殊功能寄存器的字节地址,“^”符号后的int constant为寻址位在特殊功能寄存器中的位号。

例如:

sbit  OV=0xD0^2;       //定义OV位地址是0xD0 字节中的第2 位 
sbit  CY=0xD0^7;       //定义CY位地址是0xD0 字节中的第7 位 

第3种格式:

sbit  bit-name = int constant; 

其中,“=”后的int constant为寻址位的绝对位地址。

例如:

sbit  OV=0xD2;         //定义OV位地址为0xD2 
sbit  CY=0xD7;         //定义CY位地址为0xD7 

MCS-51系列单片机的特殊功能寄存器的数量与类型不尽相同,因此建议将所有特殊的“sfr”定义放入一个头文件中,该文件应包括MCS-51单片机系列机型中的SFR定义。

说明:在C51存储器类型中提供了一个bdata的存储器类型,是指可位寻址的数据存储器,位于单片机的可位寻址区中,可以将要求可位寻址的数据定义为bdata,如:

unsigned char bdata xb;   //在可位寻址区定义unsigned char 类型的变量xb 
int bdata yb[2];          //在可位寻址区定义数组yb[2],这些也称为可寻址位对象 
sbit xb7=xb^7             //用关键字 sbit定义位变量来独立访问可寻址位对象的其中一位 
sbit yb12=yb[1]^12; 

操作符“^”后面的位位置的最大值取决于指定的数据类型,char为0~7,int为0~15,long为0~31。

2.3.4 C语言常量与变量

常量在程序运行过程中不能改变,而变量在程序运行过程中可以不断变化。变量可以使用C51编译器支持的所有数据类型,而常量可以使用的数据类型只有整型、浮点型、字符型、字符串型和位变量。

1. 常量

常量可用在不必改变值的场合,如固定的数据表、字库等。

(1)整型常量可以表示为十进制,如123,0、-89等。十六进制则以0x开头,如0x34、-0x3B等。长整型是在数字后面加字母L,如104L、034L、0xF340等。

(2)浮点型常量采用十进制和指数表示形式。十进制由数字和小数点组成,如0.888、3345.345、0.0等,整数或小数部分为0,可以省略,但必须有小数点。指数表示形式为[±]数字[.数字]e[±]数字,[]中的内容为可选项,根据具体情况可有可无,但其余部分必须有,如125e3、7e9、-3.0e-3。

(3)字符型常量是指单引号内的字符,如'a'、'd'等,而对于无法显示的控制字符,可以在该字符前面加一个反斜杠“\”组成专用转义字符。常用转义字符参见表2-6。

(4)字符串型常量由双引号内的字符组成,如"test"、"OK"等。当引号内没有字符时,为空字符串。在使用特殊字符时同样要使用转义字符。字符串常量是作为字符类型数组来处理的,在存储字符串时,系统会在字符串尾部加上“\o”转义字符作为该字符串的结束符。字符串常量"A"和字符常量'A'是不同的,前者在存储时会多占用一个字节的空间(用于存储“\o”)。

表2-6 常用转义字符表

(5)位标量的值是一个二进制。

常量的定义方式有几种,下面来加以说明。

#difine False 0x0;          //用预定义语句定义常量 
#difine True 0x1;           //这里定义False为0,True为1 

程序中用到False和True,在编译时,会将False替换为0,True替换为1。

unsigned int code a=100;    //这一句用code把a定义在程序存储器中并赋值
const unsigned int c=100;   //用const定义c为无符号int常量并赋值 

a和c的值都保存在程序存储器中,而程序存储器在运行中是不允许被修改的,如果之后用了类似a=110、a++这样的赋值语句,编译时将会出错。

2. 变量

变量是一种在程序执行过程中其值能不断变化的量。要在程序中使用变量必须先用标识符作为变量名,并指出所用的数据类型和存储模式,这样编译系统才能为变量分配相应的存储空间。定义一个变量的格式如下:

[存储种类]数据类型[存储器类型]变量名表

在定义格式中,除了数据类型和变量名表是必要的,其他都是可选项。

(1)存储种类。存储种类有4种:自动(auto)、外部(extern)、静态(static)和寄存器(register),缺省类型为自动(auto)。

(2)数据类型。和前面的各种数据类型的定义是一样的。说明了一个变量的数据类型后,还可选择说明该变量的存储器类型。

(3)存储器类型。存储器类型是指定该变量在C51硬件系统中所使用的存储区域,并在编译时准确地定位。C51的存储器类型如表2-7所示。

如果省略存储器类型,系统会按存储模式SMALL、COMPACT或LARGE所规定的默认存储器类型去指定变量的存储区域。无论是什么存储模式,都可以声明变量位于任何的8051存储区范围,还有变量的存储种类与存储器类型是完全无关的。

表2-7 存储器类型

(4)存储模式。在SMALL存储模式中,所有函数变量和局部数据段都被放在8051系统的内部数据存储区,访问数据非常快,但SMALL存储模式的地址空间受限。在小型的应用程序中,变量和数据放在data内部数据存储器中是很好的,因为访问速度快。但在较大的应用程序中,data区最好只存放小的变量、数据或常用的变量(如循环计数、数据索引),而大的数据则放置在别的存储区域。

在COMPACT存储模式中,所有的函数和程序的变量和局部数据段均定位在8051系统的外部数据存储区。外部数据存储区最多可有256字节(一页)。在本模式中,外部数据存储区的短地址用@R0/R1表示。

在LARGE存储模式中,所有函数和过程的变量和局部数据段都定位在8051系统的外部数据存储区,外部数据存储区最多可有64KB,这要求用DPTR数据指针访问数据。

【技能训练2-2】汽车转向灯控制设计

任务5是通过开关来控制LED循环点亮的,我们如何使用开关来完成汽车转向灯控制设计呢?

1. 汽车转向灯功能分析

汽车转向灯有前左右转向灯和后左右转向灯,它们的功能如下。

(1)左右转向灯的作用是在汽车需要左转或者右转时,提醒前面和后面的车辆、行人等,车辆需要转弯,请注意。

(2)左右转向灯同时闪烁时,示意车辆有危险情况,请注意避让。

2. 汽车转向灯控制电路设计

根据汽车转向灯功能分析,开关控制汽车转向灯电路主要由单片机最小系统、开关和LED等电路组成,如图2-17所示。

根据图2-17所示,在任务5的基础上添加单刀三掷开关(SW-ROT-3)、黄色发光二极管(LED-YELLOW)和排阻(RESPACK-7)。汽车转向灯控制电路设计方法如下。

(1)开关控制电路

转向灯开关和双闪开关的右边引脚接地、左边引脚经上拉电阻接电源;

转向灯开关左边有3个引脚,上面引脚(左转开关)接P0.5引脚,中间引脚悬空,下面引脚(右转开关)接P0.7引脚;

双闪开关的左边引脚接P0.4引脚。

(2)汽车转向灯电路

汽车的前后左转向灯LED的阴极接P2.5,汽车的前后右转向灯LED的阴极接P2.4,转向灯LED的阳极经限流电阻接电源。

图2-17 汽车转向灯控制电路

3. 汽车转向灯控制实现分析

如图2-17所示,转向灯开关是分别控制前左右转向灯和后左右转向灯的,双闪开关是同时控制前左右转向灯和后左右转向灯的。汽车转向灯控制实现过程如下。

(1)当转向灯开关打到左转位置时,左侧前后转向灯LED开始闪烁。

(2)当转向灯开关打到右转位置时,右侧前后转向灯LED开始闪烁。

(3)当双闪开关闭合时,不论转向灯开关处于什么位置,左右侧的前后转向灯都同时开始闪烁,直到双闪开关断开停止闪烁。

(4)当转向灯开关打到悬空位置时,转向灯LED停止闪烁。

4. 汽车转向灯程序设计

从以上分析可以看出,汽车转向灯控制C语言程序如下:

#include <reg52.h>            //包含reg52.h头文件 
sbit SW=P0^4;                 //定义SW,是双闪开关 
sbit SWL=P0^5;                //定义SWL ,是左转开关 
sbit SWR=P0^7;                //定义SWR ,是右转开关 
sbit LEDR=P2^4;               //定义LEDR,控制右侧前后转向灯LED 亮和灭 
sbit LEDL=P2^5;               //定义LEDL,控制左侧前后转向灯LED 亮和灭 
 
void  Delay()                 //延时函数 
{ 
  unsigned char i, j; 
      for (i=0;i<255;i++) 
         for (j=0;j<255;j++); 
} 
void main() 
{  
while(1) 
   { 
       while(SW==0)           //双闪开关闭合,左右侧的前后转向灯同时开始闪烁 
    { 
         LEDR=0;              //右侧前后转向灯LED 点亮 
         LEDL=0;              //左侧前后转向灯LED 点亮 
         Delay();             //延时,左右侧的前后转向灯都保持点亮一段时间 
         LEDR=1;              //右侧前后转向灯LED 熄灭 
         LEDL=1;              //左侧前后转向灯LED 熄灭 
         Delay();             //延时,左右侧的前后转向灯都保持熄灭一段时间 
       } 
       while(SWL==0)          //左转开关闭合,左侧的前后转向灯同时开始闪烁 
    { 
  LEDL=0;                     //左侧前后转向灯LED 点亮 
    Delay();                  //延时,左侧的前后转向灯都保持点亮一段时间 
  LEDL=1;                     //左侧前后转向灯LED 熄灭 
    Delay();                  //延时,左侧的前后转向灯都保持熄灭一段时间 
    if(SW==0) break;          //若双闪开关闭合,进入双闪状态 
    } 
    while(SWR==0)             //右转开关闭合,右侧的前后转向灯同时开始闪烁 
    { 
  LEDR=0;                     //右侧前后转向灯LED 点亮 
    Delay();                  //延时,右侧的前后转向灯都保持点亮一段时间 
  LEDR=1;                     //右侧前后转向灯LED 熄灭 
    Delay();                  //延时,右侧的前后转向灯都保持熄灭一段时间
  if(SW==0) break;            //若双闪开关闭合,进入双闪状态 
    } 
    LEDR=1;                   //右侧前后转向灯LED 熄灭 
   LEDL=1;                    //左侧前后转向灯LED 熄灭 
  } 
}