diff --git a/CHANGELOG.md b/CHANGELOG.md index 31a018f..762b320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,8 +36,8 @@ - `tca9535PowerEn(bool on)` — read-modify-write P1.2,static inline - P1.3 = POWER_BOOT 输入,低电平有效(按键按下接地) - `tca9535ReadPowerBoot()` — 读取 P1.3 状态,static inline -- 开机流程:物理按键 → MOS 导通 → ESP32 得电 → init() 检测 P1.3 持续按住 2 秒 → POWER_EN 拉高维持供电 - - 未按满 2 秒松开 → 不拉高 POWER_EN → MOS 断开 → 自动断电 +- 开机流程:物理按键 → MOS 导通 → ESP32 得电 → main.cpp 立即锁 POWER_EN → 等待 P1.3 持续按住 2 秒确认 → 启动系统 + - 3 秒内未按满 2 秒 → POWER_EN 拉低 → MOS 断开 → 自动断电 - 关机流程:运行中 P1.3 持续按住 2 秒 → 清空屏幕 → POWER_EN 拉低 → 用户松手后 MOS 断开断电 - 电源状态机:`BOOT_PENDING` → `RUNNING` → `SHUTDOWN_PENDING` - P1 口配置:`0x8B`(P1.2=输出, P1.3=输入, P1.4=输出, P1.5=输出, P1.6=输出, P1.7=输出) @@ -63,6 +63,21 @@ ### Changed +#### 开机流程改为 early-lock + 确认窗口 +- `main.cpp`:`Wire.begin()` 后立即 `tca9535PowerEn(true)` 锁住供电,防止初始化途中掉电 +- 新增开机确认窗口:等待 P1.3 持续按住 2 秒确认开机,最多等 3 秒,超时则断电关机 +- `TCA9535ButtonThread::init()` 不再负责开机确认,只设置状态机为 RUNNING + +#### 快捷回复 ↔ 九宫格输入导航(esp32c3_moonshine_travelers) +- **INACTIVE**:UP/DOWN 进入快捷回复列表(恢复原始行为) +- **ACTIVE**(快捷回复列表):LEFT/RIGHT 进入九宫格文本输入(FREETEXT),不再映射为上下滚动 +- **FREETEXT**(九宫格输入):LEFT/RIGHT 返回快捷回复列表,保留已输入文字 +- `*` 号键映射为退格键(backspace) +- `isUpEvent()` / `isDownEvent()` 移除 ACTIVE 状态对 LEFT/RIGHT 的映射 + +#### 充电检测轮询间隔缩短 +- TCA9535 CHARGE_DET 轮询间隔从 2000ms 缩短到 500ms,加快充电状态响应 + #### 按键映射更新(key3/key7/key11/key15 = 方向键) - 矩阵按键映射从 `key1=UP, key2=DOWN, key3=LEFT, key4=RIGHT` 改为 `key3=UP, key7=DOWN, key11=LEFT, key15=RIGHT` - 方向键全部位于 COL3 列(key3=ROW0·COL3, key7=ROW1·COL3, key11=ROW2·COL3, key15=ROW3·COL3) diff --git a/PCB/Moonshine_travelers.eprj2 b/PCB/Moonshine_travelers.eprj2 index 342eab0..f92c413 100644 Binary files a/PCB/Moonshine_travelers.eprj2 and b/PCB/Moonshine_travelers.eprj2 differ diff --git a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp index fa5cb02..a861c52 100644 --- a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp +++ b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp @@ -116,13 +116,11 @@ bool TCA9535ButtonThread::init() tca9535GpsEn(true); // =================================================================== - // 第二步:POWER_EN 已由 main.cpp 在 Wire.begin() 后立即拉高(早期锁定) - // 此处只需确认状态机进入 RUNNING,不再需要等待 P1.3 按住 2 秒。 - // 原因:系统从 Wire.begin() 到 tca9535ButtonThread::init() 之间需要 - // 数秒的初始化时间(LoRa/WiFi/BLE/GPS 等),用户早已松开按键, - // 无法在此处等待。开机供电维持已在 main.cpp 最早期完成。 + // 第三步:POWER_EN 已由 main.cpp 在 Wire.begin() 后立即拉高, + // 并等待 P1.3 持续按住 2 秒确认开机(超时 3 秒则断电关机)。 + // 此处只需确认状态机进入 RUNNING。 // =================================================================== - LOG_INFO("TCA9535: POWER_EN already latched in early boot, skipping boot-hold wait"); + LOG_INFO("TCA9535: Boot already confirmed in early boot, state=RUNNING"); _powerState = TCA9535PowerState::RUNNING; // =================================================================== @@ -211,7 +209,7 @@ int32_t TCA9535ButtonThread::runOnce() // 充电检测:轮询 P1.1 (CHARGE_DET),高电平=正在充电 // =================================================================== #ifdef TCA9535_CHARGE_DET_PIN - if (millis() - _chargeDetLastMs >= 2000) { + if (millis() - _chargeDetLastMs >= 500) { _chargeDetLastMs = millis(); bool charging = tca9535ReadChargeDet(); if (charging != tca9535IsCharging) { diff --git a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h index c35ee71..20c3e3b 100644 --- a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h +++ b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h @@ -16,8 +16,8 @@ * * 电源管理逻辑: * 开机:物理按键按下 → MOS 导通 → ESP32/TCA9535 得电 - * init() 读 P1.3,持续按住 2 秒 → tca9535PowerEn(true) 维持供电 - * 未按满 2 秒松开 → 不拉高 POWER_EN → MOS 断开 → 断电 + * main.cpp Wire.begin() 后立即拉高 POWER_EN 锁住供电, + * 然后等待 P1.3 持续按住 2 秒确认开机(超时 3 秒则断电关机) * 关机:运行中 P1.3 持续低电平 2 秒 → tca9535PowerEn(false) → 断电 * * 寄存器布局: diff --git a/code/firmware-2.7.15.567b8ea/src/main.cpp b/code/firmware-2.7.15.567b8ea/src/main.cpp index 37c9ff5..5162224 100644 --- a/code/firmware-2.7.15.567b8ea/src/main.cpp +++ b/code/firmware-2.7.15.567b8ea/src/main.cpp @@ -586,10 +586,43 @@ void setup() Wire.begin(I2C_SDA, I2C_SCL); #ifdef HAS_TCA9535_BUTTON // TCA9535 POWER_EN 必须在 I²C 初始化完成后立即拉高,否则用户松开按键后 - // MOS 断电,系统在 setup() 中途就会掉电。此处无条件锁住供电, - // 后续 tca9535ButtonThread::init() 只负责键盘和状态机初始化。 + // MOS 断电,系统在 setup() 中途就会掉电。 tca9535PowerEn(true); LOG_INFO("TCA9535: POWER_EN latched HIGH (early boot)"); + + // 开机确认窗口:检测 P1.3 是否持续按住 2 秒,防止意外通电卡死 + // 最多等待 3 秒,3 秒内未连续按满 2 秒则断电关机 + { + bool bootConfirmed = false; + uint32_t pressStart = 0; + uint32_t deadline = millis() + 3000; + LOG_INFO("TCA9535: Waiting for 2s button hold to confirm boot (timeout 3s)..."); + + while (millis() < deadline) { + bool pressed = tca9535ReadPowerBoot(); + if (pressed && pressStart == 0) { + pressStart = millis(); + } else if (pressed && pressStart != 0) { + if (millis() - pressStart >= 2000) { + bootConfirmed = true; + LOG_INFO("TCA9535: Boot confirmed (button held 2s)"); + break; + } + } else if (!pressed && pressStart != 0) { + // 松手重置计时 + pressStart = 0; + } + delay(50); // 50ms 轮询 + } + + if (!bootConfirmed) { + LOG_WARN("TCA9535: Boot not confirmed, shutting down"); + tca9535PowerEn(false); + // 等待 MOS 断开 + delay(500); + doDeepSleep(0, false, false); + } + } #endif #elif defined(ARCH_PORTDUINO) if (portduino_config.i2cdev != "") { diff --git a/code/firmware-2.7.15.567b8ea/src/modules/CannedMessageModule.cpp b/code/firmware-2.7.15.567b8ea/src/modules/CannedMessageModule.cpp index 2ba7863..051b9e9 100644 --- a/code/firmware-2.7.15.567b8ea/src/modules/CannedMessageModule.cpp +++ b/code/firmware-2.7.15.567b8ea/src/modules/CannedMessageModule.cpp @@ -368,6 +368,18 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) case CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE: return 1; + // Canned message list: LEFT/RIGHT enters free text input + case CANNED_MESSAGE_RUN_STATE_ACTIVE: + if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_RIGHT) { + runState = CANNED_MESSAGE_RUN_STATE_FREETEXT; + requestFocus(); + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + return 1; + } + break; + // If sending, block all input except global/system (handled above) case CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER: return handleEmotePickerInput(event); @@ -380,7 +392,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_RIGHT) { break; } - // Handle UP/DOWN: activate canned message list! + // Handle UP/DOWN: activate canned message list if (event->inputEvent == INPUT_BROKER_UP || event->inputEvent == INPUT_BROKER_DOWN || event->inputEvent == INPUT_BROKER_ALT_LONG) { LaunchWithDestination(NODENUM_BROADCAST); @@ -415,14 +427,14 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) bool CannedMessageModule::isUpEvent(const InputEvent *event) { return event->inputEvent == INPUT_BROKER_UP || - ((runState == CANNED_MESSAGE_RUN_STATE_ACTIVE || runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER || + ((runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER || runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) && (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS)); } bool CannedMessageModule::isDownEvent(const InputEvent *event) { return event->inputEvent == INPUT_BROKER_DOWN || - ((runState == CANNED_MESSAGE_RUN_STATE_ACTIVE || runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER || + ((runState == CANNED_MESSAGE_RUN_STATE_EMOTE_PICKER || runState == CANNED_MESSAGE_RUN_STATE_DESTINATION_SELECTION) && (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS)); } @@ -853,18 +865,13 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) return true; } - // Move cursor left - if (event->inputEvent == INPUT_BROKER_LEFT) { - payload = INPUT_BROKER_LEFT; - lastTouchMillis = millis(); - runOnce(); - return true; - } - // Move cursor right - if (event->inputEvent == INPUT_BROKER_RIGHT) { - payload = INPUT_BROKER_RIGHT; - lastTouchMillis = millis(); - runOnce(); + // LEFT/RIGHT in FREETEXT: go back to canned message list (ACTIVE), preserving input + if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_RIGHT) { + runState = CANNED_MESSAGE_RUN_STATE_ACTIVE; + UIFrameEvent e; + e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; + notifyObservers(&e); + screen->forceDisplay(); return true; } @@ -890,6 +897,14 @@ bool CannedMessageModule::handleFreeTextInput(const InputEvent *event) return handleTabSwitch(event); // Reuse tab logic } + // '*' key from TCA9535 numpad acts as backspace + if (event->kbchar == '*') { + payload = 0x08; + lastTouchMillis = millis(); + runOnce(); + return true; + } + // Printable ASCII (add char to draft) if (event->kbchar >= 32 && event->kbchar <= 126) { payload = event->kbchar;