TIM定时器控制按键(按键长短按)

2 篇文章 0 订阅
1 篇文章 0 订阅

最近在做的公司给我练手的小项目,是在MCU上做一个管理系统,其中需要用按键的长短按来控制开关机。最初第一个方案是使用延时,可是很快就发现弊端,这样的长按时间很不稳定,经常出差错,所以很快就被我否定了,项目再简单也是项目毕竟不是学校里面的课设。所以我决定用TIM定时器中断来判断开关状态以此来完成长按短按功能。

一、标志位法

通过一些资料的搜集,发现使用标志位法,首先完成TIM定时器的初始化,我这里使用的是CUBE。

首先打开TIM定时器并且初始化好,以及TIM定时器更新中断:

配置好分频系数以及计数器周期。

打开定时器更新中断。

这样关于定时器的初始化就完成了,其他的外设大家可以根据需要配置。

最终得到初始化好的代码:

#include "tim.h"

TIM_HandleTypeDef htim1;

/* TIM1 init function */
void MX_TIM1_Init(void)
{

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 800-1;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 100-1;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */
    HAL_TIM_Base_Start_IT(&htim1); //使能定时器 1和定时器 1 更新中断
  /* USER CODE END TIM1_Init 2 */

}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM1)
  {
    __HAL_RCC_TIM1_CLK_ENABLE();

    HAL_NVIC_SetPriority(TIM1_UP_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM1_UP_IRQn);
  }
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM1)
  {
    __HAL_RCC_TIM1_CLK_DISABLE();
    HAL_NVIC_DisableIRQ(TIM1_UP_IRQn);

  }
}

其中,第三十五行代码是用来使能定时器以及定时器更新中断的,这个需要手动添加,不然定时器和中断不会工作:

HAL_TIM_Base_Start_IT(&htim1);

定时器配置好了,那么我们来算算多久会激活一次定时器更新中断,上述代码可见我的分频系数是800,周期是100。

定时器的时钟为 8Mhz,分频系数为 800,所以分频后的计数频8Mhz/800=10KHz,然后计数到 100,所以时长为 100/10000=0.01s,也就是 10ms。所以说每1ms都会触发一次定时器更新中断。所以我们在中断回调函数里面判断开关状态:

1、首先设置按键标志位(为了方便看清楚,提供一个思路,不然一组标志位就够了):

int iButtonCount;      //ButtonCount表示按键计数变量
int iButtonFlag;       //ButtonFlag表示重按键标志,1代表重新按键_0为没有重新按键
int g_iButtonState;    //g是globle代表全局变量,会在其他地方引用;ButtonState表示按键标志_1代表按下_0代表松开

int longcount;
int longflag;
int longstate;

2、编写中断回调函数(两组标志位完全没卵用,只是分开给人看着更清晰一些):

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//按键中断回调
{
 if(htim==(&htim1))
 {
     
    if( HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3) == GPIO_PIN_RESET )//如果弿关丿引脚棿测到为使
    {    
        longcount++;
        iButtonCount++; //按键按下,计数iButtonCount加一
        if(iButtonCount>=30) //1ms中断服务函数里运行一次,iButtonCount大于等于10,即按键已稳定按30ms
        {
            if(iButtonFlag==0) //判断有没有重按键
            {
                g_iButtonState=1; //设置按键标志
                iButtonCount=0;
                iButtonFlag=1; //设置重按键标志
            }
            else //如果重按键,则重新计数
            iButtonCount=0; 
        }
        else //如果没有稳定按下10ms,则代表没有按下按键
            g_iButtonState=0;
        
       if(longcount >= 300)//3s长按
       {
           if(longflag == 0)
           {
              longState = 1;
              longcount = 0;
              longflag = 1;
           }
           else
            longcount = 0;//如果重按键,则重新计数
       }
        else
         longState = 0;//如果没有稳定按下3s,则代表没有长按
   }
   else //如果一直没有检测到测到低电平,即一直无按键按下
   {
        iButtonCount=0; //清零iButtonCount
        g_iButtonState=0; //清除按键标志
        iButtonFlag=0; //清除重按键标志

        longState = 0;
        longcount = 0;
        longflag = 0;
   }
  }
}

