按键扫描函数(基于FIFO思想)

按键扫描函数(基于FIFO思想)基于FIFO思想设计按键扫描程序,支持多按键、点击、双击、三击、多击、长按等事件。

多按键扫描事件处理程序设计

基于FIFO原理的多按键扫描程序设计。
支持多按键的单击、双击、三连击、、、长按等事件处理。

1.按键FIFO

1.1原理

FIFO(First In First Out)是一种先进先出的数据缓存器,顺序写入数据,顺序的读出数据。
假设我们在内存存上面开辟一个10字节的内存做FIFO的数据缓存器
开始时,未发生任何按键读写事件、Write=0,Read=0
我们按下三个不同的按键事件,按键1单击、按键2双击、按键3长按,将三个按键事件更新到按键FIFO的缓存器中不进行读,此时Write = 3,Read = 0,表示有三个按键事件未处理。
我们每读取一个按键事件Read+1
当Read=Write时,所有事件处理完毕。
FIFO空间满时Write重置,原有未处理事件会被覆盖。
在这里插入图片描述

1.2代码实现

1.定义按键FIFO缓冲区及按键事件代码

#define KEY_FIFO_SIZE 10
typedef struct
{ 
   
    uint8_t Buf[KEY_FIFO_SIZE]; /* FIFO缓冲区 */
    uint8_t Read; 				/* 缓冲区读指针 */
    uint8_t Write; 				/* 缓冲区写指针 */
}KEY_FIFO_STRUCT;
static KEY_FIFO_STRUCT m_sKeyFifo;

typedef enum
{ 
   
    KEY_NONE = 0,

    KEY_1_ONE,    /* 单击 */
    KEY_1_DOUBLE, /* 双击 */
    KEY_1_THREE,  /* 三击 */
    KEY_1_LONG,   /* 长按 */

    KEY_2_ONE,
    KEY_2_DOUBLE,
    KEY_2_THREE,
    KEY_2_LONG,

    KEY_3_ONE,
    KEY_3_DOUBLE,
    KEY_3_THREE,
    KEY_3_LONG,  
}KEY_STATE_ENUM;

2.写按键事件值到FIFO缓冲区

/** * 将按键事件压入FIFO缓冲区 * handle: 按键事件 */
void KeyPutFifo(uint8_t handle)
{ 
   
    m_sKeyFifo.Buf[m_sKeyFifo.Write] = handle;
    if (++m_sKeyFifo.Write >= KEY_FIFO_SIZE)
    { 
   
    	m_sKeyFifo.Write = 0;
    }
} 

3.读取按键FIFO缓冲区的数值

/** * 2.按键 FIFO 缓冲区读取一个键值。 */
uint8_t KeyGetState(void)
{ 
   
  uint8_t ret;
  if (m_sKeyFifo.Read == m_sKeyFifo.Write)
  { 
   
    return KEY_NONE;
  }
  else
  { 
   
    ret = m_sKeyFifo.Buf[m_sKeyFifo.Read];
    if (++m_sKeyFifo.Read >= KEY_FIFO_SIZE)
    { 
   
      m_sKeyFifo.Read = 0;
    }
    return ret;
  }
} 

2.按键扫描

按键扫描需要注意:
1.硬件初始化,对应IO配置为输入
2.按键按下时,对应IO的状态应该是高电平还是低电平?
3.同时按下了几个按键?
4.按键滤波时长。
5.按键按下多久是长按?连续按下按键的间隔是多少才合适?

2.1按键IO定义初始化

// PB13 
// PC12 
// PD2 
typedef struct 
{ 
   
  uint32_t      Pin;    /* GPIO引脚 */
  GPIO_TypeDef  *Port;  /* GPIO端口 */
  GPIO_PinState State;  /* 按下状态 */
  
}KEY_GPIO_STRUCT;

