固件 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 行删除
+61 -10
查看文件
@@ -278,15 +278,30 @@ static void test_hard_reset_walks_the_full_power_up_sequence(void)
run_ms(1);
TEST_ASSERT_EQUAL_UINT32(ACT_HR_RAMP_MS, hal_time_now_ms() - t_on);
/* ⭐ PG 到了 ⇒ 当场按下电源键开机。电池端上电的手机停在关机态,
不按这一下的硬重启等于「关机」(小米8 实测,t_actuator.h)。 */
TEST_ASSERT_EQUAL_INT(ACT_ST_HR_BOOT, t_actuator_state(rail));
TEST_ASSERT_EQUAL_INT(1, key_pin(rail, PCA9555_KEY_PWR));
TEST_ASSERT_EQUAL_INT(0, key_pin(rail, PCA9555_KEY_VUP));
TEST_ASSERT_EQUAL_INT(0, key_pin(rail, PCA9555_KEY_VDN));
run_ms(ACT_HR_BOOT_KEY_MS - 1);
TEST_ASSERT_EQUAL_INT(1, key_pin(rail, PCA9555_KEY_PWR));
TEST_ASSERT_TRUE(t_actuator_busy(rail));
run_ms(1);
TEST_ASSERT_EQUAL_INT(0, key_pin(rail, PCA9555_KEY_PWR)); /* 松开 */
TEST_ASSERT_FALSE(t_actuator_busy(rail));
TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail));
TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(en));
TEST_ASSERT_EQUAL_UINT32(1, t_actuator_done(rail));
/* ⭐ 硬重启在总线上什么都不该发:键本来就没按着 ⇒ 影子不脏 ⇒ 一个事务都不发。
R2 里这条比 R1 更要紧 —— 每一次多余的事务都是从巡检嘴里抢锁。 */
TEST_ASSERT_EQUAL_UINT32(takes0, sim_lock_takes(HAL_LOCK_I2C));
assert_only_this_blade_moved(0, l0, w0); /* 0 = 五片一个都不许动 */
/* ⭐ 总线上只有按下、松开这两次事务,而且只动这一片。断电那一段一个都不发:
键本来就没按着 ⇒ 影子不脏。R2 里每一次多余的事务都是从巡检嘴里抢锁。 */
TEST_ASSERT_EQUAL_UINT32(takes0 + 2, sim_lock_takes(HAL_LOCK_I2C));
assert_only_this_blade_moved(rail, l0, w0);
TEST_ASSERT_EQUAL_HEX8(l0[rail - 1], latch(rail)); /* 收工后这一片回到原样 */
TEST_ASSERT_EQUAL_UINT32(0, sim_lock_violations(HAL_LOCK_I2C));
}
@@ -301,7 +316,7 @@ static void test_hard_reset_touches_only_its_own_en_pin(void)
}
s_pg_val[rail - 1] = true;
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
run_ms(ACT_HR_OFF_MS + ACT_HR_RAMP_MS);
run_ms(ACT_HR_OFF_MS + ACT_HR_RAMP_MS + ACT_HR_BOOT_KEY_MS);
TEST_ASSERT_FALSE(t_actuator_busy(rail));
for (int r = 1; r <= N; r++) {
@@ -332,6 +347,8 @@ static void test_no_pg_leaves_the_rail_powered(void)
TEST_ASSERT_FALSE(t_actuator_busy(rail));
TEST_ASSERT_EQUAL_INT(ACT_RES_NO_PG, t_actuator_last_result(rail));
TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(en)); /* ⭐ 不许再关掉 */
/* 开机键也不按:键和 PG 在同一片 PCA9555 上,读不到 PG 时去按只会卡在 CLEANUP。 */
TEST_ASSERT_EQUAL_INT(0, key_pin(rail, PCA9555_KEY_PWR));
}
/* 读得到、但一直是低(电源确实没好)走的是同一条路:照样不自作主张断电。 */
@@ -360,6 +377,8 @@ static void test_late_pg_is_still_accepted(void)
s_pg_val[rail - 1] = true;
run_ms(ACT_HR_PG_POLL_MS);
TEST_ASSERT_EQUAL_INT(ACT_ST_HR_BOOT, t_actuator_state(rail));
run_ms(ACT_HR_BOOT_KEY_MS);
TEST_ASSERT_FALSE(t_actuator_busy(rail));
TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail));
}
@@ -385,8 +404,8 @@ static void test_the_task_loop_only_sleeps_what_step_asks_for(void)
for (int i = 0; i < 10000; i++) {
const uint32_t w = t_actuator_step(); /* 任务体:它说睡多久就睡多久 */
if (!t_actuator_busy(rail)) {
total = hal_time_now_ms() - t0; /* 收工的**那一刻**,不是下一次醒来 */
if (t_actuator_state(rail) == ACT_ST_HR_BOOT) {
total = hal_time_now_ms() - t0; /* 等到 PG 的**那一刻**,不是下一次醒来 */
break;
}
sim_time_advance_ms(w == 0 ? 1 : w);
@@ -396,9 +415,18 @@ static void test_the_task_loop_only_sleeps_what_step_asks_for(void)
}
}
TEST_ASSERT_TRUE(pg_raised);
TEST_ASSERT_TRUE(total > 0 && total <= pg_at + 2 * ACT_HR_PG_POLL_MS);
/* 按键保持期也照样听 step 的:睡到点就松键收工,不多睡一整段。 */
for (int i = 0; i < 10000 && t_actuator_busy(rail); i++) {
const uint32_t w = t_actuator_step();
if (t_actuator_busy(rail)) {
sim_time_advance_ms(w == 0 ? 1 : w);
}
}
TEST_ASSERT_FALSE(t_actuator_busy(rail));
TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail));
TEST_ASSERT_TRUE(total <= pg_at + 2 * ACT_HR_PG_POLL_MS);
TEST_ASSERT_TRUE(hal_time_now_ms() - t0 <= total + ACT_HR_BOOT_KEY_MS + ACT_CLEANUP_RETRY_MS);
}
/* 硬重启之前先把这一路的三个键松开:断电重启时有键按着,手机会开进
@@ -544,7 +572,7 @@ static void test_the_key_hold_never_holds_the_i2c_lock(void)
TEST_ASSERT_EQUAL_INT(-1, sim_lock_owner(HAL_LOCK_I2C)); /* 写完当场就放掉 */
TEST_ASSERT_FALSE(hal_lock_held(HAL_LOCK_I2C));
for (int i = 0; i < 120; i++) {
for (int i = 0; i < (int)(ACT_KEY_MS_ON / 20) + 20; i++) {
(void)t_actuator_step();
TEST_ASSERT_EQUAL_INT(-1, sim_lock_owner(HAL_LOCK_I2C));
TEST_ASSERT_FALSE(hal_lock_held(HAL_LOCK_I2C));
@@ -836,6 +864,26 @@ static void test_abort_during_hard_reset_does_not_restore_power(void)
TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(en)); /* 还是关着的,等上层拿主意 */
}
/* 开机键按着的时候被打断(过压关路走的就是这条):键当场松开,电不动。
键留在按下状态的现象是手机再也起不来,而遥测上一切正常。 */
static void test_abort_during_boot_press_releases_the_key(void)
{
const int rail = 2;
const int en = rail_cfg(rail)->efuse_en;
s_pg_val[rail - 1] = true;
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
run_ms(ACT_HR_OFF_MS + ACT_HR_RAMP_MS + 1000);
TEST_ASSERT_EQUAL_INT(ACT_ST_HR_BOOT, t_actuator_state(rail));
TEST_ASSERT_EQUAL_INT(1, key_pin(rail, PCA9555_KEY_PWR));
t_actuator_abort(rail);
TEST_ASSERT_FALSE(t_actuator_busy(rail));
TEST_ASSERT_EQUAL_INT(ACT_RES_ABORTED, t_actuator_last_result(rail));
TEST_ASSERT_EQUAL_INT(0, key_pin(rail, PCA9555_KEY_PWR));
TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(en));
}
/* EDL 跑到「已插 USB」那一段被打断:`EDL_EN` 立刻松(那是本序列按下去的),
**`VBUS_EN` 一动不动** —— R2 里它和按键在同一个字节里,一不小心就会被
收尾那次读改写顺手清掉,而现象是「手机莫名其妙从 9008 掉线」。 */
@@ -972,7 +1020,9 @@ static void test_timings_match_the_documents(void)
TEST_ASSERT_TRUE(ACT_HR_PG_POLL_MS > 0 && ACT_HR_PG_POLL_MS * 10 <= ACT_HR_PG_TIMEOUT_MS);
TEST_ASSERT_EQUAL_UINT32(100, ACT_KEY_MS_TAP); /* SW-5 三档 */
TEST_ASSERT_EQUAL_UINT32(2000, ACT_KEY_MS_ON);
TEST_ASSERT_EQUAL_UINT32(5000, ACT_KEY_MS_ON); /* 小米8 实测 2 s 不够(t_actuator.h) */
TEST_ASSERT_TRUE(ACT_KEY_MS_ON < ACT_KEY_MS_FORCE); /* 开机那一下不能按成强制重启 */
TEST_ASSERT_EQUAL_UINT32(ACT_KEY_MS_ON, ACT_HR_BOOT_KEY_MS);
TEST_ASSERT_EQUAL_UINT32(10000, ACT_KEY_MS_FORCE);
TEST_ASSERT_TRUE(ACT_KEY_MS_FORCE <= ACT_KEY_MAX_MS); /* 最长那一档不能被自己的上限拦掉 */
@@ -1075,6 +1125,7 @@ int main(void)
RUN_TEST(test_a_failed_press_still_goes_through_cleanup);
RUN_TEST(test_abort_releases_the_key_and_leaves_the_pins_alone);
RUN_TEST(test_abort_during_hard_reset_does_not_restore_power);
RUN_TEST(test_abort_during_boot_press_releases_the_key);
RUN_TEST(test_abort_during_edl_releases_the_short_but_keeps_vbus);
RUN_TEST(test_actuator_and_safety_share_one_lock_without_starving);