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]>
187 行
8.8 KiB
C
187 行
8.8 KiB
C
/* PCA9555 —— **每片刀片上一颗**(`U4`,五片全是 0x20),R2 D9 取代 R1 的
|
||
74HC595 链。五片靠底板 TCA9548A 分通道区分(D8),所以从驱动看它们**完全一样**。
|
||
|
||
⚠️⚠️ **R1 那版驱动「16 个脚全是输入」的前提在 R2 不成立了。**
|
||
R2 的 P0.0~P0.4 是**输出**:三个侧键 + EDL + VBUS 开关。
|
||
拿 R1 的记忆改这个文件会在板子上冒烟,原因见下面那个框。
|
||
|
||
位分配从 `.claude/r2/_blade_pcb_nets.json` 的 `U4` 逐脚读回(2026-09-16):
|
||
|
||
Port0 bit0 KEY_PWR bit1 KEY_VUP bit2 KEY_VDN bit3 EDL_EN bit4 VBUS_EN
|
||
bit5~7 **直接接地**
|
||
Port1 bit0 EFUSE_FLT# bit1 BUCK_PG bit2 INA_ALERT bit3 EFUSE_PG
|
||
bit4~7 **直接接地**
|
||
|
||
┌─ ⛔⛔ 初始化顺序是这块板子上最危险的一处 ──────────────────────────────┐
|
||
│ PCA9555 的 **Output Port 复位值是 0xFF**,Config 复位值也是 0xFF(全输入)。│
|
||
│ 所以**必须先把输出锁存器写成 0,最后才改方向**: │
|
||
│ OUTPUT0=0 → OUTPUT1=0 → POLINV=0 → CONFIG1=0xFF → CONFIG0=0xE0 │
|
||
│ 顺序反过来的那一刻,P0.0~P0.4 一起输出 1 │
|
||
│ = 三个键全按下 + EDL 短接 + VBUS 上电,五台手机一起被按住电源键。 │
|
||
│ 而 P0.5~7 / P1.4~7 七个脚在刀片上**直接接地、不串限流电阻** —— │
|
||
│ 方向写错一位就是对地短路:不报错、不 NAK,只会发烫。 │
|
||
│ ⇒ `pca9555_dir_safe()` 是硬闸,走到 false 直接 abort。 │
|
||
└──────────────────────────────────────────────────────────────────────────┘
|
||
|
||
§10.10 的降级行为:某片不应答 = 失去那一路的 FAULT / PG 可见性,**不关输出**,
|
||
改用 INA226 的电压判据兜底并持续告警。所以本驱动的读失败一律只返回错误码。
|
||
⚠️ R2 新增的代价:这片不应答时**那一路的 VBUS 也控制不了了**(计划 §5.4)。
|
||
|
||
本驱动只经 hal_i2c_*,**不碰 mux**:通道由调用方(`slot.c`)先选好。 */
|
||
#ifndef PCA9555_H
|
||
#define PCA9555_H
|
||
|
||
#include <stdbool.h>
|
||
#include <stdint.h>
|
||
|
||
#include "hal_err.h"
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/* ---- 地址与实例 ---------------------------------------------------------- */
|
||
|
||
/* A0 = A1 = A2 = GND(`U4` 的 pin21 / pin2 / pin3,已按 PCB 焊盘网络核过)。
|
||
**五片同址** —— 这是 D8 的全部意义,不是笔误。 */
|
||
#define PCA9555_ADDR 0x20
|
||
|
||
#define PCA9555_SLOT_COUNT 5
|
||
|
||
/* ---- 寄存器(全部 8 位) ------------------------------------------------- */
|
||
|
||
#define PCA9555_REG_INPUT0 0x00
|
||
#define PCA9555_REG_INPUT1 0x01
|
||
#define PCA9555_REG_OUTPUT0 0x02
|
||
#define PCA9555_REG_OUTPUT1 0x03
|
||
#define PCA9555_REG_POLINV0 0x04
|
||
#define PCA9555_REG_POLINV1 0x05
|
||
#define PCA9555_REG_CONFIG0 0x06
|
||
#define PCA9555_REG_CONFIG1 0x07
|
||
|
||
/* 方向寄存器:**1 = 输入**(复位值 0xFF)。 */
|
||
#define PCA9555_DIR_ALL_INPUT 0xFFu
|
||
|
||
/* 本板的两个方向字。Port0 只有 bit0~4 是输出。 */
|
||
#define PCA9555_CONFIG0_INIT 0xE0u
|
||
#define PCA9555_CONFIG1_INIT 0xFFu
|
||
|
||
/* 极性反转必须是 0。写成非 0 的现象是「故障全都看不见」,而 I²C 一切正常。 */
|
||
#define PCA9555_POLINV_NONE 0x00u
|
||
|
||
/* ---- Port0:五个输出位 --------------------------------------------------- */
|
||
|
||
/* 三个侧键的逻辑键位。**上层一律用这个枚举,不许写 `1 << 0`。**
|
||
(R1 时这个枚举在 `sr595.h` 里,链退役之后搬到这里。) */
|
||
typedef enum {
|
||
PCA9555_KEY_PWR = 0,
|
||
PCA9555_KEY_VUP,
|
||
PCA9555_KEY_VDN,
|
||
PCA9555_KEY_COUNT,
|
||
} pca9555_key_t;
|
||
|
||
#define PCA9555_BIT_KEY_PWR 0
|
||
#define PCA9555_BIT_KEY_VUP 1
|
||
#define PCA9555_BIT_KEY_VDN 2
|
||
#define PCA9555_BIT_EDL_EN 3
|
||
#define PCA9555_BIT_VBUS_EN 4
|
||
|
||
#define PCA9555_P0_OUT_MASK 0x1Fu /* bit0~4:唯一允许配成输出的五位 */
|
||
#define PCA9555_P0_GND_MASK 0xE0u /* bit5~7 焊死在地上 */
|
||
|
||
/* ---- Port1:四个输入位(全是开漏 + 上拉) -------------------------------- */
|
||
|
||
#define PCA9555_BIT_EFUSE_FLT 0
|
||
#define PCA9555_BIT_BUCK_PG 1
|
||
#define PCA9555_BIT_INA_ALERT 2
|
||
#define PCA9555_BIT_EFUSE_PG 3
|
||
|
||
#define PCA9555_P1_IN_MASK 0x0Fu
|
||
#define PCA9555_P1_GND_MASK 0xF0u /* bit4~7 焊死在地上 */
|
||
|
||
/* 键 → Port0 位号。**不带路号**:五片刀片同构,位号与路号无关(计划 §5.7)。
|
||
越界 abort。 */
|
||
int pca9555_key_bit(pca9555_key_t key);
|
||
|
||
/* 一路上三个键的掩码(Port0)。`t_actuator` 的「防御性松键」用它。 */
|
||
uint8_t pca9555_key_mask(void);
|
||
|
||
/* 位分配自检:五个输出位互不重复、四个输入位互不重复、
|
||
**一个信号都没落到接地脚上**、输出位与输入位分属两个端口。
|
||
启动自检调一次(和 `ina226_addr_table_ok()` 一起)。 */
|
||
bool pca9555_map_ok(void);
|
||
|
||
/* 0x20 不与主干上的 TCA9548A(0x70) / INA226(0x43) / DAC63204(0x4A/0x4B) 撞车。
|
||
⚠️ 刀片上的 INA226 也是 0x40,与 0x20 本来就不同,两者同在一条支路上没问题。 */
|
||
bool pca9555_addr_ok(void);
|
||
|
||
/* `dir` 里 1 = 输入。返回 false = 这个方向字会把某个**接地脚**配成输出,
|
||
或者把 Port1 上那四个开漏信号源配成输出(与 eFuse 的 FLT# 打架)。
|
||
驱动内部走到 false 直接 abort:悄悄放过去的后果是板子发烫,不是功能不好用。 */
|
||
bool pca9555_dir_safe(int port, uint8_t dir);
|
||
|
||
/* ---- 器件操作(当前 mux 通道上那一片) ----------------------------------- */
|
||
|
||
/* 认件:读回两个方向寄存器,全 0xFF(复位值)才算是它。
|
||
不应答 → HAL_ERR_NAK;应答了但方向寄存器不是复位值 → HAL_ERR_BUS。
|
||
⚠️ 已经被 `pca9555_init()` 配过的片子会返回 HAL_ERR_BUS —— 这是对的:
|
||
probe 的语义是「这是一片刚上电的 PCA9555 吗」。 */
|
||
hal_err_t pca9555_probe(void);
|
||
|
||
/* 按文件头那个框里的顺序初始化,并**逐个回读校验**。
|
||
`slot` 是 0~`PCA9555_SLOT_COUNT-1`,**只用来记影子**,不参与总线操作 ——
|
||
通道必须由调用方先选好(本驱动不碰 mux)。 */
|
||
hal_err_t pca9555_init(int slot);
|
||
|
||
/* 巡检主路径:**一次事务**读两个输入端口(PCA9555 的读指针在 0x00 / 0x01 之间
|
||
toggle,这两个字节是 datasheet 保证的)。 */
|
||
typedef struct {
|
||
uint8_t port0;
|
||
uint8_t port1;
|
||
} pca9555_in_t;
|
||
|
||
hal_err_t pca9555_read_inputs(pca9555_in_t *out);
|
||
|
||
/* ---- Port0 输出:影子 + 唯一写入点 --------------------------------------- */
|
||
|
||
/* 改 `mask` 指定的那几位为 `value` 里的值,其余位保持影子里的值,整字节写下去。
|
||
组合键的两位落在**同一次写**里(§10.6:分两次写会先单按一个键)。
|
||
|
||
⚠️ **`slot` 必须与当前 mux 通道对应的那一路一致。** 驱动没法自己验证这件事,
|
||
传错的后果是影子记在了另一片上 —— 下一次写会把这一片的键"顺手"改掉。
|
||
这就是 `slot.c` 存在的理由:别在 `slot.c` 之外调这个函数。
|
||
|
||
`mask` 里出现输出位以外的位 → HAL_ERR_ARG(不是 abort:那可能是上层算错了
|
||
掩码,返回错误码能让它记一条事件)。写失败时**影子不更新**。 */
|
||
hal_err_t pca9555_write_out0(int slot, uint8_t mask, uint8_t value);
|
||
|
||
uint8_t pca9555_shadow(int slot); /* 该片 Port0 输出锁存器的影子(init 后是 0) */
|
||
|
||
/* 把影子全部清成 0,**不碰总线**。给测试的 setUp() 与 A15 的降级路径用
|
||
(「现在生效的是上电默认值」)。 */
|
||
void pca9555_shadow_reset(void);
|
||
|
||
/* ---- 语义判读(Port1) ---------------------------------------------------
|
||
四个信号**两种极性**,这是本文件第二个容易写反的地方。极性只在下面四个函数里
|
||
各出现一次。上层一律问「有没有故障」,不许自己去 & 位。 */
|
||
|
||
/* TPS25940A 的 `FLT#`:开漏、上拉 ⇒ **低 = 有故障**。 */
|
||
bool pca9555_efuse_fault(const pca9555_in_t *in);
|
||
|
||
/* TPS25940A 的 `PGOOD`:开漏、上拉,输出过 2.97 V 才释放 ⇒ **高 = 电源良好**。 */
|
||
bool pca9555_efuse_pg(const pca9555_in_t *in);
|
||
|
||
/* 本路 Buck 的 `PWRGD` ⇒ 高 = 正常。
|
||
⚠️ R1 里这一位是**五路线或**,R2 里它只代表**本路**(一片刀片一个 Buck)。 */
|
||
bool pca9555_buck_pg(const pca9555_in_t *in);
|
||
|
||
/* 本片 INA226 的 `ALERT`:开漏、低有效 ⇒ **低 = 在告警**。
|
||
⚠️ R1 里是六片线或,R2 里刀片上的 ALERT 不跨板,只进本片的 P1.2。 */
|
||
bool pca9555_ina_alert(const pca9555_in_t *in);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* PCA9555_H */
|