/* Hub(U32 USB2517I)按需启停(0.1.14,2026-09-29 用户定,推翻 R2 D13 代价 2 / R-11 的 「运行期不许动 `HUB_RST#`」)。 为什么要有它:五台手机的 VBUS 平时全关,但 Hub 只要上行插着电脑就一直是活动态 —— ESP32 自己的 USB-Serial-JTAG 挂在第 6 口上,电脑永远不会把它挂起。 2026-09-29 板 B 实测 Hub 工作 vs 按住复位差 1.03 W(12 V 输入差约 88 mA),而这 1 W 大部分时间什么都没在干。 把 `RESET_N`(43 脚)拉低 ⇒ Hub 从电脑那边消失,PHY 与 24 MHz 晶振都停,只剩 U31 的静态电流。 ---- 什么时候开(任一条成立就开,并刷新「最后一次需要」的时刻)------------- | why | 条件 | |---------|------| | mode | 模式 = 常开(`hub` 命令,存 NVS) | | vbus | 任一路 VBUS 通着 —— 手机的 USB 只有这时才在 Hub 上 | | flash | 任一路刷机模式窗口内(chgmode.h) | | act | 任一路执行器在跑序列(EDL 要枚举 9008;硬重启中途 VBUS 暂时是关的) | | hold | `hub` 命令给的保持窗口内(要用 ESP32 的 USB 控制台 / 烧录) | | link | MQTT 连续 HUB_LINK_LOST_MS 没连上(含从没配过网)⇒ **USB 是唯一的路,必须开** | | idle | 以上都不成立,但离「最后一次需要」还不到 HUB_IDLE_MS | 「最后一次需要」还会被三件事刷新:启动、上行电脑插上(`UP_VBUS_DET` 上升沿)、 USB 链路上来了一条命令 —— 都是「有人正要用 USB」的信号。 全不成立 ⇒ 关(`HUB_RST#` 拉低)。 ---- 退路(这是推翻 R-11 的前提,一条都不许删)----------------------------- 1. `link`:WiFi/MQTT 断了 ⇒ 1 分钟内 Hub 自己回来,USB 控制台可用; 2. ESP32 任何一次复位(上电 / 看门狗 / esp_restart / esptool / J33 EN)引脚都回到高阻, R716 把 Hub 拉出复位,而且启动算一次「需要」⇒ 至少 HUB_IDLE_MS 的窗口; 3. 板子断电重上 = 第 2 条; 4. J33 UART 应急口不经过 Hub,永远在。 ⚠️ 只许**拉低**或**放成输入**(靠 R716 拉高),不许主动驱高 —— 和 bringup 的脉冲同一口径: 放开 = 高阻,ESP32 这一侧出任何事 Hub 都停在「工作」那一边。 ---- 线程 ---------------------------------------------------------------- **只有执行器任务写**(`hubctl_tick()` 在执行器循环里、命令处理器也在那个任务里), status 跨任务读几个单字,遥测无害(cmd.h 同一条)。 */ #ifndef HUBCTL_H #define HUBCTL_H #include #include #include "hal_err.h" #ifdef __cplusplus extern "C" { #endif #define HUB_IDLE_MS (10u * 60000u) /* 最后一次需要之后再开多久 */ #define HUB_LINK_LOST_MS 60000u /* MQTT 断多久算「只剩 USB 这条路」 */ #define HUB_HOLD_MAX_MIN 240 /* 模式。⚠️ 进 NVS,冻结。 */ typedef enum { HUB_MODE_AUTO = 0, HUB_MODE_ON = 1, HUB_MODE_COUNT, } hub_mode_t; /* 为什么开着 / 关着。⚠️ 进事件(LOG_EV_HUB 的 bus_mv),冻结,只许在末尾追加。 */ typedef enum { HUB_WHY_OFF = 0, HUB_WHY_MODE = 1, HUB_WHY_VBUS = 2, HUB_WHY_FLASH = 3, HUB_WHY_ACT = 4, HUB_WHY_HOLD = 5, HUB_WHY_LINK = 6, HUB_WHY_IDLE = 7, HUB_WHY_COUNT, } hub_why_t; /* LOG_EV_HUB 的 arg。⚠️ 进 flash,冻结。 */ #define HUB_EV_OFF 0u #define HUB_EV_ON 1u #define HUB_EV_MODE 0x10u /* | 新模式 */ /* 一拍的输入(`hubctl_tick()` 自己采;宿主机测试直接喂 `hub_decide()`)。 */ typedef struct { bool vbus_any; bool flash_any; bool act_any; bool link_up; bool host; /* UP_VBUS_DET */ } hub_in_t; /* 模式存盘回调(app_main 写 NVS)。NULL = 不存(宿主机测试)。 */ typedef void (*hub_save_fn)(void *ctx, hub_mode_t mode); /* 起步:记下模式、把 `HUB_RST#` 放成输入(Hub 工作),启动算一次「需要」。 模式越界按 AUTO。 */ void hubctl_init(hub_mode_t mode, hub_save_fn save, void *ctx, uint32_t now); /* 纯判定:这一拍 Hub 该不该开、为什么。会推进内部的时刻(最后一次需要 / 断线起点 / 上行边沿), **不碰引脚**。宿主机测试用它。 */ hub_why_t hub_decide(const hub_in_t *in, uint32_t now); /* 执行器循环每拍调:采输入 → `hub_decide()` → 状态变了才动引脚、记事件。 */ void hubctl_tick(uint32_t now); /* USB 链路上来了一条命令(cmd.c 调):算一次「需要」。 */ void hubctl_touch(uint32_t now); /* 命令:改模式(存盘、记事件)。越界 HAL_ERR_ARG。 */ hal_err_t hubctl_set_mode(hub_mode_t mode); /* 命令:保持 minutes 分钟(1~HUB_HOLD_MAX_MIN,覆盖原窗口);0 = 取消保持**并结束 idle 窗口** (没有别的理由就下一拍关 —— 实测省电用)。越界 HAL_ERR_ARG。 */ hal_err_t hubctl_hold(uint32_t minutes, uint32_t now); hub_mode_t hubctl_mode(void); bool hubctl_is_on(void); /* 引脚此刻放着(Hub 在工作) */ hub_why_t hubctl_why(void); /* 上一拍的结论 */ uint32_t hubctl_hold_left_s(uint32_t now); uint32_t hubctl_idle_left_s(uint32_t now); /* why == idle 时离自动关还有几秒,否则 0 */ const char *hub_mode_str(hub_mode_t m); const char *hub_why_str(hub_why_t w); bool hub_mode_parse(const char *s, hub_mode_t *out); #ifdef __cplusplus } #endif #endif /* HUBCTL_H */