找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 1579|回复: 0
打印 上一主题 下一主题
收起左侧

水滴效果实现和讲解

[复制链接]
跳转到指定楼层
楼主
ID:357406 发表于 2018-6-23 20:01 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
简介
学习嵌入式第一个例子通常都是控制一个 LED 亮灭,然后是花样繁多的流水灯,但不管灯的花样如何变化,单个 LED 的亮度没有变化,只有亮、灭两个状态,本章我们实现如何控制 LED 的亮度。
1 什么是 PWM
脉冲宽度调制(Pulse Width Modulation,简称 PWM),是利用微处理器的数字输出来对模拟电路进行控制的一种技术。在本章的应用中可以认为 PWM 就是一种方波。比如图 1:
file:///C:\Users\杨磊\AppData\Local\Temp\ksohtml\wpsCB4D.tmp.jpg
1 方 波是周期为 10ms,占空比为 60%的 PWM。
占空比:高电平在一个周期之内所占的时间比率。
2 硬件设计
在例说 51 单片机的第三章,我们讲过如何控制开发板上 LED 的亮灭。首先译码器输出端
LEDS6 为低,T10 导通,给 8 LED 供电,然后通过缓冲器 8 个输出端 BD0~BD7 的控制 LED 的亮灭(低亮高灭)。
file:///C:\Users\杨磊\AppData\Local\Temp\ksohtml\wpsCB6D.tmp.png
2 LED 硬件连接


如果 BD 口输出高低不断变化,则 LED 会闪烁;如果这种高低电平变化非常快,由于人的视觉暂留现象,LED 就会出现不同的亮度。
3 软件设计
3.1 PWM 能否控制亮度
下面我们就用实践验证 PWM 是否能够控制 LED 的亮度,测试代码如下:
程序清单 L1: 验证 PWM 能否控制 LED 的亮度
#include <reg52.h>
#include "my_type.h"
#include "hw_config.h"
void main(void)
{
u8 i = 0;
//使能独立 LED 的供电,即 LEDS6 输出低电平LEDEN = 0;
ADDR0 = 0;
ADDR1 = 1;
ADDR2 = 1;
ADDR3 = 1;
//第一个 LED 亮P0 = 0xFE;
while(1)
{
for(i=0; i<250; i++) //第 22 行
{
if(i<10)
{
P0 &= 0xFD;        //第二个灯亮
}
else


{
P0 |= 0x02;        //第二个灯灭
}
} //第 32 行
}
}
L1(22-32) :这段代码实现 P0.1 输出占空比为 96%的方波,而 P0.0 恒为低。
P0.1 输出如图 3 所示(受纸张限制,图中高低电平长度比例和实际有偏差)。
file:///C:\Users\杨磊\AppData\Local\Temp\ksohtml\wpsCB6E.tmp.jpg
3
下载验证:从开发板上可以看到运行效果,D1 D2 亮。这里说明一点:当 P0 输出低电平时 LED 亮,所以,PWM 的占空比越小越亮)。
3.2 产生 8 个亮度级别
3.1 节的例子证实了我们的设想,PWM 可控制 LED 的亮度,下面我们设计几组占空比不同的 PWM,看看对 LED 亮度的控制效果。代码如下:
程序清单 L2:不同占空比对 LED 亮度的控制
#include <reg52.h>
#include "hw_config.h"
#include "my_type.h"
//亮度级别表
code u8 LightLevel[8]={0,1,2,4,8,16,32,64};
void main(void)
{
u8 i = 0; u8 j = 0; u8 k = 0;
u8 temp = 0;


//使能独立 LED 的供电,即 LEDS6 输出低电平LEDEN = 0;
ADDR0 = 0;
ADDR1 = 1;
ADDR2 = 1;
ADDR3 = 1;
//开始全灭P0 = 0xFF;
while(1)
{
//P0 端口输出 8 组占空比不同的 PWM for(i=0; i<64; i++) //第 29 行
{
for( j=0; j<8; j++)
{
if(LightLevel[ j] <= i)
{


}
else
{
}
}
temp |= (1<<j);
temp &= ~(1<<j);


P0 = temp;
}
} //第 45 行
}
L2(29-45).此段程序是让 P0 口输出 8 组占空比不同的 PWM,如图 4:


