两忘而化其道

Edge & debounce

0
阅读(3417)

Edge & debounce

——两忘而化其道(fei199311

一、名词解释

    边沿检测:就是检测输入信号,或者 FPGA 内部逻辑信号的跳变,即上升沿或者下降沿的检测,这在 FPGA 电路设计中相当的广泛[1]

    按键消抖:通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,如下图所示。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。






1.按键抖动

    抖动时间:抖动时间的长短由按键的机械特性决定,一般为5ms10ms。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。键抖动会引起一次按键被误读多次。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。


二、设计需求

    边沿检测的用处很多,尤其在于获取突发的沿信号(非系统时钟信号),其实想想,那个系统复位信号在设计的时候一定要好好注意一下。还有用边沿检测技术发出使能信号,用于时钟同步化。

    按键作为最常用的系统输入设备之一,有着极其广泛的应用,并且历史悠久,如我们日常最常用的键盘。由于按键抖动的普遍存在性,了解并解决按键抖动问题是十分必要的。例如,我在测试程序过程中,将去抖动的延时时间减小,则按键按下时led有时会出现跳变,这是抖动存在的具体表现,当我将去抖动延时增大到510ms时,led稳定变化。


三、设计思想

    用边沿检测模块检测到输入的跳变沿,将使能信号发送出去;去抖动模块没有接收到边沿使能信号时,处于IDLE状态;当去抖动模块接收到边沿使能信号时,进入wait状态,等待计数器计时,达到510ms时就发出一个时钟周期的计数器使能信号;将计数器使能信号与当前输入信号对比,若是干扰,则不做任何动作;若是按键信号,则发出按键使能。


四、设计方案

    其实去抖动的设计大致有两个方面:硬件方式和软件方式。这里的方案主要是关于FPGA的实现方案。

    关于边沿检测模块的设计,我在这里进行了升级和改进,且适用于多种不同的情况。例如可以检测上升沿,也可以检测下降沿,并且可以增加采样的位数从而增加电路的抗干扰性。详见代码。

    边沿检测模块的原理:其原理就是保存以往信息,然后与当前的信息进行适当的比较,从而作出边沿判断。例如,高电平1到低电平0,我们用一个D触发器保存上一个时钟的高电平1,然后用当前的低电平0取非,再与上一状态相与,从而得到下降沿使能,上升沿使能于此相同。

    还有一点需要注意,用于判断的信息越多,作出的判断相对来说就越准,但是会牺牲一些逻辑资源,这些都需要平衡。例如,采样到的信号为01,其表示上升沿,但是若这个0只是一个干扰,而且恰好被采样到呢,如010,就无法判断其是干扰还是真的需要处理的信号,所以我采用了0011来判断上升沿,这样可以去除一个周期的干扰,如此下去00011100001111。。。。。。哈哈,这个也只是一个我的思路,在一些特定的设计中或许可以用到。电路图如下:













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.边沿检测模块测试波形:

4. 边沿检测模块测试波形

如上图所示,neg_epos_e信号为两对采样信号做比较而得到的使能波形,neg_edgepos_edge为一对采样信号做比较而得到的使能波形。可以清楚的看到,两对采样信号对干扰的滤除作用优于一对采样信号的效果。且这些使能信号都“正确”的根据输入信号而发出了相应的使能信号。

2.去抖动测试波形:

5.去抖动测试波形

七、总结分析

在设计时,一定要注意按键是高有效(有效表示按下)还是低有效,关于这个问题我错了好多次。。。还是好好看看原理图在做设计吧。

原来看的是特权同学的代码,现在自己根据理解写一写,感觉还是不错的,其实主要是这周要给大家将这方面的东西,所以还是多多准备为妙。

八、参考资料

[1] CrazyBingo. 从零开始走 FPG A 世界V2.0. 杭州:杭电无线电爱好者协会出版社. 2011



注:上传源代码:FPGA_edge&debounce_verilog.zip