【手撕代码】—Verilog实现呼吸灯

【手撕代码】—Verilog实现呼吸灯

​ 小张最近痴迷于刷题,恰好看到了这个呼吸灯手撕题,让我回想起本科学EDA的课堂作业,今天趁机来复习一下

1. 题目

​ 实现1s内呼吸灯从暗到亮,下一秒1s呼吸灯从亮到暗,并不断循环此过程。

2. 原理剖析

​ 不整废话,直接干货。呼吸灯的本质是PWM占空比的改变导致led灯两端的电压的不断改变。从暗到亮的过程是占空比从0%到100%的过程。从亮到暗即对应从100%到0%。

​ 那么不懂的小伙伴可能会问,什么是PWM?PWM是脉宽调制技术(Pulse-width modulation)。通常情况下,你看到的灯是处于全亮或全灭的状态,也就对应着0和1。而如果我们定义一个采样周期内, 一半的周期处于全亮状态(状态1),一半的处于全灭状态(状态0)。其实这就是50%的PWM脉冲。那么想想看,你会看到什么呢?

其实这取决于PWM信号的频率,频率越高,对人眼来说停滞感越强。比如频率是0.1HZ,那么你会在前5s内看到灯亮,后5s内看到灯暗。对于人眼来说这个停滞时间就是5s。而如果频率是5HZ,那停滞时间就是0.1s,你会看到灯在高频率闪烁。而如果频率是50HZ,(没错,我们平常的日关灯也是50HZ,只不过这个频率不是PWM频率而是交流信号频率,但原理相同),那停滞时间就是0.01s,你会看到灯处于常亮状态,但亮度是最大亮度的一半。产生这样的原因是因为人眼的刷新率是24帧,那么人眼的图像滞留时间约为0.04s。因此只要PWM频率大于24帧,人眼看到的都是常亮状态。因此在设计呼吸灯的时候,本质上是存在一个极限周期的,要保证停滞时间大于人演的图像滞留时间,才能实现呼吸灯的效果。 当然,呼吸灯的效果也取决于呼吸时间周期的设定。但与此题无关,扯远了。。

​ 还记得刚刚我们说的吗?50%的PWM脉冲,如果PWM频率是50HZ,我们人眼会看到灯常亮,但亮度是最大亮度的一半。核心原因是在这个时钟周期内灯两端的电压 U L U_L UL= 1 2 \frac{1}{2} 21 U d c Udc Udc。如果假如占空比是 α \alpha α,那么灯两端的电压为 U L U_L UL= α ∗ \alpha* α U d c Udc Udc。如果 α \alpha α从0增加到1,就会实现呼吸灯的效果。

3. Verilog代码实现

代码相对简单,在这里就不赘述实现方法了。

代码剖解:

1s内实现占空比从0-100%,1s分为1000份,占空比精度 0.1%,而1ms分1000份代表占空比,每1ms累加0.1%。

 assign state0 = (number_1us < number_1ms)?1'b1:1'b0;//占空比

number_1us 代表运行到第几个us。number_1ms代表运行到第几个ms。

这段代码number_1ms会从0ms增加到999ms,而0ms-1ms时1000个us都会输出1,即占空比100%。而 999ms-0ms中 1000个us都会输出0。因此这也就实现了每增加1ms,占空比降低0.1%

Verilog源码:
//呼吸灯
//1s从暗到亮,下一个1s从亮到暗
//1s内实现占空比从0-100%,1s分为1000份,占空比精度 0.1%
//1ms分1000份代表占空比,每1ms累加0.1%
// date :2022/05/31
module Breath_led #(
    parameter [25:0] cnt_1s  = 26'd50_000_000 - 1'd1,
    parameter [15:0] cnt_1ms = 16'd50_000     - 1'd1,
    parameter [ 5:0] cnt_1us = 6'd50         - 1'd1  
)(
    input 	wire 	 clk, 
    input	wire 	 rstn,  
    output	wire     led  
);
    reg              flag_1s,flag_1ms,flag_1us; //计数标志位,每到1s、us ms都会有一个高脉冲
    reg   [25:0]     count_1s;                      
    reg   [15:0]     count_1ms;  
    reg   [ 5:0]     count_1us;  
    reg              led_flag;                  //1s状态切换标志位
    reg   [ 9:0]     number_1us,number_1ms;     //表示系统运行到第几个us,方便进行占空比比较
    wire             state0;                    //亮到暗 
    wire             state1;                    //暗到亮 
    assign state0 = (number_1us < number_1ms)?1'b1:1'b0;//占空比  累加
    assign state1 = (number_1us < number_1ms)?1'b0:1'b1;//占空比  累减
    assign led    =  (led_flag)?state0:state1;          //判断状态
