1.按键消抖的原理



图1.按键抖动示意图

我们平常所用的按键为机械弹性开关,由于触点的弹性作用,按键在闭合时不会马上稳定的接通,而是有一段时间的抖动,在断开时也不会立即断开。抖动时间由按键的机械特性所决定,一般为5ms~10ms。所以我们在做按键检测时都要加一个消抖的过程。

按键消抖主要有两种方案:

一是延时重采样;二是持续采样。



从理论上来说,延时(如10ms)重采样的准确率肯定低于持续采样。



 



2.按 键消抖的方法



(1)延时重采样



延时重采样的意思是,当第一次检测到键值由'1'变为'0'时,再延时一段时间(如10ms),再次采样,确认是否仍是'0';若是'0'则认为此时键值为'0',否则,重新执行检测过程。



这个方案在特权同学的《深入浅出玩转FPGA》的p191有例程;



 



该方案的缺陷:a.如果延时太短,有可能两次采样时都处于抖动时间,因此可能引起误判;



b.如果延时太长,可能检测不出按键变换



 



(2)持续采样



持续采样的原理是,当检测到按键处于某电平(如'0')时,在之后的N个时钟周期内连续检测此按键的电平,如果一直不变,则读出此按键的电平值(如'0')。



持续采样的优点:a.样本足够多,减少误判的可能性。



b.对于按键按下('1'->'0'),按键释放('0'->'1')都可以检测。



持续采样的缺点:持续检测的时间太长(大于按键按下和释放的时间差),则可能无法检测按键的变换。



 



1)单个按键的检测



按键检测的输出有两种方式:1.电平输出,此时按键功能犹如拨码开关。



2.脉冲输出,此时每按下一次按键,输出一个脉冲信号。



 






图2.按键检测输出波形示意图



如图2所示,Key_out1的输出与Key_in的变换趋势相同,只是滤除了抖动成分;



Key_out2则是每当按键按下后输出一个高电平脉冲。在大多数的应用中会用到Key_out2所示功能。



 



输出为电平(用于电平判断事件,类似于开关选择)



