Files
STM32F4-Base/test/host/phasor/phasor_test.c
T
wandering1 3762c03bb4 L2 相量层:采样环缓冲 + 全周傅氏 + 主机单测(台架间歇复位待查)
新增(均已通过主机侧单测与构建,**台架存在未定位的间歇性回归,见下**):
- App/algo/sample_ring.c/h:按语义通道的采样环缓冲(2 周波 = 40 点),
  整帧压入 + 帧计数,供 L2 按窗取数。
- App/algo/phasor.c/h:全周傅氏相量层,覆盖规划 §3.2 的 L2-1/2/3/6/7——
  系数表按 docs/算法/傅氏算法详解.md §2.3 预乘 √2/N(fAmSQ 直接即有效值);
  5 个数据驱动实例(IA/UA/UN 基波 + IA 二/五次谐波);暴力滑动窗;
  计算区/使用区双区原子切换。
- test/host/phasor/:主机侧单测(52/52 PASSED,无需桩)——系数表逐项复核、
  幅值/有效值/相位、直流与谐波抑制、任意次谐波提取、频率敏感性(±2Hz≤3%)、
  环缓冲时序与回绕、实例层与双区交换。
- 集成:adc_task 每拍压帧 + 重算(v2.4);app_main 调 phasor_init();
  Keil 工程挂 phasor.c/sample_ring.c(App/algo 已在 IncludePath)。

验证:
- 主机单测 52/52 PASSED。
- @build 0 错误 0 警告;内存快照已刷新(RW_IRAM1 89,320→90,936 B)。
- 台架:L2 链路数据正常(ring=10001、skip=0、回读一致、相量日志可见),
  但 10 次运行中 3 次出现 UI 任务看门狗停喂(mask=0x67 缺 TASK_WD_UI)
  → IWDG 复位;改动前基线 6 次运行 0 次。属**未定位的间歇性回归**。

未完成:定位并修复该间歇性复位;L2 文档状态与登记簿条目尚未按结论更新。
2026-10-10 17:39:37 +08:00

