代码拉取完成,页面将自动刷新
// Copyright (c) 2023 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#pragma once
#include <type_traits>
#include "ioxx.hpp"
#include "ioxx_scheduler_basic.hpp"
namespace scheduler_basic {
/**
* @brief 集成消抖功能的单按键轮询监听工具,只支持低电平有效的按键。
*
* @tparam TimeType 默认参数假设时间单位是毫秒,如果不是,需要调整按键扫描周期、长按时间、双击间隔时间等参数
* @tparam LongPressThreshold 按键按下后,经过指定次数的轮询还没抬起,则触发长按事件。参数值要考虑消抖所需的额外轮询次数,若长按次数为6,而滤波参数要求8 次1 才能抬起按键,则每次按键都会触发长按
* @tparam DoublePressThreshold 一次click 后,若在等待时间内按键再次按下,触发double_press 事件;若按键抬起后触发了第二次click,则与第二次click 同时触发双击事件。参数值同样要考虑消抖所需的轮询次数。
* @tparam ScanPeriod 默认轮询周期8ms,忽略定时任务的延迟,扫描周期是125Hz
* @tparam FilterMask 默认值0x003f,表示只关心ff 对应的低6 位,即连续6 次读按键的电平。若6 次全为0,则判定按键按下;反之,若全为1,则为抬起
*/
template <typename TimeType,
uint16_t LongPressThreshold = 120,
uint16_t DoublePressThreshold = 30,
TimeType ScanPeriod = 8,
uint16_t FilterMask = 0x003f>
class ButtonListener : TaskBase<TimeType> {
private:
ioxx::PinToken _button_pin;
uint16_t _flag_bits = 0;
uint16_t _long_press_counter = 0;
uint16_t _double_click_waiting_counter = 0;
uint16_t _filter_buffer = 0xffff;
// 每个枚举值对应_flag_bits 里的一个位,用于存储标志。用这种方式而不是位域,原因是这样更方便同时操作多个标志位。
enum class _flag {
pressed = 0,
double_click_before_release,
running,
// 有button 前缀的标志位可以称为“按键信号”或“按键事件”,由调用者在响应事件后随时清零。
// 除了button_long_pressing 标志,其他标志扫描函数只负责置位,不自动清零
// 没有前缀的标志,如pressed,用于支持内部逻辑,调用者不能直接控制
button_pressed,
button_released,
button_clicked,
button_double_pressed,
button_double_clicked,
button_double_press_timeout, // 按键抬起后自动清零,在阈值时间内没有发生双击,则触发双击超时事件,
button_long_pressed,
button_long_press_released,
button_long_pressing, // 在长按发生时置位,长按抬起时自动清零
// 一共 12 个标志位,可以放进uint16_t 里
};
constexpr auto _flag_to_num(_flag f) {
return static_cast<std::underlying_type_t<decltype(f)>>(f);
}
constexpr uint16_t _calc_flag_mask_sum(_flag f) {
return (1 << _flag_to_num(f));
}
template <typename... Ts>
constexpr uint16_t _calc_flag_mask_sum(_flag f, Ts... fs) {
return (1 << _flag_to_num(f)) | _calc_flag_mask_sum(fs...);
}
template <typename... Ts>
void _set_flag(_flag f, Ts... fs) {
_flag_bits |= _calc_flag_mask_sum(f, fs...);
}
template <typename... Ts>
void _clr_flag(_flag f, Ts... fs) {
_flag_bits &= ~_calc_flag_mask_sum(f, fs...);
}
auto _test_flag(_flag f) const {
return (_flag_bits & _calc_flag_mask_sum(f));
}
auto _test_and_clr_flag(_flag f) {
auto r = _test_flag(f);
_clr_flag(f);
return r;
}
public:
ButtonListener(ioxx::PinToken button_pin) :
_button_pin(button_pin) {
_set_flag(_flag::running);
}
void poll_button() {
this->_filter_buffer <<= 1;
if (ioxx::test_pin(this->button_pin)) {
this->_filter_buffer |= 0x01;
}
if (!_test_flag(_flag::pressed)) { // 按键抬起时
if ((this->_filter_buffer & FilterMask) == 0) { // 连续8 次低电平,表示按键确实按下了
_set_flag(_flag::pressed, _flag::button_pressed);
// 第二次按下发生在双击等待时间内,抬起后可能触发双击
if (_double_click_waiting_counter != 0) {
_set_flag(_flag::double_click_before_release, _flag::button_double_pressed);
_double_click_waiting_counter = 0;
}
// 启动长按计数
_long_press_counter = LongPressThreshold;
}
else { // 否则按键还在抬起状态,更新双击等待计数器,直到计数器归零
if (_double_click_waiting_counter != 0) {
--_double_click_waiting_counter;
if (_double_click_waiting_counter == 0) {
_set_flag(_flag::button_double_press_timeout);
}
}
}
}
else { // 按键保持按下时
if (((~this->_filter_buffer) & FilterMask) == 0) { // 连续8 次高电平,表示按键抬起。所以一次press 至少会持续8 次扫描
// 启动双击等待计数器
_double_click_waiting_counter = DoublePressThreshold;
// 长按后抬起
if (_long_press_counter == 0) {
_set_flag(_flag::button_long_press_released);
_clr_flag(_flag::button_long_pressing);
}
else { // 如果按下的时间没到长按的阈值,则在按键抬起时发出clicked 信号,否则忽略
_set_flag(_flag::button_clicked);
// 双击事件和第二次clicked 事件同时发出
if (_test_flag(_flag::double_click_before_release)) {
_set_flag(_flag::button_double_clicked);
}
}
// 抬起后清零长按计数器
_long_press_counter = 0;
// 无论如何,双击等待标志都要清零,还有双击超时事件
_clr_flag(_flag::pressed, _flag::double_click_before_release, _flag::button_double_press_timeout);
_set_flag(_flag::button_released);
}
else { // 否则按键尚未抬起,更新长按计数器和长按事件
if (this->_long_press_counter != 0) {
--(this->_long_press_counter);
if (this->_long_press_counter == 0) {
_set_flag(_flag::button_long_pressed, _flag::button_long_pressing);
}
}
}
}
}
TimeType run() override {
if (_test_flag(_flag::running)) {
poll_button();
}
return ScanPeriod;
}
void stop_polling() {
_clr_flag(_flag::running);
}
void start_polling() {
_set_flag(_flag::running);
}
/**
* @brief 完全复位内部状态,停止轮询按键
*
*/
void reset() {
_flag_bits = 0;
_long_press_counter = 0;
_double_click_waiting_counter = 0;
_filter_buffer = 0xffff;
}
/**
* @brief 在按键抬起后重置内部状态。
*
* 如果在按键尚未抬起时重置状态,可能在单次点击中重复触发按键事件。比如,按键按下时触发了button_pressed 事件,
* 然后立即重置状态,若按键没有抬起,button_pressed 事件会再置位一次。
*
* @return true 按键已释放,内部状态和事件标志已复位
* @return false 按键尚未释放
*/
bool reset_after_button_released() {
if (_test_flag(_flag::pressed)) {
return false;
}
else {
reset();
return true;
}
}
// pressed === 事件
auto test_pressed_flag() const {
return _test_flag(_flag::button_pressed);
}
void clr_pressed_flag() {
_clr_flag(_flag::button_pressed);
}
auto test_and_clr_pressed_flag() {
return _test_and_clr_flag(_flag::button_pressed);
}
// released === 事件
auto test_released_flag() const {
return _test_flag(_flag::button_released);
}
void clr_released_flag() {
_clr_flag(_flag::button_released);
}
auto test_and_clr_released_flag() {
return _test_and_clr_flag(_flag::button_released);
}
// clicked === 事件
auto test_clicked_flag() const {
return _test_flag(_flag::button_clicked);
}
void clr_clicked_flag() {
_clr_flag(_flag::button_clicked);
}
auto test_and_clr_clicked_flag() {
return _test_and_clr_flag(_flag::button_clicked);
}
// double clicked === 事件
auto test_double_clicked_flag() const {
return _test_flag(_flag::button_double_clicked);
}
void clr_double_clicked_flag() {
_clr_flag(_flag::button_double_clicked);
}
auto test_and_clr_double_clicked_flag() {
return _test_and_clr_flag(_flag::button_double_clicked);
}
// long_pressed === 事件
auto test_long_pressed_flag() const {
return _test_flag(_flag::button_long_pressed);
}
void clr_long_pressed_flag() {
_clr_flag(_flag::button_long_pressed);
}
auto test_and_clr_long_pressed_flag() {
return _test_and_clr_flag(_flag::button_long_pressed);
}
// long_press_released === 事件
auto test_long_press_released_flag() const {
return _test_flag(_flag::button_long_press_released);
}
void clr_long_press_released_flag() {
_clr_flag(_flag::button_long_press_released);
}
auto test_and_clr_long_press_released_flag() {
return _test_and_clr_flag(_flag::button_long_press_released);
}
};
} // namespace scheduler_basic
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。