Files
STM32F4-Base/test/host/phasor/phasor_test.c
T
wandering1 63c262fe2c 相量层单测:补 phasor_zero/init 清零用例(L1/L2 函数覆盖 20/20)
函数级覆盖审计发现 phasor_zero() 仅被 phasor_init() 间接执行、其效果
无可观测断言(init 同时把 s_ready 清 0,公开 API 看不到清零)。补直接
白盒用例:

- TC-PH-030 直接调 phasor_zero,四字段(含 fAm/fAmSQ)全清零
- TC-PH-031/032/033 先把双区写脏,init 后双区全零 + 使用区复位 +
  就绪标志清(get -> -2)
- TC-PH-034 init 幂等

审计结果:App/algo 三个源共 20 个函数,0 个未覆盖。
相量层单测 57/57 PASSED(原 52)。
2026-10-10 17:48:13 +08:00

554 lines
23 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-PH-030..034 phasor_zero 四字段清零 / phasor_init 双区清零 + 使用区与就绪标志复位 + 幂等
*
* 通过:全部 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-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-PH-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-PH-031 phasor_init zeroes both result zones");
CHECK(s_use_zone == 0u, "TC-PH-032 phasor_init resets use zone to 0");
CHECK(phasor_get(0u, &p) == -2, "TC-PH-033 phasor_init clears ready (get -> -2)");
/* 3) 幂等:重复 init 不改变结果 */
(void)phasor_init();
CHECK((s_use_zone == 0u) && (phasor_get(0u, &p) == -2), "TC-PH-034 phasor_init is idempotent");
}
/* ===================== 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("-- 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;
}