固件 0.1.5~0.1.13-c5:刷机模式、多组 WiFi、热插拔初始化、总线锁误判、硬重启断 VBUS

- 0.1.5/0.1.6:刷机模式(flash_mode,最多 2 路,60 分钟可加钟)——窗口内顶高按 4450 mV 绝对值判、不断 VBUS
- 0.1.7/0.1.8:反灌断 VBUS 阈值放宽到 -300 mA(adb reboot bootloader 过渡实测 -208 mA)
- 0.1.9:WiFi 最多存 4 组,连不上轮换;NVS v1 自动迁移并双写(OTA 回滚仍能上网)
- 0.1.10:热插拔进来的刀片重新初始化 PCA9555 + INA226(否则 VBUS 常开、电流恒读 0)
- 0.1.11:i2cbus 嵌套检测改看本任务持锁(原全局标志让 key 随机回 busy、vbus 随机回 io)
- 0.1.12:硬重启先断 VBUS,开机校验通过 60 s 后再接回(否则假重启 / 误判没开机 / 进关机充电)
- 0.1.13:这一路被安全判据断电时一并退出刷机模式(事件 21 arg 254)

26 ctest 全绿;新增用例均先在旧代码上确认变红。板 B 已 OTA 到 0.1.13-c5 并实测。

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
这个提交包含在:
zhou yang
2026-09-29 08:16:21 +08:00
共同撰写人 Claude Opus 5.5
父节点 385d2f9c47
当前提交 9fb5bba367
共修改 35 个文件,包含 5120 行新增和 3675 行删除
+110
查看文件
@@ -1231,6 +1231,113 @@ static void test_boot_check_first_press_boots(void)
TEST_ASSERT_EQUAL_UINT32(0, sim_lock_violations(HAL_LOCK_I2C));
}
/* ⭐ 0.1.11(板 B 实测):VBUS 通着做硬重启 —— 手机靠 USB 撑着没断电、直供电流 ≈ 0 ⇒
校验判「没开起来」再按两下、断电时带着 USB 进关机充电。现在:断电前先断 VBUS,
整个开机 + 校验期间都断着,开机校验通过才接回。 */
static void test_hard_reset_cuts_vbus_first_and_restores_it_after_boot(void)
{
const int rail = 4;
s_pg_val[rail - 1] = true;
phone_up(1, false);
TEST_ASSERT_EQUAL_INT(HAL_OK, slot_vbus_enable(rail, true));
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
TEST_ASSERT_EQUAL_INT(0, pin(rail, PCA9555_BIT_VBUS_EN)); /* 断电之前就断了 */
run_phone_ms(rail, HR_TO_PRESS_MS + ACT_HR_BOOT_KEY_MS + 10);
TEST_ASSERT_EQUAL_INT(ACT_ST_HR_VERIFY, t_actuator_state(rail));
TEST_ASSERT_EQUAL_INT(0, pin(rail, PCA9555_BIT_VBUS_EN)); /* 校验期间也断着 */
for (int i = 0; i < 1000 && t_actuator_busy(rail); i++) {
run_phone_ms(rail, 10); /* 走到校验刚结束的那一刻,从这里起算 60 s */
}
TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail));
TEST_ASSERT_TRUE(s_boot_booted);
/* 开起来了,但**还不接回**:这时手机在 bootloader / 内核早期,关充电补丁没生效,
接回 USB 就被充到 4.4 V(板 B 实测 13 s 顶高断 VBUS)。执行器已空闲、不挡命令。 */
TEST_ASSERT_FALSE(t_actuator_busy(rail));
TEST_ASSERT_TRUE(t_actuator_vbus_back_pending(rail));
TEST_ASSERT_EQUAL_INT(0, pin(rail, PCA9555_BIT_VBUS_EN));
run_phone_ms(rail, ACT_HR_VBUS_BACK_MS - 1000);
TEST_ASSERT_EQUAL_INT(0, pin(rail, PCA9555_BIT_VBUS_EN));
run_phone_ms(rail, 1500);
TEST_ASSERT_EQUAL_INT(1, pin(rail, PCA9555_BIT_VBUS_EN)); /* 到点:原样接回 */
TEST_ASSERT_TRUE(slot_vbus_is_on(rail));
TEST_ASSERT_FALSE(t_actuator_vbus_back_pending(rail));
}
/* 等接回的这 60 s 里:有人手动动了 VBUS / 电,或者这一路被关了电 ⇒ 不再接回。 */
static void test_a_pending_vbus_restore_is_dropped_when_someone_else_acts(void)
{
const int rail = 1;
s_pg_val[rail - 1] = true;
/* ① 人手动断 VBUS(cmd 层会调 cancel)。 */
phone_up(1, false);
TEST_ASSERT_EQUAL_INT(HAL_OK, slot_vbus_enable(rail, true));
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
run_phone_ms(rail, HR_TO_PRESS_MS + ONE_TRY_MS + 100);
TEST_ASSERT_TRUE(t_actuator_vbus_back_pending(rail));
t_actuator_cancel_vbus_back(rail);
run_phone_ms(rail, ACT_HR_VBUS_BACK_MS + 1000);
TEST_ASSERT_EQUAL_INT(0, pin(rail, PCA9555_BIT_VBUS_EN));
/* ② 这一路在等的时候被关了电(安全判据 / 拔刀片):到点也不接。 */
phone_up(1, false);
TEST_ASSERT_EQUAL_INT(HAL_OK, slot_vbus_enable(rail, true));
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
run_phone_ms(rail, HR_TO_PRESS_MS + ONE_TRY_MS + 100);
TEST_ASSERT_TRUE(t_actuator_vbus_back_pending(rail));
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_efuse_enable(rail, false));
run_phone_ms(rail, ACT_HR_VBUS_BACK_MS + 1000);
TEST_ASSERT_EQUAL_INT(0, pin(rail, PCA9555_BIT_VBUS_EN));
TEST_ASSERT_FALSE(t_actuator_vbus_back_pending(rail));
/* ③ 再来一次硬重启:上一次排的接回作废(新的一次从头算)。 */
TEST_ASSERT_EQUAL_INT(HAL_OK, rail_efuse_enable(rail, true));
phone_up(1, false);
TEST_ASSERT_EQUAL_INT(HAL_OK, slot_vbus_enable(rail, true));
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
run_phone_ms(rail, HR_TO_PRESS_MS + ONE_TRY_MS + 100);
TEST_ASSERT_TRUE(t_actuator_vbus_back_pending(rail));
phone_up(1, false);
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail)); /* VBUS 这时是断的 */
TEST_ASSERT_FALSE(t_actuator_vbus_back_pending(rail));
run_phone_ms(rail, HR_TO_PRESS_MS + ONE_TRY_MS + ACT_HR_VBUS_BACK_MS + 1000);
TEST_ASSERT_EQUAL_INT(0, pin(rail, PCA9555_BIT_VBUS_EN));
}
/* 没开起来 / 被打断:VBUS **不接回** —— 接回去就是让一台没开机的手机进关机充电。
开始时本来就没通的,成功了也不许凭空打开。 */
static void test_hard_reset_leaves_vbus_off_unless_the_phone_booted(void)
{
const int rail = 2;
s_pg_val[rail - 1] = true;
phone_up(0, false);
TEST_ASSERT_EQUAL_INT(HAL_OK, slot_vbus_enable(rail, true));
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
run_phone_ms(rail, HR_TO_PRESS_MS + 3 * ONE_TRY_MS + 1000);
TEST_ASSERT_EQUAL_INT(ACT_RES_NO_BOOT, t_actuator_last_result(rail));
TEST_ASSERT_EQUAL_INT(0, pin(rail, PCA9555_BIT_VBUS_EN));
TEST_ASSERT_FALSE(slot_vbus_is_on(rail));
TEST_ASSERT_EQUAL_INT(HAL_OK, slot_vbus_enable(rail, true));
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail));
run_phone_ms(rail, ACT_HR_OFF_MS / 2);
t_actuator_abort(rail);
run_phone_ms(rail, ONE_TRY_MS);
TEST_ASSERT_EQUAL_INT(ACT_RES_ABORTED, t_actuator_last_result(rail));
TEST_ASSERT_EQUAL_INT(0, pin(rail, PCA9555_BIT_VBUS_EN));
const int r5 = 5;
s_pg_val[r5 - 1] = true;
phone_up(1, false);
TEST_ASSERT_FALSE(slot_vbus_is_on(r5));
TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(r5));
run_phone_ms(r5, HR_TO_PRESS_MS + ONE_TRY_MS + 1000);
TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(r5));
TEST_ASSERT_EQUAL_INT(0, pin(r5, PCA9555_BIT_VBUS_EN));
}
/* ⭐ 这次的主症状:第一下没压上,第二下开了。 */
static void test_boot_check_presses_again_when_the_phone_stays_off(void)
{
@@ -1375,6 +1482,9 @@ int main(void)
RUN_TEST(test_actuator_and_safety_share_one_lock_without_starving);
RUN_TEST(test_boot_check_first_press_boots);
RUN_TEST(test_hard_reset_cuts_vbus_first_and_restores_it_after_boot);
RUN_TEST(test_hard_reset_leaves_vbus_off_unless_the_phone_booted);
RUN_TEST(test_a_pending_vbus_restore_is_dropped_when_someone_else_acts);
RUN_TEST(test_boot_check_presses_again_when_the_phone_stays_off);
RUN_TEST(test_boot_check_gives_up_after_three_presses_and_keeps_power);
RUN_TEST(test_boot_check_does_not_press_again_when_current_is_unreadable);