A7:五路配置表 + 表驱动遍历 + 启动自检

六组编号(INA 地址 / DAC 片通道 / 三个按键位 / EDL 位 / 两根 EN 的 GPIO)拼成
一张 const 表 rail_cfg(1..5),没有一列是顺排的。开机 rail_selftest() 把它和
A3~A6 四个驱动的表逐格对账,再查全板引脚重复 / 越界,不过就 abort ——
那四个自检谓词至此全部有了调用点。

判据拎成 rail_selftest_table(t) 单独可调:真表是 const 的,不这么做就没法证明
「真有事的时候查得出来」。测试复制一张表出来逐样改坏,六种坏法都验过。

顺带修一个真 bug:HAL 两侧 GPIO 边界写的是 pin < 48,而 EFUSE_EN2 就在 IO48
上 —— CH2 的 eFuse 既开不了也关不掉。sim 与 ESP 两份都改成 0~48 含。

9 条 ctest(131 个用例)全绿,idf.py build 通过,16 条注错 15 条变红
(唯一没红的那条与原因记在 HANDOFF.md)。

Co-Authored-By: Claude Opus 5 <[email protected]>
这个提交包含在:
zhou yang
2026-09-07 07:13:04 +08:00
共同撰写人 Claude Opus 5
父节点 eb3df9ff49
当前提交 45de84ed3c
共修改 13 个文件,包含 1249 行新增和 63 行删除
+609
查看文件
@@ -0,0 +1,609 @@
/* 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();
}