固件 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 行删除
+107
查看文件
@@ -5,8 +5,10 @@
判重、名字表、回应拼装在 cmd.c;status / events 的正文在 cmd_report.c。 */
#include <string.h>
#include "chgmode.h"
#include "cmd_priv.h"
#include "evlog.h"
#include "hal_time.h"
#include "i2cbus.h"
#include "rail.h"
#include "rail_cfg.h"
@@ -209,6 +211,7 @@ cmd_err_t do_vbus(ctx_t *c)
if (on && !rail_efuse_is_on(c->rail)) {
return reject(c, CMD_ERR_STATE, 1);
}
t_actuator_cancel_vbus_back(c->rail); /* 人动了 VBUS:硬重启排的「过会儿接回」作废 */
return from_bus(slot_vbus_enable(c->rail, on));
}
@@ -234,6 +237,7 @@ cmd_err_t do_power(ctx_t *c)
if (t_actuator_busy(c->rail)) {
return CMD_ERR_BUSY; /* 硬重启走到一半再开关一次是自作主张(t_actuator_abort 同理) */
}
t_actuator_cancel_vbus_back(c->rail); /* 人动了电:硬重启排的「过会儿接回 VBUS」作废 */
if (on) {
const hal_err_t ee = rail_efuse_enable(c->rail, true);
if (ee == HAL_OK) {
@@ -248,10 +252,113 @@ cmd_err_t do_power(ctx_t *c)
const hal_err_t ee = rail_efuse_enable(c->rail, false);
if (ee == HAL_OK) {
note_intent(c->rail, false); /* 直供确实断了:下次开机也别开它 */
/* 这一路停了,刷机模式也一并退出(不占 CHG_MODE_MAX_RAILS 的名额)。 */
if (chgmode_active(c->rail, hal_time_now_ms())) {
(void)chgmode_set(c->rail, 0, hal_time_now_ms());
evlog_put((uint8_t)c->rail, LOG_EV_CHG_MODE, CHG_EV_OFF, now_mv(c->rail), now_ua(c->rail));
}
}
return from_bus(ee != HAL_OK ? ee : ev);
}
/* 0.1.5 刷机模式(chgmode.h):`{"cmd":"flash_mode","rail":1,"on":true,"min":60}`。
min 省略 = CHG_MODE_DEFAULT_MIN;关不带 min。回应带 `"left":秒`。
0.1.6 加钟:`{"cmd":"flash_mode","rail":1,"add":30}`(与 on / min 互斥)—— 只给开着的窗口加,
剩余最多 CHG_MODE_MAX_MIN 分钟(超出截断);没开着回 state。
⚠️ 只改判据,**不动 VBUS**:开 VBUS 仍用 `vbus` 命令(顺序无所谓:先开模式再开 VBUS,
或 VBUS 被 SW-8 断了之后开模式再重开 VBUS)。 */
cmd_err_t do_flash_mode(ctx_t *c)
{
cmd_err_t e = need_rail(c);
if (e != CMD_OK) {
return e;
}
/* 加钟(0.1.6):`{"cmd":"flash_mode","rail":1,"add":30}`,与 on 二选一。 */
int64_t add = 0;
const int ga = cj_get_int(c->o, "add", &add);
if (ga < 0) {
return CMD_ERR_ARG;
}
if (ga == 1) {
bool on_probe = false;
int64_t min_probe = 0;
if (cj_get_bool(c->o, "on", &on_probe) != 0 || cj_get_int(c->o, "min", &min_probe) != 0) {
return CMD_ERR_ARG; /* add 不和 on / min 混用 */
}
if (add < 1 || add > CHG_MODE_MAX_MIN) {
return reject(c, CMD_ERR_RANGE, add);
}
const uint32_t now = hal_time_now_ms();
const hal_err_t he = chgmode_extend(c->rail, (uint32_t)add, now);
if (he == HAL_ERR_STATE) {
return reject(c, CMD_ERR_STATE, 0); /* 没开着:要重新开,不是加钟 */
}
if (he != HAL_OK) {
return reject(c, CMD_ERR_RANGE, add);
}
evlog_put((uint8_t)c->rail, LOG_EV_CHG_MODE, (uint8_t)(CHG_EV_EXTEND | (uint32_t)add),
now_mv(c->rail), now_ua(c->rail));
cj_w(c->w, ",\"left\":%lu", (unsigned long)chgmode_left_s(c->rail, now));
return CMD_OK;
}
bool on = false;
if (cj_get_bool(c->o, "on", &on) != 1) {
return CMD_ERR_ARG;
}
int64_t min = CHG_MODE_DEFAULT_MIN;
const int g = cj_get_int(c->o, "min", &min);
if (g < 0 || (!on && g == 1)) {
return CMD_ERR_ARG;
}
const uint32_t now = hal_time_now_ms();
if (!on) {
const bool was = chgmode_active(c->rail, now);
(void)chgmode_set(c->rail, 0, now);
if (was) {
evlog_put((uint8_t)c->rail, LOG_EV_CHG_MODE, CHG_EV_OFF, now_mv(c->rail), now_ua(c->rail));
}
cj_w(c->w, ",\"left\":0");
return CMD_OK;
}
if (min < 1 || min > CHG_MODE_MAX_MIN) {
return reject(c, CMD_ERR_RANGE, min);
}
/* 关着电的路开刷机模式没有意义(手机没电),与 vbus 命令同一条规矩。 */
if (!rail_efuse_is_on(c->rail)) {
return reject(c, CMD_ERR_STATE, 1);
}
const hal_err_t he = chgmode_set(c->rail, (uint32_t)min, now);
if (he == HAL_ERR_STATE) {
return reject(c, CMD_ERR_STATE, chgmode_count(now)); /* 已有 CHG_MODE_MAX_RAILS 路开着 */
}
if (he != HAL_OK) {
return reject(c, CMD_ERR_RANGE, min);
}
evlog_put((uint8_t)c->rail, LOG_EV_CHG_MODE, (uint8_t)min, now_mv(c->rail), now_ua(c->rail));
cj_w(c->w, ",\"left\":%lu", (unsigned long)chgmode_left_s(c->rail, now));
return CMD_OK;
}
void cmd_chgmode_tick(uint32_t now)
{
const uint32_t ended = chgmode_tick(now);
for (int r = 1; r <= PHONERAIL_RAIL_COUNT; r++) {
if ((ended & (1u << (r - 1))) != 0) {
evlog_put((uint8_t)r, LOG_EV_CHG_MODE, CHG_EV_EXPIRED, now_mv(r), now_ua(r));
}
}
/* 0.1.13:这一路已经断了电(刀片被拔走 / SR-5 等判据关路 / mux 失联全断 —— 都是 t_safety 关的 eFuse,
不经过 `power` 命令)⇒ 刷机模式一并退出。不退的话它一直占着 CHG_MODE_MAX_RAILS 的名额
(2026-09-28 板 B:第 2 槽刀片拔走后还挂着 48 分钟),插回来上电时还带着放宽的判据。
硬重启那几秒 eFuse 本来就关着,不算(执行器忙)。 */
for (int r = 1; r <= PHONERAIL_RAIL_COUNT; r++) {
if (chgmode_active(r, now) && !rail_efuse_is_on(r) && !t_actuator_busy(r)) {
(void)chgmode_set(r, 0, now);
evlog_put((uint8_t)r, LOG_EV_CHG_MODE, CHG_EV_DROPPED, now_mv(r), now_ua(r));
}
}
}
cmd_err_t do_fan(ctx_t *c)
{
int64_t duty = 0;