当前位置:   article > 正文

STM32实现按键单击、双击、长按、连按功能,使用状态机,无延时,不阻塞_stm32按键单击双击

stm32按键单击双击

常见的按键判定程序,如正点原子按键例程,只能判定单击事件,对于双击、长按等的判定逻辑较复杂,且使用main函数循环扫描的方式,容易被阻塞,或按键扫描函数会阻塞其他程序的执行。使用定时器设计状态机可以规避这一问题。

本文在博主 老子姓李! 程序的基础上添加连按功能,整合双击功能并补充双击二次按下消抖,添加可调消抖时间,添加长按、连按、双击使能,扩展一个按键为多按键,修改在定时器运行状态机,在主函数判断,精简部分代码,并添加大量注释方便阅读。
参考博主文章【源码详解-按键状态机-简洁易懂】1.单个按键实现短按长按的功能(基于STM32)

功能介绍

本程序功能:
使用定时器状态机实现按键单击、双击、长按、连按功能。消抖时间可调,长按时间可调,双击判定时间可调,连按单击间隔可调,可选择使能长按、连按、双击功能,无延时不阻塞,稳定触发。移植只需修改读IO函数,结构体初始化和宏定义时间参数即可。

注:

  1. 在定时器状态机判定产生事件标志,在主函数处理并清除事件标志。
  2. 单击是最基本事件,除以下情况外,经过消抖后,在按键释放时触发单击事件。
  3. 使能长按后,若按键按下时间大于长按判定时间,则释放时触发长按事件,若不使能,释放时触发单击事件。
  4. 使能连按后,按住按键时持续触发连按事件,可自定义等效为单击事件。无论是否使能长按,按键长按不释放,先经过长按判定时间触发第一次连按事件,然后循环进行连按计时,每次计时结束后都会触发一次连按事件,直到按键释放,触发长按事件(使能长按),或单击事件(不使能长按)。
  5. 使能双击后,若两次单击行为之间,由释放到按下的时间小于双击判定时间,则第一次单击行为释放时不触发单击事件,第二次单击行为在释放时触发双击事件。一次单击行为在双击判定时间内无按键按下动作,之后才触发单击事件。无论是否使能长按,若上述第二次行为是长按,则第二次释放时不会触发双击事件,而是到达长按判定时间后先触发属于第一次的单击事件,然后在第二次释放按键时触发长按事件(使能长按),或单击事件(不使能长按)。

代码

头文件 my_key.h

#ifndef ___MY_KEY_H__
#define ___MY_KEY_H__
#include "main.h"
#define ARR_LEN(arr) ((sizeof(arr)) / (sizeof(arr[0]))) //数组大小宏函数

#define KEY_DEBOUNCE_TIME 10      //消抖时间
#define KEY_LONG_PRESS_TIME 500   //长按判定时间
#define KEY_QUICK_CLICK_TIME 100  //连按时间间隔
#define KEY_DOUBLE_CLICK_TIME 200 //双击判定时间
#define KEY_PRESSED_LEVEL 0 //按键被按下时的电平

//按键动作
typedef enum
{
  KEY_Action_Press,   //按住
  KEY_Action_Release, //松开
} KEY_Action_TypeDef;

//按键状态
typedef enum
{
  KEY_Status_Idle,             //空闲
  KEY_Status_Debounce,         //消抖
  KEY_Status_ConfirmPress,     //确认按下
  KEY_Status_ConfirmPressLong, //确认长按
  KEY_Status_WaitSecondPress,  //等待再次按下
  KEY_Status_SecondDebounce,   //再次消抖
  KEY_Status_SecondPress,      //再次按下
} KEY_Status_TypeDef;

//按键事件
typedef enum
{
  KEY_Event_Null,        //空事件
  KEY_Event_SingleClick, //单击
  KEY_Event_LongPress,   //长按
  KEY_Event_QuickClick,  //连击
  KEY_Event_DoubleClick, //双击
} KEY_Event_TypeDef;

