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]>
195 行
6.2 KiB
C
195 行
6.2 KiB
C
/* PCA9555 驱动。只经 hal_i2c_*,不碰任何平台头文件。 */
|
||
#include "pca9555.h"
|
||
|
||
#include <assert.h>
|
||
#include <stddef.h>
|
||
|
||
#include "hal_i2c.h"
|
||
|
||
/* 位表见 pca9555.h。这两张表**这次确实是顺排的**,但仍然写成表:
|
||
哪天硬件改一根线,改这里一处就够,而不是满工程找 `1 << (rail - 1)`。 */
|
||
static const uint8_t s_flt_bit[PCA9555_RAIL_COUNT] = { 0, 1, 2, 3, 4 }; /* Port0 */
|
||
static const uint8_t s_pg_bit[PCA9555_RAIL_COUNT] = { 0, 1, 2, 3, 4 }; /* Port1 */
|
||
|
||
int pca9555_flt_bit(int rail)
|
||
{
|
||
assert(rail >= 1 && rail <= PCA9555_RAIL_COUNT); /* 越界是调用方算错了,不许悄悄返回 */
|
||
return s_flt_bit[rail - 1];
|
||
}
|
||
|
||
int pca9555_pg_bit(int rail)
|
||
{
|
||
assert(rail >= 1 && rail <= PCA9555_RAIL_COUNT);
|
||
return s_pg_bit[rail - 1];
|
||
}
|
||
|
||
bool pca9555_map_ok(void)
|
||
{
|
||
uint8_t p0 = 0, p1 = 0;
|
||
|
||
for (int rail = 1; rail <= PCA9555_RAIL_COUNT; rail++) {
|
||
const uint8_t f = (uint8_t)(1u << s_flt_bit[rail - 1]);
|
||
const uint8_t g = (uint8_t)(1u << s_pg_bit[rail - 1]);
|
||
if ((p0 & f) != 0 || (p1 & g) != 0) {
|
||
return false; /* 两路占了同一位 */
|
||
}
|
||
p0 |= f;
|
||
p1 |= g;
|
||
}
|
||
|
||
const uint8_t buck = (uint8_t)(1u << PCA9555_BIT_BUCK_PG);
|
||
const uint8_t alrt = (uint8_t)(1u << PCA9555_BIT_INA_ALERT);
|
||
if ((p0 & buck) != 0 || (p0 & alrt) != 0 || buck == alrt) {
|
||
return false; /* 线或的两根撞到某路 FLT 上,或者撞到一起 */
|
||
}
|
||
p0 |= (uint8_t)(buck | alrt);
|
||
|
||
/* 一个信号都不许落到接地脚上 —— 落上去就等于永远读到 0(FLT 永远报故障,
|
||
PG 永远报「不良」),而 I²C 一切正常。 */
|
||
if ((p0 & PCA9555_P0_GND_MASK) != 0 || (p1 & PCA9555_P1_GND_MASK) != 0) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool pca9555_addr_ok(void)
|
||
{
|
||
/* 同一条 I²C 上的其它器件(§8.2)。 */
|
||
static const uint8_t others[] = { 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x48, 0x4A, 0x4B };
|
||
|
||
if (PCA9555_ADDR > 0x7f) {
|
||
return false;
|
||
}
|
||
for (size_t i = 0; i < sizeof(others); i++) {
|
||
if (others[i] == PCA9555_ADDR) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool pca9555_dir_safe(int port, uint8_t dir)
|
||
{
|
||
if (port != 0 && port != 1) {
|
||
return false;
|
||
}
|
||
/* 1 = 输入。接地脚对应的位必须是 1,一位都不能少。 */
|
||
const uint8_t gnd = (port == 0) ? PCA9555_P0_GND_MASK : PCA9555_P1_GND_MASK;
|
||
return (dir & gnd) == gnd;
|
||
}
|
||
|
||
/* ---- 寄存器读写 ---------------------------------------------------------
|
||
PCA9555 的寄存器是 8 位的,命令字节在**成对**的寄存器之间 toggle
|
||
(0/1、2/3、4/5、6/7),不是无限自增。本驱动只在一对之内连读 2 字节,
|
||
落在 datasheet 保证的范围里;跨对连读(比如从 0x06 读 4 字节)行为不同,
|
||
别那么写。 */
|
||
|
||
static hal_err_t read_reg(uint8_t reg, uint8_t *val)
|
||
{
|
||
return hal_i2c_write_read(PCA9555_ADDR, ®, 1, val, 1);
|
||
}
|
||
|
||
static hal_err_t write_reg(uint8_t reg, uint8_t val)
|
||
{
|
||
const uint8_t wr[2] = { reg, val };
|
||
return hal_i2c_write(PCA9555_ADDR, wr, sizeof(wr));
|
||
}
|
||
|
||
/* 写完立刻回读比对。回读不一致 = 总线上有东西但不是我们以为的那片。 */
|
||
static hal_err_t write_verify(uint8_t reg, uint8_t val)
|
||
{
|
||
hal_err_t err = write_reg(reg, val);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
uint8_t rb = 0;
|
||
err = read_reg(reg, &rb);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
return (rb == val) ? HAL_OK : HAL_ERR_BUS;
|
||
}
|
||
|
||
hal_err_t pca9555_probe(void)
|
||
{
|
||
uint8_t c0 = 0, c1 = 0;
|
||
hal_err_t err = read_reg(PCA9555_REG_CONFIG0, &c0);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
err = read_reg(PCA9555_REG_CONFIG1, &c1);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
/* 刚上电的 PCA9555 两个方向寄存器都是 0xFF。不是的话,要么这地址上蹲着别的
|
||
器件,要么它已经被谁配过了 —— 两种都要在启动时就叫出来。 */
|
||
return (c0 == PCA9555_DIR_ALL_INPUT && c1 == PCA9555_DIR_ALL_INPUT) ? HAL_OK : HAL_ERR_BUS;
|
||
}
|
||
|
||
hal_err_t pca9555_init(void)
|
||
{
|
||
/* 方向必须先于一切:这一步写错就是四个接地脚被驱动成高电平。
|
||
全输入是唯一合法值,assert 拦的是「哪天有人顺手改了这个常量」。 */
|
||
assert(pca9555_dir_safe(0, PCA9555_DIR_ALL_INPUT));
|
||
assert(pca9555_dir_safe(1, PCA9555_DIR_ALL_INPUT));
|
||
|
||
hal_err_t err = write_verify(PCA9555_REG_CONFIG0, PCA9555_DIR_ALL_INPUT);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
err = write_verify(PCA9555_REG_CONFIG1, PCA9555_DIR_ALL_INPUT);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
|
||
/* 极性反转清零。复位值本来就是 0,但「本来就是」不值得赌 —— 看门狗复位
|
||
只复位 ESP32,PCA9555 是不掉电的,上一条命的残留值会原样留着。 */
|
||
err = write_verify(PCA9555_REG_POLINV0, PCA9555_POLINV_NONE);
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
return write_verify(PCA9555_REG_POLINV1, PCA9555_POLINV_NONE);
|
||
}
|
||
|
||
hal_err_t pca9555_read_inputs(pca9555_in_t *out)
|
||
{
|
||
if (out == NULL) {
|
||
return HAL_ERR_ARG;
|
||
}
|
||
const uint8_t reg = PCA9555_REG_INPUT0;
|
||
uint8_t rd[2] = { 0, 0 };
|
||
hal_err_t err = hal_i2c_write_read(PCA9555_ADDR, ®, 1, rd, sizeof(rd));
|
||
if (err != HAL_OK) {
|
||
return err;
|
||
}
|
||
out->port0 = rd[0];
|
||
out->port1 = rd[1];
|
||
return HAL_OK;
|
||
}
|
||
|
||
/* ---- 语义判读(极性各出现一次,见头文件) -------------------------------- */
|
||
|
||
bool pca9555_rail_fault(const pca9555_in_t *in, int rail)
|
||
{
|
||
assert(in != NULL);
|
||
return (in->port0 & (uint8_t)(1u << pca9555_flt_bit(rail))) == 0; /* FLT# 低有效 */
|
||
}
|
||
|
||
bool pca9555_rail_pg(const pca9555_in_t *in, int rail)
|
||
{
|
||
assert(in != NULL);
|
||
return (in->port1 & (uint8_t)(1u << pca9555_pg_bit(rail))) != 0; /* PGOOD 高 = 良好 */
|
||
}
|
||
|
||
bool pca9555_buck_pg(const pca9555_in_t *in)
|
||
{
|
||
assert(in != NULL);
|
||
return (in->port0 & (uint8_t)(1u << PCA9555_BIT_BUCK_PG)) != 0; /* 高 = 五路都正常 */
|
||
}
|
||
|
||
bool pca9555_ina_alert(const pca9555_in_t *in)
|
||
{
|
||
assert(in != NULL);
|
||
return (in->port0 & (uint8_t)(1u << PCA9555_BIT_INA_ALERT)) == 0; /* ALERT 低有效 */
|
||
}
|