/* * 模块名称:保护元件框架(L3,P1 子集)主机侧单元测试 * 模块功能:在 PC 上对 App/algo/prot_elm.c 的判据核做离线验证。 * * 被测对象:真实源文件(#include 原样纳入): * prot_elm.c + prot_set.c(判据吃定值 RAM) * phasor.c + sample_ring.c(prot_elm.c 链接依赖;本测试只走 * prot_elm_eval 纯判据路径,不依赖 run() 适配层) * * 覆盖点(docs/算法/继电保护功能复现规划.md §3.3 L3-1/3/4/6、§8 验收): * TC-PE-001 init:全状态清零、动作位图 0 * TC-PE-002 过量滞回 + 定时限:1.01× 定值启动、延时到点动作、 * 返回系数内保持、跌破返回门槛释放(1.2×/0.9× 验收口径的核) * TC-PE-003 延时 0:启动同拍即动作 * TC-PE-004 欠量方向(UV):低于定值启动、高于返回门槛释放 * TC-PE-005 投退=0:不判据、状态复位 * TC-PE-006 定值闭锁(L5-1 联动):locked → 全元件复位;解锁后恢复 * TC-PE-007 时基回绕:跨 0xFFFFFFFF 的延时判定仍正确 * TC-PE-008 动作位图聚合 * TC-PE-009 守卫:state(NULL)/越界编号、name 越界 * * 通过:全部 TC PASS(main 返回 0)。 * 创建日期:2026-10-11 */ #include #include static int s_total = 0, s_passed = 0, s_failed = 0; #define CHECK(cond, name) do { \ s_total++; \ if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \ else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \ } while (0) #include "prot_set.c" #include "sample_ring.c" #include "phasor.c" #include "prot_elm.c" /* 中性测量基线:电流 1.0 A、电压 100 V、3U0 0 V(全部不越限) */ static void mk_base(float m[PE_COUNT]) { uint32_t i; for (i = 0u; i < (uint32_t)PE_COUNT; i++) { m[i] = 1.0f; } m[PE_OV] = 100.0f; m[PE_UV] = 100.0f; m[PE_3U0] = 0.0f; } static void tc_init_clear(void) { prot_set_init(); prot_elm_init(); CHECK(prot_elm_active_mask() == 0u, "TC-PE-001 init: active mask 0"); { prot_elm_state_t st; uint32_t i; int clean = 1; for (i = 0u; i < (uint32_t)PE_COUNT; i++) { prot_elm_state((prot_elm_id_t)i, &st); if ((st.picked != 0u) || (st.asserted != 0u) || (st.enabled != 0u)) { clean = 0; } } CHECK(clean, "TC-PE-001 init: all element states clear"); } } static void tc_over_hysteresis(void) { float m[PE_COUNT]; prot_elm_state_t st; prot_set_init(); prot_elm_init(); (void)prot_set_set(PS_OC1_DLY, PROT_SET_Q10(100.0)); /* 100 ms */ /* 默认 OC1_CUR=10.0 A,RET_OVER=0.95 → 返回门槛 ≈9.492 */ mk_base(m); m[PE_OC1] = 9.9f; prot_elm_eval(1000u, m); prot_elm_state(PE_OC1, &st); CHECK((st.picked == 0u) && (st.enabled != 0u), "TC-PE-002 0.99x set: no pickup"); m[PE_OC1] = 10.1f; prot_elm_eval(1001u, m); prot_elm_state(PE_OC1, &st); CHECK((st.picked != 0u) && (st.asserted == 0u), "TC-PE-002 1.01x set: pickup before delay"); prot_elm_eval(1100u, m); /* 1100-1001 = 99 ms */ prot_elm_state(PE_OC1, &st); CHECK(st.asserted == 0u, "TC-PE-002 not asserted at 99ms"); prot_elm_eval(1101u, m); /* 100 ms 整 */ prot_elm_state(PE_OC1, &st); CHECK(st.asserted != 0u, "TC-PE-002 asserted at 100ms"); m[PE_OC1] = 9.7f; /* 返回门槛(9.492)与动作门槛(10)之间 */ prot_elm_eval(1200u, m); prot_elm_state(PE_OC1, &st); CHECK((st.picked != 0u) && (st.asserted != 0u), "TC-PE-002 hysteresis: holds between return and pickup threshold"); m[PE_OC1] = 9.4f; /* 跌破返回门槛 */ prot_elm_eval(1300u, m); prot_elm_state(PE_OC1, &st); CHECK((st.picked == 0u) && (st.asserted == 0u), "TC-PE-002 below return threshold: drops out"); } static void tc_zero_delay(void) { float m[PE_COUNT]; prot_elm_state_t st; prot_set_init(); prot_elm_init(); (void)prot_set_set(PS_OC3_DLY, PROT_SET_Q10(0.0)); mk_base(m); m[PE_OC3] = 2.1f; /* OC3 定值 2.0 */ prot_elm_eval(5000u, m); prot_elm_state(PE_OC3, &st); CHECK((st.picked != 0u) && (st.asserted != 0u), "TC-PE-003 dly=0: asserted on the same beat as pickup"); } static void tc_under_direction(void) { float m[PE_COUNT]; prot_elm_state_t st; prot_set_init(); prot_elm_init(); (void)prot_set_set(PS_UV_DLY, PROT_SET_Q10(50.0)); /* 默认 UV_VOL=20.0,RET_UNDER=1.05 → 返回门槛 ≈20.996 */ mk_base(m); m[PE_UV] = 25.0f; prot_elm_eval(2000u, m); prot_elm_state(PE_UV, &st); CHECK(st.picked == 0u, "TC-PE-004 UV above set: no pickup"); m[PE_UV] = 19.0f; prot_elm_eval(2001u, m); prot_elm_state(PE_UV, &st); CHECK(st.picked != 0u, "TC-PE-004 UV below set: pickup"); prot_elm_eval(2051u, m); /* 50 ms */ prot_elm_state(PE_UV, &st); CHECK(st.asserted != 0u, "TC-PE-004 UV asserted after delay"); m[PE_UV] = 20.5f; /* 20 与 20.996 之间:保持 */ prot_elm_eval(2100u, m); prot_elm_state(PE_UV, &st); CHECK(st.asserted != 0u, "TC-PE-004 UV holds below return threshold"); m[PE_UV] = 21.5f; /* 高于返回门槛:释放 */ prot_elm_eval(2200u, m); prot_elm_state(PE_UV, &st); CHECK((st.picked == 0u) && (st.asserted == 0u), "TC-PE-004 UV above return threshold: drops out"); } static void tc_disabled(void) { float m[PE_COUNT]; prot_elm_state_t st; prot_set_init(); prot_elm_init(); (void)prot_set_set(PS_OC2_EN, 0); mk_base(m); m[PE_OC2] = 999.0f; prot_elm_eval(3000u, m); prot_elm_state(PE_OC2, &st); CHECK((st.enabled == 0u) && (st.picked == 0u) && (st.asserted == 0u), "TC-PE-005 enable=0: no judgement even at 999x"); } static void tc_set_lock(void) { float m[PE_COUNT]; prot_elm_state_t st; prot_set_init(); prot_elm_init(); mk_base(m); m[PE_OC1] = 10.1f; (void)prot_set_set(PS_OC1_DLY, PROT_SET_Q10(0.0)); prot_elm_eval(4000u, m); prot_elm_state(PE_OC1, &st); CHECK(st.asserted != 0u, "TC-PE-006 pre: element operates while settings valid"); (void)prot_set_set(PS_OC1_CUR, PROT_SET_Q10(9.0)); /* 改值不封存 */ CHECK(prot_set_selfcheck() == -1, "TC-PE-006 tamper detected by selfcheck"); prot_elm_eval(4100u, m); prot_elm_state(PE_OC1, &st); CHECK((st.enabled == 0u) && (st.asserted == 0u), "TC-PE-006 settings lock resets all elements (L5-1 gating)"); prot_set_seal(); prot_elm_eval(4200u, m); prot_elm_state(PE_OC1, &st); CHECK((st.enabled != 0u) && (st.asserted != 0u), "TC-PE-006 unlock restores judgement"); } static void tc_tick_wrap(void) { float m[PE_COUNT]; prot_elm_state_t st; prot_set_init(); prot_elm_init(); (void)prot_set_set(PS_OC2_DLY, PROT_SET_Q10(100.0)); mk_base(m); m[PE_OC2] = 5.1f; /* OC2 定值 5.0 */ prot_elm_eval(0xFFFFFFF0u, m); /* 启动点贴近回绕 */ prot_elm_state(PE_OC2, &st); CHECK(st.picked != 0u, "TC-PE-007 pickup near tick wrap"); prot_elm_eval(0x00000010u, m); /* 回绕后 32 ms */ prot_elm_state(PE_OC2, &st); CHECK(st.asserted == 0u, "TC-PE-007 not asserted at 32ms after wrap"); prot_elm_eval(0x00000060u, m); /* 回绕后 112 ms */ prot_elm_state(PE_OC2, &st); CHECK(st.asserted != 0u, "TC-PE-007 asserted at 112ms across wrap"); } static void tc_active_mask(void) { float m[PE_COUNT]; prot_set_init(); prot_elm_init(); (void)prot_set_set(PS_OC1_DLY, PROT_SET_Q10(0.0)); (void)prot_set_set(PS_OV_DLY, PROT_SET_Q10(0.0)); mk_base(m); m[PE_OC1] = 10.1f; m[PE_OV] = 130.0f; /* OV 定值 120 */ prot_elm_eval(6000u, m); CHECK(prot_elm_active_mask() == ((1uL << PE_OC1) | (1uL << PE_OV)), "TC-PE-008 active mask aggregates OC1+OV only"); } static void tc_guards(void) { prot_elm_state_t st; prot_set_init(); prot_elm_init(); prot_elm_state(PE_COUNT, &st); CHECK((st.enabled == 0u) && (st.picked == 0u) && (st.asserted == 0u), "TC-PE-009 out-of-range id -> zero state"); prot_elm_state(PE_OC1, NULL); /* 不得崩溃 */ CHECK(prot_elm_name(PE_COUNT) != NULL, "TC-PE-009 name out-of-range -> fallback"); CHECK(prot_elm_name(PE_3U0) != NULL, "TC-PE-009 name valid id -> non-NULL"); prot_elm_eval(7000u, NULL); /* NULL 测量:按 0 处理,不得崩溃 */ CHECK(1, "TC-PE-009 eval(NULL meas) survives"); } int main(void) { printf("=== Prot Elm Tests ===\n"); tc_init_clear(); tc_over_hysteresis(); tc_zero_delay(); tc_under_direction(); tc_disabled(); tc_set_lock(); tc_tick_wrap(); tc_active_mask(); tc_guards(); printf("\n=== Prot Elm Tests: %d/%d PASSED (failed=%d) ===\n", s_passed, s_total, s_failed); return (s_failed == 0) ? 0 : 1; }