feat(algo): B8 频率测量层 + 过频/低频保护(L3-5)
新增 App/algo/freq.c/h:由 L2 基波相量导出系统频率。相邻两拍共轭 乘积 P=Z(m)·conj(Z(m-1)) 取角 = 每拍相位增量(免显式解缠绕),再按 自适应窗长 K_eff=round(8·Fs/(2·f_hat))(45~55 Hz 段 73~89 拍)做滑动 平均。定长窗受 20 点矩形窗负序泄漏纹波限制(实测扫频最坏 0.317 Hz; 60 拍 Kay 复数和 0.197 Hz,交叉项和角不按 1/K^2 衰减),自适应窗使 窗长恰为纹波周期 fs/(2f) 整数倍 -> 最坏 0.0101 Hz。推导与两轮弯路 写在 freq.c 文件头。 假值钳制带取 35~65 Hz,刻意宽于系统合理带 40.5~60.5:单拍相位差本身 带 ±5 Hz 泄漏纹波,贴边钳制会非对称丢弃纹波半周 -> 均值偏置(实测 45 Hz 点 +0.16 Hz)。系统合理带由 OF/UF 定值范围在消费侧把关。 幅值门 FREQ_AMP_MIN=50 码(拟定,待 L1-5 标定复核):低于门判无有效 波形,停更新、valid 清零、读数保持 -> 浮空/PT 断线不发散。源优先级 UA(C02)->IA(C01),按名前缀解析、每拍动态回退,切换即废相位基准。 prot_set:尾追加 8 项(PS_ID_COUNT 32->40,编号不回收) OF_EN/OF_HZ/OF_DLY/UF_EN/UF_HZ/UF_DLY/RET_OF/RET_UF。OF/UF 逐项通讯 地址不在本仓库 -> 全部 comm_addr=0 本地项(104 C_SE 不可寻址,待对照 参考表补)。频率返回系数独立(0.99/1.01),勿与电气量 0.95/1.05 共用。 bad_mask 随项数扩为 uint64_t。PROT_SET_BLOB_VER 1->2。 prot_set_store:换代与损坏分流。load 返回 2 = 版本/项数换代(本文件 打 INFO "prot set blob version change, defaults in use"),-1 = 真损坏 (defaultTask 按 CORRUPT 报 ERROR + 自检留痕)。B7 旧档(ver=1/32 项) 升级首启属预期路径,不得报成损坏。语义分流收在属主文件内, default_task.c 不动。 prot_elm:实例表加 src 字段(0=phasor rms,1=freq),PE_OF/PE_UF 尾 追加(PE_COUNT 7->9)。freq 无有效读数按方向填"必不启动"值(过量 0 / 欠量 1e30 -> 滞回比较恒假 -> 元件复位),不整拍跳过——频率源缺失不得 饿死电流/电压类元件(B5 行为不回归)。OF/UF 暂未映射独立 104 遥信点, 动作只经 YX15 保护总聚合可见(送检点表 = B10)。 板端接线:adcTask 在 phasor_run() 之后同拍调 freq_run();心跳/首拍日志 追加 freq(<tag>): f=%ld valid=%u (x0.01)(MicroLIB 无 %f)。app_main 内核前 freq_init()。 主机单测:freq 46 用例(J1 扫频 45~55 Hz <=0.02 Hz 实测最坏 0.0101; J2 噪声 1% <=0.10 实测 0.0160;J3 启动收敛 <=40 ms 实测 20;J4 阶跃 50->51 <=120 ms 实测 98;J5 浮空 valid=0 不发散;J6 跳变 60 ms 内回 ±0.05;另直流 8000 码 / 3-5 次谐波 20%/10% / UA->IA 回退)。 prot_set 90(+TC-PS-011 OF/UF 行 + 64 位 bad_mask bit39 + 旧档拒绝)、 prot_elm 47(+OF/UF 滞回与方向隔离 + run() 测频接线)。回归:phasor 57、iec104 94 全绿。 J4=120 ms 是"精度优先于响应"的显式取舍(Lead 批准):4 周波窗换 0.317->0.0101 Hz,测频响应 98 ms << OF/UF 定时限默认 1000 ms(占 总动作时间 <10%),暂态另有 J6 <=60 ms 兜底。 注册:freq.c 入 uvprojx algo 组 + check_registration.py SIM_EXEMPT (纯算法、模拟器无消费路径,照 prot_set.c 先例)。 @build 0 错误 0 警告(Code=207468 RO=18304 RW=1068 ZI=153084)。 RAM:RW_IRAM1 96096->96728 B(+632 B,73.3%->73.8%)、RW_CCM 不变 57376 B(87.9%)、Flash 224596->226840 B(+2244 B,21.4%->21.6%); 台账随合并由 Lead 回填。stack_report:adcTask 静态最坏链 1320 B / 余量 1240 B(48%)改前后完全不变(freq_run 帧未进最坏链,无需抬栈); protectTask 链 872 B / 余 1176 B(57%)同样不变。 gate: only mem-budget/mem-numbers pending lead ledger backfill -------------------------------------------------------------- 门禁实跑(pre-commit run --all-files,Lead 批准 --no-verify 提交): 28 Passed / 2 Failed,失败仅 mem-numbers 与 mem-budget 两条——均为 docs/内存管理 受管快照与本 .map 不一致(台账由 Lead 每次合并后 --write 回填,避免多分支同改受管表冲突)。其余含 doc-links / coding-standard / no-mojibake / cppcheck / registration-check / 13 条主机单测钩子全 Passed(gitlint 属 commit-msg 阶段,不在该次 --all-files 内)。ci_local L0.5 全绿(freq 46/46 等)。 字节卫生自查(--no-verify 跳过卫生钩子,故手工核):17 个暂存文件 全部 BOM=无 / CR=0(纯 LF)/ U+FFFD=0;自研 .c/.h 去注释后字符串 字面量 100% ASCII(唯一非 ASCII 字面量在 test/host/iec104/iec104_test.c 的既有用例名,非本次改动行;test/ 不在 coding-standard 钩子范围)。
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* 模块名称:频率测量层(L2 派生量,B8)主机侧单元测试
|
||||
* 模块功能:在 PC 上对 App/algo/freq.c + phasor.c + sample_ring.c 做离线验证。
|
||||
*
|
||||
* 被测对象:真实源文件(#include 原样纳入,纯算法无需桩):
|
||||
* ../../../App/algo/sample_ring.c
|
||||
* ../../../App/algo/phasor.c
|
||||
* ../../../App/algo/freq.c
|
||||
*
|
||||
* 精度判据(与 App/algo/README.md §3.7 同口径,规划 §4.2 测频范围的落实):
|
||||
* J1 纯音扫频 45.00~55.00 Hz(0.5 Hz 步进):|f_meas - f_true| <= 0.02 Hz
|
||||
* (实测最坏 0.0101 Hz @55.0 Hz,判据留 2 倍裕度)
|
||||
* J2 噪声工况(幅值 1000 码、噪声 rms = 1% 幅值):|f_meas - f_true| <= 0.10 Hz
|
||||
* (实测最坏 0.016 Hz,判据留 ~6 倍裕度)
|
||||
* J3 启动收敛:首个有效相量后 <= 40 ms 出有效读数
|
||||
* J4 频率阶跃 50->51 Hz:<= 120 ms 进入 ±0.05 Hz 并保持(自适应窗
|
||||
* K_eff≈73~89 拍,阶跃后窗冲刷决定物理下限;OF/UF 定时限 >=100 ms
|
||||
* 叠加其上,总动作时间仍 <220 ms,符合定时限保护常规)
|
||||
* J5 零幅/浮空(rms < 幅值门):valid=0、读数保持、不发散(无 NaN/无跳变;
|
||||
* 信号消失过渡段 <=20 ms 窗内残窗参与均值,读数漂移有界 <=0.3 Hz)
|
||||
* J6 假值钳制:相位/幅值跳变拍产生的单拍折算越出 35~65 Hz 假值带 -> 丢弃,
|
||||
* 读数在跳变后 60 ms 内回到 ±0.05 Hz
|
||||
*
|
||||
* 覆盖点:
|
||||
* TC-FR-001 50 Hz 收敛时序(J3)+ 稳态精度(J1 子集)
|
||||
* TC-FR-002 扫频 45~55 Hz @0.5 Hz(J1,逐点判 + 最坏值打印)
|
||||
* TC-FR-003 噪声鲁棒(J2,最坏值打印)
|
||||
* TC-FR-004 频率阶跃 50->51(J4)
|
||||
* TC-FR-005 零幅浮空不发散(J5)+ 恢复后重新收敛
|
||||
* TC-FR-006 直流偏置抑制(全周傅氏滤直流 -> 测频不受影响)
|
||||
* TC-FR-007 3/5 次谐波污染抑制(相量基波选择性)
|
||||
* TC-FR-008 相位跳变假值钳制(J6)
|
||||
* TC-FR-009 UA 无幅值 -> 自动落 IA 源
|
||||
* TC-FR-010 init 幂等 / get 参数守卫 / 未收敛 -2
|
||||
* TC-FR-011 幅值门中途失效 -> valid 清零 + 读数保持(J5 中途版)
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 创建日期:2026-10-12(B8)
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define FR_PI 3.14159265358979323846
|
||||
#define FR_FS 1000.0
|
||||
|
||||
/* ===================== 断言 ===================== */
|
||||
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 "sample_ring.c"
|
||||
#include "phasor.c"
|
||||
#include "freq.c"
|
||||
|
||||
/* ===================== 波形驱动 ===================== */
|
||||
|
||||
/* 全局采样时刻(拍):驱动函数按此生成"当前时刻"的样本,跨调用连续 */
|
||||
static uint32_t s_t = 0u;
|
||||
|
||||
/* 确定性伪随机(LCG):噪声可复现 */
|
||||
static uint32_t s_rng = 0x12345678u;
|
||||
static double s_noise_unit(void) { /* [-0.5, 0.5) */
|
||||
s_rng = s_rng * 1103515245u + 12345u;
|
||||
return (double)((s_rng >> 8) & 0xFFFFFFu) / (double)0x1000000u - 0.5;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:合成一拍整帧并跑一遍 L2+测频链
|
||||
* 入口参数:f - 信号频率(Hz)
|
||||
* amp - 基波峰值(码值)
|
||||
* dc - 直流偏置(码值)
|
||||
* h3, h5 - 3/5 次谐波峰值(码值)
|
||||
* noise_rms- 高斯样近似噪声 rms(码值,均匀分布近似)
|
||||
* ua_en - UA 通道是否带波形(0 = 只挂 0 码,测 IA 回退)
|
||||
* 返 回 值:无
|
||||
* 限定条件:无
|
||||
* 函数说明:9 路语义通道同波形(除 ua_en=0 时 UA 置 0);每拍
|
||||
* push_frame -> phasor_run -> freq_run,与 adcTask 同序。
|
||||
*/
|
||||
static void drive_beat(double f, double amp, double dc, double h3, double h5,
|
||||
double noise_rms, uint8_t ua_en) {
|
||||
float frame[ADC_SEM_COUNT];
|
||||
double t = (double)s_t / FR_FS;
|
||||
double v = amp * sin(2.0 * FR_PI * f * t)
|
||||
+ h3 * sin(2.0 * FR_PI * 3.0 * f * t)
|
||||
+ h5 * sin(2.0 * FR_PI * 5.0 * f * t)
|
||||
+ dc
|
||||
+ s_noise_unit() * noise_rms * 1.7320508; /* 均匀 -> rms 折算 */
|
||||
uint8_t c;
|
||||
|
||||
for (c = 0u; c < (uint8_t)ADC_SEM_COUNT; c++) {
|
||||
frame[c] = (float)v;
|
||||
}
|
||||
if (ua_en == 0u) {
|
||||
frame[ADC_SEM_UA] = 0.0f; /* UA 无波形(门失败),IA 仍有 */
|
||||
}
|
||||
if (f == 0.0) {
|
||||
for (c = 0u; c < (uint8_t)ADC_SEM_COUNT; c++) {
|
||||
frame[c] = 0.0f; /* 浮空:全 0 帧 */
|
||||
}
|
||||
}
|
||||
(void)sample_ring_push_frame(frame, (uint8_t)ADC_SEM_COUNT);
|
||||
phasor_run();
|
||||
freq_run();
|
||||
s_t++;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:连续驱动 n 拍
|
||||
*/
|
||||
static void drive_n(uint32_t n, double f, double amp, double dc, double h3,
|
||||
double h5, double noise_rms, uint8_t ua_en) {
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0u; i < n; i++) {
|
||||
drive_beat(f, amp, dc, h3, h5, noise_rms, ua_en);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:复位整条链(采样时刻 + 三层状态)
|
||||
*/
|
||||
static void chain_reset(void) {
|
||||
s_t = 0u;
|
||||
s_rng = 0x12345678u;
|
||||
(void)phasor_init(); /* 连带 sample_ring_init */
|
||||
(void)freq_init();
|
||||
}
|
||||
|
||||
/* ===================== TC-FR-001:50 Hz 收敛时序 + 稳态精度 ===================== */
|
||||
static void tc_converge_50(void) {
|
||||
float f = 0.0f;
|
||||
uint32_t first_valid_t = 0u, phasor_ready_t = 0u;
|
||||
uint32_t i;
|
||||
int got = 0;
|
||||
|
||||
chain_reset();
|
||||
for (i = 0u; i < 200u; i++) {
|
||||
drive_beat(50.0, 1000.0, 0.0, 0.0, 0.0, 0.0, 1u);
|
||||
if (phasor_ready_t == 0u) {
|
||||
phasor_t p;
|
||||
if (phasor_get(1u, &p) == 0) {
|
||||
phasor_ready_t = i; /* 首个有效相量拍 */
|
||||
}
|
||||
}
|
||||
if (!got && (freq_valid() != 0u) && (freq_get(&f) == 0)) {
|
||||
first_valid_t = i;
|
||||
got = 1;
|
||||
}
|
||||
}
|
||||
CHECK(got, "TC-FR-001 freq becomes valid at 50 Hz");
|
||||
CHECK((first_valid_t >= phasor_ready_t)
|
||||
&& ((first_valid_t - phasor_ready_t) <= 40u),
|
||||
"TC-FR-001 J3: first valid reading <= 40 ms after first phasor");
|
||||
(void)freq_get(&f);
|
||||
printf(" [DATA] 50 Hz steady: f=%.4f err=%.4f Hz (first valid @%u ms, phasor @%u ms)\n",
|
||||
(double)f, (double)f - 50.0, (unsigned)first_valid_t, (unsigned)phasor_ready_t);
|
||||
CHECK(fabsf(f - 50.0f) <= 0.01f, "TC-FR-001 J1(50Hz): |err| <= 0.01 Hz");
|
||||
}
|
||||
|
||||
/* ===================== TC-FR-002:扫频 45~55 Hz @0.5 Hz(J1) ===================== */
|
||||
static void tc_sweep(void) {
|
||||
double worst = 0.0, worst_f = 0.0;
|
||||
int ok = 1;
|
||||
int k;
|
||||
|
||||
for (k = 0; k <= 20; k++) {
|
||||
double f_true = 45.0 + 0.5 * (double)k;
|
||||
float f_meas = 0.0f;
|
||||
double err;
|
||||
|
||||
chain_reset();
|
||||
drive_n(150u, f_true, 1000.0, 0.0, 0.0, 0.0, 0.0, 1u);
|
||||
CHECK(freq_get(&f_meas) == 0, "TC-FR-002 freq valid at each sweep point");
|
||||
err = fabs((double)f_meas - f_true);
|
||||
if (err > worst) { worst = err; worst_f = f_true; }
|
||||
if (err > 0.02) { ok = 0; }
|
||||
}
|
||||
printf(" [DATA] sweep 45.00~55.00 Hz step 0.5: worst |err| = %.4f Hz @ %.1f Hz (J1 <= 0.02)\n",
|
||||
worst, worst_f);
|
||||
CHECK(ok, "TC-FR-002 J1: |f_meas - f_true| <= 0.02 Hz over 45~55 Hz");
|
||||
}
|
||||
|
||||
/* ===================== TC-FR-003:噪声鲁棒(J2) ===================== */
|
||||
static void tc_noise(void) {
|
||||
double worst = 0.0;
|
||||
int ok = 1;
|
||||
int k;
|
||||
|
||||
for (k = 0; k < 5; k++) {
|
||||
double f_true = 45.0 + 2.5 * (double)k; /* 45/47.5/50/52.5/55 */
|
||||
float f_meas = 0.0f;
|
||||
double err;
|
||||
|
||||
chain_reset();
|
||||
drive_n(200u, f_true, 1000.0, 0.0, 0.0, 0.0, 10.0, 1u); /* rms = 1% 幅值 */
|
||||
(void)freq_get(&f_meas);
|
||||
err = fabs((double)f_meas - f_true);
|
||||
if (err > worst) { worst = err; }
|
||||
if (err > 0.10) { ok = 0; }
|
||||
}
|
||||
printf(" [DATA] noise rms=1%% amp: worst |err| = %.4f Hz (J2 <= 0.10)\n", worst);
|
||||
CHECK(ok, "TC-FR-003 J2: |err| <= 0.10 Hz with 1% noise");
|
||||
}
|
||||
|
||||
/* ===================== TC-FR-004:频率阶跃 50->51(J4) ===================== */
|
||||
static void tc_step(void) {
|
||||
float f = 0.0f;
|
||||
uint32_t settle = 0u;
|
||||
uint32_t i;
|
||||
int ok = 1;
|
||||
|
||||
chain_reset();
|
||||
drive_n(150u, 50.0, 1000.0, 0.0, 0.0, 0.0, 0.0, 1u);
|
||||
(void)freq_get(&f);
|
||||
printf(" [DATA] pre-step f=%.4f\n", (double)f);
|
||||
|
||||
/* 阶跃拍 = t=150;判"进入 ±0.05 且保持" */
|
||||
for (i = 0u; i < 100u; i++) {
|
||||
drive_beat(51.0, 1000.0, 0.0, 0.0, 0.0, 0.0, 1u);
|
||||
(void)freq_get(&f);
|
||||
if (settle == 0u) {
|
||||
if (fabsf(f - 51.0f) <= 0.05f) {
|
||||
settle = i + 1u; /* 阶跃后第 N 拍进入带内 */
|
||||
}
|
||||
} else if (fabsf(f - 51.0f) > 0.05f) {
|
||||
ok = 0; /* 进入后又出带 */
|
||||
}
|
||||
}
|
||||
printf(" [DATA] step 50->51: settle = %u ms, final f=%.4f err=%.4f\n",
|
||||
(unsigned)settle, (double)f, (double)f - 51.0);
|
||||
CHECK((settle != 0u) && (settle <= 120u), "TC-FR-004 J4: settle <= 120 ms");
|
||||
CHECK(ok, "TC-FR-004 J4: stays within +/-0.05 Hz after settle");
|
||||
CHECK(fabsf(f - 51.0f) <= 0.01f, "TC-FR-004 J1(51Hz): steady |err| <= 0.01 Hz");
|
||||
}
|
||||
|
||||
/* ===================== TC-FR-005:零幅浮空不发散(J5) ===================== */
|
||||
static void tc_zero_amp(void) {
|
||||
float f = 123.0f;
|
||||
uint32_t i;
|
||||
int never_valid = 1, no_nan = 1;
|
||||
|
||||
chain_reset();
|
||||
for (i = 0u; i < 500u; i++) {
|
||||
drive_beat(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1u); /* 全 0 帧(浮空 rms=0) */
|
||||
if (freq_valid() != 0u) { never_valid = 0; }
|
||||
(void)freq_get(&f);
|
||||
if (!(f == f)) { no_nan = 0; } /* NaN 自比较为假 */
|
||||
}
|
||||
CHECK(never_valid, "TC-FR-005 J5: zero-amplitude never asserts valid");
|
||||
CHECK(no_nan && (f == 0.0f), "TC-FR-005 J5: reading stays 0, no divergence/NaN");
|
||||
/* 恢复:重新注入波形后应再次收敛 */
|
||||
drive_n(150u, 49.0, 1000.0, 0.0, 0.0, 0.0, 0.0, 1u);
|
||||
(void)freq_get(&f);
|
||||
printf(" [DATA] recover after float: f=%.4f err=%.4f\n", (double)f, (double)f - 49.0);
|
||||
CHECK((freq_valid() != 0u) && (fabsf(f - 49.0f) <= 0.01f),
|
||||
"TC-FR-005 recovers and meets J1 after waveform returns");
|
||||
}
|
||||
|
||||
/* ===================== TC-FR-006:直流偏置抑制 ===================== */
|
||||
static void tc_dc_offset(void) {
|
||||
float f = 0.0f;
|
||||
|
||||
chain_reset();
|
||||
drive_n(150u, 50.3, 1000.0, 8000.0, 0.0, 0.0, 0.0, 1u); /* DC = 8000 码 */
|
||||
(void)freq_get(&f);
|
||||
printf(" [DATA] DC=8000: f=%.4f err=%.4f\n", (double)f, (double)f - 50.3);
|
||||
CHECK(fabsf(f - 50.3f) <= 0.01f, "TC-FR-006 DC offset rejected: |err| <= 0.01 Hz");
|
||||
}
|
||||
|
||||
/* ===================== TC-FR-007:3/5 次谐波污染 ===================== */
|
||||
static void tc_harmonics(void) {
|
||||
float f = 0.0f;
|
||||
|
||||
chain_reset();
|
||||
drive_n(150u, 48.0, 1000.0, 0.0, 200.0, 100.0, 0.0, 1u); /* 20%/10% 谐波 */
|
||||
(void)freq_get(&f);
|
||||
printf(" [DATA] h3=20%% h5=10%%: f=%.4f err=%.4f\n", (double)f, (double)f - 48.0);
|
||||
CHECK(fabsf(f - 48.0f) <= 0.01f, "TC-FR-007 harmonics rejected: |err| <= 0.01 Hz");
|
||||
}
|
||||
|
||||
/* ===================== TC-FR-008:相位跳变假值钳制(J6) ===================== */
|
||||
static void tc_phase_jump(void) {
|
||||
float f = 0.0f;
|
||||
uint32_t i;
|
||||
int ok = 1;
|
||||
|
||||
chain_reset();
|
||||
drive_n(150u, 50.0, 1000.0, 0.0, 0.0, 0.0, 0.0, 1u);
|
||||
/* 跳变:瞬时切到相位偏移 60 度的等价频率段——用 1 拍 55 Hz 高幅制造
|
||||
* 窗内畸变(f_inst 出带),随后回 50 Hz,判 60 ms 内回带 */
|
||||
drive_beat(55.0, 4000.0, 0.0, 0.0, 0.0, 0.0, 1u); /* 幅值/频率双跳变 */
|
||||
for (i = 0u; i < 60u; i++) {
|
||||
drive_beat(50.0, 1000.0, 0.0, 0.0, 0.0, 0.0, 1u);
|
||||
(void)freq_get(&f);
|
||||
if (i >= 59u) {
|
||||
if (fabsf(f - 50.0f) > 0.05f) { ok = 0; }
|
||||
}
|
||||
}
|
||||
printf(" [DATA] after jump: f=%.4f err=%.4f (recover window 60 ms)\n",
|
||||
(double)f, (double)f - 50.0);
|
||||
CHECK(ok, "TC-FR-008 J6: recovers to +/-0.05 Hz within 60 ms after jump");
|
||||
CHECK(freq_valid() != 0u, "TC-FR-008 J6: still valid after clamp path");
|
||||
}
|
||||
|
||||
/* ===================== TC-FR-009:UA 无幅值 -> IA 回退 ===================== */
|
||||
static void tc_src_fallback(void) {
|
||||
float f = 0.0f;
|
||||
|
||||
chain_reset();
|
||||
drive_n(150u, 52.0, 1000.0, 0.0, 0.0, 0.0, 0.0, 0u); /* UA 置 0,IA 带波形 */
|
||||
(void)freq_get(&f);
|
||||
printf(" [DATA] UA=0 IA=1000: f=%.4f err=%.4f\n", (double)f, (double)f - 52.0);
|
||||
CHECK((freq_valid() != 0u) && (fabsf(f - 52.0f) <= 0.01f),
|
||||
"TC-FR-009 falls back to IA source when UA below amplitude gate");
|
||||
}
|
||||
|
||||
/* ===================== TC-FR-010:init 幂等 / 参数守卫 ===================== */
|
||||
static void tc_init_guards(void) {
|
||||
float f = 5.0f;
|
||||
|
||||
chain_reset();
|
||||
CHECK(freq_get(&f) == -2, "TC-FR-010 get before convergence -> -2");
|
||||
CHECK((f == 0.0f), "TC-FR-010 get -2 writes 0 to out");
|
||||
CHECK(freq_get(NULL) == -1, "TC-FR-010 NULL out -> -1");
|
||||
CHECK(freq_valid() == 0u, "TC-FR-010 valid=0 before convergence");
|
||||
CHECK(freq_last() == 0.0f, "TC-FR-010 last=0 before convergence");
|
||||
(void)freq_init();
|
||||
CHECK(freq_get(&f) == -2, "TC-FR-010 freq_init idempotent (still -2)");
|
||||
}
|
||||
|
||||
/* ===================== TC-FR-011:幅值门中途失效 -> 保持 ===================== */
|
||||
static void tc_gate_hold(void) {
|
||||
float f_before = 0.0f, f_after = 0.0f;
|
||||
|
||||
chain_reset();
|
||||
drive_n(150u, 50.0, 1000.0, 0.0, 0.0, 0.0, 0.0, 1u);
|
||||
(void)freq_get(&f_before);
|
||||
CHECK(freq_valid() == 1u, "TC-FR-011 valid before gate loss");
|
||||
|
||||
drive_n(50u, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1u); /* 波形消失 */
|
||||
CHECK(freq_valid() == 0u, "TC-FR-011 J5: valid cleared on gate loss");
|
||||
(void)freq_get(&f_after);
|
||||
printf(" [DATA] hold: before=%.4f held=%.4f delta=%.4f\n",
|
||||
(double)f_before, (double)f_after, (double)(f_after - f_before));
|
||||
CHECK(fabsf(f_after - f_before) <= 0.3f,
|
||||
"TC-FR-011 J5: reading bounded (|delta| <= 0.3 Hz) during gate loss");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== Freq Tests (B8: phase-unwrap frequency measurement) ===\n");
|
||||
tc_converge_50();
|
||||
tc_sweep();
|
||||
tc_noise();
|
||||
tc_step();
|
||||
tc_zero_amp();
|
||||
tc_dc_offset();
|
||||
tc_harmonics();
|
||||
tc_phase_jump();
|
||||
tc_src_fallback();
|
||||
tc_init_guards();
|
||||
tc_gate_hold();
|
||||
|
||||
printf("\n=== Freq Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
return (s_failed == 0) ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""频率测量层(L2 派生量,B8)主机侧单元测试运行器(L0.5,纯标准库)。
|
||||
|
||||
做什么:用本机 MSVC 离线编译 test/host/freq/freq_test.c(#include 纳入生产
|
||||
源文件 App/algo/freq.c + phasor.c + sample_ring.c),验证相位解缠绕
|
||||
测频的精度判据 J1~J6(45~55 Hz 扫频 / 噪声 / 收敛 / 阶跃 / 浮空 /
|
||||
假值钳制 / 源回退),判据数字见 freq_test.c 文件头与 App/algo/README.md。
|
||||
|
||||
用法(仓库根目录):
|
||||
& '.venv\\Scripts\\python.exe' test/host/freq/run_unit_test.py
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误。
|
||||
|
||||
设计依据:docs/算法/继电保护功能复现规划.md §3.3(L3-5)、§4.2(测频范围)。
|
||||
创建日期:2026-10-12(B8)
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||||
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
|
||||
|
||||
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
|
||||
|
||||
BUILD = TestBuild(
|
||||
name="Freq Tests",
|
||||
src=os.path.join(HERE, "freq_test.c"),
|
||||
include_dirs=[
|
||||
os.path.join(ROOT, "App", "algo"),
|
||||
HERE,
|
||||
],
|
||||
summary_marker="=== Freq Tests:",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_host_unit_test(BUILD))
|
||||
@@ -53,6 +53,7 @@ static int s_total = 0, s_passed = 0, s_failed = 0;
|
||||
#include "prot_set.c"
|
||||
#include "sample_ring.c"
|
||||
#include "phasor.c"
|
||||
#include "freq.c"
|
||||
#include "prot_elm.c"
|
||||
#include "iec104_pts.c"
|
||||
#include "iec104_link.c"
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
*
|
||||
* 被测对象:真实源文件(#include 原样纳入):
|
||||
* prot_elm.c + prot_set.c(判据吃定值 RAM)
|
||||
* phasor.c + sample_ring.c(prot_elm.c 链接依赖;本测试只走
|
||||
* prot_elm_eval 纯判据路径,不依赖 run() 适配层)
|
||||
* phasor.c + sample_ring.c + freq.c(prot_elm.c 链接依赖;
|
||||
* TC-PE-001~010 走 prot_elm_eval 纯判据路径,TC-PE-013 走
|
||||
* run() 适配层 = 合成波形跑真实 L2+测频链)
|
||||
*
|
||||
* 覆盖点(docs/算法/继电保护功能复现规划.md §3.3 L3-1/3/4/6、§8 验收):
|
||||
* TC-PE-001 init:全状态清零、动作位图 0
|
||||
@@ -19,10 +20,15 @@
|
||||
* TC-PE-008 动作位图聚合
|
||||
* TC-PE-009 守卫:state(NULL)/越界编号、name 越界
|
||||
* TC-PE-010 跨上下文复归请求(B5):request 不当拍生效、下一拍 eval 头部消费
|
||||
* TC-PE-011 过频 OF(B8/L3-5):越限 + RET_OF 滞回 + 定时限
|
||||
* TC-PE-012 低频 UF(B8/L3-5):欠量方向 + RET_UF 滞回 + 方向隔离
|
||||
* TC-PE-013 run() 接线(B8):测频驱动 OF 动作;幅值门失效 OF 复位且
|
||||
* 相量类元件仍被评估(不整拍跳过)
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 创建日期:2026-10-11
|
||||
* 创建日期:2026-10-11(B5);2026-10-12 扩 OF/UF(B8)
|
||||
*/
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -37,9 +43,12 @@ static int s_total = 0, s_passed = 0, s_failed = 0;
|
||||
#include "prot_set.c"
|
||||
#include "sample_ring.c"
|
||||
#include "phasor.c"
|
||||
#include "freq.c"
|
||||
#include "prot_elm.c"
|
||||
|
||||
/* 中性测量基线:电流 1.0 A、电压 100 V、3U0 0 V(全部不越限) */
|
||||
/* 中性测量基线:电流 1.0 A、电压 100 V、3U0 0 V、频率 50 Hz(全部不越限)。
|
||||
* OF/UF 填 50.0 = 标称:OF 定值范围下限 50.0(50>50 假)、UF 定值范围上限
|
||||
* 50.0(50<50 假)-> 两侧都不启动(定值范围约束保证,见 prot_set 表)。 */
|
||||
static void mk_base(float m[PE_COUNT]) {
|
||||
uint32_t i;
|
||||
|
||||
@@ -49,6 +58,8 @@ static void mk_base(float m[PE_COUNT]) {
|
||||
m[PE_OV] = 100.0f;
|
||||
m[PE_UV] = 100.0f;
|
||||
m[PE_3U0] = 0.0f;
|
||||
m[PE_OF] = 50.0f;
|
||||
m[PE_UF] = 50.0f;
|
||||
}
|
||||
|
||||
static void tc_init_clear(void) {
|
||||
@@ -283,6 +294,154 @@ static void tc_request_reset(void) {
|
||||
"TC-PE-010 next eval consumes request -> cleared in hysteresis band");
|
||||
}
|
||||
|
||||
/* ===================== OF/UF(L3-5,B8)===================== */
|
||||
|
||||
/* TC-PE-011:过频——越限 + RET_OF 滞回 + 定时限(判据核级) */
|
||||
static void tc_of_hysteresis(void) {
|
||||
float m[PE_COUNT];
|
||||
prot_elm_state_t st;
|
||||
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
(void)prot_set_set(PS_OF_DLY, PROT_SET_Q10(100.0));
|
||||
/* 默认 OF_HZ=51.0,RET_OF=0.99 -> 返回门槛 = 51*0.99 = 50.49 */
|
||||
|
||||
mk_base(m);
|
||||
m[PE_OF] = 50.9f;
|
||||
prot_elm_eval(1000u, m);
|
||||
prot_elm_state(PE_OF, &st);
|
||||
CHECK((st.picked == 0u) && (st.enabled != 0u),
|
||||
"TC-PE-011 OF 50.9 (<51): no pickup");
|
||||
|
||||
m[PE_OF] = 51.1f;
|
||||
prot_elm_eval(1001u, m);
|
||||
prot_elm_state(PE_OF, &st);
|
||||
CHECK((st.picked != 0u) && (st.asserted == 0u),
|
||||
"TC-PE-011 OF 51.1: pickup before delay");
|
||||
|
||||
prot_elm_eval(1100u, m);
|
||||
prot_elm_state(PE_OF, &st);
|
||||
CHECK(st.asserted == 0u, "TC-PE-011 OF not asserted at 99ms");
|
||||
|
||||
prot_elm_eval(1101u, m);
|
||||
prot_elm_state(PE_OF, &st);
|
||||
CHECK(st.asserted != 0u, "TC-PE-011 OF asserted at 100ms");
|
||||
|
||||
m[PE_OF] = 50.7f; /* 50.49 与 51 之间:滞回保持 */
|
||||
prot_elm_eval(1200u, m);
|
||||
prot_elm_state(PE_OF, &st);
|
||||
CHECK((st.picked != 0u) && (st.asserted != 0u),
|
||||
"TC-PE-011 OF holds between return(50.49) and pickup(51)");
|
||||
|
||||
m[PE_OF] = 50.4f; /* 跌破返回门槛 */
|
||||
prot_elm_eval(1300u, m);
|
||||
prot_elm_state(PE_OF, &st);
|
||||
CHECK((st.picked == 0u) && (st.asserted == 0u),
|
||||
"TC-PE-011 OF below return threshold: drops out");
|
||||
}
|
||||
|
||||
/* TC-PE-012:低频——欠量方向 + RET_UF 滞回(与 OF 系数独立,勿混 0.95/1.05) */
|
||||
static void tc_uf_direction(void) {
|
||||
float m[PE_COUNT];
|
||||
prot_elm_state_t st;
|
||||
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
(void)prot_set_set(PS_UF_DLY, PROT_SET_Q10(50.0));
|
||||
/* 默认 UF_HZ=49.0,RET_UF=1.01 -> 返回门槛 = 49*1.01 = 49.49 */
|
||||
|
||||
mk_base(m);
|
||||
m[PE_UF] = 49.1f;
|
||||
prot_elm_eval(2000u, m);
|
||||
prot_elm_state(PE_UF, &st);
|
||||
CHECK(st.picked == 0u, "TC-PE-012 UF 49.1 (>49): no pickup");
|
||||
|
||||
m[PE_UF] = 48.9f;
|
||||
prot_elm_eval(2001u, m);
|
||||
prot_elm_state(PE_UF, &st);
|
||||
CHECK(st.picked != 0u, "TC-PE-012 UF 48.9 (<49): pickup");
|
||||
|
||||
prot_elm_eval(2051u, m);
|
||||
prot_elm_state(PE_UF, &st);
|
||||
CHECK(st.asserted != 0u, "TC-PE-012 UF asserted after 50ms");
|
||||
|
||||
m[PE_UF] = 49.3f; /* 49 与 49.49 之间:保持 */
|
||||
prot_elm_eval(2100u, m);
|
||||
prot_elm_state(PE_UF, &st);
|
||||
CHECK(st.asserted != 0u, "TC-PE-012 UF holds below return threshold");
|
||||
|
||||
m[PE_UF] = 49.6f; /* 高于返回门槛:释放 */
|
||||
prot_elm_eval(2200u, m);
|
||||
prot_elm_state(PE_UF, &st);
|
||||
CHECK((st.picked == 0u) && (st.asserted == 0u),
|
||||
"TC-PE-012 UF above return threshold: drops out");
|
||||
|
||||
/* OF 不得被低频误启动(方向隔离);UF 不得被过频误启动 */
|
||||
m[PE_OF] = 52.0f;
|
||||
m[PE_UF] = 52.0f;
|
||||
prot_elm_eval(2300u, m);
|
||||
prot_elm_state(PE_OF, &st);
|
||||
CHECK(st.picked != 0u, "TC-PE-012 OF picks at 52 (over direction)");
|
||||
prot_elm_state(PE_UF, &st);
|
||||
CHECK(st.picked == 0u, "TC-PE-012 UF NOT picked at 52 (direction isolation)");
|
||||
}
|
||||
|
||||
/* ---- run() 适配层驱动:合成波形跑真实 L2+测频链(同 freq_test 口径) ---- */
|
||||
static uint32_t s_t = 0u;
|
||||
|
||||
static void drive_beat(double f, double amp) {
|
||||
float frame[ADC_SEM_COUNT];
|
||||
double v = (f == 0.0) ? 0.0 : amp * sin(2.0 * 3.14159265358979 * f * (double)s_t / 1000.0);
|
||||
uint8_t c;
|
||||
|
||||
for (c = 0u; c < (uint8_t)ADC_SEM_COUNT; c++) {
|
||||
frame[c] = (float)v;
|
||||
}
|
||||
(void)sample_ring_push_frame(frame, (uint8_t)ADC_SEM_COUNT);
|
||||
phasor_run();
|
||||
freq_run();
|
||||
s_t++;
|
||||
}
|
||||
|
||||
/* TC-PE-013:run() 级——freq 有效读数驱动 OF 动作;幅值门失效后 OF 复位
|
||||
* 且相量类元件仍被评估(不整拍跳过,B5 行为不回归) */
|
||||
static void tc_run_freq_wiring(void) {
|
||||
prot_elm_state_t st;
|
||||
uint32_t i;
|
||||
|
||||
s_t = 0u;
|
||||
(void)phasor_init();
|
||||
(void)freq_init();
|
||||
prot_set_init();
|
||||
prot_elm_init();
|
||||
(void)prot_set_set(PS_OF_DLY, PROT_SET_Q10(0.0)); /* 启动同拍动作 */
|
||||
(void)prot_set_set(PS_UV_DLY, PROT_SET_Q10(0.0)); /* 观察相量类仍评估 */
|
||||
|
||||
/* 52.5 Hz(> OF 51)驱动到测频收敛(20 拍启动 + 窗)+ 判据拍 */
|
||||
for (i = 0u; i < 140u; i++) {
|
||||
drive_beat(52.5, 1000.0);
|
||||
prot_elm_run(i);
|
||||
}
|
||||
prot_elm_state(PE_OF, &st);
|
||||
CHECK((st.picked != 0u) && (st.asserted != 0u),
|
||||
"TC-PE-013 run(): OF asserted from measured 52.5 Hz");
|
||||
prot_elm_state(PE_UF, &st);
|
||||
CHECK(st.picked == 0u, "TC-PE-013 run(): UF clear at 52.5 Hz");
|
||||
|
||||
/* 波形消失(浮空):freq 幅值门失效 -> OF 按方向填充复位;
|
||||
* 同时相量 rms=0 -> UV(定值 20,dly=0)启动 = 证明未整拍跳过 */
|
||||
for (i = 0u; i < 60u; i++) {
|
||||
drive_beat(0.0, 0.0);
|
||||
prot_elm_run(1000u + i);
|
||||
}
|
||||
prot_elm_state(PE_OF, &st);
|
||||
CHECK((st.picked == 0u) && (st.asserted == 0u),
|
||||
"TC-PE-013 run(): OF resets when freq source loses amplitude");
|
||||
prot_elm_state(PE_UV, &st);
|
||||
CHECK(st.picked != 0u,
|
||||
"TC-PE-013 run(): UV still evaluated with zero-amplitude (no whole-beat skip)");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== Prot Elm Tests ===\n");
|
||||
tc_init_clear();
|
||||
@@ -295,6 +454,9 @@ int main(void) {
|
||||
tc_active_mask();
|
||||
tc_guards();
|
||||
tc_request_reset();
|
||||
tc_of_hysteresis();
|
||||
tc_uf_direction();
|
||||
tc_run_freq_wiring();
|
||||
|
||||
printf("\n=== Prot Elm Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
|
||||
@@ -20,9 +20,12 @@
|
||||
* (位精确 + 封存自洽 + parse 不置脏)
|
||||
* TC-PS-010 损坏 blob 整表拒绝(all-or-nothing):值区位翻转/坏 magic/
|
||||
* 截断/超长/坏版本/伪造自洽越界 → 全部拒绝且活跃值区一字不动
|
||||
* TC-PS-011 OF/UF 定值行(B8):40 项 / blob 版本 2 / 本地项地址 0 /
|
||||
* 默认值与范围 / 返回系数独立性 / 64 位 bad_mask(bit 39)/
|
||||
* B7 旧档(32 项)换代拒绝
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 创建日期:2026-10-11
|
||||
* 创建日期:2026-10-11;2026-10-12 扩 OF/UF(B8)
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -94,7 +97,7 @@ static void tc_load_raw(void) {
|
||||
tbl[PS_OC3_DLY] = PROT_SET_Q10(60001.0); /* 越上限 */
|
||||
tbl[PS_OL_CUR] = PROT_SET_Q10(2.5); /* 合法改动 */
|
||||
CHECK(prot_set_load_raw(tbl) == -1, "TC-PS-004 bad table rejected");
|
||||
CHECK(prot_set_bad_mask() == (1uL << PS_OC3_DLY),
|
||||
CHECK(prot_set_bad_mask() == (1uLL << PS_OC3_DLY),
|
||||
"TC-PS-004 bad_mask points at the offending item");
|
||||
CHECK(prot_set_get_f(PS_OL_CUR) > 1.49f && prot_set_get_f(PS_OL_CUR) < 1.51f,
|
||||
"TC-PS-004 rejected load keeps OLD table (all-or-nothing)");
|
||||
@@ -136,7 +139,7 @@ static void tc_meta_sanity(void) {
|
||||
CHECK(ok_range, "TC-PS-006 meta: min<=def<=max for all items");
|
||||
CHECK(ok_ctl, "TC-PS-006 meta: ctl items are 0..1");
|
||||
CHECK(ok_addr, "TC-PS-006 meta: comm addresses unique");
|
||||
CHECK((uint32_t)PS_ID_COUNT <= 32u, "TC-PS-006 item count fits bad_mask bitmap");
|
||||
CHECK((uint32_t)PS_ID_COUNT <= 64u, "TC-PS-006 item count fits 64-bit bad_mask (B8: 40)");
|
||||
}
|
||||
|
||||
/* TC-PS-007 */
|
||||
@@ -235,11 +238,89 @@ static void tc_store_corrupt(void) {
|
||||
(uint32_t)(g_prot_set_meta[PS_OC1_CUR].max + 1));
|
||||
s_forge_tail(blob, len);
|
||||
CHECK(prot_set_store_parse(blob, len) == -1, "TC-PS-010 forged consistent out-of-range rejected");
|
||||
CHECK(prot_set_bad_mask() == (1uL << PS_OC1_CUR), "TC-PS-010 bad_mask points at item");
|
||||
CHECK(prot_set_bad_mask() == (1uLL << PS_OC1_CUR), "TC-PS-010 bad_mask points at item");
|
||||
CHECK(prot_set_get(PS_OC1_CUR) == PROT_SET_Q10(10.0), "TC-PS-010 defaults still intact");
|
||||
CHECK(prot_set_selfcheck() == 0, "TC-PS-010 active table still self-consistent after all rejects");
|
||||
}
|
||||
|
||||
/* TC-PS-011(B8:OF/UF 定值行 + 40 项 + 64 位 bad_mask + 换代拒绝) */
|
||||
static void tc_of_uf_meta(void) {
|
||||
uint8_t blob[PROT_SET_BLOB_SZ];
|
||||
uint32_t len = 0u;
|
||||
|
||||
prot_set_init();
|
||||
|
||||
/* 结构:40 项、blob 版本 2、尺寸随项数自适应 */
|
||||
CHECK((uint32_t)PS_ID_COUNT == 40u, "TC-PS-011 PS_ID_COUNT == 40 (32 + 8 OF/UF)");
|
||||
CHECK(PROT_SET_BLOB_VER == 2u, "TC-PS-011 blob version bumped to 2 (B8)");
|
||||
CHECK((uint32_t)PROT_SET_BLOB_SZ == (PROT_SET_BLOB_HDR_SZ + 4u * 40u + 8u),
|
||||
"TC-PS-011 blob size = hdr + 4*40 + tail");
|
||||
|
||||
/* 地址:参考逐项地址不在本仓库 -> 8 项全部本地项(comm_addr=0,104 不可寻址) */
|
||||
CHECK((g_prot_set_meta[PS_OF_EN].comm_addr == 0u)
|
||||
&& (g_prot_set_meta[PS_OF_HZ].comm_addr == 0u)
|
||||
&& (g_prot_set_meta[PS_OF_DLY].comm_addr == 0u)
|
||||
&& (g_prot_set_meta[PS_UF_EN].comm_addr == 0u)
|
||||
&& (g_prot_set_meta[PS_UF_HZ].comm_addr == 0u)
|
||||
&& (g_prot_set_meta[PS_UF_DLY].comm_addr == 0u)
|
||||
&& (g_prot_set_meta[PS_RET_OF].comm_addr == 0u)
|
||||
&& (g_prot_set_meta[PS_RET_UF].comm_addr == 0u),
|
||||
"TC-PS-011 OF/UF items all local (comm_addr=0, pending reference table)");
|
||||
|
||||
/* 投退 = ctl 项(0/1),其余 = 模拟量 Q10 */
|
||||
CHECK((g_prot_set_meta[PS_OF_EN].is_ctl == 1u) && (g_prot_set_meta[PS_UF_EN].is_ctl == 1u)
|
||||
&& (g_prot_set_meta[PS_OF_HZ].is_ctl == 0u) && (g_prot_set_meta[PS_RET_UF].is_ctl == 0u),
|
||||
"TC-PS-011 EN items are ctl, HZ/DLY/RET are Q10 analog");
|
||||
|
||||
/* 出厂默认(拟定值)与范围 */
|
||||
CHECK(prot_set_get(PS_OF_EN) == PROT_SET_Q10(1.0), "TC-PS-011 OF EN default 1");
|
||||
CHECK(prot_set_get(PS_UF_EN) == PROT_SET_Q10(1.0), "TC-PS-011 UF EN default 1");
|
||||
CHECK(prot_set_get(PS_OF_HZ) == PROT_SET_Q10(51.0), "TC-PS-011 OF default 51.0 Hz");
|
||||
CHECK(prot_set_get(PS_UF_HZ) == PROT_SET_Q10(49.0), "TC-PS-011 UF default 49.0 Hz");
|
||||
CHECK(prot_set_get(PS_OF_DLY) == PROT_SET_Q10(1000.0), "TC-PS-011 OF default 1000 ms");
|
||||
CHECK(prot_set_get(PS_UF_DLY) == PROT_SET_Q10(1000.0), "TC-PS-011 UF default 1000 ms");
|
||||
CHECK(prot_set_get(PS_RET_OF) == PROT_SET_Q10(0.99), "TC-PS-011 RET_OF default 0.99");
|
||||
CHECK(prot_set_get(PS_RET_UF) == PROT_SET_Q10(1.01), "TC-PS-011 RET_UF default 1.01");
|
||||
CHECK((g_prot_set_meta[PS_OF_HZ].min == PROT_SET_Q10(50.0))
|
||||
&& (g_prot_set_meta[PS_OF_HZ].max == PROT_SET_Q10(55.0)),
|
||||
"TC-PS-011 OF range 50.0~55.0 Hz");
|
||||
CHECK((g_prot_set_meta[PS_UF_HZ].min == PROT_SET_Q10(45.0))
|
||||
&& (g_prot_set_meta[PS_UF_HZ].max == PROT_SET_Q10(50.0)),
|
||||
"TC-PS-011 UF range 45.0~50.0 Hz");
|
||||
|
||||
/* 频率返回系数与电气量系数独立:返回**门槛** = 定值×系数(Q10 乘积
|
||||
* /1024)——OF 门槛 < 动作门槛、UF 门槛 > 动作门槛(方向相反) */
|
||||
CHECK(((int64_t)prot_set_get(PS_OF_HZ) * (int64_t)prot_set_get(PS_RET_OF)) / 1024
|
||||
< (int64_t)prot_set_get(PS_OF_HZ),
|
||||
"TC-PS-011 OF return threshold (set x 0.99) below pickup");
|
||||
CHECK(((int64_t)prot_set_get(PS_UF_HZ) * (int64_t)prot_set_get(PS_RET_UF)) / 1024
|
||||
> (int64_t)prot_set_get(PS_UF_HZ),
|
||||
"TC-PS-011 UF return threshold (set x 1.01) above pickup");
|
||||
|
||||
/* 投退可经 set_sealed 投/退(YK 口径同 GLOBAL_EN;Q10(2)=2048 越界拒) */
|
||||
CHECK(prot_set_set_sealed(PS_OF_EN, 0) == 0, "TC-PS-011 OF EN set_sealed OK");
|
||||
CHECK(prot_set_get_ctl(PS_OF_EN) == 0u, "TC-PS-011 OF EN readable after seal");
|
||||
CHECK(prot_set_set_sealed(PS_OF_EN, PROT_SET_Q10(2)) == -1, "TC-PS-011 OF EN rejects Q10(2)");
|
||||
CHECK(prot_set_selfcheck() == 0, "TC-PS-011 no lock after OF/UF seal ops");
|
||||
|
||||
/* 64 位 bad_mask:末项(bit 39 > 31)越界可指认 */
|
||||
(void)prot_set_set_sealed(PS_OC1_CUR, PROT_SET_Q10(11.0));
|
||||
(void)prot_set_dirty_take();
|
||||
CHECK(prot_set_store_serialize(blob, sizeof(blob), &len) == 0, "TC-PS-011 serialize ok");
|
||||
s_put32(blob + PROT_SET_BLOB_HDR_SZ + 4u * (uint32_t)PS_RET_UF,
|
||||
(uint32_t)(g_prot_set_meta[PS_RET_UF].max + 1));
|
||||
s_forge_tail(blob, len);
|
||||
CHECK(prot_set_store_parse(blob, len) == -1, "TC-PS-011 out-of-range RET_UF rejected");
|
||||
CHECK(prot_set_bad_mask() == (1uLL << PS_RET_UF),
|
||||
"TC-PS-011 bad_mask bit 39 addressable (64-bit mask, B8)");
|
||||
CHECK(prot_set_get(PS_RET_UF) == PROT_SET_Q10(1.01), "TC-PS-011 default RET_UF intact");
|
||||
|
||||
/* 换代语义:B7 旧档(ver=1 / 32 项 = 144 B)在新固件必被拒 -> 出厂默认 */
|
||||
CHECK(prot_set_store_parse(blob, 8u + 4u * 32u + 8u) == -1,
|
||||
"TC-PS-011 legacy 32-item blob length rejected");
|
||||
CHECK(prot_set_selfcheck() == 0, "TC-PS-011 active table intact after legacy reject");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== Prot Set Tests ===\n");
|
||||
tc_init();
|
||||
@@ -252,6 +333,7 @@ int main(void) {
|
||||
tc_global_plate_dirty();
|
||||
tc_store_roundtrip();
|
||||
tc_store_corrupt();
|
||||
tc_of_uf_meta();
|
||||
|
||||
printf("\n=== Prot Set Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
|
||||
Reference in New Issue
Block a user