- warm_esp.{h,c}:warm_restart() 记下开着的路(RTC_NOINIT,魔数 + 掩码 + 反码)→ gpio_hold 锁五根 EN → 重启;
目标镜像没有能力标记(第 288 字节 PRCAPS1,.rodata_custom_desc)就不锁 —— 老固件写不动锁住的 EN。
warm_take():软件复位 + 标记对得上才接管,一次性;再读 EN 真电平核对
- bringup:adopt 掩码;rail_en_init_keep() 接管的路写高、五根一律放开 hold(崩溃时旧锁可能还在);
刀片不在 / mux 失联的接管路立刻关(SLOT_EMPTY arg 1);第二段已开着的路不重开、不等 PG
- pca9555_init_keep():方向寄存器是本固件写的才留 VBUS(掉过电的片子锁存器是 0xFF),键 / EDL 清零
- HAL:hal_gpio_hold();ESP 输出脚配 INPUT_OUTPUT(读得到真电平);sim 给 hold 建模
- 事件 LOG_EV_OTA arg 7 = 接管;兜底定时器回滚故意不走接管
- 顶层 CMake:phonerail.h 进 CMAKE_CONFIGURE_DEPENDS(否则镜像头版本停在旧值);-u g_phonerail_caps 防 gc
- test_bringup 加 6 例(接管 / 非计划复位全关 / 空槽 / mux 失联 / PCA 掉过电 / hold 冻结),全工程 448 例全绿
- rack:固件库识别能力标记,推送对话框按「支持 / 不支持重启不断电」给不同提示(已部署)
- 需求文档:SR-6 改为「非计划复位全关」+ 计划内重启例外;§10.9 新增「重启不断电」
- 板 B:台架镜像(空槽强制 CH1 在位,未提交)开 CH1 → OTA 带重启 → 事件 18/7 mv=1,CH1 一直开着;
现跑 0.1.0-c3。未测:示波器看重启期间 EN / 输出毛刺(要插刀片)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
154 行
8.2 KiB
C
154 行
8.2 KiB
C
/* 五路一张表(需求 §10.4,**R2 版**)—— **禁止用通道号算任何东西**。
|
||
|
||
---- R1 → R2:这条规矩换了对象,没有作废 --------------------------------
|
||
|
||
R1 的板子上,和「第几路」有关的编号有六组,没有一组是顺排的。
|
||
R2 把五路做成了**五片完全相同的刀片**,于是:
|
||
|
||
· INA226 地址:五路**全是 0x40**(五片同构,靠 mux 区分)
|
||
· PCA9555 地址与位号:五路**逐格相同**(所以根本不进这张表,在 pca9555.h)
|
||
· DAC 片/通道:**一个字没改**,仍然是 R1 那张不顺排的表
|
||
(⚠️ CH3 在 `U7` 而不是 `U6`,这个坑原样还在)
|
||
· mux 通道:0 1 2 3 4,**这次真的是顺排的**
|
||
· `EFUSE_EN`:IO9 IO10 IO11 IO12 IO13,也是顺排的(R2 §29.5 为布线重排过)
|
||
· `PRSNT#`:IO15 IO16 IO17 IO18 **IO7** —— **到第五路就断了**(方案 B 重排)
|
||
|
||
顺排的那三列仍然写进表里查,理由是:**顺排是当下的事实,不是不变量**。
|
||
`PRSNT#` 那一列就是现成的反例 —— 四路顺排、第五路不是。
|
||
|
||
⚠️ **这一片写错了,板子照样正常工作** —— 只是「重启 CH1」重启了 CH4、
|
||
「读 CH3 电流」读的是 CH5。功能测试测不出来,五台手机跑起来也看不出来。
|
||
|
||
**除了本文件里这张表,任何地方都不许出现 `rail - 1`、`0x40 + rail`
|
||
这类由通道号算出来的器件编号 / 通道号。**
|
||
|
||
---- 不在这张表里的东西 --------------------------------------------------
|
||
|
||
| 东西 | 在哪 | 为什么不在这里 |
|
||
|---|---|---|
|
||
| 三个按键位 / EDL / VBUS 位 | `pca9555.h` | 五路逐格相同,是「板级常量」不是「每路一个」 |
|
||
| `VBUS_EN` 的 GPIO | **没有了** | R2 里它在刀片的 PCA9555 P0.4 上(计划 §5.4) |
|
||
| 24 位移位链 | **没有了** | R2 D9:整条链退役 |
|
||
|
||
---- 这张表和三个驱动里那三张表是什么关系 --------------------------------
|
||
|
||
驱动各自带着自己那一张表(`ina226_rail_addr()` 等),因为驱动要能单独测。
|
||
本表是**把它们横着拼起来**的那一张,多出来的是 mux 通道与两列 GPIO。
|
||
`rail_selftest()` 会**逐格**拿本表和驱动的取值函数对账,对不上直接判失败。 */
|
||
#ifndef RAIL_CFG_H
|
||
#define RAIL_CFG_H
|
||
|
||
#include <stdbool.h>
|
||
#include <stdint.h>
|
||
|
||
#include "hal_err.h"
|
||
#include "phonerail.h"
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
typedef struct {
|
||
/* 本路 INA226 的地址。**五路相同(0x40)** —— 区分是哪一路的是下面那个通道。 */
|
||
uint8_t ina_addr;
|
||
|
||
/* 本路在底板 TCA9548A 上的通道(0~7)。⚠️ **R2 里选错它 = 读到另一台手机**,
|
||
而量纲、范围、变化趋势全都正常(计划 §5.1 C-d)。 */
|
||
uint8_t mux_ch;
|
||
|
||
uint8_t dac_addr; /* 0x4A: CH1/2/4 0x4B: CH3/5 —— 不是「前三路在 U6」 */
|
||
uint8_t dac_ch; /* 该片上的 OUT 号(0~3) */
|
||
|
||
/* `EFUSE_EN`:ESP32 GPIO **直连**,安全关键(R2 §7.3 的框:保护链第三层
|
||
唯一的执行手段,任何时候都不许挪到 I²C 后面)。
|
||
类型是 `int`,因为 `hal_gpio_*()` 收的就是 int;本表是唯一的出处。 */
|
||
int efuse_en;
|
||
|
||
/* `PRSNT#`:GPIO 输入,板上 3V3 上拉,**低 = 刀片在位**。
|
||
空槽上电 = 金手指裸露带 12V,所以这是一条安全判据(计划 §5.8)。 */
|
||
int prsnt_n;
|
||
} rail_cfg_t;
|
||
|
||
/* rail 是 **1~5**(不是 0~4)。越界直接 abort:那是调用方算错了路号,
|
||
悄悄返回 CH1 的配置比崩掉危险得多。 */
|
||
const rail_cfg_t *rail_cfg(int rail);
|
||
|
||
/* ---- 启动自检(需求 §10.4 的验收项) ------------------------------------
|
||
全部是纯计算,不碰总线也不碰引脚,什么时候调都行。
|
||
返回 0 = 全过;非 0 是下面这些位的或。 */
|
||
|
||
typedef enum {
|
||
RAIL_SELFTEST_INA = 1u << 0, /* ina226_addr_table_ok() 不过 */
|
||
RAIL_SELFTEST_DAC = 1u << 1, /* dac63204_map_ok() 不过 */
|
||
RAIL_SELFTEST_MUX = 1u << 2, /* mux 通道重复 / 越界 / 撞上 PD 那一路 */
|
||
RAIL_SELFTEST_PCA9555 = 1u << 3, /* pca9555_map_ok() / _addr_ok() 不过 */
|
||
RAIL_SELFTEST_TABLE = 1u << 4, /* 本表和驱动的表分家了 */
|
||
RAIL_SELFTEST_PINS = 1u << 5, /* GPIO 重复 / 越界 / 撞上别的信号 */
|
||
} rail_selftest_bit_t;
|
||
|
||
uint32_t rail_selftest(void);
|
||
|
||
/* 只查**一张给定的**五路表(`TABLE` / `PINS` / `MUX` 三位;另外三位是驱动
|
||
自己的表,和 t 无关)。`t` 要指向 PHONERAIL_RAIL_COUNT 行。
|
||
|
||
存在的理由:**自检逻辑本身也得被测。** 拿真表跑一遍只能证明「现在没事」,
|
||
证明不了「真有事的时候查得出来」—— 而真表是 const 的,测试没法把它改坏。 */
|
||
uint32_t rail_selftest_table(const rail_cfg_t *t);
|
||
|
||
/* 单个失败位的人话名字,用来打日志。传 0 或者多于一位返回 "?"。 */
|
||
const char *rail_selftest_str(uint32_t bit);
|
||
|
||
/* ---- `EFUSE_EN`(表驱动遍历) --------------------------------------------
|
||
**高有效**,板上带 100 kΩ 下拉 ⇒ ESP32 复位期间 GPIO 高阻、五路输出全关。
|
||
极性只在 rail_cfg.c 的 `en_write()` 里出现这一次,上层一律传 bool。
|
||
|
||
⚠️ `VBUS_EN` **不在这里** —— 它在刀片的 PCA9555 上,走 `slot_vbus_enable()`。
|
||
这不只是换了个接口:I²C 失联时 VBUS **关不掉**,而 `EFUSE_EN` 永远关得掉
|
||
(计划 §5.4)。两者的可靠性等级从此不一样,别再把它们当成一对。 */
|
||
|
||
/* 五个脚配成输出并**全部拉低**(= 五路输出全关),然后才谈别的。
|
||
⚠️ 必须在 `rail_volt_init()` 与 `slot_init()` 之前调:DAC 上电那一刻输出就
|
||
有电压了,eFuse 的 EN 这时得已经确定是低的。任何一个脚配失败就返回错误码。 */
|
||
hal_err_t rail_en_init(void);
|
||
|
||
/* 同上,但 `keep` 里的那几路(bit0 = CH1)写**高**:「重启不断电」的接管
|
||
(warm_esp.c 记下的、重启前开着的路)。写完电平之后**五个脚一律放开 hold**,
|
||
不管接不接管 —— 崩溃 / 看门狗复位时上一条命的 hold 可能还锁着,不放开固件就关不掉它。
|
||
`rail_en_init()` = `rail_en_init_keep(0)`。 */
|
||
hal_err_t rail_en_init_keep(uint8_t keep);
|
||
|
||
/* 五路影子的掩码(bit0 = CH1)。计划内重启前记下「哪几路开着」用。 */
|
||
uint8_t rail_efuse_mask(void);
|
||
|
||
/* 把五根 EN 脚**此刻的电平**锁住(hal_gpio_hold),跨软件复位保持。
|
||
⚠️ 锁住之后 `rail_efuse_enable()` 写不到线上 —— 只许在 esp_restart() 的前一刻调。 */
|
||
hal_err_t rail_en_hold_all(void);
|
||
|
||
/* rail 越界 → HAL_ERR_ARG;没 `rail_en_init()` 过 → HAL_ERR_ARG(HAL 会拦)。 */
|
||
hal_err_t rail_efuse_enable(int rail, bool on);
|
||
|
||
/* 这根线**最后一次成功写进去**的值(上电与 `rail_en_init()` 之后都是 false)。
|
||
|
||
存在的理由是 A10 的判据要知道「这一路现在该不该有电」:关着的路电压本来就是 0,
|
||
拿它去和设定值比,每一圈都会得出「偏低 3900 mV」。判据只在这个影子为 true 时武装。
|
||
—— 不去读回引脚,是因为 `hal_gpio_read()` 读输出脚在两个目标上的语义不一样,
|
||
而这里要的本来就是「固件的意图」,不是「线上的电平」。 */
|
||
bool rail_efuse_is_on(int rail);
|
||
|
||
/* 五路全关:**先断 VBUS 再关 eFuse**,中间不留「只靠 USB 供着电」的窗口。
|
||
十次操作全部试一遍,不因为某一次失败就提前返回。返回第一个错误码。
|
||
|
||
⚠️ R2 里前半段(断 VBUS)要走 I²C,**可能失败**(mux / PCA9555 失联时必然
|
||
失败);后半段(关 eFuse)是 GPIO,永远成功。所以这个函数在最坏情况下的
|
||
语义是「五路直供确实断了,但 VBUS 可能还通着」—— 这是 R2 用一根跨板针换来的
|
||
代价(计划 §5.4),不是 bug。
|
||
⚠️ 这是复位 / 收尾路径。A10 的故障快路径要直接调 `rail_efuse_enable(rail,false)`,
|
||
别绕这里:过压时该立刻断的是出事那一路,不是等着遍历五路。 */
|
||
hal_err_t rail_all_off(void);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* RAIL_CFG_H */
|