From eacbbc08dc81dbe5f6e145d6ceef121d38ad5cf9 Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 29 Mar 2026 16:29:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=80=E6=9C=BA=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E7=AA=97=E5=8F=A3=20+=20=E5=BF=AB=E6=8D=B7=E5=9B=9E=E5=A4=8D/?= =?UTF-8?q?=E4=B9=9D=E5=AE=AB=E6=A0=BC=E5=AF=BC=E8=88=AA=20+=20=E5=85=85?= =?UTF-8?q?=E7=94=B5=E6=A3=80=E6=B5=8B=E5=8A=A0=E9=80=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 19 +++++++- PCB/Moonshine_travelers.eprj2 | Bin 2322432 -> 2330624 bytes .../src/input/TCA9535ButtonThread.cpp | 12 ++--- .../src/input/TCA9535ButtonThread.h | 4 +- code/firmware-2.7.15.567b8ea/src/main.cpp | 37 +++++++++++++- .../src/modules/CannedMessageModule.cpp | 45 ++++++++++++------ 6 files changed, 89 insertions(+), 28 deletions(-) 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 342eab00d29df0c3353ed3ba5a024f6f307212f8..f92c413d07a28db113eb14ee1e7a24805e17ba51 100644 GIT binary patch delta 21089 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 delta 14303 zcmbW8Urbw79LIax-nNu;+DljHTK-+YDgK)kRzQr7Vdivhv!F;E>VV1IT!zRp&2-bA z`+yHqN8Keiz8FUIVGo*4x^qwB7I6_1A58YLHy@gqCBDgnJeqljiO=`1vdUi$T7cak$L)T&L-Pt6`ZK65&_ zVwfwYvGtSx@0agQ&G*53l|h%l8fD2L=lR?)AUIKeaiZT88P29Eh0i zjfB2$B<9xN4S1K^<+4eAR$FZ-E@^J^4}Bx%-aV-={Sy4G>W*YHhqYL5p}%(^-`DFH z&*|UBclH1Oe$TKjoO~g2lki!^g0VnI@Pijlky09vl5!|w2T7NS0+&%N0;O~yC1o*U z&-+GNC-x=|l%!Z4kdpEi1!QlOw#Yb8l41!UB^QcOS&Z41@21?2(O6PIRf;tLDY;OK z$y%K~>YJqyr?E5vRVmg8q`cve6PmJ41jzvQ%~zi8&HyBtw2i3 zQaxes+9ZN>0wpQd0iz2ivc?JL$xMO< zG6hI{COAa{c?Bp*u`wX!-2|ryBeG5eISiDf*sDOw>qyE@`JVMVM36Ut zk`#L#NJ+VoBFy@|b$=ANjAGMJY6?ing~e@hF>QDHZqKzeZD5W8RVg+Dq`Z6kG@(6X zjWm$AfszzE0i?V$rIGNEvP1-#1xixv6p)f~xskAi-64X^10^Z;E|Bsjp8`^7qAjiW zfRYqD1Ejp~x|#^jChJ6y4}g*sI}4<|L#2tZYi$xiJ_1To>^zW?)lAv{;pm+yT!etj zcri$)aDn+0GgBV+eWq+tK|+NK$f$6EDV4cQ+5eYETU7N}uOPj`1?E>OnesHPS4oTt zu_oTaMHG#c=5}hYwqaC|b>RZiE^=~v+5a~(TPrYwQ9=HN3rN6Jv((L^8H0jc3>TP; zDQ3%^w6SW(s30-J1!!in;e6=j(9ce^lAUnhtYim6m&{_f^ZWJOeeR#D*(*C~%v~5W zj7y>K?|ZAicu{=69BJVM9CM=#|_FXTFl_3CF*R zpLM^k!w?$a$g3W+#_B4ovY{c9nL@J^h55EA96k8v?f1>{W%~>#06xD zq}fb81tCww1tf~N08LRdb?NqFy@IR}7nnAxkg1-;s33*J1>}&bE!0-+#-Jdb#0BP) ziY?T${TYl3a!Xuba;el(o~Lbr`!Fgi$+I)YI_+KLOz zTb0_V4=|9p;sP>PTtMnd*2q+EVZDMJ78jVr8fvGWS0^zlNN90^8LbkT>K%*ZS)e&JU6vSS-w)S(rPs zFn3n!Al}9bp%;l+r?E(h3UZ&^AlxiDct G%IaT(Dil2c 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;