From e236c951cc436b0268906d3fbcd5006bf60d0213 Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 29 Mar 2026 08:49:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(travelers):=20TCA9535=20=E5=85=85=E7=94=B5?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=20+=20=E9=94=AE=E7=9B=98=E8=83=8C=E5=85=89?= =?UTF-8?q?=20+=20isVbusIn=20=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 P1.1 CHARGE_DET 充电检测(高电平=充电中),轮询间隔 2s - Power.cpp isCharging()/isVbusIn() 均使用 TCA9535_CHARGE_DET_PIN 分支 - 新增 P1.0 键盘背光(高电平点亮),按键时亮,5s 无操作自动熄灭 - 修复开机供电维持:POWER_EN 在 Wire.begin() 后立即锁定 - 修复 P1 config 寄存器值 0x0A(之前 0x8D 导致 P1.2 高阻断电) - ⚠️ 已知问题:TP4057 电压反串导致未充电时 P1.1 仍读高,需硬件修改 --- .workbuddy/expert-history.json | 17 ++ .workbuddy/memory/2026-03-29.md | 83 +++++++++ .workbuddy/memory/MEMORY.md | 103 +++++++++++ PCB/Moonshine_travelers.eprj2 | Bin 2318336 -> 2322432 bytes code/firmware-2.7.15.567b8ea/src/Power.cpp | 11 +- .../src/input/TCA9535ButtonThread.cpp | 167 ++++++++++-------- .../src/input/TCA9535ButtonThread.h | 63 ++++++- code/firmware-2.7.15.567b8ea/src/main.cpp | 7 + .../src/modules/CannedMessageModule.cpp | 27 ++- .../diy/esp32c3_moonshine_travelers/variant.h | 58 ++++-- 10 files changed, 435 insertions(+), 101 deletions(-) create mode 100644 .workbuddy/expert-history.json create mode 100644 .workbuddy/memory/2026-03-29.md create mode 100644 .workbuddy/memory/MEMORY.md diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json new file mode 100644 index 0000000..46f5546 --- /dev/null +++ b/.workbuddy/expert-history.json @@ -0,0 +1,17 @@ +{ + "version": 2, + "sessions": { + "e341ab1f344d4f54946c2ba835bc7aa3": [ + { + "expertId": "EmbeddedFirmwareEngineer", + "name": "Owen", + "profession": "嵌入式固件工程师", + "avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/EmbeddedFirmwareEngineer/EmbeddedFirmwareEngineer.png", + "promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/EmbeddedFirmwareEngineer/EmbeddedFirmwareEngineer_zh.md", + "usedAt": 1774720870071, + "industryId": "all" + } + ] + }, + "lastUpdated": 1774745350742 +} \ No newline at end of file diff --git a/.workbuddy/memory/2026-03-29.md b/.workbuddy/memory/2026-03-29.md new file mode 100644 index 0000000..fba2979 --- /dev/null +++ b/.workbuddy/memory/2026-03-29.md @@ -0,0 +1,83 @@ +# 2026-03-29 工作日志 + +## 项目初始扫描 +- 首次读取 firmware-2.7.15.567b8ea 代码结构 +- 确认项目为 Meshtastic 官方固件的自定义 fork +- 发现 4 个自研板卡变体:esp32c3_moonshine / moonshine(带ADC/USB CDC)/ moonshine_mv(全功能)/ moonshine_travelers +- 详细记录写入 MEMORY.md + +## TCA9535PWR 矩阵键盘驱动实现 +- 目标板卡:esp32c3_moonshine_travelers +- 新建驱动文件: + - `src/input/TCA9535ButtonThread.h` - 驱动类声明,含寄存器宏、extern 指针 + - `src/input/TCA9535ButtonThread.cpp` - 4×4 矩阵扫描实现 +- 修改 `variants/esp32c3/diy/esp32c3_moonshine_travelers/variant.h`: + - 添加 `HAS_TCA9535_BUTTON` 宏 + - 添加 `TCA9535_KEY_MAP`(4×4 矩阵行优先映射) + - `TCA9535_INT_PIN=5`(GPIO5,低电平有效,下降沿触发) +- 修改 `src/main.cpp`:include TCA9535ButtonThread.h,在 setupModules() 后初始化线程 +- I²C 地址:0x20(A0=A1=A2=0),与 SH1106 屏幕共用 Wire(SDA=0, SCL=1) +- 矩阵接法:P0.0~P0.3 行输出,P0.4~P0.7 列输入,逐行拉低扫描,50µs 行间延时 + +## LTO 链接错误修复 +- 编译报 `undefined reference to TCA9535ButtonThread::*`(LTO -flto 导致) +- 原因:.h/.cpp 中的 `#if defined(HAS_TCA9535_BUTTON)` 守卫导致在部分编译单元中符号被丢弃 +- 修复:去掉 .h/.cpp 中的条件守卫,让类定义和实现始终编译 + - main.cpp 中的实例化和初始化仍由 `#ifdef HAS_TCA9535_BUTTON` 控制 + - extern 指针声明保留在 `#ifdef` 中,非目标板卡不会引用 + +## RadioLib LoRa RST 通过 TCA9535 P1.4 控制 +- 自定义 HAL 子类 `TCA9535GpioHal` 拦截虚拟引脚 200,转发到 I²C +- `tca9535LoraReset(bool high)` 静态函数,read-modify-write P1.4 + +## 电源管理(P1.2 + P1.3) +- P1.2 = POWER_EN 输出,高电平有效,驱动 MOS 管维持供电 +- P1.3 = POWER_BOOT 输入,低电平有效(按键按下接地) +- 开机流程:物理按键 → MOS 导通 → ESP32 得电 → init() 等 P1.3 持续按住 2 秒 → POWER_EN 拉高 + - 未按够 2 秒松开 → 不拉高 POWER_EN → 断电 +- 关机流程:运行中 P1.3 持续按住 2 秒 → POWER_EN 拉低 → 断电 +- 电源状态机:BOOT_PENDING → RUNNING → SHUTDOWN_PENDING +- init() 中 P1 config = 0xEB(P1.2=输出, P1.3=输入, P1.4=输出) +- 新增 `tca9535ReadPowerBoot()` 静态函数,读取 P1.3 输入状态 +- 新增 `tca9535PowerEn(bool on)` 静态函数,read-modify-write P1.2 + +## GPS 启用(GP-02 模块) +- 问题:`tca9535GpsEn(false)` 在 TCA9535 init 中关闭了 GPS 电源,而 GPS::setup()->probe() 时 powerState=GPS_OFF,writePinEN(true) 只在 GPS_ACTIVE 时才调用 → probe 时模块没电 +- 修复:在 main.cpp 的 `createGps()` + enablePin 桥接之后,立即 `tca9535GpsEn(true)` + `delay(1000)` 给 GP-02 冷启动上电时间 +- GP-02 接线:VCC→3.3V, GND→GND, TX→GPIO20(ESP RX), RX→GPIO21(ESP TX),EN/RST/PPS/VRTC 悬空即可 +- 编译验证通过(esp32c3_moonshine_travelers SUCCESS) + +## GPS 修复:P1.6/P1.7 默认高电平 + RX/TX 交换 +- 用户要求 GPS_RST(P1.6) 和 GPS_EN(P1.7) 通电后直接设为高电平 + - TCA9535ButtonThread.cpp init() 中改为 `tca9535GpsEn(true)` 和 `tca9535GpsReset(true)` + - 移除 main.cpp 中的 delay workaround +- GPS 仍然检测不到 → 交换 GPS_RX_PIN(21→20) 和 GPS_TX_PIN(20→21) +- 更新 CHANGELOG.md,提交 311232c,push + +## 九宫格键盘映射 +- 添加 4×4 矩阵中的数字键:key0-2=1-3, key4-6=4-6, key8-10=7-9, key12=*, key13=0, key14=# +- 方向键保留:key3=UP, key7=DOWN, key11=LEFT, key15=RIGHT +- 新增 `TCA9535_KEY_CHAR_MAP` 数组,`dispatchEvent()` 传 `kbchar` 参数 + +## ⚠️ TCA9535 MATRIXKEY 崩溃修复 + 键盘输入修复 +- 崩溃现象:按下矩阵键后立即 Load access fault at 0x40058766 +- 根因:`TCA9535_KEY_CHAR_MAP` 传的是 ASCII 字符(如 '0'=0x30, '9'=0x39),但 `CannedMessageModule` 把 `kbchar` 当 1-based 索引用(`currentMessageIndex = event->kbchar - 1`)。ASCII '0'=48 → index=47 → 数组越界 → 野指针 → 崩溃 +- 参考 RAK14004 实现:`kbI2cBase.cpp` 中 `PrintDataBuf = aCount*4 + bCount + 1` 传的是 1-16 的索引 +- 第一版修复(错误方向):把 TCA9535_KEY_CHAR_MAP 改为传索引 1-12 + - 问题:这样矩阵键只能选 canned message,无法进入文本输入 +- 正确修复: + 1. `TCA9535_KEY_CHAR_MAP` 恢复为 ASCII 字符('0'-'9', '*', '#') + 2. `CannedMessageModule::handleInputEvent()` 修改 MATRIXKEY 拦截逻辑: + - 如果 `kbchar` 是可打印 ASCII(32-126):不拦截,让事件 fall through 到正常输入路径 + → INACTIVE 状态进入 FREETEXT,FREETEXT 状态追加字符 + - 如果 `kbchar` 是 1-based 索引(如 RAK14004):继续走 canned message 选择路径 + - 加边界检查 `idx < 0 || idx >= messagesCount` 防御性忽略 + +## P1.1 充电检测 (CHARGE_DET) +- P1.1 = CHARGE_DET 输入,高电平=正在充电 +- P1 config 从 0x8B 改为 0x8D(P1.1 配置为输入) +- variant.h 新增 `TCA9535_CHARGE_DET_PIN (1u << 1)` +- TCA9535ButtonThread.h 新增 `tca9535ReadChargeDet()` 静态内联函数 +- TCA9535ButtonThread.cpp 的 runOnce() 每 2 秒轮询 P1.1,更新全局 `tca9535IsCharging` +- Power.cpp 的 `AnalogBatteryLevel::isCharging()` 读取 `tca9535IsCharging`(`#ifdef TCA9535_CHARGE_DET_PIN`) +- Power.cpp include 条件从 `TCA9535_LORA_RST_VIRTUAL_PIN` 改为 `HAS_TCA9535_BUTTON` diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md new file mode 100644 index 0000000..48efda3 --- /dev/null +++ b/.workbuddy/memory/MEMORY.md @@ -0,0 +1,103 @@ +# 项目长期记忆 + +## 项目概述 +- **项目**:LoRa Meshtastic 固件开发 +- **主代码目录**:`code/firmware-2.7.15.567b8ea`(基于 Meshtastic 官方固件 v2.7.15) +- **构建系统**:PlatformIO + ESP-IDF (Arduino framework for ESP32) + +## 硬件平台 & 自定义板卡 +项目有多个自研板卡(均为 ESP32-C3 + LoRa 模块组合): + +### esp32c3_moonshine(基础版) +- MCU:ESP32-C3(esp32-c3-devkitm-1) +- LoRa:E220-400M30S(LLCC68/SX1262/SX1268 三选一探测) +- 无屏幕、无 GPS +- SPI: SCK=10, MISO=6, MOSI=7, CS=8, RST=5, DIO1=3, BUSY=4 +- LED_POWER=12,DIO3 TCXO 1.8V,TCXO_OPTIONAL +- Flash: 4MB, dio 模式, 80MHz + +### esp32c3_moonshine(firmware 版本,variants 中) +- 同上基础版,增加: + - 支持 SX126X_RXEN=2(RX 使能 GPIO) + - 支持 E22_400M33S(SX126X_MAX_POWER=22, TX_GAIN=0) + - BATTERY_PIN=1, ADC_MULTIPLIER=2.0f + - USB CDC 启动:ARDUINO_USB_MODE=1, ARDUINO_USB_CDC_ON_BOOT=1 + +### esp32c3_moonshine_mv(带屏幕+GPS+多按键版) +- MCU:ESP32-C3 +- LoRa:RA-01SC-P(SETTING_MAX_POWER=29, TX_GAIN=26, SX126X_MAX_POWER=3) +- 屏幕:SSD1306(I2C: SDA=0, SCL=1) +- GPS:GPS_RX=21, GPS_TX=20, GPS_POWER_TOGGLE, PIN_GPS_EN=12 +- 多按键:PCF8574 IO 扩展器(地址 0x27,INT=9),映射 SELECT/UP/DOWN/LEFT/RIGHT/CANCEL +- NeoPixel:GPIO13, 1颗 NEO_GRB +- BATTERY_PIN=2 + +### esp32c3_moonshine_travelers(旅行者版) +- MCU:ESP32-C3 +- LoRa:RA-01SC-P(SETTING_MAX_POWER=3, TX_GAIN=0, SX126X_MAX_POWER=3) +- 屏幕:SH1106(I2C: SDA=0, SCL=1) +- GPS:GPS_RX=21, GPS_TX=20, GPS_RST=P1.6(TCA9535), GPS_EN=P1.7(TCA9535) + - GPS EN 通过 `GpioTca9535GpsEnPin` + `GpioUnaryTransformer` 桥接 `gps->enablePin` + - GPS RST 由 `tca9535GpsReset()` 控制,init 中释放(高电平) +- BATTERY_PIN=2 +- **GPIO9**:短按=SELECT,长按=无功能(`BUTTON_DISABLE_LONG_PRESS`) +- **TCA9535PWR 4×4 矩阵键盘**:与屏幕共用 I2C,A0=A1=A2=0,地址 0x20 + - 驱动:`src/input/TCA9535ButtonThread.h/.cpp` + - 矩阵接法:P0.0~P0.3 行输出(ROW0~ROW3),P0.4~P0.7 列输入(COL0~COL3) + - 扫描方式:逐行拉低输出,读列检测低电平,50µs 行间延时 + - 中断引脚:TCA9535_INT_PIN=5(GPIO5,低电平有效,下降沿触发) + - 映射(variant.h):key0-2=索引1-3, key4-6=索引4-6, key8-10=索引7-9, key12=索引10(*), key13=索引11(0), key14=索引12(#);key3=UP, key7=DOWN, key11=LEFT, key15=RIGHT + - **kbchar 字段语义**: + - RAK14004 传 1-based 索引(kbchar=1..16),走 canned message 选择路径 + - TCA9535 传 ASCII 字符('0'-'9','*','#'),走文本输入路径(INACTIVE→FREETEXT) + - `CannedMessageModule` 的 MATRIXKEY 拦截:可打印 ASCII 不拦截(fall through),索引才拦截 + - 不能传 ASCII 给索引路径!ASCII '0'=48 → index=47 → 数组越界 → 崩溃 + - SELECT 由 GPIO9 短按处理,CANCEL 由 POWER_BOOT 短按处理 + - **P1.2 = POWER_EN**:高电平有效,驱动 MOS 管维持供电 + - `tca9535PowerEn(bool on)` 静态函数,read-modify-write P1.2 + - **P1.3 = POWER_BOOT**:输入,低电平有效(按键按下接地) + - 开机:物理按键 → MOS → ESP32 得电 → **main.cpp Wire.begin() 后立即调用 `tca9535PowerEn(true)` 锁住供电** + - ⚠️ 曾经的设计错误:在 `tca9535ButtonThread::init()` 里等 P1.3 按住 2s 再拉高 POWER_EN,但 setup() 需要数秒初始化,用户早已松手导致 MOS 断电。已修复为在 Wire.begin() 后立即上锁。 + - 短按:派发 INPUT_BROKER_CANCEL + - 长按 2s:派发 INPUT_BROKER_SHUTDOWN(走系统关机流程 → Power::shutdown() → POWER_EN 拉低) + - `tca9535ReadPowerBoot()` 静态函数读取 P1.3 状态 + - **P1.4 = LoRa RST**:通过 TCA9535GpioHal 自定义 HAL 拦截虚拟引脚 200 转发到 I²C + - `tca9535LoraReset(bool high)` 静态函数,read-modify-write P1.4 + - `LORA_RESET = TCA9535_LORA_RST_VIRTUAL_PIN(200)`,RadioLib 的 `findChip()`/`reset()` 全链路走 I²C + - **P1.5 = 状态指示灯**:输出,低电平点亮 + - `tca9535StatusLed(bool on)` 静态函数,read-modify-write P1.5 + - init() 中配置为输出,默认熄灭(高电平) + - 在 TCA9535ButtonThread::runOnce() 中独立驱动,500ms 亮 + 500ms 灭 = 1 秒闪烁 + - 不再跟随 LED_PIN(已从 GpioSplitter 中移除) + - **P1.0 = 键盘背光**:输出,高电平点亮 + - `TCA9535_BIT_P10` 宏 = `(1u << 0)` + - `tca9535Backlight(bool on)` 静态函数,read-modify-write P1.0 + - init() 中默认熄灭(低电平) + - runOnce() 中驱动:按键按下时点亮,刷新 5 秒计时;5 秒无操作自动熄灭 + - **P1.1 = CHARGE_DET**:输入,高电平=正在充电 + - `TCA9535_CHARGE_DET_PIN` 宏 = `(1u << 1)`,在 variant.h 中定义 + - `tca9535ReadChargeDet()` 静态内联函数,读 P1.1 输入寄存器 + - `tca9535IsCharging` 全局 volatile bool,由 runOnce() 每 2 秒轮询更新 + - `Power.cpp` 的 `AnalogBatteryLevel::isCharging()` 和 `isVbusIn()` 均读取此变量 + - P1 config = 0x0A(P1.1=输入 CHARGE_DET, P1.3=输入 POWER_BOOT, 其余输出) + - ⚠️ 踩坑1:加 CHARGE_DET 后曾将 config 从 0x8B 改为 0x8D,导致 P1.2(POWER_EN) 被误配成输入(高阻),MOS 失控断电。正确值 0x0A + - ⚠️ 踩坑2:TP4057 充电芯片电压反串导致 P1.1 在未充电时仍读高电平,充电检测误报。需硬件修改解决。 +- **关机路径**:所有关机触发最终走 Power::shutdown() → tca9535PowerEn(false) → doDeepSleep() + +## 代码架构要点 +- **踩坑记录**:TCA9535 矩阵扫描 cols 计算中 `~` 运算符对 uint8_t 会整数提升为 int,导致高 4 位被污染。修复:`((~(p0In & 0xF0)) >> 4) & 0x0F`。见 scanMatrix()。 +- 路由层:FloodingRouter → ReliableRouter → NextHopRouter +- 无线接口:RadioLib 抽象层(SX126x/SX128x/LR11x0/RF95) +- 模块系统:`src/modules/` 下各功能模块(TextMessage, Position, Telemetry, etc.) +- Protobuf 消息定义:`src/mesh/generated/` + `protobufs/` +- 关键全局变量:`router`, `service`, `screen`, `gps`, `rIf` + +## 自定义修改记录(readme.md) +- 增加 CN 频段定义:`RDEF(CN, 470.0f, 510.0f, 100, 0, SETTING_MAX_POWER, true, false, false)` +- SETTING_MAX_POWER 使用宏保护:`#ifndef SETTING_MAX_POWER / #define SETTING_MAX_POWER 3` + +## 构建注意事项 +- `platformio.ini` 中 lib_deps 均已固定 commit hash,符合生产规范 +- 排除了大量 RadioLib 不用的协议(AX25/LoRaWAN/APRS 等)以节省 flash +- MAX_THREADS=40 +- 默认 env:tbeam(需要切换到 esp32c3_moonshine 系列时要指定 env) diff --git a/PCB/Moonshine_travelers.eprj2 b/PCB/Moonshine_travelers.eprj2 index e9898ab38b7ba9d2546da932d35be7945d225bef..342eab00d29df0c3353ed3ba5a024f6f307212f8 100644 GIT binary patch delta 277 zcmXBMu};EJ7zN<|Ew>iAEw_N8)XGI?W1|BjBn)oEorDO+#6<>t0YVH6q)|db=k^Vh z;jVoO=RN_Wv!|MzFFDCc&MXaSmWA};u}stS_F8r*`+Qixmu*glPvhxm^7#DpswYAw zQv4=g1-Z*+T8YZ%raM!FLH)mR@dxWk9l|2fWfzYpn@L)pn(oS2tgP) h7!ZLd#2^j{NJ0wIkbx}ZAP)s7f`Af~9cxfo7$1Z5P+$N6 diff --git a/code/firmware-2.7.15.567b8ea/src/Power.cpp b/code/firmware-2.7.15.567b8ea/src/Power.cpp index cb1d5c6..76de8f0 100644 --- a/code/firmware-2.7.15.567b8ea/src/Power.cpp +++ b/code/firmware-2.7.15.567b8ea/src/Power.cpp @@ -20,7 +20,7 @@ #include "meshUtils.h" #include "sleep.h" -#ifdef TCA9535_LORA_RST_VIRTUAL_PIN +#ifdef HAS_TCA9535_BUTTON #include "input/TCA9535ButtonThread.h" #endif @@ -445,7 +445,10 @@ class AnalogBatteryLevel : public HasBatteryLevel /// so we use EXT_PWR_DETECT GPIO pin to detect external power source virtual bool isVbusIn() override { -#ifdef EXT_PWR_DETECT +#ifdef TCA9535_CHARGE_DET_PIN + // 使用 TCA9535 CHARGE_DET 检测外部供电(高电平=充电中=有外部电源) + return tca9535IsCharging; +#elif defined(EXT_PWR_DETECT) #if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB) // if external powered that pin will be pulled down if (digitalRead(EXT_PWR_DETECT) == LOW) { @@ -472,7 +475,9 @@ class AnalogBatteryLevel : public HasBatteryLevel return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse; } #endif -#ifdef EXT_CHRG_DETECT +#ifdef TCA9535_CHARGE_DET_PIN + return tca9535IsCharging; +#elif defined(EXT_CHRG_DETECT) return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value; #else #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION) 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 3309f1a..fa5cb02 100644 --- a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp +++ b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.cpp @@ -5,38 +5,67 @@ 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_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 */ \ + INPUT_BROKER_MATRIXKEY, /* key0 = ROW0·COL0 → '1' */ \ + INPUT_BROKER_MATRIXKEY, /* key1 = ROW0·COL1 → '2' */ \ + INPUT_BROKER_MATRIXKEY, /* key2 = ROW0·COL2 → '3' */ \ + INPUT_BROKER_UP, /* key3 = ROW0·COL3 */ \ + INPUT_BROKER_MATRIXKEY, /* key4 = ROW1·COL0 → '4' */ \ + INPUT_BROKER_MATRIXKEY, /* key5 = ROW1·COL1 → '5' */ \ + INPUT_BROKER_MATRIXKEY, /* key6 = ROW1·COL2 → '6' */ \ + INPUT_BROKER_DOWN, /* key7 = ROW1·COL3 */ \ + INPUT_BROKER_MATRIXKEY, /* key8 = ROW2·COL0 → '7' */ \ + INPUT_BROKER_MATRIXKEY, /* key9 = ROW2·COL1 → '8' */ \ + INPUT_BROKER_MATRIXKEY, /* key10 = ROW2·COL2 → '9' */ \ + INPUT_BROKER_LEFT, /* key11 = ROW2·COL3 */ \ + INPUT_BROKER_MATRIXKEY, /* key12 = ROW3·COL0 → '*' */ \ + INPUT_BROKER_MATRIXKEY, /* key13 = ROW3·COL1 → '0' */ \ + INPUT_BROKER_MATRIXKEY, /* key14 = ROW3·COL2 → '#' */ \ + INPUT_BROKER_RIGHT, /* key15 = ROW3·COL3 */ \ + } +#endif + +// 默认按键字符映射(仅 INPUT_BROKER_MATRIXKEY 类型的按键使用) +// 传 ASCII 字符,CannedMessageModule 会根据 kbchar 走文本输入路径 +// variant.h 中可用 #define TCA9535_KEY_CHAR_MAP { ... } 覆盖 +#ifndef TCA9535_KEY_CHAR_MAP +#define TCA9535_KEY_CHAR_MAP \ + { \ + '1', /* key0 = ROW0·COL0 */ \ + '2', /* key1 = ROW0·COL1 */ \ + '3', /* key2 = ROW0·COL2 */ \ + 0, /* key3 = ROW0·COL3 → 方向键,无字符 */ \ + '4', /* key4 = ROW1·COL0 */ \ + '5', /* key5 = ROW1·COL1 */ \ + '6', /* key6 = ROW1·COL2 */ \ + 0, /* key7 = ROW1·COL3 → 方向键,无字符 */ \ + '7', /* key8 = ROW2·COL0 */ \ + '8', /* key9 = ROW2·COL1 */ \ + '9', /* key10 = ROW2·COL2 */ \ + 0, /* key11 = ROW2·COL3 → 方向键,无字符 */ \ + '*', /* key12 = ROW3·COL0 */ \ + '0', /* key13 = ROW3·COL1 */ \ + '#', /* key14 = ROW3·COL2 */ \ + 0, /* key15 = ROW3·COL3 → 方向键,无字符 */ \ } #endif static const input_broker_event tca9535KeyMap[TCA9535_KEY_COUNT] = TCA9535_KEY_MAP; +static const unsigned char tca9535KeyCharMap[TCA9535_KEY_COUNT] = TCA9535_KEY_CHAR_MAP; // ----------------------------------------------------------------------- // 中断标志(ISR -> runOnce 通信,volatile,只做 set/clear) // ----------------------------------------------------------------------- static volatile bool tca9535IntPending = false; +#ifdef HAS_TCA9535_BUTTON +volatile bool tca9535IsCharging = false; +#endif + #ifdef TCA9535_INT_PIN static void IRAM_ATTR tca9535ISR() { @@ -58,18 +87,23 @@ bool TCA9535ButtonThread::init() { // =================================================================== // 第一步:配置 P1 口方向 - // P1.2=输出(POWER_EN), P1.3=输入(POWER_BOOT), P1.4=输出(LoRa RST), - // P1.5=输出(状态灯), P1.6=输出(GPS RST), P1.7=输出(GPS EN) + // P1.0=输出(未用), P1.1=输入(CHARGE_DET), P1.2=输出(POWER_EN), + // P1.3=输入(POWER_BOOT), P1.4=输出(LoRa RST), P1.5=输出(状态灯), + // P1.6=输出(GPS RST), P1.7=输出(GPS EN) // Configuration 寄存器:1=input, 0=output - // P1.2=0, P1.3=1, P1.4=0, P1.5=0, P1.6=0, P1.7=0 → 0x8B (1000 1011) + // bit: P1.7 P1.6 P1.5 P1.4 P1.3 P1.2 P1.1 P1.0 + // 0 0 0 0 1 0 1 0 = 0x0A // =================================================================== - if (!writeReg(TCA9535_REG_CONFIG_P1, 0x8B)) { + if (!writeReg(TCA9535_REG_CONFIG_P1, 0x0A)) { LOG_WARN("TCA9535: P1 config write failed"); return false; } + // P1.0 键盘背光默认熄灭(低电平) + tca9535Backlight(false); + // 确保 P1.4 输出高电平(LoRa RST 高 = 正常工作) - // 注意:此时不拉高 POWER_EN,等开机确认后再拉高 + // 注意:POWER_EN 已由 main.cpp 在 Wire.begin() 后立即拉高,此处无需再操作 tca9535LoraReset(true); // P1.5 状态灯默认熄灭(高电平) @@ -82,54 +116,13 @@ bool TCA9535ButtonThread::init() tca9535GpsEn(true); // =================================================================== - // 第二步:开机检测 — 等待用户持续按住 P1.3 达 2 秒 - // 物理按键已使 MOS 导通(ESP32 得电),但 POWER_EN 尚未拉高 - // 用户必须持续按住 2 秒,否则 init() 返回 false → 系统不完成启动 + // 第二步:POWER_EN 已由 main.cpp 在 Wire.begin() 后立即拉高(早期锁定) + // 此处只需确认状态机进入 RUNNING,不再需要等待 P1.3 按住 2 秒。 + // 原因:系统从 Wire.begin() 到 tca9535ButtonThread::init() 之间需要 + // 数秒的初始化时间(LoRa/WiFi/BLE/GPS 等),用户早已松开按键, + // 无法在此处等待。开机供电维持已在 main.cpp 最早期完成。 // =================================================================== - LOG_INFO("TCA9535: Waiting for power button hold (%d ms)...", TCA9535_POWER_BOOT_HOLD_MS); - - uint32_t holdStart = 0; - bool wasPressed = false; - - while (true) { - bool pressed = tca9535ReadPowerBoot(_wire); - - if (pressed && !wasPressed) { - // 按键刚按下,记录起始时间 - holdStart = millis(); - wasPressed = true; - } else if (!pressed && wasPressed) { - // 按键松开 — 检查是否按够时间 - uint32_t held = millis() - holdStart; - if (held >= TCA9535_POWER_BOOT_HOLD_MS) { - // 按够 2 秒,确认开机 - LOG_INFO("TCA9535: Power button held %lu ms -> boot confirmed", held); - break; - } else { - // 未按够,重新等待 - LOG_INFO("TCA9535: Power button released after %lu ms (need %d), waiting...", held, - TCA9535_POWER_BOOT_HOLD_MS); - wasPressed = false; - } - } else if (pressed && wasPressed) { - // 持续按住中,检查是否已达 2 秒(即使没松开也确认) - if ((millis() - holdStart) >= TCA9535_POWER_BOOT_HOLD_MS) { - LOG_INFO("TCA9535: Power button held >= %d ms -> boot confirmed", TCA9535_POWER_BOOT_HOLD_MS); - break; - } - } - - delay(TCA9535_POWER_BOOT_CHECK_MS); - } - - // =================================================================== - // 第三步:确认开机 → 拉高 POWER_EN 维持供电 - // =================================================================== - if (!tca9535PowerEn(true)) { - LOG_WARN("TCA9535: Failed to set POWER_EN high"); - return false; - } - LOG_INFO("TCA9535: POWER_EN set HIGH (system powered)"); + LOG_INFO("TCA9535: POWER_EN already latched in early boot, skipping boot-hold wait"); _powerState = TCA9535PowerState::RUNNING; // =================================================================== @@ -206,6 +199,28 @@ int32_t TCA9535ButtonThread::runOnce() tca9535StatusLed(_statusLedOn); } + // =================================================================== + // P1.0 键盘背光:有按键按下时点亮,5 秒无操作自动熄灭 + // =================================================================== + if (_backlightOn && millis() - _backlightLastMs >= 5000) { + _backlightOn = false; + tca9535Backlight(false); + } + + // =================================================================== + // 充电检测:轮询 P1.1 (CHARGE_DET),高电平=正在充电 + // =================================================================== +#ifdef TCA9535_CHARGE_DET_PIN + if (millis() - _chargeDetLastMs >= 2000) { + _chargeDetLastMs = millis(); + bool charging = tca9535ReadChargeDet(); + if (charging != tca9535IsCharging) { + tca9535IsCharging = charging; + LOG_INFO("TCA9535: Charging %s", charging ? "DETECTED" : "STOPPED"); + } + } +#endif + // =================================================================== // 矩阵键盘扫描(仅 RUNNING 状态) // =================================================================== @@ -231,9 +246,17 @@ int32_t TCA9535ButtonThread::runOnce() // 遍历所有键位,派发按下事件 for (uint8_t i = 0; i < TCA9535_KEY_COUNT; i++) { if (pressed & (1u << i)) { + // 按键按下 → 点亮键盘背光(重置 5 秒计时) + if (!_backlightOn) { + _backlightOn = true; + _backlightLastMs = millis(); + tca9535Backlight(true); + } else { + _backlightLastMs = millis(); // 已亮则刷新计时 + } input_broker_event evt = tca9535KeyMap[i]; if (evt != INPUT_BROKER_NONE) { - dispatchEvent(evt); + dispatchEvent(evt, tca9535KeyCharMap[i]); } } } @@ -308,12 +331,12 @@ bool TCA9535ButtonThread::readReg(uint8_t reg, uint8_t &val) return true; } -void TCA9535ButtonThread::dispatchEvent(input_broker_event evt) +void TCA9535ButtonThread::dispatchEvent(input_broker_event evt, unsigned char kbchar) { InputEvent e = {}; e.source = _originName; e.inputEvent = evt; - e.kbchar = 0; + e.kbchar = kbchar; e.touchX = 0; e.touchY = 0; this->notifyObservers(&e); 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 4bed1b0..c35ee71 100644 --- a/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h +++ b/code/firmware-2.7.15.567b8ea/src/input/TCA9535ButtonThread.h @@ -7,10 +7,12 @@ * - A0=0, A1=0, A2=0 → I²C 地址 0x20 * - P0.0~P0.3:行输出(ROW0~ROW3),逐行拉低扫描 * - P0.4~P0.7:列输入(COL0~COL3),读取按键状态 - * - P1.2:电源使能(POWER_EN),高电平有效,驱动 MOS 管维持供电 + * - P1.1:充电检测输入(CHARGE_DET),高电平=正在充电 + * - P1.2:电源使能(POWER_EN),高电平有效,驱动 MOS 管维持供电(输出) * - P1.3:电源开机按钮(POWER_BOOT),输入,低电平有效(按键按下接地) * - P1.4:LoRa RST 输出(通过 I²C 控制 RadioLib 复位序列) * - P1.5:状态指示灯,低电平点亮 + * P1 Config 寄存器 = 0x0A(P1.1、P1.3 为输入,其余为输出) * * 电源管理逻辑: * 开机:物理按键按下 → MOS 导通 → ESP32/TCA9535 得电 @@ -81,6 +83,7 @@ #define TCA9535_REG_CONFIG_P1 0x07 // P1 口引脚位掩码 +#define TCA9535_BIT_P10 (1u << 0) // 键盘背光输出(高电平点亮) #define TCA9535_BIT_P12 (1u << 2) // POWER_EN 输出 #define TCA9535_BIT_P13 (1u << 3) // POWER_BOOT 输入 #define TCA9535_BIT_P14 (1u << 4) // LoRa RST 输出 @@ -88,6 +91,24 @@ #define TCA9535_BIT_P16 (1u << 6) // GPS RST 输出 #define TCA9535_BIT_P17 (1u << 7) // GPS EN 输出(高电平有效) +#ifdef TCA9535_CHARGE_DET_PIN +/** + * 通过 I²C 读取 TCA9535 CHARGE_DET(P1.1)输入状态。 + * @return true=正在充电(高电平),false=未充电(低电平) + */ +static inline bool tca9535ReadChargeDet() +{ + Wire.beginTransmission(TCA9535_I2C_ADDR); + Wire.write(TCA9535_REG_INPUT_P1); + if (Wire.endTransmission(false) != 0) + return false; + if (Wire.requestFrom((uint8_t)TCA9535_I2C_ADDR, (uint8_t)1) != 1) + return false; + uint8_t p1In = Wire.read(); + return !!(p1In & TCA9535_CHARGE_DET_PIN); +} +#endif + /** * 通过 I²C 控制 TCA9535 P1.2 上的电源使能(POWER_EN)。 * 高电平有效:控制 MOS 管维持系统供电。 @@ -191,6 +212,33 @@ static inline bool tca9535StatusLed(bool on) return (Wire.endTransmission() == 0); } +/** + * 通过 I²C 控制 TCA9535 P1.0 上的键盘背光。 + * 高电平点亮,低电平熄灭。 + * @param on true=点亮(高电平),false=熄灭(低电平) + */ +static inline bool tca9535Backlight(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.0 位:高电平点亮 + if (on) + p1Out |= TCA9535_BIT_P10; // 拉高 = 点亮 + else + p1Out &= ~TCA9535_BIT_P10; // 拉低 = 熄灭 + + Wire.beginTransmission(TCA9535_I2C_ADDR); + Wire.write(TCA9535_REG_OUTPUT_P1); + Wire.write(p1Out); + return (Wire.endTransmission() == 0); +} + /** * 通过 I²C 控制 TCA9535 P1.6 上的 GPS RST。 * @param high true=释放复位(高电平),false=触发复位(低电平) @@ -288,6 +336,13 @@ class TCA9535ButtonThread : public Observable, public concur bool _statusLedOn = false; uint32_t _statusLedToggleMs = 0; + // 充电检测轮询间隔 + uint32_t _chargeDetLastMs = 0; + + // P1.0 键盘背光控制(按键时点亮,5 秒无操作熄灭) + bool _backlightOn = false; + uint32_t _backlightLastMs = 0; + // 写寄存器 bool writeReg(uint8_t reg, uint8_t val); @@ -298,10 +353,14 @@ class TCA9535ButtonThread : public Observable, public concur bool scanMatrix(uint16_t &keys); // 派发事件到 InputBroker - void dispatchEvent(input_broker_event evt); + void dispatchEvent(input_broker_event evt, unsigned char kbchar = 0); }; // 仅在 HAS_TCA9535_BUTTON 启用时导出全局指针声明 #ifdef HAS_TCA9535_BUTTON extern TCA9535ButtonThread *tca9535ButtonThread; + +// 全局充电检测状态,由 TCA9535ButtonThread::runOnce() 轮询更新 +// 可被 Power 等外部模块读取(需 #ifdef TCA9535_CHARGE_DET_PIN 守卫) +extern volatile bool tca9535IsCharging; #endif diff --git a/code/firmware-2.7.15.567b8ea/src/main.cpp b/code/firmware-2.7.15.567b8ea/src/main.cpp index d011b26..37c9ff5 100644 --- a/code/firmware-2.7.15.567b8ea/src/main.cpp +++ b/code/firmware-2.7.15.567b8ea/src/main.cpp @@ -584,6 +584,13 @@ void setup() Wire.begin(); #elif defined(I2C_SDA) && !defined(ARCH_RP2040) Wire.begin(I2C_SDA, I2C_SCL); +#ifdef HAS_TCA9535_BUTTON + // TCA9535 POWER_EN 必须在 I²C 初始化完成后立即拉高,否则用户松开按键后 + // MOS 断电,系统在 setup() 中途就会掉电。此处无条件锁住供电, + // 后续 tca9535ButtonThread::init() 只负责键盘和状态机初始化。 + tca9535PowerEn(true); + LOG_INFO("TCA9535: POWER_EN latched HIGH (early boot)"); +#endif #elif defined(ARCH_PORTDUINO) if (portduino_config.i2cdev != "") { LOG_INFO("Use %s as I2C device", portduino_config.i2cdev.c_str()); 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 f435f60..2ba7863 100644 --- a/code/firmware-2.7.15.567b8ea/src/modules/CannedMessageModule.cpp +++ b/code/firmware-2.7.15.567b8ea/src/modules/CannedMessageModule.cpp @@ -322,14 +322,27 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event) if (event->kbchar == INPUT_BROKER_MSG_TAB && handleTabSwitch(event)) return 1; - // Matrix keypad: If matrix key, trigger action select for canned message + // Matrix keypad: If matrix key with printable char, let it fall through to + // the normal input path (INACTIVE→FREETEXT or FREETEXT→append char). + // Only intercept as canned-message selector if kbchar is a valid 1-based index + // (e.g., RAK14004 sends kbchar=1..16). if (event->inputEvent == INPUT_BROKER_MATRIXKEY) { - runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; - payload = INPUT_BROKER_MATRIXKEY; - currentMessageIndex = event->kbchar - 1; - lastTouchMillis = millis(); - requestFocus(); - return 1; + // Printable ASCII (32-126): treat as keyboard input, don't intercept + if (event->kbchar >= 32 && event->kbchar <= 126) { + // Fall through to normal input handling below + } else { + // 1-based index from hardware like RAK14004 + int idx = event->kbchar - 1; + if (idx < 0 || idx >= messagesCount) { + return 0; // kbchar out of range, ignore + } + runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT; + payload = INPUT_BROKER_MATRIXKEY; + currentMessageIndex = idx; + lastTouchMillis = millis(); + requestFocus(); + return 1; + } } // Always normalize navigation/select buttons for further handlers 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 81d3046..3fb75e8 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 @@ -30,6 +30,7 @@ // - 与屏幕共用 I²C 总线 (SDA=0, SCL=1) // - A0=0, A1=0, A2=0 → 地址 0x20 (TCA9535_I2C_ADDR) // - P0.0~P0.3 行输出(ROW0~ROW3),P0.4~P0.7 列输入(COL0~COL3) +// - P1.0 键盘背光输出(高电平点亮,按键时亮 5 秒后自动熄灭) // - P1.2 电源使能(POWER_EN),高电平有效,驱动 MOS 管维持供电 // - P1.3 电源开机按钮(POWER_BOOT),输入,低电平有效 // 开机:持续按住 2 秒 → POWER_EN 拉高维持供电 @@ -37,35 +38,58 @@ // - P1.4 LoRa RST 输出(通过 I²C 控制 RadioLib 复位序列) // - P1.6 GPS_RST 输出(通过 tca9535GpsReset() 控制,init 中释放) // - P1.7 GPS_EN 输出(高电平有效,通过 enablePin 桥接到 TCA9535) +// - P1.1 CHARGE_DET 输入(高电平=正在充电) // - 中断引脚 GPIO5,低电平有效,下降沿触发 // ----------------------------------------------------------------------- #define HAS_TCA9535_BUTTON #define TCA9535_INT_PIN 5 // TCA9535 INT → GPIO5(低电平有效,下降沿触发) #define TCA9535_POWER_EN_BIT (1u << 2) // P1.2 = 电源使能(高电平=开机) +#define TCA9535_CHARGE_DET_PIN (1u << 1) // P1.1 = 充电检测输入(高电平=正在充电) // 按键映射:4×4 矩阵,行优先排列 // KEY[0]=ROW0·COL0, KEY[1]=ROW0·COL1, ..., KEY[15]=ROW3·COL3 // 低电平有效(按下接地,列读取到低电平=按下) +// 九宫格:key0~2=1~3, key4~6=4~6, key8~10=7~9, key12=*, key13=0, key14=# +// 方向键:key3=UP, key7=DOWN, key11=LEFT, key15=RIGHT // SELECT 由 GPIO9 短按触发,CANCEL 由 POWER_BOOT(P1.3) 短按触发 -// 矩阵仅保留方向键 #define TCA9535_KEY_MAP \ { \ - 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 */ \ + INPUT_BROKER_MATRIXKEY, /* key0 = ROW0·COL0 → '1' */ \ + INPUT_BROKER_MATRIXKEY, /* key1 = ROW0·COL1 → '2' */ \ + INPUT_BROKER_MATRIXKEY, /* key2 = ROW0·COL2 → '3' */ \ + INPUT_BROKER_UP, /* key3 = ROW0·COL3 */ \ + INPUT_BROKER_MATRIXKEY, /* key4 = ROW1·COL0 → '4' */ \ + INPUT_BROKER_MATRIXKEY, /* key5 = ROW1·COL1 → '5' */ \ + INPUT_BROKER_MATRIXKEY, /* key6 = ROW1·COL2 → '6' */ \ + INPUT_BROKER_DOWN, /* key7 = ROW1·COL3 */ \ + INPUT_BROKER_MATRIXKEY, /* key8 = ROW2·COL0 → '7' */ \ + INPUT_BROKER_MATRIXKEY, /* key9 = ROW2·COL1 → '8' */ \ + INPUT_BROKER_MATRIXKEY, /* key10 = ROW2·COL2 → '9' */ \ + INPUT_BROKER_LEFT, /* key11 = ROW2·COL3 */ \ + INPUT_BROKER_MATRIXKEY, /* key12 = ROW3·COL0 → '*' */ \ + INPUT_BROKER_MATRIXKEY, /* key13 = ROW3·COL1 → '0' */ \ + INPUT_BROKER_MATRIXKEY, /* key14 = ROW3·COL2 → '#' */ \ + INPUT_BROKER_RIGHT, /* key15 = ROW3·COL3 */ \ + } + +#define TCA9535_KEY_CHAR_MAP \ + { \ + '1', /* key0 */ \ + '2', /* key1 */ \ + '3', /* key2 */ \ + 0, /* key3 → 方向键 */ \ + '4', /* key4 */ \ + '5', /* key5 */ \ + '6', /* key6 */ \ + 0, /* key7 → 方向键 */ \ + '7', /* key8 */ \ + '8', /* key9 */ \ + '9', /* key10 */ \ + 0, /* key11 → 方向键 */ \ + '*', /* key12 */ \ + '0', /* key13 */ \ + '#', /* key14 */ \ + 0, /* key15 → 方向键 */ \ }