当前位置: 首页 > news >正文

单片机-89C51部分:5、点亮LED

飞书文档https://x509p6c8to.feishu.cn/wiki/SlB5wYD1QiPRzWkfijEcIvv8nyc

一、应用场景

二、点灯原理

插件led灯珠长引脚为正极,短引脚为负极。

LED(发光二极管)两端存在电压差,有一定的电流流过时会亮起。电流可以理解为水流,电压差可以理解为水位差,当两个点水位高度不一样时,水流会从高水位流向低水位。

但是要注意:流过LED的电流需要在一定范围内,否则会烧坏LED,一般小于20ma,所以我们就需要串联电阻分压,那串联的电阻需要多大阻值呢?

LED的限流电阻计算:

一般插件LED电流是20ma左右,压降:红/黄色1.8V, 蓝/白色 3V, 实际电压要看 LED 规格书。

一般贴片LED电流是5ma左右,压降:红/绿/橙色1.8V,蓝/白色 3V

例如:供电电压是3.3V,黄色贴片LED

根据V = I * R ,则R= (3.3 - 1.8) /0.005 (5ma = 0.005), 所以 R = 300 欧姆.

很多时候你看到别人设计的电路中,LED串联的电阻去到几百欧或几千欧都有,是设计错了吗?

实际上这是非常合理的,因为大多数电路中,LED只是一个提示灯,对亮度没有要求,反而希望把功耗降低,所以需要增大限流电阻来实现超低电流,像产品中的贴片LED去到0.5ma也是能看清楚灯光的。

(3.3-1.8)/0.005 = 300欧

三、点亮LED

知道LED的点亮原理后,我们来看下如何用单片机点亮LED

首先是接线部分,我们可以通过单片机的引脚,又叫做GPIO,这些IO都是具备输出和输入能力的,什么是 GPIO 呢?GPIO 的 英文全程是 General-purpose input/output,翻译过来就是:通用输入输出,也就是我们可以设置某个引脚输出高低电平,或读取某个引脚输入的电平。

对于这种有44个引脚的封装方式的51单片机,它有以下IO口:

P0段:P0.0 P0.1 P0.2 P0.3 P0.4 P0.5 P0.6 P0.7

P1段:P1.0 P1.1 P1.2 P1.3 P1.4 P1.5 P1.6 P1.7

P2段:P2.0 P2.1 P2.2 P2.3 P2.4 P2.5 P2.6 P2.7

P3段:P3.0 P3.1 P3.2 P3.3 P3.4 P3.5 P3.6 P3.7

P4段:P4.0 P4.1 P4.2 P4.3 P4.4 P4.5 P4.6

其中,P0段的GPIO口叫做P0 IO口,简称P0口。

所以,所以我们的做法是:将LED和电阻串联,一段接负极(接地),另一端接单片机IO口,然后 控制单片机的IO口电平 就可以决定LED是否点亮了

方式一

方式二
 

注意:通常我们不采用第一种情况,因为单片机IO口虽然可以达到高电压,但是输出电流不会大,因此单片机IO口给LED供电时可能会出现电流不足,导致LED不会全亮。
因此,我们一般选择第二种,利用电源直接供电,电流灌入单片机IO口的方式来给LED灯供电

那我们如何通过编程控制芯片某个IO输出高低电平呢?

其实就是操作芯片对应IO的寄存器即可,这部分我们可以在芯片手册上看到

51单片机的每组IO口的电平状态都被储存在一个单独的8位寄存器中,寄存器的位为0或1就分别对应其相应的GPIO电平为低或高。

那如何点亮LED呢?板卡LED部分的原理图如下:

我们可以看到LED1 LED2 LED3分别接在P2.7 P2.6 P2.5上,所以要点亮LED1,就需要设置P2.7输出低电平,如何用代码设置IO输出低电平呢?下面是操作方法:

因为摆放顺序错的原因,LED灯的顺序如下哦:

方法一:操作整个寄存器

