51单片机(五)—— GPIO点亮一个LED

一、LED控制介绍

        发光二极管是半导体二极管的一种,可以把电能转化成光能,常简写为LED。发光二极管与普通二极管一样是由一个PN结组成,也具有单向导电特性。当给发光二极管加上正向电压时就会发光,光的强弱与工作电流成正比。一般情况下,LED的正向工作电流在10mA左右,如果电流过大就会烧坏LED,因此使用时必须串联限流电阻以控制通过二极管的电流。

        普通发光二极管的正向饱和压降为1.6V~2.1V。发光二极管的特点是:工作电压很低(有的仅一点几伏);工作电流很小(有的仅零点几毫安即可发光);抗冲击和抗震性能好,可靠性高,寿命长;通过调制通过的电流强弱可以方便地调制发光的强弱。由于有这些特点,发光二极管在一些光电控制设备中用作光源,并广泛应用于各种电子电路、家电、仪表等设备中。

        本文使用的开发板上设计了8个发光二极管,其中有2个红色、2个黄色、2个绿色和2个蓝色。发光二极管部分的原理图如下图所示。R21是470欧姆的8位阻排,LED1~LED8是8个发光二极管,阻排的公共端与5V电源VCC相连,发光二极管的正极与阻排相连,负极与插针J21相连,J21经过杜邦线可以连接到需要使用的I/O口。连接好后,单片机输出低电平时对应的LED灯点亮,高电平时对应的LED灯熄灭。

        我们来了解下I/O口的工作原理。I/O口即输入和输出口,单片机的I/O口既可以作为输入信号端、也可以作为输出信号端。图中,J21可以接5V、悬空、也可以接地。以LED1为例,我们将LED1的等效电路单独画出来,如下图所示。

图中VCC是5V的电源,当电路的右侧接5V或者悬空时,电路没有电流通过,LED灯的状态是熄灭的;当电路的右侧接地时,两端压差是5V,所以LED灯被点亮。在数字电路中,接+5V为电平“1”,接地为“0”。

        所以在设计中,将LED连接到单片机的I/O口,此时我们只需要控制单片机的I/O口为“1”或者为“0”就可以控制LED灯的亮灭了。

二、例程测试

        在这个实验中我们用两种不同的方法对一个LED灯进行控制,在测试之前需要将上边J21的1引脚与单片机的P00用杜邦线连接起来。

方法1:

实现的代码如下所示

#include<reg52.h> 

sbit LED=P0^0;   
			  
void main (void)
{
	LED=0;            //将P00口赋值0,对外输出低电平
	for(;;);          //死循环,原地等待
}

将这个代码编译之后将Hex文件烧写到单片机中,可以看到LED1点亮。

        1、在上面的代码中,#include<reg52.h>这是一个预处理命令,所谓的预处理命令就是在程序编译之前进行的命令。预处理命令以“#”开始,“include”是文件包含命令。除了文件包含命令,常见的预处理命令还有:宏定义和条件编译。在C语言中,头文件被大量使用。头文件是一种包含功能函数、数据接口声明的载体文件,C程序的头文件以“.h”为后缀。一般在一个应用开发体系中,功能的真正逻辑实现是以硬件层为基础,在驱动程序、功能层程序以及用户的应用程序中完成的。头文件的主要作用在于调用库功能,对各个被调用函数给出一个描述,其本身不包含程序的逻辑实现代码,它只起描述性作用,告诉应用程序通过相应途径寻找相应功能函数的真正逻辑实现代码。用户程序只需要按照头文件中的接口声明来调用库功能,编译器会从库中提取相应的代码。#include<reg52.h>这一句的含义就是这个工程包含了reg52.h这个文件中的内容。reg52.h这个文件中的内容如下所示。

#ifndef __REG52_H__
#define __REG52_H__