file:///C:\Users\杨磊\AppData\Local\Temp\ksohtml\wpsCB8F.tmp.jpg
4
下载验证:从开发板上可以看到运行效果,从 D1 到 D8 的亮度逐渐增大。
3.3 水滴下落效果
根据 PWM 可控制 LED 亮度的原理,我们用 8 个 LED 实现水滴下落的效果。第一步,水滴逐渐变大,用 D1 从暗变亮模拟;第二步,水滴下落,带有拖尾效果,LED 逐个亮,移动速度加快,且越靠前的 LED 亮度越大。
程序清单 L3 水滴流水灯
#include <reg52.h>
#include "hw_config.h"
#include "my_type.h"
//亮度级别表
code u8 LightLevel[8]={0,1,2,4,8,16,32,64};
//水滴时间,实现加速效果
code u8 LightTime[16]={16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1};
void main(void)
{
u8 i,j,k;


u8 temp,count; u8 state;
//使能独立 LED 的供电,即 LEDS6 输出低电平LEDEN = 0;
ADDR0 = 0;
ADDR1 = 1;
ADDR2 = 1;
ADDR3 = 1;
while(1)
{
//开始全灭P0 = 0xFF;
//---------------水滴逐渐变大(第一个 LED 亮度逐渐变大)--------- for(i=0; i<64; i++)
{
//一个亮度级别发送 64 个脉冲for( j=0; j<64; j++)
{
P0 = 0xFE;
//以 i 为亮度级别,随着 i 的增大,占空比增大for(k=0; k<64; k++)
{
if(k > i)
{
P0 = 0xFF;
}
}
}
}
//----------------------水滴降落过程---------------------
for(state=0; state<16; state++)
{


//每一状态维持 LightTime[state]个脉冲for(count=0; count<=LightTime[state]; count++)
{
//temp 记录 8 个 LED 的状态,0 代表亮,1 代表灭temp = 0x00;
//一个脉冲长度 j 从 0 到 63 for( j=0; j<64; j++)
{
//根据亮度表,依次确定 8 个 LED 当前状态,亮或灭for(k=0; k<8; k++)
{
//以 j 为亮度级别,每个 LED 亮度不一样
if(LightLevel[k] == j)
{
temp |= (1 << k);
}
}
if(state <= 7)
{


}
else
{
}
}
}
file:///C:\Users\杨磊\AppData\Local\Temp\ksohtml\wpsCB90.tmp.png}
}
}
P0 = ~((~temp) >> (7-state));
P0 = ~((~temp) << (state-7));


L2(31-46).实现水滴变大效果,这段代码的作用可用图形表达,如图 5:


file:///C:\Users\杨磊\AppData\Local\Temp\ksohtml\wpsCB91.tmp.jpg
5
控制 D1 由暗变亮,用了 64 个亮度级别,每个级别发送 64 个脉冲。
L2(49-81).实现水滴下落。代码就不逐行解释了,大家可根据注释自己分析,主要说一下实现的方法。
定义 LED 有 8 个亮度级别,若用开发板上的 8 个 LED 表示,如图 6:
file:///C:\Users\杨磊\AppData\Local\Temp\ksohtml\wpsCB92.tmp.png
6
图中的红色面积代表亮度程度。实现流水效果的方法就是:让所有的亮度依次经过在所有  LED, 如图 7:


file:///C:\Users\杨磊\AppData\Local\Temp\ksohtml\wpsCB93.tmp.png
7
状态的持续时间从 0-15 逐渐减小,以模拟水滴加速。下载验证:下载到开发板上,可以看到水滴下落效果。
3.4 定时器产生 PWM
前面 3 个例子中,我们用循环语句虽然能产生占空比不同的 PWM,但 PWM 的周期不好控制,对此,我们学习如何用定时器产生特定周期 PWM。关于 8051 定时器的使用方法,大家可以参考例说 51 单片机的 4 章和 5 章。
我们用定时器 0 产生 PWM,代码如下:
程序清单 L4 定时器 0 产生 PWM
#include <reg52.h>
#include "hw_config.h"
#include "my_type.h"


//亮度级别表
code u8 LightLevel[8]={1,2,4,8,16,28,50,64};
//函数声明
void timer0_init(void);
void main(void)
{
//使能独立 LED 的供电,即 LEDS6 输出低电平LEDEN = 0;
ADDR0 = 0;
ADDR1 = 1;
ADDR2 = 1;
ADDR3 = 1;
timer0_init();
while(1)
{
}
}
/********************************************************** 函数名称:timer0_init
功        能:初始化定时器 0
**********************************************************/ void timer0_init(void)
{
TMOD = 0x01;                //运行模式 1 TH0                = 0xFF;        //10us 中断TL0        = 0xFA;
EA        = 1;        //开启中断ET0        = 1;
TR0        = 1;        //启动定时器


}
/************************************************************ 函数名称:timer0_overflow
功        能:定时器 0 溢出中断
************************************************************/ void timer0_overflow(void) interrupt TIMER0_OVERFLOW
{
u8 i,temp = 0; static u8 count = 0;
count++; count %= 64;
for(i=0; i<8; i++)
{
if(LightLevel <= count)
{


}
else
{
}
}
temp |= (1<<i);
temp &= ~(1<<i);


P0 = temp;
TR0 = 0;
TH0 = 0xFF;        //重新赋值TL0 = 0xF7;
TR0 = 1;
}
L4(32).初始化定时器 0,没 10us 产生一次中断。
L4(55-65).控制输出 8 组不同占空比的 PWM。这段代码功能和程序清单 2 中的功        能一致。


下载验证:下载到开发板上,可以看到 D1 到 D8 亮度逐渐增大。
3.5 亮度不同的点阵
学习了用定时器产生 PWM,我们可以控制更多的 LED,比如 LED 点阵的亮度。下面的例子实现 LED 点阵每行的亮度都不同。
程序清单 5 亮度不同的点阵
#include <reg52.h>
#include "hw_config.h"
#include "my_type.h"
//亮度级别表
code u8 LightLevel[8]={1,2,4,8,16,32,50,64};
//函数声明
void timer0_init(void);
void main(void)
{
//使能控制点阵的译码器 LEDEN = 0;
ADDR3 = 0;
timer0_init();
while(1)
{}
}
/***************************************************************** 函数名称:timer0_init
功        能:初始化定时器 0
*****************************************************************/ void timer0_init(void)
{
TMOD = 0x01;                //运行模式 1 TH0        = 0xFF;        //中断时间 10us


TL0
= 0xF7
EA ET0 TR0
}
= 1;
= 1;
= 1;
//开启中断
//启动定时器
/***************************************************************** 函数名称:timer0_overflow
功        能:定时器 0 溢出中断
*****************************************************************/ void timer0_overflow(void) interrupt TIMER0_OVERFLOW
{
u8 i;
u8 p1_value = 0;
static u8 state = 0;        //点阵状态(扫描行数) static u8 count = 0;
TR0 = 0;
count++; if(count == 64)
{
state++; state %= 8;
count = 0;
}
if(count < LightLevel[state])
{


}
else
{
}
P0 = 0x00;
P0 = 0xFF;


p1_value = P1 & 0xf8; p1_value   |=   state; P1 = p1_value;
TH0 = 0xFF;        //重新赋值TL0 = 0xFA;
TR0 = 1;
}
L5(28).初始化定时器,每 10us 中断一次。
L5(51-57).每中断 64 次,点阵扫描移动到下一行,用 state 记录当前行数。
L5(59-66).扫描每一行输出的 PWM 都不一样,使用的方式和处理独立 LED 一致。L5(68-70).输出点阵对应的位码。
下载验证:下载到开发板上,可以看到运行效果,点阵第一行最暗,越往下越亮。
3.6 点阵模拟音乐频谱分析效果
在很多音乐播放软件上,都有频谱分析的图形,如图 8:
file:///C:\Users\杨磊\AppData\Local\Temp\ksohtml\wpsCB94.tmp.png
8
我们用也可以模拟相似的图形,代码如下:
程序清单 6:点阵模拟音乐频谱分析
#include <reg52.h>
#include "hw_config.h"
#include "my_type.h"
// 频 谱 波 形 表     code u8 Wave[16][8]=
{
{0xFF,0xFF,0xFF,0xFF,0xFE,0xBB,0xFE,0xAA},
{0xFF,0xFF,0xFF,0xFE,0xFB,0xAE,0xFA,0xAA},
{0xFF,0xFF,0xFF,0xFE,0xEB,0xBE,0xEA,0xAA},
{0xFF,0xFF,0xFE,0xFB,0xAF,0xFE,0xAA,0xAA},
{0xFF,0xFE,0xFB,0xBE,0xEA,0xBA,0xAA,0xAA},
{0xFF,0xFE,0xBB,0xEE,0xBA,0xBA,0xAA,0xAA},


{0xFE,0xBB,0xEE,0xBA,0xAA,0xAA,0xAA,0xAA},
{0xBA,0xEF,0xBE,0xAA,0xAA,0xAA,0xAA,0xAA},
{0xEE,0xBB,0xFE,0xAA,0xAA,0xAA,0xAA,0xAA},
{0xEE,0xBB,0xFE,0xEA,0xAA,0xAA,0xAA,0xAA},
{0xFE,0xEB,0xBE,0xFE,0xAA,0xAA,0xAA,0xAA},
{0xFF,0xEE,0xBB,0xFF,0xAE,0xAA,0xAA,0xAA},
{0xFF,0xFE,0xAF,0xFB,0xEE,0xAA,0xAA,0xAA},
{0xFF,0xFF,0xFE,0xBB,0xEF,0xBA,0xAA,0xAA},
{0xFF,0xFF,0xFF,0xFE,0xAB,0xFF,0xEE,0xAA},
{0xFF,0xFF,0xFF,0xFF,0xFE,0xEB,0xBE,0xAA}
};
//亮度级别表
code u8 LightLevel[8]={1,2,4,8,16,32,50,64};
//函数声明
void timer0_init(void);
void main(void)
{
//使能控制点阵的译码器 LEDEN = 0;
ADDR3 = 0;
timer0_init();
while(1)
{
}
}
/***************************************************************** 函数名称:timer0_init
功        能:初始化定时器 0
*****************************************************************/ void timer0_init(void)


{
TMOD = 0x01;                //运行模式 1 TH0                = 0xFF;        //10us 中断TL0        = 0xFA;
EA        = 1;        //开启中断ET0        = 1;
TR0        = 1;        //启动定时器
}
/***************************************************************** 函数名称:timer0_overflow
功        能:定时器 0 溢出中断
*****************************************************************/ void timer0_overflow(void) interrupt TIMER0_OVERFLOW
{
u8 i;
u8 p1_value = 0;
static u8 state = 0;        //点阵状态(扫描行数) static u8 count = 0;
static u8 wave_state = 0;        //波形状态static u16 wave_count = 0;
TR0 = 0;
//每中断 1000 次,改变波形状态wave_count++;   if(wave_count == 1000)
{
wave_count = 0; wave_state++; wave_state  %= 16;
}
//每中断 64 次,改变扫描的行


count++; if(count == 64)
{
state++; state %= 8;
count = 0;
}
if(count < LightLevel[state])
{


}
else
{
}
P0 = Wave[wave_state][state];
P0 = 0xFF;


//输出位码
p1_value = P1 & 0xf8; p1_value |= state;
P1 = p1_value;
//定时器重新赋值 TH0 = 0xFF;
TL0 = 0xF7; TR0 = 1;
}
L6(6).波形表,共 16 个状态,每个状态下有 8 个数据,即一个点阵界面。扫描点        阵时循环发送,实现动态效果。
L6(77-83).每中断 1000 次,更换一个状态。
L6(86-101).和 L5 的功能一致,只是点阵段码输出的数据变成了波形表。下载验证:下载程序到开发板,可以看到,点阵显示的频谱分析效果。

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享淘帖 顶 踩
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表