状态机实现N位按键消抖
1、原理
利用状态机实现按键的消抖,具体的原理可参考
(50条消息) 基于FPGA的按键消抖_fpga 按键消抖_辣子鸡味的橘子的博客-CSDN博客
状态机简介:
状态机分类可以主要分为两类:moore和mealy
根据三段式状态机最后一段的组合逻辑,根据状态机的输出是否与输出条件有关可以用来区分moore状态机和mealy状态机
若输出只与当前状态机有关,则为moore状态机
always @*
begin
if(current_state == s4) dout = 1;
else dout = 0;
end
Moore状态机仅仅和当前状态有关
Mealy状态机:输出不仅取决于当前状态,还和输入有关;
同样是三段式描述,最后的输出为:
always @(*)
begin
if(reset) dout = 1'b0;
else if( (current_state == s3)&&(din == 1'b1) ) dout = 1'b1;
else dout = 1'b0;
end
可见,输出不仅和当前状态和输入都有关系。
最后,Moore状态机和Mealy状态机可以相互转换。上述两个状态转移图实际上实现的是同一个功能,就是检测序列1101.
状态机按照段式分类,可分为:一段式、二段式、三段式
可参考:
(50条消息) 状态机详解(一段式、二段式、三段式)_状态机一段式二段式三段式_CuteBaBaKiller的博客-CSDN博客
2、代码
module fsm_key_n#(parameter N = 4,parameter TIME_20MS = 1000_000)(
input wire clk,
input wire rst_n,
input wire[N-1:0] key_in,
output wire[N-1:0] key_out
);
reg[3:0] key_out_r;//中间信号
reg[24:0] cnt_20ms;//20ms计数器
//状态空间
parameter IDLE = 4'b0001,
FILTER_DOWN = 4'b0010,
DOWN = 4'b0100,
FILTER_UP = 4'b1000;
reg[3:0] cstate;//现态
reg[3:0] nstate;//次态
reg[N-1:0] key_r0,key_r1,key_r2;//按键延时
reg flag;//检测下降沿和上升沿,寄存
//****************************************************************
//--状态转移条件定义
//****************************************************************
wire idle2filter_down;
wire filter_down2down;
wire down2filter_up;
wire filter_up2idle;
//****************************************************************
//--"计时开始结束条件
//****************************************************************
wire add_cnt_20ms;
wire end_cnt_20ms;
//****************************************************************
//--下降沿上升沿检测
//****************************************************************
assign nedge = |(~key_r1&key_r2);
assign podge = |(key_r1&key_r2);
//****************************************************************
//--状态转移条件约束
//****************************************************************
assign idle2filter_down = nedge && cstate == IDLE;
assign filter_down2down = end_cnt_20ms && cstate == FILTER_DOWN;
assign down2filter_up = podge && cstate == DOWN;
assign filter_up2idle = end_cnt_20ms && cstate == FILTER_UP;
//****************************************************************
//--"信号延时
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
key_r0 <= {N{1'b1}};
key_r1 <= {N{1'b1}};
key_r2 <= {N{1'b1}};
end
else begin
key_r0<=key_in;
key_r1<=key_r0;
key_r2<=key_r1;
end
end
//****************************************************************
//--"flag信号约束
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
flag<=1'b0;
end
else if(nedge || podge)begin
flag<=1;
end
else if(end_cnt_20ms)begin
flag<=0;
end
else begin
flag<=flag;
end
end
//****************************************************************
//--"20ms计数
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
cnt_20ms<='d0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <='d0;
end
else if(nedge)begin
cnt_20ms <= 0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
else begin
cnt_20ms<=cnt_20ms;
end
end
//****************************************************************
//--"20ms计数条件约束
//****************************************************************
assign add_cnt_20ms = flag;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
//****************************************************************
//--"三段式状态机,第一段,时序逻辑
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cstate<=IDLE;//初始当前状态为空闲
end
else begin
cstate<=nstate;//次态赋值给现态
end
end
//****************************************************************
//--"三段式状态机,第二段,组合逻辑,状态转移
//****************************************************************
always @(*) begin
case(cstate)
IDLE:begin
if(idle2filter_down)begin
nstate = FILTER_DOWN;
end
else begin
nstate = cstate;
end
end
FILTER_DOWN:
begin
if(filter_down2down)begin
nstate = DOWN;
end
else begin
nstate = cstate;
end
end
DOWN:begin
if(down2filter_up)begin
nstate = FILTER_UP;
end
else begin
nstate = cstate;
end
end
FILTER_UP:begin
if(filter_up2idle)begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default:
nstate = cstate;
endcase
end
//****************************************************************
//--"有限状态机,第三段,时序逻辑
//****************************************************************
always @(posedge clk or negedge rst_n) begin
if(~rst_n) begin
key_out_r<={N{1'b0}};
end
else if(filter_down2down)begin
key_out_r<=~key_r1;
end
else begin
key_out_r<={N{1'b0}};
end
end
assign key_out = key_out_r;
endmodule
3、仿真代码
`timescale 1ns/1ns
module fsm_key_tb();
reg clk;
reg rst_n;
reg[3:0] key;
reg[3:0] delay;
wire[3:0] key_r;
parameter SYS_CLK = 20;
parameter TIME_20MS = 10;
parameter N = 4;
always #(SYS_CLK/2) clk = ~clk;
task task_init;
begin
clk=1'b0;
rst_n=1'b0;
#(2*SYS_CLK);
rst_n=1'b1;
key = 4'b1111;
#(2*SYS_CLK);
end
endtask
task task_key;
input[3:0] key_in;
output[3:0] key_out;
begin
key_out[0] = ~key_in[0];
key_out[2] = ~key_in[2];
key_out[3] = key_in[3];
key_out[1] = key_in[1];
end
endtask
initial begin
task_init();
repeat(10)begin
repeat (20) begin
task_key(key,key);
// key[0] = ~key[0];
// key[2] = ~key[2];
delay = {$random()}%4;
#(SYS_CLK*delay);
end
task_key(key,key);
// key[0] = ~key[0];
// key[2] = ~key[2];
//wait(inst_fsm_key.end_cnt_20ms);
#(30*SYS_CLK);
end
$stop;
end
fsm_key_n #(
.N(N),
.TIME_20MS(TIME_20MS)
) inst_fsm_key (
.clk (clk),
.rst_n (rst_n),
.key_in (key),
.key_out (key_r)
);
endmodule
4、仿真结果
5、总结
使用状态机进行按键消抖,可以经消抖分为四个部分,空闲状态、下降沿状态、按下状态、上升沿状态,这几个状态使用状态机进行按键消抖,可以更好的理解消抖的原理和过程。状态机的规范编写也是提升自己理解时序,理解逻辑的好的方式