目录

  • 前言
  • 硬件介绍
    • 单色LED
      • 封装形式
      • 正负极判断
    • 双色LED
    • 三色LED
    • 七彩自动闪烁LED(内置IC)
  • 原理图分析
  • 软件实现
    • 点亮一颗LED
      • 方法一
      • 方法二
    • LED闪烁
    • 流水灯
    • 跑马灯
      • 方法一
      • 方法二
      • 方法三
    • LED常用函数封装
  • 总结

前言

本节内容我们学习如何控制一颗LED灯,并简单控制它的行为:闪烁。并对多个LED实现流水灯跑马灯效果。除此之外,了解一些多色LED内部构造驱动方式

硬件介绍


单色LED

LED全称Light Emitting Diode,即发光二极管。其特点是功耗低、高亮度、抗振动、低光衰,属于冷光源

  • 响应时间短。LED 灯的响应时间为纳秒级,而白炽灯的响应时间要达到毫秒级
  • 发光色谱宽。例如:砷化镓二极管发红光,磷化镓二极管发绿光,碳化硅二极管发黄光,氮化镓二极管发蓝光
  • 驱动电压低。通常工作电压为1.6-2.2V电流在5-20mA之间,亮度随电流变化。

封装形式

一般LED有两种封装直插式贴片式


正负极判断

如何判断LED的正负极?仔细观察。

  • 直插式LED

    • 长脚正极短脚负极
    • 大电极片负极小电极片正极
  • 贴片式LED
    • 三角形顶点所对的为负极底边所对的为正极

双色LED

顾名思义,可以发出2种颜色的灯光,一般为绿两种颜色。本质是将两个LED封装在一起,每次单独点亮其中一个,达到双色显示的目的。

图1 透明双色LED 图2 雾状双色LED 图3 原理图

直插式的双色LED由三个引脚组成,中间的引脚为公共端口,有共阴极共阳极两种。可以观察中间引脚的电极片,如果是大电极,则为负极,即为共阴极


三色LED

与双色LED类似,三色LED有4个引脚,分别为GNDRGB,控制相应引脚即可发出红、绿、蓝三种颜色的灯光。


七彩自动闪烁LED(内置IC)

2个引脚,从表面看和单色LED一致,但实际上电后可以自动转换7种颜色。每一个小小的灯里都内置IC,成本贵于普通LED,但控制方便,稳定。


原理图分析

LED的电路符号如下:

最大的特点即具有单向导通性。由于单颗LED的驱动电流比较小,过大的电流会烧毁LED,故一般在电路中串联阻值100Ω左右的电阻,称为限流电阻

由于开发板中已经帮我们焊好了外设和电路,所以我们不需要自己搭建电路,但需要去查看电路原理图(各个开发板不一样,但这没有关系),找到它所对应的控制引脚,这有利于我们的后续编程。

在我的开发板中,可以看到他设计的是所有 LED 共阳极(即 LED 的阳极接在一起),故当对应 IO 引脚置低电平时 LED 发光,高电平时 LED 熄灭。


软件实现

点亮一颗LED

方法一

#include <REGX52.H>
#define LED_PORT P2void main(){//法一:直接定义整个P2口的电平LED_PORT = 0xfe; //1111 1110  D1灯点亮while(1); //使程序在这里死循环。
}

首先引入头文件 “REGX52.H”,这是C51的常用头文件,里面定义了各种寄存器,方便我们操作硬件。这里对P2口使用宏定义是需要注意的编程习惯,因为单片机的P0~P3口不同的硬件电路设计中连接的外设并不相同,并且,通用的引脚名并不能让阅读者立马知晓它的作用,利用宏定义可以增强代码的可读性可维护性。以后的代码中,我都会采用这样的代码风格。

其次,由原理图可知,8颗LED对应P2口的8位引脚,故若要使D1灯点亮,则需要将P2.0置低电平,其余全部置高电平,即1111 1110,常用16进制表示,即0xfe

最后,我们希望灯点亮之后保持这样的状态,而不是反复被点亮,所以应使程序一直停滞在main函数中,故采用一个死循环实现这样的目的。当然,如果没有写这样的死循环,你会发现实验现象并没有改变。但事实情况是,当执行完赋值语句后,程序会退出main函数,随后会不断的反复执行main函数,P2.0也会反复的置低电平,这当然不会有问题,但当任务复杂时,反复执行main函数会产生意想不到的错误。


方法二

