/* A15:启动顺序 + 降级矩阵(需求 §10.9 / §10.10,计划 A15 那一行)。 验收原话:「逐个『拔掉』器件,行为与 §10.10 的表逐行对上;五路上电错开 100 ms; `HUB_RST` 只脉冲一次」,外加 ⭐ 第一条用例必须是 `ina226_set_reversed()` 那一行。 ---- 这块假板子和别的测试有什么不一样 ------------------------------------ · **PGOOD 跟着 EN 走**:`sim_gpio_set_hook()` 让某一路 `EFUSE_EN` 抬高的那一刻, 那一片 PCA9555 的 P1.3 跟着变高。没有这一条,「等 PG」要么永远等到(PG 静态拉高, 测不出超时),要么永远等不到。 · 时刻一律从 `sim_gpio_rise_us()` 取(线上真实的边),**不信** `bringup_report_t` 自己报的 `on_ms` —— 那是自己证明自己。 · 事件从 flash 读回来数(evlog 的真路径),不在被测代码里插探针。 */ #include #include "board_pins.h" #include "bringup.h" #include "ch224a.h" #include "dac63204.h" #include "evlog.h" #include "hal_gpio.h" #include "i2cbus.h" #include "ina226.h" #include "pca9555.h" #include "rail.h" #include "rail_cfg.h" #include "sim_hal.h" #include "slot.h" #include "tca9548a.h" #include "unity.h" #define N PHONERAIL_RAIL_COUNT #define ALL ((uint8_t)((1u << N) - 1u)) #define BIT(rail) ((uint8_t)(1u << ((rail) - 1))) /* Port1 静息:FLT# 高(无故障)、Buck PG 高、ALERT 高、**eFuse PG 低**(还没上电)。 */ #define P1_IDLE ((uint8_t)(0x0Fu & ~(1u << PCA9555_BIT_EFUSE_PG))) #define P1_PG ((uint8_t)0x0Fu) static bool g_pg_follows[N]; /* false = 这一路的 PG 永远不来 */ static bool g_flt[N]; /* true = 这一路的 FLT# 在报 */ static bool g_pca_gone[N]; /* 这一片被拔了:钩子别再去驱它 */ static bringup_report_t g_r; static bool g_pd_on_trunk = true; /* false = board() 不在主干上挂 CH224A */ static int ch_of(int rail) { return (int)rail_cfg(rail)->mux_ch; } /* 外部世界:EN 一抬高 PG 就来(真板上是 ~17 ms 之后;这里早来晚来只改变「等了几圈」)。 */ static void world(int pin, int level) { for (int rail = 1; rail <= N; rail++) { if (pin != rail_cfg(rail)->efuse_en || g_pca_gone[rail - 1]) { continue; } uint8_t v = (level != 0 && g_pg_follows[rail - 1]) ? P1_PG : P1_IDLE; if (g_flt[rail - 1]) { v &= (uint8_t)~(1u << PCA9555_BIT_EFUSE_FLT); } sim_pca9555_drive(rail - 1, 1, v); } } static void put_ina_ids(int ch, uint8_t addr) { if (ch < 0) { sim_i2c_set_reg(addr, INA226_REG_MFG_ID, INA226_MFG_ID); sim_i2c_set_reg(addr, INA226_REG_DIE_ID, INA226_DIE_ID); } else { sim_i2c_set_reg_ch(ch, addr, INA226_REG_MFG_ID, INA226_MFG_ID); sim_i2c_set_reg_ch(ch, addr, INA226_REG_DIE_ID, INA226_DIE_ID); } } /* 满配、五槽全插的假板子。各用例在 `bringup_devices()` 之前「拔掉」自己关心的那一个。 */ static void board(void) { sim_i2c_add_mux(TCA9548A_ADDR, BOARD_PIN_MUX_RST_N); for (int i = 0; i < DAC63204_COUNT; i++) { const uint8_t a = dac63204_all_addr[i]; sim_i2c_add(a, 2); sim_i2c_set_reg(a, DAC63204_REG_GENERAL_STATUS, (uint16_t)(DAC63204_DEVICE_ID << 2)); sim_i2c_set_reg(a, DAC63204_REG_COMMON_CONFIG, 0x0FFF); } /* NVM 默认:五路都是 3.90 V(上电复位时读回来比对的就是它)。 */ for (int rail = 1; rail <= N; rail++) { sim_i2c_set_reg(dac63204_rail_addr(rail), DAC63204_REG_DATA(dac63204_rail_ch(rail)), DAC63204_CODE_TO_REG(rail_mv_to_code(RAIL_MV_SAFE))); } sim_i2c_add(INA226_ADDR_INPUT, 2); put_ina_ids(-1, INA226_ADDR_INPUT); sim_i2c_set_reg(INA226_ADDR_INPUT, INA226_REG_BUS_V, (uint16_t)(12000 * 4 / 5)); /* CH224A:实测在主干(PROGRESS 顶部),20 V / 3 A 档 = 60 W。 */ if (g_pd_on_trunk) { sim_i2c_add(CH224A_ADDR_DEFAULT, 1); sim_i2c_set_reg(CH224A_ADDR_DEFAULT, CH224A_REG_CURRENT, 3000 / 50); } for (int rail = 1; rail <= N; rail++) { const int ch = ch_of(rail); sim_i2c_add_ch(ch, rail_cfg(rail)->ina_addr, 2); put_ina_ids(ch, rail_cfg(rail)->ina_addr); sim_pca9555_attach_ch(rail - 1, ch, PCA9555_ADDR); sim_pca9555_drive(rail - 1, 1, P1_IDLE); sim_gpio_drive(rail_cfg(rail)->prsnt_n, 0); /* 插着:刀片把 PRSNT# 短到地 */ g_pg_follows[rail - 1] = true; g_flt[rail - 1] = false; g_pca_gone[rail - 1] = false; } sim_gpio_set_hook(world); } static void pull_slot(int rail) /* 整片刀片拔走:PRSNT# 高 + 两片器件都不在 */ { sim_gpio_drive(rail_cfg(rail)->prsnt_n, 1); sim_i2c_set_present_ch(ch_of(rail), rail_cfg(rail)->ina_addr, false); sim_i2c_set_present_ch(ch_of(rail), PCA9555_ADDR, false); g_pca_gone[rail - 1] = true; } static void devices(bool por) { const bringup_in_t in = { .power_on_reset = por }; TEST_ASSERT_EQUAL_INT(HAL_OK, bringup_devices(&in, &g_r)); } /* 某类事件(可限定路号,-1 = 不限)在 flash 里有几条。 */ static int count_ev(log_ev_t type, int rail) { (void)evlog_flush(0); evlog_stats_t st; evlog_stats(&st); int n = 0; evlog_rec_t rec; for (uint32_t seq = st.oldest_seq; seq < st.next_seq; seq++) { if (evlog_read(seq, &rec, 1) != 1) { break; } if (rec.type == (uint8_t)type && (rail < 0 || rec.rail == (uint8_t)rail)) { n++; } } return n; } static bool last_ev(log_ev_t type, evlog_rec_t *out) { (void)evlog_flush(0); evlog_stats_t st; evlog_stats(&st); bool hit = false; evlog_rec_t rec; for (uint32_t seq = st.oldest_seq; seq < st.next_seq; seq++) { if (evlog_read(seq, &rec, 1) == 1 && rec.type == (uint8_t)type) { *out = rec; hit = true; } } return hit; } void setUp(void) { sim_hal_reset(); pca9555_shadow_reset(); rail_setpoint_reset(); ina226_set_reversed(INA226_ADDR_INPUT, false); /* 进程里是活的:清掉上一条用例留下的 */ board(); TEST_ASSERT_EQUAL_INT(HAL_OK, evlog_init_ex(0)); } void tearDown(void) { TEST_ASSERT_EQUAL_UINT32(0, sim_i2c_ambiguous()); } /* ---- ⭐ 第一条:输入侧 INA226 的对调接 ------------------------------------ */ /* 计划 A15 的 ⭐:这一行以前只在 `app_main.c` 里,唯一的拦网是人眼。 板上 U13 的 IN+/IN− 对调 ⇒ 正常供电时芯片自己读出的电流是**负**的; 启动之后驱动必须把它翻回来,否则整板功率与反灌判据全拿的是反号。 ⚠️ 断的是「读出来的电流是正的」这个**后果**,不只是那个标志位。 */ static void test_first_input_ina_is_marked_reversed(void) { /* 12 V × 2 A 正常供电,芯片按对调接读出 −2 A。 */ sim_i2c_set_reg(INA226_ADDR_INPUT, INA226_REG_CURRENT, (uint16_t)(int16_t)(-2000000 / 500)); TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_init(NULL)); ina226_vi_t vi; TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_vi(INA226_ADDR_INPUT, &vi)); TEST_ASSERT_TRUE_MESSAGE(vi.current_ua < 0, "前提:不登记对调时读出来是负的"); devices(true); TEST_ASSERT_TRUE(ina226_is_reversed(INA226_ADDR_INPUT)); TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_vi(INA226_ADDR_INPUT, &vi)); TEST_ASSERT_EQUAL_INT32(2000000, vi.current_ua); /* 刀片上那五片是正接的,别被一起翻了。 */ TEST_ASSERT_FALSE(ina226_is_reversed(INA226_ADDR_RAIL)); } /* ---- 满配:什么都在 ------------------------------------------------------ */ /* 六片 INA226 都写了 Config / Calibration。A14 之前这一步**根本没做** (app_main 里那句「在那之前 t_safety 读回来的电流恒为 0」)。 */ static void test_full_board_initialises_every_device(void) { devices(true); TEST_ASSERT_EQUAL_UINT32(0, g_r.deg); TEST_ASSERT_EQUAL_UINT8(0, g_r.empty | g_r.blind | g_r.io_lost); TEST_ASSERT_EQUAL_UINT16(INA226_CALIB, sim_i2c_get_reg(INA226_ADDR_INPUT, INA226_REG_CALIB)); TEST_ASSERT_EQUAL_UINT16(INA226_CONFIG, sim_i2c_get_reg(INA226_ADDR_INPUT, INA226_REG_CONFIG)); for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_UINT16(INA226_CALIB, sim_i2c_get_reg_ch(ch_of(rail), INA226_ADDR_RAIL, INA226_REG_CALIB)); /* PCA9555 按板级方向字配好,锁存器全 0(键全松、VBUS 断)。 */ TEST_ASSERT_EQUAL_UINT8(PCA9555_CONFIG0_INIT, sim_pca9555_config(rail - 1, 0)); TEST_ASSERT_EQUAL_UINT8(0, sim_pca9555_output(rail - 1, 0)); TEST_ASSERT_EQUAL_UINT16(RAIL_MV_SAFE, (uint16_t)g_r.dac_nvm_mv[rail - 1]); /* 第一段一路电都不上。 */ TEST_ASSERT_FALSE(rail_efuse_is_on(rail)); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(rail_cfg(rail)->efuse_en)); } TEST_ASSERT_EQUAL_UINT32(0, sim_pca9555_unsafe_enable()); TEST_ASSERT_EQUAL_UINT32(0, sim_pca9555_gnd_driven()); TEST_ASSERT_EQUAL_UINT32(60000, g_r.pd_limit_mw); TEST_ASSERT_EQUAL_INT(0, count_ev(LOG_EV_I2C_LOST, -1)); TEST_ASSERT_EQUAL_INT(1, count_ev(LOG_EV_PD_LIMIT, 0)); } /* ⭐ §10.9 第 5 步:五路上电**错开 100 ms**,按 CH1→CH5 的顺序。 时刻取线上的边,不取被测代码自报的 `on_ms`。上界 = 100 ms + 一次轮询 + 总线时间。 */ static void test_five_rails_come_up_100ms_apart(void) { devices(true); bringup_power_restore(ALL, &g_r); TEST_ASSERT_EQUAL_UINT8(ALL, g_r.on); TEST_ASSERT_EQUAL_UINT8(0, g_r.pg_fail | g_r.unverified); for (int rail = 2; rail <= N; rail++) { const uint64_t a = sim_gpio_rise_us(rail_cfg(rail - 1)->efuse_en); const uint64_t b = sim_gpio_rise_us(rail_cfg(rail)->efuse_en); TEST_ASSERT_TRUE_MESSAGE(a != 0 && b > a, "后一路必须在前一路之后抬高"); TEST_ASSERT_TRUE_MESSAGE(b - a >= BRINGUP_STAGGER_MS * 1000u, "错开不足 100 ms"); TEST_ASSERT_TRUE_MESSAGE(b - a < (BRINGUP_STAGGER_MS + BRINGUP_PG_POLL_MS + 2u) * 1000u, "错开拖得太久:前一路的 PG 等待不该拖住后一路"); TEST_ASSERT_TRUE(rail_efuse_is_on(rail)); } /* 上电序列从不开 VBUS(那是命令的事)。 */ for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_FALSE(slot_vbus_is_on(rail)); } } /* 只恢复「上次开着的」那几路;跳过的路**不占** 100 ms 的格子。 */ static void test_only_wanted_rails_come_up(void) { devices(true); bringup_power_restore(BIT(1) | BIT(4), &g_r); TEST_ASSERT_EQUAL_UINT8(BIT(1) | BIT(4), g_r.on); TEST_ASSERT_EQUAL_UINT64(0, sim_gpio_rise_us(rail_cfg(2)->efuse_en)); TEST_ASSERT_EQUAL_UINT64(0, sim_gpio_rise_us(rail_cfg(3)->efuse_en)); const uint64_t d = sim_gpio_rise_us(rail_cfg(4)->efuse_en) - sim_gpio_rise_us(rail_cfg(1)->efuse_en); TEST_ASSERT_TRUE(d >= 100000u && d < 110000u); } /* 新板(NVS 里什么都没有)= 0 = 一路都不开,而且一个时钟都不花。 */ static void test_nothing_wanted_nothing_powered(void) { devices(true); const uint32_t delays = sim_time_delay_calls(); bringup_power_restore(0, &g_r); TEST_ASSERT_EQUAL_UINT8(0, g_r.on); TEST_ASSERT_EQUAL_UINT32(delays, sim_time_delay_calls()); } /* ---- `HUB_RST#` 只脉冲一次 ----------------------------------------------- */ /* 上电复位:正好一个低脉冲(写两次:0 再 1),而且第二段**一个字都不再写它**。 */ static void test_hub_reset_pulses_exactly_once_on_power_on(void) { devices(true); bringup_power_restore(ALL, &g_r); TEST_ASSERT_EQUAL_UINT32(2, sim_gpio_writes(BOARD_PIN_HUB_RST_N)); TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(BOARD_PIN_HUB_RST_N)); } /* 其余复位原因(看门狗 / esptool 的 USB 复位 / esp_restart):**碰都不碰**, 连「配成输出」都不做 —— 配成输出那一刻 HAL 两侧都是输出 0 = 就是一次复位。 */ static void test_hub_reset_untouched_on_warm_reboot(void) { devices(false); bringup_power_restore(ALL, &g_r); TEST_ASSERT_EQUAL_UINT32(0, sim_gpio_writes(BOARD_PIN_HUB_RST_N)); TEST_ASSERT_EQUAL_INT(-1, sim_gpio_level(BOARD_PIN_HUB_RST_N)); /* 没配过 */ } /* ---- §10.10 逐行 --------------------------------------------------------- */ /* 行 1:某片 INA226 无应答 ⇒ **保持供电**、标盲跑、记事件。PG 照样从 PCA9555 来。 */ static void test_row_rail_ina_missing_keeps_power(void) { sim_i2c_set_present_ch(ch_of(3), INA226_ADDR_RAIL, false); devices(true); bringup_power_restore(ALL, &g_r); TEST_ASSERT_EQUAL_UINT8(BIT(3), g_r.blind); TEST_ASSERT_EQUAL_UINT8(ALL, g_r.on); TEST_ASSERT_TRUE(rail_efuse_is_on(3)); evlog_rec_t rec; TEST_ASSERT_TRUE(last_ev(LOG_EV_I2C_LOST, &rec)); TEST_ASSERT_EQUAL_UINT8(3, rec.rail); TEST_ASSERT_EQUAL_UINT8(INA226_ADDR_RAIL, rec.arg); TEST_ASSERT_EQUAL_INT(1, count_ev(LOG_EV_I2C_LOST, -1)); } /* 行 3:DAC 无应答 ⇒ **不关输出**;另一片照样初始化;影子回到 NVM 默认。 */ static void test_row_dac_missing_does_not_stop_the_other_or_the_rails(void) { rail_setpoint_reset(); sim_i2c_set_present(DAC63204_ADDR_U7, false); devices(true); bringup_power_restore(ALL, &g_r); TEST_ASSERT_TRUE((g_r.deg & BRINGUP_DEG_DAC_U7) != 0); TEST_ASSERT_FALSE((g_r.deg & BRINGUP_DEG_DAC_U6) != 0); /* U6 真的被初始化了:内部基准开、用到的通道上电(不再是复位值 0x0FFF)。 */ TEST_ASSERT_EQUAL_UINT16(dac63204_cc_run(DAC63204_ADDR_U6), sim_i2c_get_reg(DAC63204_ADDR_U6, DAC63204_REG_COMMON_CONFIG)); TEST_ASSERT_EQUAL_UINT8(ALL, g_r.on); for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_INT(RAIL_MV_SAFE, rail_setpoint_mv(rail)); } evlog_rec_t rec; TEST_ASSERT_TRUE(last_ev(LOG_EV_I2C_LOST, &rec)); TEST_ASSERT_EQUAL_UINT8(0, rec.rail); TEST_ASSERT_EQUAL_UINT8(DAC63204_ADDR_U7, rec.arg); } /* 行 4:某片 PCA9555 无应答 ⇒ **不关输出**,PG 改按 INA226 电压判。电压到了就算好。 */ static void test_row_pca_missing_falls_back_to_voltage(void) { sim_i2c_set_present_ch(ch_of(2), PCA9555_ADDR, false); g_pca_gone[1] = true; sim_i2c_set_reg_ch(ch_of(2), INA226_ADDR_RAIL, INA226_REG_BUS_V, (uint16_t)(3900 * 4 / 5)); devices(true); bringup_power_restore(ALL, &g_r); TEST_ASSERT_EQUAL_UINT8(BIT(2), g_r.io_lost); TEST_ASSERT_EQUAL_UINT8(ALL, g_r.on); TEST_ASSERT_EQUAL_UINT8(0, g_r.pg_fail); TEST_ASSERT_EQUAL_INT(1, count_ev(LOG_EV_I2C_LOST, 2)); } /* 同一行的反面:PCA9555 看不见、电压也一直起不来 ⇒ 超时关回去,arg 标明是按电压判的。 */ static void test_row_pca_missing_and_no_voltage_times_out(void) { sim_i2c_set_present_ch(ch_of(2), PCA9555_ADDR, false); g_pca_gone[1] = true; devices(true); bringup_power_restore(ALL, &g_r); TEST_ASSERT_EQUAL_UINT8(BIT(2), g_r.pg_fail); TEST_ASSERT_FALSE(rail_efuse_is_on(2)); TEST_ASSERT_EQUAL_UINT8((uint8_t)(ALL & ~BIT(2)), g_r.on); evlog_rec_t rec; TEST_ASSERT_TRUE(last_ev(LOG_EV_PG_TIMEOUT, &rec)); TEST_ASSERT_EQUAL_UINT8(2, rec.rail); TEST_ASSERT_EQUAL_UINT8(BRINGUP_ARG_BY_VOLT, rec.arg); } /* PCA9555 与 INA226 都看不见:**开着**、报「确认不了」,不当成坏了去断电。 */ static void test_row_pca_and_ina_missing_stays_on_unverified(void) { sim_i2c_set_present_ch(ch_of(5), PCA9555_ADDR, false); sim_i2c_set_present_ch(ch_of(5), INA226_ADDR_RAIL, false); g_pca_gone[4] = true; devices(true); bringup_power_restore(ALL, &g_r); TEST_ASSERT_EQUAL_UINT8(BIT(5), g_r.unverified); TEST_ASSERT_TRUE(rail_efuse_is_on(5)); TEST_ASSERT_EQUAL_INT(0, count_ev(LOG_EV_PG_TIMEOUT, -1)); } /* 行 5:mux 选不中 ⇒ 先脉冲一次 `MUX_RST#`,仍不通 ⇒ **五路一路都不上电** + MUX_LOST。 */ static void test_row_mux_missing_pulses_reset_then_powers_nothing(void) { sim_i2c_set_present(TCA9548A_ADDR, false); devices(true); const uint32_t resets = sim_i2c_mux_resets(); bringup_power_restore(ALL, &g_r); TEST_ASSERT_TRUE((g_r.deg & BRINGUP_DEG_MUX) != 0); TEST_ASSERT_EQUAL_UINT32(1, i2cbus_recoveries()); /* 就一次,不是每试一次复位一次 */ TEST_ASSERT_EQUAL_UINT32(resets, sim_i2c_mux_resets()); /* 第二段不再动它 */ TEST_ASSERT_EQUAL_UINT8(0, g_r.on); for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_UINT64(0, sim_gpio_rise_us(rail_cfg(rail)->efuse_en)); } TEST_ASSERT_EQUAL_INT(1, count_ev(LOG_EV_MUX_LOST, 0)); /* 整板级记一条就够:不逐路刷十条,也不替够不着的主干器件刷 I2C_LOST (主干要经 mux 写「全关」,mux 死了主干也够不着 —— bringup.c 的那段)。 */ TEST_ASSERT_EQUAL_INT(0, count_ev(LOG_EV_I2C_LOST, -1)); TEST_ASSERT_EQUAL_UINT32(60000, g_r.pd_limit_mw); for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_INT(RAIL_MV_SAFE, rail_setpoint_mv(rail)); } } /* 同一行:复位**救回来了**(第一次认件失败、复位后应答)⇒ 照常起。 */ static void test_row_mux_recovers_after_one_reset(void) { sim_i2c_inject_nak(TCA9548A_ADDR, 1); /* 只 NAK 认件那一次 */ devices(true); bringup_power_restore(ALL, &g_r); TEST_ASSERT_FALSE((g_r.deg & BRINGUP_DEG_MUX) != 0); TEST_ASSERT_EQUAL_UINT32(1, i2cbus_recoveries()); TEST_ASSERT_EQUAL_UINT8(ALL, g_r.on); TEST_ASSERT_EQUAL_INT(0, count_ev(LOG_EV_MUX_LOST, -1)); } /* 行 6:`PRSNT#` 高 ⇒ 不初始化(一次总线都不发)、不上电;想开的才记 SLOT_EMPTY。 */ static void test_row_empty_slot_is_never_touched(void) { pull_slot(4); devices(true); TEST_ASSERT_EQUAL_UINT8(BIT(4), g_r.empty); TEST_ASSERT_EQUAL_UINT32(0, sim_i2c_dev_ch(ch_of(4), PCA9555_ADDR)->n_write); TEST_ASSERT_EQUAL_UINT32(0, sim_i2c_dev_ch(ch_of(4), INA226_ADDR_RAIL)->n_write); TEST_ASSERT_EQUAL_INT(0, count_ev(LOG_EV_I2C_LOST, -1)); /* 空槽不是失联 */ bringup_power_restore(ALL, &g_r); TEST_ASSERT_EQUAL_UINT8((uint8_t)(ALL & ~BIT(4)), g_r.on); TEST_ASSERT_EQUAL_UINT64(0, sim_gpio_rise_us(rail_cfg(4)->efuse_en)); TEST_ASSERT_EQUAL_INT(1, count_ev(LOG_EV_SLOT_EMPTY, 4)); } static void test_row_empty_slot_not_wanted_is_silent(void) { pull_slot(4); devices(true); bringup_power_restore((uint8_t)(ALL & ~BIT(4)), &g_r); TEST_ASSERT_EQUAL_INT(0, count_ev(LOG_EV_SLOT_EMPTY, -1)); /* 三槽机器每次开机不刷事件 */ } /* 行 7:CH224A 读不到 ⇒ **不关输出**,按 60 W。 */ static void test_row_pd_missing_assumes_60w(void) { sim_i2c_set_present(CH224A_ADDR_DEFAULT, false); devices(true); bringup_power_restore(ALL, &g_r); TEST_ASSERT_TRUE((g_r.deg & BRINGUP_DEG_PD) != 0); TEST_ASSERT_EQUAL_UINT32(60000, g_r.pd_limit_mw); TEST_ASSERT_EQUAL_UINT8(ALL, g_r.on); evlog_rec_t rec; TEST_ASSERT_TRUE(last_ev(LOG_EV_PD_LIMIT, &rec)); TEST_ASSERT_EQUAL_UINT8(1, rec.arg); TEST_ASSERT_EQUAL_INT32(600, rec.bus_mv); /* mW/100 */ } /* 同一行:档位偏低 ⇒ 照读到的来(真相比保守值更可信),标 PD_LOW。 */ static void test_row_pd_low_uses_what_it_reads(void) { sim_i2c_set_reg(CH224A_ADDR_DEFAULT, CH224A_REG_CURRENT, 2250 / 50); /* 45 W */ devices(true); TEST_ASSERT_TRUE((g_r.deg & BRINGUP_DEG_PD_LOW) != 0); TEST_ASSERT_FALSE((g_r.deg & BRINGUP_DEG_PD) != 0); TEST_ASSERT_EQUAL_UINT32(45000, g_r.pd_limit_mw); } /* 设计文档说 CH224A 在 mux 通道 5,实测在主干 —— 两处都得找得到。 */ static void test_pd_found_behind_channel_5_too(void) { sim_hal_reset(); pca9555_shadow_reset(); g_pd_on_trunk = false; /* 主干上不挂 —— 同址挂两处,sim 会先匹配到主干那颗 */ board(); g_pd_on_trunk = true; TEST_ASSERT_EQUAL_INT(HAL_OK, evlog_init_ex(0)); sim_i2c_add_ch(TCA9548A_CH_PD, CH224A_ADDR_DEFAULT, 1); sim_i2c_set_reg_ch(TCA9548A_CH_PD, CH224A_ADDR_DEFAULT, CH224A_REG_CURRENT, 5000 / 50); devices(true); TEST_ASSERT_EQUAL_UINT32(0, g_r.deg); TEST_ASSERT_EQUAL_UINT32(100000, g_r.pd_limit_mw); } /* 行 8:看门狗复位 ⇒ BOOT 事件带复位原因;输出照 §10.9 恢复。 */ static void test_row_watchdog_reset_is_recorded_in_boot(void) { sim_hal_reboot(); pca9555_shadow_reset(); board(); TEST_ASSERT_EQUAL_INT(HAL_OK, evlog_init_ex(7)); /* ESP_RST_TASK_WDT = 7 */ devices(false); bringup_power_restore(ALL, &g_r); evlog_rec_t rec; TEST_ASSERT_TRUE(last_ev(LOG_EV_BOOT, &rec)); TEST_ASSERT_EQUAL_UINT8(7, rec.arg); TEST_ASSERT_EQUAL_UINT8(ALL, g_r.on); } /* ---- 等 PG --------------------------------------------------------------- */ /* §10.9 第 6 步:PG 等不到 ⇒ 500 ms 后关回去 + 记事件(带 FLT#);别的路不受牵连。 */ static void test_pg_timeout_turns_that_rail_back_off(void) { g_pg_follows[2] = false; g_flt[2] = true; devices(true); bringup_power_restore(ALL, &g_r); TEST_ASSERT_EQUAL_UINT8(BIT(3), g_r.pg_fail); TEST_ASSERT_FALSE(rail_efuse_is_on(3)); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(rail_cfg(3)->efuse_en)); TEST_ASSERT_EQUAL_UINT8((uint8_t)(ALL & ~BIT(3)), g_r.on); evlog_rec_t rec; TEST_ASSERT_TRUE(last_ev(LOG_EV_PG_TIMEOUT, &rec)); TEST_ASSERT_EQUAL_UINT8(3, rec.rail); TEST_ASSERT_EQUAL_UINT8(BRINGUP_ARG_FLT, rec.arg); /* 关回去的时刻 ≈ 抬高 + 500 ms(不早于)。 */ TEST_ASSERT_TRUE(hal_time_now_ms() - g_r.on_ms[2] >= BRINGUP_PG_TIMEOUT_MS); /* 后面两路没有被它拖住:仍是 100 ms 一格。 */ const uint64_t d = sim_gpio_rise_us(rail_cfg(4)->efuse_en) - sim_gpio_rise_us(rail_cfg(3)->efuse_en); TEST_ASSERT_TRUE(d < 110000u); } /* ---- DAC 的 NVM 核对 ----------------------------------------------------- */ /* 上电复位时读回的不是 3.90 V ⇒ 「DAC 不应答时停在安全值」这条前提不成立了,要报。 */ static void test_dac_nvm_mismatch_is_flagged_on_power_on(void) { sim_i2c_set_reg(dac63204_rail_addr(3), DAC63204_REG_DATA(dac63204_rail_ch(3)), DAC63204_CODE_TO_REG(rail_mv_to_code(3600))); devices(true); TEST_ASSERT_TRUE((g_r.deg & BRINGUP_DEG_DAC_NVM) != 0); TEST_ASSERT_EQUAL_UINT16(3600, g_r.dac_nvm_mv[2]); } /* 热重启时 DAC 没断过电,里面是上次命令设的值 —— 不许报 NVM 不对。 */ static void test_dac_nvm_not_checked_on_warm_reboot(void) { sim_i2c_set_reg(dac63204_rail_addr(3), DAC63204_REG_DATA(dac63204_rail_ch(3)), DAC63204_CODE_TO_REG(rail_mv_to_code(3600))); devices(false); TEST_ASSERT_FALSE((g_r.deg & BRINGUP_DEG_DAC_NVM) != 0); /* 而初始化照样把它写回安全值。 */ int mv = 0; TEST_ASSERT_EQUAL_INT(HAL_OK, rail_get_mv(3, &mv)); TEST_ASSERT_EQUAL_INT(RAIL_MV_SAFE, mv); } /* ---- 意图掩码 ------------------------------------------------------------ */ static void test_want_update(void) { TEST_ASSERT_EQUAL_UINT8(BIT(3), bringup_want_update(0, 3, true)); TEST_ASSERT_EQUAL_UINT8(BIT(1), bringup_want_update(BIT(1) | BIT(3), 3, false)); TEST_ASSERT_EQUAL_UINT8(BIT(2), bringup_want_update(BIT(2), 0, true)); /* 越界不动 */ TEST_ASSERT_EQUAL_UINT8(BIT(2), bringup_want_update(BIT(2), N + 1, true)); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_first_input_ina_is_marked_reversed); RUN_TEST(test_full_board_initialises_every_device); RUN_TEST(test_five_rails_come_up_100ms_apart); RUN_TEST(test_only_wanted_rails_come_up); RUN_TEST(test_nothing_wanted_nothing_powered); RUN_TEST(test_hub_reset_pulses_exactly_once_on_power_on); RUN_TEST(test_hub_reset_untouched_on_warm_reboot); RUN_TEST(test_row_rail_ina_missing_keeps_power); RUN_TEST(test_row_dac_missing_does_not_stop_the_other_or_the_rails); RUN_TEST(test_row_pca_missing_falls_back_to_voltage); RUN_TEST(test_row_pca_missing_and_no_voltage_times_out); RUN_TEST(test_row_pca_and_ina_missing_stays_on_unverified); RUN_TEST(test_row_mux_missing_pulses_reset_then_powers_nothing); RUN_TEST(test_row_mux_recovers_after_one_reset); RUN_TEST(test_row_empty_slot_is_never_touched); RUN_TEST(test_row_empty_slot_not_wanted_is_silent); RUN_TEST(test_row_pd_missing_assumes_60w); RUN_TEST(test_row_pd_low_uses_what_it_reads); RUN_TEST(test_pd_found_behind_channel_5_too); RUN_TEST(test_row_watchdog_reset_is_recorded_in_boot); RUN_TEST(test_pg_timeout_turns_that_rail_back_off); RUN_TEST(test_dac_nvm_mismatch_is_flagged_on_power_on); RUN_TEST(test_dac_nvm_not_checked_on_warm_reboot); RUN_TEST(test_want_update); return UNITY_END(); }