固件:首次上板修三处(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]>
这个提交包含在:
@@ -507,6 +507,42 @@ static void test_one_dead_ina226_does_not_stop_the_other_four(void)
|
||||
g_snap.bus_us);
|
||||
}
|
||||
|
||||
/* 空槽**一个字节也不许发**(2026-09-27 上板实测)。空机时五路无条件各读一次,
|
||||
真板上每次 NAK 要十几 ms,槽 1 两次就把整圈预算吃光 —— 槽 2~5 和主干的 0x43
|
||||
一圈都没读到,输入侧监控整个是瞎的。这里把空槽的器件也拔掉:谁碰了它就会 NAK、
|
||||
`i2c_errors` 立刻非零;全空那一圈还要求主干那片照读不误。 */
|
||||
static void test_empty_slots_are_never_touched_and_the_input_still_reads(void)
|
||||
{
|
||||
const int empty[] = { 2, 4 };
|
||||
for (unsigned i = 0; i < sizeof(empty) / sizeof(empty[0]); i++) {
|
||||
const int rail = empty[i];
|
||||
sim_gpio_drive(rail_cfg(rail)->prsnt_n, 1); /* 高 = 不在位 */
|
||||
sim_i2c_set_present_ch(ch_of(rail), rail_cfg(rail)->ina_addr, false);
|
||||
sim_i2c_set_present_ch(ch_of(rail), PCA9555_ADDR, false);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, t_safety_step(&g_snap));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, g_snap.i2c_errors);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, g_snap.skipped);
|
||||
for (int rail = 1; rail <= N; rail++) {
|
||||
const bool here = (rail != 2 && rail != 4);
|
||||
TEST_ASSERT_EQUAL(here, g_snap.rail_ok[rail - 1]);
|
||||
TEST_ASSERT_EQUAL(here, g_snap.io_ok[rail - 1]);
|
||||
TEST_ASSERT_EQUAL(here, g_snap.present[rail - 1]);
|
||||
}
|
||||
TEST_ASSERT_TRUE(g_snap.input_ok);
|
||||
|
||||
/* 五槽全空(上板时的真实状态):总线上只剩主干那一片。 */
|
||||
for (int rail = 1; rail <= N; rail++) {
|
||||
sim_gpio_drive(rail_cfg(rail)->prsnt_n, 1);
|
||||
sim_i2c_set_present_ch(ch_of(rail), rail_cfg(rail)->ina_addr, false);
|
||||
sim_i2c_set_present_ch(ch_of(rail), PCA9555_ADDR, false);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT(HAL_OK, t_safety_step(&g_snap));
|
||||
TEST_ASSERT_EQUAL_UINT32(0, g_snap.i2c_errors);
|
||||
TEST_ASSERT_TRUE(g_snap.input_ok);
|
||||
TEST_ASSERT_EQUAL_INT32(12000, g_snap.input.bus_mv);
|
||||
}
|
||||
|
||||
/* 读失败时保留**上一圈的值**并把 ok 标成 false —— 不清零。
|
||||
清零的话,判据会看到一个 0 V 的「正常数」;而 `ok` 正是让它先看一眼
|
||||
「这数是不是新的」。拿一个没更新的电压去判过压是最难查的那种错。 */
|
||||
@@ -615,6 +651,7 @@ int main(void)
|
||||
RUN_TEST(test_a_busy_bus_skips_the_cycle_instead_of_waiting_forever);
|
||||
|
||||
RUN_TEST(test_one_dead_ina226_does_not_stop_the_other_four);
|
||||
RUN_TEST(test_empty_slots_are_never_touched_and_the_input_still_reads);
|
||||
RUN_TEST(test_a_failed_read_keeps_the_stale_value_and_flags_it);
|
||||
RUN_TEST(test_a_dead_pca9555_does_not_spoil_the_same_rails_ina226);
|
||||
RUN_TEST(test_the_beat_survives_a_dead_blade);
|
||||
|
||||
在新工单中引用
屏蔽一个用户