发光二极管是半导体二极管的一种,可以把电能转化成光能,常简写为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用杜邦线连接起来。
实现的代码如下所示
#include
sbit LED=P0^0;
void main (void)
{
LED=0; //将P00口赋值0,对外输出低电平
for(;;); //死循环,原地等待
}
将这个代码编译之后将Hex文件烧写到单片机中,可以看到LED1点亮。
1、在上面的代码中,#include
#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
#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就可以点亮了,其余的灯熄灭。