直接在C语言代码中用赋值法即可,例如操作P2口的P2.0 P2.1 P2.4 P2.6口输出高电平的代码是

Java
P2 = 0x53;
0x53
的是16进制的53,即53H,对应二进制01010011,即01010011B,所以它对应P2.0 P2.1 P2.4 P2.6口输出高电平

这里的0x53是十六进制的表示方法,为什么需要十六进制,什么是十进制,什么是二进制呢?

进制的作用:更好的表示数值。
十进制:十进制是人类最自然的数字系统,因为我们有十个手指,所以我们使用十进制进行计数,使用 0-9 来表示数字,逢10进1。
二进制:二进制的出现是因为数字电路中,数字信号只有高/低或开/关两种状态,所以只需要使用两个数字 0 和 1 来表示数字,优点是非常直观,缺点是书写起来冗长,过长的二进制容易记录错误,不便阅读和书写。
十六进制:为了可以更紧凑地表示二进制数据,就有了十六进制,使用十六个数字 0-9 和字母 A-F 来表示数字,在代码中,使用0x前缀来表示十六进制。

Java
十进制
char i = 10;
十六进制
char i = 0x0a;

所以,我们可以设置P2.0-P2.7输出都为低电平,代码这样写。

Java
#include <reg52.h>
void main(){
  P2=0x00;  //0000 0000
  while(1)
  {
   
  }
}

那如果我们只想控制某个IO,例如P2.7,不想控制P2所有IO呢?是否有办法?

方法二 操作寄存器的某个位

在C语言中,先定义你要操作的位,使用关键字sbit(这是标准C语言没有的关键字,是C51编译器的拓展关键字,用于定义寄存器的某个位)。你就可以理解成sbit就是给寄存器的某个位起一个别名。之后用等号赋值即可更改此位的0或1

sbit led1= Px^y;

符号^在C51中也表示寄存器的第某位。如上方代码,Px^y表示Px寄存器的第y位。

//定义变量led1表示P2寄存器的第7位
sbit led1= P2^7;

注意:"^"实质代表的是异或运算,可以算一下,恰好符合。

Java
//定义变量led1表示P2寄存器的第7位
sbit led1= P2^7;
//定义变量led2表示P2寄存器的第6位
sbit led2= P2^6;
        ...
        //将P2.7口 输出低电平
        led1= 0;
        //将P2.6口 输出高电平
        led2= 1;
        ...

所以,我们可以这样来定义P2.7这个引脚的输出高电平。

C
#include <reg52.h>
sbit led = P2^7;
void main(void)
{
    led = 0;

    while(1)
    {
               
    }
}
 

四、闪烁控制

前面,我们通过设置某个引脚输出低电平,点亮LED,也就是我们设置输出高电平时,会关闭LED,那我们需要实现LED闪烁的功能时,如何实现呢?

答案是:先点亮LED,延时一段时间,关闭LED,再延时一段时间,如此反复,就可以得到一个闪烁的LED功能。

在单片机中如何实现延时功能呢?有以下几种生成延时函数的生成方法

1、让芯片循环执行一些无意义的代码

我们知道,芯片执行每一句代码都需要时间,假设执行一句代码的时间为1ms,那我们点亮LED,然后执行500句(500ms)无意义的代码,再关闭LED,就可以得到闪烁的效果啦。

Java
51单片机的时钟、机器周期和指令周期
①时钟周期
主频:芯片主时钟的频率,通常以MHz或者GHz为单位,单片机的主时钟频率是外部晶振。
时钟周期(振荡周期) = 晶振频率的倒数。例如12M的晶振,它的时间周期就是1/12 us),是计算机中最基本的、最小的时间单位。

③机器周期
在芯片中,为了便于管理,常把一条指令的执行过程划分为若干个阶段,每一阶段完成一项工作。例如,取指令、存储器读、存储器写等,这每一项工作称为一个基本操作。完成一个基本操作所需要的时间称为机器周期。一般情况下,一个机器周期由若干个时钟周期组成。

