/* A7:五路配置表 + 表驱动遍历 + 启动自检(需求 §10.4)。 验收主项:**启动自检 —— 五路的地址 / 位号 / 引脚无重复、无越界。** 第二主项:那张五路表和四个驱动自己的表**没有分家**(逐格比,不是抽查)。 第三主项:**没有一列能用通道号算出来** —— 这一片最容易写出来的错就是 哪天有人把表换回 `0x40 + rail`,那之后板子照样跑,只是命令发给了另一路。 */ #include "board_pins.h" #include "dac63204.h" #include "hal_gpio.h" #include "ina226.h" #include "pca9555.h" #include "phonerail.h" #include "rail_cfg.h" #include "sim_hal.h" #include "sr595.h" #include "unity.h" #define N PHONERAIL_RAIL_COUNT void setUp(void) { sim_hal_reset(); } void tearDown(void) {} /* ---- 验收主项一:自检 ----------------------------------------------------- */ static void test_selftest_passes(void) { TEST_ASSERT_EQUAL_HEX32(0, rail_selftest()); } /* 自检里的六项每一项都要真的被算过。这条挨个调它们背后的谓词 —— `rail_selftest()` 要是哪天被改成 `return 0;`,上一条测试照样绿。 */ static void test_selftest_covers_all_four_driver_tables(void) { TEST_ASSERT_TRUE(ina226_addr_table_ok()); TEST_ASSERT_TRUE(dac63204_map_ok()); TEST_ASSERT_TRUE(sr595_map_ok()); TEST_ASSERT_TRUE(pca9555_map_ok()); TEST_ASSERT_TRUE(pca9555_addr_ok()); } static void test_selftest_str_names_every_bit(void) { static const uint32_t bits[] = { RAIL_SELFTEST_INA, RAIL_SELFTEST_DAC, RAIL_SELFTEST_SR595, RAIL_SELFTEST_PCA9555, RAIL_SELFTEST_TABLE, RAIL_SELFTEST_PINS, }; for (unsigned i = 0; i < sizeof(bits) / sizeof(bits[0]); i++) { TEST_ASSERT_TRUE(rail_selftest_str(bits[i])[0] != '?'); } TEST_ASSERT_EQUAL_STRING("?", rail_selftest_str(0)); TEST_ASSERT_EQUAL_STRING("?", rail_selftest_str(1u << 31)); /* 两位一起传进来也是 "?":调用方该逐位拆。 */ TEST_ASSERT_EQUAL_STRING("?", rail_selftest_str(RAIL_SELFTEST_INA | RAIL_SELFTEST_DAC)); } /* 自检**逻辑本身**得被测:拿真表跑一遍只能证明「现在没事」。 真表是 const 的,改不动,所以这里复制一份出来逐样改坏,看它认不认。 这几条同时也是「哪天有人把某个判据删了」的拦网。 */ static void copy_real_table(rail_cfg_t *dst) { for (int rail = 1; rail <= N; rail++) { dst[rail - 1] = *rail_cfg(rail); } } static void test_a_faithful_copy_of_the_real_table_passes(void) { rail_cfg_t t[N]; copy_real_table(t); TEST_ASSERT_EQUAL_HEX32(0, rail_selftest_table(t)); /* 真表本身走的也是这条路 —— rail_cfg(1) 就是那张表的首行。 */ TEST_ASSERT_EQUAL_HEX32(0, rail_selftest_table(rail_cfg(1))); TEST_ASSERT_TRUE((rail_selftest_table(NULL) & RAIL_SELFTEST_TABLE) != 0); } /* 表和驱动分家:只改一格,而且改成一个**合法**的地址(0x43 是输入侧那片)。 这一手光看「地址是不是合法、有没有重复」是查不出来的。 */ static void test_a_cell_that_drifted_from_the_driver_is_caught(void) { rail_cfg_t t[N]; copy_real_table(t); t[2].ina_addr = 0x43; TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_TABLE, rail_selftest_table(t)); copy_real_table(t); t[0].dac_ch = 0; /* CH1 从 U6.OUT2 挪到 OUT0 —— 那是 CH2 的通道 */ TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_TABLE, rail_selftest_table(t)); copy_real_table(t); t[3].key_bit[SR595_KEY_VUP] = t[3].key_bit[SR595_KEY_VDN]; /* 音量键上下写反的近亲 */ TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_TABLE, rail_selftest_table(t)); copy_real_table(t); t[1].pg_bit = 4; TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_TABLE, rail_selftest_table(t)); } /* 引脚:重复 / 撞上别人 / strapping / 不存在的号 / 越界,五种都要认出来。 */ static void test_every_kind_of_bad_pin_is_caught(void) { rail_cfg_t t[N]; copy_real_table(t); t[1].vbus_en = t[0].vbus_en; /* 两路共用一根 VBUS_EN */ TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_PINS, rail_selftest_table(t)); copy_real_table(t); t[2].efuse_en = BOARD_PIN_I2C_SDA; /* 撞上 SDA:I²C 直接哑掉 */ TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_PINS, rail_selftest_table(t)); copy_real_table(t); t[0].efuse_en = 45; /* strapping */ TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_PINS, rail_selftest_table(t)); copy_real_table(t); t[4].vbus_en = 24; /* S3 上根本没有 IO24:gpio_config() 不报错,什么也不做 */ TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_PINS, rail_selftest_table(t)); copy_real_table(t); t[4].vbus_en = 36; /* R8 版八线 PSRAM 占着 */ TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_PINS, rail_selftest_table(t)); copy_real_table(t); t[3].efuse_en = BOARD_PIN_NUM_MAX + 1; TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_PINS, rail_selftest_table(t)); copy_real_table(t); t[3].efuse_en = -1; TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_PINS, rail_selftest_table(t)); /* 撞上 595 那四根线也算 —— 它们不在 board_pins.h 里,是从驱动的宏取的。 */ copy_real_table(t); t[0].vbus_en = SR595_PIN_RCLK; TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_PINS, rail_selftest_table(t)); } /* 两样一起坏,两位一起报 —— 别在第一个错上就 return。 */ static void test_two_kinds_of_breakage_report_two_bits(void) { rail_cfg_t t[N]; copy_real_table(t); t[2].ina_addr = 0x43; t[0].efuse_en = 45; TEST_ASSERT_EQUAL_HEX32(RAIL_SELFTEST_TABLE | RAIL_SELFTEST_PINS, rail_selftest_table(t)); } /* ---- 验收主项二:无重复 --------------------------------------------------- */ /* 六组编号,每一组五路两两不同。撞了的现象都是「两路共用一个东西」: 共用 INA 地址 = 两路读到同一个电流;共用 (DAC 片, 通道) = 调这路那路也动; 共用按键位 = 按 CH1 的电源键 CH4 也被按。 */ static void test_no_two_rails_share_anything(void) { for (int a = 1; a <= N; a++) { const rail_cfg_t *x = rail_cfg(a); for (int b = a + 1; b <= N; b++) { const rail_cfg_t *y = rail_cfg(b); TEST_ASSERT_NOT_EQUAL(x->ina_addr, y->ina_addr); TEST_ASSERT_FALSE(x->dac_addr == y->dac_addr && x->dac_ch == y->dac_ch); TEST_ASSERT_NOT_EQUAL(x->edl_bit, y->edl_bit); TEST_ASSERT_NOT_EQUAL(x->flt_bit, y->flt_bit); TEST_ASSERT_NOT_EQUAL(x->pg_bit, y->pg_bit); TEST_ASSERT_NOT_EQUAL(x->efuse_en, y->efuse_en); TEST_ASSERT_NOT_EQUAL(x->vbus_en, y->vbus_en); for (int i = 0; i < SR595_KEY_COUNT; i++) { TEST_ASSERT_NOT_EQUAL(x->key_bit[i], y->edl_bit); TEST_ASSERT_NOT_EQUAL(y->key_bit[i], x->edl_bit); for (int j = 0; j < SR595_KEY_COUNT; j++) { TEST_ASSERT_NOT_EQUAL(x->key_bit[i], y->key_bit[j]); } } } /* 同一路自己的三个键也不许撞,而且都不许撞自己的 EDL 位。 */ for (int i = 0; i < SR595_KEY_COUNT; i++) { TEST_ASSERT_NOT_EQUAL(x->key_bit[i], x->edl_bit); for (int j = i + 1; j < SR595_KEY_COUNT; j++) { TEST_ASSERT_NOT_EQUAL(x->key_bit[i], x->key_bit[j]); } } /* 一路自己的两根 EN 更不能是同一个脚。 */ TEST_ASSERT_NOT_EQUAL(x->efuse_en, x->vbus_en); } } /* 24 位链上还蹲着 `HUB_RST` 和三个空脚 —— 五路的 20 个位一个都不许踩上去。 */ static void test_shift_chain_bits_avoid_hub_reset_and_the_unused_pins(void) { uint32_t seen = 0; for (int rail = 1; rail <= N; rail++) { const rail_cfg_t *c = rail_cfg(rail); for (int i = 0; i < SR595_KEY_COUNT; i++) { seen |= (uint32_t)1u << c->key_bit[i]; } seen |= (uint32_t)1u << c->edl_bit; } TEST_ASSERT_EQUAL_HEX32(0, seen & SR595_MASK_UNUSED); TEST_ASSERT_EQUAL_HEX32(0, seen & ((uint32_t)1u << SR595_BIT_HUB_RST)); TEST_ASSERT_EQUAL_HEX32(seen, seen & SR595_MASK_VALID); /* 5×3 键 + 5 EDL = 20 位,一位不多一位不少。 */ int n = 0; for (int b = 0; b < SR595_BITS; b++) { if ((seen & ((uint32_t)1u << b)) != 0) { n++; } } TEST_ASSERT_EQUAL_INT(20, n); } /* ---- 验收主项二续:无越界 ------------------------------------------------- */ static void test_every_field_is_in_range(void) { for (int rail = 1; rail <= N; rail++) { const rail_cfg_t *c = rail_cfg(rail); TEST_ASSERT_TRUE(c->ina_addr <= 0x7f); TEST_ASSERT_TRUE(c->dac_addr == DAC63204_ADDR_U6 || c->dac_addr == DAC63204_ADDR_U7); TEST_ASSERT_TRUE(c->dac_ch <= 3); /* 该通道必须在那片的「真的接了线」位图里 —— 没接线的通道 FBx 悬空, 上电就是开环(datasheet 表 5-1)。 */ TEST_ASSERT_TRUE((dac63204_used_mask(c->dac_addr) & (1u << c->dac_ch)) != 0); for (int i = 0; i < SR595_KEY_COUNT; i++) { TEST_ASSERT_TRUE(c->key_bit[i] < SR595_BITS); } TEST_ASSERT_TRUE(c->edl_bit < SR595_BITS); TEST_ASSERT_TRUE(c->flt_bit < PCA9555_RAIL_COUNT); /* Port0 bit0~4 */ TEST_ASSERT_TRUE(c->pg_bit < PCA9555_RAIL_COUNT); /* Port1 bit0~4 */ } } /* FLT / PG 分属两个端口,所以两者位号相同是**对的**,不是撞车。 这条把「顺手给 PG 表加个偏移让它和 FLT 不一样」这种好心办坏事拦下来。 */ static void test_flt_and_pg_live_on_different_ports_so_equal_bits_are_fine(void) { for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_INT(pca9555_flt_bit(rail), rail_cfg(rail)->flt_bit); TEST_ASSERT_EQUAL_INT(pca9555_pg_bit(rail), rail_cfg(rail)->pg_bit); } } /* ---- 验收主项三:一列都不许是算式 ----------------------------------------- */ /* 六组编号里但凡有一组能被 `rail` 算出来,就会有人把它算出来。 这条**不去猜别人会写哪个算式**(猜错了就成了一句空话),而是直接断言那几列 「不是等差数列」—— 任何 `起点 + 步长 × ch` 形式的算式都被它一次拦掉。 ⚠️ 逐个格子去撞算式是不管用的:CH4 的按键位正好是 9/10/11,CH1 的 EDL 位 正好是 16,单看一路都「像顺排」。看整列才看得出来。 */ static bool is_arithmetic(const int *v, int n) { const int step = v[1] - v[0]; for (int i = 2; i < n; i++) { if (v[i] - v[i - 1] != step) { return false; } } return true; } static void test_no_column_is_an_arithmetic_progression(void) { int ina[N], pwr[N], vup[N], vdn[N], edl[N], efuse[N], vbus[N]; for (int rail = 1; rail <= N; rail++) { const rail_cfg_t *c = rail_cfg(rail); ina[rail - 1] = c->ina_addr; pwr[rail - 1] = c->key_bit[SR595_KEY_PWR]; vup[rail - 1] = c->key_bit[SR595_KEY_VUP]; vdn[rail - 1] = c->key_bit[SR595_KEY_VDN]; edl[rail - 1] = c->edl_bit; efuse[rail - 1] = c->efuse_en; vbus[rail - 1] = c->vbus_en; } TEST_ASSERT_FALSE_MESSAGE(is_arithmetic(ina, N), "INA226 地址不是顺排(0x40+ch 是错的)"); TEST_ASSERT_FALSE_MESSAGE(is_arithmetic(pwr, N), "PWR 键位不是顺排"); TEST_ASSERT_FALSE_MESSAGE(is_arithmetic(vup, N), "VUP 键位不是顺排"); TEST_ASSERT_FALSE_MESSAGE(is_arithmetic(vdn, N), "VDN 键位不是顺排"); TEST_ASSERT_FALSE_MESSAGE(is_arithmetic(edl, N), "EDL 位不是顺排"); TEST_ASSERT_FALSE_MESSAGE(is_arithmetic(efuse, N), "EFUSE_EN 引脚不是顺排"); TEST_ASSERT_FALSE_MESSAGE(is_arithmetic(vbus, N), "VBUS_EN 引脚不是顺排"); } /* 需求 §10.4 点名的那个算式:`0x40 + ch`。**只有 CH3/CH4/CH5 是错的** —— 前两路对,所以「拿 CH1 试一下没问题」是这条错误最常见的验证方式。 */ static void test_ina_addr_formula_is_wrong_for_exactly_three_rails(void) { int wrong = 0; for (int rail = 1; rail <= N; rail++) { if (rail_cfg(rail)->ina_addr != (uint8_t)(0x40 + rail - 1)) { wrong++; } } TEST_ASSERT_EQUAL_INT(3, wrong); TEST_ASSERT_EQUAL_HEX8(0x40, rail_cfg(1)->ina_addr); /* 算式在这两路上是对的 */ TEST_ASSERT_EQUAL_HEX8(0x41, rail_cfg(2)->ina_addr); } /* DAC 那两列不是等差数列能描述的(片 + 通道两个量),单独钉死: 「前三路在 U6、通道号 = ch−1」这句话五路里有四路不成立。 */ static void test_dac_mapping_is_not_the_obvious_one(void) { int wrong = 0; for (int rail = 1; rail <= N; rail++) { const rail_cfg_t *c = rail_cfg(rail); if (c->dac_addr != ((rail <= 3) ? DAC63204_ADDR_U6 : DAC63204_ADDR_U7) || c->dac_ch != (uint8_t)((rail - 1) % 3)) { wrong++; } } TEST_ASSERT_EQUAL_INT_MESSAGE(4, wrong, "只有 CH5 正好撞上那个算式"); } /* FLT / PG 这两列**确实是顺排的**(§10.4 特意说明)。写在这里是为了让 「哪些列可以是算式」有个明面上的清单:以后有人给 PG 加个偏移,这条会红。 */ static void test_flt_and_pg_really_are_sequential(void) { for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_UINT8(rail - 1, rail_cfg(rail)->flt_bit); TEST_ASSERT_EQUAL_UINT8(rail - 1, rail_cfg(rail)->pg_bit); } } /* 抽三个最容易被「顺手改顺」的格子,直接钉死实测值(出处:§8.2 / §26.18 C / §8.1, 都从 `.claude/pcb1-parts.json` 的真实焊盘网络核过)。 */ static void test_the_three_most_surprising_cells(void) { TEST_ASSERT_EQUAL_HEX8_MESSAGE(0x44, rail_cfg(3)->ina_addr, "CH3 的 INA226 是 0x44 不是 0x42"); TEST_ASSERT_EQUAL_HEX8_MESSAGE(DAC63204_ADDR_U7, rail_cfg(3)->dac_addr, "CH3 的 DAC 在 U7"); TEST_ASSERT_EQUAL_INT_MESSAGE(0, rail_cfg(4)->edl_bit, "EDL_EN4 在 U21.QA,也就是 bit0"); TEST_ASSERT_EQUAL_UINT8_MESSAGE(2, rail_cfg(1)->dac_ch, "CH1 挂在 U6.OUT2,不是 OUT0"); TEST_ASSERT_EQUAL_INT_MESSAGE(48, rail_cfg(2)->efuse_en, "EFUSE_EN2 在 IO48"); } /* ---- 表与四个驱动没有分家 ------------------------------------------------- */ /* `rail_selftest()` 内部也比一遍,但那是**被测代码自己**在比。 这一条是外面独立比的:内部那段哪天被删了,这里照样红。 */ static void test_table_matches_every_driver_table(void) { for (int rail = 1; rail <= N; rail++) { const rail_cfg_t *c = rail_cfg(rail); TEST_ASSERT_EQUAL_HEX8(ina226_rail_addr(rail), c->ina_addr); TEST_ASSERT_EQUAL_HEX8(dac63204_rail_addr(rail), c->dac_addr); TEST_ASSERT_EQUAL_UINT8(dac63204_rail_ch(rail), c->dac_ch); TEST_ASSERT_EQUAL_INT(sr595_key_bit(rail, SR595_KEY_PWR), c->key_bit[SR595_KEY_PWR]); TEST_ASSERT_EQUAL_INT(sr595_key_bit(rail, SR595_KEY_VUP), c->key_bit[SR595_KEY_VUP]); TEST_ASSERT_EQUAL_INT(sr595_key_bit(rail, SR595_KEY_VDN), c->key_bit[SR595_KEY_VDN]); TEST_ASSERT_EQUAL_INT(sr595_edl_bit(rail), c->edl_bit); TEST_ASSERT_EQUAL_INT(pca9555_flt_bit(rail), c->flt_bit); TEST_ASSERT_EQUAL_INT(pca9555_pg_bit(rail), c->pg_bit); } } /* ---- 引脚 ----------------------------------------------------------------- */ static bool in_list(const int *v, int n, int pin) { for (int i = 0; i < n; i++) { if (v[i] == pin) { return true; } } return false; } /* 十根 EN 不许踩到 strapping / 不存在的号 / R8 版 PSRAM 占的号, 也不许撞上全板任何一个已经有主的脚。 */ static void test_en_pins_do_not_collide_with_anything_else(void) { static const int absent[] = BOARD_PINS_ABSENT; static const int strapping[] = BOARD_PINS_STRAPPING; static const int reserved[] = BOARD_PINS_RESERVED; static const int others[] = { BOARD_PIN_I2C_SDA, BOARD_PIN_I2C_SCL, BOARD_PIN_FAN_TACH, BOARD_PIN_FAN_PWM, BOARD_PIN_NTC1, BOARD_PIN_NTC2, BOARD_PIN_USB_DM, BOARD_PIN_USB_DP, BOARD_PIN_UP_VBUS_DET, SR595_PIN_SER, SR595_PIN_SRCLK, SR595_PIN_RCLK, SR595_PIN_OE_N, PCA9555_PIN_INT_N, }; for (int rail = 1; rail <= N; rail++) { const int pins[2] = { rail_cfg(rail)->efuse_en, rail_cfg(rail)->vbus_en }; for (int i = 0; i < 2; i++) { TEST_ASSERT_TRUE(pins[i] >= 0 && pins[i] <= BOARD_PIN_NUM_MAX); TEST_ASSERT_FALSE(in_list(absent, (int)(sizeof(absent) / sizeof(int)), pins[i])); TEST_ASSERT_FALSE(in_list(strapping, (int)(sizeof(strapping) / sizeof(int)), pins[i])); TEST_ASSERT_FALSE(in_list(reserved, (int)(sizeof(reserved) / sizeof(int)), pins[i])); TEST_ASSERT_FALSE(in_list(others, (int)(sizeof(others) / sizeof(int)), pins[i])); } } } /* ⚠️ 两列 GPIO 是**全固件唯一一处**出处 —— 没有第二张表能和它对账 (INA/DAC/595/PCA 那四列都有驱动那边兜着,这两列没有)。 所以这里把十个值连同模组脚号一起钉死;出处是 `.claude/pcb1-parts.json` 里 U20 的真实焊盘网络(2026-09-07 逐脚核过),改这条之前先回去重核一遍。 ⚠️ 光靠自检查不出「`EFUSE_EN1` 和 `VBUS_EN1` 写反了」:两个都是合法的空闲脚、 不重复、不越界。那种错只有这条测试和板上实测能拦。 */ static void test_en_pins_match_the_pcb_netlist(void) { /* EFUSE_EN VBUS_EN (模组脚号写在注释里) */ static const int want[N][2] = { { 39, 40 }, /* CH1: 脚32 / 脚33 */ { 48, 10 }, /* CH2: 脚25 / 脚18 */ { 6, 11 }, /* CH3: 脚6 / 脚19 */ { 41, 42 }, /* CH4: 脚34 / 脚35 */ { 8, 17 }, /* CH5: 脚12 / 脚10 */ }; for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_INT(want[rail - 1][0], rail_cfg(rail)->efuse_en); TEST_ASSERT_EQUAL_INT(want[rail - 1][1], rail_cfg(rail)->vbus_en); } } static void test_ntc_pins_are_on_adc1(void) { /* ADC2 在 WiFi 开着的时候读不了,而本板 WiFi 常开。 */ TEST_ASSERT_TRUE(BOARD_PIN_NTC1 >= 1 && BOARD_PIN_NTC1 <= 10); TEST_ASSERT_TRUE(BOARD_PIN_NTC2 >= 1 && BOARD_PIN_NTC2 <= 10); } /* ⚠️ 回归拦网:`EFUSE_EN2` 在 **IO48** 上,而 ESP32-S3 的编号是 0~48。 HAL 两边原来的边界都是 `pin < 48`,正好把它关在门外 —— 现象是 CH2 的 eFuse 既开不了也**关不掉**。 */ static void test_io48_is_a_usable_pin(void) { TEST_ASSERT_EQUAL_INT(48, rail_cfg(2)->efuse_en); TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_config(48, HAL_GPIO_OUTPUT)); TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_write(48, 1)); TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(48)); /* 49 就真的越界了。 */ TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_gpio_config(49, HAL_GPIO_OUTPUT)); } /* ---- 表驱动遍历:两根 EN 线 ----------------------------------------------- */ static void assert_all_off(void) { for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(rail_cfg(rail)->efuse_en)); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(rail_cfg(rail)->vbus_en)); } } /* 上电第一件事:十个脚都配成输出、都是低。「五路全关」不许靠某个默认值。 */ static void test_en_init_leaves_all_ten_pins_driven_low(void) { TEST_ASSERT_EQUAL_INT(HAL_OK, rail_en_init()); assert_all_off(); for (int rail = 1; rail <= N; rail++) { /* sim_gpio_level() 对没配过的脚返回 −1,所以上面那句已经证明配过了; 这里再确认「真的写过一次 0」,而不只是配置时的默认值。 */ TEST_ASSERT_TRUE(sim_gpio_writes(rail_cfg(rail)->efuse_en) >= 1); TEST_ASSERT_TRUE(sim_gpio_writes(rail_cfg(rail)->vbus_en) >= 1); } } /* 开一路只动那一路的那一个脚。这条拦的是「rail−1 当下标」那类错位: 错位之后打开的是别人,而两个脚都是好的,功能上「有一路开了」。 */ static void test_enabling_one_rail_touches_exactly_one_pin(void) { TEST_ASSERT_EQUAL_INT(HAL_OK, rail_en_init()); for (int on = 1; on <= N; on++) { TEST_ASSERT_EQUAL_INT(HAL_OK, rail_efuse_enable(on, true)); for (int rail = 1; rail <= N; rail++) { const int want = (rail == on) ? 1 : 0; TEST_ASSERT_EQUAL_INT(want, sim_gpio_level(rail_cfg(rail)->efuse_en)); /* VBUS 一根都不许跟着动 —— 两张表混用是这里最像的错。 */ TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(rail_cfg(rail)->vbus_en)); } TEST_ASSERT_EQUAL_INT(HAL_OK, rail_efuse_enable(on, false)); } for (int on = 1; on <= N; on++) { TEST_ASSERT_EQUAL_INT(HAL_OK, rail_vbus_enable(on, true)); for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_INT((rail == on) ? 1 : 0, sim_gpio_level(rail_cfg(rail)->vbus_en)); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(rail_cfg(rail)->efuse_en)); } TEST_ASSERT_EQUAL_INT(HAL_OK, rail_vbus_enable(on, false)); } } /* 高有效:true = 导通。写反了板子上是「命令开电结果全关」,一眼看得出来, 但**关电命令会变成开电**,那才是危险的一半。 */ static void test_enable_is_active_high(void) { TEST_ASSERT_EQUAL_INT(HAL_OK, rail_en_init()); TEST_ASSERT_EQUAL_INT(HAL_OK, rail_efuse_enable(1, true)); TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(rail_cfg(1)->efuse_en)); TEST_ASSERT_EQUAL_INT(HAL_OK, rail_efuse_enable(1, false)); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(rail_cfg(1)->efuse_en)); } /* 全关:十个脚都回到低,而且**五根 VBUS 全部早于任何一根 eFuse** —— 反过来的话中间有一小段「手机只靠 USB 供着电」。 */ static void test_all_off_cuts_vbus_before_efuse(void) { TEST_ASSERT_EQUAL_INT(HAL_OK, rail_en_init()); for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_INT(HAL_OK, rail_efuse_enable(rail, true)); TEST_ASSERT_EQUAL_INT(HAL_OK, rail_vbus_enable(rail, true)); } TEST_ASSERT_EQUAL_INT(HAL_OK, rail_all_off()); assert_all_off(); uint32_t last_vbus = 0, first_efuse = 0xFFFFFFFFu; for (int rail = 1; rail <= N; rail++) { const uint32_t v = sim_gpio_write_seq(rail_cfg(rail)->vbus_en); const uint32_t e = sim_gpio_write_seq(rail_cfg(rail)->efuse_en); if (v > last_vbus) { last_vbus = v; } if (e < first_efuse) { first_efuse = e; } } TEST_ASSERT_TRUE_MESSAGE(last_vbus < first_efuse, "最后一根 VBUS 必须早于第一根 eFuse"); } /* 关电路径上「有一个脚失败就别管剩下九个了」是最坏的选择。 注错的办法是把 CH1 的 VBUS 脚偷偷改回输入 —— 之后写它必然 HAL_ERR_ARG, 而剩下九个脚该关的还得关。 */ static void test_all_off_keeps_going_after_one_pin_fails(void) { TEST_ASSERT_EQUAL_INT(HAL_OK, rail_en_init()); for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_INT(HAL_OK, rail_efuse_enable(rail, true)); TEST_ASSERT_EQUAL_INT(HAL_OK, rail_vbus_enable(rail, true)); } const int broken = rail_cfg(1)->vbus_en; TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_config(broken, HAL_GPIO_INPUT)); const uint32_t before = sim_gpio_writes(broken); TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_all_off()); /* 如实报第一个错误码 */ TEST_ASSERT_EQUAL_UINT32(before, sim_gpio_writes(broken)); /* 那个脚确实没写成 */ /* 另外九个必须都已经关掉了。 */ for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(rail_cfg(rail)->efuse_en)); if (rail != 1) { TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(rail_cfg(rail)->vbus_en)); } } } /* 没 rail_en_init() 过就调,十个脚全失败:返回错误码,不崩、不乱写。 */ static void test_all_off_before_init_reports_an_error(void) { TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_all_off()); for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_INT(-1, sim_gpio_level(rail_cfg(rail)->efuse_en)); /* 没配过 */ TEST_ASSERT_EQUAL_INT(-1, sim_gpio_level(rail_cfg(rail)->vbus_en)); } } static void test_rail_number_out_of_range_is_rejected(void) { TEST_ASSERT_EQUAL_INT(HAL_OK, rail_en_init()); TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_efuse_enable(0, true)); TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_efuse_enable(N + 1, true)); TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_efuse_enable(-1, false)); TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_vbus_enable(0, true)); TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, rail_vbus_enable(N + 1, true)); assert_all_off(); /* 一个脚都没被越界的调用碰过 */ } int main(void) { UNITY_BEGIN(); RUN_TEST(test_selftest_passes); RUN_TEST(test_selftest_covers_all_four_driver_tables); RUN_TEST(test_selftest_str_names_every_bit); RUN_TEST(test_a_faithful_copy_of_the_real_table_passes); RUN_TEST(test_a_cell_that_drifted_from_the_driver_is_caught); RUN_TEST(test_every_kind_of_bad_pin_is_caught); RUN_TEST(test_two_kinds_of_breakage_report_two_bits); RUN_TEST(test_no_two_rails_share_anything); RUN_TEST(test_shift_chain_bits_avoid_hub_reset_and_the_unused_pins); RUN_TEST(test_every_field_is_in_range); RUN_TEST(test_flt_and_pg_live_on_different_ports_so_equal_bits_are_fine); RUN_TEST(test_no_column_is_an_arithmetic_progression); RUN_TEST(test_ina_addr_formula_is_wrong_for_exactly_three_rails); RUN_TEST(test_dac_mapping_is_not_the_obvious_one); RUN_TEST(test_flt_and_pg_really_are_sequential); RUN_TEST(test_the_three_most_surprising_cells); RUN_TEST(test_table_matches_every_driver_table); RUN_TEST(test_en_pins_do_not_collide_with_anything_else); RUN_TEST(test_en_pins_match_the_pcb_netlist); RUN_TEST(test_ntc_pins_are_on_adc1); RUN_TEST(test_io48_is_a_usable_pin); RUN_TEST(test_en_init_leaves_all_ten_pins_driven_low); RUN_TEST(test_enabling_one_rail_touches_exactly_one_pin); RUN_TEST(test_enable_is_active_high); RUN_TEST(test_all_off_cuts_vbus_before_efuse); RUN_TEST(test_all_off_keeps_going_after_one_pin_fails); RUN_TEST(test_all_off_before_init_reports_an_error); RUN_TEST(test_rail_number_out_of_range_is_rejected); return UNITY_END(); }