状态图
首先看这样一个按键消抖的时序图:
![](https://img-blog.csdnimg.cn/img_convert/994b3a7d661a28c154d05357de65ee3a.png)
粗略画了一下按键时抖动(20ms)的情况,最初为高电平,按键按下变成低电平有一个抖动的过程,同样由低到高有抖动,按键消抖就是在时序图上反应滤除抖动的结果。
可以将其分成几个状态,并分别用独热码表示:
1,5,7:4'b0001,IDLE保持高电平;
2,6 :4'b0010,按下/由高电平作为开始的抖动;
3 :4'b0100,保持低电平;
4 :4'b1000,释放时出现的抖动.
由此可画出状态图:
![](https://img-blog.csdnimg.cn/img_convert/db21455ac85df697240712fba1c40a55.png)
开始为空闲状态IDLE(高电平),当按键按下,经过滤除抖动进入低电平状态DOWN(形容从高到低的一个趋势,即2,6状态,并不是指低电平),然后保持低电平状态HOLD,最后释放按键滤除抖动进入UP状态(由低到高的趋势,即4状态)。另外,由于7状态是直接经过抖动滤除后变成的高电平(未经过低电平保持状态),所以在经过DOWN状态时需要进行判断,判断其是否会有低电平的保持态,若检测到低电平则进入HOLD状态,若未检测到低电平而是上升沿pos,则回到高电平状态IDLE。
状态机自带两个信号,分别是state_c,state_n,即现态和次态。
时序图
画出时序图
分别进行上升沿和下降沿检测:
上升沿检测:r0为高电平,r1为低电平时,检测测到上升沿
下降沿检测:r0为低电平,r1为高电平时,检测到下降沿
(1)开头的时序图,首先检测到了下降沿,状态机次态state_n由起始的空闲状态0001变为DOWN状态(由状态1到状态3要经历状态2),即有下降沿的状态,并且次态是用组合逻辑描述的,只要条件满足就立即变化,所以次态变为0010.此时的现态state_c需要更新,由于现态用时序逻辑描述,因此,现态在下一个时钟上升沿进行变化,更新到0010.
![](https://img-blog.csdnimg.cn/img_convert/195bebd4783ca52f824cf1c52ca41cf9.png)
(2)计数器从检测到第一个下降沿开始计数,有20ms的抖动,计数到20ms变为低电平(HOLD状态),次态变为0100,现态在下一个时钟周期上升沿变为0100。
![](https://img-blog.csdnimg.cn/img_convert/86db81d4a536156262033e39df696976.png)
(3)保持低电平状态(HOLD)直到上升沿(UP)出现则由转为高电平状态,即IDLE(0001),计数器在上升沿时开始计抖动延时20ms,结束后次态变为0001,现态在下一个时钟周期变为0001.
![](https://img-blog.csdnimg.cn/img_convert/b9bb421afcd77fbe3ad9079cf360fd48.png)
综上,总体时序图绘制如下:
![](https://img-blog.csdnimg.cn/img_convert/8303d7ecb8f6c3a1c4fe4261240453e7.png)
代码
顶层key_fliter
module key_fliter (
input clk ,
input rst_n ,
input key_in ,
output reg key_out //时序输出减少毛刺
);
//参数定义
parameter DELAY = 1000_000;//20ms
//状态机参数
parameter IDLE = 4'b0001 ,//空闲状态,保持高电平
DOWN = 4'b0010 ,//按键按下抖动状态
HOLD = 4'b0100 ,//保持状态 保持低电平状态
F_UP = 4'b1000 ;//按键释放抖动状态
//信号定义
//状态机信号
reg [3:0] state_c ;//现态
reg [3:0] state_n ;//次态
reg key_r0 ;//按键同步
reg key_r1 ;//打拍
wire nedge ;//下降沿检测
wire pegde ;//上升沿检测
reg [19:0] cnt ;//延时计数器
wire add_cnt ;
wire end_cnt ;
wire idle2down ;
wire down2hold ;
wire down2idle ;
wire hold2f_up ;
wire f_up2idle ;
//第一段 时序逻辑 状态的转移
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state_c <= IDLE ;
end
else begin
state_c <= state_n;
end
end
//第二段 组合逻辑 描述状态转移的规律
always @(*) begin
case (state_c)
IDLE: begin
if(idle2down) //如果从idle状态到down状态,这里_2_理解为像个状态转换的过程
state_n = DOWN ;//则将down状态赋给次态
else
state_n = state_c;//否则将现态赋给次态
end
DOWN: begin
if(down2hold)
state_n = HOLD ;
else if(down2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
HOLD: begin
if(hold2f_up)
state_n = F_UP ;
else
state_n = state_c ;
end
F_UP: begin
if(f_up2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default: state_n = IDLE;
endcase
end
//状态之间的条件(状态之间由多少箭头就有多少条件)
assign idle2down = state_c == IDLE && (nedge);//出现dile转为down的条件是出现下降沿
assign down2hold = state_c == DOWN && (end_cnt && (key_r1 == 1'b0)) ;//由down转为保持前提是在下降沿消除抖动(20ms)后变得平稳的输出,观察时序图r0和r1的情况
assign down2idle = state_c == DOWN && (end_cnt && (key_r1 == 1'b1)) ;//由down转为idle也需要消除抖动,并且观察时序图此时的r1为高电平
assign hold2f_up = state_c == HOLD && (pegde) ; //释放抖动
assign f_up2idle = state_c == F_UP && (end_cnt);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_r0 <= 1'b1 ;
key_r1 <= 1'b1 ;
end
else begin
key_r0 <= key_in ;//同步
key_r1 <= key_r0 ;//打拍
end
end
assign nedge = key_r1 && ~key_r0 ; //下降沿r0为低电平,r1为高电平
assign pegde = ~key_r1 && key_r0 ; //上升沿r0为高电平,r1低电平
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 'd0;
end
else if (add_cnt) begin
if (end_cnt) begin
cnt <= 'd0 ;
end
else begin
cnt <= cnt + 1'b1;
end
end
end
assign add_cnt = (state_c == DOWN) || (state_c == F_UP) ;
assign end_cnt = add_cnt && (cnt == DELAY - 1);
// key_out时序输出减少毛刺
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_out <= 'd0;
end
else if (down2hold) begin
key_out <= 1'b1;
end
else begin
key_out <= 1'b0;
end
end
endmodule
注意,key_out输出的0或者1表示按键是否按下的标志,所以最终的输出只有在输入为电平时期输出才有有一个拉高的标志,表示按键按下,而后当输入高电平时,输出为低电平表示按键未按下。
rtl视图:
![](https://img-blog.csdnimg.cn/img_convert/6597050c42d0c2749cc6d04cda0f7281.png)
分析状态机的正确性:
![](https://img-blog.csdnimg.cn/img_convert/b672633a3e630ba57be92dbe0fb36d6c.png)
仿真
测试文件
`timescale 1ns/1ps
module key_fliter_tb();
reg clk ;
reg rst_n ;
reg key ;
wire key_out ;
//参数
parameter CYCLE = 20 ;//50M时钟
defparam u_key_fliter.DELAY = 20 ;
//模块例化
key_fliter u_key_fliter(
.clk (clk ),
.rst_n (rst_n ),
.key_in (key ),
.key_out (key_out)
);
//激励
initial begin //产生时钟
clk = 1'b1 ;
forever begin
#(CYCLE/2);
clk = ~clk ;
end
end
initial begin
rst_n = 1'b1 ;
#(CYCLE);
rst_n = 1'b0 ;//上电复位
key = 1'b1 ;
#(CYCLE*2);
#2;
rst_n = 1'b1 ;
#(CYCLE*4);
//模拟顶层文件按键信号(key_in)输入
//模拟按键按下
key = 1'b0 ;
#(CYCLE*30);
key = 1'b1 ;
#(CYCLE*5);//加延时避免出现毛刺
//模拟按键出现抖动
key = 1'b0 ;
#(CYCLE*17);//需要小于DELYA
key = 1'b1 ;
#(CYCLE*10);
$stop;
end
endmodule
仿真图:
![](https://img-blog.csdnimg.cn/img_convert/0cc2cfafa818f80946f196730d33a086.png)