延时消抖与定时器消抖

一、按键抖动

按键抖动是电子设备中常见的问题,尤其是在机械开关如按键中。抖动发生是因为当按键被按下或释放时,接触点之间的快速接触和断开导致信号线上产生快速的电平变化,这可能导致微控制器错误地识别多次按键动作。

按键抖动的时间一般为10~50ms

二、硬件消抖

硬件消抖主要是通过两种方式实现:RS触发器和电容滤波器。

  1. RS触发器

    • RS触发器利用其特性来吸收按键的抖动。当按键被按下时,触发器会立即翻转状态,而按键的抖动不会影响输出,因为触发器的状态改变是瞬间的,不受后续抖动的影响
  2. 电容滤波

    • 电容滤波器通过并联一个电容器在按键两端,利用电容的充放电特性来消除抖动。当按键产生抖动时,电容器会吸收这些快速变化的电平,从而减少抖动对电路的影响。

这两种硬件消抖方法可以有效减少按键抖动对电路的影响,确保信号的稳定性。在没有MCU的情况下,硬件消抖电路是常用的消抖手段。而在嵌入式开发中,通常通过软件消抖来实现,因为这种方法成本较低。

硬件消抖会额外增加硬件设施,会有更多的成本,并且不能保证完全可靠,因此更多时候需要添加软件消抖,常见的软件消抖有延时消抖和定时器消抖。

三、延时消抖

延时消抖是一种常用的软件消抖方法,其基本原理是在检测到按键状态变化后,通过引入一定的延时来等待抖动消失,然后再进行一次状态检测以确认按键的真实状态。这种方法简单易实现,但也存在一些缺点,比如会占用CPU资源,影响程序的实时性,以及不够精准。以下是具体的实现步骤和代码示例:

        1.延时消抖原理:

                当检测到按键按下时,先等待一个固定的延时时间(通常为10ms~50ms),这段时间足以让大部分由接触不良引起的抖动消失。延时过后,再次检测按键状态,如果按键仍然处于按下状态,则认为是有效的按键操作。

        2.代码实现:

#include "stm32f4xx.h"

static GPIO_InitTypeDef GPIO_InitStructure;
static EXTI_InitTypeDef EXTI_InitStructure;
static NVIC_InitTypeDef NVIC_InitStructure;

void delay_ms(uint32_t ms)
{
	while(ms --)
	{
		SysTick->CTRL = 0; 			// 关闭系统定时器后才能配置寄存器
		SysTick->LOAD = 21000; 			// 设置计数值,用于设置定时的时间
		SysTick->VAL = 0; 			// 清空当前值还有计数标志位
		SysTick->CTRL = 1; 			// 使能系统定时器工作,且时钟源为系统时钟的8分频(168MHz/8=21MHz)
		while ((SysTick->CTRL & (1<<16))==0);	// 等待系统定时器计数完毕
		SysTick->CTRL = 0; 			// 关闭系统定时器	
	}
}

#define PEout(n)	(*(volatile uint32_t *)(0x42000000+(GPIOE_BASE+0x14-0x40000000)*32+n*4))
#define PFout(n)	(*(volatile uint32_t *)(0x42000000+(GPIOF_BASE+0x14-0x40000000)*32+n*4))
#define PAin(n)		(*(volatile uint32_t *)(0x42000000+(GPIOA_BASE+0x10-0x40000000)*32+n*4))
	

static uint32_t g_key_event=0;//按键标志位

int main(void)
{	
	/* 打开端口A的硬件时钟(就是对硬件供电),默认状态下,所有时钟都是关闭 */
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
	
	/* 打开端口F的硬件时钟(就是对硬件供电),默认状态下,所有时钟都是关闭 */
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
	
	
	/* 打开系统配置syscfg时钟(就是对硬件供电),默认状态下,所有时钟都是关闭 */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);	
	
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9|GPIO_Pin_10;//指定9 10号引脚
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;//引脚工作在输出模式
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度越高,响应时间越短,但是功耗就越高,电磁干扰也越高
	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//如果外部没有上拉电阻,就配置推挽输出模式
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不需要使能上下拉电阻
	GPIO_Init(GPIOF,&GPIO_InitStructure);	
	
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;//指定0号引脚
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN;//引脚工作在输入模式
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;//速度越高,响应时间越短,但是功耗就越高,电磁干扰也越高
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不需要使能上下拉电阻
	GPIO_Init(GPIOA,&GPIO_InitStructure);

	/* 将外部中断连接到指定的引脚,特别说明:引脚编号决定了使用哪个外部中断 */
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
	
	/* 配置外部中断0 */
	EXTI_InitStructure.EXTI_Line = EXTI_Line0; //指定外部中断0
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//工作在中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发:就是发现有从高电平到低电平的跳变,就向CPU中断请求处理  
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;//允许外部中断0工作
	EXTI_Init(&EXTI_InitStructure);
	
	/* 通过NVIC管理外部中断0的中断请求:中断号、优先级、中断打开/关闭 */
	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//中断号
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;//响应优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//中断打开
	NVIC_Init(&NVIC_InitStructure);
	
	
	PFout(9) =1;
	PFout(10)=1;
	
	while(1)
	{
		if(g_key_event)
		{
			delay_ms(50);
			
			if(PAin(0)==0)
				PFout(9)^=1;
			
			//等待按键的释放
			while(PAin(0)==0);
			
			//释放时也消抖
			delay_ms(50);
			
			//等待按键的释放
			while(PAin(0)==0);			
			
			g_key_event=0;
			
			/* 打开EXTI0外部中断请求 */
			NVIC_EnableIRQ(EXTI0_IRQn);			
		}
	}
}

