17-呼吸灯
1.呼吸灯
呼吸灯在我们的生活中很常见,在手机上多作为消息提醒指示灯而被广泛使用,其效果是小灯在一段时间内从完全熄灭的状态逐渐变到最亮,再在同样的时间段内逐渐达到完全熄灭的状态,并循环往复。这种效果就像“呼吸”一样,有张有弛,而且给人一种很舒服的感觉。其工作原理是利用 PWM 来控制小灯在相同时间段内的不同占空比,即在同样小时间段内,小灯亮的时间依次增加到最大后再依次减小,从而实现渐亮到渐灭的“呼吸”效果。
2.PWM介绍
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。
2.1 PWM频率/周期/占空比/脉冲时间
- PWM频率:是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);也就是说一秒钟PWM有多少个周期
- PWM周期:上升沿到上升沿(下降沿到下降沿的时间)
- 脉冲时间:高电平时间
- 占空比(duty cycle):一个周期内,高电平时间占整个周期的比例
2.2 占空比运算
- 50M时钟,时钟周期为20ns,占空比50%,高电平时间为20*50% = 10ns
- 周期是10ms,脉冲时间是8ms,占空比8/10 = 80%
2.3 占空比原理
- 以单片机为例,我们知道,单片机的IO口输出的是数字信号,IO口只能输出高电平和低电平。假设高电平为5V 低电平则为0V 那么我们要输出不同的模拟电压,就要用到PWM,通过改变IO口输出的方波的占空比从而获得使用数字信号模拟成的模拟电压信号
- 电压是以一种连接1或断开0的重复脉冲序列被夹到模拟负载上去的(例如LED灯,直流电机等),连接即是直流供电输出,断开即是直流供电断开。通过对连接和断开时间的控制,理论上来讲,可以输出任意不大于最大电压值(即0~5V之间任意大小)的模拟电压,比方说 占空比为50% 那就是高电平时间一半,低电平时间一半,在一定的频率下,就可以得到模拟的2.5V输出电压 那么75%的占空比 得到的电压就是3.75V
- PWM的调节作用来源于对“占周期”的宽度控制,“占周期”变宽,输出的能量就会提高,通过阻容变换电路所得到的平均电压值也会上升,“占周期”变窄,输出的电压信号的电压平均值就会降低,通过阻容变换电路所得到的平均电压值也会下降,也就是,在一定的频率下,通过不同的占空比 即可得到不同的输出模拟电压,PWM就是通过这种原理实现D/A转换的。
- PWM就是在合适的信号频率下,通过一个周期里改变占空比的方式来改变输出的有效电压
2.4 PWM控制呼吸灯
- 人眼对于80Hz以上的刷新频率完全没有闪烁感,刷新频率太低看起来就回闪烁。对于LED灯,当其频率大于50Hz的时候,基本看不到闪烁(常亮)’
* 频率很高时,看不到闪烁,占空比越大,LED越亮; - 频率很低时,可看到闪烁,占空比越大,LED越亮。
- 所以,在频率一定下,可以用不同占空比改变LED灯的亮度。 使其达到一个呼吸灯的效果
- 一个周期内高电平时间越长,灯越暗
3.FPGA设计
- 使用LED灯实现呼吸的效果,LED灯电平点亮
3.1 模块框图和波形图
- 整个呼吸灯“呼吸”的效果分为两部分,一个过程是从灭到亮,另一个过程是从亮到灭。为了把复杂的问题简单化,我们把 led 整个“呼吸”的动作进行分解,先分析从灭到亮的过程,而从亮到灭则是与之相反的一个过程。
- 假设灯从熄灭到完全点亮的时间为1s,通过控制 PWM 的占空比来实现 led 灯
越亮的效果。我们知道同一时间段内,如果供给 led 灯一个脉冲信号的低电平持续的时间越长(高电平持续的时间越短)led 灯就越亮,我们就是通过调整 PWM 实现高低电平的占空来调控 led 灯的亮度,我们取 n 个相同的时间段,然后让低电平的持续时间按照相等的时间间隔逐渐增多,这样子我们看上去的 led 灯就会越来越亮了。 - 将亮灯的1s分为等长的时间段,每一段时间内低电平的时间不断增加,实现灯的亮度依次增加,实现呼吸的效果,将1s分为1000份,每一份是1ms,在每一份的1ms内低电平的时间逐渐增加,每次增加1us
第1个 1ms 1us
第2个 1ms 2us
第3个 3ms 3us
.........
- 需要三个计数器cnt_1s、cnt_1ms、cnt_1us
- cnt_1us - 时钟周期是20ns,1us需要50个时钟周期,最大计数到49
- cnt_1us 0-49 0-49 0-49 0-49 0-49 0-49 ......
- cnt_1ms - 1ms = 1000us = 1000个0-49计数时间,cnt_1us经过一个0-49的计数周期,cnt_1ms自加1
- cnt_1ms需要计数1000个0-49,计数最大值是1000-1 = 999
- cnt_1s - 1s = 1000ms = 1000个cnt_1ms计数周期(0-999),cnt_1ms每经历一个0-999计数,cnt_1s自加1
如何在1ms内控制低电平时间不断增加?
cnt_1s 1 2 3
cnt_1ms 0 1 .... 999 0 1 .... 999 0 1 2 ... 999
第1个周期cnt_1ms有1个周期的数值小于cnt_1s
第2个周期cnt_1ms有2个周期的数值小于cnt_1s
第3个周期cnt_1ms有3个周期的数值小于cnt_1s
.............
第999个周期cnt_1ms有999个周期的数值小于cnt_1s
- 发现 led_out 为低电平时间时 cnt_1s 计数器的计数值总是小于 cnt_1ms 计数器的计数值,所以得出控制低电平时间的关键条件:led_out,当cnt_1ms<=cnt_1s,保持低电平,其他为高电平
- 熄灭信号就是led_out进行取反
要使 cnt_1s 计数器的计数值总是大于 cnt_1ms 计数器的计数值才能实现每一个 1ms 的时间小段的低电平持续时间都不一样且渐渐减小的效果。为了区分从灭到亮和从亮到灭两个过程我们使用一个标志信号,这个标志信号不是脉冲,而是一个电平,更准确的来讲应该叫使能信号,这里我们取名为 cnt_1s_en
3.2 RTL
module breath_led
#(
parameter CNT_1US_MAX = 6'd49 ,
parameter CNT_1MS_MAX = 10'd999 ,
parameter CNT_1S_MAX = 10'd999
)
(
input wire sys_clk , //系统时钟 50MHz
input wire sys_rst_n , //全局复位
output reg led_out //输出信号,控制 led 灯
);
//reg define
reg [5:0] cnt_1us ;
reg [9:0] cnt_1ms ;
reg [9:0] cnt_1s ;
reg cnt_1s_en ;
//cnt_1us:1us 计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1us <= 6'b0;
else if(cnt_1us == CNT_1US_MAX)
cnt_1us <= 6'b0;
else
cnt_1us <= cnt_1us + 1'b1;
//cnt_1ms:1ms 计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1ms <= 10'b0;
else if(cnt_1ms == CNT_1MS_MAX && cnt_1us == CNT_1US_MAX)
cnt_1ms <= 10'b0;
else if(cnt_1us == CNT_1US_MAX)
cnt_1ms <= cnt_1ms + 1'b1;
//cnt_1s:1s 计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1s <= 10'b0;
else if(cnt_1s == CNT_1S_MAX && cnt_1ms == CNT_1MS_MAX
&& cnt_1us == CNT_1US_MAX)
cnt_1s <= 10'b0;
else if(cnt_1ms == CNT_1MS_MAX && cnt_1us == CNT_1US_MAX)
cnt_1s <= cnt_1s + 1'b1;
//cnt_1s_en:1s 计数器标志信号
// 1s计数器计数到最大值的时候取反,低电平表示从完全点亮到完全熄灭,高电平表示从完全熄灭到完全点亮
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1s_en <= 1'b0;
else if(cnt_1s == CNT_1S_MAX && cnt_1ms == CNT_1MS_MAX
&& cnt_1us == CNT_1US_MAX)
cnt_1s_en <= ~cnt_1s_en;
//led_out:输出信号连接到外部的 led 灯
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
led_out <= 1'b0;
else if((cnt_1s_en == 1'b1 && cnt_1ms < cnt_1s) ||
(cnt_1s_en == 1'b0 && cnt_1ms > cnt_1s))
led_out <= 1'b0;
else
led_out <= 1'b1;
endmodule
3.3 Testbench
`timescale 1ns/1ns
module tb_breath_led();
//wire define
wire led_out ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
//********************************************************************/
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、全局复位
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
//sys_clk:模拟系统时钟,每 10ns 电平翻转一次,周期为 20ns,频率为 50MHz
always #10 sys_clk = ~sys_clk;
breath_led
#(
.CNT_1US_MAX(6'd4 ),
.CNT_1MS_MAX(10'd9 ),
.CNT_1S_MAX (10'd9 )
)
breath_led_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.led_out (led_out ) //output led_out
);
endmodule