一. 什么是按键消抖
按键消抖通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,如下图所示,为了不产生这种现象而作的措施就是按键消抖。
![](https://picgo-dakang.oss-cn-hangzhou.aliyuncs.com/img/公众号图片水印-Verilog功能模块%20——%20按键消抖-1.png#pic_center)
从按键对应的数字电平来看,按键的抖动过程如下图所示。在抖动期间,按键的输入电平是不稳定的,消抖的目的就是去除抖动的影响,得到能表征按键过程的稳定的按键输入电平。
![](https://picgo-dakang.oss-cn-hangzhou.aliyuncs.com/img/公众号图片水印-Verilog功能模块%20——%20按键消抖-2.png#pic_center)
二. 按键消抖的方案
2.1 硬件消抖
通常的做法是在按键两端并联一个贴片电容,利用电容两端电压不能突变的特性来消除抖动。
![](https://picgo-dakang.oss-cn-hangzhou.aliyuncs.com/img/公众号图片水印-Verilog功能模块%20——%20按键消抖-3.png#pic_center)
2.2 软件消抖
利用按键按下的特点来消抖:
1.按键按下和弹起都会抖动,抖动的持续时间和按键类型和质量有关,一般为5~10ms
2.按键按下后保持稳定电平的时间一般不小于120ms
三. Verilog实现按键消抖
3.1 模块框图
![](https://picgo-dakang.oss-cn-hangzhou.aliyuncs.com/img/公众号图片水印-Verilog功能模块%20——%20按键消抖-4.png#pic_center)
3.2 参数列表 与 接口列表
参数名 | 说明 |
---|---|
KEY_DOWN_OUTPUT_HIGH_LEVEL | 按键按下的输出电平, 默认高电平 |
CLKFREQ | 计时时钟频率, 注意修改 |
INTI_MS | 初始检测未按下电平, 需要持续多少MS才视为检测成功, 默认50ms, 通常无需修改 |
KEEP_MS | 检测到按键按下/松开需要持续多少MS才视为有效, 默认40ms, 通常无需修改 |
接口名 | 说明 |
---|---|
key_in | 按键输入 |
clk | 工作时钟 |
key_down | 消抖后的按键状态 |
key_down_one_time | 按键按下一次 |
key_up_one_time | 按键松开一次 |
3.3 模块IP框图 与 使用说明
![](https://picgo-dakang.oss-cn-hangzhou.aliyuncs.com/img/公众号图片水印-Verilog功能模块%20——%20按键消抖-5.png#pic_center)
![](https://picgo-dakang.oss-cn-hangzhou.aliyuncs.com/img/公众号图片水印-Verilog功能模块%20——%20按键消抖-6.png#pic_center)
使用说明请参照3.2 参数列表与接口列表 以及 3.4 模块源码。
3.4 模块源码
/*
* @Author : Xu Dakang
* @Email : xudaKang_up@qq.com
* @Date : 2021-12-20 15:13:10
* @LastEditors : Xu xiaokang
* @LastEditTime : 2022-04-14 21:03:31
* @Filename :
* @Description :
*/
/*
! 模块功能: 消除按键抖动, 得到正确的按键输入的电平
* 思路:
1.先检测按键未按下时的电平
必须保证在复位完成后的50ms(可通过参数INTI_MS修改)内,按键一直处于未按下的状态,否则未按下电平的检测会出错
2.当按键电平为按下电平时,开始一个40ms(可通过参数KEEP_MS修改)的计数器,计数到最大值则认为按键按下
3.当按键电平为松开电平时,开始一个40ms(可通过参数KEEP_MS修改)的计数器,计数到最大值则认为按键松开
*/
module myKeyEliminateJitter
#(
parameter KEY_DOWN_OUTPUT_HIGH_LEVEL = 1, // 按键按下输出的电平
parameter CLKFREQ = 100, // 计时时钟频率, 注意修改
parameter INTI_MS = 50, // 初始检测未按下电平, 需要持续多少MS才视为检测成功, 默认50ms, 通常无需修改
parameter KEEP_MS = 40 // 检测到按键按下/松开需要持续多少MS才视为有效, 默认40ms, 通常无需修改
)(
output logic key_down, // 按键按下
output logic key_down_one_time, // 按键按下一次
output logic key_up_one_time, // 按键松开一次
input logic key_in, // 按键输入
input logic clk
);
//++ 按键输入同步 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
logic key_in_r1;
logic key;
always_ff @(posedge clk) begin
key_in_r1 <= key_in;
key <= key_in_r1;
end
//-- 按键输入同步 ------------------------------------------------------------
//++ 按键未按下时的电平检测 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
logic key_r1;
always_ff @(posedge clk) begin
key_r1 <= key;
end
logic auto_detection_success;
// 50ms计数器
localparam CNT_50MS_MAX = CLKFREQ * 1000 * INTI_MS;
logic [$clog2(CNT_50MS_MAX+1)-1 : 0] cnt_50ms;
always_ff @(posedge clk) begin
// 按键信号一改变就重新计数, 初始情况下这不应该发生, 只是为了应对电磁干扰或机械振动等极端条件
if (auto_detection_success)
cnt_50ms <= cnt_50ms;
else if (key_r1 != key)
cnt_50ms <= '0;
else if (cnt_50ms < CNT_50MS_MAX)
cnt_50ms <= cnt_50ms + 1'b1;
else
cnt_50ms <= '0;
end
always_comb begin
if (cnt_50ms == CNT_50MS_MAX) // 按键信号从复位开始50ms内输入未发生变化
auto_detection_success = 1'b1;
else
auto_detection_success = 1'b0;
end
logic auto_detection_success_r1;
always_ff @(posedge clk) begin
auto_detection_success_r1 <= auto_detection_success;
end
assign auto_detection_success_pedge = auto_detection_success && ~auto_detection_success_r1;
logic key_up_value; // 存储按键未按下时的引脚电平
always_ff @(posedge clk) begin
if (auto_detection_success_pedge)
key_up_value <= key;
else
key_up_value <= key_up_value;
end
//-- 按键未按下时的电平检测 ------------------------------------------------------------
//++ 按键消抖 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 检测按下电平, 如果按下保持40ms, 则认为键正常按下
localparam CNT_40MS_MAX = CLKFREQ * 1000 * KEEP_MS;
logic [$clog2(CNT_40MS_MAX+1)-1 : 0] key_down_cnt_40ms;
always_ff @(posedge clk) begin
if (auto_detection_success_r1 && key == ~key_up_value) // 按键按下了
if (key_down_cnt_40ms < CNT_40MS_MAX)
key_down_cnt_40ms <= key_down_cnt_40ms + 1'b1;
else
key_down_cnt_40ms <= key_down_cnt_40ms;
else
key_down_cnt_40ms <= '0;
end
// 检测按键松开, 如果松开保持40ms, 则认为键正常松开
logic [$clog2(CNT_40MS_MAX+1)-1 : 0] key_up_cnt_40ms;
always_ff @(posedge clk) begin
if (auto_detection_success_r1 && key == key_up_value) // 按键松开了
if (key_up_cnt_40ms < CNT_40MS_MAX)
key_up_cnt_40ms <= key_up_cnt_40ms + 1'b1;
else
key_up_cnt_40ms <= key_up_cnt_40ms;
else
key_up_cnt_40ms <= '0;
end
always_ff @(posedge clk) begin
if (key_down_cnt_40ms == CNT_40MS_MAX) // 按下计数值保持最大值不变说明键处于按下的状态
key_down <= KEY_DOWN_OUTPUT_HIGH_LEVEL;
else if (key_up_cnt_40ms == CNT_40MS_MAX) // 松开计数值保持最大值不变说明键处于松开的状态
key_down <= ~KEY_DOWN_OUTPUT_HIGH_LEVEL;
else
key_down <= key_down;
end
logic key_down_r1;
always_ff @(posedge clk) begin
key_down_r1 <= key_down;
end
assign key_down_pedge = key_down && ~key_down_r1;
assign key_down_nedge = ~key_down && key_down_r1;
assign key_down_one_time = key_down_pedge;
assign key_up_one_time = key_down_nedge;
//-- 按键消抖 ------------------------------------------------------------
endmodule
3.5 工程分享
Verilog功能模块 —— 按键消抖 Vivado 2021.2工程 基于正点原子ZYNQ领航者V1-20220414。
欢迎大家关注我的公众号:徐晓康的博客,回复以下代码获取。
2352
建议复制过去不会码错字!
![](https://picgo-dakang.oss-cn-hangzhou.aliyuncs.com/img/徐晓康的博客水印.png#pic_center)