/* A9:三个执行器状态机 + 互斥(需求 §10.6)。计划里这一包的验收原话是 「场景测试:硬重启走完 17 ms 上电时序;按键序列不重入;EDL 顺序固定」。 ⚠️ 这三条都**不是**「函数返回了 HAL_OK」能证明的东西,所以本文件里的断言 一律打在**线上真实发生的事**上: · 24 位链看 `sim_sr595_*`(RCLK 沿的个数、栅极上的电平),不看驱动自己的影子 —— 对着影子断言等于自己证明自己,「分两次写把组合键拆开了」影子看不出来; · 两根 EN 线看 `sim_gpio_level()` 与**全局写序号** `sim_gpio_write_seq()` —— 只看终值的话,「先插 USB 再短接」和「先短接再插 USB」长得一模一样。 虚拟时钟下这些场景是零耗时的:`run_ms()` 干的就是任务体干的事 —— 调一次 `t_actuator_step()`,按它返回的时间往前拨表。 */ #include #include #include "hal_gpio.h" #include "hal_lock.h" #include "hal_time.h" #include "phonerail.h" #include "rail_cfg.h" #include "sim_hal.h" #include "sr595.h" #include "t_actuator.h" #include "unity.h" #define N PHONERAIL_RAIL_COUNT /* ---- 假 PG --------------------------------------------------------------- 真板上这个值来自 `t_safety` 每 20 ms 读回来的 PCA9555 快照(app_main.c 里接的)。 `readable = false` 模拟「PCA9555 失联 / 巡检还没跑过一圈」—— 那是**读不到**, 和「读到了,是低」(电源还没好)是两回事,两条路径下面都要测到。 */ static bool s_pg_readable; static bool s_pg_val[N]; static bool fake_pg(void *ctx, int rail, bool *pg) { (void)ctx; if (!s_pg_readable) { return false; } *pg = s_pg_val[rail - 1]; return true; } static void board_up(void) { TEST_ASSERT_EQUAL_INT(HAL_OK, hal_lock_init()); TEST_ASSERT_EQUAL_INT(HAL_OK, rail_en_init()); /* 十根 EN 线,全部拉低 */ sim_sr595_attach(SR595_PIN_SER, SR595_PIN_SRCLK, SR595_PIN_RCLK, SR595_PIN_OE_N); TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_init()); /* 安全影子 + 使能输出(A5) */ /* 强制加锁纪律 —— 但要在 `sr595_init()` **之后**打开:初始化是启动期 独占跑的,本来就不该被要求先拿锁。打开之后,动那四根线却没持锁就记违规。 */ sim_lock_require(HAL_LOCK_SR595, true); s_pg_readable = true; memset(s_pg_val, 0, sizeof(s_pg_val)); t_actuator_init(fake_pg, NULL); } void setUp(void) { sim_hal_reset(); board_up(); } void tearDown(void) {} /* 把虚拟时钟往前推 ms,期间按 `t_actuator_step()` 自己要的节奏叫它 —— 这正是 app_main.c 里那个任务体在干的事。 ⚠️ 用**绝对目标时刻**而不是「累减 left」:等锁超时会在 step 内部把虚拟时钟 花掉(hal_lock_sim.c),累减的写法会因此推过头。 */ static void run_ms(uint32_t ms) { const uint32_t target = hal_time_now_ms() + ms; for (;;) { uint32_t w = t_actuator_step(); const uint32_t now = hal_time_now_ms(); if ((int32_t)(now - target) >= 0) { return; /* 到点了,而且最后这一步已经推过 */ } const uint32_t left = target - now; if (w == 0 || w > left) { w = left; } sim_time_advance_ms(w); } } static uint32_t keys_on_chain(void) { return sim_sr595_outputs() & sr595_all_keys_mask(); } /* ==== 验收主项一:硬重启走完 17 ms 上电时序 =============================== */ /* 用 CH3 —— 它的 `EFUSE_EN` 是 IO6、`VBUS_EN` 是 IO11、DAC 在**另一片**上, 全是「按通道号算就一定算错」的那一路。 */ static void test_hard_reset_walks_the_full_power_up_sequence(void) { const int rail = 3; const int en = rail_cfg(rail)->efuse_en; /* 先让这一路开着 —— 硬重启是给**正在跑的**手机用的。 */ TEST_ASSERT_EQUAL_INT(HAL_OK, rail_efuse_enable(rail, true)); TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(en)); s_pg_val[rail - 1] = true; /* PG 一直是好的 */ const uint32_t latches0 = sim_sr595_latches(); const uint32_t t_req = hal_time_now_ms(); TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail)); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(en)); /* 请求那一刻就断电,不等下一拍 */ TEST_ASSERT_TRUE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_INT(ACT_SEQ_HARD_RESET, t_actuator_seq(rail)); /* 断电窗口是 §7.4 的 3~5 s(放电由硬件联锁做,τ 22 ms)。差一毫秒都不许上电: 缩短它的现象**不是**「重启快了」,而是「有的机型压根没重启」。 */ run_ms(ACT_HR_OFF_MIN_MS - 1); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(en)); TEST_ASSERT_EQUAL_INT(ACT_ST_HR_OFF, t_actuator_state(rail)); run_ms(ACT_HR_OFF_MS - (ACT_HR_OFF_MIN_MS - 1)); TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(en)); /* EN↑ */ TEST_ASSERT_EQUAL_INT(ACT_ST_HR_RAMP, t_actuator_state(rail)); const uint32_t t_on = hal_time_now_ms(); const uint32_t off_ms = t_on - t_req; TEST_ASSERT_TRUE(off_ms >= ACT_HR_OFF_MIN_MS && off_ms <= ACT_HR_OFF_MAX_MS); /* ⭐ 这一条就是「17 ms 上电时序」:**PG 早就是 1 了**,序列也一步都不许提前完成。 tON 5.1 ms + 缓启动斜坡 11.5 ms 之内,输出还在爬。 少等这 17 ms 的实现在这里当场变红。 */ run_ms(ACT_HR_RAMP_MS - 1); TEST_ASSERT_EQUAL_INT(ACT_ST_HR_RAMP, t_actuator_state(rail)); TEST_ASSERT_TRUE(t_actuator_busy(rail)); run_ms(1); TEST_ASSERT_EQUAL_UINT32(ACT_HR_RAMP_MS, hal_time_now_ms() - t_on); TEST_ASSERT_FALSE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail)); TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(en)); TEST_ASSERT_EQUAL_UINT32(1, t_actuator_done(rail)); /* 硬重启在 24 位链上什么都不该发(键本来就没按着 ⇒ 影子不变 ⇒ 不脏 ⇒ 不发帧)。 */ TEST_ASSERT_EQUAL_UINT32(latches0, sim_sr595_latches()); /* 也不该碰 I²C 那把锁 —— 两把锁因此永远不会嵌套。 */ TEST_ASSERT_EQUAL_UINT32(0, sim_lock_takes(HAL_LOCK_I2C)); TEST_ASSERT_EQUAL_UINT32(0, sim_lock_violations(HAL_LOCK_SR595)); } /* 别的四路的两根 EN 线,从头到尾一次都不该被写。 */ static void test_hard_reset_touches_only_its_own_two_pins(void) { const int rail = 2; uint32_t before[N][2]; for (int r = 1; r <= N; r++) { before[r - 1][0] = sim_gpio_writes(rail_cfg(r)->efuse_en); before[r - 1][1] = sim_gpio_writes(rail_cfg(r)->vbus_en); } s_pg_val[rail - 1] = true; TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail)); run_ms(ACT_HR_OFF_MS + ACT_HR_RAMP_MS); TEST_ASSERT_FALSE(t_actuator_busy(rail)); for (int r = 1; r <= N; r++) { if (r == rail) { continue; } TEST_ASSERT_EQUAL_UINT32(before[r - 1][0], sim_gpio_writes(rail_cfg(r)->efuse_en)); TEST_ASSERT_EQUAL_UINT32(before[r - 1][1], sim_gpio_writes(rail_cfg(r)->vbus_en)); } /* VBUS 是 EDL 的事,硬重启不碰。 */ TEST_ASSERT_EQUAL_UINT32(before[rail - 1][1], sim_gpio_writes(rail_cfg(rail)->vbus_en)); } /* PG 等不到(PCA9555 失联)⇒ 记 NO_PG,但**电继续开着**。 §10.10:这片不应答 = 失去 PG 可见性,**不关输出**。把「看不见」当「坏了」去断电, 等于一片 I/O 扩展器失联就让五台手机一起停机。 */ static void test_no_pg_leaves_the_rail_powered(void) { const int rail = 4; const int en = rail_cfg(rail)->efuse_en; s_pg_readable = false; TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail)); run_ms(ACT_HR_OFF_MS + ACT_HR_RAMP_MS + ACT_HR_PG_TIMEOUT_MS); TEST_ASSERT_FALSE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_INT(ACT_RES_NO_PG, t_actuator_last_result(rail)); TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(en)); /* ⭐ 不许再关掉 */ } /* 读得到、但一直是低(电源确实没好)走的是同一条路:照样不自作主张断电。 */ static void test_pg_stuck_low_also_leaves_the_rail_powered(void) { const int rail = 1; s_pg_readable = true; s_pg_val[rail - 1] = false; TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail)); run_ms(ACT_HR_OFF_MS + ACT_HR_RAMP_MS + ACT_HR_PG_TIMEOUT_MS); TEST_ASSERT_EQUAL_INT(ACT_RES_NO_PG, t_actuator_last_result(rail)); TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(rail_cfg(rail)->efuse_en)); } /* PG 迟到(斜坡走完之后 200 ms 才置位)也要认,不能只在进 HR_PG 那一刻看一眼。 */ static void test_late_pg_is_still_accepted(void) { const int rail = 5; s_pg_readable = true; s_pg_val[rail - 1] = false; TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail)); run_ms(ACT_HR_OFF_MS + ACT_HR_RAMP_MS + 200); TEST_ASSERT_EQUAL_INT(ACT_ST_HR_PG, t_actuator_state(rail)); s_pg_val[rail - 1] = true; run_ms(ACT_HR_PG_POLL_MS); TEST_ASSERT_FALSE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail)); } /* ⚠️ 上面那些用的都是 `run_ms()` —— 它按**测试自己的**节奏叫 step, 所以测不出「step 返回的睡眠时长本身对不对」。真板上的任务体是**完全听 step 的**: 它说睡多久就睡多久。这个测试照那样跑,PG 在斜坡走完之后才置位, 序列必须在一个轮询间隔之内收工 —— 「等 PG 时一觉睡满 500 ms 超时」的写法在这里变红 (那种写法把「等到 PG 就结束」悄悄变成了「一律等满 500 ms」,功能测试看不出来)。 */ static void test_the_task_loop_only_sleeps_what_step_asks_for(void) { const int rail = 2; const uint32_t pg_at = ACT_HR_OFF_MS + ACT_HR_RAMP_MS + 1; s_pg_readable = true; s_pg_val[rail - 1] = false; TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail)); const uint32_t t0 = hal_time_now_ms(); bool pg_raised = false; uint32_t total = 0; for (int i = 0; i < 10000; i++) { const uint32_t w = t_actuator_step(); /* 任务体:它说睡多久就睡多久 */ if (!t_actuator_busy(rail)) { total = hal_time_now_ms() - t0; /* 收工的**那一刻**,不是下一次醒来 */ break; } sim_time_advance_ms(w == 0 ? 1 : w); if (!pg_raised && hal_time_now_ms() - t0 >= pg_at) { s_pg_val[rail - 1] = true; pg_raised = true; } } TEST_ASSERT_TRUE(pg_raised); TEST_ASSERT_FALSE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail)); TEST_ASSERT_TRUE(total <= pg_at + 2 * ACT_HR_PG_POLL_MS); } /* 硬重启之前先把这一路的三个键松开:断电重启时有键按着,手机会开进 Recovery / Fastboot 而不是正常起来。这里手工造一个「上一次收尾没落地」的脏位。 */ static void test_hard_reset_releases_a_stuck_key_before_cutting_power(void) { const int rail = 1; const int bit = sr595_key_bit(rail, SR595_KEY_PWR); const int other = sr595_key_bit(4, SR595_KEY_VUP); TEST_ASSERT_EQUAL_INT(HAL_OK, hal_lock_take(HAL_LOCK_SR595, 10)); TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_write(((uint32_t)1u << bit) | ((uint32_t)1u << other), ((uint32_t)1u << bit) | ((uint32_t)1u << other))); hal_lock_give(HAL_LOCK_SR595); TEST_ASSERT_EQUAL_INT(1, sim_sr595_pin(bit)); TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail)); TEST_ASSERT_EQUAL_INT(0, sim_sr595_pin(bit)); /* 松开了 */ TEST_ASSERT_EQUAL_INT(1, sim_sr595_pin(other)); /* 别路的键一动没动 */ /* 顺序也要对:先松键,再断电。 */ TEST_ASSERT_TRUE(sim_gpio_write_seq(SR595_PIN_RCLK) < sim_gpio_write_seq(rail_cfg(rail)->efuse_en)); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(rail_cfg(rail)->efuse_en)); } /* ==== 验收主项二:按键序列不重入 ========================================= */ /* 短按 100 ms(SW-5 的第一档)。位号一律查 `sr595_key_bit()`:CH5 的电源键 在 U22.QH(bit15),按通道号算怎么算都算不出来。 */ static void test_key_tap_presses_the_table_bit_and_releases_it(void) { const int rail = 5; const int bit = sr595_key_bit(rail, SR595_KEY_PWR); const uint32_t latches0 = sim_sr595_latches(); TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(rail, ACT_KEYS_PWR, ACT_KEY_MS_TAP)); TEST_ASSERT_EQUAL_INT(1, sim_sr595_pin(bit)); /* 栅极真的被拉起来了 */ TEST_ASSERT_EQUAL_UINT32(latches0 + 1, sim_sr595_latches()); TEST_ASSERT_EQUAL_UINT32((uint32_t)1u << bit, keys_on_chain()); run_ms(ACT_KEY_MS_TAP - 1); TEST_ASSERT_EQUAL_INT(1, sim_sr595_pin(bit)); TEST_ASSERT_TRUE(t_actuator_busy(rail)); run_ms(1); TEST_ASSERT_EQUAL_INT(0, sim_sr595_pin(bit)); TEST_ASSERT_EQUAL_UINT32(latches0 + 2, sim_sr595_latches()); /* 按下一帧、松开一帧 */ TEST_ASSERT_EQUAL_UINT32(0, keys_on_chain()); TEST_ASSERT_FALSE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail)); TEST_ASSERT_EQUAL_UINT32(0, sim_lock_violations(HAL_LOCK_SR595)); } /* ⭐ §10.6 的硬约束:组合键必须**同一帧**写进去。 分两次写的实现在这里 `latches` 会 +2,而且中间那一帧只按下了一个键 ——「先单按了一下 VOL−」在有的机型上就是调音量。 */ static void test_combo_keys_land_in_exactly_one_frame(void) { const int rail = 2; const uint32_t want = ((uint32_t)1u << sr595_key_bit(rail, SR595_KEY_VDN)) | ((uint32_t)1u << sr595_key_bit(rail, SR595_KEY_PWR)); const uint32_t latches0 = sim_sr595_latches(); TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(rail, ACT_KEYS_FASTBOOT, ACT_KEY_MS_ON)); TEST_ASSERT_EQUAL_UINT32(latches0 + 1, sim_sr595_latches()); TEST_ASSERT_EQUAL_UINT32(want, keys_on_chain()); run_ms(ACT_KEY_MS_ON); TEST_ASSERT_EQUAL_UINT32(0, keys_on_chain()); TEST_ASSERT_EQUAL_UINT32(latches0 + 2, sim_sr595_latches()); } /* Recovery 是 VOL+ + PWR,和 Fastboot 差在哪一个音量键上 —— 两者不能相等。 */ static void test_recovery_and_fastboot_are_different_key_sets(void) { const int rail = 3; TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(rail, ACT_KEYS_RECOVERY, ACT_KEY_MS_ON)); const uint32_t recovery = keys_on_chain(); run_ms(ACT_KEY_MS_ON); TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(rail, ACT_KEYS_FASTBOOT, ACT_KEY_MS_ON)); const uint32_t fastboot = keys_on_chain(); run_ms(ACT_KEY_MS_ON); TEST_ASSERT_NOT_EQUAL(recovery, fastboot); TEST_ASSERT_EQUAL_UINT32((uint32_t)1u << sr595_key_bit(rail, SR595_KEY_PWR), recovery & fastboot); /* 共有的只有电源键 */ } /* ⭐ 不重入:一路上正在跑序列时,三个请求全部被拒,而且**一个字节都不发**。 */ static void test_a_running_sequence_rejects_every_new_request(void) { const int rail = 1; const int en = rail_cfg(rail)->efuse_en; TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(rail, ACT_KEYS_PWR, ACT_KEY_MS_FORCE)); const uint32_t latches1 = sim_sr595_latches(); const uint32_t shadow1 = sr595_shadow(); const uint32_t en_writes1 = sim_gpio_writes(en); TEST_ASSERT_EQUAL_INT(HAL_ERR_STATE, t_actuator_key(rail, ACT_KEYS_VUP, ACT_KEY_MS_TAP)); TEST_ASSERT_EQUAL_INT(HAL_ERR_STATE, t_actuator_hard_reset(rail)); TEST_ASSERT_EQUAL_INT(HAL_ERR_STATE, t_actuator_edl(rail)); TEST_ASSERT_EQUAL_UINT32(latches1, sim_sr595_latches()); TEST_ASSERT_EQUAL_UINT32(shadow1, sr595_shadow()); TEST_ASSERT_EQUAL_UINT32(en_writes1, sim_gpio_writes(en)); /* 被拒的硬重启没断电 */ /* 被拒的请求也不许改掉正在跑的那一个:仍然按 10 s 松开,不是 100 ms。 */ run_ms(ACT_KEY_MS_TAP + 1); TEST_ASSERT_TRUE(t_actuator_busy(rail)); run_ms(ACT_KEY_MS_FORCE - ACT_KEY_MS_TAP - 1); TEST_ASSERT_FALSE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_UINT32(1, t_actuator_done(rail)); /* 只跑过一个序列 */ } /* 互斥是**每路一个**:别的路照样能同时跑。这也是影子寄存器存在的理由 —— §10.6 那句「另一台手机的按键被顺手清掉」说的就是这里。 */ static void test_two_rails_run_at_the_same_time_without_clobbering(void) { const int b1 = sr595_key_bit(1, SR595_KEY_PWR); const int b4 = sr595_key_bit(4, SR595_KEY_VUP); TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(1, ACT_KEYS_PWR, ACT_KEY_MS_ON)); /* 2 s */ run_ms(50); TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(4, ACT_KEYS_VUP, ACT_KEY_MS_TAP)); /* 100 ms */ TEST_ASSERT_EQUAL_INT(1, sim_sr595_pin(b1)); TEST_ASSERT_EQUAL_INT(1, sim_sr595_pin(b4)); run_ms(ACT_KEY_MS_TAP); /* CH4 到点 */ TEST_ASSERT_EQUAL_INT(0, sim_sr595_pin(b4)); TEST_ASSERT_EQUAL_INT(1, sim_sr595_pin(b1)); /* ⭐ CH1 没被顺手清掉 */ TEST_ASSERT_TRUE(t_actuator_busy(1)); TEST_ASSERT_FALSE(t_actuator_busy(4)); run_ms(ACT_KEY_MS_ON); /* CH1 到点 */ TEST_ASSERT_EQUAL_UINT32(0, keys_on_chain()); TEST_ASSERT_FALSE(t_actuator_busy(1)); } /* ==== 验收主项三:EDL 顺序固定 =========================================== */ /* SW-6:`EDL_EN=1` → `VBUS_EN=1` → 等枚举 → `EDL_EN=0`。**先短接再插 USB**。 只看终值是分不出顺序的(终值都是「EDL 松开、VBUS 开着」),所以这里比的是 全局写序号。 */ static void test_edl_order_is_short_then_vbus_then_release(void) { const int rail = 3; const int edl = sr595_edl_bit(rail); const int vb = rail_cfg(rail)->vbus_en; TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(vb)); TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_edl(rail)); TEST_ASSERT_EQUAL_INT(1, sim_sr595_pin(edl)); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(vb)); /* USB 还没插 */ const uint32_t seq_short = sim_gpio_write_seq(SR595_PIN_RCLK); run_ms(ACT_EDL_SHORT_MS - 1); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(vb)); run_ms(1); TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(vb)); TEST_ASSERT_EQUAL_INT(1, sim_sr595_pin(edl)); /* 还短接着 */ const uint32_t seq_vbus = sim_gpio_write_seq(vb); run_ms(ACT_EDL_ENUM_MS - 1); TEST_ASSERT_EQUAL_INT(1, sim_sr595_pin(edl)); run_ms(1); TEST_ASSERT_EQUAL_INT(0, sim_sr595_pin(edl)); /* 枚举完才松短接 */ const uint32_t seq_release = sim_gpio_write_seq(SR595_PIN_RCLK); TEST_ASSERT_TRUE(seq_short < seq_vbus); TEST_ASSERT_TRUE(seq_vbus < seq_release); /* VBUS **留着** —— 手机现在正挂在 9008 上,把它断了等于白跑一趟。 */ TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(vb)); TEST_ASSERT_FALSE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_INT(ACT_RES_OK, t_actuator_last_result(rail)); } /* EDL 用的是 `sr595_edl_bit()` 那张表,一路都不能串。CH1 在 U33.QA(16), CH2~5 在 U21 上而且不是顺序的 —— 算出来的位号必错。 */ static void test_edl_uses_the_lookup_table_not_arithmetic(void) { for (int rail = 1; rail <= N; rail++) { TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_edl(rail)); TEST_ASSERT_EQUAL_UINT32((uint32_t)1u << sr595_edl_bit(rail), sim_sr595_outputs() & ~sr595_all_keys_mask() & ~((uint32_t)1u << SR595_BIT_HUB_RST)); run_ms(ACT_EDL_SHORT_MS + ACT_EDL_ENUM_MS); TEST_ASSERT_FALSE(t_actuator_busy(rail)); } } /* ==== 收尾:按下去的东西一定要松开 ======================================= */ /* 595 那把锁被别人占着时,松键落不下去。**不许放弃** —— 放弃 = 电源键留在按下状态,而遥测上五路电压电流一切正常。 */ static void test_cleanup_retries_until_the_key_is_really_released(void) { const int rail = 1; const int bit = sr595_key_bit(rail, SR595_KEY_PWR); TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(rail, ACT_KEYS_PWR, ACT_KEY_MS_TAP)); /* 扮演第二个任务,把 595 那把锁占住(A15 的初始化、A10 的收尾都会拿它)。 */ sim_lock_set_task(1); TEST_ASSERT_EQUAL_INT(HAL_OK, hal_lock_take(HAL_LOCK_SR595, 10)); sim_lock_set_task(0); run_ms(ACT_KEY_MS_TAP); TEST_ASSERT_EQUAL_INT(ACT_ST_CLEANUP, t_actuator_state(rail)); TEST_ASSERT_TRUE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_INT(1, sim_sr595_pin(bit)); /* 键还按着 */ for (int i = 0; i < 5; i++) { (void)t_actuator_step(); } TEST_ASSERT_TRUE(t_actuator_cleanup_retries(rail) > 0); /* 收尾期间这一路照样算 busy,新请求进不来。 */ TEST_ASSERT_EQUAL_INT(HAL_ERR_STATE, t_actuator_key(rail, ACT_KEYS_PWR, ACT_KEY_MS_TAP)); sim_lock_set_task(1); hal_lock_give(HAL_LOCK_SR595); sim_lock_set_task(0); (void)t_actuator_step(); TEST_ASSERT_EQUAL_INT(0, sim_sr595_pin(bit)); /* 锁一放开就落地 */ TEST_ASSERT_FALSE(t_actuator_busy(rail)); } /* 按下那一步就失败(595 那把锁被别人占着)。请求返回错误码,但**不能就这么算了** —— 影子有可能已经被改成「按下」了(拿到锁而整帧没发出去的那种失败),直接回 IDLE 等于把一个脏的「按下」留在那儿:谁下一次 flush 谁就替我们把键按下去, 而没有任何人会来松开它。所以失败的按下照样要走收尾。 */ static void test_a_failed_press_still_goes_through_cleanup(void) { const int rail = 4; const int bit = sr595_key_bit(rail, SR595_KEY_PWR); sim_lock_set_task(1); TEST_ASSERT_EQUAL_INT(HAL_OK, hal_lock_take(HAL_LOCK_SR595, 10)); sim_lock_set_task(0); TEST_ASSERT_EQUAL_INT(HAL_ERR_TIMEOUT, t_actuator_key(rail, ACT_KEYS_PWR, ACT_KEY_MS_TAP)); TEST_ASSERT_EQUAL_INT(ACT_ST_CLEANUP, t_actuator_state(rail)); TEST_ASSERT_EQUAL_INT(ACT_RES_IO, t_actuator_last_result(rail)); TEST_ASSERT_EQUAL_INT(0, sim_sr595_pin(bit)); /* 这一次键根本没按下去 */ sim_lock_set_task(1); hal_lock_give(HAL_LOCK_SR595); sim_lock_set_task(0); (void)t_actuator_step(); TEST_ASSERT_FALSE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_INT(0, sim_sr595_pin(bit)); } /* `t_actuator_abort()`:立刻收尾,键当场松开,但**不动两根 EN 线** —— 把电再关一次或再开一次都是自作主张(那是 A10 的判据和上位机命令的事)。 */ static void test_abort_releases_the_key_and_leaves_the_pins_alone(void) { const int rail = 2; const int bit = sr595_key_bit(rail, SR595_KEY_PWR); const int en = rail_cfg(rail)->efuse_en; TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(rail, ACT_KEYS_PWR, ACT_KEY_MS_FORCE)); run_ms(500); const uint32_t en_writes = sim_gpio_writes(en); t_actuator_abort(rail); TEST_ASSERT_EQUAL_INT(0, sim_sr595_pin(bit)); TEST_ASSERT_FALSE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_INT(ACT_RES_ABORTED, t_actuator_last_result(rail)); TEST_ASSERT_EQUAL_UINT32(en_writes, sim_gpio_writes(en)); } /* 硬重启走到一半被打断:序列停下,但已经断掉的电**不会**被 abort 悄悄开回来。 */ static void test_abort_during_hard_reset_does_not_restore_power(void) { const int rail = 5; const int en = rail_cfg(rail)->efuse_en; TEST_ASSERT_EQUAL_INT(HAL_OK, rail_efuse_enable(rail, true)); TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_hard_reset(rail)); run_ms(1000); t_actuator_abort(rail); TEST_ASSERT_FALSE(t_actuator_busy(rail)); TEST_ASSERT_EQUAL_INT(ACT_RES_ABORTED, t_actuator_last_result(rail)); TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(en)); /* 还是关着的,等上层拿主意 */ } /* ==== 参数与常量 ========================================================== */ static void test_bad_arguments_are_rejected(void) { TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, t_actuator_hard_reset(0)); TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, t_actuator_hard_reset(N + 1)); TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, t_actuator_edl(0)); TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, t_actuator_key(0, ACT_KEYS_PWR, 100)); TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, t_actuator_key(1, 0, 100)); /* 一个键都没选 */ TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, t_actuator_key(1, 1u << 5, 100)); /* 不存在的键 */ TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, t_actuator_key(1, ACT_KEYS_PWR, 0)); /* 按 0 ms */ /* 上限拦的是调用方的手误:传一个 10 分钟进来等于把电源键按死。 */ TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, t_actuator_key(1, ACT_KEYS_PWR, ACT_KEY_MAX_MS + 1)); TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(1, ACT_KEYS_PWR, ACT_KEY_MAX_MS)); /* 被拒的请求一律不留痕迹。 */ TEST_ASSERT_FALSE(t_actuator_busy(2)); TEST_ASSERT_EQUAL_UINT32(0, sim_lock_violations(HAL_LOCK_SR595)); } /* 时间常量与文档逐格对账。改了这里就是改了需求,得先去改文档。 */ static void test_timings_match_the_documents(void) { TEST_ASSERT_EQUAL_UINT32(17, ACT_HR_RAMP_MS); /* tON 5.1 + 斜坡 11.5,§7.4 */ TEST_ASSERT_EQUAL_UINT32(3000, ACT_HR_OFF_MIN_MS); /* §7.4 的 3~5 s 窗口 */ TEST_ASSERT_EQUAL_UINT32(5000, ACT_HR_OFF_MAX_MS); TEST_ASSERT_TRUE(ACT_HR_OFF_MS >= ACT_HR_OFF_MIN_MS && ACT_HR_OFF_MS <= ACT_HR_OFF_MAX_MS); TEST_ASSERT_EQUAL_UINT32(100, ACT_KEY_MS_TAP); /* SW-5 三档 */ TEST_ASSERT_EQUAL_UINT32(2000, ACT_KEY_MS_ON); TEST_ASSERT_EQUAL_UINT32(10000, ACT_KEY_MS_FORCE); TEST_ASSERT_TRUE(ACT_KEY_MS_FORCE <= ACT_KEY_MAX_MS); /* 最长那一档不能被自己的上限拦掉 */ /* 逻辑键位就是 sr595 的那三个下标,组合键是它们的并。 */ TEST_ASSERT_EQUAL_UINT32(ACT_KEYS_VDN | ACT_KEYS_PWR, (uint32_t)ACT_KEYS_FASTBOOT); TEST_ASSERT_EQUAL_UINT32(ACT_KEYS_VUP | ACT_KEYS_PWR, (uint32_t)ACT_KEYS_RECOVERY); } /* 加锁纪律那条检查本身不能是句空话:不拿锁直接写一帧,违规必须 +1。 (没有这条反向测试,上面那些「violations == 0」在检查失灵时会永远绿。) */ static void test_the_lock_discipline_check_actually_fires(void) { TEST_ASSERT_EQUAL_UINT32(0, sim_lock_violations(HAL_LOCK_SR595)); (void)sr595_write((uint32_t)1u << sr595_key_bit(1, SR595_KEY_PWR), 0xFFFFFFFFu & SR595_MASK_VALID); TEST_ASSERT_TRUE(sim_lock_violations(HAL_LOCK_SR595) > 0); } /* 空闲时 `t_actuator_step()` 什么也不做,也不该去动锁或引脚。 */ static void test_idle_step_is_free(void) { const uint32_t takes = sim_lock_takes(HAL_LOCK_SR595); const uint32_t latches = sim_sr595_latches(); for (int i = 0; i < 100; i++) { TEST_ASSERT_EQUAL_UINT32(ACT_IDLE_WAIT_MS, t_actuator_step()); sim_time_advance_ms(ACT_IDLE_WAIT_MS); } TEST_ASSERT_EQUAL_UINT32(takes, sim_lock_takes(HAL_LOCK_SR595)); TEST_ASSERT_EQUAL_UINT32(latches, sim_sr595_latches()); } static void test_format_prints_every_rail(void) { char buf[160]; TEST_ASSERT_EQUAL_INT(HAL_OK, t_actuator_key(2, ACT_KEYS_PWR, ACT_KEY_MS_ON)); const int n = t_actuator_format(buf, sizeof(buf)); TEST_ASSERT_TRUE(n > 0 && n < (int)sizeof(buf)); TEST_ASSERT_NOT_NULL(strstr(buf, "ch2=key-hold")); TEST_ASSERT_NOT_NULL(strstr(buf, "ch5=idle")); printf("%s\n", buf); /* 缓冲太小也不许越界。 */ char small[12]; memset(small, 0x7F, sizeof(small)); const int m = t_actuator_format(small, sizeof(small)); TEST_ASSERT_TRUE(m < (int)sizeof(small)); TEST_ASSERT_EQUAL_CHAR('\0', small[sizeof(small) - 1]); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_hard_reset_walks_the_full_power_up_sequence); RUN_TEST(test_hard_reset_touches_only_its_own_two_pins); RUN_TEST(test_no_pg_leaves_the_rail_powered); RUN_TEST(test_pg_stuck_low_also_leaves_the_rail_powered); RUN_TEST(test_late_pg_is_still_accepted); RUN_TEST(test_the_task_loop_only_sleeps_what_step_asks_for); RUN_TEST(test_hard_reset_releases_a_stuck_key_before_cutting_power); RUN_TEST(test_key_tap_presses_the_table_bit_and_releases_it); RUN_TEST(test_combo_keys_land_in_exactly_one_frame); RUN_TEST(test_recovery_and_fastboot_are_different_key_sets); RUN_TEST(test_a_running_sequence_rejects_every_new_request); RUN_TEST(test_two_rails_run_at_the_same_time_without_clobbering); RUN_TEST(test_edl_order_is_short_then_vbus_then_release); RUN_TEST(test_edl_uses_the_lookup_table_not_arithmetic); RUN_TEST(test_cleanup_retries_until_the_key_is_really_released); RUN_TEST(test_a_failed_press_still_goes_through_cleanup); RUN_TEST(test_abort_releases_the_key_and_leaves_the_pins_alone); RUN_TEST(test_abort_during_hard_reset_does_not_restore_power); RUN_TEST(test_bad_arguments_are_rejected); RUN_TEST(test_timings_match_the_documents); RUN_TEST(test_the_lock_discipline_check_actually_fires); RUN_TEST(test_idle_step_is_free); RUN_TEST(test_format_prints_every_rail); return UNITY_END(); }