#include <REGX52.H>
#define LED_PORT P2
//法二:定义P2口需要控制的位,并赋值电平
sbit LED_1 = LED_PORT^0; //D1灯void main(){ //赋值低电平LED_1 = 0;while(1); //使程序在这里死循环。
}

在方法一中,我们定义了8个引脚的电平情况,但事实上这是比较费事的。因为我们P2口内部有上拉电阻,即引脚默认输出高电平,我们实际只需要将一个引脚置低电平即可。故方法二中,我们先定义了需要控制的引脚位,再进行赋值

注意:

  • sbitC51扩展的标识符,而非数据类型sbit声明的部分是编译器预处理的部分,是在函数没有编译之前必须完成的,所以必须写在main函数外。且不能写进数组或是结构体循环调用
  • 位定义时,^代表的是位的位置,而不是异或运算符

LED闪烁

#include <REGX52.H>
#define LED_PORT P2
//typedef可以将一些复杂的关键字重命名
typedef unsigned char u8; //0-255,1字节
typedef unsigned int u16; //0-65535,2字节void main(){//申明延时函数void delay(u16 msec);while(1){LED_PORT = 0xfe; // 1111 1110delay(50000); //大约450msLED_PORT = 0xff; // 1111 1111delay(50000); }
}//延时函数 大致10微秒
void delay(u16 msec){while(msec--);
}

所有视频、动画、游戏的理论基础都是人眼的暂留效应。一般暂留时间大约为50ms,即一秒钟20帧以上的画面就会产生动态的视觉效果。单片机的晶振频率12MHz,计算其机器周期大约为1微秒,也就是说,两个连续的语句执行时间间隔相当短暂,以致于人眼根本无法分别LED灯的实际状态。

为了能够看到闪烁的效果,必须在点亮熄灭的语句之间加入延时函数。延时函数的本质即让运行中的程序暂停一段时间(CPU空转一段时间)。经过Keil的软件仿真50000次自减循环大致消耗450ms,这个时间间隔足以让人眼观察到灯的熄灭和点亮了。

当然你也可以自己去计算精确的延时时间,但这在本节中意义不大,我们仅需观察到闪烁现象即可。精确的延时需要配合定时器实现。


流水灯

我理解的流水灯是这种累加的效果

具体代码实现

