目录
- 背景
- 硬件驱动器
- 软件驱动器
-
- 像航障灯一样闪烁
- 想怎么闪就怎么闪
本文记录了一段旅程–让一颗LED灯光闪烁。
背景
我拿了一块普中51单片机的开发板,上面碰巧有很多led灯,我想让其中一个以我想象的方式闪烁。
硬件驱动器
虽然开发板上有硬件驱动,但我不需要走这条路,但对于旅行来说,也许沿途的一簇花草和一片叶子只是下一次旅行的起点。
先来看看开发板上的开发板。LED驱动器罢LED以共阳极的形式,LED的阴极接了470R单片机的另一端被引入IO口。 我不想给自己添麻烦。我决定只观察D12极管,我需要画一个更简单的电路来分析。 上手前先查攻略,看大家怎么说。
红色发光二极管的正导压降一般为1.8-2.2V,工作电流一般为5-20mA,如果用于指示灯,一般为10mA就比较亮了。
回头看看电路,当VCC=5V,b点的电位为0V当主干线上的电流和a点的电位如下。实际分析的电路设计参数与在线描述不同,但似乎感觉应该可以。实际效果是好是坏,实际上大锤80小锤100验证。
发光二极管压降(自变量) | Va(因变量) | 主干线电流(因变量) |
---|---|---|
1.8V | 3.2V | 6.8mA |
2.2V | 2.8V | 5.9mA |
对于这种感觉可以做到的二极管驱动器,不讨论单片机的噪声容量,不讨论可以做到led在明亮的电压范围内,我们抽象控制电压的数字。当二极管亮起时,我们将在b点发送逻辑0,当二极管熄灭时,我们将在b点发送逻辑1。
软件驱动器
控制LED灯的单片机端口是P2.0端口LED灯就往P2.0端口寄存器写0,要熄灭就写1。这是一个非常简单的操作,但也值得一摸。
#include "reg52.h" sbit LED1=P2^0; void main(){
LED1=0; //点亮 //LED1=1; //熄灭 while(1){
} }
灯亮了,现在可以看硬件驱动部分的分析和实际情况的对比。用万用表简单测量一下VCC,a点和b点的电位,下图为b点的电位,图太多会影响观感,用表格看好。
VCC | Ea | Eb | Vled=VCC-Ea | I |
---|---|---|---|---|
5.15V | 3.258V | 0.302V | 1.892V | 2.97mA |
实际开发板上使用的限流电阻标称值为1k,测量下来996R,所以主干线的电流是2.97mA,但是看起来LED灯的亮度感觉很好,完全依靠经验和杜娘是不可取的,不同的制造商,不同型号的设备不同,看更多的手册是避开坑的秘密。
像航障灯一样闪烁
我想让它像高层建筑屋顶的航空障碍灯一样闪烁。粗略检查一下,我觉得40闪/分更好看。40闪/分意味着每1分.5S如果发光管需要切换一次,那么LED控制信号应如下,1和0每次保持时间为1.5s。 然后开始设计程序,目标是单片机P2.上述无限循环周期逻辑在0口上产生。有一个开发板demo程序,忍不住ctrl c,ctrl v。但是demo延迟函数数void delay_10us(u16 ten_us)1不能直接实现.5s延时,因为ten_us最大值只能是65535,假设最大值只能为65535us延时,当ten_us=65535时,延迟时间为655.35ms,小于需要的1500ms,我们可以简单粗暴地堆叠3个延迟500ms延迟单元来处理这个问题。
while循环语句可以使一个过程无限循环,所以我们只需要实现一个周期:点亮并保持–>延时1.5s–>熄灭保持–>延时1.5s,这是一个简单的逻辑。
#include "reg52.h" typedef unsigned int uint16_t; sbit LED1=P2^0; void delay_10us(uint16_t ten_us){
while(ten_us--); } void main(){
while(1){
LED1=0; //点亮 delay_10us(51176); //11.0592MHz,延时500ms delay_10us(51176); delay_10us(51176); LED1=1; //熄灭 delay_10us(51176); delay_10us(51176); delay_10us(51176); } }
出于各种原因,通过理论推算延时函数所需要传入的值有些许复杂,耍个花招,通过实际调试来确定数值。修改调试环境中晶振值和开发板上的值相同,这里是11.0592MHz,然后启动软件模拟调试,运行到光标所在行,感觉可以就可以。
想怎么闪就怎么闪
冲一杯茶,歇一歇,收拾收拾心情,我不知道是从哪架飞机翅膀上看见的,它那个灯隔段时间闪一下,也有隔段时间闪两下的,我觉得隔段时间闪两下的灯比较漂亮,就这么决定了。 为了让时间的计算更加简单准确,配置过程更快捷,我决定用定时器的办法来抵达这个目标,因为里面有两种不同的时间,也或许有更多不同长度的时间片段。
我们构造一个趁手的工具–虚拟定时器virtualTimer,它由一个递减计数器,重载时间和重载标志组成,如果定时器的重载标志为1,那么它就是一个周期定时器,反之它是一个单次定时器。 虚拟定时器由一个硬件定时器驱动,硬件定时每滴答作响一次,虚拟定时器的递减计数器就减一个数,所以我们需要精心地设计硬件定时器的滴答时间和虚拟定时器的重载值来配置实际时间。 在虚拟定时器的递减计数器倒计时的过程中,我们通过指定在不同的计数区间led的不同状态来实现亮灭的随意操控,也就是想怎么闪就怎么闪。 最后付上代码
#include "reg52.h"
#define TIMER0_MIDDLE_VALUE 9174 //11.0592MHz晶振,10ms溢出一次,定时器数9174个数
typedef unsigned int uint16_t;
typedef struct virtualTimer {
//虚拟定时器,定时时间与使用的定时器定时间隔有关系
unsigned short counter;
unsigned short reloadTime;
unsigned short reloadFlag;
}virtualTimer_t;
sbit LED1=P2^0;
virtualTimer_t LEDStateIndicatorTimer = {
0,450,1};
void main(){
TMOD = 0x01; //T0方式1->16位不自动重装定时器
TMOD &= ~(1<<2); //T0定时器模式
TMOD &= ~(1<<3); //T0启停仅受TCON的TR0控制
TH0 = (65535 - TIMER0_MIDDLE_VALUE) / 256;
TL0 = (65535 - TIMER0_MIDDLE_VALUE) % 256;
ET0 = 1; //T0中断使能
EA = 1; //总中断使能
TR0 = 1; //T0使能
while(1){
if(LEDStateIndicatorTimer.counter > 50 ||
(LEDStateIndicatorTimer.counter < 35 && LEDStateIndicatorTimer.counter >= 15))
LED1=1; //熄灭
else
LED1=0; //点亮
}
}
void T0_interrupt(void) interrupt 1 {
TH0 = (65535 - TIMER0_MIDDLE_VALUE) / 256; //重装初值
TL0 = (65535 - TIMER0_MIDDLE_VALUE) % 256;
if(LEDStateIndicatorTimer.counter != 0)
--LEDStateIndicatorTimer.counter;
if(!LEDStateIndicatorTimer.counter && LEDStateIndicatorTimer.reloadFlag)
LEDStateIndicatorTimer.counter = LEDStateIndicatorTimer.reloadTime;
}