//1s计数器
always @(posedge clk or negedge rstn) begin
    if(!rstn)begin
        flag_1s  <=   1'b0;
        count_1s <=  26'd0;
    end else if (count_1s == cnt_1s ) begin
        count_1s <= 26'd0;
        flag_1s  <= ~flag_1s;
    end else begin
        count_1s <= count_1s + 1'd1;
        flag_1s  <= 1'b0;
    end
end
//1ms计数器
always @(posedge clk or negedge rstn) begin
    if(!rstn)begin
        flag_1ms  <=   1'b0;
        count_1ms <=  16'd0;
    end else if (count_1ms == cnt_1ms ) begin
        count_1ms <= 16'd0;
        flag_1ms  <= ~flag_1ms;
    end else begin
        count_1ms <= count_1ms + 1'd1;
        flag_1ms  <= 1'b0;
    end
end
//1us计数器
always @(posedge clk or negedge rstn) begin
    if(!rstn)begin
        flag_1us  <=   1'b0;
        count_1us <=   6'd0;
    end else if (count_1us == cnt_1us ) begin
        count_1us <=   6'd0;
        flag_1us  <= ~flag_1us;
    end else begin
        count_1us <= count_1us + 1'd1;
        flag_1us  <= 1'b0;
    end
end
//定位当前系统运行到第几个us
always @(posedge clk or negedge rstn) begin
    if(!rstn)
        number_1us <= 10'd0;
    else if(number_1us == 10'd999) 
        number_1us <= 10'd0;
    else begin
        if(flag_1us)
             number_1us <= number_1us + 1'b1;
        else 
             number_1us <= number_1us;
    end
end
//定位当前系统运行到第几个ms
always @(posedge clk or negedge rstn) begin
    if(!rstn)
        number_1ms <= 10'd0;
    else if(number_1ms == 10'd999) 
        number_1ms <= 10'd0;
    else begin
        if(flag_1ms)
             number_1ms <= number_1ms + 1'b1;
        else 
             number_1ms <= number_1ms;
    end
end
// 每1s切换呼吸灯状态 
always @(posedge clk or negedge rstn) begin
    if(!rstn) 
        led_flag <= 1'b1;
    else begin
        if(flag_1s)
            led_flag <= ~led_flag;
        else
            led_flag <=  led_flag;
    end
end
endmodule 

4. TestBench验证

`timescale  1ns / 1ps

module tb_Breath_led;

// Breath_led Parameters
parameter PERIOD   = 20                   ; //50MHz
parameter cnt_1s   = 26'd50_000_000 - 1'd1;
parameter cnt_1ms  = 16'd50_000     - 1'd1;
parameter cnt_1us  = 6'd50         - 1'd1 ;

// Breath_led Inputs
reg   clk                                  = 0 ;
reg   rstn                                 = 0 ;

// Breath_led Outputs
wire  led                                  ;


initial
begin
    forever #(PERIOD/2)  clk=~clk;
end

initial
begin
    rstn  =   0;
    #(PERIOD*2) ;
    rstn  =   1;
end

Breath_led #(
    .cnt_1s  ( cnt_1s  ),
    .cnt_1ms ( cnt_1ms ),
    .cnt_1us ( cnt_1us ))
 u_Breath_led (
    .clk                     ( clk    ),
    .rstn                    ( rstn   ),

    .led                     ( led    )
);
/*
initial
begin

    $finish;
end
*/
endmodule

仿真结果: 从暗到亮过程

仿真结果: 从暗到亮过程

<i

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值