文件
phonerail/firmware/main/rail_cfg.h
T
zhou yang和Claude Opus 5 45de84ed3c A7:五路配置表 + 表驱动遍历 + 启动自检
六组编号(INA 地址 / DAC 片通道 / 三个按键位 / EDL 位 / 两根 EN 的 GPIO)拼成
一张 const 表 rail_cfg(1..5),没有一列是顺排的。开机 rail_selftest() 把它和
A3~A6 四个驱动的表逐格对账,再查全板引脚重复 / 越界,不过就 abort ——
那四个自检谓词至此全部有了调用点。

判据拎成 rail_selftest_table(t) 单独可调:真表是 const 的,不这么做就没法证明
「真有事的时候查得出来」。测试复制一张表出来逐样改坏,六种坏法都验过。

顺带修一个真 bug:HAL 两侧 GPIO 边界写的是 pin < 48,而 EFUSE_EN2 就在 IO48
上 —— CH2 的 eFuse 既开不了也关不掉。sim 与 ESP 两份都改成 0~48 含。

9 条 ctest(131 个用例)全绿,idf.py build 通过,16 条注错 15 条变红
(唯一没红的那条与原因记在 HANDOFF.md)。

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-07 07:13:04 +08:00

121 行
6.2 KiB
C
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/* 五路一张表(需求 §10.4)—— **禁止用通道号算任何东西**。
板上和「第几路」有关的编号有六组,**没有一组是顺排的,也没有两组是一致的**:
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
六组编号各有各的来历(I²C 地址按布线难度、595 位号按 PCB 几何、GPIO 按出线
方向),三条都在设计文档里:§8.2 / §17.2 + §26.18 C / §8.1。
⚠️ **这一片写错了,板子照样正常工作** —— 只是「重启 CH1」重启了 CH4、
「读 CH3 电流」读的是 CH5。功能测试测不出来,五台手机跑起来也看不出来,
要等到某一路真出故障、而你关掉了另一路的时候才暴露。所以:
**除了本文件里这张表,任何地方都不许出现 `0x40 + rail`、`rail - 1`
这类由通道号算出来的器件编号。**
---- 这张表和四个驱动里那四张表是什么关系 ----------------------------------
A3~A6 四个驱动各自带着自己那一张表(`ina226_rail_addr()` 等),因为驱动要能
单独测。本表是**把它们横着拼起来**的那一张,多出来的只有两列 GPIO
(`EFUSE_EN` / `VBUS_EN` 是 GPIO 直连,没有驱动可挂)。
两处写同一个数就有分家的风险,所以 `rail_selftest()` 会**逐格**拿本表和四个
驱动的取值函数对账,对不上直接判失败(`RAIL_SELFTEST_TABLE`)。
宿主机 `ctest` 每次都跑,上板 `app_main()` 开机也跑一次。
—— 之所以不做成「开机时从驱动现算」,是因为那样**按定义就查不出分家**,
而且表就不是 const 的了,用之前得先记着初始化。 */
#ifndef RAIL_CFG_H
#define RAIL_CFG_H
#include <stdbool.h>
#include <stdint.h>
#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」 */
uint8_t dac_ch; /* 该片上的 OUT 号(0~3) */
/* GPIO 直连,安全关键(§8.1「任何情况下都要能关电」)。
类型是 `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 —— 两个端口长得一模一样,别搞反 */
} 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_SR595 = 1u << 2, /* sr595_map_ok() 不过 */
RAIL_SELFTEST_PCA9555 = 1u << 3, /* pca9555_map_ok() / _addr_ok() 不过 */
RAIL_SELFTEST_TABLE = 1u << 4, /* 本表和四个驱动的表分家了 */
RAIL_SELFTEST_PINS = 1u << 5, /* EN 引脚重复 / 越界 / 撞上别的信号 */
} rail_selftest_bit_t;
uint32_t rail_selftest(void);
/* 只查**一张给定的**五路表(`RAIL_SELFTEST_TABLE` / `RAIL_SELFTEST_PINS` 两位;
另外四位是四个驱动自己的表,和 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);
/* ---- 两根 EN 线(表驱动遍历) --------------------------------------------
`EFUSE_EN` / `VBUS_EN` 都是**高有效**,而且都在板上带 100 kΩ 下拉
(R114 / R701~705)—— ESP32 复位期间 GPIO 高阻,五路输出全关、VBUS 全断。
极性只在 rail_cfg.c 的 `en_write()` 里出现这一次,上层一律传 bool。 */
/* 十个脚配成输出并**全部拉低**(= 五路全关、VBUS 全断),然后才谈别的。
⚠️ 必须在 `rail_volt_init()` 之前调:DAC 上电那一刻输出就有电压了,
eFuse 的 EN 这时得已经确定是低的。任何一个脚配失败就返回 HAL_ERR_ARG。 */
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);
/* 五路全关:**先断 VBUS 再关 eFuse**,中间不留「只靠 USB 供着电」的窗口。
十个脚全部试一遍,不因为某一个失败就提前返回 —— 关电路径上「剩下九个也别关了」
是最坏的选择。返回第一个错误码(全成功就是 HAL_OK)。
⚠️ 这是复位 / 收尾路径。A10 的故障快路径要直接调 `rail_efuse_enable(rail,false)`,
别绕这里:过压时该立刻断的是出事那一路,不是等着遍历五路。 */
hal_err_t rail_all_off(void);
#ifdef __cplusplus
}
#endif
#endif /* RAIL_CFG_H */