单片机-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 |
这里的0x53是十六进制的表示方法,为什么需要十六进制,什么是十进制,什么是二进制呢?
进制的作用:更好的表示数值。
十进制:十进制是人类最自然的数字系统,因为我们有十个手指,所以我们使用十进制进行计数,使用 0-9 来表示数字,逢10进1。
二进制:二进制的出现是因为数字电路中,数字信号只有高/低或开/关两种状态,所以只需要使用两个数字 0 和 1 来表示数字,优点是非常直观,缺点是书写起来冗长,过长的二进制容易记录错误,不便阅读和书写。
十六进制:为了可以更紧凑地表示二进制数据,就有了十六进制,使用十六个数字 0-9 和字母 A-F 来表示数字,在代码中,使用0x前缀来表示十六进制。
Java |
所以,我们可以设置P2.0-P2.7输出都为低电平,代码这样写。
Java |
那如果我们只想控制某个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 |
所以,我们可以这样来定义P2.7这个引脚的输出高电平。
C |
四、闪烁控制
前面,我们通过设置某个引脚输出低电平,点亮LED,也就是我们设置输出高电平时,会关闭LED,那我们需要实现LED闪烁的功能时,如何实现呢?
答案是:先点亮LED,延时一段时间,关闭LED,再延时一段时间,如此反复,就可以得到一个闪烁的LED功能。
在单片机中如何实现延时功能呢?有以下几种生成延时函数的生成方法
1、让芯片循环执行一些无意义的代码
我们知道,芯片执行每一句代码都需要时间,假设执行一句代码的时间为1ms,那我们点亮LED,然后执行500句(500ms)无意义的代码,再关闭LED,就可以得到闪烁的效果啦。
Java |
具体如何计算呢:使用编译器编译为汇编代码,结合芯片的指令周期计算时间,可以参考https://godbolt.org/
Java |
2、使用STC-ISP工具生成
Java |
最终效果
Java |
五、流水灯
如果需要实现多个灯的显示和熄灭呢?
其实就是流水灯的效果
C |
代码优化
Java |
继续优化:
Java |
六、扩展
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 |
sbit的用法有三种:
第一种方法:sbit 位变量名=地址值
第二种方法:sbit 位变量名=SFR名称^变量位地址值
第三种方法:sbit 位变量名=SFR地址值^变量位地址值
Java |
sfr 不是标准C 语言的关键字,而是Keil 为能直接访问80C51 中的SFR 而提供了一个新的关键词,其用法是:
sfrt 变量名=地址值。
例:sfr P1 = 0x90;
这样的一行即定义P1 与地址0x90 对应,P1 口的地址就是0x90.
SFR的定义在头文件reg51.h或reg52.h中。