【嵌入式底层知识修炼】基于通用消抖算法,拓展通用非阻塞Key按键识别算法


首先学习别人的东西,然后吸收别人的东西,接着应用并改造别人的东西,最后这个东西就属于你自己了

————小白


  如果你对按键Key识别处理算法有下列需求,则可以移植代码进行使用,并留意注意点

  • 算法所占code少于0.5K
  • 能识别最基本的初始化、短按、长按、松开,四种状态
  • 可移植性高,配置容易,Key可裁剪性高
  • 注意:在得到LongPress之前,会得到一个ShortPress

  对于按键处理,很多MCU芯片都自带库,但是当自己遇到一款没有库并且要实现按键处理功能的需求时,往往要自己实现,我们都不建议造轮子,既然已经有了轮子,我们改造便是

01 - 通用消抖算法

  下列为一个通用的消抖算法,以按键Key消抖为例,Key_IO是按键对应的IO口值,Service_10ms()需要放置一个每10ms就执行一次的硬件或软件定时器中,作用是滤去抖动,消抖的时间是DEBOUNCE_TIMES * 10ms,比如下列宏定义为5,则消抖时间为50ms

#define DEBOUNCE_TIMES 	5

uint8_t Key_CurrState, Key_LastState, Key_Counter;
uint8_t Key_GetState(void) {
	uint8_t Flag = Key_IO;	//1、Get key IO value
	if (Key_CurrState != Flag) {
		Key_CurrState = Flag;
		Key_Counter = DEBOUNCE_TIMES;
	}
	if (Key_LastState != Key_CurrState) {
		if (Key_Counter == 0) {
			Key_LastState = Key_CurrState; //2、The key state has changed after debounce
		}
	}
	return Key_LastState;
}

void Key_DebounceService_10ms(void) {
	if (PluseKey_Counter) {
		PluseKey_Counter--;
	}
}

  其中按键Key真正得到改变的识别代码是注释2所在行,也就是在此能够得到Key改变时当前的状态,可以在此进行拓展

02 - Key按键识别需求

  按键Key识别需要识别出Key最基本的4个状态:初始化、短按、长按、松开,其中初始化是指系统开始时,按键Key没有发生任何变化,此时不算为松开状态,故计入初始化状态,再加上嵌入式的资源紧张条件,要求算法所占code不能太多,因此有下列需求:

  • 算法所占code少于0.5K
  • 能识别最基本的初始化、短按、长按、松开,四种状态
  • 可移植性高,配置容易,Key可裁剪性高

03 - 拓展通用非阻塞Key按键算法

  基于通用消抖算法,我们能够针对上列需求,对算法进行拓展,利用好CurrIOStateLastIOState这两个变量即可,前者代表当前状态,后者代表上一次的状态,对于按键Key,我们定义IO口的状态只有两种:1代表按下,0代表松开,于是能有4种组合:

CurrIOStateLastIOState含义
00初始化
01松开
10短按
11长按

  考虑到可移植性,需要把实际按键Key的IO值抽象,同时IO口的状态也有正负逻辑之分,实际可能是0代表按下,1代表松开,最后实际的长按时间也各不相同,或短或长,需要使用者去定义,于是第一个封装好的结构体如下:

typedef uint8_t (*fpGetIOStateFunc)(void);
typedef struct {
	uint8_t LongPressTime;
	uint8_t ZeroIsPress;
	fpGetIOStateFunc GetIOFunc;
} KeyInfo_t;

  此结构体由使用者提供配置,具体含义如下:

变量含义
LongPressTime按下多久算长按(不包括消抖时间),单位是10ms,如果为0,使用默认值2000ms
ZeroIsPress按键Key电路的逻辑,若ZeroIsPress==1,代表Key闭合的时候IO的值为0,Key断开时IO的值为1;0则相反
GetIOFunc得到按键Key电路IO值的函数,返回uint8_t,此函数必须提供,不能为NULL

  随后,把通用消抖算法所用到的变量进行封装,得到另一个结构体,对使用者而言,只关心其中一个State

typedef enum {
	KeyState_None,
	KeyState_ShortPress,
	KeyState_LongPress,
	KeyState_Release,
} KeyState_t;

typedef struct {
	uint8_t CurrIOState;
	uint8_t LastIOState;
	uint8_t DebounceCounter;
	KeyState_t State;
} Key_t;
变量含义
CurrIOState当前按键Key的IO值
LastIOState上一次按键Key的IO值
DebounceCounter按键Key的消抖倒计数
State按键当前Key状态

  添加两个按键SmoothieKey和PluseKey为例,最后源代码为:
Key.h

typedef enum {
    KeyState_None,
    KeyState_ShortPress,
    KeyState_LongPress,
    KeyState_Release,
} KeyState_t;
 
typedef uint8_t (*fpGetIOStateFunc)(void);
typedef struct {
    uint8_t LongPressTime;
    uint8_t ZeroIsPress;
    fpGetIOStateFunc GetIOFunc;
} KeyInfo_t;
 
typedef struct {
    uint8_t CurrIOState;
    uint8_t LastIOState;
    uint8_t DebounceCounter;
    KeyState_t State;
} Key_t;
 
extern void Key_Init(void);
extern void Key_DebounceService_10ms(void);
extern void Key_Scand(void);
 
extern KeyState_t PluseKey_GetState(void);
extern KeyState_t SmoothieKey_GetState(void);

Key.c

#define DEBOUNCE_TIME_10MS              5
#define DEFAULT_LONGPRESS_TIME_10MS     200
 
