diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58275e2 Binary files /dev/null and b/.gitignore differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d2cd69..f453f8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,10 +55,20 @@ ### Changed -- 无 +#### 按键映射更新(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) +- SELECT 由 GPIO9 短按处理,CANCEL 由 POWER_BOOT(P1.3) 短按处理 + +#### P1.5 状态灯改为独立 1 秒闪烁 +- P1.5 状态灯从 `GpioSplitter` + `LED_PIN` 同步驱动改为独立驱动 +- 在 `TCA9535ButtonThread::runOnce()` 中每 500ms 翻转(亮 500ms + 灭 500ms = 1 秒周期) +- 移除 `Led.cpp` 中的 `GpioTca9535LedPin` 类及相关 `#ifdef TCA9535_LORA_RST_VIRTUAL_PIN` 代码 ### Fixed +- **矩阵扫描 cols 整数提升 bug**:`~` 运算符对 `uint8_t` 提升为 `int`,导致 `cols` 高 4 位被污染,key4~key15 永远无法触发 + - 修复:`((~(p0In & 0xF0)) >> 4) & 0x0F` — 显式截断到 4 bit - **LTO 链接错误**:编译时 `-flto` 导致 `undefined reference to TCA9535ButtonThread::*` - 原因:.h/.cpp 中的 `#if defined(HAS_TCA9535_BUTTON)` 守卫导致部分编译单元中符号被丢弃 - 修复:去掉 .h/.cpp 中的条件守卫,类定义和实现始终编译;main.cpp 中的实例化仍由 `#ifdef` 控制 diff --git a/PCB/Moonshine_travelers.eprj2 b/PCB/Moonshine_travelers.eprj2 index 913a8e0..e9898ab 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/Led.cpp b/code/firmware-2.7.15.567b8ea/src/Led.cpp index 6406cd2..aa022e0 100644 --- a/code/firmware-2.7.15.567b8ea/src/Led.cpp +++ b/code/firmware-2.7.15.567b8ea/src/Led.cpp @@ -41,6 +41,8 @@ static GpioSplitter ledFinalPin(&ledHwPin, &ledPmuHwPin); static GpioPin &ledFinalPin = ledHwPin; #endif +// P1.5 状态灯由 TCA9535ButtonThread 独立驱动 1 秒闪烁,不再跟随 LED_PIN + #ifdef USE_POWERMON /** * We monitor changes to the LED drive output because we use that as a sanity test in our power monitor stuff. diff --git a/code/firmware-2.7.15.567b8ea/src/Power.cpp b/code/firmware-2.7.15.567b8ea/src/Power.cpp index fa8661d..cb1d5c6 100644 --- a/code/firmware-2.7.15.567b8ea/src/Power.cpp +++ b/code/firmware-2.7.15.567b8ea/src/Power.cpp @@ -20,6 +20,10 @@ #include "meshUtils.h" #include "sleep.h" +#ifdef TCA9535_LORA_RST_VIRTUAL_PIN +#include "input/TCA9535ButtonThread.h" +#endif + #if defined(ARCH_PORTDUINO) #include "api/WiFiServerAPI.h" #include "input/LinuxInputImpl.h" @@ -779,6 +783,14 @@ void Power::shutdown() #ifdef PIN_LED3 ledOff(PIN_LED3); #endif + +#ifdef TCA9535_LORA_RST_VIRTUAL_PIN + // TCA9535 电源管理板卡:拉低 POWER_EN 切断硬件供电 + // 按键仍按着时 MOS 还导通,屏幕 "Shutting Down..." 已显示完毕 + tca9535PowerEn(false); + delay(500); // 等待 I²C 写入完成和屏幕显示稳定 +#endif + doDeepSleep(DELAY_FOREVER, true, true); #elif defined(ARCH_PORTDUINO) exit(EXIT_SUCCESS); 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 3cc03c5..5c7a864 100644 --- a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp +++ b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp @@ -4,29 +4,29 @@ using namespace concurrency; -// ----------------------------------------------------------------------- // 默认按键映射(4×4 矩阵,行优先:KEY[0]=ROW0·COL0 ... KEY[15]=ROW3·COL3) +// 仅保留方向键,SELECT/CANCEL 由其他按键处理 // variant.h 中可用 #define TCA9535_KEY_MAP { ... } 覆盖 // ----------------------------------------------------------------------- #ifndef TCA9535_KEY_MAP #define TCA9535_KEY_MAP \ { \ - INPUT_BROKER_SELECT, /* ROW0·COL0 */ \ - INPUT_BROKER_UP, /* ROW0·COL1 */ \ - INPUT_BROKER_DOWN, /* ROW0·COL2 */ \ - INPUT_BROKER_LEFT, /* ROW0·COL3 */ \ - INPUT_BROKER_RIGHT, /* ROW1·COL0 */ \ - INPUT_BROKER_CANCEL, /* ROW1·COL1 */ \ - INPUT_BROKER_NONE, /* ROW1·COL2 */ \ - INPUT_BROKER_NONE, /* ROW1·COL3 */ \ - INPUT_BROKER_NONE, /* ROW2·COL0 */ \ - INPUT_BROKER_NONE, /* ROW2·COL1 */ \ - INPUT_BROKER_NONE, /* ROW2·COL2 */ \ - INPUT_BROKER_NONE, /* ROW2·COL3 */ \ - INPUT_BROKER_NONE, /* ROW3·COL0 */ \ - INPUT_BROKER_NONE, /* ROW3·COL1 */ \ - INPUT_BROKER_NONE, /* ROW3·COL2 */ \ - INPUT_BROKER_NONE, /* ROW3·COL3 */ \ + INPUT_BROKER_NONE, /* key0 = ROW0·COL0 */ \ + INPUT_BROKER_NONE, /* key1 = ROW0·COL1 */ \ + INPUT_BROKER_NONE, /* key2 = ROW0·COL2 */ \ + INPUT_BROKER_UP, /* key3 = ROW0·COL3 */ \ + INPUT_BROKER_NONE, /* key4 = ROW1·COL0 */ \ + INPUT_BROKER_NONE, /* key5 = ROW1·COL1 */ \ + INPUT_BROKER_NONE, /* key6 = ROW1·COL2 */ \ + INPUT_BROKER_DOWN, /* key7 = ROW1·COL3 */ \ + INPUT_BROKER_NONE, /* key8 = ROW2·COL0 */ \ + INPUT_BROKER_NONE, /* key9 = ROW2·COL1 */ \ + INPUT_BROKER_NONE, /* key10 = ROW2·COL2 */ \ + INPUT_BROKER_LEFT, /* key11 = ROW2·COL3 */ \ + INPUT_BROKER_NONE, /* key12 = ROW3·COL0 */ \ + INPUT_BROKER_NONE, /* key13 = ROW3·COL1 */ \ + INPUT_BROKER_NONE, /* key14 = ROW3·COL2 */ \ + INPUT_BROKER_RIGHT, /* key15 = ROW3·COL3 */ \ } #endif @@ -58,11 +58,11 @@ bool TCA9535ButtonThread::init() { // =================================================================== // 第一步:配置 P1 口方向 - // P1.2 = 输出(POWER_EN),P1.3 = 输入(POWER_BOOT),P1.4 = 输出(LoRa RST) + // P1.2 = 输出(POWER_EN),P1.3 = 输入(POWER_BOOT),P1.4 = 输出(LoRa RST),P1.5 = 输出(状态灯) // Configuration 寄存器:1=input, 0=output - // P1.2=bit2=0, P1.3=bit3=1, P1.4=bit4=0 → 0xEB (1110 1011) + // P1.2=bit2=0, P1.3=bit3=1, P1.4=bit4=0, P1.5=bit5=0 → 0xCB (1100 1011) // =================================================================== - if (!writeReg(TCA9535_REG_CONFIG_P1, 0xEB)) { + if (!writeReg(TCA9535_REG_CONFIG_P1, 0xCB)) { LOG_WARN("TCA9535: P1 config write failed"); return false; } @@ -71,6 +71,9 @@ bool TCA9535ButtonThread::init() // 注意:此时不拉高 POWER_EN,等开机确认后再拉高 tca9535LoraReset(true); + // P1.5 状态灯默认熄灭(高电平) + tca9535StatusLed(false); + // =================================================================== // 第二步:开机检测 — 等待用户持续按住 P1.3 达 2 秒 // 物理按键已使 MOS 导通(ESP32 得电),但 POWER_EN 尚未拉高 @@ -174,26 +177,28 @@ int32_t TCA9535ButtonThread::runOnce() // 持续按住中 uint32_t held = millis() - _powerBtnPressStart; if (held >= TCA9535_POWER_BOOT_HOLD_MS) { - LOG_WARN("TCA9535: Shutdown button held %lu ms -> POWERING OFF", held); - _powerState = TCA9535PowerState::SHUTDOWN_PENDING; - - // 清空屏幕,给用户"即将关机"的视觉反馈 - // 按键仍在按着所以 MOS 还导通,系统不会立刻断电 - if (screen) - screen->setOn(false); - - tca9535PowerEn(false); - // 用户松手后 MOS 断开,系统断电 - return INT32_MAX; // 不再调度 + LOG_WARN("TCA9535: Power button held %lu ms -> SHUTDOWN", held); + _powerBtnPressStart = 0; + dispatchEvent(INPUT_BROKER_SHUTDOWN); } } else if (!pressed && _powerBtnPressStart != 0) { - // 按键松开,未达关机时间 + // 按键松开,未达关机时间 → 短按 = CANCEL 事件 uint32_t held = millis() - _powerBtnPressStart; - LOG_DEBUG("TCA9535: Shutdown button released after %lu ms (no action)", held); + LOG_DEBUG("TCA9535: Power button short press (%lu ms) -> CANCEL", held); _powerBtnPressStart = 0; + dispatchEvent(INPUT_BROKER_CANCEL); } } + // =================================================================== + // P1.5 状态灯闪烁:500ms 亮 + 500ms 灭 = 1 秒周期 + // =================================================================== + if (millis() - _statusLedToggleMs >= 500) { + _statusLedToggleMs = millis(); + _statusLedOn = !_statusLedOn; + tca9535StatusLed(_statusLedOn); + } + // =================================================================== // 矩阵键盘扫描(仅 RUNNING 状态) // =================================================================== @@ -221,8 +226,6 @@ int32_t TCA9535ButtonThread::runOnce() if (pressed & (1u << i)) { input_broker_event evt = tca9535KeyMap[i]; if (evt != INPUT_BROKER_NONE) { - LOG_DEBUG("TCA9535: key[%u] (R%uC%u) pressed -> event %d", i, i / TCA9535_COLS, i % TCA9535_COLS, - (int)evt); dispatchEvent(evt); } } @@ -262,7 +265,8 @@ bool TCA9535ButtonThread::scanMatrix(uint16_t &keys) // 提取列位(P0.4~P0.7),低电平=按下 // 将列状态从高4位移到低位,便于索引 - uint8_t cols = (~(p0In & TCA9535_COL_MASK)) >> 4; // bit0=COL0, bit3=COL3 + // 注意:~ 运算会将 uint8_t 提升为 int,必须 & 0x0F 截断到 4 bit + uint8_t cols = ((~(p0In & TCA9535_COL_MASK)) >> 4) & 0x0F; // bit0=COL0, bit3=COL3 // 组装到 keys(每行 4 列) keys |= ((uint16_t)cols << (row * TCA9535_COLS)); 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 9ffffc0..192f192 100644 --- a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h +++ b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h @@ -10,6 +10,7 @@ * - P1.2:电源使能(POWER_EN),高电平有效,驱动 MOS 管维持供电 * - P1.3:电源开机按钮(POWER_BOOT),输入,低电平有效(按键按下接地) * - P1.4:LoRa RST 输出(通过 I²C 控制 RadioLib 复位序列) + * - P1.5:状态指示灯,低电平点亮 * * 电源管理逻辑: * 开机:物理按键按下 → MOS 导通 → ESP32/TCA9535 得电 @@ -83,6 +84,7 @@ #define TCA9535_BIT_P12 (1u << 2) // POWER_EN 输出 #define TCA9535_BIT_P13 (1u << 3) // POWER_BOOT 输入 #define TCA9535_BIT_P14 (1u << 4) // LoRa RST 输出 +#define TCA9535_BIT_P15 (1u << 5) // 状态指示灯输出(低电平点亮) /** * 通过 I²C 控制 TCA9535 P1.2 上的电源使能(POWER_EN)。 @@ -160,6 +162,33 @@ static inline bool tca9535LoraReset(bool high) return (Wire.endTransmission() == 0); } +/** + * 通过 I²C 控制 TCA9535 P1.5 上的状态指示灯。 + * 低电平点亮,高电平熄灭。 + * @param on true=点亮(低电平),false=熄灭(高电平) + */ +static inline bool tca9535StatusLed(bool on) +{ + Wire.beginTransmission(TCA9535_I2C_ADDR); + Wire.write(TCA9535_REG_OUTPUT_P1); + if (Wire.endTransmission(false) != 0) + return false; + if (Wire.requestFrom((uint8_t)TCA9535_I2C_ADDR, (uint8_t)1) != 1) + return false; + uint8_t p1Out = Wire.read(); + + // 修改 P1.5 位:低电平点亮 + if (on) + p1Out &= ~TCA9535_BIT_P15; // 拉低 = 点亮 + else + p1Out |= TCA9535_BIT_P15; // 拉高 = 熄灭 + + Wire.beginTransmission(TCA9535_I2C_ADDR); + Wire.write(TCA9535_REG_OUTPUT_P1); + Wire.write(p1Out); + return (Wire.endTransmission() == 0); +} + /** * 电源管理状态机 */ @@ -202,6 +231,10 @@ class TCA9535ButtonThread : public Observable, public concur // 上次扫描结果(16 位,每 bit 对应 row*4+col),用于边沿检测 uint16_t _lastKeys = 0x0000; + // P1.5 状态灯闪烁控制 + bool _statusLedOn = false; + uint32_t _statusLedToggleMs = 0; + // 写寄存器 bool writeReg(uint8_t reg, uint8_t val); diff --git a/code/firmware-2.7.15.567b8ea/src/main.cpp b/code/firmware-2.7.15.567b8ea/src/main.cpp index 1d67306..ecbcc6d 100644 --- a/code/firmware-2.7.15.567b8ea/src/main.cpp +++ b/code/firmware-2.7.15.567b8ea/src/main.cpp @@ -1151,6 +1151,13 @@ void setup() userConfig.longPress = INPUT_BROKER_SELECT; userConfig.longPressTime = 500; userConfig.longLongPress = INPUT_BROKER_SHUTDOWN; +#ifdef BUTTON_SINGLE_PRESS_EVENT + userConfig.singlePress = BUTTON_SINGLE_PRESS_EVENT; +#endif +#ifdef BUTTON_DISABLE_LONG_PRESS + userConfig.longPress = INPUT_BROKER_NONE; + userConfig.longLongPress = INPUT_BROKER_NONE; +#endif UserButtonThread->initButton(userConfig); } else { ButtonConfig userConfigNoScreen; diff --git a/code/firmware-2.7.15.567b8ea/variants/esp32c3/diy/esp32c3_moonshine_travelers/variant.h b/code/firmware-2.7.15.567b8ea/variants/esp32c3/diy/esp32c3_moonshine_travelers/variant.h index 12259bd..dbed402 100644 --- a/code/firmware-2.7.15.567b8ea/variants/esp32c3/diy/esp32c3_moonshine_travelers/variant.h +++ b/code/firmware-2.7.15.567b8ea/variants/esp32c3/diy/esp32c3_moonshine_travelers/variant.h @@ -1,5 +1,12 @@ #define BUTTON_PIN 9 +// GPIO9 短按直接触发 SELECT(而非默认的 USER_PRESS 切换) +// 长按无功能(关机由 POWER_BOOT 长按处理) +#ifndef BUTTON_SINGLE_PRESS_EVENT +#define BUTTON_SINGLE_PRESS_EVENT INPUT_BROKER_SELECT +#endif +#define BUTTON_DISABLE_LONG_PRESS 1 + #define HAS_SCREEN 1 #define USE_SH1106 @@ -36,25 +43,27 @@ // 按键映射:4×4 矩阵,行优先排列 // KEY[0]=ROW0·COL0, KEY[1]=ROW0·COL1, ..., KEY[15]=ROW3·COL3 -// 低电平有效(按下接地,列读取到低电平=按下) +// 低电平有效(按下接地,列读取到低电平=按下) +// SELECT 由 GPIO9 短按触发,CANCEL 由 POWER_BOOT(P1.3) 短按触发 +// 矩阵仅保留方向键 #define TCA9535_KEY_MAP \ { \ - INPUT_BROKER_SELECT, /* ROW0·COL0 */ \ - INPUT_BROKER_UP, /* ROW0·COL1 */ \ - INPUT_BROKER_DOWN, /* ROW0·COL2 */ \ - INPUT_BROKER_LEFT, /* ROW0·COL3 */ \ - INPUT_BROKER_RIGHT, /* ROW1·COL0 */ \ - INPUT_BROKER_CANCEL, /* ROW1·COL1 */ \ - INPUT_BROKER_NONE, /* ROW1·COL2 */ \ - INPUT_BROKER_NONE, /* ROW1·COL3 */ \ - INPUT_BROKER_NONE, /* ROW2·COL0 */ \ - INPUT_BROKER_NONE, /* ROW2·COL1 */ \ - INPUT_BROKER_NONE, /* ROW2·COL2 */ \ - INPUT_BROKER_NONE, /* ROW2·COL3 */ \ - INPUT_BROKER_NONE, /* ROW3·COL0 */ \ - INPUT_BROKER_NONE, /* ROW3·COL1 */ \ - INPUT_BROKER_NONE, /* ROW3·COL2 */ \ - INPUT_BROKER_NONE, /* ROW3·COL3 */ \ + INPUT_BROKER_NONE, /* key0 = ROW0·COL0 */ \ + INPUT_BROKER_NONE, /* key1 = ROW0·COL1 */ \ + INPUT_BROKER_NONE, /* key2 = ROW0·COL2 */ \ + INPUT_BROKER_UP, /* key3 = ROW0·COL3 */ \ + INPUT_BROKER_NONE, /* key4 = ROW1·COL0 */ \ + INPUT_BROKER_NONE, /* key5 = ROW1·COL1 */ \ + INPUT_BROKER_NONE, /* key6 = ROW1·COL2 */ \ + INPUT_BROKER_DOWN, /* key7 = ROW1·COL3 */ \ + INPUT_BROKER_NONE, /* key8 = ROW2·COL0 */ \ + INPUT_BROKER_NONE, /* key9 = ROW2·COL1 */ \ + INPUT_BROKER_NONE, /* key10 = ROW2·COL2 */ \ + INPUT_BROKER_LEFT, /* key11 = ROW2·COL3 */ \ + INPUT_BROKER_NONE, /* key12 = ROW3·COL0 */ \ + INPUT_BROKER_NONE, /* key13 = ROW3·COL1 */ \ + INPUT_BROKER_NONE, /* key14 = ROW3·COL2 */ \ + INPUT_BROKER_RIGHT, /* key15 = ROW3·COL3 */ \ }