I/O输出——实例1:LED点亮-入门版

时间:2024-03-18 18:28:17
I/O输出——实例1:LED点亮-入门版


一、实例目的
      通过本实例,我们应:
      1. 了解发光二极管(LED)驱动电路的原理;
      2. 掌握单片机I/O控制LED的程序设计;
      3. 掌握C51程序的结构;
      4. 会编写软件延时函数。 
      提示:本实例属于入门级,初学者应该熟练掌握,即在学完本课程后,不看资料就能画出电路图和编写出程序并验证。

二、LED驱动原理
      点亮LED通常是单片机初学者的第一个程序,如同学习C语言的第一个程序永远是打印“Hello World”一样,亘古不变。
      要点亮一个LED,需要用到单片机的IO输出口,输出一个高电平(数字1)或低电平(数字0),至于是高电平还是低电平,这取决于LED驱动电路。LED驱动电路通常有两种,如图1所示。图中,R为限流电阻。
     I/O输出——实例1:LED点亮-入门版
图1 LED驱动电路

  • 方式1: LED的负极接MCU的输出I/O,正极通过一个限流电阻接到电源VCC。LED驱动电流Id由电源VCC提供,流向MCU内部,称为灌电流。 控制I/O输出低电平时,LED正向导通,LED点亮;控制I/O输出高电平时,LED反向截止,LED熄灭。方式1对MCU的I/O电流驱动能力要求低。
  • 方式2: LED的正极接MCU的输出/O,负极通过一个限流电阻接到地GND。LED驱动电流由MCU提供。 控制I/O输出高电平时,LED正向导通,LED点亮;控制I/O输出低电平时,LED截止,LED熄灭。方式2要求MCU的I/O具有mA级的电流输出能力。 
 
       发光LED的正向导通电压一般在2.0V上下,不同材质和颜色的导通电压不同。LED的工作电流一般为mA级别(10~20mA),电流越大LED越亮。限流电阻R计算公式如下:
       R = (VCC-Ud)/Id
       取Ud=2.0V, VCC=5V, Id=10mA,代入公式得:R=300Ω。通常限流电阻为几百欧,过大则可能达不到导通电压,无法点亮LED,过小LED工作电流大,可能烧掉LED,或影响寿命。
       注意,51单片机的I/O驱动能力只有几百uA,采用方式2是无法直接点亮一个LED的。因此只能采取方式1来驱动LED。
三、LED驱动电路设计
     假设通过8051单片机P1口驱动8只LED,P2.1管脚驱动一只LED。电路图如图2所示。
   I/O输出——实例1:LED点亮-入门版
图2 LED驱动电路实例

参考“Keil基本操作",使用Proteus ISIS画出以上电路图。
   四、LED驱动程序设计
(一)编程实现D1~D4和D9点亮,D5~D8熄灭。
  根据图2所示的电路,可知LED控制管脚输出低电平时,LED点亮;输出高电平时,LED熄灭。根据LED与I/O的连接关系,得到表1。
表1  LED与单片机I/O连接及驱动电平
LED
D9
D8
D7
D6
D5
D4
D3
D2
D1
I/O P2.1
P1.7
P1.6
P1.5
P1.4
P1.3
P1.2
P1.1
P1.0
电平
0
1
1
1
1
0
0
0
0

根据表1,P0输出1111_0000,即十六进制的0xF0,P2.1输出0即可满足要求。

代码如下,相关说明见注释:
//样例一
#include<reg52.h>       //包含单片机头文件,51系列单片机为reg51.h,51系列为reg52.h
#define LED1 P1           // 定义宏LED1,值为P1寄存器,编译时先使用P1替换所有的LED1
sbit LED2 = P2^1;        //定义sbit变量LED2,指向P2.1,^符号取位符
void main()                  //main()函数为单片机程序入口,无返回值,即void类型
{
  while(1)                     //单片机程序就是一个死循环,反复执行循环体,但一般根据输入或中断完成相应功能。
  {
    LED1 = 0xF0;          //根据要求控制LED亮和灭,D1~D4亮,D5-D8灭。
    LED2 = 0;               //P2.1输出低电平,D9亮
  }
}

试一试: 将以上代码编译生成.hex,导入Proteus电路图进行仿真,进行验证。

