最近做一些项目,对按键操作要求比较复杂,要实现按键按下、弹起、组合、长按等不同状态的响应,之前写过一篇按键实现的文章,现在把代码重新优化了一下,分享给大家,代码实现了下面功能:
- 按下:(按键被按下去,但没有松开)
- 弹起 : (松开按键)
- 重复:(按键被一直按住,键值重复出现,像电脑键盘一样,按住一个字符不放它就能一直输入)
- 长按:(按住一定时间不松开)
- 按下时长:(记录按键从按下到弹起的时间)**
采用时间片轮询方式扫描按键动作,带消抖功能,运行稳定,可移植性好,不同平台,只要简单修改硬件相关的部分。
这个按键实现方法可以满足绝大部分应用的需求,后面有示例讲解
先上代码一堵为快,程序不难,主要部分都有注释,代码有点长,但是可移植性好,可读性强,有不懂的可以留言交流
H文件
/************************************************************
Copyright (C), 2013-2020
@FileName: key.h
@Author : 祥子 QQ:570525287
@Version : 3.0
@Date : 2020-10-23
@Description: 按键扫描
@Function List:
@History :
<author> <time> <version > <desc>
Lennon 2020/10/23 2.0 增加对按键按下弹起的支持
移植注意事项:
1、注意键值的定义是u8 还是u16类型
2、用define定义的16位数值,要加上 ul,否则可能会出错
***********************************************************/
#ifndef __KEY_H
#define __KEY_H
#include "stm8s.h"
typedef u16 key_u16;
//按键类型
typedef struct _Key{
key_u16 Value; //键值
u16 PressTime; //按下时长
u16 Flag; //标记
}Key_TypeDef;
/*---------用bit位表示键值 (最多支持16个键),支持组合键----------*/
//也可以用数字码表示,但不支持组合键
#define KEY_OFF 0x0000
#define KEY_UP 0x0001
#define KEY_DN 0x0002
#define KEY_LEFT 0x0004
#define KEY_RIGHT 0x0008
#define KEY_OK 0x0010
#define KEY_STOP 0x0020
#define KEY_RUN 0x0040
//----------按键动作标记---------
#define FLAG_PRESS 0x8000 //按下
#define FLAG_RELEASE 0x4000 //释放
#define FLAG_REPEAT 0x2000 //重复
#define FLAG_LONG 0x1000 //长按
/*----------按键重复-----------*/
//下面的数据根据Key_Scan函数的扫描周期计算,这里是20ms扫描一次
#define KEY_CNT_SHAKE 2ul //消抖次数
#define KEY_CNT_REP_SPEED 5ul //重复键出现的速度 5*20ms = 0.1秒,即按下1秒出现10次
#define KEY_CNT_REP_END 50ul //按下一定时间后,如果没有松开按键,则出现重复键 50*20ms = 1秒
#define KEY_CNT_LONG_PRESS 99ul //按下2秒不放为长按 时间:100*20ms = 2s 这里改成99是防止长按和重复标志同时出现
/*-------------------------------*/
extern Key_TypeDef gKey; //可以通过这个变量直接得到按键扫描结果,也可以通过Key_GetValue和Key_GetFlag得到
/*-------------函数--------------*/
void Key_Init(void);
void Key_Scan(void); //扫描按键
key_u16 Key_GetValue(void); //读取扫描到的按键
u16 Key_GetFlag(void); //读取按键动作标记
u16 Key_GetPressTime(void); //读取按下的时长
void KeyTestDemo();
#endif /* __KEY_H */
C文件
/************************************************************
Copyright (C), 2013-2020
@FileName: gKey.c
@Author : 祥子 QQ:570525287
@Version : 3.0
@Date : 2020-10-23
@Description: 按键扫描
@Function List:
@History :
<author> <time> <version > <desc>
Lennon 2020/10/23 2.0 增加对按键按下弹起的支持,增加按下时间记录
***********************************************************/
#include "key.h"
Key_TypeDef gKey;
//================================与硬件相关的内容,移植时需要修改============================================
//*-----------硬件IO相关-----------*/
//初始化按键
void Key_Init(void)
{
GPIO_Init(KEY1_PORT,KEY1_PIN,GPIO_MODE_IN_PU_NO_IT); //上拉无中断
GPIO_Init(KEY2_PORT,KEY2_PIN,GPIO_MODE_IN_PU_NO_IT); //上拉无中断
GPIO_Init(KEY3_PORT,KEY3_PIN,GPIO_MODE_IN_PU_NO_IT); //上拉无中断
GPIO_Init(KEY4_PORT,KEY4_PIN,GPIO_MODE_IN_PU_NO_IT); //上拉无中断
GPIO_Init(KEY5_PORT,KEY5_PIN,GPIO_MODE_IN_PU_NO_IT); //上拉无中断
GPIO_Init(KEY6_PORT,KEY6_PIN,GPIO_MODE_IN_PU_NO_IT); //上拉无中断
GPIO_Init(KEY7_PORT,KEY7_PIN,GPIO_MODE_IN_PU_NO_IT); //上拉无中断
}
//读取按键值
key_u16 Get_IO_Value(void)
{
key_u16 key_value;
key_value = 0x00;
if (!Key1_ReadSta())key_value |= KEY_UP;
if (!Key2_ReadSta())key_value |= KEY_DN;
if (!Key3_ReadSta())key_value |= KEY_LEFT;
if (!Key4_ReadSta())key_value |= KEY_RIGHT;
if (!Key5_ReadSta())key_value |= KEY_OK;
if (!Key6_ReadSta())key_value |= KEY_STOP;
if (!Key7_ReadSta())key_value |= KEY_RUN;
return key_value;
}
//================================ END ============================================
/*----------------------------------------------------------------------------------
@Function :Key_Scan
@Description:按键扫描
@Input :无
@Retrun :无
@Others :
----------------------------------------------------------------------------------*/
void Key_Scan(void)
{
key_u16 key_curr;
static key_u16 key_value;
static key_u16 key_last = 0x00; //最后一次被按下的键
static u8 cntDown = 0; //按下计数
gKey.Value = KEY_OFF;
gKey.Flag = 0x00;
key_curr = Get_IO_Value();//读取按键值
if (key_curr) //有键按下
{
if (key_last == key_curr) //当前与最一后次为同一个按键
{
cntDown++;
gKey.PressTime++; //记数按下的时间
if(cntDown == KEY_CNT_SHAKE) //消抖
{
gKey.Value = key_curr;
gKey.Flag = FLAG_PRESS; //标记按键按下
key_value = key_curr; //记下按键
gKey.PressTime = KEY_CNT_SHAKE; //需要重置这个记数
}
else if (cntDown == KEY_CNT_REP_END) //达到出现重复键的时间
{
cntDown = KEY_CNT_REP_END - KEY_CNT_REP_SPEED; //回退计数
gKey.Value = key_curr;
gKey.Flag = FLAG_REPEAT; //标记重复
}
if (gKey.PressTime == KEY_CNT_LONG_PRESS) //达到长按时间
{
gKey.Value = key_curr;
gKey.Flag = FLAG_LONG; //标记长按
}
}
key_last = key_curr;
}
else
{
if (cntDown > KEY_CNT_SHAKE) //按下达到消抖时间
{
gKey.Value = key_value;
gKey.Flag = FLAG_RELEASE; //标记释放
}
cntDown = 0;
key_last = KEY_OFF;
}
}
key_u16 Key_GetValue(void)
{
return gKey.Value;
}
u16 Key_GetFlag(void)
{
return gKey.Flag;
}
u16 Key_GetPressTime(void)
{
return gKey.PressTime;
}
//-------------END OF FILE--------------------
应用示例:
void KeyTestDemo();
int main(void)
{
/*设置内部高速时钟16M为主时钟*/
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1);
Uart2_Init(115200);
SystemClock_Init();
LED_Init();
Key_Init();
enableInterrupts();
while(1)
{
if (SysGetSignal_2ms(DELAY_20ms,1))
{
KeyTestDemo();
}
}
}
//每隔20ms调用一次KeyTestDemo
void KeyTestDemo()
{
Key_Scan();//按键检测
if (gKey.Value)
{
printf("[%x][%d] ",gKey.Value,gKey.PressTime); //打印键值和按下时间
}
if (gKey.Flag == FLAG_PRESS) //按下
{
switch(gKey.Value)
{
case KEY_UP:
LED_STA_ON(); //亮灯
break;
case KEY_DN:
break;
case (KEY_DN | KEY_UP): //组合键
LED_STA_ON();//亮灯
break;
}
}
else if (gKey.Flag == FLAG_RELEASE) //释放
{
switch(gKey.Value)
{
case KEY_UP:
LED_STA_OFF();
break;
case KEY_DN:
break;
}
}
else if (gKey.Flag == FLAG_REPEAT) //重复
{
switch(gKey.Value)
{
case KEY_UP:
LED_STAToggle();
break;
case KEY_DN:
break;
}
}
else if (gKey.Flag == FLAG_LONG) //长按
{
switch(gKey.Value)
{
case KEY_UP:
break;
case KEY_DN:
break;
}
}
}
这个示例演示了KEY_UP按下的时候LED亮,松开后LED灭,可以用在带灯的触摸键上
KEY_UP按住不放后,LED灯闪烁
KEY_UP和KEY_DN同时按下,指示灯亮起
也可以实现同一个按键按住1秒 、3秒、5秒实现不同功能
代码:
if (gKey.Flag == FLAG_REPEAT) //重复
{
switch(gKey.Value)
{
case KEY_UP:
if (gKey.PressTime == 50ul) //20ms扫描一次,50*20=1000,即1秒
{
//处理事情A
}
else if (gKey.PressTime == 150ul) //150*20=3000,即3秒
{
//处理事情B
}
else if (gKey.PressTime == 250ul) //150*20=3000,即3秒
{
//处理事情C
}
break;
}
}