我们从2011年坚守至今,只想做存粹的技术论坛,记录你的技术足迹,有空就回来看看。  由于网站在外面,点击附件后可能要20秒才弹出下载,请耐心等待,勿重复点击

 找回密码
 立即注册
搜索
查看: 1801|回复: 15

[资料贡献] STM32单片机按键消抖和FPGA按键消抖大全

[复制链接]

该用户从未签到

126

主题

239

回帖

0

积分

二级逆天

积分
0

终身成就奖金点子奖原创先锋奖

发表于 2020-5-3 16:54:12 | 显示全部楼层 |阅读模式
STM32单片机按键消抖和FPGA按键消抖大全

按键去抖:由上图可以看出理想波形与实际波形之间是有区别的,实际波形在按下和释放的瞬间都有抖动的现象,抖动时间的长短和按键的机械特性有关,一般为5~10ms。通常我们手动按键然后释放,这个动作中稳定闭合的时间超过了20ms。因此单片机在检测键盘是否按下时都要加上去抖动操作,有专用的去抖动电路,也有专门的去抖动芯片,但通常我们采用软件延时的方法就可以解决抖动问题。

1. 单片机中按键消抖程序
1.1  单片机中,比如STM32中,一般的方法(最简单的方法)
软件消抖程序:
   if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==1)
                      {   
                               delay_ms(20);//延时20ms再去检测按键值
                  if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==0) // 相当于下降沿
{
KEY1 = 1;  //表示KEY1被按下
}
}
1.2 比较全面的按键消抖程序及按键状态检测程序

第一步:初始化全局时间戳的定时器,一般采用SysTick定时器来产生,每ms一次tick即可。

第二步:初始化按键对应的IO,复用为边沿触发的外部中断。

第三步:在外部中断函数中添加按键事件处理函数。
代码部分:
typedef struct _Key_t  
{  
    u32 last_time;  
    enum  
    {  
        May_Press,  
        Release,  
    }private_state;  
    enum  
    {  
        No_Press,  
        Short_Press,  
        Long_Press,  
    }state;  
}Key_t;  
  
#define Is_ShortPress_Threshold   1500  
简单定义一个按键状态的结构体,用于管理每个按键的状态。顺便再定义一个长短按的识别阈值,用于区分按键的长短按。

