/* * 模块名称:相量层(L2)主机侧单元测试 * 模块功能:在 PC 上对 App/algo/phasor.c + App/algo/sample_ring.c 做离线验证。 * * 被测对象:真实源文件(以 #include 原样纳入,保证测的是生产代码而非副本): * ../../../App/algo/phasor.c * ../../../App/algo/sample_ring.c * 本层为**纯算法**(无寄存器、无 RTOS、无文件/外设依赖),故**无需任何桩**。 * * 为什么要主机侧单测(而不是只用板端套件): * 1. 相量算法是纯数学变换,与硬件无关;板端**无信号源**,无法注入已知幅值/相位/ * 频率的标准波形,因而无法验证"读数 = 设定值"这一核心判据(同 STM32 ADC1 启动与扫描 的替代口径)。 * 主机侧可合成任意波形,把幅值精度/谐波抑制/频率敏感性一次性钉死。 * 2. 可精确构造边界:恰好一周波 / 不足一周波 / 环形回绕 / 任意次谐波 / 频偏。 * 3. 不占 MCU 的 RAM,毫秒级完成。 * * 覆盖点(对应 docs/算法/继电保护功能复现规划.md §3.2): * TC-PHASOR-001 全周傅氏系数表逐项等于 sin(2πk/N)·(√2/N) / cos(2πk/N)·(√2/N)(防转录错) * TC-PHASOR-002 全周傅氏系数表精确零点(sin@0,10 / cos@5,15)与归一化 Σcoe² = 1/N * TC-PHASOR-003 相量有效值输出 纯基波:fAmSQ = A/√2(有效值),fAm = √2·fAmSQ(幅值) * TC-PHASOR-004 相量有效值输出 / 相位输出 实部虚部与相位:atan2(fIm,fRe) = 注入相角 * TC-PHASOR-005 直流 + 2/3/5 次谐波被完全滤除(基波读数不变) * TC-PHASOR-006 任意次谐波提取(索引步进 = 谐波次数):k=2 / k=5 * TC-PHASOR-007 频率偏离 50 Hz 的幅值误差(±1 Hz ≤ 1.5%,±2 Hz ≤ 3%,见 §9 Q3) * TC-PHASOR-010 采样环:入环/取窗时序、回绕、饱和、帧计数、参数守卫 * TC-PHASOR-020 基波相量实例层:实例数/实例表/未攒满一周波不产出 * TC-PHASOR-021 每 1 ms 滑动窗刷新:连续滑动 20 拍基波读数恒定 * TC-PHASOR-022 双区指针原子交换:使用区交替 + get 返回**最后一次** run 的结果(非陈旧) * TC-PHASOR-030..034 phasor_zero 四字段清零 / phasor_init 双区清零 + 使用区与就绪标志复位 + 幂等 * * 通过:全部 TC PASS(main 返回 0)。 * 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。 * * 创建日期:2026-10-10 */ #include #include #include #define PH_PI 3.14159265358979323846 /* 采样率(Hz):1000 Hz @ 50 Hz → 20 点/周波 */ #define PH_FS 1000.0 #define PH_F0 50.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" /* ===================== 工具 ===================== */ /* * 函数功能:生成一段正弦窗 * 入口参数:x - 输出(长度 >= n) * n - 点数 * amp - 正弦峰值 * f - 频率(Hz) * phase - 初相(度) * 返 回 值:无 * 限定条件:无 * 函数说明:以 t = i/PH_FS 采样,即"以当前时刻为终止的前 n 点窗"。 */ static void gen_sine(float *x, uint8_t n, double amp, double f, double phase) { uint8_t i; for (i = 0u; i < n; i++) { x[i] = (float)(amp * sin(2.0 * PH_PI * f * (double)i / PH_FS + phase * PH_PI / 180.0)); } } /* * 函数功能:按时刻 t 构造一整帧(9 路语义通道同值) * 入口参数:f - 输出帧(长度 = ADC_SEM_COUNT) * t - 采样序号(t/PH_FS 秒) * amp - 正弦峰值 * phase - 初相(度) * 返 回 值:无 * 限定条件:无 * 函数说明:仅用于驱动实例层/采样环;**不代表真实硬件**(V0.1 上各通道不同刻, * 此处只为验证相量层本身)。 */ static void frame_at(float *f, uint32_t t, double amp, double phase) { uint8_t c; double v = amp * sin(2.0 * PH_PI * PH_F0 * (double)t / PH_FS + phase * PH_PI / 180.0); for (c = 0u; c < (uint8_t)ADC_SEM_COUNT; c++) { f[c] = (float)v; } } /* ===================== TC-PHASOR-001/002:全周傅氏系数表 ===================== */ static void tc_table(void) { const double scale = sqrt(2.0) / (double)PHASOR_N; double sum_sin = 0.0, sum_cos = 0.0; int ok_sin = 1, ok_cos = 1, ok_norm = 1; uint8_t k; for (k = 0u; k < (uint8_t)PHASOR_N; k++) { double arg = 2.0 * PH_PI * (double)k / (double)PHASOR_N; double es = sin(arg) * scale; double ec = cos(arg) * scale; if (fabs((double)k_fft_sin[k] - es) > 1e-7) { ok_sin = 0; } if (fabs((double)k_fft_cos[k] - ec) > 1e-7) { ok_cos = 0; } /* 归一化:sin²+cos² = (√2/N)² = 2/N² */ if (fabs((double)k_fft_sin[k] * (double)k_fft_sin[k] + (double)k_fft_cos[k] * (double)k_fft_cos[k] - 2.0 / (PHASOR_N * PHASOR_N)) > 1e-9) { ok_norm = 0; } sum_sin += (double)k_fft_sin[k] * (double)k_fft_sin[k]; sum_cos += (double)k_fft_cos[k] * (double)k_fft_cos[k]; } CHECK(ok_sin, "全周傅氏系数表 coeff sin[k] == sin(2*pi*k/N)*(sqrt2/N) for all k"); CHECK(ok_cos, "全周傅氏系数表 coeff cos[k] == cos(2*pi*k/N)*(sqrt2/N) for all k"); CHECK(ok_norm, "全周傅氏系数表 per-k sin^2+cos^2 == 2/N^2 (normalization)"); /* Σsin² = Σcos² = (√2/N)²·(N/2) = 1/N:这就是"fAmSQ 直接是有效值"的根 */ CHECK(fabs(sum_sin - 1.0 / PHASOR_N) < 1e-7, "全周傅氏系数表 sum(sin^2) == 1/N (=> RMS, not amplitude)"); CHECK(fabs(sum_cos - 1.0 / PHASOR_N) < 1e-7, "全周傅氏系数表 sum(cos^2) == 1/N (=> RMS, not amplitude)"); CHECK((k_fft_sin[0] == 0.0f) && (k_fft_sin[10] == 0.0f), "全周傅氏系数表 sin exact zeros at k=0,10"); CHECK((k_fft_cos[5] == 0.0f) && (k_fft_cos[15] == 0.0f), "全周傅氏系数表 cos exact zeros at k=5,15"); CHECK(fabs((double)k_fft_sin[5] - sqrt(2.0) / PHASOR_N) < 1e-7, "全周傅氏系数表 sin[5] == sqrt(2)/N (peak coefficient)"); } /* ===================== TC-PHASOR-003/004:基波幅值 / 有效值 / 相位 ===================== */ static void tc_fundamental(void) { static const struct { double amp; double ph; } k_case[] = { { 57.7, 0.0 }, { 57.7, 30.0 }, { 57.7, -45.0 }, { 100.0, 90.0 }, { 1.0, 0.0 }, { 32767.0, 137.0 }, }; const unsigned int ncase = (unsigned int)(sizeof(k_case) / sizeof(k_case[0])); unsigned int i; int ok_rms = 1, ok_amp = 1, ok_ph = 1; float x[PHASOR_N]; for (i = 0u; i < ncase; i++) { phasor_t p; double exp_rms = k_case[i].amp / sqrt(2.0); double ph_meas; gen_sine(x, (uint8_t)PHASOR_N, k_case[i].amp, PH_F0, k_case[i].ph); phasor_calc(x, (uint8_t)PHASOR_N, 1u, &p); if (fabs((double)p.fAmSQ - exp_rms) / exp_rms > 0.01) { ok_rms = 0; } if (fabs((double)p.fAm - exp_rms * sqrt(2.0)) / (exp_rms * sqrt(2.0)) > 0.01) { ok_amp = 0; } ph_meas = atan2((double)p.fIm, (double)p.fRe) * 180.0 / PH_PI; if (fabs(ph_meas - k_case[i].ph) > 0.5) { printf(" (amp=%.1f ph=%.1f -> measured %.4f)\n", k_case[i].amp, k_case[i].ph, ph_meas); ok_ph = 0; } } CHECK(ok_rms, "相量有效值输出 pure fundamental: fAmSQ == A/sqrt(2) (RMS) within 1%"); CHECK(ok_amp, "相量有效值输出 fAm == sqrt(2)*fAmSQ (amplitude) within 1%"); CHECK(ok_ph, "相位输出 / 相量有效值输出 atan2(fIm,fRe) == injected phase (6 amp/phase cases)"); } /* ===================== TC-PHASOR-005:直流与谐波被滤除 ===================== */ static void tc_filtering(void) { const double amp = 57.7, exp_rms = amp / sqrt(2.0); static const struct { double dc, h2, h3, h5; const char *name; } k_case[] = { { 0.0, 0.0, 0.0, 0.0, "pure" }, { 1.0, 0.0, 0.0, 0.0, "with 1V DC" }, { 0.0, 0.0, 30.0, 0.0, "with 30% 3rd harmonic" }, { 0.0, 0.0, 0.0, 11.5, "with 20% 5th harmonic" }, { 1.0, 20.0, 15.0, 11.5, "DC + 2nd + 3rd + 5th (doc 6 case)" }, }; const unsigned int ncase = (unsigned int)(sizeof(k_case) / sizeof(k_case[0])); unsigned int i; int ok = 1; float x[PHASOR_N]; for (i = 0u; i < ncase; i++) { phasor_t p; uint8_t k; double err; for (k = 0u; k < (uint8_t)PHASOR_N; k++) { double arg = 2.0 * PH_PI * (double)k / (double)PHASOR_N; x[k] = (float)(amp * sin(arg + 30.0 * PH_PI / 180.0) + k_case[i].dc + k_case[i].h2 * sin(2.0 * arg) + k_case[i].h3 * sin(3.0 * arg) + k_case[i].h5 * sin(5.0 * arg)); } phasor_calc(x, (uint8_t)PHASOR_N, 1u, &p); err = fabs((double)p.fAmSQ - exp_rms) / exp_rms; if (err > 0.01) { printf(" (%s -> fAmSQ=%.4f expect=%.4f err=%.3f%%)\n", k_case[i].name, (double)p.fAmSQ, exp_rms, err * 100.0); ok = 0; } } CHECK(ok, "相量有效值输出 DC + 2nd/3rd/5th harmonics fully rejected (5 cases, err <= 1%)"); } /* ===================== TC-PHASOR-006:任意次谐波提取 ===================== */ static void tc_harmonic(void) { /* 基波 100 + 二次 25 + 五次 12(峰值);三根谱线应各自独立读出 */ static const struct { uint8_t harm; double amp; } k_case[] = { { 1u, 100.0 }, { 2u, 25.0 }, { 5u, 12.0 }, }; const unsigned int ncase = (unsigned int)(sizeof(k_case) / sizeof(k_case[0])); unsigned int i; int ok = 1; float x[PHASOR_N]; uint8_t k; for (k = 0u; k < (uint8_t)PHASOR_N; k++) { double arg = 2.0 * PH_PI * (double)k / (double)PHASOR_N; x[k] = (float)(100.0 * sin(arg) + 25.0 * sin(2.0 * arg) + 12.0 * sin(5.0 * arg)); } for (i = 0u; i < ncase; i++) { phasor_t p; double exp_rms = k_case[i].amp / sqrt(2.0); phasor_calc(x, (uint8_t)PHASOR_N, k_case[i].harm, &p); if (fabs((double)p.fAmSQ - exp_rms) / exp_rms > 0.01) { printf(" (harm=%u -> fAmSQ=%.4f expect=%.4f)\n", (unsigned)k_case[i].harm, (double)p.fAmSQ, exp_rms); ok = 0; } } CHECK(ok, "全周傅氏系数表 harmonic extraction k=1/2/5 from a mixed signal (index step = order)"); /* 纯基波时谐波轨应≈0(选择性) */ gen_sine(x, (uint8_t)PHASOR_N, 100.0, PH_F0, 0.0); { phasor_t p2, p5; phasor_calc(x, (uint8_t)PHASOR_N, 2u, &p2); phasor_calc(x, (uint8_t)PHASOR_N, 5u, &p5); CHECK(((double)p2.fAmSQ < 1e-3) && ((double)p5.fAmSQ < 1e-3), "全周傅氏系数表 pure fundamental leaves 2nd/5th harmonic bins ~0"); } } /* ===================== TC-PHASOR-007:频率敏感性 ===================== */ static void tc_freqdev(void) { const double amp = 57.7, exp_rms = amp / sqrt(2.0); /* 最坏相角下的幅值误差(实测:±1 Hz ≤1.1%,±2 Hz ≤2.33%;口径见 §9 Q3) */ static const struct { double f; double limit_pct; } k_case[] = { { 50.0, 0.10 }, { 49.0, 1.50 }, { 51.0, 1.50 }, { 48.0, 3.00 }, { 52.0, 3.00 }, }; const unsigned int ncase = (unsigned int)(sizeof(k_case) / sizeof(k_case[0])); unsigned int i; int ok = 1; float x[PHASOR_N]; for (i = 0u; i < ncase; i++) { double worst = 0.0; int ph; for (ph = 0; ph < 360; ph += 15) { phasor_t p; double err; gen_sine(x, (uint8_t)PHASOR_N, amp, k_case[i].f, (double)ph); phasor_calc(x, (uint8_t)PHASOR_N, 1u, &p); err = fabs((double)p.fAmSQ - exp_rms) / exp_rms * 100.0; if (err > worst) { worst = err; } } printf(" (f=%5.1f Hz worst err over phase = %.2f%%, limit %.2f%%)\n", k_case[i].f, worst, k_case[i].limit_pct); if (worst > k_case[i].limit_pct) { ok = 0; } } CHECK(ok, "相量有效值输出 frequency sensitivity: 50Hz<=0.1%, +-1Hz<=1.5%, +-2Hz<=3% (worst phase)"); } /* ===================== TC-PHASOR-010:采样环 ===================== */ static void tc_ring(void) { float f[ADC_SEM_COUNT]; float w[SAMPLE_RING_DEPTH]; uint8_t c; int i; int ok; sample_ring_init(); CHECK(sample_ring_filled() == 0u, "L2-in ring: init clears fill"); CHECK(sample_ring_frames() == 0u, "L2-in ring: init clears frame counter"); CHECK(sample_ring_window(ADC_SEM_UA, 20u, w) == -2, "L2-in ring: window before data -> -2"); /* 前 3 帧:UA = 100/200/300 */ ok = 1; for (i = 0; i < 3; i++) { for (c = 0u; c < (uint8_t)ADC_SEM_COUNT; c++) { f[c] = 0.0f; } f[ADC_SEM_UA] = (float)(100 * (i + 1)); if (sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT) != 0) { ok = 0; } } CHECK(ok, "L2-in ring: 3 frames pushed OK"); CHECK(sample_ring_filled() == 3u, "L2-in ring: filled == 3"); CHECK((sample_ring_window(ADC_SEM_UA, 3u, w) == 0) && (w[0] == 100.0f) && (w[1] == 200.0f) && (w[2] == 300.0f), "L2-in ring: window is chronological (old -> new)"); /* 通道隔离:其它语义通道不应被 UA 的写入污染 */ CHECK((sample_ring_window(ADC_SEM_IB, 3u, w) == 0) && (w[0] == 0.0f) && (w[1] == 0.0f) && (w[2] == 0.0f), "L2-in ring: channels are independent"); /* 推到 45 帧(> DEPTH=40)→ 回绕 */ for (i = 3; i < 45; i++) { for (c = 0u; c < (uint8_t)ADC_SEM_COUNT; c++) { f[c] = 0.0f; } f[ADC_SEM_UA] = (float)i; (void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT); } CHECK(sample_ring_filled() == SAMPLE_RING_DEPTH, "L2-in ring: fill saturates at DEPTH"); CHECK(sample_ring_frames() == 45u, "L2-in ring: frame counter is NOT saturated"); ok = (sample_ring_window(ADC_SEM_UA, 20u, w) == 0); for (i = 0; i < 20; i++) { if (w[i] != (float)(25 + i)) { ok = 0; } } CHECK(ok, "L2-in ring: wrapped window == last 20 values, in order"); ok = (sample_ring_window(ADC_SEM_UA, (uint8_t)SAMPLE_RING_DEPTH, w) == 0); for (i = 0; i < (int)SAMPLE_RING_DEPTH; i++) { if (w[i] != (float)(5 + i)) { ok = 0; } } CHECK(ok, "L2-in ring: full-depth window == last DEPTH values, in order"); CHECK(sample_ring_window(ADC_SEM_UA, 0u, w) == -1, "L2-in ring: n=0 rejected"); CHECK(sample_ring_window(ADC_SEM_UA, (uint8_t)(SAMPLE_RING_DEPTH + 1u), w) == -1, "L2-in ring: n > DEPTH rejected"); CHECK(sample_ring_window((adc_semantic_t)ADC_SEM_COUNT, 20u, w) == -1, "L2-in ring: out-of-range channel rejected"); CHECK(sample_ring_window(ADC_SEM_UA, 20u, NULL) == -1, "L2-in ring: NULL out rejected"); CHECK(sample_ring_push_frame(NULL, (uint8_t)ADC_SEM_COUNT) == -1, "L2-in ring: NULL frame rejected"); CHECK(sample_ring_push_frame(f, 3u) == -1, "L2-in ring: wrong frame length rejected"); } /* ===================== TC-PHASOR-020/021/022:实例层 + 滑动窗 + 双区 ===================== */ static void tc_instances(void) { const double amp = 100.0, exp_rms = amp / sqrt(2.0); float f[ADC_SEM_COUNT]; phasor_t p; uint32_t t; int ok; uint8_t i; CHECK(phasor_inst_count() == 5u, "基波相量实例 instance count == 5"); CHECK((phasor_inst(0u) != NULL) && (phasor_inst(0u)->name != NULL), "基波相量实例 instance name available"); CHECK(phasor_inst((uint8_t)phasor_inst_count()) == NULL, "基波相量实例 out-of-range instance -> NULL"); phasor_init(); CHECK(s_use_zone == 0u, "双区指针原子交换 use zone starts at 0"); CHECK(phasor_get(0u, &p) == -2, "基波相量实例 no result before a full cycle"); CHECK(phasor_get((uint8_t)phasor_inst_count(), &p) == -1, "基波相量实例 out-of-range index -> -1"); CHECK(phasor_get(0u, NULL) == -1, "基波相量实例 NULL out -> -1"); /* 喂 19 帧(不足一周波)→ 仍不产出 */ for (t = 0u; t < 19u; t++) { frame_at(f, t, amp, 0.0); (void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT); phasor_run(); } CHECK(phasor_get(0u, &p) == -2, "基波相量实例 still no result after 19 frames"); CHECK(s_use_zone == 0u, "双区指针原子交换 use zone unchanged while not producing"); /* 第 20 帧 → 攒满一周波,产出 */ frame_at(f, 19u, amp, 0.0); (void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT); phasor_run(); CHECK(phasor_get(0u, &p) == 0, "基波相量实例 result available after 20 frames"); CHECK(s_use_zone == 1u, "双区指针原子交换 use zone toggles to 1 after first production"); CHECK(fabs((double)p.fAmSQ - exp_rms) / exp_rms < 0.01, "基波相量实例 / 相量有效值输出 instance 0 (C01-IA-F1) RMS == A/sqrt(2)"); { phasor_t p2, p5; int ok_sel = 1; if (phasor_get(3u, &p2) != 0) { ok_sel = 0; } if (phasor_get(4u, &p5) != 0) { ok_sel = 0; } CHECK(ok_sel && ((double)p2.fAmSQ < 1e-3) && ((double)p5.fAmSQ < 1e-3), "基波相量实例 harmonic instances (C04-IA-F2 / C05-IA-F5) ~0 on pure fundamental"); } /* 每 1 ms 滑动窗刷新:继续推 20 帧,每拍重算,基波读数应恒定 */ ok = 1; for (t = 20u; t < 40u; t++) { frame_at(f, t, amp, 0.0); (void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT); phasor_run(); if (phasor_get(0u, &p) != 0) { ok = 0; } else if (fabs((double)p.fAmSQ - exp_rms) / exp_rms > 0.01) { ok = 0; } } CHECK(ok, "每 1 ms 滑动窗刷新 sliding window: RMS constant across 20 consecutive slides"); /* 实例表一致性:逐实例复核"实例窗 == 独立 phasor_calc 结果" */ ok = 1; for (i = 0u; i < phasor_inst_count(); i++) { const phasor_inst_t *d = phasor_inst(i); float win[PHASOR_N]; phasor_t ref, got; if (d == NULL) { ok = 0; break; } if (sample_ring_window(d->sem, (uint8_t)PHASOR_N, win) != 0) { ok = 0; break; } phasor_calc(win, (uint8_t)PHASOR_N, d->harm, &ref); if (phasor_get(i, &got) != 0) { ok = 0; break; } if ((ref.fRe != got.fRe) || (ref.fIm != got.fIm) || (ref.fAmSQ != got.fAmSQ)) { ok = 0; break; } } CHECK(ok, "基波相量实例 / 每 1 ms 滑动窗刷新 instance results == phasor_calc() on the same ring window"); } /* ===================== TC-PHASOR-030..034:phasor_zero / init 清零 ===================== */ static void tc_zero(void) { phasor_t p; uint8_t i; int zones_dirty = 0; /* 1) 直接白盒:phasor_zero 把四个字段(含 fAm / fAmSQ)全部清零 */ p.fRe = 1.0f; p.fIm = -2.0f; p.fAm = 3.0f; p.fAmSQ = 4.0f; phasor_zero(&p); CHECK((p.fRe == 0.0f) && (p.fIm == 0.0f) && (p.fAm == 0.0f) && (p.fAmSQ == 0.0f), "TC-PHASOR-030 phasor_zero clears all four fields"); /* 2) 经 phasor_init:先把双区写成脏值,init 后必须全零,使用区复位、就绪标志清 */ for (i = 0u; i < (uint8_t)PHASOR_INST_N; i++) { s_zone[0][i].fRe = 9.0f; s_zone[0][i].fIm = 9.0f; s_zone[0][i].fAm = 9.0f; s_zone[0][i].fAmSQ = 9.0f; s_zone[1][i].fRe = 9.0f; s_zone[1][i].fIm = 9.0f; s_zone[1][i].fAm = 9.0f; s_zone[1][i].fAmSQ = 9.0f; } s_use_zone = 1u; (void)phasor_init(); for (i = 0u; i < (uint8_t)PHASOR_INST_N; i++) { if ((s_zone[0][i].fRe != 0.0f) || (s_zone[0][i].fIm != 0.0f) || (s_zone[0][i].fAm != 0.0f) || (s_zone[0][i].fAmSQ != 0.0f) || (s_zone[1][i].fRe != 0.0f) || (s_zone[1][i].fIm != 0.0f) || (s_zone[1][i].fAm != 0.0f) || (s_zone[1][i].fAmSQ != 0.0f)) { zones_dirty = 1; } } CHECK(zones_dirty == 0, "TC-PHASOR-031 phasor_init zeroes both result zones"); CHECK(s_use_zone == 0u, "TC-PHASOR-032 phasor_init resets use zone to 0"); CHECK(phasor_get(0u, &p) == -2, "TC-PHASOR-033 phasor_init clears ready (get -> -2)"); /* 3) 幂等:重复 init 不改变结果 */ (void)phasor_init(); CHECK((s_use_zone == 0u) && (phasor_get(0u, &p) == -2), "TC-PHASOR-034 phasor_init is idempotent"); } /* ===================== TC-PHASOR-022:双区指针原子交换 ===================== */ static void tc_dualzone(void) { const double amp = 100.0; float f[ADC_SEM_COUNT]; phasor_t p; uint32_t t; int ok; /* 1) 使用区逐次产出交替(0 -> 1 -> 0 -> 1) */ phasor_init(); for (t = 0u; t < 20u; t++) { frame_at(f, t, amp, 0.0); (void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT); phasor_run(); } CHECK(s_use_zone == 1u, "双区指针原子交换 use zone == 1 after 1st production"); frame_at(f, 20u, amp, 0.0); (void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT); phasor_run(); CHECK(s_use_zone == 0u, "双区指针原子交换 use zone == 0 after 2nd production"); frame_at(f, 21u, amp, 0.0); (void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT); phasor_run(); CHECK(s_use_zone == 1u, "双区指针原子交换 use zone == 1 after 3rd production"); /* 2) 每拍产出后 get() 必须等于"当前环窗的独立重算"。 * 幅值每 20 拍切换(100 <-> 10),故"读到陈旧区"会立刻不相等—— * 连测 60 拍,覆盖两个区的来回切换,比单次比较强得多。 */ phasor_init(); ok = 1; for (t = 0u; t < 60u; t++) { float win[PHASOR_N]; phasor_t ref, got; double a = (((t / 20u) % 2u) == 0u) ? amp : (amp / 10.0); frame_at(f, t, a, 0.0); (void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT); phasor_run(); if (sample_ring_window(ADC_SEM_IA, (uint8_t)PHASOR_N, win) != 0) { continue; /* 尚未攒满一周波:本拍按契约不产出,无需比较 */ } phasor_calc(win, (uint8_t)PHASOR_N, 1u, &ref); if (phasor_get(0u, &got) != 0) { ok = 0; break; } if ((ref.fRe != got.fRe) || (ref.fIm != got.fIm) || (ref.fAmSQ != got.fAmSQ)) { ok = 0; break; } } CHECK(ok, "双区指针原子交换 dual-zone: get() == fresh recompute of the live window, all 60 beats"); CHECK(phasor_get(0u, &p) == 0, "双区指针原子交换 result still readable after the sweep"); } int main(void) { printf("=== Phasor Tests ===\n"); printf("-- 全周傅氏系数表 coefficient table --\n"); tc_table(); printf("-- 相量有效值输出 / 相位输出 fundamental amplitude/RMS/phase --\n"); tc_fundamental(); printf("-- DC/harmonic rejection --\n"); tc_filtering(); printf("-- arbitrary harmonic extraction --\n"); tc_harmonic(); printf("-- frequency sensitivity --\n"); tc_freqdev(); printf("-- L2 input sample ring --\n"); tc_ring(); printf("-- 基波相量实例 / 每 1 ms 滑动窗刷新 / 双区指针原子交换 instance layer --\n"); tc_instances(); printf("-- 双区指针原子交换 dual-zone swap --\n"); tc_dualzone(); printf("-- L2 zero / init --\n"); tc_zero(); printf("\n=== Phasor Tests: %d/%d PASSED (failed=%d) ===\n", s_passed, s_total, s_failed); return (s_failed == 0) ? 0 : 1; }