KEY_GPIO_STRUCT g_sGpioList[KEY_NUM] = 
{ 
   
  { 
   GPIO_PIN_2,  GPIOD, GPIO_PIN_RESET}, /* 按键1 */
  { 
   GPIO_PIN_13, GPIOB, GPIO_PIN_RESET}, /* 按键2 */
  { 
   GPIO_PIN_12, GPIOC, GPIO_PIN_RESET}, /* 按键3 */
};

static void gpioInit(void)  // 按键GPIO口初始化
{ 
   
    GPIO_InitTypeDef GPIO_Init;
  
    /* 第 1 步:打开 GPIO 时钟 */
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();

    /* 第 2 步:配置所有的按键 GPIO 为浮动输入模式*/
    GPIO_Init.Mode = GPIO_MODE_INPUT;
    GPIO_Init.Pull = GPIO_NOPULL;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  
    for (uint8_t i = 0; i < KEY_NUM; i++)
    { 
   
        GPIO_Init.Pin = g_sGpioList[i].Pin;
        HAL_GPIO_Init(g_sGpioList[i].Port, &GPIO_Init);
    }
}

2.2判断按键是否按下

/* * 判断按键是否按下 * 返回值 1 表示按下, 0 表示未按下 */
static uint8_t KeyPinActive(uint8_t id)
{ 
   
  GPIO_PinState level = HAL_GPIO_ReadPin(g_sGpioList[id].Port, g_sGpioList[id].Pin);
  if(level == g_sGpioList[id].State)
  { 
   
    return 1;
  }
  else
  { 
   
    return 0;
  }
} 

/** * 按键是否按下,限制当前只有一个按键事件 * 1 表示按下, 0 表示未按下 */
static uint8_t KeyIsDownFunc(uint8_t id)
{ 
   
  if (id < KEY_NUM)  /* 实体单键 */
  { 
   
    uint8_t i;
    uint8_t count = 0;
    uint8_t save = 255;
    /* 判断有几个键按下 */
    for (i = 0; i < KEY_NUM; i++)
    { 
   
      if (KeyPinActive(i))
      { 
   
        count++;
        save = i;
      }
    }
    if (count == 1 && save == id)
    { 
   
      return 1; /* 只有 1 个键按下时才有效 */
    }
    return 0;
  }
  return 0;
}  

2.3按键检测事件结构体、定义、初始化

