FPGA拾忆_(11):按键控制呼吸灯_按键消抖_递进计时条件错误

1.硬件特征:

        呼吸灯,渐亮渐暗的灯,实现原理为PWM(脉冲宽度调制)

        PWM:参考资料,其中说明了PWM在呼吸灯,电机转速和舵机控制中发挥的作用。

2.原理图:

3.端口以及模块结构:

我再此基础上额外加了按键控制功能,具体实现功能为:小灯以呼吸灯的方式亮灭,按一次按键之后转换为500ms亮,500ms灭 ,再按一次重新变为呼吸灯方式亮灭。

 这次我重写了key_filter逻辑,尽可能减少了中间变量,除了必要的亚稳态处理的buffer信号,cnt信号和edge_flag信号,没有其他的中间变量了

呼吸灯模块我的代码有重大问题,后面细说:

4.代码技巧:

正点原子代码:

module breath_led(
    input       sys_clk ,      //系统时钟 50MHz
    input       sys_rst_n ,    //系统复位,低电平有效
    
    output reg  led            //LED灯
);

//parameter define
parameter CNT_2US_MAX = 7'd100;    
parameter CNT_2MS_MAX = 10'd1000; 
parameter CNT_2S_MAX  = 10'd1000; 

//reg define
reg [6:0] cnt_2us; 
reg [9:0] cnt_2ms;   
reg [9:0] cnt_2s;     
reg       inc_dec_flag; //亮度递增/递减 0:递增 1:递减

//*****************************************************
//**                  main code
//*****************************************************