/*  BYTE Registers  */
sfr P0    = 0x80;
sfr P1    = 0x90;
sfr P2    = 0xA0;
sfr P3    = 0xB0;
sfr PSW   = 0xD0;
sfr ACC   = 0xE0;
sfr B     = 0xF0;
sfr SP    = 0x81;
sfr DPL   = 0x82;
sfr DPH   = 0x83;
sfr PCON  = 0x87;
sfr TCON  = 0x88;
sfr TMOD  = 0x89;
sfr TL0   = 0x8A;
sfr TL1   = 0x8B;
sfr TH0   = 0x8C;
sfr TH1   = 0x8D;
sfr IE    = 0xA8;
sfr IP    = 0xB8;
sfr SCON  = 0x98;
sfr SBUF  = 0x99;

/*  8052 Extensions  */
sfr T2CON  = 0xC8;
sfr RCAP2L = 0xCA;
sfr RCAP2H = 0xCB;
sfr TL2    = 0xCC;
sfr TH2    = 0xCD;


/*  BIT Registers  */
/*  PSW  */
sbit CY    = PSW^7;
sbit AC    = PSW^6;
sbit F0    = PSW^5;
sbit RS1   = PSW^4;
sbit RS0   = PSW^3;
sbit OV    = PSW^2;
sbit P     = PSW^0; //8052 only

/*  TCON  */
sbit TF1   = TCON^7;
sbit TR1   = TCON^6;
sbit TF0   = TCON^5;
sbit TR0   = TCON^4;
sbit IE1   = TCON^3;
sbit IT1   = TCON^2;
sbit IE0   = TCON^1;
sbit IT0   = TCON^0;

/*  IE  */
sbit EA    = IE^7;
sbit ET2   = IE^5; //8052 only
sbit ES    = IE^4;
sbit ET1   = IE^3;
sbit EX1   = IE^2;
sbit ET0   = IE^1;
sbit EX0   = IE^0;

/*  IP  */
sbit PT2   = IP^5;
sbit PS    = IP^4;
sbit PT1   = IP^3;
sbit PX1   = IP^2;
sbit PT0   = IP^1;
sbit PX0   = IP^0;

/*  P3  */
sbit RD    = P3^7;
sbit WR    = P3^6;
sbit T1    = P3^5;
sbit T0    = P3^4;
sbit INT1  = P3^3;
sbit INT0  = P3^2;
sbit TXD   = P3^1;
sbit RXD   = P3^0;

/*  SCON  */
sbit SM0   = SCON^7;
sbit SM1   = SCON^6;
sbit SM2   = SCON^5;
sbit REN   = SCON^4;
sbit TB8   = SCON^3;
sbit RB8   = SCON^2;
sbit TI    = SCON^1;
sbit RI    = SCON^0;

/*  P1  */
sbit T2EX  = P1^1; // 8052 only
sbit T2    = P1^0; // 8052 only
             
/*  T2CON  */
sbit TF2    = T2CON^7;
sbit EXF2   = T2CON^6;
sbit RCLK   = T2CON^5;
sbit TCLK   = T2CON^4;
sbit EXEN2  = T2CON^3;
sbit TR2    = T2CON^2;
sbit C_T2   = T2CON^1;
sbit CP_RL2 = T2CON^0;

#endif

        从这个文件的定义可以看出,这个文件主要定义了单片机的端口和特殊功能寄存器。程序中包含了头文件之后,就可以直接使用定义过的标志符。例如:P0口的寄存器地址是0x80,其中0x表示它后边的数值是16进制的。如果对P0口进行操作,我们直接使用P0这个标志符就行了,而不需要了解P0口寄存器的物理地址和内部结构。

        2、sfr和sbit,reg52.h中可以看到这两个关键字。“sfr P0= 0x80;”这一句的含义是将单片机内部地址为0x80的寄存器重新起名为P0,以后我们在程序中就可以直接操作P0,就相当于对单片机内部的0x80地址处的寄存器进行操作。实际上,通过sfr关键字的定义,让Keil编译器在单片机和用户之间搭建了一条可以进行沟通的桥梁,我们操作P0口,而单片机并不知道什么是P0口,但它知道知道它内部的地址0x80是什么。

        “sbit CY= PSW^7;”这一句的意思是将PSW这个寄存器的第7位重新命名为CY,所以在需要单独操作PSW寄存器第7位时,可以直接操作CY。

        在程序中有一句“sbit LED=P0^0;” 这一句的意思是将P0口寄存器的第0位,也就是最低位定义为LED,因此程序中操作LED时相当于操作P0口寄存器的第0位。例如:LED=0; 相当将0赋值给P0口寄存器的第0位。

        3、main()函数,主函数就是main函数,是程序的入口,程序一旦执行的时候就会从这个入口开始,任何一个程序中有且只有一个主函数。

        4、for(;;)。for()循环是C语言中一种基本的循环方式,当条件为真时,进入循环体,条件不满足时,跳出。

