历史上的今天

今天是:2024年10月06日(星期日)

2018年10月06日 | STM32单片机按键消抖和FPGA按键消抖大全

发布者:trendsetter9 来源: eefocus关键字:STM32  单片机  按键消抖 手机看文章 扫描二维码
随时随地手机看文章

写在前面:


STM32单片机按键消抖和FPGA按键消抖大全

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仿真,但还没有在实际的项目中验证。在整理准备这个博客的时候,我又想到了一个感觉是更巧妙的方案,具体是这样的:在第三个方案的基础上,因为按键输入有变化的第一时刻,输出就已经改变了,在这种情况下,我可以把计时的时长改为一个很小的值,该值只要比抖动中的最长高低电平变化时间长即可。但想想也没这个必要,且这个抖动的高低电平变化时长我也很难去给它界定一个值。


关键字:STM32  单片机  按键消抖 引用地址:STM32单片机按键消抖和FPGA按键消抖大全

上一篇:stm32的库文件的用法解释
下一篇:stm32单片机检测12V电路

推荐阅读

   最近,苹果和高通之间的口水战打的火热,英特尔也被卷了进来。苹果新发布的iPhone XS和iPhone XS Max毅然弃用了高通的基带芯片,转而全部采用英特尔的基带芯片。高通很不爽,指责苹果窃取其技术机密,用来帮助英特尔提高芯片性能。苹果和英特尔则矢口否认。一时间,芯片圈也好,手机圈也好,这都成了最为火爆的新闻。不怕事大的我们,都搬着小板...
正受经济增长、城市化以及视频监控技术发展所驱动,全球视频监控市场保持续强劲的两位数增长,中国是其中最大、增长最快的市场,近年来表现持续强劲,发展速度超过全球其他地区。在技术和方案实践层面,随着智慧城市的兴起,中国也因快速发展和容量巨大,成为应用和锤炼全球最先进技术方案的场所。近年来,伴随无线通信技术的发展,以及人工智能等技术广泛...
  工业机器人目前在工业中逐渐适用,工业机器人将代替人工重复性劳动。现实中,更多工厂均采用进口工业机器人,那国产工业机器人与其到底有何差距呢?此外,目前工业机器人又有何应用呢?本文将揭露这些问题的答案。如果你对本文具有兴趣,不妨继续往下阅读哦。   一、国内外工业机器人差距分析   在我国工业机器人市场的需求一直在持续增长,但...
来源:学习军团·解放军新闻传播中心融媒体作者:解放军报记者 韩 成 通讯员 于 晨“机器人技术正在深刻改变着人类的生产和生活方式,中国空间站机械臂也助力我们完成了两次出舱任务。”9月中旬,在北京亦庄开幕的2021世界机器人大会上,神舟十二号飞行乘组3位航天员聂海胜、刘伯明、汤洪波从中国空间站传来“太空点赞”。被表扬的主角,是我国自主研...

史海拾趣

问答坊 | AI 解惑

wince中断如何写?

我知道wince中中断分中断服务例程(ISR)和中断服务线程(IST),那我要写一个按键的中断,需要完成哪些工作? 下面是终端过程: ①当内核的异常处理代码接收到一个来自硬件的中断时,内核会侦测到一个异常情况发生,并会提交这个硬件中断。 ②内核 ...…

查看全部问答∨

能帮忙解释下么

file:///C:/Documents%20and%20Settings/Administrator/桌面/未命名.jpg][img=http:///C:/Documents%20and%20Settings/Administrator/桌面/未命名.jpg[/img]LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; ENTITY TEST IS PORT(CLK,CLR:IN BIT; D ...…

查看全部问答∨

boot loader引导内核

boot loader是如何引导内核的? 现在我的boot loader已经正常运行,kernel image也已经build,可是我想通过boot loader下载kernel image到flash,然后引导kernel的运行,但是每次都会出现“data abort”异常,请问是boot loader的那个部分出了问题 ...…

查看全部问答∨

关于一个串口类定义两个子串口类对象,和两个通信协议的问题.

问题描述: 一个串口类,采用线程,然后定义两个类对象,分别用于两个通信协议, RS485的MODBUS通信协议(假设为A协议)和用于GPRS上的通信协议(假设为B协议), 那么我原本是在串口类中采用回调函数来处理接收到的数据, 但是由于,底层的驱动是是每接收 ...…

查看全部问答∨

STM32F103串口dma发送与中断接收能同时进行么?

                                 在本人的设计中,STM32的USART(波特率115200)需每秒发送4000+字节到上位机。开启了STM32串口dma发送方式(normal mo ...…

查看全部问答∨

版主,ST的新网站什么时候能弄好,我想下个新版的usb库

这两天在做一个SD卡+usb mass storage的原型验证 老版的库被我误删除了 新网站貌似还没完全好 链接打不开 或者页面部分出处 方便的话发份给俺把 谢了 east3@163.com 老网站挺好的么 看来老外也喜欢折腾 哈哈…

查看全部问答∨

同一个always中对同一个变量多次赋值的不同结果

在同一个变量中,不要对同一个变量赋值,如果对同一变量多次赋值,那么它只执行最后一次赋值操作。 module test(clk,datain,dataout); input clk;input [2:0] datain;output [2:0] dataout; reg [2:0] dataout; always@(posedge clk)begin& ...…

查看全部问答∨

单片机学习进入迷茫期

各位大侠:    本人现进入打片机迷茫期,不知道应该学什么了,前段时间编了个51和数码管做得时钟,现在不知道接下来该学习什么了,请各位大侠指点一二!谢谢!…

查看全部问答∨

新手学单片机

我刚学单片机,想写个按键控制8个灯依次亮的程序,但总是顺序不对,不知道问题出在哪,肯请各位高手指点一下.不胜感激!!         #include <REG52.h> //52头文件 #define DY_PORT  P0 //设置LED连接的I/O组sbi ...…

查看全部问答∨

问个5.6寸TFT驱动的问题

想搞个设计,把CVBS电视信号转换到群创5.6寸TFT LCD屏显示,请高手指引一下方向。 看过有公司用台湾MST芯片的方案做的产品,但苦于找不到资料,不知有没有其它的好方案?…

查看全部问答∨
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved