#STM32#定时器扫描按键消抖#按键控制LED灯亮灭#标准库

一.机械按键抖动

在按下按键后金属弹片会来回震动影响I/O口的电平变化,影响检测和判断操作。

通常抖动时间为:5ms~10ms

影响:在不加消除抖动的情况下按下按键LED灯可能会出现失灵的情况,因为这时的判断按键情况通常是判断电平的高低,由于电平不停的发转,所以呀很难判断此时是否是被按下。

二.消除抖动的方法

 1.软件延时消抖

按下按键后我们Delay一会儿跳过这个抖动时间过去,这样就轻松的解决了这个问题。不过这样会出现cpu等待的情况,这个时候CPU干不了其他事情,就这样干等着。明显有点屈才了。

2.定时器扫描按键消抖

让硬件自己隔段时间就去看看按键的状态,要是符合情况再让CPU去处理。这样就起到不断扫描的作用。

 

三.代码的实现(基于标准库)

1.变量的定义

// 按键状态变量
volatile uint8_t key_pressed = 0;  // 0: 未按下, 1: 短按, 2: 长按
volatile uint32_t key_timer = 0;   // 按键计时器

// LED状态变量
volatile uint8_t led_state = 0;    // 0: LED灭, 1: LED亮

 volatile关键字是一种类型修饰符,用于告诉编译器某个变量的值可能会在程序外部被意外地改变。使用volatile修饰的变量,编译器在每次使用时都会重新从内存中读取它的值,而不是使用缓存中的值。

2.定时器中断(Timer_Init)

定时器为1ms中断 ,频率为1000,可以通过改变ARR与PSC的值来调整中断频率。

这里的APB1是低速外设总线最大频率为36*10^6Hz.

#include "stm32f10x.h"


/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟,TIM是外设时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数,计数的方式
	TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 3600 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断,开启了更新中断到NVIC的通路
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线,TIM2_IRQn是定时器2在NUVIC里的通道//中断号
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行,使能计数器
}


/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)	    //获取中断标志位
	{
		
            //可写操作
            

		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);         //清楚标志位
	}
}
*/

3.I/O口的配置 (IO_Init)

IO_Init函数用于初始化按键和LED引脚。按键配置为上拉输入模式,LED配置为推挽输出模式。

   

#include "stm32f10x.h"

// 定义按键和LED引脚
#define KEY_PIN GPIO_Pin_1
#define KEY_PORT GPIOB
#define LED_PIN GPIO_Pin_1
#define LED_PORT GPIOA


// 初始化按键和LED引脚
void IO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;

    // 使能GPIOA和GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);

    // 配置按键引脚为输入模式,上拉
    GPIO_InitStructure.GPIO_Pin = KEY_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY_PORT, &GPIO_InitStructure);

    // 配置LED引脚为输出模式
    GPIO_InitStructure.GPIO_Pin = LED_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(LED_PORT, &GPIO_InitStructure);

    // 初始化LED状态
    GPIO_ResetBits(LED_PORT, LED_PIN);  // LED灭,高电平
}

 4.中断函数进行操作(TIM2_IRQHandler)

  • 如果按键按下时间超过200ms,则认为是长按;否则,如果之前的状态是高电平(未按下),并且当前检测到低电平(按下),则认为是短按。
  • TIM2_IRQHandler函数是TIM定时器的中断服务函数,用于实现按键扫描、消抖处理和LED控制。
  • 使用key_timer变量来计时按键按下的时间。
  • 当按键释放时,如果之前检测到按键按下(无论是短按还是长按),则切换LED的状态。
  • /**
      * 函    数:TIM2中断函数
      * 参    数:无
      * 返 回 值:无
      * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
      *           函数名为预留的指定名称,可以从启动文件复制
      *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
      */
    void TIM2_IRQHandler(void){
    	
    	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){			//判断是否是TIM2的更新事件触发的中断
    	
    		static uint8_t last_key_state = 1;  					// 初始化为1,因为按键默认是高电平(未按下)
       
          // 读取按键状态
            uint8_t current_key_state = GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN);
            
            // 消抖处理
            if (current_key_state == 0) {  			// 按键按下(低电平)
                key_timer++;
            
                if (key_timer >= 200) {  			// 假设长按时间为200ms
                    key_pressed = 2;  				// 长按
                }
                else if (last_key_state == 1) {  	// 确保不是抖动
                    key_pressed = 1;  				// 短按
                }
            }
            else {
                // 按键释放
                if (key_pressed == 1 || key_pressed == 2) {
                    // 如果之前检测到按键按下(无论是短按还是长按),则处理按键事件
                    key_pressed = 0;  				// 重置按键状态
            
                    // 切换LED状态
                    led_state = !led_state;
                    if (led_state) {
                        GPIO_SetBits(LED_PORT, LED_PIN);  	// LED亮
                    }
                    else {
                        GPIO_ResetBits(LED_PORT, LED_PIN);  // LED灭
                    }
                }
            
                key_timer = 0;  							// 重置计时器
            }
            
            last_key_state = current_key_state;  			// 更新上一次按键状态
    	}
    }
    

    5.主函数

    main函数的主循环中,不需要添加额外的按键处理逻辑,因为所有的按键处理和LED控制都已经在中断服务函数中完成了。 

    int main(void)
    {
    	IO_Init();        
    	Timer_Init();
    	
    	while(1){
    		
            //可以有其他的操作
    
    	}	
    }

    6.结束语

  • 示例代码中的消抖时间和长按时间可能需要根据实际硬件进行调整。
  • 在实际应用中,可能需要添加更多的按键处理逻辑或功能。
  • 确保STM32的标准外设库已经正确包含在项目中,并且已经配置了正确的系统时钟(SystemCoreClock)。
  • 作者:HFDTBS@WZW

    物联沃分享整理
    物联沃-IOTWORD物联网 » #STM32#定时器扫描按键消抖#按键控制LED灯亮灭#标准库

    发表回复