[编程技巧]: 全功能按键非阻塞式实现 按键单击 双击 长按

全功能按键非阻塞式实现 按键单击 双击 长按

标志位定义.png
设置标志位.png
状态转移图.png

代码移植流程:

Key.h中
确定KEY_COUNT 按键的个数;
定义每个按键的名称+ 搜引号.
Key.c中
实现Key_Init 按键引脚初始化的代码;
实现Key_GetState 读取按键引脚状态的代码.
main.c中
实现一个1ms定时中断,并在中断中调用Key_Tick函数;
调用Key_Check函数,若指定按键的指定时间发生,者返回1,否则返回0.

注意事项:

  • 在一轮主循环中,只能检查一次指定按键的指定事件(KEY_HOLD除外),若确实需要检查多次,则可先调用一次Key_Check函数并用变量存储返回值,后续多次判断此变量即可.
  • 双击事件的存在,使得单击事件响应有一些延迟,若程序中没有使用到双击,则可将双击时间阈值改为0,这样可以消除单击事件的延迟.
  • 按键产生了事件,对应的标志位就会一直置1,直到检查了此事件,才会自动清0,这在模式切换时可能会导致误动作(例如:模式1中没有检查过某个标志位,但是按下过按键,此标志位已经置1,随后切换为模式2,开始检查此标志位,那么一旦进入模式2,此标志位的动作就会立刻响应),解决办法是在切换模式时,统一将所有的Key_Flag清0,避免上一个模式的按键标志位对这个模式产生影响.

代码部分

//Key.h

#ifndef __KEY_H
#define __KEY_H

//最多四个按键.按键数量
#define KEY_COUNT               4
//索引号0~3, 宏定义
#define KEY_1                   0
#define KEY_2                   1
#define KEY_3                   2
#define KEY_4                   3

//定义位的掩码.
#define KEY_HOLD                0x01
#define KEY_DOWN                0x02
#define KEY_UP                  0x04
#define KEY_SINGLE              0x08
#define KEY_DOUBLE              0x10
#define KEY_LONG                0x20
#define KEY_REPEAT              0x40

void Key_Init(void);
uint8_t Key_Check(uint8_t n, uint8_t Flag);
void Key_Tick(void);
#endif

//Key.c

#include "stm32f10x.h"                  // Device header
#include "Key.h"

#define KEY_PRESSED             1
#define KEY_UNPRESSED           0

#define KEY_TIME_DOUBLE         200 //双击间隔阈值
#define KEY_TIME_LONG           2000//长按触发时间.
#define KEY_TIME_REPEAT         100 //长按间隔中的多次触发间隔.决定数据增加速度.

uint8_t Key_Flag[KEY_COUNT];//按键数量4.

void Key_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    //下接按键(常用)(上拉输入):按下为低电平0,松开为高电平1
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    //上接按键(下拉输入):按下为高电平1,松开为低电平0.
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;//下拉输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

//n代表具体的按键编号0~3,与数组的索引号一致.
//返回目标按键是否按下了.
uint8_t Key_GetState(uint8_t n)//按键的按下状态.
{
    if (n == KEY_1)
    {
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
        {
            return KEY_PRESSED;
        }
    }
    else if (n == KEY_2)
    {
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
        {
            return KEY_PRESSED;
        }
    }
    else if (n == KEY_3)
    {
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 1)
        {
            return KEY_PRESSED;
        }
    }
    else if (n == KEY_4)
    {
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_15) == 1)
        {
            return KEY_PRESSED;
        }
    }
    return KEY_UNPRESSED;
}

uint8_t Key_Check(uint8_t n, uint8_t Flag)
{
    if (Key_Flag[n] & Flag)
    {
        if (Flag != KEY_HOLD)
        {
            Key_Flag[n] &= ~Flag;//指定位自动清零
        }
        return 1;//指定标志位
    }
    return 0;
}

