一,理论概念
- 按键抖动
按键抖动:按键抖动通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。当按下一次按键,可能在A点检测到一次低电平,在B点检测到一次高电平,在C点又检测到一次低电平。同时抖动是随机,不可测的。那么按下一次按键,抖动可能会误以为按下多次按键。 - 按键消抖的目的
消除抖动对程序的影响 - 解决方案一
延迟采样:一般的抖动在20ms之内,所以我们可以通过检测下降沿后,延迟20ms再进行采样 - 解决方案二
信号平稳变化后延迟20ms后再进行采样,等到检测最后一个下降沿结束后再开始采样
二,项目代码
这里简单做了一个按键控制led用来简单检测按键是否消抖成功
- 按键消抖模块
第一个模块是不适用状态机来实现的,只需要考虑按键按下时的抖动,不考虑按键弹起时的抖动
module key_debounce (
input wire clk ,
input wire rst_n ,
input wire [3:0] key_in ,
output wire [3:0] key_out
);
parameter MAX20ms = 20'd999_999;
wire add_cnt;//倒计时开始使能
wire end_cnt;//倒计时结束使能
reg [19:0] cnt_20ms;//20ms计数寄存器
reg [3:0] key_r0;//同步
reg [3:0] key_r1;//打拍
reg start;//下降沿检测寄存器
reg [3:0] flag;
reg [3:0] key_out_r;//输出按键信号寄存器
wire nedge;
//下降沿检测
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_r0 <= 4'b1111;
key_r1 <= 4'b1111;
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
end
end
assign nedge = (~key_r0[0]&key_r1[0])||(~key_r0[1]&key_r1[1])||(~key_r0[2]&key_r1[2])||(~key_r0[3]&key_r1[3]);
//20ms计时器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_20ms <= 20'd0;
end
else if (nedge) begin
cnt_20ms <= 20'd0;
end
else if (add_cnt) begin
if (end_cnt) begin
cnt_20ms <= 20'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
else begin
cnt_20ms <= 20'd0;
end
end
assign add_cnt = start;
assign end_cnt = add_cnt && (cnt_20ms == MAX20ms);
//约束start
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
start <= 1'b0;
end
else if (nedge) begin
start <= 1'b1;
end
else if (end_cnt) begin
start <= 1'b0;
end
else begin
start <= start ;
end
end
//约束flag
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
flag <= 4'b1111;
end
else if (nedge) begin
flag <= 4'b1111;
end
else if (end_cnt) begin
flag <= key_r0;
end
else begin
flag <= 4'b1111 ;
end
end
// //脉冲信号
// always @(posedge clk or negedge rst_n) begin
// if (!rst_n) begin
// key_out_r <= 4'b1111;
// end
// else if (!flag[0]) begin
// key_out_r <= 4'b1110;
// end
// else if (!flag[1]) begin
// key_out_r <= 4'b1101;
// end
// else if (!flag[2]) begin
// key_out_r <= 4'b1011;
// end
// else if (!flag[3]) begin
// key_out_r <= 4'b0111;
// end
// else begin
// key_out_r <= 4'b1111;
// end
// end
//持续信号
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_out_r <= 4'b1111;
end
else if (!flag[0]) begin
key_out_r <= 4'b1110;
end
else if (!flag[1]) begin
key_out_r <= 4'b1101;
end
else if (!flag[2]) begin
key_out_r <= 4'b1011;
end
else if (!flag[3]) begin
key_out_r <= 4'b0111;
end
else begin
key_out_r <= key_out_r;
end
end
assign key_out = key_out_r;
endmodule
这里可以通过控制key_out_r的状态来达到是脉冲还是持续信号
第二个是使用状态机来实现消抖,这个方法需要完整的考虑整个按键按下和弹起的全过程
module fsm_key_debounce # (parameter KEY_W = 4,TIME_20MS = 1_000_000)(
input clk ,
input rst_n ,
input [KEY_W - 1:0] key_in ,
output [KEY_W - 1:0] key_out
);
//参数定义
localparam IDLE = 4'b0001;//初始状态
localparam DOWN = 4'b0010;//按键按下抖动
localparam HOLD = 4'b0100;//按键按下后稳定
localparam UP = 4'b1000;//按键上升抖动
//信号定义
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态
//状态转移条件定义
wire idle2down;
wire down2idle;
wire down2hold;
wire hold2up ;
wire up2idle ;
reg [KEY_W - 1:0] key_r0;//同步
reg [KEY_W - 1:0] key_r1;//打拍
wire [KEY_W - 1:0] nedge;//下降沿
wire [KEY_W - 1:0] pedge;//上升沿
//20ms计数器
reg [19:0] cnt_20ms;
wire add_cnt_20ms;
wire end_cnt_20ms;
reg [KEY_W - 1:0] key_out_r;//输出寄存
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)begin
state_n = DOWN;
end
else begin
state_n = state_c;
end
end
DOWN:begin
if(down2idle)begin
state_n = IDLE;
end
else if(down2hold)begin
state_n = HOLD;
end
else begin
state_n = state_c;
end
end
HOLD:begin
if(hold2up)begin
state_n = UP;
end
else begin
state_n = state_c;
end
end
UP:begin
if(up2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default:state_n = state_c;
endcase
end
assign idle2down = (state_c == IDLE) && nedge;//检测到下降沿
assign down2idle = (state_c == DOWN) && (pedge&& end_cnt_20ms);//计时未到20ms时且出现上升沿表示按键意外抖动,回到初始态
assign down2hold = (state_c == DOWN) && (~pedge && end_cnt_20ms);//计时到20ms时没有出现上升沿标志按键按下后保持稳定
assign hold2up = (state_c == HOLD) && (pedge);//检测到上升沿跳转到上升态
assign up2idle = (state_c == UP) && end_cnt_20ms;//计数器计数到20ms跳转到初始态
//20ms计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = state_c == DOWN || state_c == UP;//当按键按下或上弹时开始计数
assign end_cnt_20ms = add_cnt_20ms && ((cnt_20ms == TIME_20MS - 1) || pedge);//当计数到最大值或检测到上升沿计数器清零
//同步打拍
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {KEY_W{1'b1}};
key_r1 <= {KEY_W{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
end
end
assign nedge = ~key_r0 & key_r1;//检测下降沿
assign pedge = key_r0 & ~key_r1;//检测上升沿
//按键赋值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out_r <= {KEY_W{1'b1}};
end
else if(state_c == HOLD && hold2up)begin
key_out_r <= key_r1;
end
else begin
key_out_r <= {KEY_W{1'b1}};
end
end
assign key_out = key_out_r;
endmodule
- led灯
这里只是写了一个小的测试led不过多陈述
module fsm_key_debounce # (parameter KEY_W = 4,TIME_20MS = 1_000_000)(
input clk ,
input rst_n ,
input [KEY_W - 1:0] key_in ,
output [KEY_W - 1:0] key_out
);
//参数定义
localparam IDLE = 4'b0001;//初始状态
localparam DOWN = 4'b0010;//按键按下抖动
localparam HOLD = 4'b0100;//按键按下后稳定
localparam UP = 4'b1000;//按键上升抖动
//信号定义
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态
//状态转移条件定义
wire idle2down;
wire down2idle;
wire down2hold;
wire hold2up ;
wire up2idle ;
reg [KEY_W - 1:0] key_r0;//同步
reg [KEY_W - 1:0] key_r1;//打拍
wire [KEY_W - 1:0] nedge;//下降沿
wire [KEY_W - 1:0] pedge;//上升沿
//20ms计数器
reg [19:0] cnt_20ms;
wire add_cnt_20ms;
wire end_cnt_20ms;
reg [KEY_W - 1:0] key_out_r;//输出寄存
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)begin
state_n = DOWN;
end
else begin
state_n = state_c;
end
end
DOWN:begin
if(down2idle)begin
state_n = IDLE;
end
else if(down2hold)begin
state_n = HOLD;
end
else begin
state_n = state_c;
end
end
HOLD:begin
if(hold2up)begin
state_n = UP;
end
else begin
state_n = state_c;
end
end
UP:begin
if(up2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default:state_n = state_c;
endcase
end
assign idle2down = (state_c == IDLE) && nedge;//检测到下降沿
assign down2idle = (state_c == DOWN) && (pedge&& end_cnt_20ms);//计时未到20ms时且出现上升沿表示按键意外抖动,回到初始态
assign down2hold = (state_c == DOWN) && (~pedge && end_cnt_20ms);//计时到20ms时没有出现上升沿标志按键按下后保持稳定
assign hold2up = (state_c == HOLD) && (pedge);//检测到上升沿跳转到上升态
assign up2idle = (state_c == UP) && end_cnt_20ms;//计数器计数到20ms跳转到初始态
//20ms计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = state_c == DOWN || state_c == UP;//当按键按下或上弹时开始计数
assign end_cnt_20ms = add_cnt_20ms && ((cnt_20ms == TIME_20MS - 1) || pedge);//当计数到最大值或检测到上升沿计数器清零
//同步打拍
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {KEY_W{1'b1}};
key_r1 <= {KEY_W{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
end
end
assign nedge = ~key_r0 & key_r1;//检测下降沿
assign pedge = key_r0 & ~key_r1;//检测上升沿
//按键赋值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out_r <= {KEY_W{1'b1}};
end
else if(state_c == HOLD && hold2up)begin
key_out_r <= key_r1;
end
else begin
key_out_r <= {KEY_W{1'b1}};
end
end
assign key_out = key_out_r;
endmodule
- 顶层
module led_test_top (
input clk ,//全局时钟
input rst_n ,//复位
input wire [3:0] key ,//2个按键
output wire [3:0] led //四个led
);
wire [3:0] key_out;
key_debounce key_debounce_inst(
.clk (clk),
.rst_n (rst_n),
.key_in (key),
.key_out(key_out)
);
led_test led_test_inst(
.clk (clk),//全局时钟
.rst_n (rst_n),//复位
.key (key_out),//2个按键
.led (led) //四个led
);
endmodule
- 测试文件
`timescale 1ns/1ns
module led_tb ();
reg clk ;
reg rst_n ;
reg [3:0] key ;
wire [3:0] led ;
parameter CYCLE = 20 ;
defparam top_inst.key_debounce_inst.MAX_20ms = 5;
always #(CYCLE/2) clk = ~clk ;
initial begin
clk = 1'b0;
rst_n = 1'b0;
key = 4'b1111;
#10
rst_n = 1'b1;
#10
key = 4'b1110;
wait(top_inst.key_debounce_inst.end_cnt)
#(CYCLE*10);
key = 4'b1111;
#(CYCLE*10)
key = 4'b1110;
wait(top_inst.key_debounce_inst.end_cnt)
#(CYCLE*10);
key = 4'b1111;
#(CYCLE*10)
key = 4'b1110;
wait(top_inst.key_debounce_inst.end_cnt)
#(CYCLE*10);
key = 4'b1111;
#(CYCLE*10)
key = 4'b1110;
wait(top_inst.key_debounce_inst.end_cnt)
#(CYCLE*10);
key = 4'b1111;
#(CYCLE*10)
key = 4'b1101;
wait(top_inst.key_debounce_inst.end_cnt)
#(CYCLE*10);
key = 4'b1111;
#(CYCLE*10)
key = 4'b0111;
wait(top_inst.key_debounce_inst.end_cnt)
#(CYCLE*10);
key = 4'b1111;
#(CYCLE*10)
$stop;
end
led_test_top top_inst(
.clk (clk),
.rst_n (rst_n),
.key (key),
.led (led)
);
endmodule
三,测试波形
四,视频演示
目前无法拍摄视频后续补上