固件 0.1.1-c3:I²C 不应答不再被报成「没初始化 / 忙」(关电回 busy 的根因)

- 根因:IDF 5.4 的 i2c_master_transmit / receive / transmit_receive 在从机 NACK / SCL 超时时返回
  ESP_ERR_INVALID_STATE,HAL 照翻成 HAL_ERR_STATE(= 没 init);命令层 from_hal 又把 STATE 当成
  「执行器忙」⇒ PCA9555 不应答时 power off 回 busy(eFuse 其实已关、意图已清),I2C_LOST 错误码是 5
- hal_i2c_esp.c:传输函数用 map_xfer_err(INVALID_STATE → NAK);总线没建起来仍单独报 STATE
- cmd_exec.c:新增 from_bus,set_voltage / vbus / power 的器件失败一律 io;from_hal 只留给执行器
- sim:sim_i2c_inject_err_ch() 可指定注入的错误码;test_cmd 加 STATE 注入 ⇒ 必须 io 不是 busy
  (改回 from_hal 时这段确实红)。26 个 ctest 全绿
- 板 B(台架镜像,CH1 强制在位,未提交):power off 回 io、CH1 关掉;I2C_LOST 错误码 5 → 2。现跑 0.1.1-c3

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-09-27 14:45:38 +08:00
共同撰写人 Claude Opus 5.5
父节点 7bf30e3c38
当前提交 284571de20
共修改 8 个文件,包含 76 行新增和 13 行删除
+17 -4
查看文件
@@ -38,6 +38,7 @@ static cmd_err_t need_rail(ctx_t *c)
return CMD_OK;
}
/* 执行器(t_actuator_*)的返回值 → 回应。它的 HAL_ERR_STATE 就是「这一路正在跑别的序列」。 */
static cmd_err_t from_hal(hal_err_t e)
{
switch (e) {
@@ -48,6 +49,18 @@ static cmd_err_t from_hal(hal_err_t e)
}
}
/* 直接动器件(I²C / GPIO)的返回值 → 回应。**失败一律 io**,不许借用 from_hal:
总线上的 HAL_ERR_STATE 不是「忙」(2026-09-27:PCA9555 不应答时关电回了 busy,
上位机会以为没执行、换个时机再关一次,其实 eFuse 早就关了)。 */
static cmd_err_t from_bus(hal_err_t e)
{
switch (e) {
case HAL_OK: return CMD_OK;
case HAL_ERR_ARG: return CMD_ERR_RANGE;
default: return CMD_ERR_IO;
}
}
/* ---- 各条命令 ------------------------------------------------------------ */
typedef struct {
@@ -79,7 +92,7 @@ cmd_err_t do_set_voltage(ctx_t *c)
/* ⚠️ DAC 在主干上,但 `rail_set_mv()` 自己**不拿锁**(它比 i2cbus 早)。
不包这一层就是在巡检的事务中间插一笔 —— 总线上两个主机同时说话。 */
set_mv_arg_t a = { .rail = c->rail, .mv = (int)mv };
e = from_hal(i2cbus_xact(I2CBUS_CH_TRUNK, SLOT_LOCK_TIMEOUT_MS, set_mv_fn, &a));
e = from_bus(i2cbus_xact(I2CBUS_CH_TRUNK, SLOT_LOCK_TIMEOUT_MS, set_mv_fn, &a));
if (e == CMD_OK) {
cj_w(c->w, ",\"set\":%d", rail_setpoint_mv(c->rail));
}
@@ -196,7 +209,7 @@ cmd_err_t do_vbus(ctx_t *c)
if (on && !rail_efuse_is_on(c->rail)) {
return reject(c, CMD_ERR_STATE, 1);
}
return from_hal(slot_vbus_enable(c->rail, on));
return from_bus(slot_vbus_enable(c->rail, on));
}
/* A15:意图出口(cmd.h 的 `cmd_intent_fn`)。 */
@@ -226,7 +239,7 @@ cmd_err_t do_power(ctx_t *c)
if (ee == HAL_OK) {
note_intent(c->rail, true);
}
return from_hal(ee);
return from_bus(ee);
}
/* 关:与 `rail_all_off()` 同序 —— **先断 VBUS 再关 eFuse**。
VBUS 走 I²C 可能断不掉,eFuse 是 GPIO 永远关得掉:前者失败也照样关后者,
@@ -236,7 +249,7 @@ cmd_err_t do_power(ctx_t *c)
if (ee == HAL_OK) {
note_intent(c->rail, false); /* 直供确实断了:下次开机也别开它 */
}
return from_hal(ee != HAL_OK ? ee : ev);
return from_bus(ee != HAL_OK ? ee : ev);
}
cmd_err_t do_fan(ctx_t *c)
+1 -1
查看文件
@@ -3,7 +3,7 @@
#define PHONERAIL_H
/* 固件版本。规则:<主>.<次>.<修订>-<最后完成的任务包> */
#define PHONERAIL_FW_VERSION "0.1.0-c3"
#define PHONERAIL_FW_VERSION "0.1.1-c3"
/* 通道数。五路直供,见设计文档 §2。 */
#define PHONERAIL_RAIL_COUNT 5