按键抖动延时+按键输入控制LED状态
前几日,针对多个按键输入时按键抖动延时考虑太过复杂,仿真效果不佳,后看到正点原子 蜂鸣器章节-抖动延时章节,有所启发,针对每个按键分别进行抖动延时处理,再根据所有抖动延时之后的按键值进行LED灯状态控制。
//****************************************Copyright (c)***********************************// //原子哥在线教学平台:www.yuanzige.com //技术支持:http://www.openedv.com/forum.php //淘宝店铺:https://zhengdianyuanzi.tmall.com //关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。 //版权所有,盗版必究。 //Copyright(C) 正点原子 2023-2033 //All rights reserved //---------------------------------------------------------------------------------------- // File name: key_led // Created by: 正点原子 // Created date: 2023年2月22日14:17:02 // Version: V1.0 // Descriptions: 按键控制LED灯实验 // //---------------------------------------------------------------------------------------- //****************************************************************************************// module key_led( //input input sys_clk, input sys_rst_n, input [3:0] key, //output // output cnt1_start, output reg [1:0] led_flag, // output reg [3:0] key_valid, // output reg [19:0] cnt1, output reg [3:0] key_filter, output reg [3:0] led ); //参数 //parameter CNT_MAX = 25'd25000000; //parameter CNT_MAX1 = 20'd1000000; parameter CNT_MAX = 25'd2500; //simulation time reduce 2500*20ns parameter CNT_MAX1 = 20'd500; //500*20ns //reg define reg [24:0] cnt; //reg [19:0] cnt1; //reg [1:0] led_flag; //reg [3:0] key_valid; //reg [3:0] key_down; //reg [3:0] key1; //reg [3:0] key2; //reg cnt1_start; wire [3:0] key_filter;//value after debounce //***************************************************** //** main code //***************************************************** //计数器计时0.5s always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) cnt <= 25'd0; else if(cnt < (CNT_MAX - 25'd1)) cnt <= cnt + 25'd1; else cnt <= 25'd0; end ////计数器计时20ms //always @(posedge sys_clk or negedge sys_rst_n) begin // if(!sys_rst_n) // cnt1 <= 19'd0; // else if(cnt1_start==1 && cnt1 < (CNT_MAX1 - 19'd1)) //计数开始且计数未达最大值 // cnt1 <= cnt1 + 19'd1; // else if(cnt1_start==1) // cnt1 <= 19'd0; //end key_debounce #( .CNT_MAX1 (CNT_MAX1) )u_key0_debounce( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .key (key[0]), .key_filter (key_filter[0]) ); key_debounce #( .CNT_MAX1 (CNT_MAX1) )u_key1_debounce( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .key (key[1]), .key_filter (key_filter[1]) ); key_debounce #( .CNT_MAX1 (CNT_MAX1) )u_key2_debounce( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .key (key[2]), .key_filter (key_filter[2]) ); key_debounce #( .CNT_MAX1 (CNT_MAX1) )u_key3_debounce( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .key (key[3]), .key_filter (key_filter[3]) ); //延时20ms后判断按键是否按下有效 //always @(posedge sys_clk or negedge sys_rst_n) begin // if(!sys_rst_n) // key_valid <= 4'b0000; // else if(key[0]==key1[0]&&cnt1 == (CNT_MAX1 - 19'd2)) //抖动延时等于20ms且此时按键被按下,认为有效 // key_valid <= 4'b0001; // else if(key[1]==key1[1]&&cnt1 == (CNT_MAX1 - 19'd2)) // key_valid <= 4'b0010; // else if(key[2]==key1[2]&&cnt1 == (CNT_MAX1 - 19'd2)) // key_valid <= 4'b0100; // else if(key[3]==key1[3]&&cnt1 == (CNT_MAX1 - 19'd2)) // key_valid <= 4'b1000; // else if (key[0]==key1[0]&&cnt1 == (CNT_MAX1 - 19'd2)&& key_valid==4'b0001) // key_valid <= 4'b0000; // else if (key[1]==key1[1]&&cnt1 == (CNT_MAX1 - 19'd2)&& key_valid==4'b0010) // key_valid <= 4'b0000; // else if (key[2]==key1[2]&&cnt1 == (CNT_MAX1 - 19'd2)&& key_valid==4'b0100) // key_valid <= 4'b0000; // else if (key[3]==key1[3]&&cnt1 == (CNT_MAX1 - 19'd2)&& key_valid==4'b1000) // key_valid <= 4'b0000; // else if(key==key1&&cnt1 == (CNT_MAX1 - 19'd2)) // key_valid <= 4'b0000; //end //LED状态切换标志位 // 0 1 2 3 // 00 01 10 11 always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) led_flag <= 2'd0; else if(cnt == (CNT_MAX - 25'd1)) led_flag <= led_flag + 2'd1; else led_flag <= led_flag; end //add debounce detection // always @(posedge sys_clk or negedge sys_rst_n) begin // if(!sys_rst_n) // key_valid <= 4'b0000; // key_down <= 4'b0000; // else begin // case(key) // 4'b1111 : key_down <= 4'b0000; // 4'b1110 : begin // key_down <=4'b0001; // end // 4'b1101 : begin // key_down <=4'b0010; // end // 4'b1011 : begin // key_down <=4'b0100; // end // 4'b0111 : begin // key_down <=4'b1000; // end // endcase // end // end // assign key1=key; // assign语句副职只能用阻塞赋值 与通过always延迟两拍有何不同,always语句可以阻塞和非阻塞赋值 // assign key2=key1; // always @(posedge sys_clk or negedge sys_rst_n) begin // if(!sys_rst_n) begin // key1<= 4'b1111; // key2<= 4'b1111; // end // else begin // key1<=key; // key2<=key1; // end // end // always @(posedge sys_clk or negedge sys_rst_n) begin // if(!sys_rst_n) begin // key_valid <= 4'b0000; // key_down <= 4'b0000; // end // else begin // case(key) //对按键进行判断 // 4'b1111 : begin // if(key!=key1) // cnt1_start <= 1'b1; // else if(key==key1&& key_valid == 4'b0000) // cnt1_start<=1'b0; // end // 4'b1110 : begin // // if(key[0]|key1[0]==1'b0)//连续两次按键为低,抖动计数开始 此种判断只能针对按下,松开时抖动无法去抖 // if(key[0]!=key1[0])// // cnt1_start<=1'b1; // else if(key[0]==key1[0]&& key_valid[0]==4'b0001) // cnt1_start<=1'b0; // end // 4'b1101 : begin // if(key[1]!=key1[1])//连续两次按键为低,抖动计数开始 // cnt1_start<=1'b1; // else if(key[1]==key1[1]&& key_valid[1]==4'b0010) // cnt1_start<=1'b0; // end // 4'b1011 : begin // if(key[2]!=key1[2])//连续两次按键为低,抖动计数开始 // cnt1_start<=1'b1; // else if(key[2]==key1[2]&& key_valid[2]==4'b0100) // cnt1_start<=1'b0; // end // 4'b0111 : begin // if(key[3]!=key1[3])//连续两次按键为低,抖动计数开始 // cnt1_start<=1'b1; // else if(key[3]==key1[3]&& key_valid[3]==4'b1000) // cnt1_start<=1'b0; // end // endcase // end // end // assign key1=key; // assign key2=key1; //LED控制(根据哪个KEY被按下,和led_flag,对LED进行赋值) always @(posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) led <= 4'b0000; else begin case(~key_filter) 4'b0000 : led <= 4'b0000; 4'b0001 : begin if(led_flag == 2'd0) led <= 4'b0001; else if(led_flag == 2'd1) led <= 4'b0010; else if(led_flag == 2'd2) led <= 4'b0100; else led <= 4'b1000; end 4'b0010 : begin if(led_flag == 2'd0) led <= 4'b1000; else if(led_flag == 2'd1) led <= 4'b0100; else if(led_flag == 2'd2) led <= 4'b0010; else led <= 4'b0001; end 4'b0100 : begin if(led_flag[0] == 1'b0) led <= 4'b1111; else led <= 4'b0000; end 4'b1000 : led <= 4'b1111; default : ; endcase end end endmodule
去抖模块代码:
//****************************************Copyright (c)***********************************// //原子哥在线教学平台:www.yuanzige.com //技术支持:http://www.openedv.com/forum.php //淘宝店铺:https://zhengdianyuanzi.tmall.com //关注微信公众平台微信号:"正点原子",免费获取ZYNQ & FPGA & STM32 & LINUX资料。 //版权所有,盗版必究。 //Copyright(C) 正点原子 2023-2033 //All rights reserved //---------------------------------------------------------------------------------------- // File name: key_debounce // Created by: 正点原子 // Created date: 2023年2月16日14:20:02 // Version: V1.0 // Descriptions: 按键消抖模块 // //---------------------------------------------------------------------------------------- //****************************************************************************************// module key_debounce( input sys_clk , input sys_rst_n , input key , //外部输入的按键值 output reg key_filter //按键消抖后的值 ); //parameter define parameter CNT_MAX1 = 20'd100_0000; //消抖时间20ms //reg define reg [19:0] cnt ; reg key_d0; //将按键信号延迟一个时钟周期 reg key_d1; //将按键信号延迟两个时钟周期 //***************************************************** //** main code //***************************************************** //对按键端口的数据延迟两个时钟周期 always @ (posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) begin key_d0 <= 1'b1; key_d1 <= 1'b1; end else begin key_d0 <= key; key_d1 <= key_d0; end end //按键值消抖 always @ (posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) cnt <= 20'd0; else begin if(key_d1 != key_d0) //检测到按键状态发生变化 cnt <= CNT_MAX1; //则将计数器置为20'd100_0000, //即延时100_0000 * 20ns(1s/50MHz) = 20ms else begin //如果当前按键值和前一个按键值一样,即按键没有发生变化 if(cnt > 20'd0) //则计数器递减到0 cnt <= cnt - 1'b1; else cnt <= 20'd0; end end end //将消抖后的最终的按键值送出去 always @ (posedge sys_clk or negedge sys_rst_n) begin if(!sys_rst_n) key_filter <= 1'b1; //在计数器递减到1时送出按键值 else if(cnt == 20'd1) key_filter <= key_d1; else key_filter <= key_filter; end endmodule
仿真代码:
`timescale 1ns/1ns //仿真的单位/仿真的精度 module tb_key_led(); parameter CLK_PERIOD = 20; parameter CNT_MAX = 25'd2500; //仅用于仿真,对应500000ns parameter CNT_MAX1 = 25'd500; //仅用于仿真,对应10000ns reg sys_clk; //周期20ns reg sys_rst_n; reg [3:0] key; wire [3:0] led; //wire cnt1_start; wire [1:0] led_flag; wire [3:0] key_filter; //wire [19:0] cnt1; initial begin sys_clk <= 1'b0; sys_rst_n <= 1'b0; key <= 4'b1111; #200 sys_rst_n <= 1'b1; #2000 key <= 4'b1110; //按下KEY0 #20 key <= 4'b1111; //增加抖动 #30 key <= 4'b1110; //增加抖动 #50 key <= 4'b1111; //增加抖动 #200 key <= 4'b1110; //增加抖动 #2000000 key <= 4'b1111; //释放KEY0 #2000000 key <= 4'b1101; //按下KEY1 #2000000 key <= 4'b1111; //释放KEY1 #2000000 key <= 4'b1011; //按下KEY2 #2000000 key <= 4'b1111; //释放KEY2 #2000000 key <= 4'b0111; //按下KEY3 #2000000 key <= 4'b1111; //释放KEY3 end always #(CLK_PERIOD/2) sys_clk = ~sys_clk; key_led #( .CNT_MAX (CNT_MAX), .CNT_MAX1 (CNT_MAX1) ) u_key_led( .sys_clk (sys_clk ), .sys_rst_n (sys_rst_n), .key (key ), // .cnt1_start (cnt1_start ), .led_flag (led_flag ), .key_filter (key_filter ), // .cnt1 (cnt1 ), .led (led ) ); endmodule
仿真结果如下:
抖动延时判断之后才更新按键状态,与设计相符。