基于FPGA的呼吸灯设计
1. 项目简介
呼吸灯,指的是一个LED从暗到亮,从亮到暗逐渐变化,如此循环反复,就像人的呼吸一样有节奏。它采用PWM的方式,在固定的频率下,通过调整占空比的方式来控制LED灯亮度的变化。
PWM(Pulse Width Modulation),即脉冲宽度调制,是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码,被广泛应用于测量、通信、功率控制等领域。
2. 设计要求
实现呼吸灯的效果,即由灭渐亮,然后再由亮渐灭,要求一个完整的呼吸过程包含:呼气(2秒)和吸气(2秒),周期为4秒。系统框图如下图所示,其中,clk为系统时钟、50Mhz,rst_n为系统复位,低电平有效,led[3:0]为输出led灯,高电平点亮。
3. 设计原理
LED的亮度与流过的电流成正比。不同的脉冲占空比的方波输出后加在LED上,LED灯就会显示不同的亮度,通过不断地调节方波的占空比,从而实现LED灯亮度的调节。在一定的频率下,如果占空比是0,则LED不亮;如果占空比是100%,则LED最亮;如果占空比刚好是50%,则LED亮度适中。如果我们让占空比从0%-100%变化,再从100%-0%不断变化,就可以实现LED一呼一吸的效果。其设计思路如下:
(1)一个完整的呼吸过程包含:呼气(2秒)和吸气(2秒),周期为4秒。
(2)考虑呼气的过程,就是让led灯要有亮灭的变化,从暗逐渐到亮。这里必须要将2秒的时间进行拆分,为什么?
(3)为什么要将2秒进行拆分?因为如果不拆分,只有一个固定不变的占空比,无论占空比是多少,led始终是固定的,即led的亮度也是固定的。
(4)如何拆分?理论上:我们可以无限的细分,拆分的份数越多,led的变化就越流畅;实际上:由于人的眼睛有视觉暂留效应,对应光的变化,人眼的分辨率是30-40ms,所以我们无限细分是无意义的。
参数取值:为了led的亮度变化比较流畅,我们取2ms的间隔将2s进行拆分,也就是每隔20ms我们给led灯一个新的亮度,
这样我们人眼就能分辨出这个亮度的变化,则 cnt * T = 2000ms,T=2ms,cnt=1000,即拆分1000份。
(5)20ms周期连续变化示意图
由步骤4得知,我们将2秒拆分为1000份的2ms,也就是2ms周期的波形在时序图上看是会重复1000次;为了体现led灯亮度,每一份20ms波形的占空比都不能相同,且必须是连续增加/减小的。如下图示意
(6)由步骤5得知,波形重复1000次,也就是2ms的时间内,我们一共有1000个状态,每一个状态就是一个占空比t * 1000 = 20ms,则 t = 2us,由公式计算得知,2us的占空比为2/2*1000 %100 = 1‰
(7)从上面的描述中,我们可以总结出,要做出呼吸灯,我们需要利用三个计数器分别计数(类似于数字钟中秒、分钟、小时)。第一个计数器cnt_2us用来对2us的计数,第二个计数器cnt_2ms实现对2ms计数,第三个计数器实现对2s计数。三个计数器中,利用后面两个计数器cnt_2ms和cnt_2s的大小进行比较,来改变一个时钟周期内的占空比。
4. 设计实现
module led_breath( input wire clk, //系统时钟,50Mhz input wire rst_n, //系统复位,低电平有效 output wire [3:0] led //led灯,高电平点亮 ); parameter T_2us = 100; parameter T_2ms = 100_000; parameter T_2s = 100_000_000; reg [6:0] cnt_2us; reg [9:0] cnt_2ms; reg [9:0] cnt_2s; reg valid; wire wave; always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) cnt_2us <= 7'd0; else if(cnt_2us < T_2us - 1'b1) cnt_2us <= cnt_2us + 1'b1; else cnt_2us <= 7'd0; end always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) cnt_2ms <= 10'd0; else if(cnt_2us == T_2us - 1'b1) if(cnt_2ms < T_2ms/T_2us - 1'b1) cnt_2ms <= cnt_2ms + 1'b1; else cnt_2ms <= 10'd0; else cnt_2ms <= cnt_2ms; end always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) cnt_2s <= 10'd0; else if((cnt_2us == T_2us - 1'b1)&&(cnt_2ms == T_2ms/T_2us - 1'b1)) if(cnt_2s < T_2s/T_2ms - 1'b1) cnt_2s <= cnt_2s + 1'b1; else cnt_2s <= 10'd0; else cnt_2s <= cnt_2s; end assign wave = (cnt_2s > cnt_2ms) ? 1'b1 : 1'b0; always@(posedge clk or negedge rst_n)begin if(rst_n == 1'b0) valid <= 1'b0; else if((cnt_2us == T_2us - 1'b1)&&(cnt_2ms == T_2ms/T_2us - 1'b1)&&(cnt_2s == T_2s/T_2ms - 1'b1)) valid <= ~valid; else valid <= valid; end assign led[0] = (valid == 1'b0) ? wave : ~wave; assign led[1] = (valid == 1'b0) ? wave : ~wave; assign led[2] = (valid == 1'b0) ? wave : ~wave; assign led[3] = (valid == 1'b0) ? wave : ~wave; endmodule
5. 仿真测试
`timescale 1ns/1ps module led_breath_tb(); reg clk; reg rst_n; wire [3:0] led; defparam led_breath_inst.T_2us = 10; defparam led_breath_inst.T_2ms = 100; defparam led_breath_inst.T_2s = 1000; led_breath led_breath_inst( .clk (clk), .rst_n (rst_n), .led (led) ); initial begin repeat(1000*10)begin clk = 1'b0; #10; clk = 1'b1; #10; end end initial begin rst_n = 1'b0; #21; rst_n = 1'b1; end endmodule
注:PWM波一般可以使用单片机或者专用PWM波生成电路产生(一般简单的单片机就可以,然后选择PWM控制器路数比较多的即可),也有很多用CPLD或者FPGA来实现PWM波的,但是实际应用中,由于FPGA成本和功耗上对单片机不具备优势,所以实际产品中很少会用FPGA或者CPLD芯片。