一、按键原理
硬件原理图
- 当按键KEY1按下时,D3V3(也就是电源)通过电阻R(原理图上折线的那一段)然后再通过按键KEY1最终进入GND形成一条通路,那么这条线路的全部电压都加到了R这个电阻上,KEY1(最左边四个IO口)这个引脚就是个低电平。
- 当松开按键后,线路断开,就不会有电流通过,那么KEY1和D3V3就应该是等电位,是一个高电平。我们就可以通过KEY1这个IO口的高低电平来判断是否有按键按下。
二、按键消抖
2.1 原理
按键抖动通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。当按下一次按键,可能在A点检测到一次低电平,在B点检测到一次高电平,在C点又检测到一次低电平。同时抖动是随机,不可测的。那么按下一次按键,抖动可能会误以为按下多次按键。
所以我们在使用按键时往往需要消抖,以确保按键被按下一次只检测到一次低电平
2.2 解决方法
延迟采样
2.2.1 设计思路
通常我们认为按键的抖动时间存在于20ms之间,因此可以设计在第一次检测到有下降沿时开始计时,到计满20ms后,我们认为此时的按键信号保持稳定,则输出一个脉冲信号表示按键确实按下。
首先需要一个模块来检测按键是否抖动,如果抖动,→ 给计时模块一个标志位开始计时,记满20ms,→ 再给输出消抖后按键信号模块一个标志位进行采样。
2.2.2 时序图分析
- clock:我们的整个程序都是在时钟的控制下运行的,所有的always模块都对时钟的上升沿敏感,本人使用的开发板时钟频率是50MHz,也就是一秒震动50_000_000次,一个周期就是20ns
- key_in:这是按键输入信号,低电平有效
- key_r0:同步打拍后的信号,为了滤除掉小于一个周期的抖动,对key_in推迟一个周期进行同步
- key_r1 :这个信号是把同步信号再延时一个周期,主要是为了保存key_r0上一个周期的值(打一拍),来判断是否出现下降沿
- key_r2:与key_r1同理,再延时一个时钟周期
- nedge:检测key_r0是否出现下降沿,若出现,则将标志位flag设为1,也是计时器开启的条件
- add_cnt:计时器开启条件,用flag表示
- end_cnt:计时器结束条件,记满20ms,也是采样模块的开启条件
- key_down:计时器记满,则开始采样,key_down拉高一个周期(脉冲信号),代表按键按下
2.2.3 代码设计
key_debounce.v
/**************************************功能介绍***********************************
Date : 2023年7月27日11:07:19
Author : Ruminating.
Version : 1.1
Description: 按键消抖模块(任意位宽实现)
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module key_debounce #(parameter WIDTH = 4,//WIDTH为按键位宽(以4位宽为例)
TIME_20MS = 20'd1000_000//20ms计数器
)(
input wire clk ,//时钟信号
input wire rst_n ,//复位信号
input wire [WIDTH-1:0] key_in ,//输入按键信号
output wire [WIDTH-1:0] key_out //输出脉冲信号
);
//---------<参数定义>---------------------------------------------------------
// parameter TIME_20MS = 1000_000;//20ms
//---------<内部信号定义>-----------------------------------------------------
reg [WIDTH-1:0] key_down;//输出信号寄存器
reg [WIDTH-1:0] key_r0 ;//同步打拍
reg [WIDTH-1:0] key_r1 ;//打一拍
reg [WIDTH-1:0] key_r2 ;//打两拍
wire [WIDTH-1:0] n_edge ;//下降沿
reg flag ;//计数器计数的标志信号(按键按下抖动标志)
reg [19:0] cnt_20ms ;//20ms计数器
wire add_cnt_20ms;//开始计数条件
wire end_cnt_20ms;//结束计数条件
//****************************************************************
//--同步打拍
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {WIDTH{1'b1}};
key_r1 <= {WIDTH{1'b1}};
key_r2 <= {WIDTH{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
key_r2 <= key_r1;
end
end
//****************************************************************
//--n_edge
//****************************************************************
assign n_edge = ~key_r1 & key_r2;//下降沿检测
// assign p_edge = key_r1 & ~key_r2;//上升沿检测
//****************************************************************
//--cnt_20ms计数器
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 20'd0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 20'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = flag;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
//****************************************************************
//--flag(检测到下降沿时开始计数,直到计满20ms后,停止计数)
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 1'b0;
end
else if(n_edge)begin
flag <= 1'b1;
end
else if(end_cnt_20ms)begin
flag <= 1'b0;
end
end
//****************************************************************
//--key_down(输出脉冲信号)
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_down <= 4'b0000;
end
else if(end_cnt_20ms)begin
key_down <= ~key_r2;
end
else begin
key_down <= 4'b0000;
end
end
//****************************************************************
//--key_out
//****************************************************************
assign key_out = key_down;
endmodule
状态机实现:
将按键的按下分为以下过程
增加了对上升沿的检测,因此可以选择在按键稳定按下或者在按键释放后生成脉冲信号
fsm_key_debounce.v
/**************************************功能介绍***********************************
Date : 2023年7月27日11:07:19
Author : Ruminating.
Version : 1.1
Description: 按键消抖模块状态机实现(任意位宽实现)
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module fsm_key_debounce #(parameter WIDTH = 4,//WIDTH为按键位宽
TIME_20MS = 20'd1000_000//20ms计数器
)(
input wire clk ,//时钟信号
input wire rst_n ,//复位信号
input wire [WIDTH-1:0] key_in ,//输入按键信号
output wire [WIDTH-1:0] key_out //输出脉冲信号
);
//---------<参数定义>---------------------------------------------------------
// parameter TIME_20MS = 1000_000;//20ms
//---------<内部信号定义>-----------------------------------------------------
//状态机参数定义
localparam IDLE = 4'b0001,//按键空闲状态
FILTER_DOWN = 4'b0010,//按键按下状态
HOLD_DOWN = 4'b0100,//按键稳定状态
FILTER_UP = 4'b1000;//按键释放状态
reg [WIDTH-1:0] state_c ;//现态
reg [WIDTH-1:0] state_n ;//次态
reg [WIDTH-1:0] key_down;//输出信号寄存器
reg [WIDTH-1:0] key_r0 ;//同步打拍
reg [WIDTH-1:0] key_r1 ;//打一拍
reg [WIDTH-1:0] key_r2 ;//打两拍
wire [WIDTH-1:0] n_edge ;//下降沿
wire [WIDTH-1:0] p_edge ;//上升沿
reg [19:0] cnt_20ms ;//20ms计数器
wire add_cnt_20ms;//开始计时条件
wire end_cnt_20ms;//结束计时条件
wire idle2filter_down ;
wire filter_down2hold_down ;
wire hold_down2filter_up ;
wire filter_up2idle ;//状态转换条件
//****************************************************************
//--同步打拍
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {WIDTH{1'b1}};
key_r1 <= {WIDTH{1'b1}};
key_r2 <= {WIDTH{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
key_r2 <= key_r1;
end
end
//****************************************************************
//--n_edge 和 p_edge
//****************************************************************
assign n_edge = ~key_r1 & key_r2;//下降沿检测
assign p_edge = key_r1 & ~key_r2;//上升沿检测
//****************************************************************
//--cnt_20ms
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 20'd0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 20'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = state_c == FILTER_DOWN || state_c == FILTER_UP;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
//****************************************************************
//--状态机实现
//****************************************************************
//第一段:时序逻辑描述状态转移
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(idle2filter_down)begin
state_n = FILTER_DOWN;
end
else begin
state_n = state_c;
end
end
FILTER_DOWN : begin
if(filter_down2hold_down)begin
state_n = HOLD_DOWN;
end
else begin
state_n = state_c;
end
end
HOLD_DOWN : begin
if(hold_down2filter_up)begin
state_n = FILTER_UP;
end
else begin
state_n = state_c;
end
end
FILTER_UP : begin
if(filter_up2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default:state_n = IDLE ;
endcase
end
assign idle2filter_down = state_c == IDLE && n_edge;
assign filter_down2hold_down = state_c == FILTER_DOWN && end_cnt_20ms;
assign hold_down2filter_up = state_c == HOLD_DOWN && p_edge;
assign filter_up2idle = state_c == FILTER_UP && end_cnt_20ms;
//第三段:描述输出,时序逻辑或组合逻辑皆可
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
key_down <= 4'b0000;
end
else if(filter_down2hold_down)begin
key_down <= ~key_r2;
end
else begin
key_down <= 4'b0000;
end
end
assign key_out = key_down;
endmodule
2.2.4 代码分析
1.下降沿或上升沿的检测
assign n_edge = ~key_r1 & key_r2;//下降沿检测
//assign p_edge = key_r1 & ~key_r2;//上升沿检测
2.脉冲信号生成
key_down <= ~key_r2;
- 生成脉冲信号时的赋值没有直接取1而是取key_r2信号的反能确保此脉冲是在按键稳定后生成(也可取~key_r1,只相差一个时钟周期差距不大)
三、总结
此按键消抖模块可以对任意位数的按键信号进行消抖,实现了按键消抖模块并使其参数化,在调用该模块时只需将参数WIDTH的值赋于自己所需要的位宽即可。