STM32单击,双击和长按按键问题

请问我的按键操作的代码有什么问题吗,如何修改?
Buttom_Scan函数要分别实现单击,双击,长按按键
单击时,灯的亮灭周期为1秒
双击时,灯的亮灭周期为2秒
长按时,灯的亮灭周期为长按的时间
当灯有现象时,单击按键使其熄灭


#出现的问题:
问题1:
单击时,得到的total_time

img


双击时,得到的total_time (双击时的total_time有一个变化,先变大后变小,先是与上图差不多的一个数,然后变为下图的数)

img


导致双击按键时,出现单击按键的现象,单击按键时没有现象

问题2:
当有现象时,单击按键不能返回原来状态,即不能让灯灭

问题3:
有的时候双击也没有现象,在调试时发现,有时候双击完后,count不知为何会一直增加,有时count不增加但就是没现象


以下为GPIO,定时器TIM3的初始化函数和中断回调函数

uint8_t count = 0;        //定时器溢出次数

TIM_HandleTypeDef htim3;

/* TIM3初始化 */
void TIM3_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim3.Instance = TIM3;
  //定时器设置为向上计数,时间为1秒,(7199+1)*(9999+1)/72000000
  htim3.Init.Prescaler = 7199;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 9999;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM3)
  {
    __HAL_RCC_TIM3_CLK_ENABLE();   //初始化TIM3时钟

//设置中断
    HAL_NVIC_SetPriority(TIM3_IRQn, 1, 2);
    HAL_NVIC_EnableIRQ(TIM3_IRQn);
  }
}

//中断回调函数,定时器每溢出一次,count+1
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
    if(htim->Instance == TIM3){
        count++;
        __HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);   //清除中断标志
    }
}

/*初始化GPIO*/
void GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET); //让PB5最开始为高电平,即让灯灭
//PE4,按键
  GPIO_InitStruct.Pin = GPIO_PIN_4;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

//PB5,灯
  GPIO_InitStruct.Pin = GPIO_PIN_5;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

以下是main.c

#define LED_OFF 0                //灯灭
#define ONE_PUSH_FLAG 1          //单击标记
#define DOUBLE_PUSH_FLAG 2   //双击标记
#define LONG_PUSH_FLAG 3       //长按标记

extern TIM_HandleTypeDef htim3;

extern uint16_t count;        //定时器溢出次数
uint16_t end_count;           //最后获取的定时器的值
uint16_t total_time;          //总时间,单位毫秒

static uint8_t Buttom_Flag = 0;
/*按键扫描函数*/
/*按键按下,引脚呈低电平*/
uint8_t Buttom_Scan(void){
    if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == 0){
        HAL_Delay(20);
        if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == 0){
            HAL_TIM_Base_Start_IT(&htim3);                      //使能中断和计数器
            __HAL_TIM_SET_COUNTER(&htim3, 0);          //将定时器设置为0
            while(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == 0);
            end_count = __HAL_TIM_GET_COUNTER(&htim3);     //获取定时器的值
            HAL_TIM_Base_Stop_IT(&htim3);                       //失能中断和计数器
            total_time = 1000 * count + end_count / 10;
            if(total_time > 2000){                              //大于2秒,长按
                Buttom_Flag = LONG_PUSH_FLAG;
            }
            else if(total_time >= 0 && total_time <= 200){      //多于0秒,小于0.2秒,单击
                if(Buttom_Flag != LED_OFF){
                    Buttom_Flag = LED_OFF;                      //当灯亮着时,单击按键使灯熄灭
                }
                else{
                    Buttom_Flag = ONE_PUSH_FLAG;
                }
            }
        }
        else if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == 1){
            HAL_TIM_Base_Start_IT(&htim3);                      //使能中断和计数器
            __HAL_TIM_SET_COUNTER(&htim3, 0);          //将定时器设置为0
            while(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == 1);
            end_count = __HAL_TIM_GET_COUNTER(&htim3);     //获取定时器的值
            HAL_TIM_Base_Stop_IT(&htim3);                       //失能中断和计数器
            while(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) == 0);
            total_time = 1000 * count + end_count / 10;
            if(total_time > 200 && total_time <=1000){          //在1秒内按两次,双击
                Buttom_Flag = DOUBLE_PUSH_FLAG;
            }
        }
    }
    count = 0;  //清零
    return Buttom_Flag;
}

