学习前先介绍一下FIFO机制,FIFO机制类似于一个队伍,在队伍里排队,有abc三个人,a先排,后排b,再排c,出来的时候是a先出来,c后出来。即先入先出!在复杂按键的读写里应用的就是这个操作,排队的队尾位置是写的位置,队伍排队的终点就是读的位置。写入多个按键值,会在终点读出来。了解完这个机制后就可以学习复杂按键了。复杂按键相对来说还是比较困难的。
首先是在key.h里写一下关于FIFO机制的结构体方便在.c里应用,
/* 按键FIFO用到变量 */
#define KEY_FIFO_SIZE 10
typedef struct
{
uint8_t Buf[KEY_FIFO_SIZE]; /* 键值缓冲区 */
uint8_t Read; /* 缓冲区读指针1 */
uint8_t Write; /* 缓冲区写指针 */
uint8_t Read2; /* 缓冲区读指针2 */
}KEY_FIFO_T;
经过刚刚的介绍,这个读写指针想必也很好理解了,然后就是和上篇一样,给这四个按键进行一下宏定义
#define RCC_ALL_KEY ( WKUP_GPIO_CLK | KEY0_GPIO_CLK | KEY1_GPIO_CLK | KEY2_GPIO_CLK )
//按键-宏定义模板--WKUP--PA0
#define WKUP_GPIO_PIN GPIO_Pin_0 //WKUP引脚号
#define WKUP_PIN_ID 0 //WKUP引脚序号
#define WKUP_GPIO_PORT GPIOA //WKUP端口号
#define WKUP_GPIO_CLK RCC_APB2Periph_GPIOA //WKUP时钟
//#define WKUP_FUN_OUT PAout //WKUP输出端口配置函数
#define WKUP_FUN_IN PAin //WKUP输入端口配置函数
#define WKUP_GPIO_MODE GPIO_Mode_IPD //WKUP端口输入模式--输入下拉
#define WKUP_ACTIVE_LEVEL 1 //WKUP按键有效电平为高电平
//按键-宏定义模板--KEY0--PE4
#define KEY0_GPIO_PIN GPIO_Pin_4 //KEY0引脚号
#define KEY0_PIN_ID 4 //KEY0引脚序号
#define KEY0_GPIO_PORT GPIOE //KEY0端口号
#define KEY0_GPIO_CLK RCC_APB2Periph_GPIOE //KEY0时钟
//#define KEY0_FUN_OUT PEout //KEY0输出端口配置函数
#define KEY0_FUN_IN PEin //KEY0输入端口配置函数
#define KEY0_GPIO_MODE GPIO_Mode_IPU //KEY0端口输入模式--输入上拉
#define KEY0_ACTIVE_LEVEL 0 //KEY0按键有效电平为低电平
//按键-宏定义模板--KEY1--PE3
#define KEY1_GPIO_PIN GPIO_Pin_3 //KEY1引脚号
#define KEY1_PIN_ID 3 //KEY1引脚序号
#define KEY1_GPIO_PORT GPIOE //KEY1端口号
#define KEY1_GPIO_CLK RCC_APB2Periph_GPIOE //KEY1时钟
//#define KEY1_FUN_OUT PEout //KEY1输出端口配置函数
#define KEY1_FUN_IN PEin //KEY1输入端口配置函数
#define KEY1_GPIO_MODE GPIO_Mode_IPU //KEY1端口输入模式--输入上拉
#define KEY1_ACTIVE_LEVEL 0 //KEY1按键有效电平为低电平
//按键-宏定义模板--KEY2--PE2
#define KEY2_GPIO_PIN GPIO_Pin_2 //KEY2引脚号
#define KEY2_PIN_ID 2 //KEY2引脚序号
#define KEY2_GPIO_PORT GPIOE //KEY2端口号
#define KEY2_GPIO_CLK RCC_APB2Periph_GPIOE //KEY2时钟
//#define KEY2_FUN_OUT PEout //KEY2输出端口配置函数
#define KEY2_FUN_IN PEin //KEY2输入端口配置函数
#define KEY2_GPIO_MODE GPIO_Mode_IPU //KEY2端口输入模式--输入上拉
#define KEY2_ACTIVE_LEVEL 0 //KEY2按键有效电平为低电平
//IO操作函数
#define WKUP WKUP_FUN_IN( WKUP_PIN_ID ) //WKUP
#define KEY0 KEY0_FUN_IN( KEY0_PIN_ID ) //KEY0
#define KEY1 KEY1_FUN_IN( KEY1_PIN_ID ) //KEY1
#define KEY2 KEY2_FUN_IN( KEY2_PIN_ID ) //KEY2
本次复杂按键包括了两个按键一起按下的情况,所以我们引入了键码值,写了六个键码值,并附有相关注释
#define KEY_COUNT 6 /* 按键个数, 4个独立建 + 2个组合键 */
/* 根据应用程序的功能重命名按键宏 */
#define WKUP_DOWN KEY_1_DOWN
#define WKUP_UP KEY_1_UP
#define WKUP_LONG KEY_1_LONG
#define KEY0_DOWN KEY_2_DOWN
#define KEY0_UP KEY_2_UP
#define KEY0_LONG KEY_2_LONG
#define KEY1_DOWN KEY_3_DOWN
#define KEY1_UP KEY_3_UP
#define KEY1_LONG KEY_3_LONG
#define KEY2_DOWN KEY_4_DOWN
#define KEY2_UP KEY_4_UP
#define KEY2_LONG KEY_4_LONG
#define SYS_DOWN_WKUP_KEY0 KEY_5_DOWN /* WKUP KEY0 组合键 */
#define SYS_UP_WKUP_KEY0 KEY_5_UP
#define SYS_LONG_WKUP_KEY0 KEY_5_LONG
#define SYS_DOWN_KEY0_KEY1 KEY_6_DOWN /* KEY0 KEY1 组合键 */
#define SYS_UP_KEY0_KEY1 KEY_6_UP
#define SYS_LONG_KEY0_KEY1 KEY_6_LONG
/* 按键ID, 主要用于bsp_KeyState()函数的入口参数 */
typedef enum
{
KID_K1 = 0,
KID_K2,
KID_K3,
KID_K4,
}KEY_ID_E;
/*
按键滤波时间50ms, 单位10ms。
只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件
即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件
*/
#define KEY_FILTER_TIME 5
#define KEY_LONG_TIME 100 /* 单位10ms, 持续1秒,认为长按事件 */
/*
每个按键对应1个全局的结构体变量。
*/
typedef struct
{
/* 下面是一个函数指针,指向判断按键手否按下的函数 */
uint8_t (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1表示按下 */
uint8_t Count; /* 滤波器计数器 */
uint16_t LongCount; /* 长按计数器 */
uint16_t LongTime; /* 按键按下持续时间, 0表示不检测长按 */
uint8_t State; /* 按键当前状态(按下还是弹起) */
uint8_t RepeatSpeed; /* 连续按键周期 */
uint8_t RepeatCount; /* 连续按键计数器 */
}KEY_T;
/*
定义键值代码, 必须按如下次序定时每个键的按下、弹起和长按事件
推荐使用enum, 不用#define,原因:
(1) 便于新增键值,方便调整顺序,使代码看起来舒服点
(2) 编译器可帮我们避免键值重复。
*/
typedef enum
{
KEY_NONE = 0, /* 0 表示按键事件 */
KEY_1_DOWN, /* 1键按下 */
KEY_1_UP, /* 1键弹起 */
KEY_1_LONG, /* 1键长按 */
KEY_2_DOWN, /* 2键按下 */
KEY_2_UP, /* 2键弹起 */
KEY_2_LONG, /* 2键长按 */
KEY_3_DOWN, /* 3键按下 */
KEY_3_UP, /* 3键弹起 */
KEY_3_LONG, /* 3键长按 */
KEY_4_DOWN, /* 4键按下 */
KEY_4_UP, /* 4键弹起 */
KEY_4_LONG, /* 4键长按 */
/* 组合键 */
KEY_5_DOWN, /* 5键按下 */
KEY_5_UP, /* 5键弹起 */
KEY_5_LONG, /* 5键长按 */
KEY_6_DOWN, /* 6键按下 */
KEY_6_UP, /* 6键弹起 */
KEY_6_LONG, /* 6键长按 */
}KEY_ENUM;
然后是key.c里的函数
#include "bsp.h"
/*
该程序适用于WSNEP_V01开发板
如果用于其它硬件,请修改GPIO定义和 IsKeyDown1 - IsKeyDown6 函数 和 static void bsp_InitKeyHard(void)函数
如果用户的按键个数小于4个,你可以将多余的按键全部定义为和第1个按键一样,并不影响程序功能
#define KEY_COUNT 6 这个在 bsp_key.h 文件中有定义
*/
static KEY_T s_tBtn[KEY_COUNT];
static KEY_FIFO_T s_tKey; /* 按键FIFO变量,结构体 */
static void bsp_InitKeyVar(void);
static void bsp_InitKeyHard(void);
static void bsp_DetectKey(uint8_t i);
/*
*********************************************************************************************************
* 函 数 名: IsKeyDownX
* 功能说明: 判断按键是否按下
* 形 参: 无
* 返 回 值: 返回值1 表示按下,0表示未按下
*********************************************************************************************************
*/
/* 为了区分6个事件: WKUP单独按下, KEY0单独按下,KEY1单独按下,KEY2单独按下, WKUP和KEY0同时按下 ,KEY0和KYE1同时按下*/
static uint8_t IsKeyDown1(void)
{
if( (WKUP == WKUP_ACTIVE_LEVEL) &&( KEY0 != KEY0_ACTIVE_LEVEL) &&( KEY1 != KEY1_ACTIVE_LEVEL) &&( KEY2 != KEY2_ACTIVE_LEVEL)
)
{
return 1;
}else
{
return 0;
}
}
static uint8_t IsKeyDown2(void)
{
if((WKUP != WKUP_ACTIVE_LEVEL) &&( KEY0 == KEY0_ACTIVE_LEVEL) &&( KEY1 != KEY1_ACTIVE_LEVEL) &&( KEY2 != KEY2_ACTIVE_LEVEL)
)
{
return 1;
}else
{
return 0;
}
}
static uint8_t IsKeyDown3(void)
{
if((WKUP != WKUP_ACTIVE_LEVEL) &&( KEY0 != KEY0_ACTIVE_LEVEL) &&( KEY1 == KEY1_ACTIVE_LEVEL) &&( KEY2 != KEY2_ACTIVE_LEVEL)
)
{
return 1;
}else
{
return 0;
}
}
static uint8_t IsKeyDown4(void)
{
if((WKUP != WKUP_ACTIVE_LEVEL) &&( KEY0 != KEY0_ACTIVE_LEVEL) &&( KEY1 != KEY1_ACTIVE_LEVEL) &&( KEY2 == KEY2_ACTIVE_LEVEL)
)
{
return 1;
}else
{
return 0;
}
}
static uint8_t IsKeyDown5(void) /* WKUP KEY0组合键 */
{
if((WKUP == WKUP_ACTIVE_LEVEL) &&( KEY0 == KEY0_ACTIVE_LEVEL) &&( KEY1 != KEY1_ACTIVE_LEVEL) &&( KEY2 != KEY2_ACTIVE_LEVEL)
)
{
return 1;
}else
{
return 0;
}
}
static uint8_t IsKeyDown6(void) /* KEY0 KEY1组合键 */
{
if((WKUP != WKUP_ACTIVE_LEVEL) &&( KEY0 == KEY0_ACTIVE_LEVEL) &&( KEY1 == KEY1_ACTIVE_LEVEL) &&( KEY2 != KEY2_ACTIVE_LEVEL)
)
{
return 1;
}else
{
return 0;
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_InitKey
* 功能说明: 初始化按键. 该函数被 bsp_Init() 调用。
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitKey(void)
{
bsp_InitKeyVar(); /* 初始化按键变量 */
bsp_InitKeyHard(); /* 初始化按键硬件 */
}
/*
*********************************************************************************************************
* 函 数 名: bsp_PutKey
* 功能说明: 将1个键值压入按键FIFO缓冲区。可用于模拟一个按键。
* 形 参: _KeyCode : 按键代码
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_PutKey(uint8_t _KeyCode)
{
s_tKey.Buf[s_tKey.Write] = _KeyCode;
if (++s_tKey.Write >= KEY_FIFO_SIZE)
{
s_tKey.Write = 0;
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_GetKey
* 功能说明: 从按键FIFO缓冲区读取一个键值。
* 形 参: 无
* 返 回 值: 按键代码
*********************************************************************************************************
*/
uint8_t bsp_GetKey(void)
{
uint8_t ret;
if (s_tKey.Read == s_tKey.Write)
{
return KEY_NONE;
}
else
{
ret = s_tKey.Buf[s_tKey.Read];
if (++s_tKey.Read >= KEY_FIFO_SIZE)
{
s_tKey.Read = 0;
}
return ret;
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_GetKey2
* 功能说明: 从按键FIFO缓冲区读取一个键值。独立的读指针。
* 形 参: 无
* 返 回 值: 按键代码
*********************************************************************************************************
*/
uint8_t bsp_GetKey2(void)
{
uint8_t ret;
if (s_tKey.Read2 == s_tKey.Write)
{
return KEY_NONE;
}
else
{
ret = s_tKey.Buf[s_tKey.Read2];
if (++s_tKey.Read2 >= KEY_FIFO_SIZE)
{
s_tKey.Read2 = 0;
}
return ret;
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_GetKeyState
* 功能说明: 读取按键的状态
* 形 参: _ucKeyID : 按键ID,从0开始
* 返 回 值: 1 表示按下, 0 表示未按下
*********************************************************************************************************
*/
uint8_t bsp_GetKeyState(KEY_ID_E _ucKeyID)
{
return s_tBtn[_ucKeyID].State;
}
/*
*********************************************************************************************************
* 函 数 名: bsp_SetKeyParam
* 功能说明: 设置按键参数
* 形 参:_ucKeyID : 按键ID,从0开始
* _LongTime : 长按事件时间
* _RepeatSpeed : 连发速度
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_SetKeyParam(uint8_t _ucKeyID, uint16_t _LongTime, uint8_t _RepeatSpeed)
{
s_tBtn[_ucKeyID].LongTime = _LongTime; /* 长按时间 0 表示不检测长按键事件 */
s_tBtn[_ucKeyID].RepeatSpeed = _RepeatSpeed; /* 按键连发的速度,0表示不支持连发 */
s_tBtn[_ucKeyID].RepeatCount = 0; /* 连发计数器 */
}
/*
*********************************************************************************************************
* 函 数 名: bsp_ClearKey
* 功能说明: 清空按键FIFO缓冲区
* 形 参:无
* 返 回 值: 按键代码
*********************************************************************************************************
*/
void bsp_ClearKey(void)
{
s_tKey.Read = s_tKey.Write;
}
/*
*********************************************************************************************************
* 函 数 名: bsp_InitKeyHard
* 功能说明: 配置按键对应的GPIO
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void bsp_InitKeyHard(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 第1步:打开GPIO时钟 */
RCC_APB2PeriphClockCmd(RCC_ALL_KEY, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); /* 关闭jtag,使能SWD,可以用SWD模式调试 */
/* 第2步:配置所有的按键GPIO */
GPIO_InitStructure.GPIO_Pin = WKUP_GPIO_PIN; /* WKUP端口 */
GPIO_InitStructure.GPIO_Mode = WKUP_GPIO_MODE; /* WKUP端口模式 */
GPIO_Init(WKUP_GPIO_PORT, &GPIO_InitStructure); /* 初始化WKUP */
GPIO_InitStructure.GPIO_Pin = KEY0_GPIO_PIN; /* KEY0端口 */
GPIO_InitStructure.GPIO_Mode = KEY0_GPIO_MODE; /* KEY0端口模式 */
GPIO_Init(KEY0_GPIO_PORT, &GPIO_InitStructure); /* 初始化KEY0 */
GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN; /* KEY1端口 */
GPIO_InitStructure.GPIO_Mode = KEY1_GPIO_MODE; /* KEY1端口模式 */
GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure); /* 初始化KEY1 */
GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN; /* KEY2端口 */
GPIO_InitStructure.GPIO_Mode = KEY2_GPIO_MODE; /* KEY1端口模式 */
GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure); /* 初始化KEY2 */
}
/*
*********************************************************************************************************
* 函 数 名: bsp_InitKeyVar
* 功能说明: 初始化按键变量
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
static void bsp_InitKeyVar(void)
{
uint8_t i;
/* 对按键FIFO读写指针清零 */
s_tKey.Read = 0;
s_tKey.Write = 0;
s_tKey.Read2 = 0;
/* 给每个按键结构体成员变量赋一组缺省值 */
for (i = 0; i < KEY_COUNT; i++)
{
s_tBtn[i].LongTime = KEY_LONG_TIME; /* 长按时间 0 表示不检测长按键事件 */
s_tBtn[i].Count = KEY_FILTER_TIME / 2; /* 计数器设置为滤波时间的一半 */
s_tBtn[i].State = 0; /* 按键缺省状态,0为未按下 */
//s_tBtn[i].KeyCodeDown = 3 * i + 1; /* 按键按下的键值代码 */
//s_tBtn[i].KeyCodeUp = 3 * i + 2; /* 按键弹起的键值代码 */
//s_tBtn[i].KeyCodeLong = 3 * i + 3; /* 按键被持续按下的键值代码 */
s_tBtn[i].RepeatSpeed = 0; /* 按键连发的速度,0表示不支持连发 */
s_tBtn[i].RepeatCount = 0; /* 连发计数器 */
}
/* 如果需要单独更改某个按键的参数,可以在此单独重新赋值 */
/* 比如,我们希望按键1按下超过1秒后,自动重发相同键值 */
// s_tBtn[KID_K1].LongTime = 100;
// s_tBtn[KID_K1].RepeatSpeed = 5; /* 每隔50ms自动发送键值 */
/* 判断按键按下的函数 */
s_tBtn[0].IsKeyDownFunc = IsKeyDown1;
s_tBtn[1].IsKeyDownFunc = IsKeyDown2;
s_tBtn[2].IsKeyDownFunc = IsKeyDown3;
s_tBtn[3].IsKeyDownFunc = IsKeyDown4;
/* 组合键 */
s_tBtn[4].IsKeyDownFunc = IsKeyDown5;
s_tBtn[5].IsKeyDownFunc = IsKeyDown6;
}
/*
*********************************************************************************************************
* 函 数 名: bsp_DetectKey
* 功能说明: 检测一个按键。非阻塞状态,必须被周期性的调用。
* 形 参: 按键结构变量指针
* 返 回 值: 无
*********************************************************************************************************
*/
static void bsp_DetectKey(uint8_t i)
{
KEY_T *pBtn;
/*
如果没有初始化按键函数,则报错
if (s_tBtn[i].IsKeyDownFunc == 0)
{
printf("Fault : DetectButton(), s_tBtn[i].IsKeyDownFunc undefine");
}
*/
pBtn = &s_tBtn[i];
if (pBtn->IsKeyDownFunc())
{
if (pBtn->Count < KEY_FILTER_TIME)
{
pBtn->Count = KEY_FILTER_TIME;
}
else if(pBtn->Count < 2 * KEY_FILTER_TIME)
{
pBtn->Count++;
}
else
{
if (pBtn->State == 0)
{
pBtn->State = 1;
/* 发送按钮按下的消息 */
bsp_PutKey((uint8_t)(3 * i + 1));
}
if (pBtn->LongTime > 0)
{
if (pBtn->LongCount < pBtn->LongTime)
{
/* 发送按钮持续按下的消息 */
if (++pBtn->LongCount == pBtn->LongTime)
{
/* 键值放入按键FIFO */
bsp_PutKey((uint8_t)(3 * i + 3));
}
}
else
{
if (pBtn->RepeatSpeed > 0)
{
if (++pBtn->RepeatCount >= pBtn->RepeatSpeed)
{
pBtn->RepeatCount = 0;
/* 常按键后,每隔10ms发送1个按键 */
bsp_PutKey((uint8_t)(3 * i + 1));
}
}
}
}
}
}
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;
/* 发送按钮弹起的消息 */
bsp_PutKey((uint8_t)(3 * i + 2));
}
}
pBtn->LongCount = 0;
pBtn->RepeatCount = 0;
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_KeyScan
* 功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_KeyScan(void)
{
uint8_t i;
for (i = 0; i < KEY_COUNT; i++)
{
bsp_DetectKey(i);
}
}
最后在.h的末尾加入.c里应用的函数的声明,复杂按键就书写完了,就可以通过主函数应用。