固件 0.1.1-c5:硬重启后自动开机、MQTT 发布死锁、欠压放宽到 −250 mV

- 硬重启等到 PG 后长按电源键 5 s(新状态 hr-boot):电池端上电的小米8
  停在关机态,原来的 power_cycle 等于关机。ACT_KEY_MS_ON 2000 → 5000
  (实测 2 s 松手即掉电)。NO_PG 时不按。
- 修死锁:执行器持 s_mtx 等 esp-mqtt 的 api_lock,esp-mqtt 任务持 api_lock
  分发 DISCONNECTED 时等 s_mtx。热点一断 USB/WiFi 全失联、按键可能松不开。
  执行器现在只往 s_outbox 拷贝,由 t_link 交给 esp-mqtt。
- SR-5 欠压一侧 −100 → −250 mV(T_SAFETY_UNDER_MV),过压仍 +100 mV:
  带屏开机峰值 2.1 A 时实测 −115 mV 被误关。需求 SR-5 / SW-2 / SW-5 / 验收表同步。

板 B 上板:power_cycle → hr-boot → 手机开机,最低 3778 mV / 2.12 A 不再跳闸。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-09-28 01:13:58 +08:00
共同撰写人 Claude Opus 5.5
父节点 afc5e81299
当前提交 1f6862ac38
共修改 10 个文件,包含 211 行新增和 47 行删除
+29 -8
查看文件
@@ -296,6 +296,7 @@ static void test_thresholds_match_the_documents(void)
{
/* SR-5 / SW-2 的两个数,逐字来自需求 §9 那两行。 */
TEST_ASSERT_EQUAL_INT(100, T_SAFETY_DEV_MV);
TEST_ASSERT_EQUAL_INT(250, T_SAFETY_UNDER_MV); /* 欠压一侧,2026-09-28 放宽 */
TEST_ASSERT_EQUAL_INT(20, T_SAFETY_DEV_HOLD_MS);
/* 判据数的是**圈**,SR-5 写的是**毫秒** —— 这一条把两者钉在一起。
@@ -321,6 +322,7 @@ static void test_thresholds_match_the_documents(void)
/* §12.1 注入的是 ±150 mV / −60 mA,两个都必须落在阈值**外侧**,
否则验收测的是「阈值刚好卡在注入值上」。 */
TEST_ASSERT_TRUE(150 > T_SAFETY_DEV_MV);
TEST_ASSERT_TRUE(300 > T_SAFETY_UNDER_MV); /* 欠压注入 −300 mV */
TEST_ASSERT_TRUE(-60000 < T_SAFETY_REV_UA);
/* ⭐ R2 新增:mux 连续选不中多少次才算失联。
@@ -365,7 +367,7 @@ static void test_the_cut_lands_exactly_one_hold_window_after_first_sight(void)
{
sim_i2c_set_bus_time(false);
power_up(2);
inject(2, NOMINAL_MV - 150, 1000000);
inject(2, NOMINAL_MV - 300, 1000000);
const uint32_t t_first_sight = hal_time_now_ms();
beat();
@@ -409,7 +411,7 @@ static void test_the_cut_is_never_late_because_of_sub_millisecond_jitter(void)
static void test_undervoltage_cuts_the_rail_too(void)
{
power_up(3);
inject(3, NOMINAL_MV - 150, 1000000);
inject(3, NOMINAL_MV - 300, 1000000);
beats(2);
TEST_ASSERT_EQUAL_INT(0, en_level(3));
@@ -441,12 +443,28 @@ static void test_exactly_one_hundred_millivolts_is_still_in_range(void)
beats(10);
TEST_ASSERT_EQUAL_INT(1, en_level(1));
inject(1, NOMINAL_MV - T_SAFETY_DEV_MV, 1000000);
inject(1, NOMINAL_MV - T_SAFETY_UNDER_MV, 1000000);
beats(10);
TEST_ASSERT_EQUAL_INT(1, en_level(1));
TEST_ASSERT_EQUAL_INT(0, g_n_ev);
}
/* ⭐ 上板实测那一幕:手机开机峰值 2.1 A 时读回 −115 mV。放宽之前这里会关路,
把正在开机的手机直接断电。过压那一侧同样的偏差照旧要关。 */
static void test_boot_current_droop_does_not_cut_the_rail(void)
{
power_up(1);
inject(1, NOMINAL_MV - 115, 2136000);
beats(10);
TEST_ASSERT_EQUAL_INT(1, en_level(1));
TEST_ASSERT_EQUAL_INT(0, g_n_ev);
inject(1, NOMINAL_MV + 115, 1000000);
beats(3);
TEST_ASSERT_EQUAL_INT(0, en_level(1));
TEST_ASSERT_EQUAL_UINT8(SAFETY_EV_OVER_VOLT, g_ev[0].type);
}
/* 比的是**该路 DAC 的设定值**,不是 3900 这个常数。SW-4 允许单路微调,
拿常数比的话,一台被调到 4.010 V 的手机会被当成过压关掉 —— 而且只有那一路。 */
static void test_the_reference_is_the_setpoint_not_a_constant(void)
@@ -460,11 +478,13 @@ static void test_the_reference_is_the_setpoint_not_a_constant(void)
TEST_ASSERT_EQUAL_INT(1, en_level(1));
TEST_ASSERT_EQUAL_INT(0, g_n_ev);
/* 回到 3900:现在这才是「偏低 110 mV」。 */
/* 调到最低档,实测仍是 3900:拿设定值比才是「偏高」。
(原来是调到最高档再回 3900 判偏低 110 mV —— 欠压放宽到 −250 mV 之后那已经不越界了。) */
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_set_mv(1, RAIL_MV_MIN));
inject(1, NOMINAL_MV, 1000000);
beats(2);
TEST_ASSERT_EQUAL_INT(0, en_level(1));
TEST_ASSERT_EQUAL_UINT8(SAFETY_EV_UNDER_VOLT, g_ev[0].type);
TEST_ASSERT_EQUAL_UINT8(SAFETY_EV_OVER_VOLT, g_ev[0].type);
}
/* ---- 判据什么时候**不**武装(三道闸,每一道都是一次真事故的预防) -------- */
@@ -507,7 +527,7 @@ static void test_a_blind_rail_keeps_its_power(void)
/* ⚠️ 顺序是有讲究的:**先让坏值真的读进快照一次**,再让器件失联。
反过来写(先失联再改寄存器)的话快照里躺着的是个正常值,
「盲跑那一路照样判据」这个错就一点痕迹都不会留下 —— 测试永远绿。 */
inject(4, NOMINAL_MV - 150, 1000000);
inject(4, NOMINAL_MV - 300, 1000000);
beat();
TEST_ASSERT_EQUAL_INT(1, en_level(4)); /* 第一个样本只起表 */
sim_i2c_set_present_ch(ch_of(4), rail_cfg(4)->ina_addr, false);
@@ -517,7 +537,7 @@ static void test_a_blind_rail_keeps_its_power(void)
TEST_ASSERT_EQUAL_INT(0, g_n_ev);
TEST_ASSERT_FALSE(g_snap.rail_ok[3]);
/* 快照里躺着的确实是那个越界的旧值 —— 这是上面那句「顺序有讲究」的凭据。 */
TEST_ASSERT_EQUAL_INT32(NOMINAL_MV - 150, g_snap.rail[3].bus_mv);
TEST_ASSERT_EQUAL_INT32(NOMINAL_MV - 300, g_snap.rail[3].bus_mv);
/* 器件回来了,值还是坏的 —— 这时才该关,而且要重新数满 20 ms。 */
sim_i2c_set_present_ch(ch_of(4), rail_cfg(4)->ina_addr, true);
@@ -545,7 +565,7 @@ static void test_the_cut_drops_vbus_inside_the_lock_on_this_rails_channel(void)
const uint32_t en_seq_before = sim_gpio_write_seq(rail_cfg(rail)->efuse_en);
inject(rail, NOMINAL_MV - 150, 1000000); /* 欠压,不是反灌 */
inject(rail, NOMINAL_MV - 300, 1000000); /* 欠压,不是反灌 */
beat(); /* 第一圈只起表 */
TEST_ASSERT_EQUAL_UINT32(0, g_w[rail - 1].n);
@@ -1177,6 +1197,7 @@ int main(void)
RUN_TEST(test_undervoltage_cuts_the_rail_too);
RUN_TEST(test_a_one_cycle_glitch_does_not_cut_anything);
RUN_TEST(test_exactly_one_hundred_millivolts_is_still_in_range);
RUN_TEST(test_boot_current_droop_does_not_cut_the_rail);
RUN_TEST(test_the_reference_is_the_setpoint_not_a_constant);
RUN_TEST(test_a_rail_that_is_off_is_never_judged);