/* PCA9555 驱动。只经 hal_i2c_*,不碰任何平台头文件。 */ #include "pca9555.h" #include #include #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 低有效 */ }