问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

手把手教你如何按键消抖

创作时间:
作者:
@小白创作中心

手把手教你如何按键消抖

引用
CSDN
1.
https://blog.csdn.net/m0_63235356/article/details/144484188

我们经常能使用到按键,然而由于按键的机械结构,导致了按键并不会完全像我们想象的那样工作。

理想的波形是上面那个,当我们一按下按钮,马上就把按键两端的电路连接起来,松开按键的时候两端电路再断开。

然而实际情况是下面那个波形,当我们按下按钮的时候,会有一段时间的不稳定状态,就是处于连接和断开之间的状态,也就是按键的抖动。

而我们一般是拿按键来用作外部中断的,外部中断条件常用的就是上升沿和下降沿(后续文章我们默认用的是下降沿触发中断),当按键在抖动的时候,很有可能触发多次外部中断,所以按键消抖是我们不得不面对的一个问题。

接下来的示例代码我用的STC库函数,也就是我最近开的一个系列,正好前天讲的是外部中断,昨天讲的定时器。

只要知道了原理,那么到其他芯片上也是一样的,用STC库函数来作为示例代码是因为最近正好在用。

第一个方法就是延时,假设我们遇到了下降沿,那么我们马上进行延时,一般来说按键的抖动时间不会超过10ms,越好(贵)的按键抖动时间越短。

经过我实测,延时7个ms就基本没什么问题了,延时过后直接就把按下的抖动给忽略掉了。

不过小伙伴们最好还是自己测试一下延时时间,因为不同的按钮抖动时长是不一样的。

这样就解决了我们按下按键的抖动问题,还有个问题就是我们松开按键的时候也是有抖动的,不过一般来说松开抖动的持续时间会比按下抖动的持续时间更短。

当我们松开按键的时候也有可能触发下降沿中断,所以在我们延时过后还需要再判断一下连接按键的引脚的电平,如果这时候还是低电平,那么才真正执行应该执行的处理代码。

#include "STC32G_GPIO.h"
#include "STC32G_Delay.h"
#include "STC32G_NVIC.h"
#include "STC32G_Exti.h"
void GPIO_Init(void){
    P3_MODE_IN_HIZ(GPIO_Pin_2);     // 将外部中断引脚配置为高阻输入
    P3_PULL_UP_ENABLE(GPIO_Pin_2);  // 将外部中断引脚使能上拉电阻
    P2_MODE_OUT_PP(GPIO_Pin_7);     // 将点灯引脚初始化为推挽输出.
}
void Exti_Init(void){
    EXTI_InitTypeDef initer;
    initer.EXTI_Mode = EXT_MODE_Fall;       // 下降沿触发中断
    Ext_Inilize(EXT_INT0, &initer);
    NVIC_INT0_Init(ENABLE, Priority_2);
}
void main(void){
    WTST = 0;		//设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
    EAXSFR();		//扩展SFR(XFR)访问使能 
    CKCON = 0;      //提高访问XRAM速度
    GPIO_Init();
    Exti_Init();
    EA = 1;         // 开启中断
    while(1){
        if(WakeUpSource == 1){  // 当下降沿中断触发了
            WakeUpSource = 0;   // 清除中断标志
            delay_ms(7);        // 延时
            // 再次判断外部中断引脚电平,确认无误之后再翻转电平
            if(P32 == 0)    P27 = !P27; 
        }
    }
    return ;
}

虽然延时的方法简单易用,但是并不适用大多数应用场景,因为延时是会阻塞我们的程序的,因此我们可以用定时器的方式代替延时。

当我们第一次收到下降沿之后,我们开启定时器,定时个5ms,等到5ms过后,我们再次判断外部中断引脚的电平。

这边还有个要注意的是只有第一次收到下降沿的时候才开启定时器,也就是说当我们定时器正在运行的时候,我们忽略掉其他下降沿,因为那些大概率是抖动。

