基于FPGA的呼吸灯设计

时间:2024-03-07 22:50:29

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芯片。

主要来源于:陪您一起学习FPGA-郝旭帅团队_哔哩哔哩_bilibili