14-按键消抖
1.按键消抖
按键是最常见的电子元器件之一,在电子设计中应用广泛;在日常生活中,遥控器\玩具\计算器等等电子产品都要使用按键
- 在FPGA的实验工程中,我们可以使用其作为系统复位信号或者控制信号的外部输入
- 常见的按键:自锁式按键或者是机械按键
按键消抖主要针对的是机械弹性开关,当机械触点断开\闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定接通,在断开时也不会一下子断开,因而在闭合及断开的瞬间伴随有一连串的抖动,为了保证系统能正确识别按键的开关,就必须对按键的抖动进行处理,就是按键消抖
- 硬件消抖:使用RS触发器(按键数量少),消耗空间和资源
- 软件消抖:使用延时程序去除抖动的部分
2.FPGA设计
2.1 框图和波形
- 软件消抖:使用延时程序去除掉抖动的部分
- 在按键按下后能够精准的识别到一次按键按下,所以需要计数器,所以要设计时序电路,输入有时钟信号和复位信号
- 输入未处理的按键信号key_in,输出消抖之后稳定的按键信号key_flag
- 前抖动和后抖动的时间大概是5s - 10ms,按键按下的稳定时间大概是20mm
- 可以使用计数器消除防抖,当key_in是低电平的时候开始计数,当key_in为高电平的时候清0,按键按下的有效时间是20ms,需要计数1000_0000,最大计数值是999_999,在计数20ms内没有识别到高电平,就表示按键按下有效,注意要在时钟的上升沿采样key_in信号
- 采用传统计数器的方式,当计数到最大值的时候计数器进行清0,输出信号在最大值的时候产生一个脉冲信号,如果信号稳定时间超过20ms,就会进行多次清0和计数,也会产生多个脉冲信号,分析这种情况是计数器清零条件导致的还是key_flag信号拉高时间导致的?如果key_flag不在最大值拉高,在其他位置拉高,也会出现这种问题,所以问题是清0条件导致的
- 这时采用当计数器计数到最大值之后,如果超过计数器最大值,计数器不清0,一直维持计数器的最大值,当key_in为高电平的时候,计数器才清0,此时key_flag信号可以在计数器产生最大值减一之后产生一个周期的脉冲信号然后拉低为0,这样key_flag信号不会产生多次
- 修改清0条件之后,key_flag信号虽然不会出现多次,但是不是脉冲信号,而是一个长时间的电平信号,此时想到,在计数到计数器最大值减1的时候,key_flag产生一个脉冲信号
2.2 RTL
module key_filter
#(
parameter CNT_MAX = 20'd999_999
)
(
input wire sys_clk,
input wire sys_rst_n,
input wire key_in,
output reg key_flag
);
reg [19:0] cnt_20ms;
// 计数器赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'd0;
else if(key_in == 1'b1)
cnt_20ms <= 20'd0;
else if(cnt_20ms == CNT_MAX)
cnt_20ms <= CNT_MAX;
else
cnt_20ms <= cnt_20ms + 20'd1;
// 输出信号赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX-20'd1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
endmodule
2.3 Testbench
`timescale 1ns/1ns
module tb_key_filter();
reg sys_clk;
reg sys_rst_n;
reg key_in;
reg [7:0] tb_cnt; // 用于模拟按键的计数
wire key_flag;
// 产生时钟
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20;
sys_rst_n <= 1'b1;
end
// 模拟按键输入信号
// 高电平-->前抖动-->低电平-->后抖动-->高电平
// 维持时间要用计数器
// 计数器计数次数:250次,8bit
//计数器赋值
always(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n)
tb_cnt <= 8'd0;
else if(tb_cnt == 8'd249)
tb_cnt <= 8'd0;
else
tb_cnt <= tb_cnt + 8'd1;
// 用计数器模拟按键产生
// 一次按键按下的总时间使用计数器进行计算
// 一次操作中间的部分可以用时间进行替代
always(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n)
key_in <= 1'b1;
else if (((tb_cnt >= 8'd19) && (tb_cnt <= 8'd49))||((tb_cnt >= 8'd169) && (tb_cnt <= 8'd249)))
key_in <= {$random} % 2;
else if((tb_cnt <= 8'd19) || (tb_cnt <= 8'd199) )
key_in <= 1'b1;
else
key_in <=1'b0;
// 例化模块
key_filter
#(
.CNT_MAX (20'd24)
)
key_filter_inst
(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.key_in (key_in),
.key_flag (key_flag)
)
endmodule
- 通过计数器控制变量的时间,先给计数器进行赋值,然后根据计数器计数到不同的数值(达到不同的时间),驱动其他信号做其他的事情