refactor(编号·批次二): 代码与测试侧 215 处保护功能号改成功能名,零行为改动
用户裁定「不要用字母加数字的编号,语义标识是最好的」后的批次二代码半(道 ui-param 执行,逐文件站点数与 自证见其交付)。唯一口径 = 映射表 ci-artifacts/lprot_map.tsv 的功能名列原文,**零自造同义词**;L2.5 与 L-TEST 经实测各 0 处(不是本族),TC 族一字未动。 固件 22 文件 128 处(adc_task.c 21、phasor.c 22、phasor.h 13…),主机测试 13 文件 87 处(pha sor_test.c 57…);设计文档指针新增 19 行,另 16 只文件本来就写着规划文档 ⇒ 不重复加。 **两条只有真改过 215 处才会踩到的坑,都写进注释规范实践**: · 指针**不能接在原句尾部**——那会替原文一个未闭合的半括号封口,实测咬坏 App/algo/sample_ring.h 这类 跨行句 ⇒ 指针一律**单独成行**; · 号删掉会留下**叠词**(「自检失败闭锁 闭锁判定」「定值校验和 定值巡检」)⇒ 复扫并收口 4 处,措辞只用 映射名本身;具体对象需要留住时写成「自检失败闭锁(采样故障闭锁)」。 自证(不靠嘴说):改前把 35 只文件原文备份到仓外 temp,逐文件比 `/*`、`*/` 计数、三类括号计数、行数增量 恰等于指针行数、全角括号增删相等、残留号 0 ⇒ **35/35 通过**;Lead 复核后跑权威 @build **0 错 0 警**, Program Size `Code=233900 RO=26104 RW=1536 ZI=136808` —— **与三条道各自改前改后测的四数逐 字节同数**, 这就是「只动注释与文本」的机器证据;八套主机单测 exit 0,cppcheck 按钩子参数零发现,coding-standard 0。 文件头「修改记录」里的号**一并换名、不还原**(Lead 裁定):这些号是全仓退役的,留一个查无此号的引用比 留一条改过名的条目更坏——「冻结历史不回改」保护的是**别替历史改口**,不是保护一个再也查不到的字符串; id-check 对变更记录行本就豁免,两种选择都不影响门禁。
This commit is contained in:
@@ -9,23 +9,23 @@
|
||||
*
|
||||
* 为什么要主机侧单测(而不是只用板端套件):
|
||||
* 1. 相量算法是纯数学变换,与硬件无关;板端**无信号源**,无法注入已知幅值/相位/
|
||||
* 频率的标准波形,因而无法验证"读数 = 设定值"这一核心判据(同 L1-3 的替代口径)。
|
||||
* 频率的标准波形,因而无法验证"读数 = 设定值"这一核心判据(同 STM32 ADC1 启动与扫描 的替代口径)。
|
||||
* 主机侧可合成任意波形,把幅值精度/谐波抑制/频率敏感性一次性钉死。
|
||||
* 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-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 L2-2 实例层:实例数/实例表/未攒满一周波不产出
|
||||
* TC-PHASOR-021 L2-6 滑动窗:连续滑动 20 拍基波读数恒定
|
||||
* TC-PHASOR-022 L2-7 双区交换:使用区交替 + get 返回**最后一次** run 的结果(非陈旧)
|
||||
* 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)。
|
||||
@@ -98,7 +98,7 @@ static void frame_at(float *f, uint32_t t, double amp, double phase) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================== TC-PHASOR-001/002:L2-1 系数表 ===================== */
|
||||
/* ===================== TC-PHASOR-001/002:全周傅氏系数表 ===================== */
|
||||
|
||||
static void tc_table(void) {
|
||||
const double scale = sqrt(2.0) / (double)PHASOR_N;
|
||||
@@ -123,17 +123,17 @@ static void tc_table(void) {
|
||||
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)");
|
||||
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, "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(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), "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((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,
|
||||
"L2-1 sin[5] == sqrt(2)/N (peak coefficient)");
|
||||
"全周傅氏系数表 sin[5] == sqrt(2)/N (peak coefficient)");
|
||||
}
|
||||
|
||||
/* ===================== TC-PHASOR-003/004:基波幅值 / 有效值 / 相位 ===================== */
|
||||
@@ -171,9 +171,9 @@ static void tc_fundamental(void) {
|
||||
}
|
||||
}
|
||||
|
||||
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)");
|
||||
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:直流与谐波被滤除 ===================== */
|
||||
@@ -215,7 +215,7 @@ static void tc_filtering(void) {
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(ok, "L2-3 DC + 2nd/3rd/5th harmonics fully rejected (5 cases, err <= 1%)");
|
||||
CHECK(ok, "相量有效值输出 DC + 2nd/3rd/5th harmonics fully rejected (5 cases, err <= 1%)");
|
||||
}
|
||||
|
||||
/* ===================== TC-PHASOR-006:任意次谐波提取 ===================== */
|
||||
@@ -250,7 +250,7 @@ static void tc_harmonic(void) {
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(ok, "L2-1 harmonic extraction k=1/2/5 from a mixed signal (index step = order)");
|
||||
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);
|
||||
@@ -259,7 +259,7 @@ static void tc_harmonic(void) {
|
||||
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");
|
||||
"全周傅氏系数表 pure fundamental leaves 2nd/5th harmonic bins ~0");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ static void tc_freqdev(void) {
|
||||
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)");
|
||||
CHECK(ok, "相量有效值输出 frequency sensitivity: 50Hz<=0.1%, +-1Hz<=1.5%, +-2Hz<=3% (worst phase)");
|
||||
}
|
||||
|
||||
/* ===================== TC-PHASOR-010:采样环 ===================== */
|
||||
@@ -370,15 +370,15 @@ static void tc_instances(void) {
|
||||
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");
|
||||
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, "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");
|
||||
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++) {
|
||||
@@ -386,27 +386,27 @@ static void tc_instances(void) {
|
||||
(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");
|
||||
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, "L2-2 result available after 20 frames");
|
||||
CHECK(s_use_zone == 1u, "L2-7 use zone toggles to 1 after first production");
|
||||
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,
|
||||
"L2-2/L2-3 instance 0 (C01-IA-F1) RMS == A/sqrt(2)");
|
||||
"基波相量实例 / 相量有效值输出 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");
|
||||
"基波相量实例 harmonic instances (C04-IA-F2 / C05-IA-F5) ~0 on pure fundamental");
|
||||
}
|
||||
|
||||
/* L2-6 滑动窗:继续推 20 帧,每拍重算,基波读数应恒定 */
|
||||
/* 每 1 ms 滑动窗刷新:继续推 20 帧,每拍重算,基波读数应恒定 */
|
||||
ok = 1;
|
||||
for (t = 20u; t < 40u; t++) {
|
||||
frame_at(f, t, amp, 0.0);
|
||||
@@ -415,7 +415,7 @@ static void tc_instances(void) {
|
||||
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");
|
||||
CHECK(ok, "每 1 ms 滑动窗刷新 sliding window: RMS constant across 20 consecutive slides");
|
||||
|
||||
/* 实例表一致性:逐实例复核"实例窗 == 独立 phasor_calc 结果" */
|
||||
ok = 1;
|
||||
@@ -430,7 +430,7 @@ static void tc_instances(void) {
|
||||
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");
|
||||
CHECK(ok, "基波相量实例 / 每 1 ms 滑动窗刷新 instance results == phasor_calc() on the same ring window");
|
||||
}
|
||||
|
||||
/* ===================== TC-PHASOR-030..034:phasor_zero / init 清零 ===================== */
|
||||
@@ -472,7 +472,7 @@ static void tc_zero(void) {
|
||||
CHECK((s_use_zone == 0u) && (phasor_get(0u, &p) == -2), "TC-PHASOR-034 phasor_init is idempotent");
|
||||
}
|
||||
|
||||
/* ===================== TC-PHASOR-022:L2-7 双区指针原子交换 ===================== */
|
||||
/* ===================== TC-PHASOR-022:双区指针原子交换 ===================== */
|
||||
|
||||
static void tc_dualzone(void) {
|
||||
const double amp = 100.0;
|
||||
@@ -488,17 +488,17 @@ static void tc_dualzone(void) {
|
||||
(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");
|
||||
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, "L2-7 use zone == 0 after 2nd production");
|
||||
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, "L2-7 use zone == 1 after 3rd production");
|
||||
CHECK(s_use_zone == 1u, "双区指针原子交换 use zone == 1 after 3rd production");
|
||||
|
||||
/* 2) 每拍产出后 get() 必须等于"当前环窗的独立重算"。
|
||||
* 幅值每 20 拍切换(100 <-> 10),故"读到陈旧区"会立刻不相等——
|
||||
@@ -521,16 +521,16 @@ static void tc_dualzone(void) {
|
||||
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");
|
||||
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("-- L2-1 coefficient table --\n");
|
||||
printf("-- 全周傅氏系数表 coefficient table --\n");
|
||||
tc_table();
|
||||
printf("-- L2-3/L2-4 fundamental amplitude/RMS/phase --\n");
|
||||
printf("-- 相量有效值输出 / 相位输出 fundamental amplitude/RMS/phase --\n");
|
||||
tc_fundamental();
|
||||
printf("-- DC/harmonic rejection --\n");
|
||||
tc_filtering();
|
||||
@@ -540,9 +540,9 @@ int main(void) {
|
||||
tc_freqdev();
|
||||
printf("-- L2 input sample ring --\n");
|
||||
tc_ring();
|
||||
printf("-- L2-2/L2-6/L2-7 instance layer --\n");
|
||||
printf("-- 基波相量实例 / 每 1 ms 滑动窗刷新 / 双区指针原子交换 instance layer --\n");
|
||||
tc_instances();
|
||||
printf("-- L2-7 dual-zone swap --\n");
|
||||
printf("-- 双区指针原子交换 dual-zone swap --\n");
|
||||
tc_dualzone();
|
||||
printf("-- L2 zero / init --\n");
|
||||
tc_zero();
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
为什么要主机侧单测(而不是只用板端套件):
|
||||
相量算法是纯数学变换,与硬件无关。**板端无信号源**,无法注入已知幅值/相位/频率
|
||||
的标准波形,因而无法验证"读数 = 设定有效值"这一 L2-3 核心判据(与 L1-3 的替代口径
|
||||
的标准波形,因而无法验证"读数 = 设定有效值"这一 相量有效值输出 核心判据(与 STM32 ADC1 启动与扫描 的替代口径
|
||||
同源)。主机侧可合成任意波形并精确构造边界(恰好一周波 / 不足一周波 / 环形回绕 /
|
||||
任意次谐波 / 频偏),且不占 MCU 的 RAM。
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
|
||||
|
||||
设计依据:docs/算法/继电保护功能复现规划.md §3.2(L2-1/2/3/4/6/7);
|
||||
设计依据:docs/算法/继电保护功能复现规划.md §3.2(全周傅氏系数表 等);
|
||||
docs/算法/傅氏算法详解.md §2.3(系数口径);
|
||||
docs/测试说明/测试开发标准流程.md §4.2(L0.5 主机侧单测)。
|
||||
创建日期:2026-10-10
|
||||
|
||||
Reference in New Issue
Block a user