固件 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 行删除
+31
查看文件
@@ -65,6 +65,19 @@ static hal_err_t cb_recover_inside(void *arg)
return HAL_OK;
}
/* 事务进行中,**另一个任务**也来要总线(sim 的假任务号 1 扮演执行器)。 */
static hal_err_t cb_other_task(void *arg)
{
ctx_t *c = (ctx_t *)arg;
c->calls++;
static ctx_t inner_ctx;
inner_ctx.addr = BLADE_INA;
sim_lock_set_task(1);
c->inner = i2cbus_xact(1, 5, cb_read_mv, &inner_ctx);
sim_lock_set_task(0);
return HAL_OK;
}
/* 在事务里再调一次 `i2cbus_xact()` —— 头文件末尾那条警告。 */
static hal_err_t cb_nested(void *arg)
{
@@ -444,6 +457,23 @@ static void test_recover_is_rejected_inside_a_transaction(void)
不拦的话会在同一个任务里第二次 take 同一把锁 —— sim 上是 HAL_ERR_STATE,
FreeRTOS 上(非递归的 mutex)就是那个任务当场停住,
现象是 `t_safety` 整个不跑了,而看门狗之外没有任何日志。 */
/* ⭐ 0.1.11:别的任务正持着锁 ≠ 嵌套。它该**等锁**(超时回 TIMEOUT),而不是回 STATE。
原来的全局标志把两者混为一谈,执行器撞上巡检就回 STATE ⇒ `key` 随机 busy、`vbus` 随机 io。 */
static void test_another_tasks_xact_waits_instead_of_looking_nested(void)
{
mount_ready();
ctx_t c = { BLADE_INA, 0, -1, HAL_OK };
TEST_ASSERT_EQUAL_INT(HAL_OK, i2cbus_xact(0, 10, cb_other_task, &c));
TEST_ASSERT_EQUAL_UINT32(1, c.calls);
TEST_ASSERT_EQUAL_INT(HAL_ERR_TIMEOUT, c.inner);
TEST_ASSERT_FALSE(hal_lock_held(HAL_LOCK_I2C));
/* 锁放了之后,那个任务再来就拿得到。 */
sim_lock_set_task(1);
ctx_t d = { BLADE_INA, 0, -1, HAL_OK };
TEST_ASSERT_EQUAL_INT(HAL_OK, i2cbus_xact(1, 5, cb_read_mv, &d));
sim_lock_set_task(0);
}
static void test_a_nested_xact_is_rejected_and_does_not_deadlock(void)
{
mount_ready();
@@ -604,6 +634,7 @@ int main(void)
RUN_TEST(test_recover_pulses_mux_rst_and_forgets_the_channel);
RUN_TEST(test_recover_is_rejected_inside_a_transaction);
RUN_TEST(test_a_nested_xact_is_rejected_and_does_not_deadlock);
RUN_TEST(test_another_tasks_xact_waits_instead_of_looking_nested);
RUN_TEST(test_a_lock_timeout_changes_nothing_at_all);
RUN_TEST(test_xact_rejects_bad_arguments_before_taking_the_lock);
RUN_TEST(test_forget_channel_only_drops_the_shadow);