/* * 模块名称:遥测快照发布层(全周傅氏相量派生·发布侧)主机侧单元测试 * 被测对象:真实源文件(#include 原样纳入,测生产代码而非副本): * App/algo/meas.c + App/algo/phasor.c + App/algo/sample_ring.c * 三源均为纯算法(无寄存器/RTOS/文件依赖)→ **无需任何桩**。 * * 为什么要主机侧(而不是只等台架):遥测快照发布的判据一半是"并发与结构"类—— * 双区切换无撕裂、seq 只增、布局与 §4.5 一致、品质汇总正确——台架上要么不可 * 观测(撕裂窗 200 ms 量级,人工比对读不到)、要么无法注入(削顶位图由两点标定 * 完成后才真实出现);主机侧可合成任意幅值/相位的 9 通道帧序列把它们钉死。 * 读数与注入源比对类判据 = 待台架实测(本套件不覆盖,见「数据可信」遥测快照发布)。 * * 覆盖点(TC-MEAS-xxx;属主登记处 = 本目录,族 TC-HOST-MEAS): * TC-MEAS-001 init 语义:seq=0 / get=-2 且清零填充 / 发布后可见 / init 幂等复位 * TC-MEAS-002 布局钉死(docs/任务规划/数据可信.md §4.5):sizeof=72 + 八字段偏移 * TC-MEAS-003 窗不足发布:seq=1 照常发布,电气 8 路 + TEMP 全 IV|NT,valid=0 * TC-MEAS-004 基波有效值透传:50 Hz 整周波窗 yc = A/√2(≤0.1 %),零幅路=0 且品质 0 * TC-MEAS-005 同拍同值(§4.5"从相量使用区取"口径钉死):yc[UA/UN/IA] 与 * phasor_get(基波实例).fAmSQ 逐位相等 * TC-MEAS-006 品质汇总(遥测快照发布 DoD 原句"任一点 CLIPPED → 该点 qds 置位"): * mask=0xE6 → UB/UC/IB/IC/IN 置 CLIPPED 且值不清零;UA 不置; * mask 位 8 对 TEMP 无效(TEMP 恒 NT) * TC-MEAS-007 seq 递增 + 最新帧:50 连发,get 恒为最新(seq/tick_ms/幅值三元组 * 互洽 → 双区切换无撕裂、无陈旧读) * TC-MEAS-008 频率注入透传:fv/f 原样进快照;fv=0 时 f 仍保持(读数保持语义在 * freq 层,快照只搬运) * TC-MEAS-009 TEMP 透传最新一点:yc[TEMP] = 最近一拍原始帧值(非均值非滞后) * TC-MEAS-010 采样环回绕连续:60+60 拍穿环深 40 后读数恒定(≤0.1 %) * TC-MEAS-011 get 参数守卫:NULL → -1 * * 通过:全部 TC PASS(main 返回 0)。 * 创建日期:2026-10-25(道D 遥测快照发布) */ #include #include #include #include #define PI 3.14159265358979323846 #define W50 (2.0 * PI * 50.0 / 1000.0) /* 50 Hz @ 1 kHz:0.1π/拍(20 点/周波) */ #define SQRT2 1.41421356237309505 /* ===================== 断言 ===================== */ 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 "meas.c" /* ===================== 工具 ===================== */ static double s_ph = 0.0; /* 全局正弦相位(拍推进) */ /* 各电气路取 sin(ph + s*0.7) 错相,避免 9 路同值互相掩盖;TEMP = 拍序号 */ static void push_beats(int n, double amp) { float f[ADC_SEM_COUNT]; int i; int s; for (i = 0; i < n; i++) { for (s = 0; s < (int)ADC_SEM_TEMP; s++) { f[s] = (float)(amp * sin(s_ph + (double)s * 0.7)); } f[ADC_SEM_TEMP] = (float)(1000.0 + s_ph * 100.0); s_ph += W50; (void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT); } } /* 每开机一段用例的干净起点:环 + 相量(内含环复位)+ 快照全复位 */ static void t_reset(void) { (void)phasor_init(); (void)meas_init(); s_ph = 0.0; } static int rms_close(float got, double want) { double tol = (want > 1.0 ? want : 1.0) * 0.001; /* 0.1 % 容差 */ return (fabs((double)got - want) <= tol) ? 1 : 0; } static int all_iv_nt(const meas_snap_t *s, int upto) { int i; for (i = 0; i < upto; i++) { if (s->qds[i] != (uint16_t)(MEAS_QDS_IV | MEAS_QDS_NT)) { return 0; } } return 1; } /* ===================== TC ===================== */ static void tc_001_init(void) { meas_snap_t s; t_reset(); CHECK(meas_seq() == 0u, "001a init: seq=0"); CHECK(meas_get(&s) == -2, "001b init: get=-2"); CHECK(s.seq == 0u && s.tick_ms == 0u && s.valid == 0u && s.freq_valid == 0u && s.freq_hz == 0.0f, "001c init: -2 时 out 清零填充"); { int i; int z = 1; for (i = 0; i < (int)ADC_SEM_COUNT; i++) { if ((s.yc[i] != 0.0f) || (s.qds[i] != 0u)) { z = 0; } } CHECK(z == 1, "001d init: yc/qds 全清零"); } push_beats(20, 100.0); meas_tick(200u, 0u, 0.0f, 0u); CHECK(meas_seq() == 1u && meas_get(&s) == 0, "001e 发布后可读"); (void)meas_init(); CHECK(meas_seq() == 0u && meas_get(&s) == -2 && s.seq == 0u, "001f init 幂等:复位序号/就绪/双区"); } static void tc_002_layout(void) { CHECK(sizeof(meas_snap_t) == 72u, "002a sizeof=72B(双区 2x72=144B 卡面预算)"); CHECK(offsetof(meas_snap_t, seq) == 0u, "002b off seq=0"); CHECK(offsetof(meas_snap_t, tick_ms) == 4u, "002c off tick_ms=4"); CHECK(offsetof(meas_snap_t, valid) == 8u, "002d off valid=8"); CHECK(offsetof(meas_snap_t, rsv) == 9u, "002e off rsv=9"); CHECK(offsetof(meas_snap_t, yc) == 12u, "002f off yc=12"); CHECK(offsetof(meas_snap_t, qds) == 48u, "002g off qds=48"); CHECK(offsetof(meas_snap_t, freq_valid) == 66u, "002h off freq_valid=66"); CHECK(offsetof(meas_snap_t, freq_hz) == 68u, "002i off freq_hz=68"); } static void tc_003_warmup(void) { meas_snap_t s; t_reset(); meas_tick(200u, 1u, 49.9f, 0xE6u); CHECK(meas_get(&s) == 0, "003a 窗不足照常发布(品质节拍性刷新)"); CHECK(s.seq == 1u && s.tick_ms == 200u, "003b seq/tick_ms 落戳"); CHECK(s.valid == 0u, "003c 整帧 valid=0"); CHECK(all_iv_nt(&s, (int)ADC_SEM_COUNT), "003d 电气 8 + TEMP 全 IV|NT"); { int i; int z = 1; for (i = 0; i < (int)ADC_SEM_COUNT; i++) { if (s.yc[i] != 0.0f) { z = 0; } } CHECK(z == 1, "003e IV 路值位保持 0(不产伪值)"); } CHECK(s.freq_valid == 1u && s.freq_hz == 49.9f, "003f 频率注入与窗不足解耦(新鲜度独立)"); } static void tc_004_rms(void) { meas_snap_t s; int i; t_reset(); push_beats(19, 100.0); meas_tick(190u, 0u, 0.0f, 0u); CHECK(meas_get(&s) == 0 && s.valid == 0u, "004a 19 拍窗不足:不产出值"); push_beats(1, 100.0); meas_tick(200u, 0u, 0.0f, 0u); CHECK(meas_get(&s) == 0 && s.valid == 1u, "004b 满 20 拍:整帧 valid=1"); for (i = 0; i < (int)ADC_SEM_TEMP; i++) { CHECK(rms_close(s.yc[i], 100.0 / SQRT2), "004c 各路 yc = A/sqrt2(有效值口径)"); CHECK(s.qds[i] == 0u, "004d 正常路品质 0"); } CHECK(s.qds[ADC_SEM_TEMP] == (uint16_t)MEAS_QDS_NT, "004e TEMP = NT(非交流量语义)"); } static void tc_005_use_zone_equiv(void) { meas_snap_t s; phasor_t p; uint8_t k; t_reset(); push_beats(20, 80.0); phasor_run(); meas_tick(400u, 0u, 0.0f, 0u); CHECK(meas_get(&s) == 0, "005a 快照可读"); for (k = 0u; k < phasor_inst_count(); k++) { const phasor_inst_t *d = phasor_inst(k); if ((d != NULL) && (d->harm == 1u) && (phasor_get(k, &p) == 0)) { CHECK(s.yc[d->sem] == p.fAmSQ, "005b yc[基波路] 与使用区 fAmSQ 逐位相等(同拍同核)"); } } } static void tc_006_clip_quality(void) { meas_snap_t s; uint8_t clip[5] = { ADC_SEM_UB, ADC_SEM_UC, ADC_SEM_IB, ADC_SEM_IC, ADC_SEM_IN }; int i; t_reset(); push_beats(20, 60.0); meas_tick(200u, 0u, 0.0f, 0xE6u); /* 0xE6 = bit1/2/5/6/7(同 cal: 行 cl 位图) */ CHECK(meas_get(&s) == 0, "006a 可读"); for (i = 0; i < 5; i++) { CHECK((s.qds[clip[i]] & (uint16_t)MEAS_QDS_CLIPPED) != 0u, "006b 削顶路 qds 置 CLIPPED"); CHECK(s.yc[clip[i]] > 30.0f, "006c 削顶路值不清零(监视语义保留)"); CHECK(s.qds[clip[i]] == (uint16_t)MEAS_QDS_CLIPPED, "006d 削顶路仅 CLIPPED(未叠 IV/NT)"); } CHECK(s.qds[ADC_SEM_UA] == 0u, "006e UA 不受位图外影响"); CHECK(s.qds[ADC_SEM_UN] == 0u && s.qds[ADC_SEM_IA] == 0u, "006f UN/IA 干净"); CHECK(s.valid == 1u, "006g CLIPPED 不拉低整帧 valid(品质在点级)"); meas_tick(400u, 0u, 0.0f, 0x1E6u); /* bit8(TEMP)应被忽略 */ CHECK(meas_get(&s) == 0 && s.qds[ADC_SEM_TEMP] == (uint16_t)MEAS_QDS_NT, "006h TEMP 品质语义独立:mask 位 8 无效"); } static void tc_007_seq_latest(void) { meas_snap_t s; int n; t_reset(); push_beats(20, 50.0); /* 先攒窗,之后每轮再推 20 拍换幅值 */ for (n = 1; n <= 50; n++) { push_beats(20, (double)n * 10.0); meas_tick((uint32_t)n * 1000u, 0u, 0.0f, 0u); CHECK(meas_get(&s) == 0 && s.seq == (uint32_t)n, "007a seq 逐拍 +1 无丢帧"); CHECK(s.tick_ms == (uint32_t)n * 1000u, "007b tick_ms 与 seq 同戳(读到的帧自洽)"); CHECK(rms_close(s.yc[ADC_SEM_UA], (double)n * 10.0 / SQRT2), "007c 幅值与 seq 匹配(无陈旧区误读)"); } CHECK(meas_seq() == 50u, "007d meas_seq 与 get 一致收敛"); } static void tc_008_freq(void) { meas_snap_t s; t_reset(); push_beats(20, 100.0); meas_tick(300u, 1u, 50.125f, 0u); CHECK(meas_get(&s) == 0 && s.freq_valid == 1u && s.freq_hz == 50.125f, "008a 有效频率透传"); meas_tick(500u, 0u, 49.875f, 0u); CHECK(meas_get(&s) == 0 && s.freq_valid == 0u && s.freq_hz == 49.875f, "008b fv=0 时读数仍搬运(保持语义在 freq 层)"); } static void tc_009_temp_passthrough(void) { meas_snap_t s; t_reset(); push_beats(20, 100.0); meas_tick(200u, 0u, 0.0f, 0u); CHECK(meas_get(&s) == 0, "009a 可读"); { float t1 = s.yc[ADC_SEM_TEMP]; CHECK(t1 > 0.0f && s.qds[ADC_SEM_TEMP] == (uint16_t)MEAS_QDS_NT, "009b TEMP 值在场 + NT"); push_beats(21, 100.0); meas_tick(400u, 0u, 0.0f, 0u); CHECK(meas_get(&s) == 0 && s.yc[ADC_SEM_TEMP] != t1, "009c TEMP = 最新一点(非均值、非首点)"); CHECK(rms_close(s.yc[ADC_SEM_UA], 100.0 / SQRT2), "009d TEMP 换值不影响电气路"); } } static void tc_010_ring_wrap(void) { meas_snap_t s1; meas_snap_t s2; t_reset(); push_beats(60, 100.0); /* 穿环深 40 一次 */ meas_tick(600u, 0u, 0.0f, 0u); CHECK(meas_get(&s1) == 0 && s1.valid == 1u, "010a 首次回绕后发布正常"); push_beats(60, 100.0); /* 再穿一次 */ meas_tick(1200u, 0u, 0.0f, 0u); CHECK(meas_get(&s2) == 0 && s2.valid == 1u, "010b 二次回绕后发布正常"); CHECK(rms_close(s1.yc[ADC_SEM_UA], 100.0 / SQRT2) && rms_close(s2.yc[ADC_SEM_UA], 100.0 / SQRT2), "010c 回绕前后读数恒定(≤0.1 %)"); CHECK(s2.seq == s1.seq + 1u, "010d 回绕不影响 seq"); } static void tc_011_get_guard(void) { t_reset(); CHECK(meas_get(NULL) == -1, "011a NULL -> -1"); } int main(void) { printf("=== Meas Layer Tests ===\n"); tc_001_init(); tc_002_layout(); tc_003_warmup(); tc_004_rms(); tc_005_use_zone_equiv(); tc_006_clip_quality(); tc_007_seq_latest(); tc_008_freq(); tc_009_temp_passthrough(); tc_010_ring_wrap(); tc_011_get_guard(); printf("\n=== Meas Layer Tests: %d/%d PASSED (failed=%d) ===\n", s_passed, s_total, s_failed); return (s_failed == 0) ? 0 : 1; }