(二)编程实现D1~D8和D9闪烁,即全亮->全灭->全亮,如此反复。闪烁间隔不限。
        为了实现闪烁,必须引入延时函数。延时函数的编写方法有软件延时函数和定时器延时。关于定时器延时,在学习到定时器再介绍。下面先介绍软件延时的原理及延时函数的编写。
        单片机指令系统里有一条NOP指令,即空指令。执行一条NOP指令,单片机仅仅是将PC加一,CPU不作其它操作,空指令因此得名。NOP指令消耗一个机器周期,如果采用12MHz的晶体,则执行一条NOP指令的耗时是1us。执行N条NOP指令,理论耗时N×1us。因此,延时函数就是通过循环执行NOP指令达到延时的目的。 
        常见的 C51的软件延时函数如下。不管延时函数如何变化,都是采用循环指令空语句(;)实现。注意,由于循环初值设置和循环递增都会消耗机器周期,延时函数的延时时间并不精确,循环次数的选取一定程序上取决于经验值(一般通过仿真或实测得到)。
/******************************
*函数:delayMS
*功能:ms级延时函数@12MHz
*参数:ms:unsigned int,最大延时约65.535s
*返回值:无
*******************************/
void delayMS(unsigned int ms)
{
    unsigned int i,j;
    for(i=0;i<ms;i++)
        for(j=0;j<150;j++);
}
    
      整个代码如下,相关说明见注释:
//样例二(a)
#include<reg52.h>       //包含单片机头文件,51系列单片机为reg51.h,51系列为reg52.h
#define LED1 P1           // 定义宏LED1,值为P1寄存器,编译时先使用P1替换所有的LED1
sbit LED2 = P2^1;        //定义sbit变量LED2,指向P2.1,^符号取位符
/******************************
*函数:delayMS
*功能:ms级延时函数@12MHz
*参数:ms:unsigned int,最大延时约65.535s
*返回值:无
*******************************/
void delayMS(unsigned int ms)
{
    unsigned int i,j;
    for(i=0;i<ms;i++)
        for(j=0;j<150;j++);
}

void main()                  //main()函数为单片机程序入口,无返回值,即void类型
{
  while(1)                     //单片机程序就是一个死循环,反复执行循环体,但一般根据输入或中断完成相应功能。
  {
      LED1 = 0x00;          //D1~D8全亮
      LED2 = 0;               //D9亮
      delayMS(1000);    //延时约1s
      LED1 = 0xFF;          //D1~D8全灭
      LED2 = 1;               //D9亮
      delayMS(1000);    //延时约1s     
  }
}

试一试: 将以上代码编译生成.hex,导入Proteus电路图进行仿真,进行验证。
在样例二(a)中,delayMS()函数是先声明且实现(定义)后才调用。C语言严格要求函数必须先声明,后调用,当然函数的声明和实现可以分开。如样例二(b) 。在编程时,可以灵活运用。
//样例二(b)
#include<reg52.h>       //包含单片机头文件,51系列单片机为reg51.h,51系列为reg52.h
#define LED1 P1           // 定义宏LED1,值为P1寄存器,编译时先使用P1替换所有的LED1
sbit LED2 = P2^1;        //定义sbit变量LED2,指向P2.1,^符号取位符
void delayMS(unsigned int ms); //delayMS()函数声明 

void main()                  //main()函数为单片机程序入口,无返回值,即void类型
{
  while(1)                     //单片机程序就是一个死循环,反复执行循环体,但一般根据输入或中断完成相应功能。
  {
      LED1 = 0x00;          //D1~D8全亮
      LED2 = 0;               //D9亮
      delayMS(1000);    //延时约1s
      LED1 = 0xFF;          //D1~D8全灭
      LED2 = 1;               //D9亮
      delayMS(1000);    //延时约1s     
  }
}

// delayMS实现
/******************************
*函数:delayMS
*功能:ms级延时函数@12MHz
*参数:ms:unsigned int,最大延时约65.535s
*返回值:无
*******************************/
void delayMS(unsigned int ms)
{
    unsigned int i,j;
    for(i=0;i<ms;i++)
        for(j=0;j<150;j++);
}



   五、试一试
  1. LED闪烁控制代码使用了6条语句,如下所示。你可以只使用四条语句完成相同功能吗?
    
      LED1 = 0x00;          //D1~D8全亮
      LED2 = 0;               //D9亮
      delayMS(1000);    //延时约1s
      LED1 = 0xFF;          //D1~D8全灭
      LED2 = 1;               //D9亮
      delayMS(1000);    //延时约1s   

    2.本实例通过I/O(P0、P2.1)控制LED,掌握I/O口输出编程非常重要且必须。试说出I/O输出和并口特殊功能寄存器的关系,写出I/O输出相关的代码。