Files
STM32F4-Base/test/host/phasor/phasor_test.c
T
wandering1 593c5d0c84 refactor(编号): 采集链分层 L1/L1.5/L2/L2.5/L3/L4/L5 → 语义名(批次 1)
用户裁定(2026-10-28):L1/L1.5/L2/L2.5/L3 这套层号「谁能看懂」,改用语义名。
映射(同一批定稿,含 L4/L5):
  L1   → 语义取数与有效性
  L1.5 → 标定折算
  L2   → 全周傅氏相量
  L2.5 → 测频与遥测快照
  L3   → 保护判据
  L4   → 出口与记录
  L5   → 动作留痕

实测规模:384 站点 / 62 文件(App/algo、App/task、test/host 六个套件、docs/算法、
产品需求、架构总览、启动顺序、ADC 采样节拍、IEC104 上行、采集短板评测、内存管理、
AGENTS.md、README.md、docs/README.md、.pre-commit-config.yaml 与 scripts 里的注释)。

改法(不是全局替换,逐模式有上下文规则):
- 先吃同位语再落裸词:L1 有效性校验 → 语义取数与有效性校验、L2 相量层 → 全周傅氏
  相量层、L3 保护 → 保护判据、L2.5 遥测快照 → 测频与遥测快照,避免「保护判据保护判据」
  这类重复(第一版就是这么坏掉的,已整体回滚重做)。
- .c/.h 只改注释:先把字符串字面量切出去(引号数为奇数的跨行注释按整行处理),
  C 字符串里的 L2-in ring 之类原样保留——AGENTS 铁律「字符串字面量必须纯 ASCII」。
- 层内条目号(L1-3 / L2-3 这类)**本批不动**,属批次 2;另外三族(UI 菜单层级
  L0~L3、链路延迟分段 L1~L8、CubeMX 防丢失三层)也不动,属批次 3。
- 顺手修的措辞:范围写法 L1~L3 → 「分层(语义取数与有效性 → 保护判据)」、
  CI 段「不含 L1/L2/L3」→「不含采集链分层」、目录地图里同一处并列表述。

验证:@build 0 错 0 警(Code=236456,与改前逐字节同尺寸——纯注释改动);
内存布局配图已按新脚本重生成(自检通过);doc-links --all --sections-strict 通过
(195 文档);mojibake / bom-guard / coding-standard / 新编号门禁自查 全 rc=0;
git diff --check 干净。
2026-10-28 13:04:02 +08:00

554 lines
24 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.
/*
* 模块名称:相量层(全周傅氏相量)主机侧单元测试
* 模块功能:在 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 <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:全周傅氏系数表 ===================== */
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;
}