④指令周期
CPU从存储器中取出并执行一条指令所需的全部时间称之为指令周期。
CPU执行一条指令的过程,计算机每执行一条指令的过程,可分解为如下步骤:
Instruction Fetch(取指令):指令放在存储器,通过PC寄存器和指令寄存器取出指令的过程,由控制器(Control Unit)操作。 从PC寄存器找到对应指令地址,据指令地址从内存把具体指令加载到指令寄存器,然后PC寄存器自增;
Instruction Decode(译码):据指令寄存器里面的指令,是哪一种类型的指令,解析成要进行什么操作,具体要操作哪些寄存器、数据或内存地址。该阶段也是由控制器执行;
Execute(执行):实际执行算术逻辑操作、数据传输或者直接的地址跳转操作。无论是算术操作、逻辑操作的指令,还是数据传输、条件分支的指令,都由算术逻辑单元(ALU)操作,即由运算器处理。如果是一个简单的无条件地址跳转,那可直接在控制器里完成,无需运算器
重复1~3的过程,这个循环完成的时间即指令周期,指令不同,所需的机器周期数也不同,一般由1~4个机器周期组成。
如:MOV A, Rn (数据传送指令) 就只需要一个机器周期, DIV AB (除法指令)就需要四个机器周期。

注意:上面讲的指令周期是51单片机的汇编指令,但我们写的程序是用C语言,C语言最终是会编译成汇编指令,在keil上的debug中可以看得到,后面讲的内容中延时程序就是通过以上的指令周期来进行计算的。

具体如何计算呢:使用编译器编译为汇编代码,结合芯片的指令周期计算时间,可以参考https://godbolt.org/

Java
//带参延时函数
void delay_ms(unsigned int xms)   //@12MHz
{
    unsigned int i, j;
    for(i=xms;i>0;i--)
    {
        for(j=124;j>0;j--)
        {}
    }
}

2、使用STC-ISP工具生成

Java
//延时函数,延时500ms
void delay_500ms()
{
        unsigned char i, j, k;
        i = 4;
        j = 129;
        k = 119;
        do
        {
                do
                {
                        while (--k);
                } while (--j);
        } while (--i);
}

最终效果

Java
#include <reg52.h>

sbit led = P2^7;

//
带参延时函数
void delay_ms(unsigned int xms)   //@12MHz
{
    unsigned int i, j;
    for(i=xms;i>0;i--)
    {
        for(j=124;j>0;j--)
        {}
    }
}

void main()
{
    while(1)
    {
        led = 0;
        delay_ms(500);
        led = 1;
        delay_ms(500);
    }
}

五、流水灯

如果需要实现多个灯的显示和熄灭呢?

其实就是流水灯的效果

C
#include "reg52.h"
#include "intrins.h"

sbit LED1 = P2^7;
sbit LED2 = P2^6;
sbit LED3 = P2^5;

void Delay1000ms()                //@11.0592MHz
{
        unsigned char i, j, k;

        _nop_();
        i = 8;
        j = 1;
        k = 243;
        do
        {
                do
                {
                        while (--k);
                } while (--j);
        } while (--i);
}

void main(){
        while(1){
                LED1 = 0;
                LED2 = 1;
                LED3 = 1;
                Delay1000ms();
                LED2 = 0;
                LED1 = 1;
                LED3 = 1;
                Delay1000ms();
                LED3 = 0;
                LED1 = 1;
                LED2 = 1;
                Delay1000ms();
        }
}

代码优化

Java
#include <reg52.h>

void delay_ms(unsigned int xms)
{
    unsigned int i, j;
    for(i=xms;i>0;i--)
    {
        for(j=124;j>0;j--)
        {}
    }
}

void main()
{
    while(1)
    {
        P2 = 0xDF; //1101 1111
        delay_ms(500);
        P2 = 0xBF; //1011 1111
        delay_ms(500);
        P2 = 0x7F; //0111 1111
        delay_ms(500);
    }
}

继续优化:

Java
#include <reg52.h>