512 lines
21 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:相量层(L2)主机侧单元测试
* 模块功能:在 PC 上对 App/algo/phasor.c + App/algo/sample_ring.c 做离线验证。
*
* 被测对象:真实源文件(以 #include 原样纳入,保证测的是生产代码而非副本):
* ../../../App/algo/phasor.c
* ../../../App/algo/sample_ring.c
* 本层为**纯算法**(无寄存器、无 RTOS、无文件/外设依赖),故**无需任何桩**。
*
* 为什么要主机侧单测(而不是只用板端套件):
* 1. 相量算法是纯数学变换,与硬件无关;板端**无信号源**,无法注入已知幅值/相位/
* 频率的标准波形,因而无法验证"读数 = 设定值"这一核心判据(同 L1-3 的替代口径)。
* 主机侧可合成任意波形,把幅值精度/谐波抑制/频率敏感性一次性钉死。
* 2. 可精确构造边界:恰好一周波 / 不足一周波 / 环形回绕 / 任意次谐波 / 频偏。
* 3. 不占 MCU 的 RAM,毫秒级完成。
*
* 覆盖点(对应 docs/算法/继电保护功能复现规划.md §3.2):
* TC-PH-001 L2-1 系数表逐项等于 sin(2πk/N)·(√2/N) / cos(2πk/N)·(√2/N)(防转录错)
* TC-PH-002 L2-1 系数表精确零点(sin@0,10 / cos@5,15)与归一化 Σcoe² = 1/N
* TC-PH-003 L2-3 纯基波:fAmSQ = A/√2(有效值),fAm = √2·fAmSQ(幅值)
* TC-PH-004 L2-3/L2-4 实部虚部与相位:atan2(fIm,fRe) = 注入相角
* TC-PH-005 直流 + 2/3/5 次谐波被完全滤除(基波读数不变)
* TC-PH-006 任意次谐波提取(索引步进 = 谐波次数):k=2 / k=5
* TC-PH-007 频率偏离 50 Hz 的幅值误差(±1 Hz ≤ 1.5%,±2 Hz ≤ 3%,见 §9 Q3)
* TC-PH-010 采样环:入环/取窗时序、回绕、饱和、帧计数、参数守卫
* TC-PH-020 L2-2 实例层:实例数/实例表/未攒满一周波不产出
* TC-PH-021 L2-6 滑动窗:连续滑动 20 拍基波读数恒定
* TC-PH-022 L2-7 双区交换:使用区交替 + get 返回**最后一次** run 的结果(非陈旧)
*
* 通过:全部 TC PASS(main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
*
* 创建日期:2026-10-10
*/
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#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-PH-001/002:L2-1 系数表 ===================== */
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, "L2-1 coeff sin[k] == sin(2*pi*k/N)*(sqrt2/N) for all k");
CHECK(ok_cos, "L2-1 coeff cos[k] == cos(2*pi*k/N)*(sqrt2/N) for all k");
CHECK(ok_norm, "L2-1 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, "L2-1 sum(sin^2) == 1/N (=> RMS, not amplitude)");
CHECK(fabs(sum_cos - 1.0 / PHASOR_N) < 1e-7, "L2-1 sum(cos^2) == 1/N (=> RMS, not amplitude)");
CHECK((k_fft_sin[0] == 0.0f) && (k_fft_sin[10] == 0.0f), "L2-1 sin exact zeros at k=0,10");
CHECK((k_fft_cos[5] == 0.0f) && (k_fft_cos[15] == 0.0f), "L2-1 cos exact zeros at k=5,15");
CHECK(fabs((double)k_fft_sin[5] - sqrt(2.0) / PHASOR_N) < 1e-7,
"L2-1 sin[5] == sqrt(2)/N (peak coefficient)");
}
/* ===================== TC-PH-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, "L2-3 pure fundamental: fAmSQ == A/sqrt(2) (RMS) within 1%");
CHECK(ok_amp, "L2-3 fAm == sqrt(2)*fAmSQ (amplitude) within 1%");
CHECK(ok_ph, "L2-4/L2-3 atan2(fIm,fRe) == injected phase (6 amp/phase cases)");
}
/* ===================== TC-PH-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, "L2-3 DC + 2nd/3rd/5th harmonics fully rejected (5 cases, err <= 1%)");
}
/* ===================== TC-PH-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, "L2-1 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),
"L2-1 pure fundamental leaves 2nd/5th harmonic bins ~0");
}
}
/* ===================== TC-PH-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, "L2-3 frequency sensitivity: 50Hz<=0.1%, +-1Hz<=1.5%, +-2Hz<=3% (worst phase)");
}
/* ===================== TC-PH-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-PH-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, "L2-2 instance count == 5");
CHECK((phasor_inst(0u) != NULL) && (phasor_inst(0u)->name != NULL), "L2-2 instance name available");
CHECK(phasor_inst((uint8_t)phasor_inst_count()) == NULL, "L2-2 out-of-range instance -> NULL");
phasor_init();
CHECK(s_use_zone == 0u, "L2-7 use zone starts at 0");
CHECK(phasor_get(0u, &p) == -2, "L2-2 no result before a full cycle");
CHECK(phasor_get((uint8_t)phasor_inst_count(), &p) == -1, "L2-2 out-of-range index -> -1");
CHECK(phasor_get(0u, NULL) == -1, "L2-2 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, "L2-2 still no result after 19 frames");
CHECK(s_use_zone == 0u, "L2-7 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, "L2-2 result available after 20 frames");
CHECK(s_use_zone == 1u, "L2-7 use zone toggles to 1 after first production");
CHECK(fabs((double)p.fAmSQ - exp_rms) / exp_rms < 0.01,
"L2-2/L2-3 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),
"L2-2 harmonic instances (C04-IA-F2 / C05-IA-F5) ~0 on pure fundamental");
}
/* L2-6 滑动窗:继续推 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, "L2-6 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, "L2-2/L2-6 instance results == phasor_calc() on the same ring window");
}
/* ===================== TC-PH-022:L2-7 双区指针原子交换 ===================== */
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, "L2-7 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, "L2-7 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, "L2-7 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, "L2-7 dual-zone: get() == fresh recompute of the live window, all 60 beats");
CHECK(phasor_get(0u, &p) == 0, "L2-7 result still readable after the sweep");
}
int main(void) {
printf("=== Phasor Tests ===\n");
printf("-- L2-1 coefficient table --\n");
tc_table();
printf("-- L2-3/L2-4 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("-- L2-2/L2-6/L2-7 instance layer --\n");
tc_instances();
printf("-- L2-7 dual-zone swap --\n");
tc_dualzone();
printf("\n=== Phasor Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}