基于独立按键消抖及原理分析
用笨笔头书写青春年华,寻觅真理,三人行,必有我师,交换思想,从点滴做起。
1.独立按键模型如下:
2.分析:在按键按下时,图中电路形成通路,在实际电路设计中将按键的一侧接到系统电源的GND上,另一侧接到FPGA芯片的管脚上,这样便可以通过FPGA IO口的状态判断按键是否按下,为了保证FPGA的管脚在按键没有被按下时是一个确定的电平,所以在电路设计时加上一个上拉电阻,这样当独立按键没有被按下时,FPGA管脚默认会检测到高电平1,按键被按下时,FPGA的管脚直接与地接通,FPGA管脚就会检测到低电平0。
3.通过以上分析,从而可以画出按键从没有按下到按下FPGA (I/O)口上的电平的波形如下:
4.实际波形是否如上图所示呢?通过使用示波器对按键连接在FPGA管脚的线路进行波形抓取发现实际波形并非上图所示,实际波形如下:
5.通过观察理想波形与实际波形了解到在按键按下与按键抬起时都有明显的抖动过程,通过示波器抓取抖动时间,发现一般为20ms抖动阶段(按键按下、按键抬起)。由于FPGA工作速度是非常快,例如:系统时钟为50MHz,周期为20ns,即FPGA会以20ns的速度去采样管脚上的电平状态,当检测到抖动阶段的下降沿时,FPGA会认为发生了一次按键按下,让led灯加1很短的时间又弹回到高电平,在经过很短的时间又出现下降沿,FPGA又会误认为又来了一次按键事件,让led灯状态再次加1,在这样的情况下,一次按键便会触发led灯多次变化,并且不可预期,释放时同理,不再分析。因此便需要对这样的不稳定期作滤波处理,即按键消抖。(以按键驱动led灯为例)
6.本次设计利用状态机,对独立按键进行消抖,因此需要分析在实际电路中,独立按键分为哪几个状态,如下所示:
7.通过以上分析显而易见共有四个状态,分别为:空闲状态,按下抖动滤除状态(按下抖动状态),按下稳定状态,释放抖动滤除状态(抬起抖动状态)。
分析完状态之后,需明确分析,这几个状态之间是怎么进行跳转的,即状态跳转条件是什么。状态转移图如下:
8.通过对状态转移图的分析,相信读者可以轻松写出代码,代码如下。
module key_filter(clk,rst_n,key_in,key_flag,key_state); input clk; input rst_n; input key_in; output reg key_flag; output reg key_state; localparam IDEL = 4'b0001, FILTER= 4'b0010, DOWN =4'b0100, FILTER1= 4'b1000; reg [3:0] state; reg key_tempa; reg key_tempb; reg cnt_fult; //---挤满标志信号 reg[19:0]cnt; //20ms计数器 reg en_cnt; wire pedge,nedge; reg key_in_sa; reg key_in_sb; //-----------对外部输入信号进行同步处理------------------------ always @(posedge clk,negedge rst_n) if(!rst_n)begin key_in_sa <= 0; key_in_sb <= 0; end else begin key_in_sa <= key_in; key_in_sb <= key_in_sa; end //-使用D触发器存储两个相邻时钟上升沿时外部输入信号(已经同步到系统时钟域中)的电平状态 always @(posedge clk,negedge rst_n) if(!rst_n) begin key_tempa <= 0; key_tempb <= 0; end else begin key_tempa <= key_in_sb; key_tempb <= key_tempa; end assign nedge = !key_tempa & key_tempb; assign pedge = key_tempa & (!key_tempb); // -------状态机程序------------------------ always @(posedge clk,negedge rst_n) if(!rst_n) cnt <= 0; else if(en_cnt) cnt <= cnt + 20'd1; else cnt <= 0; always @(posedge clk,negedge rst_n) if(!rst_n) cnt_fult <= 0; else if(cnt == 999_999) cnt_fult <= 1; else cnt_fult <= 0; //------------------------------------------- always@(posedge clk,negedge rst_n) if(!rst_n)begin en_cnt <= 0; state <= IDEL; key_flag <= 0; key_state <= 1; end else begin case(state) IDEL: begin key_flag <= 0; if(nedge)begin state <= FILTER; en_cnt <= 1; end else state <= IDEL; end FILTER: if(cnt_fult)begin key_flag <= 1; key_state <= 0; en_cnt <= 0; state <= DOWN; end else if(pedge )begin state <= IDEL; en_cnt <= 0; end else state <= FILTER; DOWN: begin key_flag <= 0; if(pedge)begin state <= FILTER1; en_cnt <= 1; end else state <= DOWN; end FILTER1: if(cnt_fult)begin state <= IDEL; key_flag <= 1; key_state <= 1; en_cnt <= 0; end else if(nedge)begin en_cnt <= 0; state <= DOWN; end else state <= FILTER1; default: begin state <= IDEL; en_cnt <= 0; key_flag <= 0; key_state <= 1; end endcase end endmodule
9:其实细读代码可以发现里边还涉及了边沿检测代码的思想,这里不再累赘,会在后面的博文中提到。