在单片机项目中,按键操作通常是产品与用户交互必不可少功能,按键又有短按、长按、重复、组合键等操作,本文介绍了一种按键扫描的实现方法,能够实现短按、长按、重复以为组合键的功能。
短按:即按下按键马上松开,如图1,按下的 时间tl小于允许的最大时间t_max,则认为是短按
长按:即按下按键3秒后再松开,如图2,按下的 时间tl大于时间t_lmax
重复:即一直按住按键不放,重复该按键,如图3,按下的时间达到t_rep,产生一次按键,达到时间r1又产生一次按键,以此重复。
我们可以通过计算按下的时间长短来确定是短按还是长按,在松开按键时,产生相应的键值。
扫描操作流程图
头文件
#ifndef __KEY_H
#define __KEY_H
#include "stm8l15x.h"
#include "stm8l15x_gpio.h"
#include "stm8l15x_exti.h"
/*--------按键标记 ------*/
#define KEY_NO 0x00 //无按键
#define KEY_S1 0x01 //按键1
#define KEY_S2 0x02 //按键2
#define KEY_S3 0x04
#define KEY_S4 0x08
#define KEY_REP_FLAG 0x40 //重复按下
#define KEY_LONG_FLAG 0x80 //长按3秒标记
//键值
typedef enum _key{
KEY_OFF = KEY_NO, //没有任何键按下
KEY_RIGHT = KEY_S2, //向右键
KEY_DN = KEY_S3, //向下键
KEY_UP = KEY_S1, //向上键
KEY_LEFT = KEY_S4, //向左键
KEY_L_UP =(KEY_LONG_FLAG|KEY_UP), //左键+上键
KEY_L_DN =(KEY_LONG_FLAG|KEY_DN),
KEY_L_RIGHT =(KEY_LONG_FLAG|KEY_RIGHT),
KEY_L_LEFT =(KEY_LONG_FLAG|KEY_LEFT),
KEY_R_UP =(KEY_REP_FLAG|KEY_UP),
KEY_R_DN =(KEY_REP_FLAG|KEY_DN),
KEY_R_RIGHT =(KEY_REP_FLAG|KEY_RIGHT),
KEY_R_LEFT =(KEY_REP_FLAG|KEY_LEFT),
KEY_L_UP_DN =(KEY_LONG_FLAG|KEY_UP|KEY_DN), //+ - 长按
KEY_L_LEFT_RIGHT =(KEY_LONG_FLAG|KEY_RIGHT|KEY_LEFT), //OK ESC 长按
KEY_L_LEFT_DN =(KEY_LONG_FLAG|KEY_DN|KEY_LEFT), //DN ESC 长按
}KeyValue_ENUM;
/*----------按键计时-----------*/
#define KEY_DOWN_CNT 50 //按下时长
#define KEY_SPEED 40 //重复按下重载时间
#define KEY_REP_3S 2 //重复速度
//*-----------IO-----------*/
#define KEY1_PORT GPIOA
#define KEY1_PIN GPIO_Pin_2
#define KEY2_PORT GPIOA
#define KEY2_PIN GPIO_Pin_3
#define KEY3_PORT GPIOC
#define KEY3_PIN GPIO_Pin_0
#define KEY4_PORT GPIOC
#define KEY4_PIN GPIO_Pin_1
/*-----------读取按键-----------*/
#define Key1_ReadSta() ((BitStatus)(KEY1_PORT->IDR&KEY1_PIN))
#define Key2_ReadSta() ((BitStatus)(KEY2_PORT->IDR&KEY2_PIN))
#define Key3_ReadSta() ((BitStatus)(KEY3_PORT->IDR&KEY3_PIN))
#define Key4_ReadSta() ((BitStatus)(KEY4_PORT->IDR&KEY4_PIN))
/*--------函数-------*/
void KEY_Init(void);
KeyValue_ENUM KeyScan(void);
#endif /* __KEY_H */
代码
/************************************************************
Copyright (C), 2013-2020
@FileName: KEY.C
@Author : 糊读虫 QQ:570525287
@Version : 2.0
@Date : 2019-8-12
@Description: 按键扫描
@Function List:
@History :
<author> <time> <version > <desc>
***********************************************************/
#include "key.h"
//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); //上拉无中断
}
//按键扫描
KeyValue_ENUM KeyScan(void)
{
uint8_t io_value;
static uint8_t key_Press; // 这个是要返回的键值
static uint8_t key_Old = 0x00;
static uint8_t DownCnt = 0; //按下计数
static struct _sta{
uint8_t up: 1; //弹起
uint8_t dn: 1; //按下
uint8_t rep: 1;
}Status; //按键状态标记
uint8_t ret;
io_value = 0x00;
ret = KEY_OFF;
//获取当前按下的按键
if (!Key1_ReadSta())io_value |= KEY_S1;
if (!Key2_ReadSta())io_value |= KEY_S2;
if (!Key3_ReadSta())io_value |= KEY_S3;
if (!Key4_ReadSta())io_value |= KEY_S4;
if (io_value) //如果按键被按下
{
if (key_Old == io_value) //判断与上次扫描到的是否为同一个按键
{
DownCnt++; //计数
if(DownCnt < KEY_DOWN_CNT)//当前计数小于长按的时间
{
if (Status.rep == 0) // 不是重复
{
Status.dn = 1;
key_Press = io_value; //记下按键
}
}
if (DownCnt >= KEY_DOWN_CNT) //当前计数大于长按的时间
{
Status.rep = 1; //标记重复
if(CntLongPress++ == KEY_REP_3S)//按下超过3秒
{
ret = io_value | KEY_LONG_FLAG; //加长按标记
}
else
{
ret = io_value | KEY_REP_FLAG; //重复
}
DownCnt = KEY_SPEED; //重复起始值
}
}
key_Old = io_value; //记录键值
}
else //按键松开
{
if (Status.dn && !Status.rep) //返回松开前的键值
{
ret = key_Press;
}
//清除标记
Status.dn = 0;
Status.rep = 0;
DownCnt = 0;
key_Old = KEY_NO;
}
return (KeyValue_ENUM)ret; //返回键值
}
//--------------END OF FILE---------------
在主函数中每隔20ms扫描一次
#include "key.h"
void main()
{
//....
KEY_Init();
while(1)
{
if(delay_20ms == 1)
{
KeyValue_ENUM key;
delay_20ms = 0;
key = KeyScan();
switch(key)
{
case KEY_RIGHT:
//处理按键
break;
case KEY_UP:
//处理按键
break;
//.....
}
}
}
}