if(key_state.private_state==Release)                  
{  
    if(KEY==0)  
    {  
        key_state.private_state=May_Press;  
        key_state.last_time=course_ms();  
    }  
}  
else if(key_state.private_state==May_Press)  
{  
    if(KEY==1)  
    {  
        if((course_ms()-key_state.last_time>10)&&(course_ms()-key_state.last_time
        {  
            key_state.state=Short_Press;  
            key_state.private_state=Release;  
        }  
        else if(course_ms()-key_state.last_time>Is_ShortPress_Threshold)  
        {  
            key_state.state=Long_Press;  
            key_state.private_state=Release;  
        }  
        else  
            key_state.private_state=Release;  
    }  
}  
以上为需要添加到中断处理函数的按键事件处理函数,算法的核心是一个状态机。在本例中,按键被默认上拉,按下接地。course_ms()为获取全局时间戳的函数。
思路解释如下:按键状态结构体有一个用于识别的状态位,默认处于Release,也就是释放的状态。一旦按键被按下,中断触发,此时检查是否是Relase状态,如果是就检查按键是否被拉低,如果是,此时进入May_Press状态,也就是可能是按下的,并且记录此时的时间戳,这一步是消抖的关键。当按键被释放,由于是边沿触发,会再次进行处理,此时检查和上一次触发之间的时间戳之差,如果小于10ms我们就认为是抖动,此时不会对按键输出状态进行修改,而是直接将按键状态置回Relase状态,反之检查差值和长短按阈值之间的关系,将state置位为对应的状态。消抖的核心在于记录时间戳,而这只是一个简单的赋值操作,并不耗费时间。

效率上来说,延时消抖花费时间在无意义延时上,而相对较好的定时轮询还是不可避免的在轮询,而现在这种方式完全是中断性质的。唯一多出的开销(全局时间戳)并不是只可以用于按键消抖,另外在HAL库中存在直接获取tick的函数,这样实现就更方便了。经实际测试,消抖效果可以达到其他两种消抖算法的水平。

2. FPGA按键消抖程序
首先,做两个假定,以方便后面的描述

假定按键的默认状态为0,被按下后为1
假定按键抖动时长小于20ms,也即使用20ms的消抖时间
核心:方案

最容易想到的方案
    在按键电平稳定的情况下,当第一次检测到键位电平变化,开始20ms计时,计时时间到后将按键电平更新为当前电平

或许这才是最容易想的方案
    在20ms计时的过程中,有任何的电平变化都立即复位计时

消除按键反应延时抖方案
    在有电平变化时立即改变按键输出电平,并开始20ms计时,忽略这其中抖动

测试平台设计(修改代码以仿真的1us代替实际1ms)

无抖动 上升沿抖动5毫秒
下降沿抖动15毫秒
上升和下降沿均抖动19毫秒
  附加测试(可以不通过)

抖动25毫秒
代码

方案1

module debounce(
    input wire clk, nrst,
    input wire key_in,
    output reg key_out
    );

    // 20ms parameter
//    localparam TIME_20MS = 1_000_000;
    localparam TIME_20MS = 1_000;       // just for test

    // variable
    reg [20:0] cnt;
    reg key_cnt;
   
    // debounce time passed, refresh key state
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            key_out <= 0;
        else if(cnt == TIME_20MS - 1)
            key_out <= key_in;
    end

    // while in debounce state, count, otherwise 0
    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt <= 0;
        else if(key_cnt)
            cnt <= cnt + 1'b1;
        else
            cnt <= 0;
    end
     
     //
     always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                key_cnt <= 0;
            else if(key_cnt == 0 && key_in != key_out)
                key_cnt <= 1;
            else if(cnt == TIME_20MS - 1)
                key_cnt <= 0;
     end


方案2


module debounce(
    input wire clk, nrst,
    input wire key_in,
    output reg key_out
    );

//    localparam TIME_20MS = 1_000_000;
    localparam TIME_20MS = 1_000;

    reg key_cnt;
    reg [20:0] cnt;

    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            key_cnt <= 0;
        else if(cnt == TIME_20MS - 1)
            key_cnt <= 0;
        else if(key_cnt == 0 && key_out != key_in)
            key_cnt <= 1;
    end

    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt <= 0;
        else if(key_cnt) begin
            if(key_out == key_in)
                cnt <= 0;
            else
                cnt <= cnt + 1'b1;
        end
        else
            cnt <= 0;
    end
     
     always @(posedge clk or negedge nrst) begin
            if(nrst == 0)
                key_out <= 0;
            else if(cnt == TIME_20MS - 1)
                key_out <= key_in;
     end



方案3


module debounce(
    input wire clk, nrst,
    input wire key_in,
    output reg key_out
    );

//    localparam TIME_20MS = 1_000_000;
    localparam TIME_20MS = 1_000;       // just for test

    reg key_cnt;
    reg [20:0] cnt;

    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            key_cnt <= 0;
        else if(key_cnt == 0 && key_out != key_in)
            key_cnt <= 1;
        else if(cnt == TIME_20MS - 1)
            key_cnt <= 0;
    end

    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            cnt <= 0;
        else if(key_cnt)
            cnt <= cnt + 1'b1;
        else
            cnt <= 0;
    end

    always @(posedge clk or negedge nrst) begin
        if(nrst == 0)
            key_out <= 0;
        else if(key_cnt == 0 && key_out != key_in)
            key_out <= key_in;
    end



测试代码

// 按键消抖测试电路

// 时间单位
`timescale 1ns/10ps

// module
module  debounce_tb;

    // time period parameter
    localparam T = 20;

    // variable
    reg clk, nrst;
    reg key_in;
    wire key_out;

    // instantiate
    debounce uut(
        .clk    (clk    ),
        .nrst   (nrst   ),
        .key_in (key_in ),
        .key_out(key_out)
    );

    // clock
    initial begin
        clk = 1;
        forever #(T/2) clk = ~clk;
    end

    // reset
    initial begin
        nrst = 1;
        @(negedge clk) nrst = 0;
        @(negedge clk) nrst = 1;
    end

    // key_in
    initial begin
        // initial value
        key_in = 0;
        
        // wait reset
        repeat(3) @(negedge clk);
        
        // no bounce
        // key down
        key_in = 1;

        // last 60ms
        repeat(3000) @(negedge clk);

        // key up
        key_in = 0;

        // wait 50ms
        repeat(2500) @(negedge clk);

        // down 5ms, up 15ms
        // key down, bounce 5ms
        repeat(251) @(negedge clk) key_in = ~key_in;

        // last 60ms
        repeat(3000) @(negedge clk);

        // key up, bounce 15ms
        repeat(751) @(negedge clk) key_in = ~key_in;

        // wait 50ms
        repeat(2500) @(negedge clk);

        // down 19ms, up 19ms
        // key down, bounce 19ms
        repeat(951) @(negedge clk) key_in = ~key_in;

        // last 60ms
        repeat(3000) @(negedge clk);

        // key up, bounce 19ms
        repeat(951) @(negedge clk) key_in = ~key_in;

        // wait 50ms
        repeat(2500) @(negedge clk);
        
        // additional, this situation shoud not ever happen
        // down 25ms, up 25ms
        // key down, bounce 25ms
        repeat(1251) @(negedge clk) key_in = ~key_in;

        // last 60ms
        repeat(3000) @(negedge clk);

        // key up, bounce 25ms
        repeat(1251) @(negedge clk) key_in = ~key_in;

        // wait 50ms
        repeat(2500) @(negedge clk);

        // stop
        $stop;
    end



放在最后的,并不一定是最不重要的

  对于上面的三种方案,我比较喜欢第三种方案,它更贴合实际的按键状态,以上的代码我都做过modelsim仿真,但还没有在实际的项目中验证。在整理准备这个博客的时候,我又想到了一个感觉是更巧妙的方案,具体是这样的:在第三个方案的基础上,因为按键输入有变化的第一时刻,输出就已经改变了,在这种情况下,我可以把计时的时长改为一个很小的值,该值只要比抖动中的最长高低电平变化时间长即可。但想想也没这个必要,且这个抖动的高低电平变化时长我也很难去给它界定一个值。[sub][/sub][sup][/sup][strike][/strike]
回复

使用道具 举报

该用户从未签到

12

主题

7221

回帖

100

积分

游客

积分
100

终身成就奖特殊贡献奖原创先锋奖优秀斑竹奖

QQ
发表于 2020-5-3 18:25:50 | 显示全部楼层
        
回复

使用道具 举报

  • TA的每日心情
    慵懒
    2024-6-14 16:03
  • 签到天数: 16 天

    [LV.4]偶尔看看III

    0

    主题

    1万

    回帖

    7618

    积分

    二级逆天

    积分
    7618

    终身成就奖特殊贡献奖原创先锋奖优秀斑竹奖

    QQ
    发表于 2020-5-4 00:02:02 | 显示全部楼层
    回复

    使用道具 举报

    该用户从未签到

    2

    主题

    1542

    回帖

    0

    积分

    二级逆天

    积分
    0

    终身成就奖优秀斑竹奖

    发表于 2020-5-4 00:28:21 | 显示全部楼层
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-5-29 20:33
  • 签到天数: 1 天

    [LV.1]初来乍到

    48

    主题

    6493

    回帖

    7755

    积分

    二级逆天

    积分
    7755

    终身成就奖特殊贡献奖原创先锋奖优秀斑竹奖

    QQ
    发表于 2020-5-4 08:06:21 | 显示全部楼层
    回复

    使用道具 举报

    该用户从未签到

    13

    主题

    1111

    回帖

    2673

    积分

    二级逆天

    积分
    2673

    社区居民终身成就奖

    QQ
    发表于 2020-5-4 08:24:36 | 显示全部楼层
    回复

    使用道具 举报

    该用户从未签到

    13

    主题

    112

    回帖

    0

    积分

    二级逆天

    积分
    0

    终身成就奖特殊贡献奖原创先锋奖

    发表于 2020-5-4 08:35:27 | 显示全部楼层
    回复

    使用道具 举报

    该用户从未签到

    1

    主题

    4704

    回帖

    5

    积分

    二级逆天

    积分
    5

    终身成就奖特殊贡献奖原创先锋奖优秀斑竹奖

    发表于 2020-5-4 09:17:17 | 显示全部楼层
    回复

    使用道具 举报

  • TA的每日心情
    慵懒
    昨天 14:29
  • 签到天数: 35 天

    [LV.5]常住居民I

    82

    主题

    1万

    回帖

    9883

    积分

    二级逆天

    积分
    9883

    社区居民社区劳模原创先锋奖终身成就奖特殊贡献奖优秀斑竹奖

    QQ
    发表于 2020-5-4 09:29:33 | 显示全部楼层
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-5-27 18:59
  • 签到天数: 1 天

    [LV.1]初来乍到

    0

    主题

    599

    回帖

    1049

    积分

    二级逆天

    积分
    1049

    终身成就奖

    发表于 2020-5-4 09:32:16 | 显示全部楼层
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    Copyright ©2011-2024 NTpcb.com All Right Reserved.  Powered by Discuz! (NTpcb)

    本站信息均由会员发表,不代表NTpcb立场,如侵犯了您的权利请发帖投诉

    平平安安
    TOP
    快速回复 返回顶部 返回列表