feat(travelers): TCA9535 充电检测 + 键盘背光 + isVbusIn 修复

- 新增 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 仍读高,需硬件修改
This commit is contained in:
2026-03-29 08:49:38 +08:00
parent 311232c9b9
commit e236c951cc
10 changed files with 435 additions and 101 deletions
+83
View File
@@ -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 地址:0x20A0=A1=A2=0),与 SH1106 屏幕共用 WireSDA=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 = 0xEBP1.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_OFFwritePinEN(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,提交 311232cpush
## 九宫格键盘映射
- 添加 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 状态进入 FREETEXTFREETEXT 状态追加字符
- 如果 `kbchar` 是 1-based 索引(如 RAK14004):继续走 canned message 选择路径
- 加边界检查 `idx < 0 || idx >= messagesCount` 防御性忽略
## P1.1 充电检测 (CHARGE_DET)
- P1.1 = CHARGE_DET 输入,高电平=正在充电
- P1 config 从 0x8B 改为 0x8DP1.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`
+103
View File
@@ -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(基础版)
- MCUESP32-C3esp32-c3-devkitm-1
- LoRaE220-400M30SLLCC68/SX1262/SX1268 三选一探测)
- 无屏幕、无 GPS
- SPI: SCK=10, MISO=6, MOSI=7, CS=8, RST=5, DIO1=3, BUSY=4
- LED_POWER=12DIO3 TCXO 1.8VTCXO_OPTIONAL
- Flash: 4MB, dio 模式, 80MHz
### esp32c3_moonshinefirmware 版本,variants 中)
- 同上基础版,增加:
- 支持 SX126X_RXEN=2RX 使能 GPIO
- 支持 E22_400M33SSX126X_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+多按键版)
- MCUESP32-C3
- LoRaRA-01SC-PSETTING_MAX_POWER=29, TX_GAIN=26, SX126X_MAX_POWER=3
- 屏幕:SSD1306I2C: SDA=0, SCL=1
- GPSGPS_RX=21, GPS_TX=20, GPS_POWER_TOGGLE, PIN_GPS_EN=12
- 多按键:PCF8574 IO 扩展器(地址 0x27INT=9),映射 SELECT/UP/DOWN/LEFT/RIGHT/CANCEL
- NeoPixelGPIO13, 1颗 NEO_GRB
- BATTERY_PIN=2
### esp32c3_moonshine_travelers(旅行者版)
- MCUESP32-C3
- LoRaRA-01SC-PSETTING_MAX_POWER=3, TX_GAIN=0, SX126X_MAX_POWER=3
- 屏幕:SH1106I2C: SDA=0, SCL=1
- GPSGPS_RX=21, GPS_TX=20, GPS_RST=P1.6TCA9535, GPS_EN=P1.7TCA9535
- GPS EN 通过 `GpioTca9535GpsEnPin` + `GpioUnaryTransformer` 桥接 `gps->enablePin`
- GPS RST 由 `tca9535GpsReset()` 控制,init 中释放(高电平)
- BATTERY_PIN=2
- **GPIO9**:短按=SELECT,长按=无功能(`BUTTON_DISABLE_LONG_PRESS`
- **TCA9535PWR 4×4 矩阵键盘**:与屏幕共用 I2CA0=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=5GPIO5,低电平有效,下降沿触发)
- 映射(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 = 0x0AP1.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
- 默认 envtbeam(需要切换到 esp32c3_moonshine 系列时要指定 env