for()循环的标准格式为

for(表达式1;表达式2;表达式3)

{语句(内部可以为空)}

我们来看一下下边这段程序。

              unsigned char i;

              for(i=2;i>0;i++)

              {      }

这段程序首先定义了一个无符号字符型变量i,然后指向for语句。i=2;i>0;i++这三个表达式中,表达式1是给i赋一个初值2,表达式2判断i>0是真还是假,表达式3是i每个周期减1。我们来分析一下这个for循环的执行过程。

第一步:给i赋初值2,此时i=2。

第二步:因为2>0,条件成立,所以其值为真,那么执行for循环下边大括号中的内容。由于大括号中为空,所以什么也不执行。

第三步:i自减1,即i=i-1=2-1=1。

第四步:跳回到第二步,因为1>0,条件成立,所以其值为真,那么执行for循环下边大括号中的内容。由于大括号中为空,所以什么也不执行。

第五步:i自减1,即i=i-1=1-1=0。

第六步:跳回到第二步,因为0>0,条件不成立,所以其值为假,那么结束for循环,程序从for循环中跳出。

“for(;;);”这个语句中for的三个表达式都为空,这个for语句是无限循环。

本例中,进入main函数对LED灯进行操作,之后进入for循环,并一直在循环中等待,不进行任何操作,因此LED灯的状态也不会发生变化。

该程序虽然简单,但是包含C语言最基础的知识,后面的程序会在此基础上增加新的内容。

        5、程序的注释,//和/* */这两种符号表示注释。注释语句虽然不对程序的运行产生任何影响,但必要的注释是程序的重要组成部分。对于一个程序员来说,及时加注释是一个好的习惯。上述两个注释的区别在于://是行注释,换行无效;/* */中间的内容都是注释,换行有效。注释可以根据大家的习惯,没有具体要求。

方法二:

        实现代码如下所示

#include<reg52.h> 
#define LED  P0    //宏定义关键字,定义LED到单片机的P0口

void main (void)
{
	LED=0xfe;         //将P0口赋值0xfe,
	                  //0xfe转换为二进制为 1111 1110,即P0口的最低位输出低电平,其它位输出高电平。
	for(;;);          //死循环,原地等待
}

在这个实验中,对LED控制的命令与方法一是不同的,主要知识点如下:

1、宏定义,宏定义又称为宏代换、宏替换,简称“宏”。

宏定义的格式:

#define 标识符 字符串

        其中的标识符就是所谓的符号常量,也称为“宏名”。预处理(预编译)工作也叫做宏展开,将宏名替换为字符串。掌握“宏”概念的关键是“换”。一切以换为前提、做任何事情之前先要换,准确理解之前就要“换”。即在对相关命令或语句的含义和功能作具体分析之前就要换。

        例如程序中#define  LED  P0

表示LED灯定义在P0口上。因此对P0口进行操作,我们可以直接用LED来代替,LED=0xfe;就相当于P0=0xfe;

2、进制转换,这个实验与第一个实验不同,直接对P0口整体进行操作。P0口的宽度是8位,用二进制表示是xxxx xxxx。单片机常用的进制是:二进制、八进制、十进制和十六进制,0x前缀表示十六进制,如:0xFF。0xFF与0xff相同,C语言中数值不区分大小写。这几种进制形式不同,但是可以相互转换。

        本实验中同样是点亮了LED1。但是对P0口进行整体赋值的方法,将0xfe赋给了P0,0xfe转换成2进制就是11111110,也就是将P0口的高7位拉高,最低位拉低,这样LED1就可以点亮了,其余的灯熄灭。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

发布了78 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/bhniunan/article/details/104332997