实际系统中常用的按键大部分都是轻触式按键,如下图所示。该按键内部由一个弹簧片和两个固定触点组成,当弹簧片被按下,则两个固定触点接通,按键闭合。弹簧片松开,两个触点断开,按键也就断开了。根据这种按键的机械特性,在按键按下时,会先有一段时间的不稳定期,在这期间,两个触点时而接通,时而断开,我们称之为抖动,当按键大约按下20ms后,两个触点才能处于稳定的闭合状态,按键松开时和闭合时情况类似。而我们的FPGA工作在很高的频率,按键接通或断开时任何一点小的抖动都能轻易的捕捉到,如果不加区分的将每一次闭合或断开都当做一次按键事件,那么势必一次按键动作会被FPGA识别为很多次按键操作,从而导致系统工作稳定性下降。
![](http://www.openedv.com/upload/2014/10/23/5a8c70f26433efb04c5d8e9066d41969_615.jpg)
轻触按键实物图
一次按键动作的大致波形如下图所示:
![](http://www.openedv.com/upload/2014/10/23/d5974c0dc0212786d1851df4361045ed_749.jpg)
因此,我们所需要做的工作,就是滤除按键按下和释放时各存在的20ms的不稳定波形。做法思路是:检测按键按下---》等待20Ms ----》检测此时按键键值,若为按下值则按下有效,否则按下无效(后面可以检测亦可以不检测,据具体情况而定----》检测到按键松开----》延迟20Ms ----》检测此时的键值,若为按下值则松开无效,否则按键松开)
硬件电路:
独立按键属于一种输入设备,其与FPGA连接的IO口被接上了10K的上拉电阻,在按键没有按下时,FPGA会检测到高电平;当按键按下后,FPGA的IO口上则将呈现低电平。因此,按键检测的实质就是读取FPGA的IO上的电平。
![](http://www.openedv.com/upload/2014/10/23/25b773ea1dfc74dc2dd8abb3b2f325ba_3.jpg)
独立按键典型
电路
verilog 程序如下所示:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :key_shake.v ** CreateDate :2015.03 ** Funtions : 按键的消抖操作:在复位之后的100us内,不响应按键的操作,在之后有按键按下后,有20ms的延迟,之后输出按键输出. ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved[F]. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content:V1.1:clk-->clk_100M, 常数声明放到一起,便于修改。 *******************************************************************************/ module key_shake ( clk_100M, rst_n, key_in, key_out ); input clk_100M; //100Mhz input rst_n; input key_in; output key_out; //-------------------------------------- //在复位之后的100us内,不响应按键的操作 localparam t_100us = 14'd9999; localparam t1ms = 17'd99999; //定时1ms localparam t_20ms = 5'd20; reg [13:0] cnt; reg key_en; //复位之后允许按键输入标志 always @(posedge clk_100M or negedge rst_n) begin if(!rst_n) begin cnt <= 0; key_en <=0; end else begin if(cnt == t_100us) begin key_en <= 1; end else begin key_en <= 0; cnt <= cnt + 1; end end end //-------------------------------------------------- wire HtoL_flag; //下降沿标志 wire LtoH_flag; //上升沿标志 reg [2:0] key_reg; always @(posedge clk_100M or negedge rst_n) begin if(!rst_n) begin key_reg <= 3'b111; //默认没按下状态为高,按下之后为低.反之则为3'b000; end else begin key_reg <= {key_reg[1:0],key_in}; end end assign HtoL_flag = key_en?(key_reg[2:1] == 2'b10):0; //下降沿检测,一个时钟的高电平 assign LtoH_flag = key_en?(key_reg[2:1] == 2'b01):0; //上升沿检测,一个时钟的高电平 //--------------------------------------------- reg cnt_en; //计数使能标志 reg [16:0] cnt2; always @(posedge clk_100M or negedge rst_n) begin if(!rst_n) begin cnt2 <= 17'd0; end else if((cnt_en)&&(cnt2 == t1ms)) begin cnt2 <= 17'd0; end else if(cnt_en) begin cnt2 <= cnt2 + 17'd1; end else cnt2 <= 17'd0; end reg [4:0] cnt3; always @(posedge clk_100M or negedge rst_n) begin if(!rst_n) begin cnt3 <= 5'd0; end else if((cnt_en)&&(cnt2 == t1ms)) begin if(cnt3 == t_20ms ) cnt3 <= t_20ms; else cnt3 <= cnt3 + 1; end else if(!cnt_en) cnt3 <= 5'd0; end //---------------------------------- //按键状态机 reg [2:0] i; reg key_down; //按键按下标志 reg key_up; //按键释放标志 always @(posedge clk_100M or negedge rst_n) begin if(!rst_n) begin key_down <= 0; key_up <= 0; i <= 0; cnt_en <= 0; end else begin case(i) 'd0: begin key_down <= 0; key_up <= 0; if(HtoL_flag) i <= 'd1; //检测到按下 else if(LtoH_flag) i <= 'd2; //检测到释放按键 else i <= 'd0; end 'd1: begin if(cnt3 == t_20ms ) begin if(!key_in) //检测到按键依然被按下 begin key_down <= 1; //按键按下成功 i <= 'd3; cnt_en <= 0; end else begin key_down <= 0; i <= 'd0; cnt_en <= 0; end end else cnt_en <= 1; end 'd2: begin if(cnt3 == t_20ms ) begin if(key_in) //检测到按键被释放 begin key_up <= 1; //按键释放成功 i <= 'd3; cnt_en <= 0; end else begin key_up <= 0; i <= 'd0; cnt_en <= 0; end end else cnt_en <= 1; end 'd3: begin key_up <= 0; key_down <= 0; i <= 'd0; end default:i <= 'd0; endcase end end assign key_out = key_down; //当按键被按下有效时 // assign key_out = key_up; //当按键被释放后才有效时 endmodule
测试代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :key_testbench.v ** CreateDate :2015.03 ** Funtions :按键消抖的测试文件 ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content: *******************************************************************************/ module key_testbench; reg clk; reg rst_n; reg key_in; wire key_out; key_shake key_shake_1( .clk, .rst_n, .key_in, .key_out ); localparam tck = 24; localparam t = 1000/tck; always #(t/2) clk = ~clk; task key_in_down; begin #(3*t) key_in = 1; #(3*t) key_in = 0; #(8*t) key_in = 1; #(8*t) key_in = 0; #(13*t) key_in = 1; #(13*t) key_in = 0; end endtask task key_in_up; begin #(3*t) key_in = 0; #(3*t) key_in = 1; #(8*t) key_in = 0; #(8*t) key_in = 1; #(13*t) key_in = 0; #(13*t) key_in = 1; end endtask initial begin clk = 0; rst_n = 0; key_in = 1; #(100*t) rst_n = 1; #(100*t); #(10*t) key_in_down; #(100*t); #(10*t) key_in_up; #(8000*t); #(10*t) repeat(2) key_in_down; //按下时抖动 #(640000*t); //按下时间 #(10*t) repeat(2) key_in_up; //释放时抖动 end endmodule
仿真结果:
1、在复位之后100us之前按下按键时,不响应。
2、抖动(按下后20ms之内释放)。
![](https://i-blog.csdnimg.cn/blog_migrate/34aaf92a83dec2d080ead8c25249e680.jpeg)
3、按下之后检测以及释放之后的检测。
![](https://i-blog.csdnimg.cn/blog_migrate/f634ca38f08c42233a48fb5faca56d2f.jpeg)
如果将按键按下有效时刻、按键释放有效时刻和按键所处状态全部表现出来,则代码稍作修改即可:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
/********************************Copyright************************************** **----------------------------File information-------------------------- ** File name :key_shake.v ** CreateDate :2015.03 ** Funtions : 按键的消抖操作:在复位之后的100us内,不响应按键的操作,在之后有按键按下后,有20ms的延迟,检测,然后松开时也有20ms的检测,之后输出按键输出. ** Operate on :M5C06N3L114C7 ** Copyright :All rights reserved[F]. ** Version :V1.0 **---------------------------Modify the file information---------------- ** Modified by : ** Modified data : ** Modify Content:V1.1:clk-->clk_100M, 常数声明放到一起,便于修改。 *******************************************************************************/ module key_shake ( clk, rst_n, key_in, key_down_out, key_up_out, key_in_out ); input clk; //24Mhz input rst_n; input key_in; output key_down_out; //按下输出 output key_up_out; //释放输出 output key_in_out; //跟随输入输出 //-------------------------------------- //在复位之后的100us内,不响应按键的操作 parameter t_20ms = 5'd20; `define CLK_20M // `define CLK_24M // `define CLK_50M `ifdef CLK_20M parameter t_100us = 12'd1999; parameter t1ms = 16'd19999; //定时1ms `endif `ifdef CLK_20M parameter t_100us = 12'd2399; parameter t1ms = 16'd23999; //定时1ms `endif `ifdef CLK_20M parameter t_100us = 13'd4999; parameter t1ms = 16'd49999; //定时1ms `endif reg [12:0] cnt; reg key_en; //复位之后允许按键输入标志 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt <= 0; key_en <=0; end else begin if(cnt == t_100us) begin key_en <= 1; end else begin key_en <= 0; cnt <= cnt + 1; end end end //-------------------------------------------------- wire HtoL_flag; //下降沿标志 wire LtoH_flag; //上升沿标志 reg [2:0] key_reg; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin key_reg <= 3'b111; //默认没按下状态为高,按下之后为低.反之则为3'b000; end else begin key_reg <= {key_reg[1:0],key_in}; end end assign HtoL_flag = key_en?(key_reg[2:1] == 2'b10):0; //下降沿检测,一个时钟的高电平 assign LtoH_flag = key_en?(key_reg[2:1] == 2'b01):0; //上升沿检测,一个时钟的高电平 //--------------------------------------------- reg cnt_en; //计数使能标志 reg [15:0] cnt2; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt2 <= 16'd0; end else if((cnt_en)&&(cnt2 == t1ms)) begin cnt2 <= 16'd0; end else if(cnt_en) begin cnt2 <= cnt2 + 16'd1; end else cnt2 <= 16'd0; end reg [4:0] cnt3; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt3 <= 5'd0; end else if((cnt_en)&&(cnt2 == t1ms)) begin if(cnt3 == t_20ms ) cnt3 <= t_20ms; else cnt3 <= cnt3 + 1; end else if(!cnt_en) cnt3 <= 5'd0; end //---------------------------------- //按键状态机 reg [2:0] i; reg key_down; //按键按下标志 reg key_up; //按键释放标志 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin key_down <= 0; key_up <= 0; i <= 0; cnt_en <= 0; end else begin case(i) 'd0: begin key_down <= 0; key_up <= 0; if(HtoL_flag) i <= 'd1; //检测到按下 else if(LtoH_flag) i <= 'd2; //检测到释放按键 else i <= 'd0; end 'd1: begin if(cnt3 == t_20ms ) begin if(!key_in) //检测到按键依然被按下 begin key_down <= 1; //按键按下成功 i <= 'd3; cnt_en <= 0; end else begin key_down <= 0; i <= 'd0; cnt_en <= 0; end end else cnt_en <= 1; end 'd2: begin if(cnt3 == t_20ms ) begin if(key_in) //检测到按键被释放 begin key_up <= 1; //按键释放成功 i <= 'd3; cnt_en <= 0; end else begin key_up <= 0; i <= 'd0; cnt_en <= 0; end end else cnt_en <= 1; end 'd3: begin key_up <= 0; key_down <= 0; i <= 'd0; end default:i <= 'd0; endcase end end //--------------------------- reg key_out; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin key_out <= 1; end else begin if(key_down) key_out <= 0; //按下为低 else if(key_up) key_out <= 1; //释放为高 else key_out <= key_out; //否则保持 end end assign key_down_out = key_down; //当按键被按下有效时 assign key_up_out = key_up; //当按键被释放后才有效时 assign key_in_out = key_out; endmodule
仿真: