我们经常能使用到按键,然而由于按键的机械结构,导致了按键并不会完全像我们想象的那样工作。
理想的波形是上面那个,当我们一按下按钮,马上就把按键两端的电路连接起来,松开按键的时候两端电路再断开。
然而实际情况是下面那个波形,当我们按下按钮的时候,会有一段时间的不稳定状态,就是处于连接和断开之间的状态,也就是按键的抖动。
而我们一般是拿按键来用作外部中断的,外部中断条件常用的就是上升沿和下降沿(后续文章我们默认用的是下降沿触发中断),当按键在抖动的时候,很有可能触发多次外部中断,所以按键消抖是我们不得不面对的一个问题。
接下来的示例代码我用的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触发器。
一般来说常用的就是并联个电容再加个小延时。
其他还有个方法是状态机,但是有点复杂,我们还是并联电容吧。