重新补充一下,
这里的消抖,可以看作开关的消抖,开关闭合和释放都要进行消抖处理,并且中间会保持一段时间。
而按键的消抖,按下释放是连着的,这一个过程会发出一个脉冲信号,证明按键按下过
1. 什么是按键抖动
实际的拨动开关和按键都是机械式的设备,在状态改变时可能会出现来回抖动多次导致状态不稳定的情况,通常抖动的持续时间不超过20ms。消抖电路的目的就是滤去这段时间内的抖动信号。
2. 消抖电路设计思路
-
使用有限状态机的思想,当输入信号稳定20ms后才改变抖动以后的输出值。假定使用的系统时钟频率为50HMz,要等待20ms的时间,那么就使用计数器数1_000_000个clk,解决延时问题。
-
按照等待延时的思路,可以这样设计:检测到按键key_in按下,启动计数器,等到20ms后检测key_in是否为0,若为0,表明按键按下状态稳定,关闭计数器,然后等待按键key_in释放;当检测到key_in释放出现上升沿,再次启动计数器,20ms后检测key_in是否为1,若为1,表明按键已释放,一次完整的按键过程结束。 每20ms检测完毕后,输出一个flag脉冲。
-
为了实现按键消抖,考虑到两种情况,按下需要消抖,释放需要消抖。状态机分为4个状态,两个稳定状态和两个消抖状态
IDLE:空闲状态,默认按键不按下为稳定高电平
FILTER_DOWN:按键按下消抖状态
DOWN:按键按下稳定状态
FILTER_UP:按键释放消抖状态
-
消抖模块端口设计
3. 代码实现
module key_filter(
output reg key_flag, // 消抖完毕输出脉冲
output reg key_state, // 按键状态输出
input wire clk, // 50MHz时钟
input wire rst,
input wire key_in
);
// 状态声明
parameter IDLE = 4'b0001;
parameter FILTER_DOWN = 4'b0010;
parameter DOWN = 4'b0100;
parameter FILTER_UP = 4'b1000;
reg [3:0] next_state;
reg key_temp; //
wire key_nedge; //
wire key_pedge; //
reg en_counter; // 计数器使能信号
reg [19:0] counter; // 计数1_000_000次
// 边沿检测电路
// 按键信号属于异步信号,在状态转移中需要对按键边沿敏感,所以先采用一级D触发器将key_in与clk同步,产生pedge和nedge信号
always @ (posedge clk)
key_temp <= key_in; // 暂存上一个按键状态
assign key_nedge = (key_temp) && (!key_in); // 下降沿检测
assign key_pedge = (!key_temp) && (key_in); // 上升沿检测
// 带使能的计数器,实现20ms延时
always @ (posedge clk) begin
if (rst == 1'b0) begin
counter <= 0;
end else if (en_counter == 1'b1) begin
counter <= counter + 1;
end else begin
counter <= 0;
end
end
// 状态机模块
always @ (posedge clk or negedge rst) begin
if (rst == 1'b0) begin
next_state <= IDLE;
en_counter <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end else begin
case (next_state)
IDLE: begin
key_flag <= 1'b0;
key_state <= 1'b1;
if (key_nedge) begin
next_state <= FILTER_DOWN;
en_counter <= 1'b1;
end else begin
next_state <= IDLE;
end
end
FILTER_DOWN: begin
if (counter >= 20'd999_999) begin // 20ms时间到
key_flag <= 1'b1; // flag输出一个高脉冲,进入稳定状态
en_counter <= 1'b0; // 计数器不使能
next_state <= DOWN;
end else if (key_pedge) begin // 20ms内检测到有上升沿
en_counter <= 1'b0; //计数器不使能,保持空闲状态
next_state <= IDLE;
end
end
DOWN: begin
key_flag <= 1'b0;
key_state <= 1'b0;
if (key_pedge) begin
next_state <= FILTER_UP;
en_counter <= 1'b1;
end else begin
next_state <= DOWN;
end
end
FILTER_UP: begin
if (counter >= 20'd999_999) begin // 20ms时间到
key_flag <= 1'b1; // flag输出一个高脉冲,进入稳定状态
en_counter <= 1'b0; // 计数器不使能
next_state <= IDLE;
end else if (key_nedge) begin // 20ms内检测到有下降沿
en_counter <= 1'b0; // 计数器不使能,保持按下稳定状态
next_state <= DOWN;
end
end
default: begin
next_state <= IDLE;
end
endcase
end
end
endmodule
4. 仿真测试
4.1. initial语句块赋值
`define clk_period 20 //100M系统时钟
module key_filter_tb();
reg clk; //50M时钟信号
reg rst; //低电平复位
reg key_in; //按键输入
wire key_flag; //消抖完毕输出脉冲
wire key_state; //按键状态输出
//例化测试模块
key_filter key_filter_test(
.clk(clk), //50M时钟信号
.rst(rst), //低电平复位
.key_in(key_in), //按键输入
.key_flag(key_flag), //消抖完毕输出脉冲
.key_state(key_state) //按键状态输出
);
//产生100M时钟信号
initial clk = 1;
always #(`clk_period / 2) clk <= ~clk;
//开始测试
initial begin
rst <= 0; //系统复位
key_in <= 1; //按键处于空闲状态
#(`clk_period * 2);
rst <= 1;
#10_000_000; //延时10ms,方便观察按键按下现象
//开始模拟按键按下抖动
key_in <= 0; #1000;
key_in <= 1; #2000;
key_in <= 0; #1400;
key_in <= 1; #2600;
key_in <= 0; #1300;
key_in <= 1; #200;
//产生一个稳定的低电平大于20ms,代表按键稳定
key_in <= 0; #30_000_000;
//模拟释放抖动
key_in <= 1; #2000;
key_in <= 0; #1000;
key_in <= 1; #2600;
key_in <= 0; #1400;
key_in <= 1; #200;
key_in <= 0; #1300;
//产生一个稳定的高电平大于20ms,代表释放稳定
key_in <= 1; #30_000_000;
$stop;
end
endmodule
局部抖动
4.2. 调用task任务封装赋值并调用$random函数
- 利用$randon函数产生随机延时置模拟抖动
reg [15:0]randnum;
randnum = $random % 50; //产生一个-49~49内的随机数
randnum = {$random} % 50; //产生一个0 ~ 50内的随机数
- 利用 task / endtask 将重复代码进行封装
task <任务名>;
<语句1>
<语句2>
<语句3>
....
endtask
`define clk_period 20 //100M系统时钟
module key_filter_2_tb();
reg clk; //50M时钟信号
reg rst; //低电平复位
reg key_in; //按键输入
wire key_flag; //消抖完毕输出脉冲
wire key_state; //按键状态输出
reg [15:0]rand_time; //按键抖动时随机时长
//例化测试模块
key_filter key_filter_test(
.clk(clk), //50M时钟信号
.rst(rst), //低电平复位
.key_in(key_in), //按键输入
.key_flag(key_flag), //消抖完毕输出脉冲
.key_state(key_state) //按键状态输出
);
//产生100M时钟信号
initial clk = 1;
always #(`clk_period / 2) clk <= ~clk;
//开始测试
initial begin
rst <= 0; //系统复位
key_in <= 1; //按键处于空闲状态
#(`clk_period * 2);
rst <= 1;
#10_000_000; //延时10ms,方便观察按键按下现象
press_key; #10000; //第一次按下按键
press_key; #10000; //第二次按下按键
press_key; #10000; //第三次按下按键
$stop;
end
task press_key;
begin
//开始模拟按键按下抖动
repeat(50)begin
rand_time = {$random} % 65536;
#rand_time key_in = ~key_in;
end
//产生一个稳定的低电平大于20ms,代表按键稳定
key_in = 0;
#30_000_000;
//模拟释放抖动
repeat(50)begin
rand_time = {$random} % 65536;
#rand_time key_in = ~key_in;
end
//产生一个稳定的高电平大于20ms,代表释放稳定
key_in = 1;
#30_000_000;
end
endtask
endmodule