固件 0.1.4-c5:整片刀片不应答(INA226 + PCA9555 同时 NAK 200 ms)⇒ 关这一路

- t_safety:eFuse 开着、本路两颗都 NAK 连续 10 圈 ⇒ abort + 关 eFuse,新事件 20 BLADE_MUTE;
  只关 eFuse,不再写刚 NAK 过的 PCA9555。只有一颗不答应仍按 §10.10 盲跑、保持供电
- 起因:板 B 第 2 槽 PRSNT2# 卡低,刀片挪走后空槽 eFuse 开了约 4 h,原判据一条没响
- 需求 §10.10 加一行 + 表后第 3 条;rack 事件 20 命名并标红
- 宿主机 26 ctest 全绿(test_t_safety_rules +4 例)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-09-28 09:07:39 +08:00
共同撰写人 Claude Opus 5.5
父节点 ef91daf07d
当前提交 385d2f9c47
共修改 11 个文件,包含 1275 行新增和 1105 行删除
+86
查看文件
@@ -133,6 +133,7 @@ static void catch_event(void *ctx, const safety_event_t *ev)
case SAFETY_EV_OVER_VOLT:
case SAFETY_EV_UNDER_VOLT:
case SAFETY_EV_SLOT_EMPTY:
case SAFETY_EV_BLADE_MUTE:
if (rail_efuse_is_on(ev->rail)) {
g_ev_before_action = true; /* 记这条时电早该断了 */
}
@@ -336,6 +337,11 @@ static void test_thresholds_match_the_documents(void)
已经落盘的旧记录改变含义(`t_safety.h` 那句「flash 格式」)。 */
TEST_ASSERT_EQUAL_INT(SAFETY_EV_PUSHED_UP + 1, SAFETY_EV_MUX_LOST);
TEST_ASSERT_EQUAL_INT(SAFETY_EV_MUX_LOST + 1, SAFETY_EV_SLOT_EMPTY);
TEST_ASSERT_EQUAL_INT(SAFETY_EV_SLOT_EMPTY + 1, SAFETY_EV_BLADE_MUTE);
/* 0.1.4 刀片不应答:连续 10 圈 = 200 ms。必须 > 1(一圈毛刺不许关一台手机)。 */
TEST_ASSERT_EQUAL_INT(10, T_SAFETY_BLADE_MUTE_N);
TEST_ASSERT_EQUAL_UINT32(200u, T_SAFETY_BLADE_MUTE_N * task_desc(TASK_SAFETY)->period_ms);
}
/* ---- 验收主项一:注入过压 → 20 ms 内关路并记事件(SR-5) ----------------- */
@@ -547,6 +553,82 @@ static void test_a_blind_rail_keeps_its_power(void)
TEST_ASSERT_EQUAL_INT(0, en_level(4));
}
/* ---- 0.1.4:整片刀片不应答 ⇒ 关路(§10.10 新增那一行) ------------------ */
static void mute_blade(int rail, bool mute)
{
sim_i2c_set_present_ch(ch_of(rail), rail_cfg(rail)->ina_addr, !mute);
sim_i2c_set_present_ch(ch_of(rail), PCA9555_ADDR, !mute);
}
/* 2026-09-28 板 B 第 2 槽的现场:`PRSNT#` 卡低(假在位)、刀片已经拔走、eFuse 还开着。
两颗都不答应满 10 圈 ⇒ 关这一路、记一条 BLADE_MUTE;别的路一根线都不动。 */
static void test_a_mute_blade_is_cut_after_ten_cycles(void)
{
power_up(2);
power_up(3);
const uint32_t other = en_writes(3);
const uint32_t out0 = g_w[1].n;
mute_blade(2, true); /* PRSNT2# 仍是低:slot_present(2) 照旧为真 */
beats(T_SAFETY_BLADE_MUTE_N - 1);
TEST_ASSERT_EQUAL_INT(1, en_level(2)); /* 9 圈:还没到 */
TEST_ASSERT_EQUAL_INT(0, g_n_ev);
beat();
TEST_ASSERT_EQUAL_INT(0, en_level(2)); /* 第 10 圈:关 */
TEST_ASSERT_FALSE(rail_efuse_is_on(2));
TEST_ASSERT_EQUAL_INT(1, g_n_ev);
TEST_ASSERT_EQUAL_INT(SAFETY_EV_BLADE_MUTE, g_ev[0].type);
TEST_ASSERT_EQUAL_INT(2, g_ev[0].rail);
/* 没有去写那片刚 NAK 过的 PCA9555(断 VBUS 那一写只会白等超时) */
TEST_ASSERT_EQUAL_UINT32(out0, g_w[1].n);
/* 第 3 路一根线都没动 */
TEST_ASSERT_EQUAL_INT(1, en_level(3));
TEST_ASSERT_EQUAL_UINT32(other, en_writes(3));
beats(50); /* 关掉之后不再刷事件 */
TEST_ASSERT_EQUAL_INT(1, g_n_ev);
}
/* 只差一圈就恢复了:从头数,不关。 */
static void test_a_blade_that_answers_again_restarts_the_count(void)
{
power_up(2);
mute_blade(2, true);
beats(T_SAFETY_BLADE_MUTE_N - 1);
mute_blade(2, false);
beat();
mute_blade(2, true);
beats(T_SAFETY_BLADE_MUTE_N - 1);
TEST_ASSERT_EQUAL_INT(1, en_level(2));
TEST_ASSERT_EQUAL_INT(0, g_n_ev);
beat();
TEST_ASSERT_EQUAL_INT(0, en_level(2));
}
/* §10.10 原有的两行不变:只有一颗不答应 ⇒ 盲跑、保持供电。 */
static void test_one_mute_chip_on_the_blade_still_keeps_power(void)
{
power_up(2);
sim_i2c_set_present_ch(ch_of(2), PCA9555_ADDR, false); /* 只有 PCA9555 不答应 */
beats(50);
TEST_ASSERT_EQUAL_INT(1, en_level(2));
sim_i2c_set_present_ch(ch_of(2), PCA9555_ADDR, true);
sim_i2c_set_present_ch(ch_of(2), rail_cfg(2)->ina_addr, false); /* 只有 INA226 不答应 */
beats(50);
TEST_ASSERT_EQUAL_INT(1, en_level(2));
TEST_ASSERT_EQUAL_INT(0, g_n_ev);
}
/* 关着的路不数:空槽假在位、没人开它,不该每 200 ms 刷一条事件。 */
static void test_a_mute_blade_that_is_off_is_left_alone(void)
{
mute_blade(2, true);
beats(50);
TEST_ASSERT_EQUAL_INT(0, en_level(2));
TEST_ASSERT_EQUAL_INT(0, g_n_ev);
}
/* ---- ⭐ 关路这件事本身:R2 的顺序与「写给了哪一片」 ---------------------- */
/* ⭐⭐ 本包最要紧的一条。关路 = **先断 VBUS(锁内、本路通道上)再关 eFuse(锁外)**。
@@ -1203,6 +1285,10 @@ int main(void)
RUN_TEST(test_a_rail_that_is_off_is_never_judged);
RUN_TEST(test_the_soft_start_window_is_not_judged);
RUN_TEST(test_a_blind_rail_keeps_its_power);
RUN_TEST(test_a_mute_blade_is_cut_after_ten_cycles);
RUN_TEST(test_a_blade_that_answers_again_restarts_the_count);
RUN_TEST(test_one_mute_chip_on_the_blade_still_keeps_power);
RUN_TEST(test_a_mute_blade_that_is_off_is_left_alone);
RUN_TEST(test_the_cut_drops_vbus_inside_the_lock_on_this_rails_channel);
RUN_TEST(test_only_the_offending_rail_is_touched);