169 lines
5.9 KiB
C++
169 lines
5.9 KiB
C++
/**
|
|
* @file button_manager.cpp
|
|
* @brief 三按钮管理器实现 - 双向去抖 + 长按/短按 + 超时自愈
|
|
*
|
|
* 核心改进 (v2):
|
|
* 1. 按下沿和释放沿都经过 BTN_DEBOUNCE_MS 稳定确认
|
|
* 2. 长按期间持续检测 (不再等到松手才触发)
|
|
* 3. 按下超过 BTN_STUCK_TIMEOUT_MS 自动复位, 防止状态机卡死
|
|
* 4. 用 stableState 替代瞬时 raw 驱动状态, 消除噪声误触发
|
|
*/
|
|
|
|
#include "button_manager.h"
|
|
|
|
// ============================================================
|
|
// 构造
|
|
// ============================================================
|
|
|
|
ButtonManager::ButtonManager()
|
|
: _cbProvision(nullptr)
|
|
, _cbDismiss(nullptr)
|
|
, _cbPage(nullptr)
|
|
{
|
|
auto initBtn = [](ButtonState& s, uint8_t pin) {
|
|
s.pin = pin;
|
|
s.stableState = false;
|
|
s.rawState = false;
|
|
s.changeTime = 0;
|
|
s.pressTime = 0;
|
|
s.longTriggered = false;
|
|
s.shortPending = false;
|
|
};
|
|
|
|
initBtn(_btnProvision, BTN_PROVISION_PIN);
|
|
initBtn(_btnDismiss, BTN_ALARM_DISMISS_PIN);
|
|
initBtn(_btnPage, BTN_PAGE_SWITCH_PIN);
|
|
}
|
|
|
|
// ============================================================
|
|
// 初始化
|
|
// ============================================================
|
|
|
|
void ButtonManager::begin() {
|
|
pinMode(BTN_PROVISION_PIN, INPUT_PULLUP);
|
|
pinMode(BTN_ALARM_DISMISS_PIN, INPUT_PULLUP);
|
|
pinMode(BTN_PAGE_SWITCH_PIN, INPUT_PULLUP);
|
|
|
|
// 强制初始化为未按下 (PCB 上电 GPIO 不稳, 忽略初始状态)
|
|
_btnProvision.rawState = false;
|
|
_btnProvision.stableState = false;
|
|
_btnDismiss.rawState = false;
|
|
_btnDismiss.stableState = false;
|
|
_btnPage.rawState = false;
|
|
_btnPage.stableState = false;
|
|
|
|
Serial.println(F("[按钮] ✅ 三按钮初始化完成 (双向去抖+超时自愈)"));
|
|
Serial.printf(" 配网: GPIO%d | 解除报警: GPIO%d | 翻页: GPIO%d\n",
|
|
BTN_PROVISION_PIN, BTN_ALARM_DISMISS_PIN, BTN_PAGE_SWITCH_PIN);
|
|
Serial.printf(" 去抖:%dms | 长按:%dms | 卡死超时:%dms\n",
|
|
BTN_DEBOUNCE_MS, BTN_LONG_PRESS_MS, BTN_STUCK_TIMEOUT_MS);
|
|
}
|
|
|
|
// ============================================================
|
|
// 主循环轮询
|
|
// ============================================================
|
|
|
|
void ButtonManager::loop() {
|
|
// 开机 10 秒静默 (PCB 上电 GPIO 不稳 + 强制初始未按下)
|
|
if (millis() < 10000) return;
|
|
|
|
// 按钮1: 配网 (仅长按)
|
|
_processButton(_btnProvision, false, true);
|
|
|
|
// 按钮2: 解除报警 (仅短按)
|
|
_processButton(_btnDismiss, true, false);
|
|
|
|
// 按钮3: 翻页 (仅短按)
|
|
_processButton(_btnPage, true, false);
|
|
}
|
|
|
|
// ============================================================
|
|
// 单按钮状态机 (基于稳定状态, 非瞬时边沿)
|
|
// ============================================================
|
|
|
|
void ButtonManager::_processButton(ButtonState& btn, bool hasShort, bool hasLong) {
|
|
uint32_t now = millis();
|
|
bool raw = _readPin(btn.pin); // true = 按下 (LOW)
|
|
|
|
// ── 第1步: raw 状态变化时启动去抖计时 ──
|
|
if (raw != btn.rawState) {
|
|
btn.rawState = raw;
|
|
btn.changeTime = now;
|
|
// 不立即改变 stableState — 等稳定确认
|
|
}
|
|
|
|
// ── 第2步: 去抖确认 — raw 保持稳定超过 BTN_DEBOUNCE_MS 才生效 ──
|
|
bool debounced = (now - btn.changeTime >= BTN_DEBOUNCE_MS);
|
|
bool stateChanged = (btn.rawState != btn.stableState);
|
|
|
|
if (debounced && stateChanged) {
|
|
// 状态正式切换
|
|
btn.stableState = btn.rawState;
|
|
|
|
if (btn.stableState) {
|
|
// ── 确认按下 ──
|
|
btn.pressTime = now;
|
|
btn.longTriggered = false;
|
|
btn.shortPending = hasShort; // 只有支持短按的按钮才标记待判定
|
|
} else {
|
|
// ── 确认释放 ──
|
|
uint32_t holdTime = now - btn.pressTime;
|
|
|
|
// 短按判定: 按下时长在去抖和长按阈值之间
|
|
if (btn.shortPending && holdTime >= BTN_DEBOUNCE_MS
|
|
&& holdTime < BTN_LONG_PRESS_MS && !btn.longTriggered) {
|
|
// 根据引脚分发短按回调
|
|
if (btn.pin == BTN_ALARM_DISMISS_PIN && _cbDismiss) {
|
|
_cbDismiss();
|
|
}
|
|
if (btn.pin == BTN_PAGE_SWITCH_PIN && _cbPage) {
|
|
_cbPage();
|
|
}
|
|
}
|
|
|
|
btn.shortPending = false;
|
|
btn.longTriggered = false;
|
|
}
|
|
}
|
|
|
|
// ── 第3步: 长按检测 (在按下稳定期间持续检查) ──
|
|
if (hasLong && btn.stableState && !btn.longTriggered) {
|
|
uint32_t holdTime = now - btn.pressTime;
|
|
if (holdTime >= BTN_LONG_PRESS_MS) {
|
|
btn.longTriggered = true;
|
|
btn.shortPending = false; // 长按已触发, 不再判定短按
|
|
if (_cbProvision) {
|
|
_cbProvision();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── 第4步: 卡死自愈 — 按下超时强制复位 ──
|
|
if (btn.stableState && (now - btn.pressTime >= BTN_STUCK_TIMEOUT_MS)) {
|
|
Serial.printf("[按钮] ⚠️ GPIO%d 按下超时 %lums, 强制复位\n",
|
|
btn.pin, now - btn.pressTime);
|
|
_resetButton(btn);
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// 强制复位
|
|
// ============================================================
|
|
|
|
void ButtonManager::_resetButton(ButtonState& btn) {
|
|
btn.stableState = false;
|
|
btn.rawState = _readPin(btn.pin); // 重新同步真实硬件状态
|
|
btn.changeTime = 0;
|
|
btn.pressTime = 0;
|
|
btn.longTriggered = false;
|
|
btn.shortPending = false;
|
|
}
|
|
|
|
// ============================================================
|
|
// 读引脚 (INPUT_PULLUP → LOW = 按下)
|
|
// ============================================================
|
|
|
|
bool ButtonManager::_readPin(uint8_t pin) {
|
|
return digitalRead(pin) == LOW;
|
|
}
|