Menu Close

自适应按键去抖Verilog实现与FPGA板载测试

在FPGA或嵌入式系统中,按键作为人机接口的重要部件被广泛使用。在作为标准的输入接口,按键可以提供状态变换,多键组合以及按照不同时间顺序的组合,可以表达各种不同的需求。比如计算机键盘,在信息输入方面几乎涵盖了各种需要。由于目前应用最多的还是机械式按键,机械式按键在每次按下时簧片都会抖动,因此在按键按下瞬间会出现按键的多次闭合与断开,效果如同该按键被多次按下,因此不处理好按键抖动,就会得到重复输入的结果。

%title插图%num

图1 机械式按键内部结构示意图

1.按键的抖动原理

通常按键所用的都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开,而是在闭合和断开的瞬间伴随了一连串的抖动,抖动又分为前沿抖动和后沿,图 2 所示。

按键稳定闭合时间长短是由操作人员决定的,通常都会在 100ms 以上,刻意快速按的话能达到 40-50ms 左右,再低就比较难了。抖动时间是由按键的机械特性决定的,一般都会在几毫秒到几十毫秒之间,为了确保程序对按键的一次操作只捕获一次闭合或者一次断开,必须进行按键的消抖处理。当检测到按键状态变化时,不是立即去做按键响应,而是先等待一段时间重新检测按键闭合或断开状态,确保按键的状态稳定后再进行处理。按键消抖一般有硬件消抖和软件消抖两种处理方式。

%title插图%num

 

图2 按键抖动原理

  • 硬件消抖

硬件消抖一般采用阻容滤波加回滞比较器的方式实现,阻容滤波一般采用无源积分电路实现,带有回滞特性的比较器可以选用施密特触发器实现,目前有许多的MCU接口都具备施密特特性的输入接口可以使用。

  • 软件消抖

在绝大多数情况下,我们是用软件即程序来实现消抖的。最简单的消抖原理,就是当检测到按键状态变化后,先等待一个 10ms–50ms 左右的延时时间,等待抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。这种检测在传统的软件设计中使用比较广泛。但是随着按键使用次数增加,按键逐渐老化,或质量较差的按键抖动的时间会加长,抖动的频次也会增加。这种算法非常简单,在早期的单片机时代应用比较广泛,但这种算法的缺点也很明显,很难在消除抖动和按键灵敏度之间平衡,即如果延时过短抖动不能滤除干净,延时过长又影响按键的灵敏度。后来逐渐被自适应算法所替代。

  • 自适应消抖算法

由于不同的按键在按键被按下时的抖动效果不同,前沿和后沿抖动时间也不一样,如果仅仅靠提供一段时间的延迟来滤掉抖动的话,那么延迟时间应尽可能的长,如果前沿总延迟超过100ms,那么总延迟将会超过200ms 以上,这样可能会严重影响用户体验。自适应延时的策略是将延时计数器不是用来控制整个前沿抖动的过程,而是利用每次的抖动对延迟计数器进行复位(清零),直到按键稳定,计数器计在没有复位的情况下计到特定的数值之后,进行按键捕获,如图2。按键释放的过程也要用同样的过程。图2中的delay_count为毫秒计数器。

%title插图%num

 

图2

2.按键抖动程序设计

  • 按键抖动测试

利用按键作为十进制计数器的输入,观察每次按键按下,计数器计数的次数,为了测试方便,将计数值译码后显示在数码管上。测试条件利用开发板FII-PRA006或FII-PRA010(Intel FPGA),按键资源如下:在开发板上共四个按键如图3,4所示,KEY0~3分别接到FPGA的四个管脚上,其映射关系如表1,

表1

按键名称 网络名称 FPGA对应管脚
UP KEY3(PB0) 11
DOWN KEY2(PB3) 10
LEFT KEY0(PB1) 3
RIGHT KEY1(PB2) 7

%title插图%num

图3

%title插图%num

 

图4

从图4中可以看出,当没有按键按下时,由于KEY1~3经过1K电阻接地,因此FPGA管脚对应的是低电平,当按键按下时应该为高电平。本测试用到的资源如表2,

表2

程序信号名 网络标号 FPGA管脚 端口说明
clk C10_50MCLK 91 输入时钟
rst KEY0 3 复位按键
bn KEY3 11 按键输入
SEL[5] SEG_3V3_D5 124 位选信号第5位
SEL[4] SEG_3V3_D4 127 位选信号第4位
SEL[3] SEG_3V3_D3 129 位选信号第3位
SEL[2] SEG_3V3_D2 141 位选信号第2位
SEL[1] SEG_3V3_D1 142 位选信号第1位
SEL[0] SEG_3V3_D0 136 位选信号第0位
SEVEN_SEG[6] SEG_PG 135 段选信号第7位
SEVEN_SEG[5] SEG_PF 138 段选信号第6位
SEVEN_SEG[4] SEG_PE 126 段选信号第5位
SEVEN_SEG[3] SEG_PD 125 段选信号第4位
SEVEN_SEG[2] SEG_PC 133 段选信号第3位
SEVEN_SEG[1] SEG_PB 137 段选信号第2位
SEVEN_SEG[0] SEG_PA 132 段选信号第1位
DP SEG_DP 128 段选信号第0位