int main(void)
{

  uint8_t buttom;
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_TIM3_Init();

  while (1)
  {
      buttom = Buttom_Scan();
      if(buttom == LED_OFF){
          HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
      }
      else if(buttom == ONE_PUSH_FLAG){
          HAL_Delay(500);
          HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
      }
      else if(buttom == DOUBLE_PUSH_FLAG){
          HAL_Delay(1000);
          HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
      }
      else if(buttom == LONG_PUSH_FLAG){
          HAL_Delay(total_time / 2);
          HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
      }
    
  }
 
}

【以下回答由 GPT 生成】

首先,根据提供的代码和问题描述,有几个问题需要解决:

  1. 单击时得到的total_time与预期不符。
  2. 当有现象时,单击按键不能返回原来状态,即不能让灯灭。
  3. 有时双击也没有现象,在调试时发现,有时双击完后,count会一直增加,有时count不增加但就是没有现象。

针对这些问题,我给出以下解决方案:

  1. 单击时得到的total_time与预期不符。

问题的关键是如何准确计算单击的时间。在原有的代码中,并没有给出计算total_time的部分。我们可以通过在按键按下和松开时分别获取系统时间,然后计算两个时间之差来获取按键持续的时间。具体的修改如下:

uint32_t press_time;       // 记录按键按下的时间
uint32_t release_time;     // 记录按键松开的时间
uint32_t total_time;       // 记录按键总共持续的时间

// 在按键按下时调用
void Button_Pressed(void)
{
    press_time = HAL_GetTick();   // 获取按下时的系统时间
}

// 在按键松开时调用
void Button_Released(void)
{
    release_time = HAL_GetTick();   // 获取松开时的系统时间
    total_time = release_time - press_time;   // 计算按键持续的时间
}

然后在 Buttom_Scan 函数中调用 Button_Pressed 函数和 Button_Released 函数即可。

  1. 当有现象时,单击按键不能返回原来状态,即不能让灯灭。

根据提供的代码,没有在按键单击时关闭灯的部分。我们可以添加一个函数来控制灯的亮灭,并在单击时调用该函数关闭灯。具体的修改如下:

// 控制灯的亮灭
void Toggle_Led(void)
{
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);   // 假设灯的控制引脚是PB0
}

// 在单击时调用
void Button_Clicked(void)
{
    Toggle_Led();   // 关闭灯
}

然后在 Buttom_Scan 函数中调用 Button_Clicked 函数。

  1. 有时双击也没有现象,在调试时发现,有时双击完后,count会一直增加,有时count不增加但就是没有现象。

这个问题可能是由于双击事件的判断条件不准确引起的。我们可以增加一个计时器,在第一次点击后,如果一定时间内没有第二次点击,则认为双击事件结束。具体的修改如下:

uint8_t click_count = 0;        // 记录点击的次数
uint32_t prev_click_time;      // 记录上一次点击的时间
uint32_t double_click_time = 300;   // 设定两次点击之间的最大时间间隔为300毫秒

// 在单击时调用
void Button_Clicked(void)
{
    uint32_t current_time = HAL_GetTick();   // 获取当前的系统时间
    if (click_count == 0)
    {
        prev_click_time = current_time;   // 记录第一次点击的时间
        click_count++;
    }
    else if (click_count == 1)
    {
        if (current_time - prev_click_time <= double_click_time)
        {
            // 双击事件发生
            Toggle_Led();   // 关闭灯
            click_count = 0;   // 重置点击次数
        }
        else
        {
            // 单击事件发生
            prev_click_time = current_time;   // 更新点击时间
            click_count++;
        }
    }
}

然后在 Buttom_Scan 函数中调用 Button_Clicked 函数即可。

最后,根据以上的修改方案,更新 Asnwer.md 文件。


如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^