/* 五路配置表 + 启动自检 + 两根 EN 线。契约与来历见 rail_cfg.h。 */ #include "rail_cfg.h" #include #include #include "board_pins.h" #include "dac63204.h" #include "hal_gpio.h" #include "ina226.h" #include "pca9555.h" #include "sr595.h" /* ---- 那张表 -------------------------------------------------------------- 六列的出处:INA 地址 §8.2;DAC 片/通道 §8.2 + §26.18;按键位与 EDL 位 §17.2 + §26.18 C(从 `.claude/pcb1-parts.json` 的真实焊盘网络逐脚核过); 两根 EN 的 GPIO §8.1(同样按 U20 的焊盘网络核过,2026-09-07)。 ⚠️ **一列都不许改成算式。** 六列里没有一列是顺排的。 */ static const rail_cfg_t k_cfg[PHONERAIL_RAIL_COUNT] = { /* INA DAC片 ch EFUSE VBUS PWR VUP VDN EDL FLT PG */ /* CH1 */ { 0x40, 0x4A, 2, 39, 40, { 1, 2, 3 }, 16, 0, 0 }, /* CH2 */ { 0x41, 0x4A, 0, 48, 10, { 19, 17, 18 }, 7, 1, 1 }, /* CH3 */ { 0x44, 0x4B, 0, 6, 11, { 22, 21, 20 }, 6, 2, 2 }, /* CH4 */ { 0x45, 0x4A, 1, 41, 42, { 9, 10, 11 }, 0, 3, 3 }, /* CH5 */ { 0x42, 0x4B, 1, 8, 17, { 15, 14, 13 }, 5, 4, 4 }, }; _Static_assert(PHONERAIL_RAIL_COUNT == INA226_RAIL_COUNT, "五路数对不上 INA226"); _Static_assert(PHONERAIL_RAIL_COUNT == DAC63204_RAIL_COUNT, "五路数对不上 DAC63204"); _Static_assert(PHONERAIL_RAIL_COUNT == PCA9555_RAIL_COUNT, "五路数对不上 PCA9555"); const rail_cfg_t *rail_cfg(int rail) { assert(rail >= 1 && rail <= PHONERAIL_RAIL_COUNT); /* 悄悄返回 CH1 比崩掉危险 */ return &k_cfg[rail - 1]; } /* ---- 自检之一:本表与四个驱动的表逐格对账 -------------------------------- 分家的现象是「命令发给了另一路」,功能测试测不出来,所以这里是**逐格**比, 不是抽查。四个驱动的取值函数越界会 abort,但这里的 rail 恒在 1~5。 */ static bool table_matches_drivers(const rail_cfg_t *t) { for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) { const rail_cfg_t *c = &t[rail - 1]; if (c->ina_addr != ina226_rail_addr(rail)) { return false; } if (c->dac_addr != dac63204_rail_addr(rail) || c->dac_ch != dac63204_rail_ch(rail)) { return false; } for (int k = 0; k < SR595_KEY_COUNT; k++) { if (c->key_bit[k] != (uint8_t)sr595_key_bit(rail, (sr595_key_t)k)) { return false; } } if (c->edl_bit != (uint8_t)sr595_edl_bit(rail)) { return false; } if (c->flt_bit != (uint8_t)pca9555_flt_bit(rail)) { return false; } if (c->pg_bit != (uint8_t)pca9555_pg_bit(rail)) { return false; } } return true; } /* ---- 自检之二:引脚无重复、无越界 ---------------------------------------- 查的是**全板**,不只是五路那十个脚:重复的两个脚一定分属两个模块, 只查自己那十个的话,「`VBUS_EN2` 写成了 IO14」这种错(撞上 SDA)查不出来。 */ static bool pin_in(const int *list, size_t n, int pin) { for (size_t i = 0; i < n; i++) { if (list[i] == pin) { return true; } } return false; } static bool pin_usable(int pin) { static const int absent[] = BOARD_PINS_ABSENT; static const int strapping[] = BOARD_PINS_STRAPPING; static const int reserved[] = BOARD_PINS_RESERVED; if (pin < 0 || pin > BOARD_PIN_NUM_MAX) { return false; } if (pin_in(absent, sizeof(absent) / sizeof(absent[0]), pin)) { return false; /* 这个号在 S3 上根本不存在,或者接着模组内部的 flash */ } if (pin_in(strapping, sizeof(strapping) / sizeof(strapping[0]), pin)) { return false; } if (pin_in(reserved, sizeof(reserved) / sizeof(reserved[0]), pin)) { return false; /* R8 版八线 PSRAM 占着,留空以便换料 */ } return true; } static bool pins_ok(const rail_cfg_t *t) { /* 全板用掉的每一个脚。顺序无所谓,这里只查「都能用」和「不重复」。 */ int used[2 * PHONERAIL_RAIL_COUNT + 14]; size_t n = 0; for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) { used[n++] = t[rail - 1].efuse_en; used[n++] = t[rail - 1].vbus_en; } used[n++] = BOARD_PIN_I2C_SDA; used[n++] = BOARD_PIN_I2C_SCL; used[n++] = BOARD_PIN_FAN_TACH; used[n++] = BOARD_PIN_FAN_PWM; used[n++] = BOARD_PIN_UP_VBUS_DET; used[n++] = BOARD_PIN_NTC1; used[n++] = BOARD_PIN_NTC2; used[n++] = BOARD_PIN_USB_DM; used[n++] = BOARD_PIN_USB_DP; /* 四根移位链的线与 PCA9555 的中断脚:**引用驱动里的宏,不重抄一遍数字**。 */ used[n++] = SR595_PIN_SER; used[n++] = SR595_PIN_SRCLK; used[n++] = SR595_PIN_RCLK; used[n++] = SR595_PIN_OE_N; used[n++] = PCA9555_PIN_INT_N; assert(n == sizeof(used) / sizeof(used[0])); for (size_t i = 0; i < n; i++) { if (!pin_usable(used[i])) { return false; } for (size_t j = i + 1; j < n; j++) { if (used[i] == used[j]) { return false; /* 两个信号抢同一个脚 */ } } } /* 两路 NTC 必须在 ADC1(IO1~IO10)上:ADC2 在 WiFi 开着的时候读不了, 而本板 WiFi 是常开的(§10.8 传输 A)。挪到 ADC2 上不会报错, 只会在连上 WiFi 之后开始读失败 —— 台架上很难复现。 */ if (BOARD_PIN_NTC1 < 1 || BOARD_PIN_NTC1 > 10 || BOARD_PIN_NTC2 < 1 || BOARD_PIN_NTC2 > 10) { return false; } return true; } /* ---- 启动自检 ------------------------------------------------------------ */ uint32_t rail_selftest_table(const rail_cfg_t *t) { uint32_t bad = 0; if (t == NULL) { return RAIL_SELFTEST_TABLE | RAIL_SELFTEST_PINS; } if (!table_matches_drivers(t)) { bad |= RAIL_SELFTEST_TABLE; } if (!pins_ok(t)) { bad |= RAIL_SELFTEST_PINS; } return bad; } uint32_t rail_selftest(void) { uint32_t bad = 0; /* 四个驱动各自的表(A3~A6 都留了这个谓词,A7 是它们的归宿)。 */ if (!ina226_addr_table_ok()) { bad |= RAIL_SELFTEST_INA; } if (!dac63204_map_ok()) { bad |= RAIL_SELFTEST_DAC; } if (!sr595_map_ok()) { bad |= RAIL_SELFTEST_SR595; } if (!pca9555_map_ok() || !pca9555_addr_ok()) { bad |= RAIL_SELFTEST_PCA9555; } return bad | rail_selftest_table(k_cfg); } const char *rail_selftest_str(uint32_t bit) { switch (bit) { case RAIL_SELFTEST_INA: return "INA226 地址表"; case RAIL_SELFTEST_DAC: return "DAC63204 映射表"; case RAIL_SELFTEST_SR595: return "74HC595 位表"; case RAIL_SELFTEST_PCA9555: return "PCA9555 位表/地址"; case RAIL_SELFTEST_TABLE: return "五路表与驱动分家"; case RAIL_SELFTEST_PINS: return "引脚重复或越界"; default: return "?"; } } /* ---- 两根 EN 线 ---------------------------------------------------------- */ /* 高有效。**全固件唯一一处极性**(和 `sr595_set_hub_reset()` 里那个反相、 `rail_mv_to_code()` 里那个减法同一个道理:多一处就多一处写反的机会)。 */ static hal_err_t en_write(int pin, bool on) { return hal_gpio_write(pin, on ? 1 : 0); } hal_err_t rail_en_init(void) { for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) { const rail_cfg_t *c = &k_cfg[rail - 1]; const int pins[2] = { c->efuse_en, c->vbus_en }; for (int i = 0; i < 2; i++) { hal_err_t err = hal_gpio_config(pins[i], HAL_GPIO_OUTPUT); if (err != HAL_OK) { return err; } /* 配成输出的那一刻两边的 HAL 都已经在输出 0 了,这一句是把「关」 写成明文 —— 别让「五路上电即全关」依赖某个默认值。 */ err = en_write(pins[i], false); if (err != HAL_OK) { return err; } } } return HAL_OK; } hal_err_t rail_efuse_enable(int rail, bool on) { if (rail < 1 || rail > PHONERAIL_RAIL_COUNT) { return HAL_ERR_ARG; } return en_write(k_cfg[rail - 1].efuse_en, on); } hal_err_t rail_vbus_enable(int rail, bool on) { if (rail < 1 || rail > PHONERAIL_RAIL_COUNT) { return HAL_ERR_ARG; } return en_write(k_cfg[rail - 1].vbus_en, on); } hal_err_t rail_all_off(void) { hal_err_t first = HAL_OK; /* 先断五路 VBUS,再关五路 eFuse:反过来的话中间有一小段「手机只靠 USB 供着电」,那既不是开也不是关。十个都要试,不许中途 return。 */ for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) { hal_err_t err = rail_vbus_enable(rail, false); if (err != HAL_OK && first == HAL_OK) { first = err; } } for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) { hal_err_t err = rail_efuse_enable(rail, false); if (err != HAL_OK && first == HAL_OK) { first = err; } } return first; }