例1:利用消抖后的按键输入实现加1计数

 /*
  mapping :
  SEVEN_SEG[0]  SEVEN_SEG[1]  SEVEN_SEG[2]  SEVEN_SEG[3]  SEVEN_SEG[4]  SEVEN_SEG[5]  SEVEN_SEG[6]
  A                 B                 C                  D           E             F             G
  */
 
 
module debouncing
(
    input            inclk,
    input            rst,
    input            bn,
    output reg [6:0] SEVEN_SEG,
    output           DP,
    output reg [5:0] SEL
);
 
reg  [3:0]  counta, countb;
reg  [2:0]  bn_r;   //按键输入锁存,消除亚稳态
reg         bn_hp;   //按键高脉冲提取
reg  [15:0] div_count;
reg         f_1ms;
reg  [3:0]  count_sel;
 
//assign       SEL = 6'b111110;
assign  DP = 1'b1;  
 
//======= 提取按键上升沿 ============
always@(posedge inclk or posedge rst )
if(rst) 
begin
    bn_r <= 0;
    bn_hp <= 0;
end
else 
begin
    bn_r <= {bn_r[1:0], bn};
    bn_hp <= ~bn_r[2] & bn_r[1];
end

//=========== 按键加一 =================
 
always@(posedge inclk or posedge rst )
if(rst) 
begin
    counta <= 0;
    countb <= 0;
end
else 
begin
    if(counta == 9) 
    begin
        counta <= 0;
        if(countb == 9)
        begin
          countb <= 0;
        end
        else
         countb <= countb + 1;
    end 
    else if(bn_hp)
        counta <= counta + 1;
end
 
//=========== 时钟分频 ============
 
always@(posedge inclk or posedge rst )
if(rst) 
begin
    div_count <= 0;
    f_1ms <= 0;
end
else 
begin
    f_1ms <= 0;
    if(div_count == 49999)
    begin
        div_count <= 0;
        f_1ms <= 1'b1;
    end
    else 
    begin
        div_count <= div_count + 1;
    end
end
 
//========= 数码管扫描 ==========
 
always@(posedge inclk or posedge rst )
if(rst) 
begin
    SEL <= 6'b11_1111;
    count_sel <= 0;
