Edge & debounce
0赞Edge & debounce
——两忘而化其道(fei199311)
一、名词解释
边沿检测:就是检测输入信号,或者 FPGA 内部逻辑信号的跳变,即上升沿或者下降沿的检测,这在 FPGA 电路设计中相当的广泛[1]。
按键消抖:通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,如下图所示。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。
图1.按键抖动
抖动时间:抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。键抖动会引起一次按键被误读多次。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。
二、设计需求
边沿检测的用处很多,尤其在于获取突发的沿信号(非系统时钟信号),其实想想,那个系统复位信号在设计的时候一定要好好注意一下。还有用边沿检测技术发出使能信号,用于时钟同步化。
按键作为最常用的系统输入设备之一,有着极其广泛的应用,并且历史悠久,如我们日常最常用的键盘。由于按键抖动的普遍存在性,了解并解决按键抖动问题是十分必要的。例如,我在测试程序过程中,将去抖动的延时时间减小,则按键按下时led有时会出现跳变,这是抖动存在的具体表现,当我将去抖动延时增大到5~10ms时,led稳定变化。
三、设计思想
用边沿检测模块检测到输入的跳变沿,将使能信号发送出去;去抖动模块没有接收到边沿使能信号时,处于IDLE状态;当去抖动模块接收到边沿使能信号时,进入wait状态,等待计数器计时,达到5~10ms时就发出一个时钟周期的计数器使能信号;将计数器使能信号与当前输入信号对比,若是干扰,则不做任何动作;若是按键信号,则发出按键使能。
四、设计方案
其实去抖动的设计大致有两个方面:硬件方式和软件方式。这里的方案主要是关于FPGA的实现方案。
关于边沿检测模块的设计,我在这里进行了升级和改进,且适用于多种不同的情况。例如可以检测上升沿,也可以检测下降沿,并且可以增加采样的位数从而增加电路的抗干扰性。详见代码。
边沿检测模块的原理:其原理就是保存以往信息,然后与当前的信息进行适当的比较,从而作出边沿判断。例如,高电平1到低电平0,我们用一个D触发器保存上一个时钟的高电平1,然后用当前的低电平0取非,再与上一状态相与,从而得到下降沿使能,上升沿使能于此相同。
还有一点需要注意,用于判断的信息越多,作出的判断相对来说就越准,但是会牺牲一些逻辑资源,这些都需要平衡。例如,采样到的信号为01,其表示上升沿,但是若这个0只是一个干扰,而且恰好被采样到呢,如010,就无法判断其是干扰还是真的需要处理的信号,所以我采用了0011来判断上升沿,这样可以去除一个周期的干扰,如此下去000111、00001111。。。。。。哈哈,这个也只是一个我的思路,在一些特定的设计中或许可以用到。电路图如下:
图2.边沿检测模块电路图
这个图3中就有上升沿检测和下降沿检测,并且有一对信号检测和两对信号检测。更多的检测请看测试代码和测试波形。
关于去抖动模块的设计,就是两个状态的跳转,如下图所示。
图3.去抖动状态机
注:单片机的去抖动方案
在单片机设计的按键去抖思路是:检测到按下时延时20ms,再检测,如果状态仍为按下,则确认是按下的;如果状态为弹起的,则确认是干扰,无按键按下。代码如下:
while(1)
{
if(key1 == 0)
{
delay_20ms();
if(key1 == 0)
led1 = key1;
}
}
五、设计代码
1.边沿检测模块:
// 边沿检测电路设计
module edge_detection
(
input wire clk, // 系统时钟
input wire rst,
input wire input_sign, // 输入的待检测信号
output wire neg_e,
output wire pos_e,
output wire neg_edge, // 下降沿使能信号
output wire pos_edge // 上升沿使能信号
);
reg [3:0] sign_reg;
/**************************************************
always @ (posedge clk)
sign_reg <= {sign_reg[2:0], input_sign}; // 四位移位寄存器
*/
always @ (posedge clk) begin
if(rst)
sign_reg <= 4'b0;
else begin
sign_reg[0] <= input_sign;
sign_reg[1] <= sign_reg[0];
sign_reg[2] <= sign_reg[1];
sign_reg[3] <= sign_reg[2];
end
end
assign neg_edge = sign_reg[3] & ~sign_reg[2];
assign pos_edge = ~sign_reg[3] & sign_reg[2];
assign neg_e = sign_reg[3] & sign_reg[2] & ~sign_reg[1] & ~sign_reg[0];
assign pos_e = ~sign_reg[3] & ~sign_reg[2] & sign_reg[1] & sign_reg[0];
endmodule
2. 边沿检测模块testbench:
`include "edge_detection.v"
module edge_detection_tb;
reg clk;
reg input_sign;
wire neg_edge;
wire pos_edge;
wire neg_e;
wire pos_e;
edge_detection U
(
.clk(clk),
.input_sign(input_sign),
.neg_e(neg_e),
.pos_e(pos_e),
.neg_edge(neg_edge),
.pos_edge(pos_edge)
);
initial begin
clk = 0;
input_sign = 0;
#10;
#3 input_sign = 1;
#4 input_sign = 0;
#10 input_sign = 1;
#32;
input_sign = 0;
#1 input_sign = 1;
#1 input_sign = 0;
#2 input_sign = 1;
#1 input_sign = 0;
#1 input_sign = 1;
#32;
#3 input_sign = 1;
#5 input_sign = 0;
// #10 input_sign = 1;
#30;
$finish;
end
always #2 clk = ~clk;
initial begin
$dumpfile("edge_detection_tb.vcd");
$dumpvars;
end
endmodule
3.按键去抖动模块:
// debounce.v
`include "edge_detection.v"
module debounce
(
input wire clk, // 50M
input wire rst, // high = ture
input wire button,// high = enabled
// test
output reg led,
output wire button_ena // enabled
// test
);
wire edge_ena;
edge_detection UNIT_edge
(
.clk(clk),
.rst(rst),
.input_sign(button),
.pos_edge(edge_ena)
);
// module 1: counter_10ms
reg [9:0] cnt;
wire cnt_ena;
always @ (posedge clk) begin
if(rst)
cnt <= 1'b0;
else if(state == S2_wait)
cnt <= cnt + 1'b1;
else
cnt <= 1'b0;
end
// counter enabled
assign cnt_ena = (cnt == 10'd1023) ? 1'b1 : 1'b0;
// module 2: FSM_only hot
parameter S1_IDLE = 2'b01,
S2_wait = 2'b10;
// S3_dete = 2'b00; // detection
reg [1:0] state; // state register
always @ (posedge clk) begin
if(rst)
state <= S1_IDLE;
else begin
case(state)
S1_IDLE:
begin
if(edge_ena)
state <= S2_wait;
else
state <= S1_IDLE;
end
S2_wait:
begin
if(cnt_ena) // counter enabled
state <= S1_IDLE;
else
state <= S2_wait;
end
default: state <= S1_IDLE;
endcase
end
end
// out sign
assign button_ena = cnt_ena && button;
// test led
always @ (posedge clk) begin
if(rst)
led <= 1'b0;
else if(button_ena)
led <= ~led;
end
endmodule
4.按键去抖动模块testbench:
`include "debounce.v"
module debounce_tb;
reg clk, rst, button;
wire led, button_ena;
debounce d_unit
(
.clk(clk),
.rst(rst),
.button(button),
.led(led),
.button_ena(button_ena)
);
initial begin
clk = 0;
rst = 1;
button = 0;
#10;
button = 1;
rst = 0;
#3000;
button = 0;
#3000;
button = 1;
#100;
button = 0;
button = 1;
#3000;
button = 0;
#4000;
$finish;
end
always #1 clk = ~clk;
initial begin
$dumpfile("debounce_tb.vcd");
$dumpvars;
end
endmodule
六、测试解释
1.边沿检测模块测试波形:
如上图所示,neg_e与pos_e信号为两对采样信号做比较而得到的使能波形,neg_edge与pos_edge为一对采样信号做比较而得到的使能波形。可以清楚的看到,两对采样信号对干扰的滤除作用优于一对采样信号的效果。且这些使能信号都“正确”的根据输入信号而发出了相应的使能信号。
2.去抖动测试波形:
七、总结分析
在设计时,一定要注意按键是高有效(有效表示按下)还是低有效,关于这个问题我错了好多次。。。还是好好看看原理图在做设计吧。
原来看的是特权同学的代码,现在自己根据理解写一写,感觉还是不错的,其实主要是这周要给大家将这方面的东西,所以还是多多准备为妙。
八、参考资料
[1] CrazyBingo. 从零开始走 进 FPG A 世界V2.0. 杭州:杭电无线电爱好者协会出版社. 2011
注:上传源代码:FPGA_edge&debounce_verilog.zip