在主函数加入相关判断(通过led变化分辨):

    if(g_iButtonState == 1)
    {
        HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5, GPIO_PIN_RESET);  
         g_iButtonState = 0;   
    }
    if(longState == 1)
    {
        HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5, GPIO_PIN_SET);
        longState = 0;
    }

如此功能确实是实现了,但是问题也来了,这样长按的时候短按也触发了一次,尽管是两个标志位(其实一个两个都没插别,因为终究是一个计数器)。所以这样很明显不行,不够严谨。所以这个时候就要用到状态机,将长按和短按状态划分开来。

二、状态机

切换成状态机也就说明前面的回调可以说白写了,当然前面的工作也没有完全白做,起码定时器还是需要的。状态机我没写过不清楚原理,所以是抄了一位大佬的作业,可以去看看原文(名字是「Net_Walke)。原文链接:https://blog.csdn.net/qq_34142812/article/details/119721386

首先,初始化状态枚举,第一个枚举是按键状态枚举,三个成员分别代表,检测状态,按下状态、释放状态;第二个枚举则是代表按键值,分别为空、短按、长按。

#define Key  HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)

typedef enum
{
    KEY_CHECK = 0,
    KEY_COMFIRM = 1,
    KEY_RELEASE = 2
}KEY_STATE;

typedef enum 
{
    NULL_KEY = 0,
    SHORT_KEY =1,
    LONG_KEY
}KEY_TYPE;

KEY_STATE KeyState = KEY_CHECK;  // 初始化按键状态为检测状态
u8 g_KeyFlag = 0;                // 按键有效标志,0: 按键值无效; 1:按键值有效

KEY_TYPE g_KeyActionFlag;        //用于区别长按和短按

按键可能没什么难的,但是还是会惊叹别人清晰的思路,对KEYstate进行判断:

void Key_Scan(void)
{
    static uint32_t TimeCnt = 0;
    static uint8_t lock = 0;
    switch (KeyState)
    {
        //按键未按下状态,此时判断Key的值
        case   KEY_CHECK:    
           if(Key)   
            {
                KeyState =  KEY_COMFIRM;  //如果按键Key值为1,说明按键开始按下,进入下一个状态
            }
            TimeCnt = 0;                  //计数复位
            lock = 0;
            break;
            
        case   KEY_COMFIRM:
            if(Key)                     //查看当前Key是否还是1,再次确认是否按下            
             {
                if(!lock)
                {
                    lock = 1;
                }                    
                
                TimeCnt++;  
                /*按键时长判断*/
                if(TimeCnt >= 300)            // 长按 3 s
                {
                    g_KeyActionFlag = LONG_KEY;
                    TimeCnt = 0;  
                    lock = 0;               //重新进入检查模式
                    KeyState =  KEY_RELEASE;    // 需要进入按键释放
                }                
            }   
            else                       
            {
                if(lock==1)                // 不是第一次进入,释放按键才执行
                {

                    g_KeyActionFlag = SHORT_KEY;          // 短按
                    KeyState =  KEY_RELEASE;    // 要进入按键释放状态 
                }
                
                else                          // 当前Key值为1,确认为抖动,则返回上一个状态
                {
                    KeyState =  KEY_CHECK;    // 返回上一个状态
                }
       
            } 
            break;
            
         case  KEY_RELEASE:
             if(!Key)                     //当前Key值为1,说明按键已经释放,返回检测状态
             { 
                 KeyState =  KEY_CHECK;    
             }
             break;
             
         default: 
             break;
    }    
}
//修改回调函数为:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//定时器中断回调
{
    if(htim == (&htim1))
    {    
       Key_Scan();
    }
}

最后在主函数,对回调函数返回的值进行判断:

switch(g_KeyActionFlag)
        {
            case SHORT_KEY:
                
                printf("short time\r\n");
                HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5, GPIO_PIN_RESET);
                g_KeyActionFlag = 0;        //回到空
                break;
            
            case LONG_KEY:
                //printf("long time,standy now!!\r\n");
                HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5, GPIO_PIN_SET);
                HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5, GPIO_PIN_SET);
                g_KeyActionFlag = 0;        //回到空
               // Sys_Enter_Standby();
                break;
            
            default: 
                break;
        }

这样对长按和短按有了完美划分,就不存在按键冲突问题,长按不松手是没办法进入短按的。状态之间一环扣一环,不会随随便便切换成其他状态。

  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值