//cnt_2us:计数2us
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        cnt_2us <= 7'b0;
    else if(cnt_2us == (CNT_2US_MAX - 7'b1 ))
        cnt_2us <= 7'b0;
    else
        cnt_2us <= cnt_2us + 7'b1;
end

//cnt_2ms:计数2ms
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        cnt_2ms <= 10'b0;
    else if(cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
        cnt_2ms <= 10'b0;
    else if(cnt_2us == CNT_2US_MAX - 7'b1)
        cnt_2ms <= cnt_2ms + 10'b1;
    else
        cnt_2ms <= cnt_2ms;
end

//cnt_2s:计数2s
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        cnt_2s <= 10'b0;
    else if(cnt_2s == (CNT_2S_MAX - 10'b1) && cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
        cnt_2s <= 10'b0;
    else if(cnt_2ms == (CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
        cnt_2s <= cnt_2s + 10'b1;
    else
        cnt_2s <= cnt_2s;         
end

//inc_dec_flag为低电平,led灯由暗变亮,inc_dec_flag为高电平,led灯由亮变暗
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        inc_dec_flag <= 1'b0;
    else if(cnt_2s == (CNT_2S_MAX - 10'b1) && cnt_2ms ==( CNT_2MS_MAX - 10'b1) && cnt_2us == (CNT_2US_MAX - 7'b1))
        inc_dec_flag <= ~inc_dec_flag;
    else
        inc_dec_flag <= inc_dec_flag;
end

//led:输出信号连接到外部的led灯
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        led <= 1'b0;
    else if((inc_dec_flag == 1'b1 && cnt_2ms >= cnt_2s) || (inc_dec_flag == 1'b0 && cnt_2ms <= cnt_2s))
        led <= 1'b1;
    else
        led <= 1'b0;
end

endmodule

我的代码:

module top_breath_led(
            input   sys_clk,
            input   sys_rst_n,
            input   key,
            output  led
                );
wire    key_filter; 
                
parameter CNT_MAX  = 20'd1000_000; //计数20ns*1000_000 =20ms

parameter CNT_MXA_2US = 100; //计数时间2us
                        
parameter CNT_MXA_2MS = 1000; //计数时间2ms             

     
 key_filter     #(
		.CNT_MAX(CNT_MAX)
			)  //lihua canshu
		key_filter_U1(
                .sys_clk(sys_clk),
                .sys_rst_n(sys_rst_n),
                .key(key),
                .key_filter(key_filter)
                    );                 
 


 
breath_led      
		#(
		.CNT_MXA_2US(CNT_MXA_2US),
		.CNT_MXA_2MS(CNT_MXA_2MS)
			)
		breath_led_U1(
                .sys_clk(sys_clk),
                .sys_rst_n(sys_rst_n),
                .key(key_filter),
                .led(led)
                    );
           
                  

endmodule

/**************************************************************************************/
/*
function:按键消抖输出
author:ZHY
time:2024年4月5日
*/
module key_filter(
            input       sys_clk,
            input       sys_rst_n,
            input       key,
            output reg  key_filter
                    );
    
    parameter CNT_MAX  = 20'd1000_000; //计数20ns*1000_000 =20ms

    reg         buffer1,buffer2,buf_res;
    wire        edge_flag;

    reg [19:0]  cnt;
    
//边沿检测模块_亚稳态处理
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        buffer1 <= 1'b0;
        buffer2 <= 1'b0;
        end
    else begin
        buffer2 <= buffer1;  //同步打拍子
        buf_res <= buffer2;
    end
end


//边沿检测模块_边沿检测 
assign edge_flag = (buf_res != buffer2)?1:0;  //前后时刻不同,说明出现边沿

//消抖模块_计数2ms
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        cnt <= 20'b0;
    else if(cnt >= CNT_MAX -1'b1) //记满则请0
        cnt <= 20'b0;
    else if(edge_flag)  //出现边沿
        cnt <= 20'b0;
    else
        cnt <= cnt + 1'b1;
end

//消抖模块_滤除抖动产生的短时信号
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        key_filter <= 1'b0;
    else if(cnt == CNT_MAX -1'b1)
        key_filter <= key;          //这里的处理应该是正确的,而且几乎没引入多余的变量
    else                            //只要计数到20ms,就判断一次稳定的按键来了,就可以
                                    //进行信号更新了,否则就保持原信号(滤除短时信号)
        key_filter <= key_filter;   //结合时序图就能理解此处了,
    
end
       
endmodule

/**************************************************************************************/
/*
function:呼吸灯+按键切换状态
author:ZHY
time:2024年4月5日
*/

module breath_led(
            input   	sys_clk,
            input   	sys_rst_n,
            input   	key,
            output  reg led
                    );

    parameter CNT_MXA_2US = 100; //计数时间2us
                        
    parameter CNT_MXA_2MS = 1000; //计数时间2ms  
                     
    reg [6:0]  cnt_us;
    reg [9:0]  cnt_ms,cnt_s;
    reg        inv_flag;
     /*******************额外设计******************************************************/
    reg  [8:0] cnt_500ms;  
    reg        buffer_test;
    reg        negedge_flag;
    reg        key_ctl;
    /*******************额外设计******************************************************/
    
    //计数时间2us
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            cnt_us <= 7'b0;
        else if(cnt_us >= CNT_MXA_2US -1'b1)      //记满2us
            cnt_us <= 7'b0;
        else
            cnt_us <= cnt_us+1'b1;
    end
    
    
    //计数时间2ms
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            cnt_ms <= 10'b0;
    //    else if(cnt_ms >= CNT_MXA_2MS -1'b1)   //加到1000次之后,计时达到2ms,此处的逻辑稍有误
        else if((cnt_ms >= CNT_MXA_2MS -1'b1) && (cnt_us == CNT_MXA_2US -1'b1))//这样最后一个2us才完整
            cnt_ms <= 10'b0;
        else if(cnt_us == CNT_MXA_2US -1'b1)   //每次计数满2us,则加一
            cnt_ms <= cnt_ms+1'b1;
        else
            cnt_ms <= cnt_ms;
        
    end
    
    //计数时间2s
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            cnt_s <= 10'b0;
    //    else if(cnt_s >= CNT_MXA_2MS -1'b1)   //加到1000次之后,计时达到2s,清零重新开始
    // !!!!   else if((cnt_s >= CNT_MXA_2MS -1'b1) &&(cnt_ms == CNT_MXA_2MS -1'b1))//这样最后一个2ms才完整,又错了,
        else if((cnt_s >= CNT_MXA_2MS -10'b1) &&(cnt_ms == CNT_MXA_2MS -10'b1)&& (cnt_us == CNT_MXA_2US -7'b1))
        //这样才完整,这样的cnt_s的清零赋值只持续一个周期,而上面的代码的话,赋值会持续2us和2ms
            cnt_s <= 10'b0;
        //else if((cnt_ms == CNT_MXA_2MS -10'b1) && (cnt_us == 7'b0)) //每次计数满2ms,则加一
        else if((cnt_ms == CNT_MXA_2MS -10'b1) && (cnt_us == CNT_MXA_2US -7'b1))
            cnt_s <= cnt_s+1'b1;
        else
            cnt_s <= cnt_s;
        
    end
    
    //递增or递减的标志信号,2s一次反转
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            inv_flag <= 1'b1;  //递增
        //else if(cnt_s == CNT_MXA_2MS -1'b1)   //加到1000次之后,计时达到2s,信号翻转
        else if((cnt_s >= CNT_MXA_2MS -10'b1) &&(cnt_ms == CNT_MXA_2MS -10'b1)&& (cnt_us == CNT_MXA_2US -7'b1))
            inv_flag <= ~inv_flag; //翻转
        else
           inv_flag <= inv_flag;  //不变
        
    end
    
    
    /*最抽象同时也是必须要掌握的一个地方来了,对于每次记满2us之后的处理,也就是
    输出递增or递减的调制信号,eg:100000000...,110000000...,111000000...,111100000...,111110000...
    这里纯思考如何设计会发现很麻烦,但是只要动手花了波形图,就会发现有个极其简单的数学关系
    所以有时候思考逻辑关系但是思考半天也想不出的情况下,一个简单的时序波形图可能就能给你
    提供直白的数学关系了,
    */
    
    //输出控制逻辑,要理解数学关系
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            led <= 1'b0;
        else if(key_ctl == 1'b1) begin         //key_ctl==1 呼吸灯
            if(inv_flag) begin      //判断标志信号为递增信号
                    if(cnt_ms > cnt_s)   //此处是数学关系,
                        led <= 1'b0;  
                    else
                        led <= 1'b1;
            end     
            else begin               //判断标志信号为递减 信号
                if(cnt_ms > cnt_s)   //此处是数学关系,
                        led <= 1'b1;  
                    else
                        led <= 1'b0;
            end     
        end 
        else 
             begin   if(cnt_500ms == 9'd249)//key_ctl==0  0.5s亮灭灯
                        led <= ~led;
                      else
                        led <= led; 
            end
            
    end
    
    /*******************************************************************************************/
                                        //额外设计按键切换状态//
    /*******************************************************************************************/
    
    //计数时间0.5s
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)
            cnt_500ms <= 9'b0;
        
        else if(cnt_500ms >= 9'd249)   //加到249次之后,计时达到0.5s,清零重新开始
            cnt_500ms <= 9'b0;
        //此处有问题!!!
        //else if(cnt_ms == CNT_MXA_2MS -1'b1)   //每次计数满2ms,则加一
        else if((cnt_ms == CNT_MXA_2MS -1'b1) && (cnt_us == CNT_MXA_2US -1'b1))  //每次计数满2ms,则加一    
            cnt_500ms <= cnt_500ms+1'b1;
        else
            cnt_500ms <= cnt_500ms;
        
    end
    
    //下边沿检测
    //边沿检测模块_亚稳态处理
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)begin
            buffer_test <= 1'b0;
            end
        else begin
            buffer_test <= key;  //同步打拍子
        end
    end
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)begin
            negedge_flag <= 1'b0;
	end
        else if((buffer_test == 1'b1)&& (key == 1'b0))  //下降沿检测
            negedge_flag <= 1'b1;
        else
            negedge_flag <= 1'b0;
    end
    //下降沿信号接收并自锁
    always@(posedge sys_clk or negedge sys_rst_n)begin
        if(!sys_rst_n)begin
            key_ctl <= 1'b0;
	end
        else if(negedge_flag)  //
            key_ctl <= ~key_ctl; //1为呼吸灯,0为亮灭亮灭
        else
            key_ctl <= key_ctl;
    end
                 
endmodule

其中呼吸灯的的2us计数,2ms计数和2s计数的清零和加一条件均有错误,包括后面我的翻转标志位inv_flag也出现错误,全部是同一种错误,如下:

else if((cnt_s >= CNT_MXA_2MS -10'b1) &&(cnt_ms == CNT_MXA_2MS -10'b1)&& (cnt_us == CNT_MXA_2US -7'b1))
            inv_flag <= ~inv_flag; //翻转

//满足这三个条件之后才翻转,但是我这样写的:

 else if(cnt_s == CNT_MXA_2MS -1'b1)
       inv_flag <= ~inv_flag; //翻转

这样写的话就会在cnt_s = 计数最大值的时刻无数次翻转,来一个时钟翻转一次,
 

这样导致的仿真结果会出现错误:

可以看到我的inv_flag信号在cnt_s=9的时候翻转了很多次,在2ms内多次翻转!

类似的错误下面出现了很多次!!!如:  

//    else if(cnt_ms >= CNT_MXA_2MS -1'b1)   //加到1000次之后,计时达到2ms,

//此处是清零的条件错误,上面这种写法会导致在计数到2ms的时刻一直清零,而且清零操作会保持2us才结束!
        else if((cnt_ms >= CNT_MXA_2MS -1'b1) && (cnt_us == CNT_MXA_2US -1'b1))//这样最后一个2us才完整
            cnt_ms <= 10'b0;

...

//    else if(cnt_s >= CNT_MXA_2MS -1'b1)   //加到1000次之后,计时达到2s,清零重新开始
    // !!!!   else if((cnt_s >= CNT_MXA_2MS -1'b1) &&(cnt_ms == CNT_MXA_2MS -1'b1))//这样最后一个2ms才完整,///又错了,这样的话清零操作依然会多持续2us才结束,而不是一个时钟周期

        else if((cnt_s >= CNT_MXA_2MS -10'b1) &&(cnt_ms == CNT_MXA_2MS -10'b1)&& (cnt_us == CNT_MXA_2US -7'b1))
        //这样才完整,这样的cnt_s的清零赋值只持续一个周期,而上面的代码的话,赋值会持续2us和2ms
            cnt_s <= 10'b0;
        //else if((cnt_ms == CNT_MXA_2MS -10'b1) && (cnt_us == 7'b0)) //每次计数满2ms,则加一
        else if((cnt_ms == CNT_MXA_2MS -10'b1) && (cnt_us == CNT_MXA_2US -7'b1))
            cnt_s <= cnt_s+1'b1;

        类似的错误我几乎全犯了一遍,我将此种错误称为:“递进计数条件错误”,这些错误一般都是出现在递进计数情况下,即cnt_us记满之后,cnt_ms+1;cnt_ms记满之后,cnt_s+1这样的递进计数关系情况下,条件不完备导致的错误,而且这些错误不是语法错误,也能完成综合,只有在仿真时才能看到异常,需要注意!!(如果cnt_s,cnt_ms,cnt_us的计数相互独立的情况下,则一般不会出现这样的条件不完备错误,但是会多占用逻辑资源)

5.仿真波形

略·

6.总结与易错点:

(1)消抖模块优化:

对比之前我写的消抖逻辑,这次我减少了一些不必要变量,比如pose_flag,nege_flag(直接用edge_flag代替即可,不必判断是上升还是下降沿),优化了key_filter的输出逻辑(稳定后可以直接用key赋值给key_filter),具体对比可看代码。

(2)递进计数条件错误(不完备):

详情如上,需要注意的是这种错误仅能在仿真中看出端倪,综合和实现过程都是可以通过的。

(3)参数重定义与参数传递:

补充之前遇到的问题,如CNT_MAX,CNT_MAX_US,为节省仿真时间需要改变参数,多次例化的情况下,如tb_breath_led例化了top_breath_led,,top_breath_led例化了breath_led,,那么要在tb中进行参数重定义,改变breath_led中定义的参数的话,tb_breath_led和top_breath_led模块都要遵循

                parameter 参数名 = xxx;(先)

然后例化底层模块时使用

                 #(   .源参数名(参数名)      )

这样的格式。

over

  • 29
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FPGA按键消抖实现流水灯的方法是通过在按键输入信号上应用消抖电路来解决按键抖动问题。消抖电路可以使用计数器和状态机的组合来实现。当按键按下时,计数器开始计数,如果在一定的时间内没有检测到按键抬起的信号,那么就认为按键有效,触发流水灯的状态变化。这样可以避免因按键抖动而导致流水灯频繁切换的问题。 在给出的代码中,按键消抖的实现是通过计数器cnt来实现的。当按键按下时,计数器开始计数,当计数器达到最大值时,即认为按键有效,触发流水灯的状态变化。同时,在计数器达到最大值之前,如果检测到按键抬起的信号,计数器会被清零,重新开始计数。 具体实现的代码如下: ```verilog module key_ctrl ( input clk, input rst_n, input wire key, output wire \[3:0\] led_on ); parameter MAX = 25'd25_000_000; reg \[25:0\] cnt; //计数寄存器 reg\[3:0\] led_r; //记录0.5s计数器设计 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt <= 26'd0; end else if (cnt == MAX - 1'd1) begin cnt <= 26'd0; end else begin cnt <= cnt + 1'd1; end end always @(posedge clk or negedge rst_n) begin if(!rst_n) begin led_r <= 4'b0001; end else if(!key) begin if (cnt == MAX - 1'd1) begin led_r <= {led_r\[2:0\], led_r\[3\]}; end else begin led_r <= led_r; end end else begin led_r <= led_r; end end assign led_on = led_r; endmodule ``` 这段代码中,按键消抖的实现是通过两个always块来完成的。第一个always块用于计数器的计数和清零,第二个always块用于观察按键状态并触发流水灯的状态变化。通过这种方式,可以实现按键消抖并实现流水灯效果。 #### 引用[.reference_title] - *1* [FPGA按键消抖后实现流水灯控制](https://blog.csdn.net/Headogerz/article/details/81529807)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [FPGA按键流水灯](https://blog.csdn.net/jynyyhd/article/details/131698029)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [【FPGA第二天】按键消抖+流水灯](https://blog.csdn.net/qq_53085291/article/details/130352656)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值