feat(travelers): 矩阵按键映射改为 key3/7/11/15 方向键, P1.5 独立闪烁, 修复 cols 整数提升 bug

This commit is contained in:
2026-03-29 06:44:15 +08:00
parent 609bee3c9d
commit 6f51e8ff83
9 changed files with 131 additions and 54 deletions
BIN
View File
Binary file not shown.
+11 -1
View File
@@ -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` 控制
Binary file not shown.
+2
View File
@@ -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.
@@ -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);
@@ -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 RSTP1.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));
@@ -10,6 +10,7 @@
* - P1.2:电源使能(POWER_EN),高电平有效,驱动 MOS 管维持供电
* - P1.3:电源开机按钮(POWER_BOOT),输入,低电平有效(按键按下接地)
* - P1.4LoRa 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<const InputEvent *>, 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);
@@ -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;
@@ -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 */ \
}