多按键扫描事件处理程序设计
基于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