Files
wandering1 da36f4ba56 refactor(编号): 自愈层号与 timeSync 挂起、CubeMX 三层、采集链层名清号(代码面)
批次六余账之二(代码与脚本面)。四族都是「批次一 / 三c 改了文档、漏了代码」的半改
状态。

- 日志自愈:「L1 文件级自愈」→「文件级自愈」、「L2 错误去重」→「错误去重」(正文
  本来就这么叫,层号纯属多余):App/log/soe_store.c、sys_log_mcu.c 三处、
  test/host/sys_log_mcu 的测试与假件头共 10 处。
- timeSync:「L3 pending 化」→ 挂起请求 / pending 化——这条口径早写在记忆里,
  今天才算全仓一致。App/sync/time_sync.c 3 处。
- CubeMX 防丢失三层优先级:scripts/check_cubemx_survival.py 6 处「由 L3 升级为
  L2」「L2 受保护」「L3 兜底」→ 写进 .ioc / 搬进 USER CODE 区 / 审计兜底;变迁史
  句子按「换号留事实」改写、日期原样保留。
- 采集链旧层名:App/README.md「采集与算法层(L1/L2/L3)」→「采集与算法层」;
  protect_task.c/.h「(L3 板端接线,B5)」→「(板端接线,B5)」。
- phasor 断言文案 19 处:改为 ASCII 的 phasor-in ring(C 字面量必须纯 ASCII)。
  改前先证明这些串无耦合——全仓只有本文件含它们、harness 只看 returncode 与汇总
  标记;改后 phasor 57/57 与基线同数。
- 零行为:剥注释 + 套登记表后与 HEAD 逐字节 / AST 相同;sys_log_mcu 141/141、
  phasor 57/57、watchdog 47/47、freq 46/46 均与动手前基线同数。
- 同一四族的文档面(CubeMX 30、采集链 5、菜单层级 2、双写 14)由下一笔收。
2026-10-28 18:52:37 +08:00

554 lines
24 KiB
C
Raw Permalink 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, "phasor-in ring: init clears fill");
CHECK(sample_ring_frames() == 0u, "phasor-in ring: init clears frame counter");
CHECK(sample_ring_window(ADC_SEM_UA, 20u, w) == -2, "phasor-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, "phasor-in ring: 3 frames pushed OK");
CHECK(sample_ring_filled() == 3u, "phasor-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),
"phasor-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),
"phasor-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, "phasor-in ring: fill saturates at DEPTH");
CHECK(sample_ring_frames() == 45u, "phasor-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, "phasor-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, "phasor-in ring: full-depth window == last DEPTH values, in order");
CHECK(sample_ring_window(ADC_SEM_UA, 0u, w) == -1, "phasor-in ring: n=0 rejected");
CHECK(sample_ring_window(ADC_SEM_UA, (uint8_t)(SAMPLE_RING_DEPTH + 1u), w) == -1,
"phasor-in ring: n > DEPTH rejected");
CHECK(sample_ring_window((adc_semantic_t)ADC_SEM_COUNT, 20u, w) == -1,
"phasor-in ring: out-of-range channel rejected");
CHECK(sample_ring_window(ADC_SEM_UA, 20u, NULL) == -1, "phasor-in ring: NULL out rejected");
CHECK(sample_ring_push_frame(NULL, (uint8_t)ADC_SEM_COUNT) == -1, "phasor-in ring: NULL frame rejected");
CHECK(sample_ring_push_frame(f, 3u) == -1, "phasor-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("-- phasor input sample ring --\n");
tc_ring();
printf("-- 基波相量实例 / 每 1 ms 滑动窗刷新 / 双区指针原子交换 instance layer --\n");
tc_instances();
printf("-- 双区指针原子交换 dual-zone swap --\n");
tc_dualzone();
printf("-- phasor 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;
}