U23 @ 0x20,16 个脚全部是输入:5×EFUSE_FLT# + 5×EFUSE_PGOOD + BUCK_PG + INA_ALERT + 4 个焊死在地上的备用脚(P0_7 / P1_5~7)。位表与地址从 .claude/pcb1-parts.json 的真实焊盘网络逐脚核回来,与设计文档 §17.2 一致。 驱动根本不提供「把某个脚配成输出」的接口(和 sr595.h 不提供「抬 OE#」同一条 规矩),方向字一律过 pca9555_dir_safe(),不安全直接 abort。原因是 PCA9555 的 Output Port 寄存器复位值是 0xFF —— 把接地脚配成输出的那一刻它就输出高电平, 而真器件既不报错也不 NAK,只是发烫。 三种极性(FLT# 低有效 / PGOOD 高有效 / INA_ALERT 低有效)各自只在一个函数里 出现一次;上层问「有没有故障」,不许自己去 & 位。 sim 侧新增按 datasheet 建模的假 PCA9555:Output 复位 0xFF、输入按位合成、 接地脚被驱动时记短路、INT# 只被「读输入端口」清除。为此给假 I²C 总线加了一个 窃听钩子(POST_WRITE / PRE_READ)—— 通用寄存器表模拟不了「配成输出就对地 短路」这类事,以后再有这种器件照这个挂。 验收:8 条 ctest(81 用例)全绿;idf.py build 通过,pca9555.c.obj 在。 12 个「一定会被写出来的错」逐个注进去跑过,全部变红。 Co-Authored-By: Claude Opus 5 <[email protected]>
140 行
6.6 KiB
C
140 行
6.6 KiB
C
/* PCA9555(U23,地址 0x20)—— 板上唯一的 I/O 扩展器,**16 个脚全部是输入**。
|
||
|
||
挂的是五路 eFuse 的 FLT#/PGOOD、Buck 的线或 PWRGD、六片 INA226 的线或 ALERT,
|
||
共 12 个信号 + 4 个直接接地的备用脚(设计文档 §17.2)。
|
||
|
||
⚠️ **本驱动不提供「把某个脚配成输出」的接口,一个都不提供。**
|
||
四个备用脚 P0_7 / P1_5~7 在板上**直接接地、不串限流电阻**(§17.2 的注,
|
||
图纸上已标)。而 PCA9555 的 Output Port 寄存器**复位值是 0xFF**——
|
||
所以只要把其中一个配成输出,它下一刻就输出高电平,**直接对地短路**。
|
||
配错既不会报错也不会 NAK,只会发烫。所以方向字一律走 pca9555_dir_safe(),
|
||
不安全直接 abort。这跟 sr595.h 不提供「抬 OE#」是同一条规矩。
|
||
|
||
§10.10 的降级行为:这片不应答 = 失去 FAULT / PG 可见性,**不关输出**,
|
||
改用 INA226 的电压判据兜底并持续告警。所以本驱动的读失败一律只是返回错误码,
|
||
不做任何动作。
|
||
|
||
本驱动只经 hal_i2c_*,宿主机上由 sim 的假 PCA9555(`sim_pca9555_*`)全部测完。 */
|
||
#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(U23 的 pin21 / pin2 / pin3,已按 PCB 焊盘网络核过)。 */
|
||
#define PCA9555_ADDR 0x20
|
||
|
||
/* `IOEXP_INT#`:开漏、低有效、板上 R608 10 kΩ 上拉,直连 ESP32 IO38(§8.1)。
|
||
⚠️ **中断只是加速路径,不是主路径**:§10.3 的 `t_safety` 每 20 ms 无条件轮询
|
||
读一次两个输入端口。原因是 PCA9555 的 INT 要**读走输入端口才会释放**,而且
|
||
输入自己变回原值时它也会释放 —— 只靠中断的话,漏掉一次读就再也等不到下一个沿。
|
||
A6 只留这个宏,读它是 A8/A10 的活。 */
|
||
#define PCA9555_PIN_INT_N 38
|
||
|
||
/* ---- 寄存器(全部 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
|
||
|
||
/* 极性反转寄存器必须是 0。写成非 0 的现象是「五路同时报故障」或者更糟的
|
||
「五路故障全都看不见」,而 I²C 一切正常 —— init 会写 0 并回读校验。 */
|
||
#define PCA9555_POLINV_NONE 0x00u
|
||
|
||
/* ---- 位分配(从 `.claude/pcb1-parts.json` 的真实焊盘网络逐脚核对) -------
|
||
|
||
Port0 bit0~4 = EFUSE_FLT1~5 bit5 = BUCK_PG bit6 = INA_ALERT bit7 = 接地
|
||
Port1 bit0~4 = EFUSE_PG1~5 bit5~7 = 接地
|
||
|
||
这一次两张表**确实是顺排的**(`bit = rail - 1`),和 INA226 地址、595 位号
|
||
那两张表不一样。所以这里最容易写出来的错不是「算式」,而是
|
||
**把 FLT 和 PG 的端口搞反** —— 两个端口的 bit0~4 长得一模一样,
|
||
搞反之后「有故障」会被读成「电源良好」,方向正好相反。
|
||
所以取位号一律走下面两个函数,不要在别处写 `1 << (rail - 1)`。 */
|
||
|
||
#define PCA9555_RAIL_COUNT 5
|
||
|
||
#define PCA9555_BIT_BUCK_PG 5 /* Port0,五路 Buck 的 PWRGD 线或(都是开漏) */
|
||
#define PCA9555_BIT_INA_ALERT 6 /* Port0,六片 INA226 的 ALERT 线或 */
|
||
|
||
/* 接地的备用脚。**永远、永远是输入。** */
|
||
#define PCA9555_P0_GND_MASK 0x80u /* P0_7 */
|
||
#define PCA9555_P1_GND_MASK 0xE0u /* P1_5 P1_6 P1_7 */
|
||
|
||
/* rail 是 **1~5**,越界 abort。返回的是**位号**(0~4),不是掩码。 */
|
||
int pca9555_flt_bit(int rail); /* Port0 */
|
||
int pca9555_pg_bit(int rail); /* Port1 */
|
||
|
||
/* 12 个信号位互不重复、都没落到接地脚上、FLT 与 PG 分属两个端口。
|
||
启动自检调一次(A7,和 ina226_addr_table_ok() / sr595_map_ok() 一起)。 */
|
||
bool pca9555_map_ok(void);
|
||
|
||
/* 0x20 不与 INA226(0x40/41/44/45/42/43) / DAC63204(0x4A/0x4B) / LM75(0x48) 撞车。 */
|
||
bool pca9555_addr_ok(void);
|
||
|
||
/* dir 里 1 = 输入。返回 false = 这个方向字会把某个**接地脚**配成输出。
|
||
驱动内部走到 false 直接 abort:悄悄放过去的后果是板子发烫,不是功能不好用。 */
|
||
bool pca9555_dir_safe(int port, uint8_t dir);
|
||
|
||
/* ---- 采样 ---------------------------------------------------------------- */
|
||
|
||
typedef struct {
|
||
uint8_t port0;
|
||
uint8_t port1;
|
||
} pca9555_in_t;
|
||
|
||
/* 认件:读回两个方向寄存器,全 0xFF(复位值)才算是它。
|
||
不应答 → HAL_ERR_NAK;应答了但方向寄存器不是复位值 → HAL_ERR_BUS
|
||
(这个地址上有东西,但不是一片刚上电的 PCA9555)。 */
|
||
hal_err_t pca9555_probe(void);
|
||
|
||
/* 写方向(全输入)与极性反转(全 0)并**逐个回读校验**,回读不一致 → HAL_ERR_BUS。
|
||
⚠️ Output Port 一个字节都不写:写它没有意义(没有输出脚),而写错方向的
|
||
后果见文件头。 */
|
||
hal_err_t pca9555_init(void);
|
||
|
||
/* 巡检主路径:**一次事务**读两个输入端口(§10.3 的 0.12 ms 预算就是按一次算的;
|
||
PCA9555 的读指针在 0x00 / 0x01 之间 toggle,这两个字节是 datasheet 保证的)。 */
|
||
hal_err_t pca9555_read_inputs(pca9555_in_t *out);
|
||
|
||
/* ---- 语义判读 ------------------------------------------------------------
|
||
四个信号**三种极性**,这是本文件第二个容易写反的地方。极性只在下面四个函数里
|
||
各出现一次(和 sr595_set_hub_reset() 里那个反相同一个道理:多一处就多一处写反
|
||
的机会)。上层一律问「有没有故障」,不许自己去 & 位。 */
|
||
|
||
/* TPS25940A 的 `FLT#`:开漏、板上 R113 47 kΩ 上拉 ⇒ **低 = 有故障**。 */
|
||
bool pca9555_rail_fault(const pca9555_in_t *in, int rail);
|
||
|
||
/* TPS25940A 的 `PGOOD`:开漏、R115 47 kΩ 上拉,输出 2.97 V 以上才释放
|
||
⇒ **高 = 电源良好**。 */
|
||
bool pca9555_rail_pg(const pca9555_in_t *in, int rail);
|
||
|
||
/* 五路 Buck 的 `PWRGD` 线或(开漏)⇒ **高 = 五路都正常**;低只说明「有某一路
|
||
不正常」,看不出是哪一路(§16.1)。 */
|
||
bool pca9555_buck_pg(const pca9555_in_t *in);
|
||
|
||
/* 六片 INA226 的 `ALERT` 线或:开漏、低有效 ⇒ **低 = 有片子在告警**。 */
|
||
bool pca9555_ina_alert(const pca9555_in_t *in);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* PCA9555_H */
|