STM32 C++类封装按键实现长按、短按、双击
一、背景
目的:
- 为了提升按键功能的可移植性、减少对底层的硬件的依赖、使其功能独立
- 减少每次开发频繁的重复工作
- 个人练习
注意??:
- 设置的回调函数内部避免使用耗时过长的功能或延时,否则影响按键响应
- 注意STM32工程的C和C++混合编程的问题
- 类的
keyFMS() 函数应该周期被调用,并且周期时长在创建对象时输入,此周期同时用于消抖 - 如果回调函数中任务简单可直接设置回调函数,否则可通过
keyFMS() 返回值判断按键事件 - 如果一直长按会周期执行回调函数而不是只执行一次
二、软硬件版本
三、代码
- 采用状态机的思想,状态+事件,动作引起状态的切换
- FS1表示按键释放状态及单击的判断
- FS2用于去抖动
- FS3用于长按及短按第一次的记录及双击的判断
- FS4用于等待长按结束
//头文件Key.h
namespace keys {
enum key_event { //表示按键的事件
KEY_RELEASE,
KEY_LONGPRESSED,
KEY_DOUBLEPRESSED,
KEY_SINGLEPRESSED,
};
class Key {
#define KEY_ISPRESSED 1
#define KEY_NOTPRESSED 0
public :
enum fms { //状态机
FS1_RELEASE,
FS2_DEBOUNCE,
FS3_PRESSED,
FS4_LONG,
};
Key(uint32_t frequency, uint16_t long_interval, uint16_t double_interval, int (*getIo)());
virtual ~Key() = default;
key_event keyFMS(); //状态机切换,内部执行回调函数,返回按键事件
void setSinglePressedFunc(void (*func)());
void setDoublePressedFunc(void (*func)());
void setLongPressedFunc(void (*func)());
private:
uint32_t key_scan_frequency_; //按键检测的频率,即调用keyFMS()的间隔ms
bool key_status_; //按键状态,按下或是抬起
fms fms_state_; //状态机的状态
uint16_t long_press_interval_; //长按的时长ms
uint16_t double_press_interval_; //双击的最大间隔ms
int (*getIoState_)(); //获取IO状态
void (*onSinglePressed)(); //事件发生时的调用函数的指针
void (*onDoublePressed)();
void (*onLongPressed)();
};
} /* namespace keys */
namespace keys {
Key::Key(uint32_t frequency, uint16_t long_interval, uint16_t double_interval, int (*getIo)())
: key_scan_frequency_(frequency), long_press_interval_(long_interval), double_press_interval_(double_interval),
getIoState_(getIo){
fms_state_ = FS1_RELEASE;
onDoublePressed = nullptr;
onLongPressed = nullptr;
onSinglePressed = nullptr;
}
key_event Key::keyFMS() {
key_event key_ret = KEY_RELEASE; //函数返回值
static uint64_t press_interval = 0; //递增的
static uint64_t press_cnt = 0; //长按次数cnt
static uint64_t single_pressed = 0; //单次按下
static bool double_waiting = false; //等待双击
key_status_ = !!getIoState_(); //获取按键状态
press_interval++;
switch(fms_state_) {
case FS1_RELEASE:
if (double_waiting && (press_interval-single_pressed) * key_scan_frequency_ >= double_press_interval_) { //双击发生
double_waiting = false;
if (onSinglePressed != nullptr) onSinglePressed();
key_ret = KEY_SINGLEPRESSED;
} else {
//do nothing
}
if (key_status_ == KEY_ISPRESSED) {
fms_state_ = FS2_DEBOUNCE;
} else {
//do nothing
}
break;
case FS2_DEBOUNCE:
if (key_status_ == KEY_ISPRESSED) {
fms_state_ = FS3_PRESSED;
} else {
fms_state_ = FS1_RELEASE; //消抖
}
break;
case FS3_PRESSED:
if (key_status_ == KEY_ISPRESSED) {
press_cnt++;
if (press_cnt >= long_press_interval_ / key_scan_frequency_) { //长按时间到
press_cnt = 0;
key_ret = KEY_LONGPRESSED;
if (onLongPressed != nullptr) onLongPressed(); //长按回调函数
fms_state_ = FS4_LONG;
}
} else {
single_pressed = press_interval; //记录单击的时间点
if (!double_waiting) {
double_waiting = true;
} else {
if (onDoublePressed != nullptr) onDoublePressed();
double_waiting = false;
key_ret = KEY_DOUBLEPRESSED;
}
fms_state_ = FS1_RELEASE;
}
break;
case FS4_LONG:
if (key_status_ == KEY_ISPRESSED) {
press_cnt++;
if (press_cnt >= long_press_interval_ / key_scan_frequency_) {
press_cnt = 0;
key_ret = KEY_LONGPRESSED;
if (onLongPressed != nullptr) onLongPressed(); //长按持续
}
} else { //长按结束
fms_state_ = FS1_RELEASE;
}
break;
}
return key_ret;
}
void Key::setSinglePressedFunc(void (*func)()) { //设置单击发生时候的函数
onSinglePressed = func;
}
void Key::setDoublePressedFunc(void (*func)()){
onDoublePressed = func;
}
void Key::setLongPressedFunc(void (*func)()){
onLongPressed = func;
}
} /* namespace keys */
四、总结
花了时间重新试着写了写这个按键类,其过程中遇到了许多的问题,如FS4的增加由于FS3长按如果直接返回FS1会导致短按发生。
另外是STM32CubeIDE对于C++头文件的支持真的是蛋疼,不知道为什么工程目录中includes上面的一系列头文件路径必须要自己手动加入到头文件路径中,否则找不到C++的头文件。
STM32CubeMX生成的代码最后还需要将一些需要C++的更改为.cpp,每次生成之前还要改回.c,否则生成同名的.c
最后建议老老实实写C代码吧
|