一、源码
drv_key.c文件
/***********************************************************
文件功能:
实现按键的检测功能
用法介绍:
1.配置宏定义DRV_KEY_GET_STATE,设置按键状态获取函数
2.配置宏定义DRV_KEY_NUM,设置按键个数
3.设置Drv_Key_Init,初始化按键驱动函数
3.将函数Drv_Key_Scan放置于10ms循环周期中执行扫描
4.使用函数Drv_Key_Result,进行循环检测,获取结果
作者:
春风催雪
更新记录:
2021-12-31>>V0.1:初次使用
2022-10-26>>V0.2:删除初始化和ad获取函数,修改结果反馈函数
************************************************************/
#include "include.h"
#define DRV_KEY_GET_STATE Dev_Key_Out_State //按键状态输入,需要经过预处理,输出 0~DRV_KEY_NUM
#define DRV_KEY_NUM 6 //按键数,最大9个
#if DRV_KEY_NUM > 9
#error "DRV_ KEY_ The maximum value of NUM is 9 !!!"
#endif
typedef struct DRVKEYSTRUCT
{
uint8_t LongPressTf; //按键长按标志
uint16_t PressCnt[DRV_KEY_NUM]; //按下计数
uint8_t PressNum[DRV_KEY_NUM]; //按下次数
uint16_t ReleaseCnt; //松开计数
uint8_t PressPos; //按下位置,按键号
uint8_t ReleaseTf; //松开标志
uint8_t Result; //按键输出结果
}DRVKEY;
static DRVKEY DrvKey; //驱动结构体
/*
描述:按键结构体初始化函数
*/
int8_t Drv_Key_Init(void)
{
uint8_t i;
DrvKey.LongPressTf = 0;
for(i=0; i<DRV_KEY_NUM; i++)
{
DrvKey.PressCnt[i] = 0;
DrvKey.PressNum[i] = 0;
}
DrvKey.ReleaseCnt = 0;
DrvKey.PressPos = 0;
DrvKey.ReleaseTf = 1;
DrvKey.Result = 0;
return 0;
}
/*
按键扫描函数
每10ms执行一次
*/
void Drv_Key_Scan(void)
{
uint8_t keystate, i, j;
keystate = DRV_KEY_GET_STATE();
for(i=0; i<DRV_KEY_NUM; i++) //对所有按键进行检测
{
if(keystate == (i + 1)) //检测按下按键
{
if(DrvKey.PressCnt[i] < 6000) //对应按键开始计数
{
DrvKey.PressCnt[i]++;
}
DrvKey.ReleaseCnt = 0; //松开计数清零
DrvKey.PressPos = i; //标记按键位置
if(100 == DrvKey.PressCnt[i]) //按下1S时间产生事件
{
DrvKey.LongPressTf = 1;
DrvKey.Result = i + 91;
}
if(500 == DrvKey.PressCnt[i]) //按下5S时间产生事件
{
DrvKey.Result = i + 1;
}
if(1 == DrvKey.ReleaseTf) //重复触发计数
{
if(DrvKey.PressCnt[i] > 4)
{
DrvKey.ReleaseTf = 0;
DrvKey.PressNum[i]++;
}
}
for(j=0; j<DRV_KEY_NUM; j++) //清除其他按键的累计计数值
{
if(j != i)
{
DrvKey.PressCnt[j] = 0;
DrvKey.PressNum[j] = 0;
}
}
break;
}
}
if(DRV_KEY_NUM == i) //没有按键按下,for循环会轮询结束
{
if(DrvKey.ReleaseCnt <= 6000) //如果松开按键
{
DrvKey.ReleaseCnt++;
}
for(j=0; j<DRV_KEY_NUM; j++) //清除按键累计计数值
{
DrvKey.PressCnt[j] = 0;
}
if(4 == DrvKey.ReleaseCnt) //松开标志位置位
{
DrvKey.ReleaseTf = 1;
}
else if(20 == DrvKey.ReleaseCnt) //松开超一定时间最终判断按键
{
if(1 == DrvKey.LongPressTf) //执行过长按,清除事件标志
{
DrvKey.LongPressTf = 0;
}
else if((DrvKey.PressNum[DrvKey.PressPos] > 0) && (DrvKey.PressNum[DrvKey.PressPos] < 9))
{
DrvKey.Result = DrvKey.PressPos + DrvKey.PressNum[DrvKey.PressPos] * 10 + 1;
}
DrvKey.PressPos = 0; //清除所有
for(j=0; j<DRV_KEY_NUM; j++)
{
DrvKey.PressNum[j] = 0;
DrvKey.PressCnt[j] = 0;
}
}
}
}
/*
描述:按键键值结果返回
返回: 1~9 表示对应按键长按超过5s
x1~x9 表示对应按键,点击x下
91~99 表示对应按键长按超过1s
*/
uint8_t Drv_Key_Result(void)
{
uint8_t feedback;
feedback = DrvKey.Result;
DrvKey.Result = 0;
return feedback;
}
drv_key.h
#ifndef __MOD_KEY_H__
#define __MOD_KEY_H__
extern int8_t Drv_Key_Init(void);
extern void Drv_Key_Scan(void);
extern uint8_t Drv_Key_Result(void);
#endif
二、功能介绍
1、说起按键怎么实现,想必很多同学的第一反应就是,判断按键按下->延时消抖->再次判断->执行。博主的第一个按键程序就是这么写的。
2、这种处理方式不失为一种简单且有效的手段。如果有多个按键需要支持,就用多个这样的循环函数来实现,也未尝不可。
3、但是随着系统变得庞大,时间资源变得越来越稀缺,按键数量以及功能变得复杂,单击、双击、长按等等功能融合到一起,就有了这个功能模块。
4、完整的按键功能分为三层,底层是获取按键信号,例如1号键按下,2、3号键同时按下,等等。将此独立为一层,是为了剥离硬件影响。
5、中间层就是本驱动,将这些按键的状态,转化为键值,例如1号键单击,2、3号键长按,等等。
6、上层是应用层,将键值对应到具体的功能,例如1号键长按开机等等。
三、用法介绍
1、使用本驱动前,先做一点介绍。在这里按键号是个模糊的概念,并非一定要对应到某个按键。
a、一号实体键输入是1,二号实体键输入是2,那一号实体键和二号实体键同时按下呢?可以认为是三号,也可以是其他任何不冲突的编号。
b、按键输入信号,并非只能是io电平,也可以是adc信号,可以是其他任何信号。
2、有了上述的基础,再来具体的处理,首先是按键初始化,这部分是需要自行编写与调用的。
3、其次,配置宏定义DRV_KEY_GET_STATE,这个宏定义是“指定”一个按键状态获取函数,自然这个函数也是需要自行编写的。编写之前反复复习第一条,理解透彻再编写。按键状态作为此函数的输出,例如没有按键按下返回0,一号按键按下返回1,n号按键按下返回n。
4、配置宏定义DRV_KEY_NUM,指定按键的数量,这里的数量指的是DRV_KEY_GET_STATE返回的最大数值,即虚拟的按键数量。
5、调用函数Drv_Key_Init,用来初始化模块的相关参数。
6、将函数Drv_Key_Scan,放置于10ms循环周期中执行扫描。
7、使用函数Drv_Key_Result,获取键值。获取键值后自动清空内部,若不获取,键值将一直保留,直至被新键值覆盖。
四、实现逻辑
1、首先要建立一个基础模型。有两个计数,分别叫“按下计数”,“松开计数”。故名思意,当按键按下时,“按下计数”开始递增,“松开计数”归零。反之亦然。
2、但是我们的按键并不是只有一个,那怎么办呢?有办法,用“按下计数n”表示。那么此时,模型就变成了,假如按键1按下,“按下计数1”开始递增,“松开计数”和其他的“按下计数”归零。反之也亦然。
3、有了这个模型以后,我们就可以进行长按判断了。比如当按键1按下时,“按下计数1”开始递增,当这个计数值超过我们长按的阈值之后,就判断按键1长按,其他按键也是如此。
4、知道了怎么判断长按,接下来就是单击。单击其实有两个动作,按下->松开。假如我们要判断按键1单击,那么我们应该先判断“按下计数1”有计数,然后判断“松开计数”有计数,这样才能完成一次完整的单击判断。
不过需要注意的是,按下和松开都要有消抖,就是多次判断防止按键信号误触发的情况。所以加入消抖后,就变成了分别判断“按下计数1”和“松开计数”都超过一定时间。程序里面是4,也就是40ms消抖时间。
5、知道了单击,那么就知道双击,三击等多次按下的判断怎么处理了。例如双击,按下->松开->按下->松开视为双击。
不过现在有一个问题,第一次点按 按下->松开 之后,程序为什么不判断为单击呢,因为这就是单击的流程啊?答案就是时间,如果单击以后在很短时间内再次单击,那就是双击,如果间隔时间太久,那就被判断成单击,程序默认时间为20,即200ms。
6、所以总结,松开后200ms就是程序的结算时间。此时间太短会导致双击难以触发,太长,会导致单击反应太慢,根据实际需求调整即可。