feat(travelers): TCA9535 矩阵键盘驱动 + 电源管理 + LoRa RST 虚拟引脚
- 新增 TCA9535ButtonThread 驱动 (4x4 矩阵键盘, 中断/轮询模式) - 电源管理: P1.2 POWER_EN (MOS 供电) + P1.3 POWER_BOOT (2s 开/关机) - LoRa RST 通过 TCA9535 P1.4 + TCA9535GpioHal 虚拟引脚 200 控制 - 修复 LTO 链接错误 (去掉 .h/.cpp 中的 HAS_TCA9535_BUTTON 守卫) - 新增 CHANGELOG.md, 更新 readme.md (PCAL9535 升级计划)
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
#include "TCA9535ButtonThread.h"
|
||||
|
||||
#include "main.h"
|
||||
|
||||
using namespace concurrency;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 默认按键映射(4×4 矩阵,行优先:KEY[0]=ROW0·COL0 ... KEY[15]=ROW3·COL3)
|
||||
// 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 */ \
|
||||
}
|
||||
#endif
|
||||
|
||||
static const input_broker_event tca9535KeyMap[TCA9535_KEY_COUNT] = TCA9535_KEY_MAP;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 中断标志(ISR -> runOnce 通信,volatile,只做 set/clear)
|
||||
// -----------------------------------------------------------------------
|
||||
static volatile bool tca9535IntPending = false;
|
||||
|
||||
#ifdef TCA9535_INT_PIN
|
||||
static void IRAM_ATTR tca9535ISR()
|
||||
{
|
||||
tca9535IntPending = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 构造 / 初始化
|
||||
// -----------------------------------------------------------------------
|
||||
TCA9535ButtonThread::TCA9535ButtonThread(const char *name, TwoWire *wire)
|
||||
: OSThread(name), _wire(wire), _originName(name)
|
||||
{
|
||||
if (inputBroker)
|
||||
inputBroker->registerSource(this);
|
||||
}
|
||||
|
||||
bool TCA9535ButtonThread::init()
|
||||
{
|
||||
// ===================================================================
|
||||
// 第一步:配置 P1 口方向
|
||||
// P1.2 = 输出(POWER_EN),P1.3 = 输入(POWER_BOOT),P1.4 = 输出(LoRa RST)
|
||||
// Configuration 寄存器:1=input, 0=output
|
||||
// P1.2=bit2=0, P1.3=bit3=1, P1.4=bit4=0 → 0xEB (1110 1011)
|
||||
// ===================================================================
|
||||
if (!writeReg(TCA9535_REG_CONFIG_P1, 0xEB)) {
|
||||
LOG_WARN("TCA9535: P1 config write failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保 P1.4 输出高电平(LoRa RST 高 = 正常工作)
|
||||
// 注意:此时不拉高 POWER_EN,等开机确认后再拉高
|
||||
tca9535LoraReset(true);
|
||||
|
||||
// ===================================================================
|
||||
// 第二步:开机检测 — 等待用户持续按住 P1.3 达 2 秒
|
||||
// 物理按键已使 MOS 导通(ESP32 得电),但 POWER_EN 尚未拉高
|
||||
// 用户必须持续按住 2 秒,否则 init() 返回 false → 系统不完成启动
|
||||
// ===================================================================
|
||||
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)");
|
||||
_powerState = TCA9535PowerState::RUNNING;
|
||||
|
||||
// ===================================================================
|
||||
// 第四步:配置 P0 口方向(矩阵键盘)
|
||||
// ===================================================================
|
||||
uint8_t configP0 = 0xF0; // P0.0~P0.3 输出(行),P0.4~P0.7 输入(列)
|
||||
if (!writeReg(TCA9535_REG_CONFIG_P0, configP0)) {
|
||||
LOG_WARN("TCA9535: P0 config write failed (addr=0x%02x)", TCA9535_I2C_ADDR);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 行输出初始状态:全部拉高(未选中任何行)
|
||||
if (!writeReg(TCA9535_REG_OUTPUT_P0, TCA9535_ALL_ROWS_HIGH)) {
|
||||
LOG_WARN("TCA9535: P0 output write failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 极性不反转
|
||||
writeReg(TCA9535_REG_INVERT_P0, 0x00);
|
||||
writeReg(TCA9535_REG_INVERT_P1, 0x00);
|
||||
|
||||
// 读取初始状态,避免第一次扫描产生误报
|
||||
scanMatrix(_lastKeys);
|
||||
|
||||
#ifdef TCA9535_INT_PIN
|
||||
pinMode(TCA9535_INT_PIN, INPUT_PULLUP);
|
||||
attachInterrupt(digitalPinToInterrupt(TCA9535_INT_PIN), tca9535ISR, FALLING);
|
||||
LOG_INFO("TCA9535: INT on GPIO%d", TCA9535_INT_PIN);
|
||||
#endif
|
||||
|
||||
LOG_INFO("TCA9535 init OK (addr=0x%02x, matrix %dx%d, power=RUNNING)", TCA9535_I2C_ADDR, TCA9535_ROWS,
|
||||
TCA9535_COLS);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 主循环:关机检测 → 矩阵扫描 → 边沿检测 → 事件派发
|
||||
// -----------------------------------------------------------------------
|
||||
int32_t TCA9535ButtonThread::runOnce()
|
||||
{
|
||||
// ===================================================================
|
||||
// 关机检测:运行中 P1.3 持续按住 2 秒 → 断电
|
||||
// ===================================================================
|
||||
if (_powerState == TCA9535PowerState::RUNNING) {
|
||||
bool pressed = tca9535ReadPowerBoot(_wire);
|
||||
|
||||
if (pressed && _powerBtnPressStart == 0) {
|
||||
// 按键刚按下,记录起始时间
|
||||
_powerBtnPressStart = millis();
|
||||
LOG_DEBUG("TCA9535: Shutdown button pressed, timing...");
|
||||
} else if (pressed && _powerBtnPressStart != 0) {
|
||||
// 持续按住中
|
||||
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; // 不再调度
|
||||
}
|
||||
} else if (!pressed && _powerBtnPressStart != 0) {
|
||||
// 按键松开,未达关机时间
|
||||
uint32_t held = millis() - _powerBtnPressStart;
|
||||
LOG_DEBUG("TCA9535: Shutdown button released after %lu ms (no action)", held);
|
||||
_powerBtnPressStart = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 矩阵键盘扫描(仅 RUNNING 状态)
|
||||
// ===================================================================
|
||||
#ifdef TCA9535_INT_PIN
|
||||
// 无中断挂起则跳过,节省 I²C 带宽
|
||||
if (!tca9535IntPending)
|
||||
return TCA9535_POWER_BOOT_CHECK_MS;
|
||||
tca9535IntPending = false;
|
||||
#endif
|
||||
|
||||
uint16_t currentKeys = 0x0000;
|
||||
if (!scanMatrix(currentKeys)) {
|
||||
LOG_WARN("TCA9535: scan failed");
|
||||
return 50;
|
||||
}
|
||||
|
||||
// 边沿检测:新按下(上升沿)和释放(下降沿)
|
||||
uint16_t pressed = currentKeys & ~_lastKeys; // 新按下的键
|
||||
uint16_t released = ~currentKeys & _lastKeys; // 新释放的键(目前不处理)
|
||||
|
||||
_lastKeys = currentKeys;
|
||||
|
||||
// 遍历所有键位,派发按下事件
|
||||
for (uint8_t i = 0; i < TCA9535_KEY_COUNT; i++) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
(void)released; // 目前仅处理按下边沿
|
||||
|
||||
#ifdef TCA9535_INT_PIN
|
||||
return 20; // 中断模式:20ms 防抖窗口(每轮扫描间隔)
|
||||
#else
|
||||
return 50; // 轮询模式:50ms 扫描间隔
|
||||
#endif
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 矩阵扫描:逐行拉低,读列状态
|
||||
// 返回 16 位,bit[i] = 1 表示 ROW(i/4)·COL(i%4) 被按下(低电平)
|
||||
// -----------------------------------------------------------------------
|
||||
bool TCA9535ButtonThread::scanMatrix(uint16_t &keys)
|
||||
{
|
||||
keys = 0x0000;
|
||||
|
||||
for (uint8_t row = 0; row < TCA9535_ROWS; row++) {
|
||||
// 拉低当前行,其余行保持高
|
||||
uint8_t outVal = TCA9535_ROW_MASK(row);
|
||||
if (!writeReg(TCA9535_REG_OUTPUT_P0, outVal)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 短延时,等待电平稳定(行列电容充放电)
|
||||
delayMicroseconds(50);
|
||||
|
||||
// 读取 P0 输入寄存器
|
||||
uint8_t p0In = 0xFF;
|
||||
if (!readReg(TCA9535_REG_INPUT_P0, p0In)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 提取列位(P0.4~P0.7),低电平=按下
|
||||
// 将列状态从高4位移到低位,便于索引
|
||||
uint8_t cols = (~(p0In & TCA9535_COL_MASK)) >> 4; // bit0=COL0, bit3=COL3
|
||||
|
||||
// 组装到 keys(每行 4 列)
|
||||
keys |= ((uint16_t)cols << (row * TCA9535_COLS));
|
||||
}
|
||||
|
||||
// 扫描完毕,恢复所有行高电平
|
||||
writeReg(TCA9535_REG_OUTPUT_P0, TCA9535_ALL_ROWS_HIGH);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// 私有:I²C 读写
|
||||
// -----------------------------------------------------------------------
|
||||
bool TCA9535ButtonThread::writeReg(uint8_t reg, uint8_t val)
|
||||
{
|
||||
_wire->beginTransmission(TCA9535_I2C_ADDR);
|
||||
_wire->write(reg);
|
||||
_wire->write(val);
|
||||
return (_wire->endTransmission() == 0);
|
||||
}
|
||||
|
||||
bool TCA9535ButtonThread::readReg(uint8_t reg, uint8_t &val)
|
||||
{
|
||||
_wire->beginTransmission(TCA9535_I2C_ADDR);
|
||||
_wire->write(reg);
|
||||
if (_wire->endTransmission(false) != 0)
|
||||
return false;
|
||||
if (_wire->requestFrom((uint8_t)TCA9535_I2C_ADDR, (uint8_t)1) != 1)
|
||||
return false;
|
||||
val = _wire->read();
|
||||
return true;
|
||||
}
|
||||
|
||||
void TCA9535ButtonThread::dispatchEvent(input_broker_event evt)
|
||||
{
|
||||
InputEvent e = {};
|
||||
e.source = _originName;
|
||||
e.inputEvent = evt;
|
||||
e.kbchar = 0;
|
||||
e.touchX = 0;
|
||||
e.touchY = 0;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* TCA9535PWR I²C IO 扩展器 — 4×4 矩阵键盘 + 电源管理 + LoRa RST 控制
|
||||
*
|
||||
* 硬件:TI TCA9535PWR
|
||||
* - 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.3:电源开机按钮(POWER_BOOT),输入,低电平有效(按键按下接地)
|
||||
* - P1.4:LoRa RST 输出(通过 I²C 控制 RadioLib 复位序列)
|
||||
*
|
||||
* 电源管理逻辑:
|
||||
* 开机:物理按键按下 → MOS 导通 → ESP32/TCA9535 得电
|
||||
* init() 读 P1.3,持续按住 2 秒 → tca9535PowerEn(true) 维持供电
|
||||
* 未按满 2 秒松开 → 不拉高 POWER_EN → MOS 断开 → 断电
|
||||
* 关机:运行中 P1.3 持续低电平 2 秒 → tca9535PowerEn(false) → 断电
|
||||
*
|
||||
* 寄存器布局:
|
||||
* 0x00 Input Port 0 (只读)
|
||||
* 0x01 Input Port 1
|
||||
* 0x02 Output Port 0 (控制行输出电平)
|
||||
* 0x03 Output Port 1 (P1.2 POWER_EN, P1.4 LoRa RST)
|
||||
* 0x04 Polarity Inversion Port 0
|
||||
* 0x05 Polarity Inversion Port 1
|
||||
* 0x06 Configuration Port 0 (1=input, 0=output)
|
||||
* 0x07 Configuration Port 1
|
||||
*
|
||||
* 使用方式:在 variant.h 中定义以下宏,main.cpp 会自动初始化:
|
||||
* #define HAS_TCA9535_BUTTON
|
||||
* #define TCA9535_INT_PIN 5 // 中断引脚(可选,低电平有效)
|
||||
* #define TCA9535_KEY_MAP { ... } // 4×4=16 元素的按键映射
|
||||
* #define TCA9535_LORA_RST_VIRTUAL_PIN 200 // LoRa RST 虚拟引脚号
|
||||
* #define LORA_RESET TCA9535_LORA_RST_VIRTUAL_PIN
|
||||
*/
|
||||
|
||||
#include "InputBroker.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "configuration.h"
|
||||
#include <Wire.h>
|
||||
|
||||
#ifndef TCA9535_I2C_ADDR
|
||||
#define TCA9535_I2C_ADDR 0x20 // A0=A1=A2=0
|
||||
#endif
|
||||
|
||||
// 矩阵行列数
|
||||
#ifndef TCA9535_ROWS
|
||||
#define TCA9535_ROWS 4
|
||||
#endif
|
||||
#ifndef TCA9535_COLS
|
||||
#define TCA9535_COLS 4
|
||||
#endif
|
||||
#define TCA9535_KEY_COUNT (TCA9535_ROWS * TCA9535_COLS)
|
||||
|
||||
// 行输出掩码:P0.0~P0.3 = bit3~bit0
|
||||
// 扫描第 n 行时,将对应 bit 拉低(0),其余拉高(1)
|
||||
#define TCA9535_ROW_MASK(r) (0xF0 | ~(1u << (r)))
|
||||
#define TCA9535_ALL_ROWS_HIGH 0xF0
|
||||
|
||||
// 列输入掩码:P0.4~P0.7 = bit7~bit4
|
||||
#define TCA9535_COL_MASK 0xF0
|
||||
|
||||
// 电源管理常量
|
||||
#ifndef TCA9535_POWER_BOOT_HOLD_MS
|
||||
#define TCA9535_POWER_BOOT_HOLD_MS 2000 // 开机/关机需持续按住的时间(毫秒)
|
||||
#endif
|
||||
#ifndef TCA9535_POWER_BOOT_CHECK_MS
|
||||
#define TCA9535_POWER_BOOT_CHECK_MS 50 // P1.3 轮询间隔(毫秒)
|
||||
#endif
|
||||
|
||||
// TCA9535 寄存器定义
|
||||
#define TCA9535_REG_INPUT_P0 0x00
|
||||
#define TCA9535_REG_INPUT_P1 0x01
|
||||
#define TCA9535_REG_OUTPUT_P0 0x02
|
||||
#define TCA9535_REG_OUTPUT_P1 0x03
|
||||
#define TCA9535_REG_INVERT_P0 0x04
|
||||
#define TCA9535_REG_INVERT_P1 0x05
|
||||
#define TCA9535_REG_CONFIG_P0 0x06
|
||||
#define TCA9535_REG_CONFIG_P1 0x07
|
||||
|
||||
// P1 口引脚位掩码
|
||||
#define TCA9535_BIT_P12 (1u << 2) // POWER_EN 输出
|
||||
#define TCA9535_BIT_P13 (1u << 3) // POWER_BOOT 输入
|
||||
#define TCA9535_BIT_P14 (1u << 4) // LoRa RST 输出
|
||||
|
||||
/**
|
||||
* 通过 I²C 控制 TCA9535 P1.2 上的电源使能(POWER_EN)。
|
||||
* 高电平有效:控制 MOS 管维持系统供电。
|
||||
* 此函数是 static 的,可被任意上下文调用(无需实例)。
|
||||
* @param on true=上电(高电平),false=断电(低电平)
|
||||
*/
|
||||
static inline bool tca9535PowerEn(bool on)
|
||||
{
|
||||
// 读取当前 P1 输出寄存器值
|
||||
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.2 位
|
||||
if (on)
|
||||
p1Out |= TCA9535_BIT_P12; // 拉高 = 上电
|
||||
else
|
||||
p1Out &= ~TCA9535_BIT_P12; // 拉低 = 断电
|
||||
|
||||
// 写回
|
||||
Wire.beginTransmission(TCA9535_I2C_ADDR);
|
||||
Wire.write(TCA9535_REG_OUTPUT_P1);
|
||||
Wire.write(p1Out);
|
||||
return (Wire.endTransmission() == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 I²C 读取 TCA9535 P1.3(POWER_BOOT)输入状态。
|
||||
* @return true=按键按下(低电平),false=按键松开(高电平)
|
||||
*/
|
||||
static inline bool tca9535ReadPowerBoot(TwoWire *wire = &Wire)
|
||||
{
|
||||
wire->beginTransmission(TCA9535_I2C_ADDR);
|
||||
wire->write(TCA9535_REG_INPUT_P1);
|
||||
if (wire->endTransmission(false) != 0)
|
||||
return false; // I²C 错误时返回 false(未按下)
|
||||
if (wire->requestFrom((uint8_t)TCA9535_I2C_ADDR, (uint8_t)1) != 1)
|
||||
return false;
|
||||
uint8_t p1In = wire->read();
|
||||
// P1.3 低电平 = 按下
|
||||
return !(p1In & TCA9535_BIT_P13);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 I²C 控制 TCA9535 P1.4 上的 LoRa RST。
|
||||
* 此函数是 static 的,可被自定义 HAL 在任意上下文调用(无需实例)。
|
||||
* @param high true=释放复位(高电平),false=触发复位(低电平)
|
||||
*/
|
||||
static inline bool tca9535LoraReset(bool high)
|
||||
{
|
||||
// 读取当前 P1 输出寄存器值
|
||||
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.4 位
|
||||
if (high)
|
||||
p1Out |= TCA9535_BIT_P14; // 拉高 = 释放复位
|
||||
else
|
||||
p1Out &= ~TCA9535_BIT_P14; // 拉低 = 触发复位
|
||||
|
||||
// 写回
|
||||
Wire.beginTransmission(TCA9535_I2C_ADDR);
|
||||
Wire.write(TCA9535_REG_OUTPUT_P1);
|
||||
Wire.write(p1Out);
|
||||
return (Wire.endTransmission() == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 电源管理状态机
|
||||
*/
|
||||
enum class TCA9535PowerState : uint8_t {
|
||||
BOOT_PENDING, // 等待开机确认(init 阶段,检测 P1.3 是否按满 2 秒)
|
||||
RUNNING, // 正常运行,POWER_EN 已拉高
|
||||
SHUTDOWN_PENDING, // 关机倒计时(P1.3 持续按住中)
|
||||
};
|
||||
|
||||
class TCA9535ButtonThread : public Observable<const InputEvent *>, public concurrency::OSThread
|
||||
{
|
||||
public:
|
||||
explicit TCA9535ButtonThread(const char *name, TwoWire *wire = &Wire);
|
||||
|
||||
/**
|
||||
* 初始化 TCA9535 并执行开机检测:
|
||||
* 1. 配置 P1 口方向(P1.2/P1.4 输出,P1.3 输入)
|
||||
* 2. 等待检测 P1.3 是否持续按住 2 秒
|
||||
* 3. 如果是 → 拉高 POWER_EN,继续初始化键盘
|
||||
* 4. 如果否 → 不拉高 POWER_EN(MOS 断开 → 断电)
|
||||
* 5. 配置 P0 口方向和矩阵键盘
|
||||
* @return true 如果开机确认成功且键盘初始化完成
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/// 当前电源状态
|
||||
TCA9535PowerState powerState() const { return _powerState; }
|
||||
|
||||
protected:
|
||||
int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
TwoWire *_wire;
|
||||
const char *_originName;
|
||||
|
||||
// 电源管理
|
||||
TCA9535PowerState _powerState = TCA9535PowerState::BOOT_PENDING;
|
||||
uint32_t _powerBtnPressStart = 0; // 按键按下时刻 (millis)
|
||||
|
||||
// 上次扫描结果(16 位,每 bit 对应 row*4+col),用于边沿检测
|
||||
uint16_t _lastKeys = 0x0000;
|
||||
|
||||
// 写寄存器
|
||||
bool writeReg(uint8_t reg, uint8_t val);
|
||||
|
||||
// 读寄存器
|
||||
bool readReg(uint8_t reg, uint8_t &val);
|
||||
|
||||
// 扫描矩阵一次,返回 16 位按键状态(bit=1 表示按下)
|
||||
bool scanMatrix(uint16_t &keys);
|
||||
|
||||
// 派发事件到 InputBroker
|
||||
void dispatchEvent(input_broker_event evt);
|
||||
};
|
||||
|
||||
// 仅在 HAS_TCA9535_BUTTON 启用时导出全局指针声明
|
||||
#ifdef HAS_TCA9535_BUTTON
|
||||
extern TCA9535ButtonThread *tca9535ButtonThread;
|
||||
#endif
|
||||
@@ -141,6 +141,43 @@ AudioThread *audioThread = nullptr;
|
||||
ExtensionIOXL9555 io;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_TCA9535_BUTTON
|
||||
#include "input/TCA9535ButtonThread.h"
|
||||
TCA9535ButtonThread *tca9535ButtonThread = nullptr;
|
||||
#endif
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 自定义 HAL:拦截 TCA9535 虚拟引脚的 GPIO 操作,转发到 I²C
|
||||
// 用于 LoRa RST 通过 TCA9535 P1.4 控制
|
||||
// ---------------------------------------------------------------------------
|
||||
#ifdef TCA9535_LORA_RST_VIRTUAL_PIN
|
||||
#include <RadioLib.h>
|
||||
|
||||
class TCA9535GpioHal : public LockingArduinoHal
|
||||
{
|
||||
public:
|
||||
TCA9535GpioHal(SPIClass &spi, SPISettings spiSettings) : LockingArduinoHal(spi, spiSettings) {}
|
||||
|
||||
void pinMode(uint32_t pin, uint32_t mode) override
|
||||
{
|
||||
if (pin == TCA9535_LORA_RST_VIRTUAL_PIN) {
|
||||
// P1.4 已由 TCA9535 驱动配置为输出,无需重复操作
|
||||
return;
|
||||
}
|
||||
LockingArduinoHal::pinMode(pin, mode);
|
||||
}
|
||||
|
||||
void digitalWrite(uint32_t pin, uint32_t value) override
|
||||
{
|
||||
if (pin == TCA9535_LORA_RST_VIRTUAL_PIN) {
|
||||
tca9535LoraReset(value == GpioLevelHigh);
|
||||
return;
|
||||
}
|
||||
LockingArduinoHal::digitalWrite(pin, value);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if HAS_TFT
|
||||
extern void tftSetup(void);
|
||||
#endif
|
||||
@@ -953,6 +990,19 @@ void setup()
|
||||
// Now that the mesh service is created, create any modules
|
||||
setupModules();
|
||||
|
||||
#ifdef HAS_TCA9535_BUTTON
|
||||
// TCA9535PWR I²C IO 扩展器按键驱动
|
||||
// Wire 已在 I2C 初始化段完成 begin(),inputBroker 已在 setupModules() 中创建
|
||||
tca9535ButtonThread = new TCA9535ButtonThread("TCA9535Btn");
|
||||
if (!tca9535ButtonThread->init()) {
|
||||
LOG_WARN("TCA9535 init failed, disabling button thread");
|
||||
delete tca9535ButtonThread;
|
||||
tca9535ButtonThread = nullptr;
|
||||
} else {
|
||||
LOG_INFO("TCA9535 button thread started");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
// Inform modules about I2C devices
|
||||
ScanI2CCompleted(i2cScanner.get());
|
||||
@@ -1219,6 +1269,8 @@ void setup()
|
||||
|
||||
#elif defined(HW_SPI1_DEVICE)
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings);
|
||||
#elif defined(TCA9535_LORA_RST_VIRTUAL_PIN)
|
||||
TCA9535GpioHal *RadioLibHAL = new TCA9535GpioHal(SPI, spiSettings);
|
||||
#else // HW_SPI1_DEVICE
|
||||
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user