固件:R2 迁移 R-0 ~ R-6(底板 + 5 片同构刀片)
2026-09-16 完成、当时未提交,这里补提交(原样,不含 09-27 上板的修改)。 - 器件层 / 拓扑层整体从 R1 换到 R2:TCA9548A 分通道、五路同址 INA226 0x40 + 输入侧 0x43、 刀片 PCA9555(VBUS_EN 挪到 P0.4)、CH224A 只读驱动、风扇闭环 t_thermal - 新增 i2cbus(mux 原子事务)、slot(刀片 I/O 门面)、host/qtest.sh - 74HC595 链退役(代码与测试保留) - 判据阈值与时间常量未动;19 个 ctest 全绿,idf.py build 通过 Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
+65
-59
@@ -1,35 +1,40 @@
|
||||
/* 五路一张表(需求 §10.4)—— **禁止用通道号算任何东西**。
|
||||
/* 五路一张表(需求 §10.4,**R2 版**)—— **禁止用通道号算任何东西**。
|
||||
|
||||
板上和「第几路」有关的编号有六组,**没有一组是顺排的,也没有两组是一致的**:
|
||||
---- R1 → R2:这条规矩换了对象,没有作废 --------------------------------
|
||||
|
||||
CH INA226 DAC 按键位(PWR/VUP/VDN) EDL 位 EFUSE_EN VBUS_EN
|
||||
1 0x40 U6.OUT2 1 2 3 16 IO39 IO40
|
||||
2 0x41 U6.OUT0 19 17 18 7 IO48 IO10
|
||||
3 0x44 **U7**.OUT0 22 21 20 6 IO6 IO11
|
||||
4 0x45 U6.OUT1 9 10 11 0 IO41 IO42
|
||||
5 0x42 U7.OUT1 15 14 13 5 IO8 IO17
|
||||
R1 的板子上,和「第几路」有关的编号有六组,没有一组是顺排的。
|
||||
R2 把五路做成了**五片完全相同的刀片**,于是:
|
||||
|
||||
六组编号各有各的来历(I²C 地址按布线难度、595 位号按 PCB 几何、GPIO 按出线
|
||||
方向),三条都在设计文档里:§8.2 / §17.2 + §26.18 C / §8.1。
|
||||
· 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。功能测试测不出来,五台手机跑起来也看不出来,
|
||||
要等到某一路真出故障、而你关掉了另一路的时候才暴露。所以:
|
||||
「读 CH3 电流」读的是 CH5。功能测试测不出来,五台手机跑起来也看不出来。
|
||||
|
||||
**除了本文件里这张表,任何地方都不许出现 `0x40 + rail`、`rail - 1`
|
||||
这类由通道号算出来的器件编号。**
|
||||
**除了本文件里这张表,任何地方都不许出现 `rail - 1`、`0x40 + rail`
|
||||
这类由通道号算出来的器件编号 / 通道号。**
|
||||
|
||||
---- 这张表和四个驱动里那四张表是什么关系 ----------------------------------
|
||||
---- 不在这张表里的东西 --------------------------------------------------
|
||||
|
||||
A3~A6 四个驱动各自带着自己那一张表(`ina226_rail_addr()` 等),因为驱动要能
|
||||
单独测。本表是**把它们横着拼起来**的那一张,多出来的只有两列 GPIO
|
||||
(`EFUSE_EN` / `VBUS_EN` 是 GPIO 直连,没有驱动可挂)。
|
||||
| 东西 | 在哪 | 为什么不在这里 |
|
||||
|---|---|---|
|
||||
| 三个按键位 / EDL / VBUS 位 | `pca9555.h` | 五路逐格相同,是「板级常量」不是「每路一个」 |
|
||||
| `VBUS_EN` 的 GPIO | **没有了** | R2 里它在刀片的 PCA9555 P0.4 上(计划 §5.4) |
|
||||
| 24 位移位链 | **没有了** | R2 D9:整条链退役 |
|
||||
|
||||
两处写同一个数就有分家的风险,所以 `rail_selftest()` 会**逐格**拿本表和四个
|
||||
驱动的取值函数对账,对不上直接判失败(`RAIL_SELFTEST_TABLE`)。
|
||||
宿主机 `ctest` 每次都跑,上板 `app_main()` 开机也跑一次。
|
||||
—— 之所以不做成「开机时从驱动现算」,是因为那样**按定义就查不出分家**,
|
||||
而且表就不是 const 的了,用之前得先记着初始化。 */
|
||||
---- 这张表和三个驱动里那三张表是什么关系 --------------------------------
|
||||
|
||||
驱动各自带着自己那一张表(`ina226_rail_addr()` 等),因为驱动要能单独测。
|
||||
本表是**把它们横着拼起来**的那一张,多出来的是 mux 通道与两列 GPIO。
|
||||
`rail_selftest()` 会**逐格**拿本表和驱动的取值函数对账,对不上直接判失败。 */
|
||||
#ifndef RAIL_CFG_H
|
||||
#define RAIL_CFG_H
|
||||
|
||||
@@ -38,28 +43,30 @@
|
||||
|
||||
#include "hal_err.h"
|
||||
#include "phonerail.h"
|
||||
#include "sr595.h" /* sr595_key_t:按键位那三格的下标就是它 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint8_t ina_addr; /* 0x40 0x41 0x44 0x45 0x42 —— 查表,不能算 */
|
||||
uint8_t dac_addr; /* 0x4A: CH1/2/4 0x4B: CH3/5 —— 也不是「前三路在 U6」 */
|
||||
/* 本路 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) */
|
||||
|
||||
/* GPIO 直连,安全关键(§8.1「任何情况下都要能关电」)。
|
||||
/* `EFUSE_EN`:ESP32 GPIO **直连**,安全关键(R2 §7.3 的框:保护链第三层
|
||||
唯一的执行手段,任何时候都不许挪到 I²C 后面)。
|
||||
类型是 `int`,因为 `hal_gpio_*()` 收的就是 int;本表是唯一的出处。 */
|
||||
int efuse_en;
|
||||
int vbus_en;
|
||||
|
||||
/* 24 位移位链上的位号。下标用 `SR595_KEY_PWR/VUP/VDN`,别用 0/1/2。 */
|
||||
uint8_t key_bit[SR595_KEY_COUNT];
|
||||
uint8_t edl_bit;
|
||||
|
||||
uint8_t flt_bit; /* PCA9555 **Port0** bit0~4 */
|
||||
uint8_t pg_bit; /* PCA9555 **Port1** bit0~4 —— 两个端口长得一模一样,别搞反 */
|
||||
/* `PRSNT#`:GPIO 输入,板上 3V3 上拉,**低 = 刀片在位**。
|
||||
空槽上电 = 金手指裸露带 12V,所以这是一条安全判据(计划 §5.8)。 */
|
||||
int prsnt_n;
|
||||
} rail_cfg_t;
|
||||
|
||||
/* rail 是 **1~5**(不是 0~4)。越界直接 abort:那是调用方算错了路号,
|
||||
@@ -73,56 +80,55 @@ const rail_cfg_t *rail_cfg(int rail);
|
||||
typedef enum {
|
||||
RAIL_SELFTEST_INA = 1u << 0, /* ina226_addr_table_ok() 不过 */
|
||||
RAIL_SELFTEST_DAC = 1u << 1, /* dac63204_map_ok() 不过 */
|
||||
RAIL_SELFTEST_SR595 = 1u << 2, /* sr595_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, /* EN 引脚重复 / 越界 / 撞上别的信号 */
|
||||
RAIL_SELFTEST_TABLE = 1u << 4, /* 本表和驱动的表分家了 */
|
||||
RAIL_SELFTEST_PINS = 1u << 5, /* GPIO 重复 / 越界 / 撞上别的信号 */
|
||||
} rail_selftest_bit_t;
|
||||
|
||||
uint32_t rail_selftest(void);
|
||||
|
||||
/* 只查**一张给定的**五路表(`RAIL_SELFTEST_TABLE` / `RAIL_SELFTEST_PINS` 两位;
|
||||
另外四位是四个驱动自己的表,和 t 无关)。`t` 要指向 PHONERAIL_RAIL_COUNT 行。
|
||||
/* 只查**一张给定的**五路表(`TABLE` / `PINS` / `MUX` 三位;另外三位是驱动
|
||||
自己的表,和 t 无关)。`t` 要指向 PHONERAIL_RAIL_COUNT 行。
|
||||
|
||||
存在的理由:**自检逻辑本身也得被测。** 拿真表跑一遍只能证明「现在没事」,
|
||||
证明不了「真有事的时候查得出来」—— 而真表是 const 的,测试没法把它改坏。
|
||||
所以判据单拎出来,测试喂几张故意写坏的表进去看它认不认。 */
|
||||
证明不了「真有事的时候查得出来」—— 而真表是 const 的,测试没法把它改坏。 */
|
||||
uint32_t rail_selftest_table(const rail_cfg_t *t);
|
||||
|
||||
/* 单个失败位的人话名字,用来打日志。传 0 或者多于一位返回 "?"。 */
|
||||
const char *rail_selftest_str(uint32_t bit);
|
||||
|
||||
/* ---- 两根 EN 线(表驱动遍历) --------------------------------------------
|
||||
`EFUSE_EN` / `VBUS_EN` 都是**高有效**,而且都在板上带 100 kΩ 下拉
|
||||
(R114 / R701~705)—— ESP32 复位期间 GPIO 高阻,五路输出全关、VBUS 全断。
|
||||
极性只在 rail_cfg.c 的 `en_write()` 里出现这一次,上层一律传 bool。 */
|
||||
/* ---- `EFUSE_EN`(表驱动遍历) --------------------------------------------
|
||||
**高有效**,板上带 100 kΩ 下拉 ⇒ ESP32 复位期间 GPIO 高阻、五路输出全关。
|
||||
极性只在 rail_cfg.c 的 `en_write()` 里出现这一次,上层一律传 bool。
|
||||
|
||||
/* 十个脚配成输出并**全部拉低**(= 五路全关、VBUS 全断),然后才谈别的。
|
||||
⚠️ 必须在 `rail_volt_init()` 之前调:DAC 上电那一刻输出就有电压了,
|
||||
eFuse 的 EN 这时得已经确定是低的。任何一个脚配失败就返回 HAL_ERR_ARG。 */
|
||||
⚠️ `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);
|
||||
|
||||
/* rail 越界 → HAL_ERR_ARG;没 `rail_en_init()` 过 → HAL_ERR_ARG(HAL 会拦)。 */
|
||||
hal_err_t rail_efuse_enable(int rail, bool on);
|
||||
hal_err_t rail_vbus_enable(int rail, bool on);
|
||||
|
||||
/* 这两根线**最后一次成功写进去**的值(上电与 `rail_en_init()` 之后都是 false)。
|
||||
/* 这根线**最后一次成功写进去**的值(上电与 `rail_en_init()` 之后都是 false)。
|
||||
|
||||
存在的理由是 A10 的判据要知道「这一路现在该不该有电」:关着的路电压本来就是 0,
|
||||
拿它去和设定值比,每一圈都会得出「偏低 3900 mV」,于是巡检会没完没了地
|
||||
「再关一次已经关着的路」并刷事件。判据只在这两个影子为 true 时武装。
|
||||
|
||||
⚠️ **只在写成功时更新**,而且方向是有讲究的:关的那一次写失败 ⇒ 影子留在 on
|
||||
⇒ 判据继续武装(还会再关一次);开的那一次写失败 ⇒ 影子留在 off
|
||||
⇒ 判据不武装(不会对着一路没开成的电报偏差)。两边都倒向安全的那一侧。
|
||||
拿它去和设定值比,每一圈都会得出「偏低 3900 mV」。判据只在这个影子为 true 时武装。
|
||||
—— 不去读回引脚,是因为 `hal_gpio_read()` 读输出脚在两个目标上的语义不一样,
|
||||
而这里要的本来就是「固件的意图」,不是「线上的电平」。 */
|
||||
bool rail_efuse_is_on(int rail);
|
||||
bool rail_vbus_is_on(int rail);
|
||||
|
||||
/* 五路全关:**先断 VBUS 再关 eFuse**,中间不留「只靠 USB 供着电」的窗口。
|
||||
十个脚全部试一遍,不因为某一个失败就提前返回 —— 关电路径上「剩下九个也别关了」
|
||||
是最坏的选择。返回第一个错误码(全成功就是 HAL_OK)。
|
||||
十次操作全部试一遍,不因为某一次失败就提前返回。返回第一个错误码。
|
||||
|
||||
⚠️ 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);
|
||||
|
||||
在新工单中引用
屏蔽一个用户