(13)基于状态机的按键消抖
前言
在开发板上的按键具有以下特点:当未按下时输出为高电平,当按键按下时输出则变为低电平,具体原理可从如下原理图看出:
而实际的按键结构中存在一个反作用弹簧片,因此当按下或松开时会产生额外的物理抖动,从而产生电平抖动
因此,按键的理想波形与实际波形如下:
上图中抖动次数和抖动周期是随机的,一般持续时间会在20ns以内,这就需要滤波来消除抖动对外部设备产生的影响(例如规定按下一次就计数一次,可见抖动会对影响其正常功能的使用),一般有硬件设计和软件两种方式,其中硬件设计常用于按键较少的情形
分析
整个过程中按键有如下几种状态:
没有被按下,静止状态,高电平
按下过程中,抖动,输出高低不定,最终稳定低电平
按下后,抖动结束,输出低电平
释放过程中,抖动,输出高低不定,最终稳定高电平
因此,我们根据以下设计原理判断按键是否处于抖动状态:
按下过后开始计时,若在没有达到20ms内出现上升沿,则认为处于抖动
状态划分:
空闲(idle):等待按键按下,按下后跳转至按下消抖状态
按下消抖(p_filter):在20ms内一边计时一边检测上升沿,若检测到上升沿则返回空闲态,如果计满20ms没有出现上升沿,则发出按键按下通知
等待释放(wait_r):一旦出现上升沿,跳转至释放消抖状态
释放消抖(r_filter):在20ms内一边计时一边检测下降沿,若检测到下降沿则返回等待释放,如果计满20ms没有出现下降沿,则发出按键释放通知
程序设计
状态转移图:
源文件:
点击查看代码
module key_filter(
input key,
input clk,
input reset_n,
output reg key_p_flag,
output reg key_r_flag
);
parameter IDLE = 2'b00,P_FILTER = 2'b01,WAIT_R = 2'b10,R_FILTER = 2'b11;
reg [1:0] r_key;
reg [20:0] cnt; // 20_000_000/20ns = 1_000_000
reg [1:0] state;
wire nedge_key;
wire posedge_key;
assign posedge_key = (r_key == 2'b01);
assign nedge_key = (r_key == 2'b10);
always @(posedge clk) begin
r_key <= {r_key[0], key};
end
always @(posedge clk or negedge reset_n) begin
if(!reset_n) begin
state <= IDLE;
key_p_flag <= 0;
key_r_flag <= 0;
cnt <= 0;
end
else begin
case(state)
IDLE: begin
key_r_flag <= 0;
if(nedge_key == 1) begin
state <= P_FILTER;
end
else if(nedge_key) begin
state <= IDLE;
end
end
P_FILTER: begin
if((posedge_key == 1)&&(cnt < 1000000-1)) begin
state <= IDLE;
cnt <= 0;
end
else if((posedge_key == 0)&&(cnt >= 1000000-1)) begin
state <= WAIT_R ;
key_p_flag <= 1;
cnt <= 0;
end
else begin
state <= P_FILTER;
cnt <= cnt + 1;
end
end
WAIT_R: begin
key_p_flag <= 0;
if(posedge_key == 1) begin
state <= R_FILTER;
end
else begin
state <= WAIT_R;
end
end
R_FILTER: begin
if((nedge_key == 1)&&(cnt < 1000000-1)) begin
state <= WAIT_R;
cnt <= 0;
end
else if((nedge_key == 0)&&(cnt >= 1000000-1)) begin
state <= IDLE;
key_r_flag <= 1;
cnt <= 0;
end
else begin
state <= R_FILTER;
cnt <= cnt + 1;
end
end
endcase
end
end
endmodule
点击查看代码
`timescale 1ns/1ps
module key_filter_tb();
reg key ;
reg clk ;
reg reset_n ;
wire key_p_flag ;
wire key_r_flag ;
key_filter u_key_filter(
.key (key) ,
.clk (clk) ,
.reset_n (reset_n) ,
.key_p_flag (key_p_flag) ,
.key_r_flag (key_r_flag)
);
initial clk = 1;
always #10 clk = ~clk;
initial begin
reset_n = 0;
key = 1;
#201
reset_n = 1;
#30000
key = 0;
#20000
key = 1;
#30000
key = 0;
#20000
key = 1;
#30000
key = 0;
#20000
key = 1;
#30000
key = 0;
#50000000
key = 1;
#30000
key = 0;
#20000
key = 1;
#30000
key = 0;
#20000
key = 1;
#50000000
$finish;
end
/*iverilog */
initial
begin
$dumpfile("wave.vcd"); //生成的vcd文件名称
$dumpvars(0, key_filter_tb); //tb模块名称
end
/*iverilog */
endmodule
波形仿真:
最开始按键一直在按下——释放——按下——释放——按下,从state也可以看到状态机状态一直在idle和p_filter之间反复横跳,但key_p_flag始终为低电平
直到稳定了20ms后,key_p_flag才被拉高,产生一个正向脉冲,此时从state也可以看到状态机从p_filter进入到wait_r状态
之后这里也是在模拟按键释放抖动的状态
也是稳定了20ms后,key_r_flag也是被拉高产生一个正向脉冲,状态机也由wait_r返回到idle状态,说明程序设计正确