//按键模式使能选择
typedef enum
{
  KEY_Mode_OnlySinge = 0x00,         //只有单击
  KEY_Mode_Long = 0x01,              //单击长按
  KEY_Mode_Quick = 0x02,             //单击连按
  KEY_Mode_Long_Quick = 0x03,        //单击长按连按
  KEY_Mode_Double = 0x04,            //单击双击
  KEY_Mode_Long_Double = 0x05,       //单击长按双击
  KEY_Mode_Quick_Double = 0x06,      //单击连按双击
  KEY_Mode_Long_Quick_Double = 0x07, //单击长按连按双击
} KEY_Mode_TypeDef;

//按键配置
typedef struct
{
  uint8_t KEY_Label;             //按键标号
  KEY_Mode_TypeDef KEY_Mode;              //按键模式
  uint16_t KEY_Count;            //按键按下计时
  KEY_Action_TypeDef KEY_Action; //按键动作,按下或释放
  KEY_Status_TypeDef KEY_Status; //按键状态
  KEY_Event_TypeDef KEY_Event;   //按键事件
} KEY_Configure_TypeDef;

extern KEY_Configure_TypeDef KeyConfig[];
extern KEY_Event_TypeDef key_event[];

void KEY_ReadStateMachine(KEY_Configure_TypeDef *KeyCfg);

#endif

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

源文件 my_key.c

#include "my_key.h"

static uint8_t KEY_ReadPin(uint8_t key_label)
{
  switch (key_label)
  {
  case 0:
    return (uint8_t)HAL_GPIO_ReadPin(K0_GPIO_Port, K0_Pin);
  case 1:
    return (uint8_t)HAL_GPIO_ReadPin(K1_GPIO_Port, K1_Pin);
  case 2:
    return (uint8_t)HAL_GPIO_ReadPin(K2_GPIO_Port, K2_Pin);
  case 3:
    return (uint8_t)HAL_GPIO_ReadPin(K3_GPIO_Port, K3_Pin);
  case 4:
    return (uint8_t)HAL_GPIO_ReadPin(K4_GPIO_Port, K4_Pin);
  // case X:
  //   return (uint8_t)HAL_GPIO_ReadPin(KX_GPIO_Port, KX_Pin);
  }
  return 0;
}

