90db528032
W1-0 台账三件套:scripts/ram_baseline.py(四模式)+ ram_baseline.tsv 趋势 + docs/内存管理/RAM预算台账.md + pre-commit mem-budget 钩子(RW_IRAM1/RW_CCM >92% 即 FAIL + Top-10 附表);审查报告 §5.5 三条闭环。 B4 IEC 60870-5-104 上行(被控站,TCP 2404):App/protocol/iec104/ 三模块 (asdu 编解码 / pts 点表+数据源 vtable / link APCI+FSM,纯逻辑零 I/O)+ App/task/iec104_serv.c(iec104Task,NET_READY 屏障 100ms 分片喂位)。 82 断言 L0.5 全绿 + iec104-unit-test 钩子(第 31 钩子); 设计说明 docs/子系统设计/IEC104上行设计说明.md 全量回填。 B5 protectTask(L3 板端接线):App/task/protect_task.c——20ms 判据节拍 prot_elm_run + L5-4 采样故障闭锁(adc_health 电平位→元件复位)+ L5-1 定值 校验和巡检;新增 prot_elm_request_reset() 收口跨上下文复归(104 YK11 落请求、 eval 头部消费);定值/元件初始化移入 app_main_init(内核前)。TC-PE-010 (prot_elm 28 用例)。 门禁与内存:TASK_WD_PROTECT=8 入 required_mask(产品 0x77→0x1F7、HW 层 0x1F5,看门狗单测 39 断言同步);FreeRTOS 堆 48→56KB(.ioc L1 参数 + FreeRTOSConfig.h 同步,RAM预算台账 §6 登记配额 48→56KB)。 ZI 144,008→152,204(+8,196 B = 堆 +8KB,B5 扩容);Code=204,348。 stack_report:protectTask 512 words 静态链 872 B/余 57%、iec104Task 640 words 1456 B/余 43% 均 PASS;RW_IRAM1 73.1% / RW_CCM 87.9%(<92%)。 验证:@build 0 错 0 警;ci_local 全绿(31 钩子 + 13 主机侧单测); doc-links 135 文档 PASS。登记簿 B4/B5 销项、W1 波次完成;保护规划/看门狗 说明/AGENTS(9 任务)/RAM 三文档全部同步。 遗留(验收窗口,非代码空洞):台架第三方 104 主站往返(送检窗口)、 标准源注入动作判据、@test PROT 锚点 + protect 栈水位 + 新堆 min_ever 复测。
303 lines
10 KiB
C
303 lines
10 KiB
C
/*
|
||
* 模块名称:保护元件框架(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-PE-010 跨上下文复归请求(B5):request 不当拍生效、下一拍 eval 头部消费
|
||
*
|
||
* 通过:全部 TC PASS(main 返回 0)。
|
||
* 创建日期:2026-10-11
|
||
*/
|
||
#include <stdint.h>
|
||
#include <stdio.h>
|
||
|
||
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");
|
||
}
|
||
|
||
static void tc_request_reset(void) {
|
||
float m[PE_COUNT];
|
||
prot_elm_state_t st;
|
||
|
||
prot_set_init();
|
||
prot_elm_init();
|
||
mk_base(m);
|
||
m[PE_OC1] = 999.0f; /* OC1 默认 dly=0 → 启动同拍动作 */
|
||
prot_elm_eval(1000u, m);
|
||
prot_elm_state(PE_OC1, &st);
|
||
CHECK(st.asserted != 0u, "TC-PE-010 pre: OC1 asserted (default dly=0)");
|
||
|
||
prot_elm_request_reset(); /* 跨上下文请求:不当拍生效 */
|
||
prot_elm_state(PE_OC1, &st);
|
||
CHECK(st.asserted != 0u, "TC-PE-010 request alone does not reset");
|
||
|
||
/* 下一拍头部消费请求 → 复位;测量落在滞回保持区(9.6 ∈ [返回9.5, 动作10))
|
||
* ——不复位则 picked/asserted 保持,复位后保持清零,可区分 */
|
||
m[PE_OC1] = 9.6f;
|
||
prot_elm_eval(1100u, m);
|
||
prot_elm_state(PE_OC1, &st);
|
||
CHECK((st.picked == 0u) && (st.asserted == 0u),
|
||
"TC-PE-010 next eval consumes request -> cleared in hysteresis band");
|
||
}
|
||
|
||
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();
|
||
tc_request_reset();
|
||
|
||
printf("\n=== Prot Elm Tests: %d/%d PASSED (failed=%d) ===\n",
|
||
s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|