大致思路有了,如何设计实现呢?貌似这是一个很复杂的设计,实则不然,FSM的本质就是对具有逻辑规律和时序逻辑的事物的描述,采用FSM设计,问题迎刃而解!
1、从状态变量入手,分析状态变量:
IDLE:按键空闲状态(由于上拉电阻的作用,按键未被按下时保持高电平);
FILTER_DOWN:按下滤波状态;
DOWN:按下稳定状态;
FILTER_UP:释放滤波状态;
2、分析状态转移条件,绘制状态转移图(visio)
3、照图施工,选用合适的描述方案
在描述的时候,有两个重要问题需要解决:
1)按键信号属于异步信号,在状态转移中需要对按键边沿敏感,所以首先采用一级D触发器将key_in与clk同步,产生pedge和nedge信号,也就是边沿检测电路,代码如下:
//边沿检测电路
always@(posedge clk)
key_temp <= key_in; //暂存上一个clk按键状态
assign key_nedge = (key_temp)&&(!key_in); //下降沿检测
assign key_pedge = (!key_temp)&&(key_in); //上升沿检测
2)当20ms延时完毕后,应该输出一个脉冲,通知其它模块检测key_flag引脚电平;
完整的verilog描述代码如下:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: key_filter
// Description: //独立按键消抖模块
//////////////////////////////////////////////////////////////////////////////////
module key_filter(
input clk, //50M时钟信号
input rst, //低电平复位
input key_in, //按键输入
output reg key_flag, //消抖完毕输出脉冲
output reg key_state //按键状态输出
);
reg [3:0]NS; //nextstate
reg key_temp;
wire key_pedge;
wire key_nedge;
reg en_cnt;
reg [19:0]cnt; //需要计数次数1_000_000
//边沿检测电路
always@(posedge clk)
key_temp <= key_in; //暂存上一个clk按键状态
assign key_nedge = (key_temp)&&(!key_in); //下降沿检测
assign key_pedge = (!key_temp)&&(key_in); //上升沿检测
//带使能端计数器,用于20ms延时
always@(posedge clk,negedge rst)
if(!rst)
cnt <= 0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 0;
//状态one-hot编码
localparam
IDLE = 4'b0001, //空闲状态
FILTER_DOWN = 4'b0010, //按下消抖状态
DOWN = 4'b0100, //按下稳定状态
FILTER_UP = 4'b1000; //释放消抖状态
//一段式状态机
always@(posedge clk,negedge rst)
if(!rst)begin
NS <= IDLE;
en_cnt <= 0;
key_flag <= 0;
key_state <= 1;
end
else
case(NS)
IDLE:
begin
key_flag <= 0;
key_state <= 1;
if(key_nedge)begin
NS <= FILTER_DOWN;
en_cnt <= 1'b1; //使能计数器
end
else
NS <= IDLE;
end
FILTER_DOWN:
if(cnt >= 20'd999_999)begin
en_cnt <= 0; //20ms时间到,失能计数器,进入稳定状态
key_flag <= 1'b1; //key_flag输出一个clk高脉冲
NS <= DOWN;
end
else if(key_pedge)begin
en_cnt <= 0; //20ms时间内发生上升沿,失能计数器,保持空闲状态
NS <= IDLE;
end
DOWN:
begin
key_flag <= 0;
key_state <= 0;
if(key_pedge)begin
NS <= FILTER_UP;
en_cnt <= 1'b1; //使能计数器
end
else
NS <= DOWN;
end
FILTER_UP:
if(cnt >= 20'd999_999)begin
en_cnt <= 0;
NS <= IDLE; //20ms时间到,失能计数器,进入稳定状态
key_flag <= 1;
end
else if(key_nedge)begin
en_cnt <= 0; //20ms时间内发生上升沿,失能计数器,保持按下稳定状态
NS <= DOWN;
end
default:
NS <= IDLE;
endcase
endmodule