void delay_ms(unsigned int xms)
{
    unsigned int i, j;
    for(i=xms;i>0;i--)
    {
        for(j=124;j>0;j--)
        {}
    }
}

void open_led(unsigned int num){
    P2 = 0xFF & ~(1 << num);
}

void main()
{
    while(1)
    {
        open_led(7);
        delay_ms(500);
        open_led(6);
        delay_ms(500);
        open_led(5);
        delay_ms(500);
    }
}

 

六、扩展

bit、sbit、sfr的区别

首先,bit和sbit都是C51扩展的变量类型

bit是一种数据类型,ta就跟int、char、double这些数据类型一样,只不过char=8位, bit=1位而已,这种变量只有两种值存在0或是1,和bool类似

sbit更像是define或者typedef一样(define和typedef也有区别,这又是另外一个坑,这里就不说了),ta是为已经分配了内存空间的变量重新取一个别名,一般用来定义特殊功能寄存器的位变量,以方便对寄存器的某位进行操作的,例如

Java
bit i = 0;意思就是在内存中划一块空间给i,让他存储0这个数据位。
sbit Flag = P0^1;意思就是给P0^1取一个别名,它叫做Flag,他是一个位操作变量,它代表访问的地址是P0^1,0x80的第二位数据位。

sbit的用法有三种:

第一种方法:sbit 位变量名=地址值

第二种方法:sbit 位变量名=SFR名称^变量位地址值

第三种方法:sbit 位变量名=SFR地址值^变量位地址值

Java
如定义PSW中的OV可以用以下三种方法:
sbit OV=0xd2 (1)说明:0xd2是OV的位地址值
sbit OV=PSW^2 (2)说明:其中PSW必须先用sfr定义好
sbit OV=0xD0^2 (3)说明:0xD0就是PSW的地址值
因此这里用sfr P1_0=P1^0;就是定义用符号P1_0来表示P1.0引脚,如果你愿意也可以起P10一类的名字,只要下面程序中也随之更改就行了。

sfr 不是标准C 语言的关键字,而是Keil 为能直接访问80C51 中的SFR 而提供了一个新的关键词,其用法是:

sfrt 变量名=地址值。

例:sfr P1 = 0x90;

这样的一行即定义P1 与地址0x90 对应,P1 口的地址就是0x90.

SFR的定义在头文件reg51.h或reg52.h中。

相关文章:

  • cocos creator使用jenkins打包流程,打包webmobile
  • python连接Elasticsearch并完成增删改查
  • 2.4java运算需要注意的细节
  • JS-OCR-demo加载本地文件
  • springboot当中的类加载器
  • C20-breakcontinue
  • AOSP Android14 Launcher3——动画核心类QuickstepTransitionManager详解
  • OneNet云平台
  • 创建laravel 12项目
  • [GXYCTF2019]Ping Ping Ping
  • 驯龙日记:用Pandas驾驭数据的野性
  • 在AWS Glue中实现缓慢变化维度(SCD)的三种类型
  • 深圳市富力达:SAP一体化管理助力精密制造升级 | 工博科技SAP客户案例
  • 织梦dedecms网站如何修改上一篇下一篇的标题字数
  • 【Flutter】Flutter + Unity 插件结构与通信接口封装
  • 光场的相位与偏振
  • 详解 Unreal Engine(虚幻引擎)
  • 开源网络入侵检测与防御系统:Snort
  • Spark SQL开发实战:从IDEA环境搭建到UDF/UDAF自定义函数实现
  • Maven下载aspose依赖失败的解决方法
  • 伊朗外长: 美伊谈判进展良好,讨论了很多技术细节
  • 全球首台环形CT直线加速器在沪正式开机,系我国自主研发
  • “70后”通化市委书记孙简已任吉林省政府领导
  • 美联合健康集团高管枪杀案嫌疑人对谋杀指控不认罪
  • 中国人民对外友好协会代表团访问美国
  • 三亚一景区发生游客溺亡事件,官方通报:排除他杀