void Key_Tick(void)//事件监测,1ms执行一次.
{
    static uint8_t Count, i;
    static uint8_t CurrState[KEY_COUNT], PrevState[KEY_COUNT];
    static uint8_t S[KEY_COUNT];
    //同时实现 定时计数/时间到标志位 定时完成自动关闭这些任务.
    static uint16_t Time[KEY_COUNT];
    
    for (i = 0; i < KEY_COUNT; i ++)
    {
        if (Time[i] > 0)
        {
            Time[i] --;
        }
    }
    
    Count ++;
    if (Count >= 20)//过滤抖动
    {
        Count = 0;
        
        for (i = 0; i < KEY_COUNT; i ++)
        {
            PrevState[i] = CurrState[i]; //前一状态
            CurrState[i] = Key_GetState(i);
            
            if (CurrState[i] == KEY_PRESSED)
            {
                Key_Flag[i] |= KEY_HOLD;//最低位置1, KEY_HOLD=0x01
            }
            else
            {
                Key_Flag[i] &= ~KEY_HOLD;//最低位清零
            }
            
            if (CurrState[i] == KEY_PRESSED && PrevState[i] == KEY_UNPRESSED)
            {
                Key_Flag[i] |= KEY_DOWN;//0x02
            }
            
            if (CurrState[i] == KEY_UNPRESSED && PrevState[i] == KEY_PRESSED)
            {
                Key_Flag[i] |= KEY_UP;//0x04
            }
            
            if (S[i] == 0)//状态机检测.
            {
                if (CurrState[i] == KEY_PRESSED)
                {
                    Time[i] = KEY_TIME_LONG;
                    S[i] = 1;//转入状态机1
                }
            }
            else if (S[i] == 1)
            {
                if (CurrState[i] == KEY_UNPRESSED)
                {   //递减计数器赋初值,双击时间200ms.
                    Time[i] = KEY_TIME_DOUBLE;
                    S[i] = 2;//
                }
                else if (Time[i] == 0)
                {   //长按时间到,进入状态4,按键已长按.
                    Time[i] = KEY_TIME_REPEAT;
                    Key_Flag[i] |= KEY_LONG;//长按
                    S[i] = 4;
                }
            }
            else if (S[i] == 2)
            {
                if (CurrState[i] == KEY_PRESSED)
                {   //设定
                    Key_Flag[i] |= KEY_DOUBLE;//双击
                    S[i] = 3;
                }
                else if (Time[i] == 0)
                {
                    Key_Flag[i] |= KEY_SINGLE;
                    S[i] = 0;
                }
            }
            else if (S[i] == 3)
            {
                if (CurrState[i] == KEY_UNPRESSED)
                {//检测按键松开,回到状态0
                    S[i] = 0;
                }
            }
            else if (S[i] == 4)
            {
                if (CurrState[i] == KEY_UNPRESSED)
                {//按键松开
                    S[i] = 0;
                }
                else if (Time[i] == 0)
                {//重设重复时间
                    Time[i] = KEY_TIME_REPEAT;
                    Key_Flag[i] |= KEY_REPEAT;//重复按键.
                    S[i] = 4;
                }
            }
        }
    }
}

//main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
#include "Timer.h"

uint16_t Num1;
uint16_t Num2;

int main(void)
{
    OLED_Init();
    Key_Init();
    Timer_Init();
    
    OLED_ShowString(1, 1, "Num1:");
    OLED_ShowString(2, 1, "Num2:");
    
    while (1)
    {
        /*示例1*/
//      if (Key_Check(KEY_1, KEY_HOLD))
//      {   //key1按住
//          Num1 = 1;
//      }
//      else
//      {   //key1释放
//          Num1 = 0;
//      }
//      if (Key_Check(KEY_2, KEY_HOLD))
//      {
//          Num2 = 1;
//      }
//      else
//      {
//          Num2 = 0;
//      }
        
        /*示例2*/
//      if (Key_Check(KEY_1, KEY_DOWN))
//      {   //按下key1瞬间,num1自增
//          Num1 ++;
//      }
//      if (Key_Check(KEY_2, KEY_UP))
//      {   //松开key2瞬间,num2增加.
//          Num2 ++;
//      }
        
        /*示例3*/
//      if (Key_Check(KEY_1, KEY_SINGLE))
//      {   //按键单击
//          Num1 ++;
//      }
//      if (Key_Check(KEY_1, KEY_DOUBLE))
//      {   //双击, 按键双击
//          Num1 += 100;
//      }
//      if (Key_Check(KEY_2, KEY_SINGLE))
//      {   
//          Num1 --;
//      }
//      if (Key_Check(KEY_2, KEY_DOUBLE))
//      {
//          Num1 -= 100;
//      }
//      if (Key_Check(KEY_1, KEY_LONG) || Key_Check(KEY_2, KEY_LONG))
//      {   //长按时间,清零
//          Num1 = 0;
//      }
        
        /*示例4*/
//      if (Key_Check(KEY_1, KEY_SINGLE) || Key_Check(KEY_1, KEY_REPEAT))
//      {   //按键单击或 长按重复触发(长按快速自增).
//          Num1 ++;
//      }
//      if (Key_Check(KEY_2, KEY_SINGLE) || Key_Check(KEY_2, KEY_REPEAT))
//      {
//          Num1 --;
//      }
//      if (Key_Check(KEY_3, KEY_SINGLE))
//      {
//          Num1 = 0;
//      }
//      if (Key_Check(KEY_3, KEY_LONG))
//      {
//          Num1 = 9999;
//      }
        
        /*示例5: 组合按键,类似Ctrl+C */
//      uint8_t K1_UP = Key_Check(KEY_1, KEY_UP);
//      uint8_t K2_UP = Key_Check(KEY_2, KEY_UP);
//      if (K1_UP && Key_Check(KEY_3, KEY_HOLD))
//      {
//          Num1 ++;
//      }
//      if (K2_UP && Key_Check(KEY_3, KEY_HOLD))
//      {
//          Num1 --;
//      }
//      if (K1_UP && Key_Check(KEY_4, KEY_HOLD))
//      {
//          Num2 ++;
//      }
//      if (K2_UP && Key_Check(KEY_4, KEY_HOLD))
//      {
//          Num2 --;
//      }
        
        OLED_ShowNum(1, 6, Num1, 5);
        OLED_ShowNum(2, 6, Num2, 5);
    }
}

//定时器中断,1ms触发一次.
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    {
        Key_Tick();//1ms定时中断
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容