固件:首次上板修三处(Hub 复位 / 空槽不走总线 / 关 I²C 驱动日志)+ I²C 扫描工具
2026-09-27 底板上板(未插刀片)发现: - HUB_RST 只在上电复位(ESP_RST_POWERON)时脉冲。原先每次重启都脉冲, esptool 经 USB 复位芯片即把自己的 COM 口断掉,只能走 J33 手动下载。 实测:USB 复位(原因 11)后日志「不动 HUB_RST#」,COM 口全程不掉。 - t_safety 空槽不走总线。原先五路无条件读,空槽 NAK 每次十几 ms, 槽 1 就吃光 10 ms 整圈预算,槽 2~5 与主干输入侧 0x43 一圈都没读到。 新用例 test_empty_slots_are_never_touched_and_the_input_still_reads(去掉修复即红)。 - 关掉 i2c.master 驱动日志:NAK 日志在持锁期间同步打印,拖垮 20 ms 节拍。 失败仍计入 t_safety 的 i2c_err。 - 上板结果:空机 skipped=0 i2c_err=0 过载 0 最迟 0 ms。 tools/i2c_scan:独立的只读扫描固件(主干 + mux 8 通道,读身份寄存器,不写任何输出)。 实测主干 0x22/0x23/0x43/0x4A/0x4B/0x70 全在,另有 0x47 应答(推测 DAC63204 广播地址)。 Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
+22
-2
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "board_pins.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "evlog.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
@@ -204,9 +205,22 @@ static void task_log(void *arg)
|
||||
⚠️ 这一下放在 `rail_en_init()` 之后、其它一切之前:Hub 复位会让 USB 重新枚举,
|
||||
期间串口日志是丢的。越早做,后面越多的启动日志能被电脑收到。
|
||||
失败**不 abort**:Hub 没起来只影响调试与手机的数据口,五路供电照常
|
||||
(§10.2:失联 ≠ 断电)。 */
|
||||
(§10.2:失联 ≠ 断电)。
|
||||
|
||||
⛔ **只在上电复位(`ESP_RST_POWERON`)时脉冲**(2026-09-27 上板改)。
|
||||
原来是「每次启动都脉冲」,而 ESP32 的每一次重启都算启动:esptool 经 USB 复位芯片、
|
||||
崩溃 / 看门狗重启、`esp_restart()` —— 每一次都把 Hub 复位、把自己的 COM 口拔掉。
|
||||
实测后果:板上一旦有这份固件,`esptool --before usb_reset` 连三次全断
|
||||
(`ClearCommError PermissionError 13`),只能走 J33 手动进下载模式。
|
||||
上电复位时 Hub 自己也刚上电,脉冲没有代价;J33 的 EN 脚复位在 S3 上也报 POWERON,
|
||||
应急路径不受影响。其余原因一律不碰 `HUB_RST#`(此时它仍是复位态的高阻,靠 R716 拉高)。 */
|
||||
static void hub_reset_pulse_once(void)
|
||||
{
|
||||
const esp_reset_reason_t why = esp_reset_reason();
|
||||
if (why != ESP_RST_POWERON) {
|
||||
ESP_LOGI(TAG, "复位原因 %d 不是上电复位 —— 不动 HUB_RST#(保住 USB 控制台)", (int)why);
|
||||
return;
|
||||
}
|
||||
hal_err_t err = hal_gpio_config(BOARD_PIN_HUB_RST_N, HAL_GPIO_OUTPUT);
|
||||
if (err != HAL_OK) {
|
||||
ESP_LOGE(TAG, "HUB_RST# 配置失败:%s —— 跳过复位脉冲", hal_err_str(err));
|
||||
@@ -233,6 +247,12 @@ void app_main(void)
|
||||
ESP_LOGI(TAG, "PhoneRail %s boot, %d rails, t=%u ms", phonerail_version(),
|
||||
PHONERAIL_RAIL_COUNT, (unsigned)hal_time_now_ms());
|
||||
|
||||
/* ⛔ 关掉 IDF I²C 驱动自己的错误日志(2026-09-27 上板实测)。它每次 NAK 打三行 E 级日志,
|
||||
打印是同步的、发生在**我们持着总线锁的时候**,一次 NAK 就吃掉十几 ms ——
|
||||
20 ms 的巡检节拍直接被它的日志拖垮,而且刷屏把真正有用的启动日志全冲掉。
|
||||
失败并没有丢:每一次都进 `t_safety` 的 i2c_err 计数,每 10 s 汇总打印一次。 */
|
||||
esp_log_level_set("i2c.master", ESP_LOG_NONE);
|
||||
|
||||
selftest_or_die();
|
||||
|
||||
/* ⭐ R2:**五根** `EFUSE_EN` 配成输出并全部拉低(R1 是十根:另外五根 `VBUS_EN`
|
||||
@@ -249,7 +269,7 @@ void app_main(void)
|
||||
真正"固件说了算"要等到下面的 `slot_init()`。 */
|
||||
ESP_LOGI(TAG, "五路 eFuse 使能已全部拉低(VBUS 此刻靠下拉电阻关着)");
|
||||
|
||||
/* USB Hub 复位脉冲 —— 只此一次,见函数头上那一段。 */
|
||||
/* USB Hub 复位脉冲 —— 只在上电复位时、只此一次,见函数头上那一段。 */
|
||||
hub_reset_pulse_once();
|
||||
|
||||
/* 锁必须早于任何一个用总线的任务。
|
||||
|
||||
@@ -50,7 +50,7 @@ extern "C" {
|
||||
/* USB2517I 的复位,上拉到 +3V3_HUB ⇒ 低有效。
|
||||
⛔⛔ **运行期一个字都不许写它**(R2 D13 代价 2):ESP32 自己的控制台与烧录口
|
||||
就挂在这个 Hub 的第 6 个下行口上,误复位 = 自断控制台,而且下一次烧录也进不来。
|
||||
只在启动自检时脉冲一次;真出事的退路是 J33 那个 5P 应急串口。 */
|
||||
只在**上电复位**时脉冲一次(其余复位原因不碰,见 app_main.c);真出事的退路是 J33 那个 5P 应急串口。 */
|
||||
#define BOARD_PIN_HUB_RST_N 8 /* 模组脚 12 → U32.43 */
|
||||
|
||||
#define BOARD_PIN_UP_VBUS_DET 6 /* 模组脚 6 → R721/R722 分压,上游 USB-C 插没插 */
|
||||
|
||||
@@ -330,8 +330,20 @@ hal_err_t t_safety_step(safety_snap_t *s)
|
||||
返回 0 表示预算用完了 —— 剩下的路这一圈不读了。 */
|
||||
const uint32_t t0 = now;
|
||||
|
||||
/* ⛔ 空槽**不走总线**(2026-09-27 上板实测)。原来五路无条件各读一次:空槽的 INA226 /
|
||||
PCA9555 必然 NAK,而 IDF 驱动每次 NAK 要十几 ms(含它自己的错误日志),
|
||||
槽 1 那两次就把 10 ms 整圈预算吃光 ⇒ 槽 2~5 **和主干上的输入侧 0x43** 一圈都没读到,
|
||||
现象是 `txns=2`、每圈 skipped、「最迟 57 ms」—— 空机时输入侧的监控整个是瞎的。
|
||||
在位脚是纯 GPIO,先 poll 再按它挑。`check_present()` 在后面照旧再 poll 一次、照旧负责断电。 */
|
||||
slot_prsnt_poll();
|
||||
|
||||
/* 五路,一路一次 `xact`(通道查表,**不许算** —— rail_cfg.h 那条禁令)。 */
|
||||
for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) {
|
||||
if (!slot_present(rail)) {
|
||||
s->rail_ok[rail - 1] = false; /* 没读 ≠ 读到了:别让上一圈的旧值冒充新鲜 */
|
||||
s->io_ok[rail - 1] = false;
|
||||
continue;
|
||||
}
|
||||
const uint32_t budget = lock_budget_left(t0);
|
||||
if (budget == 0) {
|
||||
skipped = true; /* 整圈预算用完:剩下的路本圈作废,下一拍重来 */
|
||||
|
||||
在新工单中引用
屏蔽一个用户