Files
STM32F4-Base/test/host/phasor/phasor_test.c
T
wandering1 bda7ab2b50 refactor(编号): 16 个测试模块码单词化 + 门禁识别面连带放宽
批 2(用户授权改名 + 「新名要一眼看懂」裁定)
- 16 个有歧义模块码单词化,共 1,146 处随 scripts/rename_ids.py 改写:
  AI→ADCIN、AH→ADCHEALTH、PS→PROTSET、PE→PROTELEM、PH→PHASOR、
  TS→TIMESYNC、VL→VOLLOCK、NP→NANDPORT、CFG→CONFIG、CLK→SYSCLK、
  WD→WATCHDOG、STO→STORAGE、SH→SHA256、FA→FTPAUTH、FR→FREQ、104→IEC104
- 不动 NET/GD5F/RTC/SOE/UI/ADC/LOG/RS485/TOOL(已是单词、芯片名或业界通用词,
  改了纯属 churn)
- 别名按全局登记:这批字面量已完全限定(TC-<码>-<nnn>),不存在裸形多义
- 映射表存档 scripts/rename_maps/batch2.json 作审计轨迹;§6 模块码表同步

连带修门禁(关键教训)
- 模块码涨到 9 位(ADCHEALTH/WATCHDOG)后,候选正则与族正则的 {0,4} 上界
  会让改名后的号**整批从门禁眼里消失**:实测编号字面量从 8271 掉到 7315、
  悬空假降 490 处。已放宽到 {0,9} 并写进 §6 注记
- 结论:改名批次必须连带复核识别面,只改数据不改判据 = 静默放行;
  这次是靠「可判定比例跌破基线即失败」那条线拦下来的

现状与遗留
- 校验 1 未登记前缀 0 处;可判定比例 82.71%(基线 82.6%,只涨不跌成立)
- 遗留:7 个模块的「单元测试说明.md」仍缺(PROTSET 116、NANDPORT 109、
  PROTELEM 65、FREQ 48、FTPAUTH 45、SHA256 25、SOE 14 处悬空)——改名不解决
  缺文档,处置方式待议
- 遗留:T-2/T-3/T-4/T-6 历史任务号悬空(别名表未收全,属本工具批次漏项)
- 未改任何 C/H 可编译语义(仅注释与宏名内的编号),故不需 @build
2026-10-21 09:53:28 +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-PHASOR-001 L2-1 系数表逐项等于 sin(2πk/N)·(√2/N) / cos(2πk/N)·(√2/N)(防转录错)
* TC-PHASOR-002 L2-1 系数表精确零点(sin@0,10 / cos@5,15)与归一化 Σcoe² = 1/N
* TC-PHASOR-003 L2-3 纯基波:fAmSQ = A/√2(有效值),fAm = √2·fAmSQ(幅值)
* TC-PHASOR-004 L2-3/L2-4 实部虚部与相位: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 L2-2 实例层:实例数/实例表/未攒满一周波不产出
* TC-PHASOR-021 L2-6 滑动窗:连续滑动 20 拍基波读数恒定
* TC-PHASOR-022 L2-7 双区交换:使用区交替 + 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 <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-PHASOR-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-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, "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-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, "L2-3 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, "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-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, "L2-3 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, "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-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: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;
}