首先学习别人的东西,然后吸收别人的东西,接着应用并改造别人的东西,最后这个东西就属于你自己了
————小白
如果你对按键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