end
else 
begin
    if(f_1ms) 
    begin
        if(SEL == 6'b11_1110) 
        begin
            SEL <= 6'b11_1101;
            count_sel <= countb;
        end
        else 
        begin
            SEL <= 6'b11_1110;
            count_sel <= counta;
        end
    end         
end
 
//====== 7段数码管译码 ==============
always@(*)
case(count_sel)
0:       SEVEN_SEG = 7'b100_0000;
1:       SEVEN_SEG = 7'b111_1001;
2:       SEVEN_SEG = 7'b010_0100;
3:       SEVEN_SEG = 7'b011_0000;
4:       SEVEN_SEG = 7'b001_1001;
5:       SEVEN_SEG = 7'b001_0010;
6:       SEVEN_SEG = 7'b000_0010;
7:       SEVEN_SEG = 7'b111_1000;
8:       SEVEN_SEG = 7'b000_0000;
9:       SEVEN_SEG = 7'b001_0000;
4'ha:    SEVEN_SEG = 7'b000_1000;
4'hb:    SEVEN_SEG = 7'b000_0011;
4'hC:    SEVEN_SEG = 7'b100_0110;
4'hd:    SEVEN_SEG = 7'b010_0001;
4'hE:    SEVEN_SEG = 7'b000_0110;
4'hF:    SEVEN_SEG = 7'b000_1110;
default: SEVEN_SEG = 7'b111_1111;   
endcase
 
endmodule

 

根据表2列出的FPGA管脚映射进行管脚锁定。重新编译,下载到开发板FII-PRA006进行测试,按动4个按钮中右边的按钮(PB2),观察计数情况,看看有没有抖动。按下PB3可以清除已有的计数,测试可以重新开始。

思考题: 在上面的代码中,分析下面的代码原理及作用

else

begin

bn_r = {bn_r[1:0], bn};

bn_hp = ~bn_r[2]  &  bn_r[1];

end

例2:修改例1中的代码,实现自适应去抖

/*
  mapping :
 
  SEVEN_SEG[0]  SEVEN_SEG[1]  SEVEN_SEG[2]  SEVEN_SEG[3]  SEVEN_SEG[4]  SEVEN_SEG[5]  SEVEN_SEG[6]
  A                 B                 C                  D           E             F             G
  */
 
 
module debouncing
(
    input            inclk,
    input            rst,
    input            bn,
    output reg [6:0] SEVEN_SEG,
    output           DP,
    output reg [5:0] SEL
);
 
reg  [3:0]  counta, countb;
reg  [2:0]  bn_r;
//reg         bn_hp;
reg         bn_hp_debounce;//hp --> high pulse
reg  [3:0]  deb_count;
reg  [1:0]  deb_st;
reg  [15:0] div_count;
reg         f_1ms;
reg  [3:0]  count_sel;
 
//assign       SEL=6'b111110;
assign  DP=1'b1;
 
  //
 
always@(posedge inclk or posedge rst )
if(rst) 
begin
    bn_r <= 0;
  //bn_hp <= 0;
    deb_st <= 0;
    deb_count <= 0;
    bn_hp_debounce <= 0;
end
else 
begin
    bn_r <= {bn_r[1:0], bn };
  //bn_hp <= ~bn_r[2] & bn_r[1];
 
    case(deb_st)
    0:
    begin
        deb_count <= 0;
        bn_hp_debounce <= 0;
        if(bn_r[2])
            deb_st <= 1;
    end
    1:
    begin
        if(~bn_r[2])
            deb_st <= 0;
        else if(deb_count == 10)
            deb_st <= 2;
        else if(f_1ms)
            deb_count <= deb_count + 1;
    end
    2:
    begin
        deb_count <= 0;
        if(~bn_r[2])
            deb_st <= 3;
    end
    3:
    begin
        if(bn_r[2])
            deb_st <= 2;
        else if(deb_count == 10) 
        begin
            deb_st <= 0;
            bn_hp_debounce <= 1'b1;
        end
        else if(f_1ms)
            deb_count <= deb_count + 1;
    end
    default: deb_st <= 0;
    endcase
end
 
always@(posedge inclk or posedge rst )
if(rst) 
begin
    counta <= 0;
    countb <= 0;
end
else 
begin
    if(bn_hp_debounce) 
    begin
        if(counta == 9) 
        begin
            counta <= 0;
            countb <= countb + 1;
        end 
        else    
            counta <= counta + 1;
    end
end
 
  //=====
 
always@(posedge inclk or posedge rst )
if(rst) 
begin
    div_count <= 0;
    f_1ms <= 0;
end
else 
begin
    f_1ms <= 0;
    if(div_count == 49999)
    begin
        div_count <= 0;
        f_1ms <= 1'b1;
    end
    else 
    begin
        div_count <= div_count + 1;
    end
end
 
  //====================
 
always@(posedge inclk or posedge rst )
if(rst) 
begin
    SEL <= 6'b11_1111;
    count_sel <= 0;
end
else 
begin
    if(f_1ms) 
    begin
        if(SEL == 6'b11_1110) 
        begin
            SEL <= 6'b11_1101;
            count_sel <= countb;
        end
        else 
        begin
            SEL <= 6'b11_1110;
            count_sel <= counta;
        end
    end
end
 
always@(*)
case(count_sel)
0:       SEVEN_SEG = 7'b100_0000;
1:       SEVEN_SEG = 7'b111_1001;
2:       SEVEN_SEG = 7'b010_0100;
3:       SEVEN_SEG = 7'b011_0000;
4:       SEVEN_SEG = 7'b001_1001;
5:       SEVEN_SEG = 7'b001_0010;
6:       SEVEN_SEG = 7'b000_0010;
7:       SEVEN_SEG = 7'b111_1000;
8:       SEVEN_SEG = 7'b000_0000;
9:       SEVEN_SEG = 7'b001_0000;
4'ha:    SEVEN_SEG = 7'b000_1000;
4'hb:    SEVEN_SEG = 7'b000_0011;
4'hC:    SEVEN_SEG = 7'b100_0110;
4'hd:    SEVEN_SEG = 7'b010_0001;
4'hE:    SEVEN_SEG = 7'b000_0110;
4'hF:    SEVEN_SEG = 7'b000_1110;
default: SEVEN_SEG = 7'b111_1111;
endcase
 
endmodule

 

重新编译,下载到FII-PRA006开发板中进行测试,按下PB2观察数码管计数情况,看看还有没有一次按下,多次计数的情况。

练习:

  1. 分析按键自适应去抖原理及程序实现
  2. 将上面的代码移植到Vivado下编译,并用FII-PRX100-D 开发板进行测试。
  3. 增减一个按键如key2, 实现按键的加减计数。

 

Posted in FPGA, FPGA, FPGA, FPGA硬件资源, Quartus II, Verilog, Verilog, 开发工具, 开发板, 教材与教案, 文章, 编程语言
0 0 投票数
Article Rating
订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论

相关链接