typedef enum {
    KeyIndex_Smoothie,
    KeyIndex_Pluse,
 
    KeyIndex_Max
} KeyIndex_t;
 
static uint8_t SmoothieKey_GetIO(void);
static uint8_t PluseKey_GetIO(void);
 
const KeyInfo_t KeyInfo_Array[KeyIndex_Max] = {
    /* {LongPressTime, ZeroIsPress, GetIOFunc(Must not NULL)} */
    {0, 1, SmoothieKey_GetIO},
    {100, 0, PluseKey_GetIO},
};
 
static Key_t KeyValue_Array[KeyIndex_Max];
 
void Key_Scand(void) {
    uint8_t i;
    uint8_t IOState;
 
    for (i = 0; i < KeyIndex_Max; ++i) {
        IOState = KeyInfo_Array[i].GetIOFunc();
        IOState = (KeyInfo_Array[i].ZeroIsPress ? (!IOState) : (IOState));
        if (KeyValue_Array[i].CurrIOState != IOState) {
            KeyValue_Array[i].CurrIOState = IOState;
            KeyValue_Array[i].DebounceCounter = DEBOUNCE_TIME_10MS;
        }
 
        //Wait for debounce
        if (KeyValue_Array[i].LastIOState != KeyValue_Array[i].CurrIOState) {
            if (KeyValue_Array[i].DebounceCounter == 0) {
                if ((KeyValue_Array[i].CurrIOState) && (!KeyValue_Array[i].LastIOState)) {
                    KeyValue_Array[i].State = KeyState_ShortPress;
                    //Ready LongPress
                    if (KeyInfo_Array[i].LongPressTime) {
                        KeyValue_Array[i].DebounceCounter = KeyInfo_Array[i].LongPressTime;
                    } else {
                        KeyValue_Array[i].DebounceCounter = DEFAULT_LONGPRESS_TIME_10MS;
                    }
                } else if ((!KeyValue_Array[i].CurrIOState) && (KeyValue_Array[i].LastIOState)) {
                    KeyValue_Array[i].State = KeyState_Release;
                } else {
                    KeyValue_Array[i].State = KeyState_None;
                }
                KeyValue_Array[i].LastIOState = KeyValue_Array[i].CurrIOState;
            }
        }
 
        //Wait for LongPress
        if (KeyValue_Array[i].State == KeyState_ShortPress) {
            if (KeyValue_Array[i].DebounceCounter == 0) {
                KeyValue_Array[i].State = KeyState_LongPress;
            }
        }
    }
}
 
void Key_DebounceService_10ms(void) {
    uint8_t i;
    for (i = 0; i < KeyIndex_Max; ++i) {
        if (KeyValue_Array[i].DebounceCounter) {
            KeyValue_Array[i].DebounceCounter--;
        }
    }
}
 
 
static uint8_t SmoothieKey_GetIO(void) {
    return IO_SmoothieKey;
}
KeyState_t SmoothieKey_GetState(void) {
    return KeyValue_Array[KeyIndex_Smoothie].State;
}
 
static uint8_t PluseKey_GetIO(void) {
    return IO_PluseKey;
}
KeyState_t PluseKey_GetState(void) {
    return KeyValue_Array[KeyIndex_Pluse].State;
}
 
void Key_Init(void) {
    uint8_t i = 0;
    for(i = 0; i < KeyIndex_Max; ++i) {
        KeyValue_Array[i].CurrIOState = 0;
        KeyValue_Array[i].LastIOState = 0;
        KeyValue_Array[i].DebounceCounter = 0;
        KeyValue_Array[i].State = KeyState_None;
    }
}

04 - 实际使用方法

  上述为添加两个按键SmoothieKeyPluseKey为例的源代码,下列讲解如何使用,共5步:
① Key.c中KeyIndex_t枚举中添加新按键Key的索引,将作为数组的下标

typedef enum {
    KeyIndex_Smoothie,
    KeyIndex_Pluse,
 
    KeyIndex_Max
} KeyIndex_t;

② Key.c中填写KeyInfo_Array[],对应的数组项需要和KeyIndex_t枚举中相对应,其中含义看上文:

static uint8_t SmoothieKey_GetIO(void);
static uint8_t PluseKey_GetIO(void);
 
const KeyInfo_t KeyInfo_Array[KeyIndex_Max] = {
    /* {LongPressTime, ZeroIsPress, GetIOFunc(Must not NULL)} */
    {0, 1, SmoothieKey_GetIO},
    {100, 0, PluseKey_GetIO},
};

③ Key.c中提供②中的GetIOFunc,以及定义读取按键Key状态的函数

static uint8_t SmoothieKey_GetIO(void) {
    return IO_SmoothieKey;
}
KeyState_t SmoothieKey_GetState(void) {
    return KeyValue_Array[KeyIndex_Smoothie].State;
}
 
static uint8_t PluseKey_GetIO(void) {
    return IO_PluseKey;
}
KeyState_t PluseKey_GetState(void) {
    return KeyValue_Array[KeyIndex_Pluse].State;
}

④ Key.h中声明读取函数,并在需要的地方调用

extern KeyState_t PluseKey_GetState(void);
extern KeyState_t SmoothieKey_GetState(void);

Key_Init()为初始化函数,需要在进入系统前调用;Key_DebounceService_10ms()为消抖函数,需要每10ms调用一次;Key_Scand()为按键扫描,扫描频率可以自己决定,放在while(1)循环或者定时器中

05 - 总结

  • 算法所占code少于0.5K
  • 能识别最基本的初始化、短按、长按、松开,四种状态
  • 可移植性高,配置容易,Key可裁剪性高
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值