#include "STC32G_Delay.h"
#include "STC32G_Exti.h"
#include "STC32G_GPIO.h"
#include "STC32G_Timer.h"
#include "STC32G_NVIC.h"
uint8 timer0_open_flag = 0;
uint8 timer0_over_flag = 0;
// 定时器0中断函数
void Timer0_ISR_Handler(void) interrupt TMR0_VECTOR { 
    ET0 = 0;
    timer0_over_flag = 1;
    timer0_open_flag = 0;
}
void GPIO_Init(void) {
    P3_MODE_IN_HIZ(GPIO_Pin_2);     // 将外部中断引脚配置为高阻输入
    P3_PULL_UP_ENABLE(GPIO_Pin_2);  // 将外部中断引脚使能上拉电阻
    P2_MODE_OUT_PP(GPIO_Pin_7);     // 将点灯引脚初始化为推挽输出. 
}
void Exti_Init(void) {
    EXTI_InitTypeDef initer;
    initer.EXTI_Mode = EXT_MODE_Fall;  // 下降沿触发中断
    Ext_Inilize(EXT_INT0, &initer);
    NVIC_INT0_Init(ENABLE, Priority_1);
}
void Timer_Init(void) {
    TIM_InitTypeDef initer;
    initer.TIM_Mode = TIM_16BitAutoReload;          // 模式0,16位自动重装载寄存器.
    initer.TIM_ClkOut = DISABLE;                    // 失能可编程时钟输出
    initer.TIM_ClkSource = TIM_CLOCK_1T;            // 1T工作模式
    initer.TIM_Value = 55536;   // 计数寄存器和重装载寄存器的值设为55536
    initer.TIM_PS = 11;         // 预分频器的值设为11,实际上是12分频
    initer.TIM_Run = ENABLE;  
    Timer_Inilize(Timer0, &initer);
    NVIC_Timer0_Init(ENABLE, Priority_2);
    ET0 = 0;
}
void Open_Timer(void) {
    T0_Load(55536);
    ET0 = 1;
}
void main(void) {
    WTST = 0;       // 设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
    EAXSFR();       // 扩展SFR(XFR)访问使能
    CKCON = 0;      // 提高访问XRAM速度
    GPIO_Init();
    Timer_Init();
    Exti_Init();
    EA = 1;         // 开启中断
    while (1) {
        if (WakeUpSource == 1) {  // 当下降沿中断触发了
            WakeUpSource = 0;
            // 若是定时器没开启,那么开启
            if (timer0_open_flag == 0){
                timer0_open_flag = 1;
                Open_Timer();
            }
        }
        if(timer0_over_flag == 1){
            // 定时器结束,若外部中断引脚仍为低电平,那么翻转电平
            timer0_over_flag = 0;
            if(P32 == 0)    P27 = !P27;
        }
    }
    return;
}

我上面代码中定时器0的中断函数是从STC32G_Timer_Isr.c里拿出来的。

我用了两个标记变量,当我们下降沿中断触发的时候,如果定时器没有开启,那么我们开启,同时把定时器开启标志置位,当定时器结束之后把定时器开启标志的那个变量置回0,同时把定时器结束标志置位。

我定时器定的是5ms,各位小伙伴要根据自己的按钮去设置合适的延时时间。

以上是软件层面的消抖方式,接下来就是硬件层面的了。

延时会阻塞程序,定时会消耗掉一个定时器的资源。

所以硬件层面的消抖也是我们经常会做的。

最简单的就是给按钮并联一个电容就行。

我常用的就是像上面那样加个100nf的电容,因为电容两端的电压不能突变,所以它可以让波形变得更平滑,也就是把抖动抹平了。

当然了,电容也不能百分百消除抖动,因为每个按键的结构都不太一样。

并且电容的取值也有讲究的,太小的话无法消除抖动,太大的话会导致我们按键按下还是没法拉低外部中断硬件的电平(一般也不会用那么大的电容)。

所以一般情况下我们无脑选个100nf就行了,然后在软件层面延时个1ms这样意思意思,或者不延时也行,效果也还是可以接受的。

如果我就是想搞清楚电容的容值放多少合适呢?

那我们就回顾一下电容的充放电公式,一个时间单位 = RC,一般来说三个时间单位我们就可以当电容充放电完成。

所以我们可以给电容再串联一个电阻,通过计算3个RC的持续长度大于抖动时间(不要大太多)来确定电容和电阻的值。

不过我们像上图那样并联个100nf就行了。

还有个不太常用的硬件解决方案,那就是加个RS触发器。

这个方案用的比较少,因为成本高,所以我们就简单提一下。

RS触发器实际上就是俩与非门,但是注意它们的连线方式。

接下来我们给AB加个上拉电阻,再加上按键控制

假设我们现在的开关接到A,那么由于A是0,所以C一定是1(与非门),因为B是1,所以D是0。

当按键进行抖动的时候,A的电平是在0和1之间飘忽不定的,重点来了,因为是与非门,A和D一起控制的C,由于D是0,所以不管A如何波动,都不会影响C和D的输出,这也就是RS消抖的原理,不过我认为实际上不是消抖,而是直接忽略了抖动。

以上就是今天分享的消抖方面的内容。

简单总结一下,软件方面是延时和定时,硬件方面是电容和RS触发器。

一般来说常用的就是并联个电容再加个小延时。

其他还有个方法是状态机,但是有点复杂,我们还是并联电容吧。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号