一、按键相关原理介绍
按键的结构
按键按下,对应的端口进入低电平。
左下角就是经典的按键电路,按键按下、进入低电平,按键释放回到高电平。这个过程就会产生抖动。
就像人去敲击一个不锈钢的脸盆,脸盆会产生多次的振动。
实际上由于释放的时候,触点已经拉的很远了,抖动几乎不可能将其拉到一起。所以释放的抖动要比按下的抖动小的多。甚至不出现抖动波形。
会产生的影响:
抖动的时间肯定是远远大于我们FPGA的时钟的周期的(20ns)。
在抖动的时候不认为按键是按下的,等到抖动停止,判断按键是按下的。
可以将抖动的按键变为理想的按键。
1、按键的工作状态
1、按键未按下时,空闲态,电路输出高电平
2、按键按下抖动,电路输出在高低电平间跳转。
3、按下抖动停止,静止态,电路输出低电平
4、按键松开抖动,电路输出在高低电平间跳转。
5、松开抖动停止,电路输出高电平。
按下的抖动和松开的抖动都不会超过20ms.
2、如何滤除按键抖动,正确判断按键的按下与释放?
1.在单片机中,最常用的是延时消抖。延时一段时间,等待抖动的时间过去。
可能在采样波形的时候,正好是低电平,虽然暂时对了但是会导致错误。
思路二的实现情况。
消抖的时序状态分析。
3、状态转移图的绘制
绘制的状态转移图如上。
模块的示意图。
先用两级D触发器进行一个同步,避免出现亚稳态的情况。
前两个D触发器对输入信号Key,进行一个打拍同步,第三个存储前一拍的值,然后通过一个与门与当前时刻的值进行一个比较,从而完成按键的一个边沿检测。
用来同步的D触发器通常命名为sync。
所以有如上命名。
有一个映射关系,
为了方便表示,可以用状态名代替数字的值。这里可以使用parameter:
parameter:用于定义一个标识符代表常量,可以通过defparam或顶层例化时进行参数传递。
localparam:本地参数定义,用于定义一个标识符代表常量,无法进行参数传递。
4、时钟周期频率值
很多的时候时钟周期的频率值并不一定是一个整数。
在verilog语法工具中,没有小数点的计算parameter是软件提前的计算,会忽略小数点后面。最好的方式是用电脑上带小数的运算方式得到一个精确的值之后,用这个精确地值作为计数的最大值。
这一块想表达的是遇到非整数周期的情况,如何让计算周期和实际周期尽可能接近
5、基于状态机思维的按键消抖功能verilog实现思路
1.对异步输入信号打拍消除亚稳态。
2.寄存器打拍后信号。对比得出上升沿与下降沿的标志信号。
3.列出所有状态,编写状态机整体框架。
4.编写各状态间跳转方向及对应跳转条件。
5.编写状态机输出逻辑,在正确时刻为输出信号赋值。
二、按键消抖的具体实现
1.模块构建
构建模块如下
module key_filter(
Clk,
Reset_n,
Key,
Key_P_Flag,
Key_R_Flag
);
input Clk;
input Reset_n;
input Key;
output reg Key_P_Flag;
output reg Key_R_Flag;
reg [1:0]state; //根据的state的值不同进行不同的操作
localparam IDLE = 0; //空闲态
localparam P_FILTER = 0;
localparam WAIT_R = 0;
localparam R_FILTER = 0;
parameter MCNT = 1000_000 - 1;
reg [29:0]cnt;
reg sync_d0_Key;
reg sync_d1_Key;
reg r_Key;
wire pedge_key;
wire nedge_key;
wire time_20ms_reached;
assign time_20ms_reached = cnt >= MCNT;
always@(posedge Clk)
sync_d0_Key <=Key;
always@(posedge Clk)
sync_d1_Key <= sync_d0_Key;
always@(posedge Clk) //用来检测边沿的
r_Key <= sync_d1_Key;
assign nedge_key = (sync_d1_Key == 0)&& (r_Key == 1); //表示下降沿的情况
assign pedge_key = (sync_d1_Key == 1)&& (r_Key == 0);
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
state <= IDLE;
Key_R_Flag <= 1'd0;
Key_P_Flag <= 1'd0;
end
else begin //实现状态的跳转
case(state)
IDLE:
begin
Key_R_Flag <= 1'd0;
if(nedge_key)
state <= P_FILTER;
end
P_FILTER: //按下消抖状态
if(time_20ms_reached)begin
state <= WAIT_R;
Key_P_Flag <= 1'd1;
end
else if(pedge_key)
state <= IDLE;
else
state <= state;
WAIT_R:
begin
Key_P_Flag <= 1'd0; //一进入等待信号就立刻将其拉低,表明不是按下状态了
if(pedge_key)
state <= R_FILTER;
end
R_FILTER: //释放消抖状态
if(time_20ms_reached)begin
state <= IDLE;
Key_R_Flag <= 1'd1; //释放的标志信号为1
end
else if(nedge_key) //在等待新的信号时又检测到了下降沿
state <= WAIT_R;
else
state <= state; //接着等
endcase
end
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
cnt <= 0;
else if((state == P_FILTER) || (state == R_FILTER))
cnt <= cnt + 1'd1;
else
cnt <=0;
endmodule
2.激励编写
我们的激励主要是针对按键信号。
`timescale 1ns / 1ps
module key_filter_tb;
reg Clk;
reg Reset_n;
reg Key;
wire Key_P_Flag;
wire Key_R_Flag;
wire Key_State;
key_filter key_filter_inst(
.Clk(Clk),
.Reset_n(Reset_n),
.Key(Key),
.Key_State(Key_State),
.Key_P_Flag(Key_P_Flag),
.Key_R_Flag(Key_R_Flag)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Reset_n = 0;
Key = 1;
#201;
Reset_n = 1;
//第一次按下测试
//空闲稳定
Key = 1;#100000000; //空闲 稳定 100ms
//按下抖动
Key = 0; #18000000; //按下 抖动 低18ms
Key = 1; #2000000; //按下 抖动 高2ms
Key = 0; #1000000; //按下 抖动 低1ms
Key = 1; #200000; //按下 抖动 高0.2ms
Key = 0; #20000000; //按下 稳定 低20ms
//按下稳定
Key = 0; #50000000; //按下 稳定 继续50ms
//释放抖动
Key = 1; #2000000; //释放 抖动 高2ms
Key = 0; #1000000; //释放 抖动 低1ms
Key = 0; #20000000; //释放 稳定 低20ms
//释放稳定
Key = 1; #50000000; //按下 稳定 继续50ms
//空闲稳定
Key = 1;#100000000; //空闲 稳定 100ms
//按下抖动
Key = 0; #18000000; //按下 抖动 低18ms
Key = 1; #2000000; //按下 抖动 高2ms
Key = 0; #1000000; //按下 抖动 低1ms
Key = 1; #200000; //按下 抖动 高0.2ms
Key = 0; #20000000; //按下 稳定 低20ms
//按下稳定
Key = 0; #50000000; //按下 稳定 继续50ms
//释放抖动
Key = 1; #2000000; //释放 抖动 高2ms
Key = 0; #1000000; //释放 抖动 低1ms
Key = 1; #20000000; //释放 稳定 高20ms
//释放稳定
Key = 1; #50000000; //释放 稳定 继续50ms
$stop;
end
endmodule
这条新的代码行 assign time_20ms_reached = cnt >= MCNT; 改变了之前逻辑判断的方向。
具体来说:
cnt >= MCNT 是一个比较表达式,它会评估为true(如果cnt的值大于或等于MCNT)或false(如果cnt的值小于MCNT)。
time_20ms_reached = cnt >= MCNT; 将这个比较结果(true或false)赋值给time_20ms_reached变量。
从新的代码来看,现在time_20ms_reached变量被设置为true的条件是cnt的值大于或等于MCNT。如果cnt是一个递增的计数器,这通常意味着time_20ms_reached会在cnt超过或等于某个预设的阈值MCNT时变为true,这个阈值可能代表20毫秒的时间点。
与之前的cnt <= MCNT条件相比,这个新的条件要求cnt达到或超过MCNT,而不是小于或等于它。这意味着time_20ms_reached现在表示的是“是否已经超过或等于20毫秒”,而不是“是否已经达到或还未超过20毫秒”。
同样,这里依然只是逻辑上的判断,并不涉及实际的计时功能。实际的计时和cnt的更新需要由其他部分的代码或硬件来完成。
3.仿真结果
这里有一个
由并行赋值逻辑可以知道:上一时刻state=1,故下一个上升沿到来,尽管state=2,但计数器仍会计数
计数器仍多计数了一个周期的问题。
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
cnt <= 0;
else if((state == P_FILTER) || (state == R_FILTER))
cnt <= cnt + 1'd1;
else
cnt <=0;
这里的state判断,判断的是上一个时钟周期的值。
对.v文件修改后的结果。
总结
对按键消抖的verilog进行了学习和仿真,相关的代码可以直接用于后续的按键消抖的工作。