- 硬重启等到 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]>
226 行
12 KiB
C
226 行
12 KiB
C
/* `t_actuator` —— 每路三个序列 + 互斥(需求 §10.6,§10.3 的第三行任务)。
|
||
|
||
三个序列,**同一路上同时只能跑一个**(§10.6 原话「彼此互斥、不可重入」):
|
||
|
||
| 序列 | 时序 | 出处 |
|
||
|---|---|---|
|
||
| 硬重启 | `EFUSE_EN↓` → 等 3~5 s → `EFUSE_EN↑` → 等 **17 ms** → 等 PG 置位 → 长按电源键 5 s 开机 | 设计文档 §7.4;开机那一步见 `ACT_HR_BOOT_KEY_MS` |
|
||
| 按键 | 置位 → 保持 T(100 ms / 2 s / 10 s)→ 清零 | SW-5 / §13.3 |
|
||
| EDL | `EDL_EN=1` → `VBUS_EN=1` → 等枚举 → `EDL_EN=0` | SW-6 / §22.4 |
|
||
|
||
---- 三条不能改的规矩 ----------------------------------------------------
|
||
|
||
1. **一个函数一步,不许写 `while(1)`。** 死循环在宿主机上一行也跑不到,
|
||
而这一包的全部价值就是那几个时间常量与它们的先后 —— 那正是要被测的东西。
|
||
`t_actuator_step()` 只看时钟、推状态、返回「下次最晚多久再来」,
|
||
任务体在 app_main.c 里,还是三行。
|
||
|
||
2. **放电由硬件联锁自动完成(SR-7),固件一个字都不要写。** 放电 MOS 的栅极
|
||
经一级反相器接在 `EFUSE_EN` 上(设计文档 §7.3),物理上不可能与 eFuse 同时导通。
|
||
固件里再补一根「放电控制」= 10 Ω 上持续 1.76 W,电阻烧掉。
|
||
所以本文件里**没有任何放电接口**,硬重启只有一根 `EFUSE_EN`。
|
||
|
||
3. **按下去的东西一定要松开。** 这一层最坏的失败不是「序列没走完」,
|
||
而是**键留在按下状态** —— 电源键被一直按着,手机再也起不来,而且遥测上
|
||
五路电压电流全都正常。所以每个序列都以 `ACT_ST_CLEANUP` 收尾(超时、
|
||
出错、正常结束走的是同一条路),而且 **CLEANUP 不放弃**:写不进去就一直重试,
|
||
这一路一直算 busy。放弃比重试危险得多。
|
||
|
||
---- PG 为什么是别人喂进来的 ----------------------------------------------
|
||
`PGOOD` 挂在刀片 PCA9555 的 P1.3 上,而 `t_safety` 已经每 20 ms 无条件读一圈
|
||
(§10.3)。执行器再去读一遍等于凭空多一份总线占用和一份两边不一致的可能,
|
||
所以 PG 从外面**喂**进来(`act_pg_fn`)。
|
||
|
||
---- ⚠️ R2 改了这一层的锁前提 ---------------------------------------------
|
||
R1 里执行器只碰 GPIO 与 74HC595,与 I²C 无交集,所以两把锁永远不会嵌套。
|
||
**R2 里按键 / EDL / VBUS 全在刀片的 PCA9555 上 ⇒ 本文件必须拿 I²C 锁**
|
||
(经 `slot_*`,它们自带 `i2cbus_xact`)。
|
||
|
||
好消息是 595 那把锁没了,全系统**只剩一把锁**,嵌套在物理上不可能。
|
||
代价是与巡检争用同一把锁,于是:
|
||
· 每一次动作都是一次短事务,**不许在持锁期间等时间**
|
||
(按键保持 100 ms 是状态机的事,不是持锁的事);
|
||
· `ACT_ST_CLEANUP` 的「不放弃」比 R1 更要紧:总线忙时写不进去必须一直重试。 */
|
||
#ifndef T_ACTUATOR_H
|
||
#define T_ACTUATOR_H
|
||
|
||
#include <stdbool.h>
|
||
#include <stddef.h>
|
||
#include <stdint.h>
|
||
|
||
#include "hal_err.h"
|
||
#include "phonerail.h"
|
||
#include "slot.h"
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* ---- 时间常量:每一个都有出处,不许拍脑袋改 ----------------------------- */
|
||
|
||
/* 硬重启断电时长。§7.4:放到 <0.5 V 只要 ~110 ms(τ = 2200 µF × 10 Ω = 22 ms),
|
||
其余是等手机彻底断电,窗口 3~5 s,取中值。
|
||
⚠️ 缩到 1 s 的现象**不是**「重启快了」,而是「有的机型压根没重启」
|
||
(某些机型要 VBAT 掉到 2.5 V 以下才算真断电,见 §7.1)。 */
|
||
#define ACT_HR_OFF_MS 4000
|
||
#define ACT_HR_OFF_MIN_MS 3000 /* §7.4 的窗口,自检与测试拿它对账 */
|
||
#define ACT_HR_OFF_MAX_MS 5000
|
||
|
||
/* `EFUSE_EN↑` 之后到输出稳定:tON 延迟 5.1 ms(C = 33 nF)+ 缓启动斜坡 11.5 ms
|
||
(0.36 V/ms)≈ **17 ms**(设计文档 §5「缓启动实际值」与 §7.4)。
|
||
这段时间之内 PG 本来就不该置位,去看它只会看到「还没好」。 */
|
||
#define ACT_HR_RAMP_MS 17
|
||
|
||
/* 等 PG 的上限。TPS25940A 的 PGOOD 在输出过 2.97 V 时释放,也就是斜坡中段就该来了;
|
||
500 ms 是 ~30 倍余量。等不到**不代表要再断一次电**,见 `ACT_RES_NO_PG`。 */
|
||
#define ACT_HR_PG_TIMEOUT_MS 500
|
||
|
||
/* 等 PG 期间多久回来看一眼。PG 是 `t_safety` 每 20 ms 刷新的快照,
|
||
比这个再密也没有新值。⚠️ 不能直接睡满 500 ms —— 那样「等到 PG 就结束」
|
||
会退化成「一律等满 500 ms」,而测试与遥测都看不出区别。 */
|
||
#define ACT_HR_PG_POLL_MS 5
|
||
|
||
/* 硬重启的最后一步:等到 PG 之后长按电源键开机。电池端上电的手机**停在关机态**
|
||
(小米8 实测:重新上电后只漏 ~2 mA,不按键就一直关着),不按这一下的硬重启
|
||
等于「关机」。时长就是 SW-5 的开机档。
|
||
⚠️ 只在**等到 PG** 时按:NO_PG 多半是这一片 PCA9555 读不到,键也在同一片上,
|
||
去按只会让这一路卡在 CLEANUP 里重试。 */
|
||
#define ACT_HR_BOOT_KEY_MS ACT_KEY_MS_ON
|
||
|
||
/* SW-5 的三档按键时长。
|
||
⚠️ 开机那一档 2026-09-28 小米8 实测:按 2 s 时 PMIC 起来了(415 mA),一松手 3 s 内
|
||
掉回 ~1 mA —— SoC 还没接住 PS_HOLD。按 5 s 松手后稳在 ~210 mA、约 45 s 进系统。
|
||
上限是 ACT_KEY_MS_FORCE:按到 10 s 就成了强制重启。 */
|
||
#define ACT_KEY_MS_TAP 100 /* 短按:唤醒 / 息屏 */
|
||
#define ACT_KEY_MS_ON 5000 /* 长按:开机 */
|
||
#define ACT_KEY_MS_FORCE 10000 /* 长按:强制重启(两级重启的「一级(软)」) */
|
||
|
||
/* 按键时长上限。上层传进来一个 10 分钟,等于把电源键按死 ——
|
||
这不是保护,是**拦调用方的手误**(SR-13 拦 4.2 V 是同一个道理)。 */
|
||
#define ACT_KEY_MAX_MS 15000
|
||
|
||
/* EDL:先短接、再插 USB(SW-6 的顺序是硬要求)。
|
||
模拟开关 TS5A3157 的 Ron ~1 Ω、导通时间 µs 级,10 ms 是给它和 1.8 kΩ 上的建立留的。 */
|
||
#define ACT_EDL_SHORT_MS 10
|
||
|
||
/* 「等枚举」。⚠️ **固件看不见枚举** —— 手机挂在板载 Hub 的下行口上,ESP32 这一侧
|
||
没有任何信号能告诉它 9008 有没有起来。所以这里只能定时等,3 s 是按 USB 枚举
|
||
典型 <1 s 给的余量。真要确认枚举成功,只能由上位机 `lsusb` 看 05c6:9008
|
||
(那是 §10.8 上位机那一侧的事)。 */
|
||
#define ACT_EDL_ENUM_MS 3000
|
||
|
||
/* 每次动作等 I²C 锁最多等多久(`slot.h` 的 `SLOT_LOCK_TIMEOUT_MS` 就是它)。
|
||
争用来自每 20 ms 一圈的巡检;等不到就下一拍再来(CLEANUP 会一直重试)。 */
|
||
#define ACT_LOCK_TIMEOUT_MS SLOT_LOCK_TIMEOUT_MS
|
||
|
||
/* CLEANUP 重试间隔,以及空闲时建议任务睡多久(A12 会换成「带超时地等队列」)。 */
|
||
#define ACT_CLEANUP_RETRY_MS 5
|
||
#define ACT_IDLE_WAIT_MS 100
|
||
|
||
/* ---- 按键:逻辑键位,不是器件上的位号 -----------------------------------
|
||
上层一律用这三个位(和它们的组合)表达「按哪几个键」,PCA9555 Port0 上的
|
||
位号由本文件经 `pca9555_key_bit()` 查表换算。
|
||
⚠️ R2 里位号**不再随路号变**(五片刀片同构),但仍然查表不算。 */
|
||
typedef enum {
|
||
ACT_KEYS_PWR = 1u << PCA9555_KEY_PWR,
|
||
ACT_KEYS_VUP = 1u << PCA9555_KEY_VUP,
|
||
ACT_KEYS_VDN = 1u << PCA9555_KEY_VDN,
|
||
|
||
/* SW-5 的两个组合键。**必须同一帧写进去**(§10.6)——
|
||
分两次写会先单按一个键,而「先单按 VOL−」在有的机型上就是音量调节。 */
|
||
ACT_KEYS_FASTBOOT = ACT_KEYS_VDN | ACT_KEYS_PWR,
|
||
ACT_KEYS_RECOVERY = ACT_KEYS_VUP | ACT_KEYS_PWR,
|
||
|
||
ACT_KEYS_ALL = ACT_KEYS_PWR | ACT_KEYS_VUP | ACT_KEYS_VDN,
|
||
} act_keys_t;
|
||
|
||
/* ---- 状态与结果 ---------------------------------------------------------- */
|
||
|
||
typedef enum {
|
||
ACT_SEQ_NONE = 0,
|
||
ACT_SEQ_HARD_RESET,
|
||
ACT_SEQ_KEY,
|
||
ACT_SEQ_EDL,
|
||
} act_seq_t;
|
||
|
||
typedef enum {
|
||
ACT_ST_IDLE = 0,
|
||
ACT_ST_HR_OFF, /* `EFUSE_EN` 已拉低,等 3~5 s(放电由硬件自己做) */
|
||
ACT_ST_HR_RAMP, /* `EFUSE_EN` 已拉高,等 17 ms 走完 tON + 斜坡 */
|
||
ACT_ST_HR_PG, /* 等 PG 置位 */
|
||
ACT_ST_HR_BOOT, /* PG 到了,电源键按着,保持 ACT_HR_BOOT_KEY_MS 开机 */
|
||
ACT_ST_KEY_HOLD, /* 键已按下,保持 T */
|
||
ACT_ST_EDL_SHORT, /* `EDL_EN=1`,等短接建立 */
|
||
ACT_ST_EDL_ENUM, /* `VBUS_EN=1`,等枚举 */
|
||
ACT_ST_CLEANUP, /* 把本序列按下去的东西松开,落不下去就重试(不放弃) */
|
||
} act_state_t;
|
||
|
||
typedef enum {
|
||
ACT_RES_NONE = 0,
|
||
ACT_RES_OK,
|
||
/* 硬重启专用:电**已经开着**,只是 500 ms 内没等到 PG(也可能是 PCA9555
|
||
读不到)。⚠️ 这时**不许**再把这一路关掉:§10.10 里「PCA9555 不应答 =
|
||
失去 FAULT/PG 可见性,**不关输出**」。把「看不见」当成「坏了」去断电,
|
||
等于一片 I/O 扩展器失联就让五台手机一起停机。 */
|
||
ACT_RES_NO_PG,
|
||
/* 写 PCA9555 或写 EN 引脚失败(含拿不到 I²C 锁)。序列中止,但收尾照做。 */
|
||
ACT_RES_IO,
|
||
/* 被 `t_actuator_abort()` 打断。 */
|
||
ACT_RES_ABORTED,
|
||
} act_result_t;
|
||
|
||
/* PG 取值回调。返回 false = **这一刻读不到**(PCA9555 失联 / 还没读过一圈),
|
||
与「读到了,是低」是两回事 —— 后者是「电源还没好」,前者是「不知道」。
|
||
传 NULL 给 `t_actuator_init()` 就是「永远读不到」,硬重启会走完并记 ACT_RES_NO_PG。 */
|
||
typedef bool (*act_pg_fn)(void *ctx, int rail, bool *pg);
|
||
|
||
/* ---- 接口 ---------------------------------------------------------------- */
|
||
|
||
/* 幂等,五路全部回到 IDLE。**不碰硬件**:SW-11 的「上电全关」是
|
||
`rail_en_init()`(GPIO)+ `slot_init()`(PCA9555 的输出锁存器先清零)
|
||
干的(A15),不是这里。 */
|
||
void t_actuator_init(act_pg_fn pg, void *ctx);
|
||
|
||
/* 三个请求。rail 是 **1~5**。
|
||
HAL_ERR_ARG —— 路号 / 键位 / 时长越界;
|
||
HAL_ERR_STATE —— 没 init,或者**这一路正在跑另一个序列**(§10.6 不可重入)。
|
||
要抢占的话调用方得先 `t_actuator_abort()`,写成显式的。
|
||
⚠️ 返回非 HAL_OK 时序列**没有开始**,只有一个例外:按下 / 置位那一步真的
|
||
发到链上但失败了 —— 那时影子可能已经改了,所以那一路会进 CLEANUP 把它撤回来,
|
||
函数仍然返回错误码,但 `t_actuator_busy()` 会短暂为 true。 */
|
||
hal_err_t t_actuator_hard_reset(int rail);
|
||
hal_err_t t_actuator_key(int rail, uint32_t keys, uint32_t hold_ms);
|
||
hal_err_t t_actuator_edl(int rail);
|
||
|
||
/* 打断这一路正在跑的序列:立刻收尾(键松开 / `EDL_EN` 清零),结果记 ACT_RES_ABORTED。
|
||
**不动 `EFUSE_EN` / `VBUS_EN`** —— 硬重启走到一半被打断时,把电再关一次
|
||
或者再开一次都是自作主张;断电是 A10 的判据和上位机命令的事。 */
|
||
void t_actuator_abort(int rail);
|
||
|
||
/* 推一步。返回**下次最晚多久再来**(ms):五路里最近的那个截止时刻。
|
||
全空闲返回 ACT_IDLE_WAIT_MS。醒早了没关系 —— 每个状态都是拿绝对时刻算的,
|
||
早来就再返回一个剩余时间。(ESP-IDF 默认 `CONFIG_FREERTOS_HZ=100`,
|
||
`vTaskDelay(pdMS_TO_TICKS(17))` 只有 1 个 tick,本来就会早醒。) */
|
||
uint32_t t_actuator_step(void);
|
||
|
||
/* ---- 观测 ---------------------------------------------------------------- */
|
||
|
||
bool t_actuator_busy(int rail);
|
||
act_state_t t_actuator_state(int rail);
|
||
act_seq_t t_actuator_seq(int rail);
|
||
act_result_t t_actuator_last_result(int rail);
|
||
uint32_t t_actuator_done(int rail); /* 走完几个序列(含失败的) */
|
||
uint32_t t_actuator_cleanup_retries(int rail); /* 收尾重试次数,遥测里报出去 */
|
||
|
||
const char *t_actuator_state_str(act_state_t st);
|
||
const char *t_actuator_result_str(act_result_t r);
|
||
|
||
/* 一行人话,给启动日志和遥测用。返回写进去的字符数(不含结尾 0)。 */
|
||
int t_actuator_format(char *buf, size_t len);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* T_ACTUATOR_H */
|