#define KEY_FILTER_TIME 20 /* 滤波时间,按下200ms才做处理 单位 10ms*/
#define KEY_LONG_TIME 100 /* 长按事件时间 1S 单位 10ms */
#define KEY_NUM 3 /* 按键个数*/
typedef struct
{ 
   
    uint8_t  (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1 表示按下 */
    uint8_t  Count;       /* 滤波器计数器 */
    uint16_t LongCount;   /* 长按计数器 */
    uint16_t LongTime;    /* 按键按下持续时间, 0 表示不检测长按 */
    uint16_t LongState;   /* 长按状态 */
    uint8_t  State;       /* 按键当前状态 */
    uint8_t  DownState;   /* 按键按下状态 */
    uint8_t  DownCount;   /* 按键按下次数 */
    uint8_t  RepeatSpeed; /* 连续按键周期 */
    uint8_t  RepeatCount; /* 连续按键计数器 */
}KEY_CHECK_STRUCT;
static KEY_CHECK_STRUCT g_sKeyCheck[KEY_NUM];  /* 按键检测 */ 

static void KeyVarInit(void)  // 初始化按键变量
{ 
   
    uint8_t i;
    m_sKeyFifo.Read = 0;
    m_sKeyFifo.Write = 0;
    for (i = 0; i < KEY_NUM; i++)
    { 
   
        g_sKeyCheck[i].LongTime = KEY_LONG_TIME; /* 长按时间 0 表示不检测长按键事件 */
        g_sKeyCheck[i].Count =    KEY_FILTER_TIME / 2; /* 计数器设置为滤波时间的一半 */
        g_sKeyCheck[i].State = 0;       /* 按键缺省状态, 0 为未按下 */
        g_sKeyCheck[i].RepeatSpeed = 0; /* 按键连发的速度, 0 表示不支持连发 */
        g_sKeyCheck[i].RepeatCount = 0; /* 连发计数器 */
    }
}
void keyInit(void)  // 初始化按键
{ 
   
    KeyVarInit(); 	/* 初始化按键变量 */
    gpioInit();    /* 初始化按键硬件 */
}

2.4按键检测
需要定时10ms调用一次void KeyScan10ms(void)

/* * 检测一个按键。 * IO 的 id, 从 0 开始编码 */
static void KeyDetectKey(uint8_t i)
{ 
   
  KEY_CHECK_STRUCT *pBtn;
  pBtn = &g_sKeyCheck[i];
  if (KeyIsDownFunc(i)) /* 1.执行按键按下的处理 */
  { 
   
    if (pBtn->Count < KEY_FILTER_TIME) /* 2.用于按键滤波前给 Count 设置一个初值 */
    { 
   
      pBtn->Count = KEY_FILTER_TIME;
    }
    else if (pBtn->Count < (KEY_FILTER_TIME + 10)) /* 这里实现 KEY_FILTER_TIME 时间长度的延迟 */
    { 
   
      pBtn->Count++;
    } /* 这里实现 KEY_FILTER_TIME 时间长度的延迟 */
    else
    { 
   
      if (pBtn->State == 0)
      { 
   
        pBtn->State = 1;
      }

      if(pBtn->DownState == 1)
      { 
   
        pBtn->DownState = 0;
        if(pBtn->DownCount < 3)
        { 
   
          pBtn->DownCount += 1;
        }
      }

      if (pBtn->LongTime > 0)
      { 
   
        if (pBtn->LongCount < pBtn->LongTime)
        { 
   
          /* 发送按钮持续按下的消息 */
          if (++pBtn->LongCount == pBtn->LongTime)
          { 
   
            KeyPutFifo((uint8_t)(4 * i + 4)); /* 键值放入按键 FIFO */
            pBtn->LongState = 1;
          }
        }
        else
        { 
   
          if (pBtn->RepeatSpeed > 0)
          { 
   
            if (++pBtn->RepeatCount >= pBtn->RepeatSpeed)
            { 
   
              pBtn->RepeatCount = 0;
             
              KeyPutFifo((uint8_t)(4 * i + 4));
            }
          }
        }
      }
    }
  }
  else // 处理按键松手,或按键没有按下的处理
  { 
   
    if (pBtn->Count > KEY_FILTER_TIME)
    { 
   
      pBtn->Count = KEY_FILTER_TIME;
    }
    else if (pBtn->Count != 0)
    { 
   
      pBtn->Count--;
    }
    else
    { 
   
      if (pBtn->State == 1)
      { 
   
        pBtn->State = 0;
        if (pBtn->LongState == 0)
        { 
   
          KeyPutFifo((uint8_t)(4 * i + pBtn->DownCount)); /* 发送按钮弹起的消息 */
        }
        else
        { 
   
          pBtn->LongState = 0;
        }
        pBtn->DownCount = 0;
      }
    }
    
    pBtn->DownState = 1;
    pBtn->LongCount = 0;
    pBtn->RepeatCount = 0;
  }
}
/** * 10ms调用一次此函数,轮询按键 */
void KeyScan10ms(void)
{ 
   
    uint8_t i;
    for (i = 0; i < KEY_NUM; i++)
    { 
   
    	KeyDetectKey(i);
    }
}

代码

KEY.c

#include "../HARDWARE/key/key.h"
#include "main.h"
#include "string.h"

// PB13 
// PC12 
// PD2 

/** * 1.将 1 个键值压入按键 FIFO 缓冲区。可用于模拟一个按键 * _KeyCode : 按键代码 */
void KeyPutFifo(uint8_t handle)
{ 
   
    m_sKeyFifo.Buf[m_sKeyFifo.Write] = handle;
    if (++m_sKeyFifo.Write >= KEY_FIFO_SIZE)
    { 
   
    	m_sKeyFifo.Write = 0;
    }
} 

/** * 2.按键 FIFO 缓冲区读取一个键值。 */
uint8_t KeyGetState(void)
{ 
   
  uint8_t ret;
  if (m_sKeyFifo.Read == m_sKeyFifo.Write)
  { 
   
    return KEY_NONE;
  }
  else
  { 
   
    ret = m_sKeyFifo.Buf[m_sKeyFifo.Read];
    if (++m_sKeyFifo.Read >= KEY_FIFO_SIZE)
    { 
   
      m_sKeyFifo.Read = 0;
    }
    return ret;
  }
} 

typedef struct 
{ 
   
  uint32_t      Pin;    /* GPIO引脚 */
  GPIO_TypeDef  *Port;  /* GPIO端口 */
  GPIO_PinState State;  /* 按下状态 */
  
}KEY_GPIO_STRUCT;

KEY_GPIO_STRUCT g_sGpioList[KEY_NUM] = 
{ 
   
  { 
   GPIO_PIN_2,  GPIOD, GPIO_PIN_RESET}, /* 按键1 */
  { 
   GPIO_PIN_13, GPIOB, GPIO_PIN_RESET}, /* 按键2 */
  { 
   GPIO_PIN_12, GPIOC, GPIO_PIN_RESET}, /* 按键3 */
};

static void gpioInit(void)  // 按键GPIO口初始化
{ 
   
    GPIO_InitTypeDef GPIO_Init;
  
    /* 第 1 步:打开 GPIO 时钟 */
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();

    /* 第 2 步:配置所有的按键 GPIO 为浮动输入模式*/
    GPIO_Init.Mode = GPIO_MODE_INPUT;
    GPIO_Init.Pull = GPIO_NOPULL;
    GPIO_Init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  
    for (uint8_t i = 0; i < KEY_NUM; i++)
    { 
   
        GPIO_Init.Pin = g_sGpioList[i].Pin;
        HAL_GPIO_Init(g_sGpioList[i].Port, &GPIO_Init);
    }
}

static void KeyVarInit(void)  // 初始化按键变量
{ 
   
    uint8_t i;
    m_sKeyFifo.Read = 0;
    m_sKeyFifo.Write = 0;
    for (i = 0; i < KEY_NUM; i++)
    { 
   
        g_sKeyCheck[i].LongTime = KEY_LONG_TIME; /* 长按时间 0 表示不检测长按键事件 */
        g_sKeyCheck[i].Count =    KEY_FILTER_TIME / 2; /* 计数器设置为滤波时间的一半 */
        g_sKeyCheck[i].State = 0;       /* 按键缺省状态, 0 为未按下 */
        g_sKeyCheck[i].RepeatSpeed = 0; /* 按键连发的速度, 0 表示不支持连发 */
        g_sKeyCheck[i].RepeatCount = 0; /* 连发计数器 */
    }
}


void keyInit(void)  // 初始化按键
{ 
   
    KeyVarInit(); 	/* 初始化按键变量 */
    gpioInit(); 	  /* 初始化按键硬件 */
}


/* * 判断按键是否按下 * 返回值 1 表示按下, 0 表示未按下 */
static uint8_t KeyPinActive(uint8_t id)
{ 
   
  GPIO_PinState level = HAL_GPIO_ReadPin(g_sGpioList[id].Port, g_sGpioList[id].Pin);
  if(level == g_sGpioList[id].State)
  { 
   
    return 1;
  }
  else
  { 
   
    return 0;
  }
}  


/** * 按键是否按下,限制当前只有一个按键事件 * 1 表示按下, 0 表示未按下 */
static uint8_t KeyIsDownFunc(uint8_t id)
{ 
   
  if (id < KEY_NUM)  /* 实体单键 */
  { 
   
    uint8_t i;
    uint8_t count = 0;
    uint8_t save = 255;
    /* 判断有几个键按下 */
    for (i = 0; i < KEY_NUM; i++)
    { 
   
      if (KeyPinActive(i))
      { 
   
        count++;
        save = i;
      }
    }
    if (count == 1 && save == id)
    { 
   
      return 1; /* 只有 1 个键按下时才有效 */
    }
    return 0;
  }
  return 0;
}  



/* * 检测一个按键。 * IO 的 id, 从 0 开始编码 */
static void KeyDetectKey(uint8_t i)
{ 
   
  KEY_CHECK_STRUCT *pBtn;
  pBtn = &g_sKeyCheck[i];
  if (KeyIsDownFunc(i)) /* 1.执行按键按下的处理 */
  { 
   
    if (pBtn->Count < KEY_FILTER_TIME) /* 2.用于按键滤波前给 Count 设置一个初值 */
    { 
   
      pBtn->Count = KEY_FILTER_TIME;
    }
    else if (pBtn->Count < (KEY_FILTER_TIME + 10)) /* 这里实现 KEY_FILTER_TIME 时间长度的延迟 */
    { 
   
      pBtn->Count++;
    } /* 这里实现 KEY_FILTER_TIME 时间长度的延迟 */
    else
    { 
   
      if (pBtn->State == 0)
      { 
   
        pBtn->State = 1;
      }

      if(pBtn->DownState == 1)
      { 
   
        pBtn->DownState = 0;
        if(pBtn->DownCount < 3)
        { 
   
          pBtn->DownCount += 1;
        }
      }

      if (pBtn->LongTime > 0)
      { 
   
        if (pBtn->LongCount < pBtn->LongTime)
        { 
   
          /* 发送按钮持续按下的消息 */
          if (++pBtn->LongCount == pBtn->LongTime)
          { 
   
            KeyPutFifo((uint8_t)(4 * i + 4)); /* 键值放入按键 FIFO */
            pBtn->LongState = 1;
          }
        }
        else
        { 
   
          if (pBtn->RepeatSpeed > 0)
          { 
   
            if (++pBtn->RepeatCount >= pBtn->RepeatSpeed)
            { 
   
              pBtn->RepeatCount = 0;
             
              KeyPutFifo((uint8_t)(4 * i + 4));
            }
          }
        }
      }
    }
  }
  else // 处理按键松手,或按键没有按下的处理
  { 
   
    if (pBtn->Count > KEY_FILTER_TIME)
    { 
   
      pBtn->Count = KEY_FILTER_TIME;
    }
    else if (pBtn->Count != 0)
    { 
   
      pBtn->Count--;
    }
    else
    { 
   
      if (pBtn->State == 1)
      { 
   
        pBtn->State = 0;
        if (pBtn->LongState == 0)
        { 
   
          KeyPutFifo((uint8_t)(4 * i + pBtn->DownCount)); /* 发送按钮弹起的消息 */
        }
        else
        { 
   
          pBtn->LongState = 0;
        }
        pBtn->DownCount = 0;
      }
    }
    
    pBtn->DownState = 1;
    pBtn->LongCount = 0;
    pBtn->RepeatCount = 0;
  }
}
/** * 10ms调用一次此函数,轮询按键 */
void KeyScan10ms(void)
{ 
   
    uint8_t i;
    for (i = 0; i < KEY_NUM; i++)
    { 
   
    	KeyDetectKey(i);
    }
}

void KeyTest(KEY_STATE_ENUM KeyHander)
{ 
   
  if(KeyHander == 0)
    return;
    char str[50];
    strcpy(str,"0");
    switch (KeyHander)
    { 
   
    case KEY_1_ONE:
      sprintf(str, "KEY 1 ONE");
      break;

    case KEY_1_DOUBLE:
      sprintf(str, "KEY 1 DOUBLE");
      break;

    case KEY_1_THREE:
      sprintf(str, "KEY 1 THREE");
      break;

    case KEY_1_LONG:
      sprintf(str, "KEY 1 LONG");
      break;

    case KEY_2_ONE:
      sprintf(str, "KEY 2 ONE");
      break;

    case KEY_2_DOUBLE:
      sprintf(str, "KEY 2 DOUBLE");
      break;

    case KEY_2_THREE:
      sprintf(str, "KEY 2 THREE");
      break;

    case KEY_2_LONG:
      sprintf(str, "KEY 2 LONG");
      break;

    case KEY_3_ONE:
      sprintf(str, "KEY 3 ONE");
      break;

    case KEY_3_DOUBLE:
      sprintf(str, "KEY 3 DOUBLE");
      break;

    case KEY_3_THREE:
      sprintf(str, "KEY 3 THREE");
      break;

    case KEY_3_LONG:
      sprintf(str, "KEY 3 LONG");
      break;

    default:
      break;
    }
    clearScreen();
    DisplayFont(1, 4, 0, str);
// usb_printf(str);
}

key.h

#ifndef _KEY_H
#define _KEY_H

#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>

#define KEY_FIFO_SIZE 10
typedef struct
{ 
   
    uint8_t Buf[KEY_FIFO_SIZE]; /* 键值缓冲区 */
    uint8_t Read; 				      /* 缓冲区读指针 1 */
    uint8_t Write; 				      /* 缓冲区写指针 */
}KEY_FIFO_STRUCT;

static KEY_FIFO_STRUCT m_sKeyFifo;       /* 按键 FIFO 变量,结构体 */

typedef enum
{ 
   
    KEY_NONE = 0,

    KEY_1_ONE,    /* 单击 */
    KEY_1_DOUBLE, /* 双击 */
    KEY_1_THREE,  /* 三击 */
    KEY_1_LONG,   /* 长按 */

    KEY_2_ONE,
    KEY_2_DOUBLE,
    KEY_2_THREE,
    KEY_2_LONG,

    KEY_3_ONE,
    KEY_3_DOUBLE,
    KEY_3_THREE,
    KEY_3_LONG,  
}KEY_STATE_ENUM;

/*按键滤波时间 50ms, 单位 10ms。 只有连续检测到 50ms 状态不变才认为有效,包括弹起和按下两种事件 即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件 */
#define KEY_FILTER_TIME 20 /* 滤波时间,按下200ms才做处理 单位 10ms*/
#define KEY_LONG_TIME 100 /* 长按事件时间 1S 单位 10ms */
#define KEY_NUM 3 /* 按键个数*/
typedef struct
{ 
   
    uint8_t  (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1 表示按下 */
    uint8_t  Count;       /* 滤波器计数器 */
    uint16_t LongCount;   /* 长按计数器 */
    uint16_t LongTime;    /* 按键按下持续时间, 0 表示不检测长按 */
    uint16_t LongState;   /* 长按状态 */
    uint8_t  State;       /* 按键当前状态 */
    uint8_t  DownState;   /* 按键按下状态 */
    uint8_t  DownCount;   /* 按键按下次数 */
    uint8_t  RepeatSpeed; /* 连续按键周期 */
    uint8_t  RepeatCount; /* 连续按键计数器 */
}KEY_CHECK_STRUCT;

static KEY_CHECK_STRUCT g_sKeyCheck[KEY_NUM];  /* 按键检测 */  

void keyInit(void); /* 按键初始化 */
void KeyScan10ms(void);
uint8_t KeyGetState(void);
void KeyTest(KEY_STATE_ENUM KeyHander);
#endif

今天的文章按键扫描函数(基于FIFO思想)分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/28359.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注