KEY_Configure_TypeDef KeyConfig[] = {
    {0, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
    {1, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
    {2, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
    {3, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
    {4, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
    // {X, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
};

KEY_Event_TypeDef key_event[ARR_LEN(KeyConfig)] = {KEY_Event_Null}; //按键事件
//按键状态处理
void KEY_ReadStateMachine(KEY_Configure_TypeDef *KeyCfg)
{
  static uint16_t tmpcnt[ARR_LEN(KeyConfig)] = {0};
  //按键动作读取
  if (KEY_ReadPin(KeyCfg->KEY_Label) == KEY_PRESSED_LEVEL)
    KeyCfg->KEY_Action = KEY_Action_Press;
  else
    KeyCfg->KEY_Action = KEY_Action_Release;

  //状态机
  switch (KeyCfg->KEY_Status)
  {
  //状态:空闲
  case KEY_Status_Idle:
    if (KeyCfg->KEY_Action == KEY_Action_Press) //动作:按下
    {
      KeyCfg->KEY_Status = KEY_Status_Debounce; //状态->消抖
      KeyCfg->KEY_Event = KEY_Event_Null;       //事件->无
    }
    else //动作:默认动作,释放
    {
      KeyCfg->KEY_Status = KEY_Status_Idle; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;   //事件->无
    }
    break;

  //状态:消抖
  case KEY_Status_Debounce:
    if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_DEBOUNCE_TIME)) //动作:保持按下,消抖时间已到
    {
      KeyCfg->KEY_Count = 0;                        //计数清零
      KeyCfg->KEY_Status = KEY_Status_ConfirmPress; //状态->确认按下
      KeyCfg->KEY_Event = KEY_Event_Null;           //事件->无
    }
    else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_DEBOUNCE_TIME)) //动作:保持按下,消抖时间未到
    {
      KeyCfg->KEY_Count++;                      //消抖计数
      KeyCfg->KEY_Status = KEY_Status_Debounce; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;       //事件->无
    }
    else //动作:释放,消抖时间未到,判定为抖动
    {
      KeyCfg->KEY_Count = 0;                //计数清零
      KeyCfg->KEY_Status = KEY_Status_Idle; //状态->空闲
      KeyCfg->KEY_Event = KEY_Event_Null;   //事件->无
    }
    break;

  //状态:确认按下
  case KEY_Status_ConfirmPress:
    if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_LONG_PRESS_TIME)) //动作:保持按下,长按时间已到
    {
      KeyCfg->KEY_Count = KEY_QUICK_CLICK_TIME;         //计数置数,生成第一次连按事件
      KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->确认长按
      KeyCfg->KEY_Event = KEY_Event_Null;               //事件->无
    }
    else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_LONG_PRESS_TIME)) //动作:保持按下,长按时间未到
    {
      KeyCfg->KEY_Count++;                          //长按计数
      KeyCfg->KEY_Status = KEY_Status_ConfirmPress; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;           //事件->无
    }
    else //动作:长按时间未到,释放
    {
      if ((uint8_t)(KeyCfg->KEY_Mode) & 0x04) //双击模式
      {
        KeyCfg->KEY_Count = 0;                           //计数清零
        KeyCfg->KEY_Status = KEY_Status_WaitSecondPress; //状态->等待再按
        KeyCfg->KEY_Event = KEY_Event_Null;              //事件->无
      }
      else //非双击模式
      {
        KeyCfg->KEY_Count = 0;                     //计数清零
        KeyCfg->KEY_Status = KEY_Status_Idle;      //状态->空闲
        KeyCfg->KEY_Event = KEY_Event_SingleClick; //事件->单击****
      }
    }
    break;

  //状态:确认长按
  case KEY_Status_ConfirmPressLong:
    if (KeyCfg->KEY_Action == KEY_Action_Press) //动作:保持按下
    {
      if ((uint8_t)KeyCfg->KEY_Mode & 0x02) //连按模式
      {
        if (KeyCfg->KEY_Count >= KEY_QUICK_CLICK_TIME) //连按间隔时间已到
        {
          KeyCfg->KEY_Count = 0;                            //计数清零
          KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->维持
          KeyCfg->KEY_Event = KEY_Event_QuickClick;         //事件->连按****
        }
        else //连按间隔时间未到
        {
          KeyCfg->KEY_Count++;                              //连按计数
          KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->维持
          KeyCfg->KEY_Event = KEY_Event_Null;               //事件->无
        }
      }
      else //非连按模式
      {
        KeyCfg->KEY_Count = 0;                            //计数清零
        KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->维持
        KeyCfg->KEY_Event = KEY_Event_Null;               //事件->无
      }
    }
    else //动作:长按下后释放
    {
      if ((uint8_t)KeyCfg->KEY_Mode & 0x01) //长按模式
      {
        KeyCfg->KEY_Count = 0;                   //计数清零
        KeyCfg->KEY_Status = KEY_Status_Idle;    //状态->空闲
        KeyCfg->KEY_Event = KEY_Event_LongPress; //事件->长按****
      }
      else //非长按模式
      {
        KeyCfg->KEY_Count = 0;                     //计数清零
        KeyCfg->KEY_Status = KEY_Status_Idle;      //状态->空闲
        KeyCfg->KEY_Event = KEY_Event_SingleClick; //事件->单击****
      }
    }
    break;

  //状态:等待是否再次按下
  case KEY_Status_WaitSecondPress:
    if ((KeyCfg->KEY_Action != KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_DOUBLE_CLICK_TIME)) //动作:保持释放,双击等待时间已到
    {
      KeyCfg->KEY_Count = 0;                     //计数清零
      KeyCfg->KEY_Status = KEY_Status_Idle;      //状态->空闲
      KeyCfg->KEY_Event = KEY_Event_SingleClick; //事件->单击****
    }
    else if ((KeyCfg->KEY_Action != KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_DOUBLE_CLICK_TIME)) //动作:保持释放,双击等待时间未到
    {
      KeyCfg->KEY_Count++;                             //双击等待计数
      KeyCfg->KEY_Status = KEY_Status_WaitSecondPress; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;              //事件->无
    }
    else //动作:双击等待时间内,再次按下
    {
      tmpcnt[KeyCfg->KEY_Label] = KeyCfg->KEY_Count;  //计数保存
      KeyCfg->KEY_Count = 0;                          //计数清零
      KeyCfg->KEY_Status = KEY_Status_SecondDebounce; //状态->再次消抖
      KeyCfg->KEY_Event = KEY_Event_Null;             //事件->无
    }
    break;

  //状态:再次消抖
  case KEY_Status_SecondDebounce:
    if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_DEBOUNCE_TIME)) //动作:保持按下,消抖时间已到
    {
      KeyCfg->KEY_Count = 0;                       //计数清零
      KeyCfg->KEY_Status = KEY_Status_SecondPress; //状态->确认再次按下
      KeyCfg->KEY_Event = KEY_Event_Null;          //事件->无
    }
    else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_DEBOUNCE_TIME)) //动作:保持按下,消抖时间未到
    {
      KeyCfg->KEY_Count++;                            //消抖计数
      KeyCfg->KEY_Status = KEY_Status_SecondDebounce; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;             //事件->无
    }
    else //动作:释放,消抖时间未到,判定为抖动
    {
      KeyCfg->KEY_Count = KeyCfg->KEY_Count + tmpcnt[KeyCfg->KEY_Label]; //计数置数
      KeyCfg->KEY_Status = KEY_Status_WaitSecondPress;                   //状态->等待再按
      KeyCfg->KEY_Event = KEY_Event_Null;                                //事件->无
    }
    break;

  //状态:再次按下
  case KEY_Status_SecondPress:
    if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_LONG_PRESS_TIME)) //动作:保持按下,长按时间已到
    {
      KeyCfg->KEY_Count = 0;                            //计数清零
      KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->确认长按
      KeyCfg->KEY_Event = KEY_Event_SingleClick;        //事件->先响应单击
    }
    else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_LONG_PRESS_TIME)) //动作:保持按下,长按时间未到
    {
      KeyCfg->KEY_Count++;                         //计数
      KeyCfg->KEY_Status = KEY_Status_SecondPress; //状态->维持
      KeyCfg->KEY_Event = KEY_Event_Null;          //事件->无
    }
    else //动作:释放,长按时间未到
    {
      KeyCfg->KEY_Count = 0;                     //计数清零
      KeyCfg->KEY_Status = KEY_Status_Idle;      //状态->空闲
      KeyCfg->KEY_Event = KEY_Event_DoubleClick; //事件->双击
    }
    break;
  }

  if (KeyCfg->KEY_Event != KEY_Event_Null) //事件记录
    key_event[KeyCfg->KEY_Label] = KeyCfg->KEY_Event;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227

定时器中断调用和主函数使用
中断周期为1ms


//调用
uint32_t tim_cnt = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == htim1.Instance)
  {
    tim_cnt++;
    if (tim_cnt % 1 == 0) // 1ms
    {
      KEY_ReadStateMachine(&KeyConfig[0]);
      KEY_ReadStateMachine(&KeyConfig[1]);
      KEY_ReadStateMachine(&KeyConfig[2]);
      KEY_ReadStateMachine(&KeyConfig[3]);
      KEY_ReadStateMachine(&KeyConfig[4]);
    }
  }
}

int main(void)
{
  while (1)
  {
    if (key_event[1] == KEY_Event_SingleClick) //单击
    {
      something1();
    }
    if (key_event[2] == KEY_Event_LongPress) //长按
    {
      something2();
    }
    if ((key_event[3] == KEY_Event_QuickClick) || (key_event[3] == KEY_Event_SingleClick)) //连按
    {
      something3();
    }
    if (key_event[4] == KEY_Event_DoubleClick) //双击
    {
      something4();
    }
    memset(key_event, KEY_Event_Null, sizeof(key_event)); //清除事件
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/877847
推荐阅读
相关标签
  

闽ICP备14008679号