#include <REGX52.H>
#define LED_PORT P2typedef unsigned char u8;
typedef unsigned int u16;void delay(u16 t){while(t--);
}void main(){while(1){u8 i;LED_PORT = 0xfe; //1111 1110delay(50000);for(i=0;i<8;i++){LED_PORT <<= 1; //左移一位delay(50000); //延时450ms}}
}

实现的关键是左移运算符<<,每往左移1位,最低位补0,即表现为LED逐一点亮的效果。


跑马灯

跑马灯应该是单个灯循环跑动的效果

方法一

#include <REGX52.H>
#define LED_PORT P2
#define DELAY_TIME 20000 //设置跑马灯时间间隔typedef unsigned char u8;
typedef unsigned int u16;void delay(u16 sec){while(sec--);
}//法一:列举法,流水灯一共8种状态。
void ledTest_1(){LED_PORT = 0xfe; // 1111 1110delay(DELAY_TIME);LED_PORT = 0xfd; // 1111 1101delay(DELAY_TIME);LED_PORT = 0xfb; // 1111 1011delay(DELAY_TIME);LED_PORT = 0xf7; // 1111 0111delay(DELAY_TIME);LED_PORT = 0xef; // 1110 1111delay(DELAY_TIME);LED_PORT = 0xdf; // 1101 1111delay(DELAY_TIME);LED_PORT = 0xbf; // 1011 1111delay(DELAY_TIME);LED_PORT  = 0x7f; // 0111 1111delay(DELAY_TIME);
}void main(){while(1){ledTest_1();}
}

方法一的思路就是把所有情况对应的十六进制都写出来,总共也8种状态,写呗。但缺陷也很明显,当LED数量多了之后,这种代码写起来就很痛苦了。


方法二

#include <REGX52.H>
#define LED_PORT P2
#define DELAY_TIME 20000 //设置跑马灯时间间隔typedef unsigned char u8;
typedef unsigned int u16;void delay(u16 sec){while(sec--);
}//法二:使用左移和取反运算,配合循环实现
void ledTest_2(){int i;for(i=0;i<8;i++){LED_PORT = ~(0x01<<i); //将1左移i位后补0,取反,即第i+1位灯亮delay(50000); //延迟450ms}
}void main(){while(1){ledTest_2();}
}

方法二的思路是,用一个循环实现每次将 0 移位。但直接应用左移运算符<<会存在一个问题:移位后低位自动补 0 ,这样低位的灯也会被点亮了。那怎么办?正面算法行不通,就尝试从反面去实现,即用一个循环实现每次将 1 移位,再将它按位取反~)即可。

:当我们只想改变某一位的状态不改变其他位的状态时,这是一个非常常用的位操作技巧

// 指定位, 置1
port |= 0x01<<i
// 指定位, 置0
port &= ~(0x01<<i)

方法三

#include <REGX52.H>
#include <INTRINS.H> // 定义了移位函数
#define LED_PORT P2
#define DELAY_TIME 20000 //设置跑马灯时间间隔typedef unsigned char u8;
typedef unsigned int u16;void delay(u16 sec){while(sec--);
}//法三:使用左移函数_crol_()
void ledTest_3(){LED_PORT = _crol_(LED_PORT, 1);  //左移1位(跟左移运算符不同,高位循环补至低位)delay(50000); //延迟450ms
}void main(){//法三:先初始化P2LED_PORT = 0xfe;delay(50000); //延迟450mswhile(1){ledTest_3();}
}

方法三的思路是调用C51定义好的函数实现。首先引入头文件INTRINS.H,然后就可以使用左移函数_crol_()。当然也有相应的右移 函数_cror_()


LED常用函数封装

在复杂场景中,LED仅仅作为一个小模块来配合整个项目(比如指示灯)。每次对硬件编程是耗时耗力的。基于模块化硬件抽象的思想,有必要将LED功能封装,像系统库函数或是应用软件API 一样,对外提供接口,直接调用,这样方便以后快速构建项目

delay.h

#ifndef _DELAY_H_
#define _DELAY_H_#include <regx52.h>typedef unsigned char u8;
typedef unsigned int u16;void delay_10us(u16);
void delay_ms(u16);#endif

delay.c

#include "delay.h"/***  @brief  延时函数(10us)*  @param  t:0~65535,循环一次约10us*  @retval 无*/
void delay_10us(u16 t){while(t--);
}/***  @brief 延时函数(ms)*  @param t:0~65535,单位ms*  @retval 无*/
void delay_ms(u16 t){while(t--){delay_10us(100);}
}

led.h

#ifndef _LED_H_
#define _LED_H_#include "delay.h"void led_on(u8);
void led_stream(u16);
void led_run(u16);#endif

led.c

#include "led.h"// 定义led引脚
#define LED_PORT P2/***  @brief  指定某个LED亮*  @param  pos: 位置*  @retval 无*/
void led_on(u8 pos){LED_PORT &= ~(0x01<<(pos-1));
}/***  @brief  指定某个LED灭*  @param  pos: 位置*  @retval 无*/
void led_off(u8 pos){LED_PORT |= 0x01<<(pos-1);
}/***  @brief  LED流水灯*  @param  time 延时时间*  @retval 无*/
void led_stream(u16 time){u8 i;for(i=0;i<8;i++){led_on(i+1);delay_10us(time);}// 全部熄灭for(i=0;i<8;i++){led_off(i+1);}
}/***  @brief  LED跑马灯*  @param  time 延时时间*  @retval 无*/
void led_run(u16 time){u8 i;for(i=0;i<8;i++){led_on(i+1);delay_10us(time);led_off(i+1);}
}

main.c

#include "led.h"
/***  @brief  仅提供一个demo*  @author Qiu*  @date   2023.4.13*/void main(){// 点亮一颗LED//led_on(2); while(1){// 流水灯//led_stream(50000);  // 跑马灯led_run(50000);  }
}

多文件调用为C语言内容,本文不再赘述。我只写了几种LED显示函数,各位童鞋可以自行扩充,改写。


总结

单片机是软件和硬件的桥梁,这跟C语言在编程语言中的地位类似。一般是先了解硬件的原理硬件电路图之后,才开始软件的算法编程,实现相应的效果。路还很长,共勉。

【51单片机实验笔记】1. LED的初级控制相关推荐

  1. 【51单片机实验笔记】3. LED点阵的基本控制

    目录 前言 硬件介绍 驱动芯片 74HC595芯片 MAX7219芯片 原理分析 软件实现 爱心图片 旋转大风车 滚动日期 螺旋线动画 LED点阵功能函数封装 总结 前言 本章接触的硬件依然与LED息 ...

  2. AutoLeaders控制组—51单片机学习笔记(LED控制、独立按键、数码管)

    本篇内容是观看B站江科大自化协UP主的教学视频所做的笔记,对其中内容有所引用,并结合自己的单片机板块进行了更改调整. 以下笔记内容以一个视频为一个片段(内容较多,可能不适合速食,望见谅) 根据测试,目 ...

  3. [51单片机学习笔记ONE]-----LED灯的多种使用方法

    一.交替闪烁8个LED灯,时间间隔为1s 1 /****************************************************** 2 实验名称: 交替闪烁8个LED灯,时间 ...

  4. 【51单片机实验笔记】2. 数码管的基本控制

    目录 前言 硬件介绍 原理图分析 段选和位选 驱动芯片 74HC138芯片 74HC245芯片 软件实现 点亮一只数码管 倒计时效果 动态显示字符 总结 前言 本节内容我们学习如何控制数码管,先尝试点 ...

  5. 【51单片机实验笔记】4. 蜂鸣器与扬声器的基本控制

    目录 前言 硬件介绍 PWM基础 蜂鸣器简介 扬声器简介 原理图分析 蜂鸣器驱动电路 扬声器驱动电路 软件实现 蜂鸣器短鸣 蜂鸣器功能封装 总结 前言 蜂鸣器和扬声器在生活中的应用实则相当广泛.通过本 ...

  6. 51单片机实验——LED小灯的操作(一)

    目录 引言 led模块原理图解析 led小灯的操作 1.点亮led小灯 位操作法 总线操作法 2.闪烁led小灯 位操作法 总线操作法 3.led流水灯 位操作法 总线操作法 总线操作-数组循环遍历法 ...

  7. 10.4.4 51单片机控制系统8个LED“跑马灯”实验

    10.4.4 51单片机控制系统8个LED"跑马灯"实验 仿真+代码 方法一 利用数组 #include"reg52.h"#define u8 unsigned ...

  8. 51单片机实验 LED半秒闪烁 定时器0方式2 中断实现

    51单片机实验 LED半秒闪烁 定时器0方式2 中断实现 实验目的: P1.0接一个发光管,实现亮半秒灭半秒, 要求使用定时器0工作方式2,中断实现. C语言代码: #include "at ...

  9. 基于51单片机的汇编/C程序:拨码开关控制八位LED实现流水灯

    基于51单片机的汇编程序:拨码开关控制八位LED实现流水灯 2022-03-08,51单片机学习笔记 功能 打开K1循环左移,打开K2循环右移,打开K3两侧到中心来回点亮,打开K4单双数位置交替点亮. ...

最新文章

  1. IIS 7 应用程序池自动回收关闭的解决方案
  2. std string与线程安全_详解linux系统中断线程的那些事
  3. 门户网站建设与运营需要付出更多成本
  4. leetcode 293.Flip Game(lintcode 914) 、294.Flip Game II(lintcode 913)
  5. linux sed给空文件首行插入_linux下批量修改文件后缀名以及合并多行
  6. Android开发之--Preferences的使用
  7. Intel Realsense C/C++ 转 python (9)rs-multicam 多摄像头可视化窗体显示
  8. LINQ系列:LINQ to SQL Join连接
  9. 用python排序算法_Python - 八大排序算法
  10. 在Java里面使用instanceof的性能影响
  11. 前端学习(3246):react的生命周期getSnap
  12. ES6_类_note
  13. vscode remote免密登录
  14. impacket安装 python_安装impacket
  15. linux怎么开启httpd服务公钥,在Apache httpd服务器上部署SSL证书
  16. 口布杯花的60种叠法_10种餐巾折花杯花的步骤用文字解说怎么折
  17. iOS开发实例 | Demo:数独小游戏
  18. Google网络硬盘GDrive在几个月内即将成真?!
  19. nuc972 linux 升级,NUC972移植工作记录
  20. uniapp中使用网页录音并上传声音文件(发语音)——js-audio-recorder的使用【伸手党福利】

热门文章

  1. 使用Grabit自定义SQL收集metadata
  2. 【YOLOv7】结合GradCAM热力图可视化
  3. 关于RFID电感耦合方式的射频前端工作原理,你了解吗?
  4. 关于三子棋游戏的简易实现与N子棋胜利判断方法
  5. 陈庆平获评2021年湖南省“最美科技工作者”
  6. ch376inc.h
  7. pc端启用微信小程序
  8. html怎么移动按钮位置,CSS Nav按钮向左下方移动(CSS Nav buttons move bottom left)
  9. linux模拟树莓派,使用QEMU模拟树莓派Raspberry Pi
  10. Redis集群Hash槽分配异常 CLUSTERDOWN Hash slot not served的解决方式