/* 中断服务函数 */
void EXTI0_IRQHandler(void)
{
	/* 检测中断是否有触发 */
	if(EXTI_GetITStatus(EXTI_Line0) == SET)
	{
		g_key_event=1;	
		/* 关闭EXTI0外部中断请求 */
		NVIC_DisableIRQ(EXTI0_IRQn);	
        //清空标志位		
		EXTI_ClearITPendingBit(EXTI_Line0);
	}
}

按键消抖简单方便,但是在裸机编程中,延时会占用CPU。在RTOS(实时操作系统)中vTaskDelay()可以通过睡眠延时进行任务调度,几乎不会占用CPU。

四、定时器消抖

定时器消抖的原理是基于时间延迟来确保按键状态的稳定性。当按键被按下或释放时,由于机械接触的不稳定性或电气噪声,可能会在短时间内产生多次快速的电平变化,这种现象称为抖动。定时器消抖的目的是等待这段不稳定的时间过去,然后确认按键的真实状态。以下是定时器消抖的详细步骤和原理:

  1. 检测按键状态

    在程序中,首先检测按键的当前状态,例如,检查GPIO引脚的电平是否表示按键被按下。
  2. 启动定时器

    如果检测到按键状态发生变化(例如,从未按下变为按下),则启动一个定时器,并设置一个适当的延时时间,通常这个时间足够长,可以覆盖大多数抖动事件,但又要足够短,以免影响用户体验,常见的延时时间为几十毫秒。
  3. 等待定时器超时

    程序等待定时器超时。在这个期间,CPU可以继续执行其他任务,或者进入低功耗模式等待定时器超时。
  4. 确认按键状态

    定时器超时后,再次检查按键状态。如果按键状态与初始检测时相同(例如,仍然是按下状态),则认为这是一个有效的按键动作,可以执行相应的处理逻辑。
  5. 处理按键事件

    如果确认按键动作有效,执行相应的按键处理程序,如切换LED状态、发送命令等。
  6. 清除定时器

    完成按键处理后,清除定时器中断标志,准备下一次按键检测。

通过这种方法,定时器消抖可以有效滤除由于按键抖动引起的错误信号,确保只有在按键状态稳定后,才将其视为有效的用户输入。这种方法简单、可靠,且不会占用过多的CPU资源,因为它允许CPU在等待定时器超时期间执行其他任务。

代码实现

void key_init(void)
{
	//使能AHB1总线上指定外设的硬件时钟,其实就是对外设进行供电 (如果该外设不使用,可以关闭其硬件时钟,降低功耗)
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
		
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE);//打开中断控制时钟

	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN;//输出模式
//	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;//推挽类型,Push Pull
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_2MHz;//引脚工作速度
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;//不使能上下拉电阻
	//对GPIOF初始化
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	/* 将外部中断连接到指定的引脚,特别说明:引脚编号决定了使用哪个外部中断 */
	SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
	
	/* 配置外部中断0 */
	EXTI_InitStructure.EXTI_Line = EXTI_Line0; //指定外部中断0
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//工作在中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发:就是发现有从高电平到低电平的跳变,就向CPU中断请求处理  
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;//允许外部中断0工作
	EXTI_Init(&EXTI_InitStructure);
	
	/* 通过NVIC管理外部中断0的中断请求:中断号、优先级、中断打开/关闭 */
	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//中断号
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;//响应优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//中断打开
	NVIC_Init(&NVIC_InitStructure);
	
}



void tim2_init(void)
{
	//使能TIM2的硬件时钟,就是对它供电
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	//配置TIM2的定时时间
	TIM_TimeBaseStructure.TIM_Period = 10000/40-1;//用于设置自动重载寄存器的值,决定了定时时间,当前定时为1秒
	TIM_TimeBaseStructure.TIM_Prescaler = 8400-1;//预分频值,将该值传递给预分频的寄存器,会自动加1,实际就是除以16800
	//TIM_TimeBaseStructure.TIM_ClockDivision = 2;//当前stm32f407是不支持的,它是属于时钟分频,也是二次分频
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数,0 ->9999 就是1秒时间的到达
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
	
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//配置TIM8的中断触发条件:更新中断(UI)
	
	//配置TIM2的中断:优先级、IRQ通道要使能
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	//使能TIM8工作
	TIM_Cmd(TIM2,ENABLE);

}

int main()
{
	led_init();
	key_init();
	tim2_init();
	
	while(1)
	{
		
	}
}

void TIM2_IRQHandler(void)
{
	//检测是否有更新中断
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
	{
		if(PAin(0)==0)
		{
			PFout(9)^= 1;
		}
		
			/* 关闭定时器2 */
		TIM_Cmd(TIM2,DISABLE);
		
		//清空标志位,告诉CPU当前中断不需要再服务,已经完成处理
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
	}
}

void EXTI0_IRQHandler(void)
{
	/* 检测中断是否有触发 */
	if(EXTI_GetITStatus(EXTI_Line0) == SET)
	{
		//清空标志位
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
		
		//关闭定时器
		TIM_Cmd(TIM2,DISABLE);
		
		//清空当前计数值
		TIM_SetCounter(TIM2,0);
		
		//重新启动定时器
		TIM_Cmd(TIM2,ENABLE);

		//清空标志位
		EXTI_ClearITPendingBit(EXTI_Line0);
	}
}

定时器消抖不会占用CPU,但是在使用时会占用多一个定时器资源,因此,在实际应用中,根据实际需求进行取舍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值