module key_scan 
 
 
 

   #(parameter DURATION = 1200)//the number of clk period 
 
 
 

   ( 
 
 
 

   input wire clk, //120MHz 
 
 
 

   input wire rst_n, 
 
 
 

   input wire key_in, 
 
 
 

     
 
 
 

   output reg key_out 
 
 
 

   ); 
 
 
 

     
 
 
 

   //key jitter filter 
 
 
 

   reg[11:0] low_cnt; 
 
 
 

   reg[11:0] high_cnt; 
 
 
 

   always @(posedge clk or negedge rst_n) 
 
 
 

   begin 
 
 
 

   if(!rst_n) 
 
 
 

   begin 
 
 
 

   low_cnt <= 0; 
 
 
 

   high_cnt <= 0; 
 
 
 

   key_out <= 1'b1; 
 
 
 

   end 
 
 
 

   else 
 
 
 

   begin 
 
 
 

   if(key_in == 1'b0) 
 
 
 

   begin 
 
 
 

   high_cnt <= 0; 
 
 
 

   if(low_cnt == DURATION) 
 
 
 

   begin low_cnt <= low_cnt; key_out <= 1'b0; end 
 
 
 

   else 
 
 
 

   low_cnt <= low_cnt + 1'b1; 
 
 
 

   end 
 
 
 

   else //key_in == 1'b1 
 
 
 

   begin 
 
 
 

   low_cnt <= 0; 
 
 
 

   if(high_cnt == DURATION) 
 
 
 

   begin high_cnt <= high_cnt;key_out <= 1'b1; end 
 
 
 

   else 
 
 
 

   high_cnt <= high_cnt + 1'b1; 
 
 
 

   end 
 
 
 

   end 
 
 
 

   end 
 
 
 

   endmodule 
 
 
 

     
 
 
 
 
  输出为脉冲(用于脉冲触发事件) 
 
 
 

   module key_scan 
 
 
 

   #(parameter DURATION = 1200)//the number of clk period 
 
 
 

   ( 
 
 
 

   input wire clk, //120MHz 
 
 
 

   input wire rst_n, 
 
 
 

   input wire key_in, 
 
 
 

     
 
 
 

   output wire key_out 
 
 
 

   ); 
 
 
 

     
 
 
 

   //key jitter filter 
 
 
 

   reg[11:0] low_cnt; 
 
 
 

   always @(posedge clk or negedge rst_n) 
 
 
 

   begin 
 
 
 

   if(!rst_n) 
 
 
 

   low_cnt <= 0; 
 
 
 

   else 
 
 
 

   begin 
 
 
 

   if(key_in == 1'b0) 
 
 
 

   begin 
 
 
 

   if(low_cnt == DURATION) 
 
 
 

   low_cnt <= low_cnt; 
 
 
 

   else 
 
 
 

   low_cnt <= low_cnt + 1'b1; 
 
 
 

   end 
 
 
 

   else //key_in == 1'b1 
 
 
 

   low_cnt <= 0; 
 
 
 

   end 
 
 
 

   end

assign key_out = (low_cnt == DURATION -1)? 1'b1 : 1'b0;

 

endmodule


 




 



2)多个独立按键的扫描(扫描得出键值)



功能:a.可以检测出哪些键按下了,甚至哪些键同时按下了。



b.键值更新后,输出一个脉冲信号,提升更新完成;



c.键值保持到下一次更新完成。



 



module key_counter_scan 
 
 
 

   #( 
 
 
 

   parameter KEY_WIDTH = 4 
 
 
 

   ) 
 
 
 

   ( 
 
 
 

   //global clock 
 
 
 

   input clk, 
 
 
 

   input rst_n, 
 
 
 

     
 
 
 

   //key interface 
 
 
 

   input [KEY_WIDTH-1:0] key_data, 
 
 
 

     
 
 
 

   //user interface 
 
 
 

   output reg key_flag, 
 
 
 

   output reg [KEY_WIDTH-1:0] key_value //H Valid 
 
 
 

   ); 
 
 
 

     
 
 
 

   //----------------------------------- 
 
 
 

   //Register key_data for compare 
 
 
 

   reg [KEY_WIDTH-1:0] key_data_r; 
 
 
 

   always @(posedge clk or negedge rst_n) 
 
 
 

   begin 
 
 
 

   if(!rst_n) 
 
 
 

   key_data_r <= {KEY_WIDTH{1'b1}}; 
 
 
 

   else 
 
 
 

   key_data_r <= key_data; 
 
 
 

   end



 



 



//----------------------------------- 
 
 
 

   //continue 20ms 
 
 
 

   localparam DELAY_TOP = 20'd1000_000; 
 
 
 

   //localparam DELAY_TOP = 20'd1000; //Just for test 
 
 
 

   reg [19:0] delay_cnt; 
 
 
 

   //----------------------------------- 
 
 
 

   //Key scan via counter detect. 
 
 
 

   always @(posedge clk or negedge rst_n) 
 
 
 

   begin 
 
 
 

   if(!rst_n) 
 
 
 

   delay_cnt <= 0; 
 
 
 

   else 
 
 
 

   begin 
 
 
 

   if((key_data == key_data_r) && (key_data != {KEY_WIDTH{1'b1}})) //20ms counter jitter 
 
 
 

   begin 
 
 
 

   if(delay_cnt < DELAY_TOP) 
 
 
 

   delay_cnt <= delay_cnt + 1'b1; 
 
 
 

   else 
 
 
 

   delay_cnt <= DELAY_TOP; 
 
 
 

   end 
 
 
 

   else 
 
 
 

   delay_cnt <= 0; 
 
 
 

   end 
 
 
 

   end 
 
 
 

     
 
 
 

     
 
 
 

   //----------------------------------- 
 
 
 

   //the complete of key_data capture 
 
 
 

   wire key_trigger = (delay_cnt == DELAY_TOP - 1'b1) ? 1'b1 : 1'b0; 
 
 
 

     
 
 
 

   //----------------------------------- 
 
 
 

   //output the valid key_value via key_trigger 
 
 
 

   always@(posedge clk or negedge rst_n) 
 
 
 

   begin 
 
 
 

   if(!rst_n) 
 
 
 

   key_value <= {KEY_WIDTH{1'b0}}; 
 
 
 

   else if(key_trigger) 
 
 
 

   key_value <= ~key_data_r; 
 
 
 

   else 
 
 
 

   key_value <= key_value; 
 
 
 

   end 
 
 
 

     
 
 
 

   //--------------------------------- 
 
 
 

   //Lag 1 clock for valid read enable 
 
 
 

   always@(posedge clk or negedge rst_n) 
 
 
 

   begin 
 
 
 

   if(!rst_n) 
 
 
 

   key_flag <= 0; 
 
 
 

   else 
 
 
 

   key_flag <= key_trigger; 
 
 
 

   end



 



endmodule