首先学习别人的东西,然后吸收别人的东西,接着应用并改造别人的东西,最后这个东西就属于你自己了
————小白
如果你对按键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按键算法
基于通用消抖算法,我们能够针对上列需求,对算法进行拓展,利用好CurrIOState
和LastIOState
这两个变量即可,前者代表当前状态,后者代表上一次的状态,对于按键Key,我们定义IO口的状态只有两种:1代表按下,0代表松开,于是能有4种组合:
CurrIOState | LastIOState | 含义 |
---|---|---|
0 | 0 | 初始化 |
0 | 1 | 松开 |
1 | 0 | 短按 |
1 | 1 | 长按 |
考虑到可移植性,需要把实际按键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 - 实际使用方法
上述为添加两个按键SmoothieKey
和PluseKey
为例的源代码,下列讲解如何使用,共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可裁剪性高