Files
health-wellness-device-2/src/button_manager.cpp
T

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;
}