【裸板驱动】按键驱动函数分享

一、源码

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就是程序的结算时间。此时间太短会导致双击难以触发,太长,会导致单击反应太慢,根据实际需求调整即可。

博客内容均系原创,未经允许严禁转载!
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