目录
一、按键初始化相关代码
1、队列相关初始化
/* uqueue.h 队列相关头文件 */
#ifndef _UQUEUE_H_
#define _UQUEUE_H_
#include "stdint.h"
#define UQUEUE_MAX_LEN 32
typedef struct{
char *buff;
int head;
int size;
int msize;
}UqueueType;
int UqueueCreate(UqueueType *queue, void *qbuff, int msize);
int UqueueLen(UqueueType *queue);
char UqueuePush(UqueueType *queue, char c);
char UqueuePop(UqueueType *queue);
char UqueueAt(UqueueType *queue, int index);
int UqueueWrite(UqueueType *queue, const char *buff, int num);
int UqueueRead(UqueueType *queue, char *buff, int num);
void UqueueClear(UqueueType *queue);
void UqueueThrow(UqueueType *queue, int num);
#endif
/* uqueue.c 队列相关 .c 文件 */
#include "uqueue.h"
#include <string.h>
#ifndef NULL
#define NULL 0
#endif
/**
* 创建一个队列
* queue - 需要初始化的队列
* qbuff - 队列数据存储位置
* msize - 队列最大长度
* 返回:是否操作成功,0-失败,1-成功
*/
int UqueueCreate(UqueueType *queue, void *qbuff, int msize)
{
if(queue==NULL || qbuff==NULL || msize==0) return 0;
queue->buff = qbuff;
queue->head=0;
queue->size=0;
queue->msize = msize;
return 1;
}
/**
* 获取队列长度
* queue - 队列
* 返回:队列长度
*/
int UqueueLen(UqueueType *queue)
{
if(queue==NULL) return 0;
return queue->size;
}
/**
* 向队列压入数据
* queue - 队列
* c - 压入的数据
* 返回:是否操作成功,0-失败,1-成功
*/
char UqueuePush(UqueueType *queue, char c)
{
if(queue==NULL) return 0;
if(queue->size<queue->msize){
queue->buff[(queue->head+queue->size)%queue->msize]=c;
queue->size++;
return 1;
}
else{
return 0;
}
}
/**
* 弹出数据
* queue - 队列
* 返回:弹出的数据
*/
char UqueuePop(UqueueType *queue)
{
if(queue==NULL || queue->size==0) return 0;
char c = queue->buff[queue->head++];
queue->head = queue->head%queue->msize;
queue->size--;
return c;
}
/**
* 读取队列中的一个数据
* queue - 队列
* index - 数据相对于队列头的偏移
* 返回:读到的数据
*/
char UqueueAt(UqueueType *queue, int index)
{
if(queue==NULL || queue->size<=index || queue->size<=0) return 0;
return queue->buff[(queue->head+index)%queue->msize];
}
/**
* 写入几个数据
* queue - 队列
* buff - 数据缓存
* num - 写入数据量
* 返回:写入的数据量
*/
int UqueueWrite(UqueueType *queue, const char *buff, int num)
{
if(queue==NULL || buff==NULL || num==0) return 0;
num = num>(queue->msize - queue->size) ? (queue->msize - queue->size):num;
if(((queue->head+queue->size)%queue->msize+num)/queue->msize){
int rem = (queue->head+queue->size+num)%queue->msize;
memcpy(&queue->buff[(queue->head+queue->size)%queue->msize], buff, num-rem);
memcpy(queue->buff, &buff[num-rem], rem);
}
else{
memcpy(&queue->buff[(queue->head+queue->size)%queue->msize], buff, num);
}
queue->size+=num;
return num;
}
/**
* 读出几个数据
* queue - 队列
* buff - 数据缓存
* num - 读出数据量
* 返回:实际读到的数据量
*/
int UqueueRead(UqueueType *queue, char *buff, int num)
{
if(queue==NULL || buff==NULL || num==0) return 0;
num = num>queue->size ? queue->size:num;
if((queue->head+num)/queue->msize){
int rem = (queue->head+num)%queue->msize;
memcpy(buff, &queue->buff[queue->head], num-rem);
memcpy(&buff[num-rem], queue->buff, rem);
}
else{
memcpy(buff, &queue->buff[queue->head], num);
}
return num;
}
/**
* 清空队列
* queue - 队列
*/
void UqueueClear(UqueueType *queue)
{
if(queue==NULL) return;
queue->head=0;
queue->size=0;
}
/**
* 弹出数个数据并丢弃
* queue - 队列
* num - 丢弃的数据个数
*/
void UqueueThrow(UqueueType *queue, int num)
{
if(queue==NULL) return;
num = queue->size>num ? num:queue->size;
queue->size-=num;
queue->head = (queue->head+num)%queue->msize;
}
2、按键硬件相关初始化
/* keys.h 文件 */
#ifndef __KEYS_H_
#define __KEYS_H_
#include "gd32f30x.h"
void KeyInit(void);
uint8_t KeyOxygenStartRead(void);
uint8_t KeyTimePlusRead(void);
uint8_t KeyTimeDivRead(void);
#endif
/* keys.c 文件 */
#include "keys.h"
#define KEY_PORT GPIOD
#define KEY_OXYGEN_START_PIN GPIO_PIN_2
#define KEY_TIME_PLUS_PIN GPIO_PIN_0
#define KEY_TIME_DIV_PIN GPIO_PIN_1
void KeyInit(void)
{
rcu_periph_clock_enable(RCU_GPIOD);
gpio_init(KEY_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, KEY_OXYGEN_START_PIN | KEY_TIME_PLUS_PIN | KEY_TIME_DIV_PIN);
}
uint8_t KeyOxygenStartRead(void)
{
return gpio_input_bit_get(KEY_PORT, KEY_OXYGEN_START_PIN);
}
uint8_t KeyTimePlusRead(void)
{
return gpio_input_bit_get(KEY_PORT, KEY_TIME_PLUS_PIN);
}
uint8_t KeyTimeDivRead(void)
{
return gpio_input_bit_get(KEY_PORT, KEY_TIME_DIV_PIN);
}
3、按键逻辑层相关函数
/* KeyScan.h 文件 */
#ifndef __KEY_SCAN_H_
#define __KEY_SCAN_H_
#include "gd32f30x.h"
//按键ID
typedef enum{
UQUEUE_KEY_NONE=0,
UQUEUE_KEY_OXYGEN_START,
UQUEUE_KEY_TIME_PLUS,
UQUEUE_KEY_TIME_DIV,
UQUEUE_KEY_MAX_NUM,
}UQUEUE_KEY_ID;
//按键状态
typedef enum{
UQUEUE_KEY_STATUS_IDLE=0,
UQUEUE_KEY_STATUS_SHORT_PRESS,
UQUEUE_KEY_STATUS_DOUBLE_SHORT_PRESS,
UQUEUE_KEY_STATUS_LONG_PRESS,
UQUEUE_KEY_MAX_STATUS,
}UQUEUE_KEY_STATUS;
//按键模式:单击 长按,单击 长按 双击,单击 连按
typedef enum{
UQUEUE_KEY_NONE_MODE = 0, //不使用按键
UQUEUE_KEY_GENERAL_MODE, //常规模式: 单击 长按
UQUEUE_KEY_DOUBLE_MODE, //带双击模式:单击 长按 双击,
UQUEUE_KEY_CONTINUS_MODE, //连按模式: 单击 连按
}UQUEUE_KEY_MODE;
typedef struct{
UQUEUE_KEY_ID id; //按键id
UQUEUE_KEY_STATUS status; //按键状态
}KeyValType;
void KeyScanInit(void);
void KeyScan(uint16_t xms);
KeyValType KeyScanRead(void);
uint8_t KeyCombinationRead(UQUEUE_KEY_ID keyId1, UQUEUE_KEY_ID keyId2);
uint8_t KeyFind(KeyValType key);
void KeyScanClear(void);
uint8_t KeyCount(void);
void KeyAdd(KeyValType key);
#endif
/* KeyScan.c 文件*/
#include "KeyScan.h"
#include "keys.h"
#include "uqueue.h"
#include <stdint.h>
//长按短按时间判定
#define SHORT_KEY_TIME 0 //20ms
#define DOUBLE_SHORT_KEY_TIME 300 //第一次按下规定的松开时间范围
#define CLICK_DOUBLE_SHORT_KEY_TIME 400 //第一次短按之后,第二次要到来的时间范围
#define LONG_KEY_TIME 2000
//触发连按的阈值和连按间隔
#define CONTINUE_THRESHOLD 800
#define CONTINUE_INTERVAL 200
#define PRESS 0
typedef struct{
KeyValType KeyVal;
UQUEUE_KEY_MODE KeyMode; //连续触发模式
uint16_t PressTime; //按下时间累加
uint8_t PressCnt; //按下次数
uint16_t ReleaseTime; //松开按键时间累加
uint8_t (*KeyRead)(void); //按键IO状态读取
}KeyCtr_t;
/* 注意:组合键只有在两个按键都是在常规模式的时候才能使用 */
//按键列表
KeyCtr_t KeyCtrMap[] =
{
{{UQUEUE_KEY_OXYGEN_START, UQUEUE_KEY_STATUS_IDLE}, UQUEUE_KEY_GENERAL_MODE, 0, 0, 0, KeyOxygenStartRead},
{{UQUEUE_KEY_TIME_PLUS, UQUEUE_KEY_STATUS_IDLE}, UQUEUE_KEY_DOUBLE_MODE, 0, 0, 0, KeyTimePlusRead},
{{UQUEUE_KEY_TIME_DIV, UQUEUE_KEY_STATUS_IDLE}, UQUEUE_KEY_GENERAL_MODE, 0, 0, 0, KeyTimeDivRead},
};
#define KeyCtrMapNumber (sizeof(KeyCtrMap)/sizeof(KeyCtrMap[0]))
/* KEY_QUEUE_MSIZE:若能快速读取队列,队列可以只有一个成员;
若有组合键,队列成员至少要有两个以上 */
#define KEY_QUEUE_MSIZE (KeyCtrMapNumber*sizeof(KeyValType))
static UqueueType KeyQueue;
static uint8_t KeyQueueBuff[KEY_QUEUE_MSIZE];
static uint8_t KeyVailCheck(KeyValType key);
static void KeyValInit(KeyValType *key);
static uint8_t KeyIsIdle(void);
/*!
\brief 初始化按键扫描功能
\param[in] none
\param[out] none
\retval none
*/
void KeyScanInit(void)
{
KeyInit();
UqueueCreate(&KeyQueue, KeyQueueBuff, KEY_QUEUE_MSIZE);
}
/*!
\brief 按键扫描函数,需要定时器循环调用
\param[in] nms - 调用间隔
\param[out] none
\retval none
*/
void KeyScan(uint16_t xms)
{
KeyCtr_t *Keyn;
for(int i=0; i<sizeof(KeyCtrMap)/sizeof(KeyCtrMap[0]); i++)
{
Keyn = &KeyCtrMap[i];
//常规模式
if(Keyn->KeyMode == UQUEUE_KEY_GENERAL_MODE)
{
//按键按下
if(Keyn->KeyRead() == PRESS)
{
if(Keyn->PressTime < LONG_KEY_TIME)
{
Keyn->PressTime += xms;
}
//长按(时间判定)
else if(Keyn->KeyVal.status != UQUEUE_KEY_STATUS_LONG_PRESS)
{
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_LONG_PRESS;
UqueueWrite(&KeyQueue, (char *)&Keyn->KeyVal, sizeof(Keyn->KeyVal));
}
}
//抬起
else
{
if(Keyn->PressTime > 0)
{
//短按
if(Keyn->PressTime >= SHORT_KEY_TIME && Keyn->PressTime < LONG_KEY_TIME)
{
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_SHORT_PRESS;
UqueueWrite(&KeyQueue, (char *)&Keyn->KeyVal, sizeof(Keyn->KeyVal));
}
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_IDLE;
Keyn->PressTime = 0;
}
}
}
//带双击模式
else if(Keyn->KeyMode == UQUEUE_KEY_DOUBLE_MODE)
{
//按键按下
if(Keyn->KeyRead() == PRESS)
{
if(Keyn->PressTime < LONG_KEY_TIME)
{
Keyn->PressTime += xms;
}
//长按(时间判定)
else if(Keyn->KeyVal.status != UQUEUE_KEY_STATUS_LONG_PRESS)
{
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_LONG_PRESS;
UqueueWrite(&KeyQueue, (char *)&Keyn->KeyVal, sizeof(Keyn->KeyVal));
}
}
//抬起
else
{
if(Keyn->PressTime > 0)
{
//双击
if((Keyn->PressTime >= SHORT_KEY_TIME && Keyn->PressTime < DOUBLE_SHORT_KEY_TIME)
|| (Keyn->ReleaseTime != 0))
{
Keyn->PressCnt++;
if(Keyn->PressCnt >= 2)
{
Keyn->PressCnt = 0;
Keyn->ReleaseTime = 0;
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_DOUBLE_SHORT_PRESS;
UqueueWrite(&KeyQueue, (char *)&Keyn->KeyVal, sizeof(Keyn->KeyVal));
}
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_IDLE;
Keyn->PressTime = 0;
}
else
{
//短按
if(Keyn->PressTime >= SHORT_KEY_TIME && Keyn->PressTime < LONG_KEY_TIME)
{
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_SHORT_PRESS;
UqueueWrite(&KeyQueue, (char *)&Keyn->KeyVal, sizeof(Keyn->KeyVal));
}
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_IDLE;
Keyn->PressTime = 0;
Keyn->PressCnt = 0;
}
}
//第一次按下之后,计时
if(Keyn->PressCnt>0)
{
Keyn->ReleaseTime++;
if(Keyn->ReleaseTime > CLICK_DOUBLE_SHORT_KEY_TIME)
{
//超出规定时间没有再次按下,按短按处理
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_SHORT_PRESS;
UqueueWrite(&KeyQueue, (char *)&Keyn->KeyVal, sizeof(Keyn->KeyVal));
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_IDLE;
Keyn->PressTime = 0;
Keyn->ReleaseTime = 0;
Keyn->PressCnt = 0;
}
}
}
}
//连按模式
else if(Keyn->KeyMode == UQUEUE_KEY_CONTINUS_MODE)
{
//按键按下
if(Keyn->KeyRead()==PRESS)
{
Keyn->PressTime+=xms;
//首次按下
if(Keyn->PressTime == xms)
{
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_SHORT_PRESS;
UqueueWrite(&KeyQueue, (char *)&Keyn->KeyVal, sizeof(Keyn->KeyVal));
}
//触发连按
else if(Keyn->PressTime >= CONTINUE_THRESHOLD+CONTINUE_INTERVAL)
{
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_SHORT_PRESS;
UqueueWrite(&KeyQueue, (char *)&Keyn->KeyVal, sizeof(Keyn->KeyVal));
Keyn->PressTime = CONTINUE_THRESHOLD;
}
}
//抬起
else
{
if(Keyn->PressTime > 0)
{
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_IDLE;
Keyn->PressTime = 0;
}
}
}
else
{
Keyn->KeyVal.status = UQUEUE_KEY_STATUS_IDLE;
Keyn->PressTime = 0;
}
}
}
/*!
\brief 读取按键
\param[in] none
\param[out] none
\retval 返回按键值和状态。按键值为KEY_NONE为没有按键事件
*/
KeyValType KeyScanRead(void)
{
uint16_t qlen = UqueueLen(&KeyQueue);
KeyValType key;
KeyValInit(&key);
if(qlen > 0)
{
//队列数据格式不正确,清零
if((qlen % sizeof(KeyValType)) > 0)
{
UqueueClear(&KeyQueue);
}
else
{
UqueueRead(&KeyQueue, (char *)&key, sizeof(key));
//为了实现组合按键,所有普通按键都抬起,才能判定短按
if(key.status != UQUEUE_KEY_STATUS_SHORT_PRESS || KeyIsIdle())
{
UqueueThrow(&KeyQueue, sizeof(key));
//按键值无效
if(!KeyVailCheck(key))
{
KeyValInit(&key);
}
}
else
{
KeyValInit(&key);
}
}
}
return key;
}
/*!
\brief 组合按键,两个普通按键按下时间有交叉,且都没有超过长按时间,判定为组合按键
\param[in] keyId1 - 按键1
\param[out] keyId2 - 按键2
\retval 返回是否存在按键组合
*/
uint8_t KeyCombinationRead(UQUEUE_KEY_ID keyId1, UQUEUE_KEY_ID keyId2)
{
uint8_t pressSta = 0, qkey1, qkey2;
KeyValType qkey[2];
uint16_t qlen = UqueueLen(&KeyQueue);
if((qlen/sizeof(KeyValType)) == 2
&& keyId1 != keyId2
&& KeyIsIdle())
{
UqueueRead(&KeyQueue, (char *)qkey, sizeof(qkey));
if(qkey[0].status == UQUEUE_KEY_STATUS_SHORT_PRESS
&& qkey[1].status == UQUEUE_KEY_STATUS_SHORT_PRESS
&& qkey[0].id != qkey[1].id
&& (keyId1 == qkey[0].id || keyId1 == qkey[1].id)
&& (keyId2 == qkey[0].id || keyId2 == qkey[1].id))
{
UqueueClear(&KeyQueue);
return 1;
}
}
return 0;
}
/*!
\brief 键值查找
\param[in] key - 键值
\retval 返回是否存在该键值
*/
uint8_t KeyFind(KeyValType key)
{
KeyValType Keyn;
uint16_t kcount = KeyCount();
if(kcount > 0)
{
for(int i=0; i<kcount; i++)
{
uint8_t res = 1;
for(int j=0; j<sizeof(KeyValType); j++)
{
if(((char *)&key)[j] != UqueueAt(&KeyQueue, j+i*sizeof(KeyValType)))
{
res = 0;
break;
}
}
if(res == 1) return 1;
}
}
return 0;
}
/*!
\brief 清空按键队列和按键按下累计时间
\param[in] none
\param[out] none
\retval none
*/
void KeyScanClear(void)
{
KeyCtr_t *Keyn;
for(int i=0; i<sizeof(KeyCtrMap)/sizeof(KeyCtrMap[0]); i++)
{
Keyn = &KeyCtrMap[i];
Keyn->PressTime=0;
}
UqueueClear(&KeyQueue);
}
uint8_t KeyCount(void)
{
return (UqueueLen(&KeyQueue)/sizeof(KeyValType));
}
/*!
\brief 手动添加一个按键事件
\param[in] key - 键值
\retval none
*/
void KeyAdd(KeyValType key)
{
UqueueWrite(&KeyQueue, (char *)&key, sizeof(key));
}
static uint8_t KeyVailCheck(KeyValType key)
{
if(key.id > 0 && key.id < UQUEUE_KEY_MAX_NUM)
{
if(key.status > 0 && key.status < UQUEUE_KEY_MAX_STATUS)
{
return 1;
}
}
return 0;
}
static void KeyValInit(KeyValType *key)
{
key->id = UQUEUE_KEY_NONE;
key->status = UQUEUE_KEY_STATUS_IDLE;
}
static uint8_t KeyIsIdle(void)
{
KeyCtr_t *Keyn;
for(int i=0; i<sizeof(KeyCtrMap)/sizeof(KeyCtrMap[0]); i++)
{
Keyn = &KeyCtrMap[i];
if(Keyn->KeyRead() == PRESS && Keyn->KeyMode == UQUEUE_KEY_GENERAL_MODE)
{
return 0;
}
}
return 1;
}
二、按键的使用
1、硬件初始化
KeyScanInit(); //硬件初始化
2、按键扫描:一般放在定时器 或者 while(1)
KeyScan(1); //循环定时 1ms 的地方调用
3、读取按键结果
while(1)
{
/* 读取组合按键,必须配合 KeyScanRead() 一起使用。使用时,可以不响应 KeyScanRead() 的结果 */
if(KeyCombinationRead(UQUEUE_KEY_OXYGEN_START, UQUEUE_KEY_TIME_DIV)
{
printf("KeyCombination!\r\n");
}
/* 读取单击,双击,长按,连按结果 */
Keyn = KeyScanRead();
if((Keyn.id != UQUEUE_KEY_NONE) && (Keyn.status != UQUEUE_KEY_STATUS_IDLE))
{
printf("Keyn.id = %d\r\n", Keyn.id);
printf("Keyn.status = %d\r\n", Keyn.status);
}
}