Files
STM32F4-Base/App/algo/phasor.c
T
wandering1 5a9988b557 chore(注释): 清掉源码里不可检索的过程编码
清掉的(读者无从解析、且所指文档已不存在)
- 已删任务文档的编号:Task #32、其内部 §5 / §4.3、P1-5、P0-2、P1-4a、
  "方案 a"、"Lead 批准 2026-10-12"
- 正文里当代词用的文件版本号:"(v2.7 起为 AboveNormal)""已随 v2.0 事件
  驱动改造删除""(v2.0:DMA 消费)""见文件头 v1.3 说明"等 → 改写成陈述现状
- 测试与脚本同类:adc_input 桩头"v2.0:DMA 模型"、stub/adc.h 的 v2.0、
  check_cubemx_survival.py 段头的"2026-10-10 v2.8"、run_all_suites.py 的"方案 A"

刻意保留
- 可检索登记簿锚点:P-AUDIT 真条目(P0-1/P1-8/P2-1/P2-9/P3-11)、S6/S11、
  E-31/E-44、N-18/N-23/N-32、B1/B5/B7/B8、L1-x~L5-x 功能号
- 文件头修改记录块内的 vN.M(规范 V1.0 §2.1/§2.2 要求保留该块;块内编号
  在本文件内自洽可读)

顺带纠正注释与代码已经不一致的过时事实
- 仍把 TIM6 写成现行节拍源:Src/main.c 的 include 注释与回调注释、
  Src/freertos.c 的 adcTask 块 → 改 TIM3;adc_input.h 修订条目里"ADC1 只认
  TIM2/TIM3/TIM8 的 TRGO"不准确 → 换成指向判据(ADC采样节拍设计说明 §3.2)
- App/algo/phasor.c 双区注释称"唯一消费者是 adcTask、为后续保护任务预留",
  实际 protectTask 已跨上下文读(prot_elm_run -> phasor_get)→ 改为"读者跨
  adcTask 心跳与 protectTask 判据两个上下文,勿改加锁、勿取消双区",并同步
  修正 docs/算法/采集与算法层设计说明.md 同一句
- adc_tick_irq_notify() 的"拒绝 ISR 内计算"理由仍写"内含 2 ms 忙等"(DMA 化
  后已无忙等)→ 换成三条真实理由:任务级共享数据 / 计算量 / 生成区回调
- 裸 §5(指代不明)与"规划 §1.6 / 规划 §4"昵称式 → 写成完整路径紧邻式

字节卫生(Edit 会静默剥 BOM)
- App/task/ui_task.c 与 Drivers/BSP/UI/ui_net_sink.c 被剥掉 BOM 两次,均按
  3 字节 EF BB BF 前缀补回并复验;全部 20 个文件 CR=0、U+FFFD=0、无行尾空白

门禁
- @build 0 错误 0 警告,且 Program Size 与清理前逐项相同
  (Code=213356 RO=18580 RW=1184 ZI=131480)→ 纯注释改动、中文未被误重编码
- ui-golden 首跑红:新工作树缺被 gitignore 的黄金参考 simulator/tests/ref/
  (不入库、只在主工作树生成过);从主工作树复制该基线后复跑通过 → 注释改动
  零像素影响
- 全套 pre-commit 在这 20 个文件上 Passed(coding-standard / cppcheck /
  6 个主机单测 / doc-links / task-table / mem-numbers / cubemx-survival /
  registration-check / mem-budget / font-charset / ui-golden / id-check)
- id-check 可判定比例 82.71% -> 82.88%(删掉的是"形似 P-AUDIT 但查无定义"的
  假锚点),悬空引用 863 -> 854
2026-10-21 11:39:53 +08:00

206 lines
7.8 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 相量计算)
* 模块功能:全周傅氏算法(单频 DFT)——系数表预乘 √2/N,fAmSQ 直接即有效值。
* 覆盖 L2-1 系数表 / L2-2 实例 / L2-3 有效值 / L2-6 滑动窗 / L2-7 双区交换。
* 适用平台:STM32F407ZGTx(Cortex-M4F,硬件 FPU)
* 作者:王建锋
* 创建日期:2026-10-10
* 修改记录:
* v1.0 2026-10-10 王建锋 创建:L2-1/2/3/6/7
*/
#include "phasor.h"
#include <math.h>
#include <stddef.h>
#include "sample_ring.h"
/* ===== L2-1 全周傅氏系数表 =====
* g_fFFTSin20[k] = sin(2πk/N)·(√2/N)
* g_fFFTCos20[k] = cos(2πk/N)·(√2/N) N = PHASOR_N = 20
* 权威口径:docs/算法/傅氏算法详解.md §2.3("把 √2 藏进系数表")。
* 本表为**离线生成**的 float 常量(不在运行期调 sinf/cosf):确定、无 libm 精度依赖。
* 主机侧单测 test/host/phasor/ 会**逐项按上式复核本表**(防转录错),
* 并以合成正弦做端到端复核(读数误差 ≤ 1%)。
* 精确零点(sin(π)=0、cos(π/2)=0)直接写 0.0f,不保留浮点噪声。 */
static const float k_fft_sin[PHASOR_N] = {
+0.000000000e+00f, +2.185080200e-02f, +4.156269506e-02f, +5.720613897e-02f, +6.724984944e-02f,
+7.071068138e-02f, +6.724984944e-02f, +5.720613897e-02f, +4.156269506e-02f, +2.185080200e-02f,
+0.000000000e+00f, -2.185080200e-02f, -4.156269506e-02f, -5.720613897e-02f, -6.724984944e-02f,
-7.071068138e-02f, -6.724984944e-02f, -5.720613897e-02f, -4.156269506e-02f, -2.185080200e-02f,
};
static const float k_fft_cos[PHASOR_N] = {
+7.071068138e-02f, +6.724984944e-02f, +5.720613897e-02f, +4.156269506e-02f, +2.185080200e-02f,
+0.000000000e+00f, -2.185080200e-02f, -4.156269506e-02f, -5.720613897e-02f, -6.724984944e-02f,
-7.071068138e-02f, -6.724984944e-02f, -5.720613897e-02f, -4.156269506e-02f, -2.185080200e-02f,
+0.000000000e+00f, +2.185080200e-02f, +4.156269506e-02f, +5.720613897e-02f, +6.724984944e-02f,
};
/* √2:幅值 = √2 × 有效值(与系数表的 √2 折算互逆) */
#define PHASOR_SQRT2 1.4142135623730951f
/* ===== L2-2 相量实例表 =====
* 参考项目为 **4 个实例**(C01 基波 I/U、C02 零序基波、C03 二次谐波、C04 五次谐波,
* `APP/CLS100_Dsp/InstCalCell.c`)。本项目按 V0.1 **有效通道**(`UA`/`UN`/`IA`,
* 见 docs/算法/继电保护功能复现规划.md §1.6:`UB/UC/IB/IC/IN` 无中点偏置 → 削顶不可用)展开——C01 的"I/U"
* 拆为两行(IA 电流 / UA 电压),并补出 C04 的五次谐波轨(L1-9)。
* 新增实例只需在表尾加一行,其余代码自动适配。 */
static const phasor_inst_t k_inst[] = {
{ "C01-IA-F1", ADC_SEM_IA, 1u }, /* A 相电流基波(L3-1 过流 / L3-3 过负荷) */
{ "C02-UA-F1", ADC_SEM_UA, 1u }, /* A 相电压基波(L3-4 过压/欠压、L3-5 频率) */
{ "C03-UN-F1", ADC_SEM_UN, 1u }, /* 零序电压基波(L3-6 零序过压) */
{ "C04-IA-F2", ADC_SEM_IA, 2u }, /* 二次谐波(L3-7 涌流制动) */
{ "C05-IA-F5", ADC_SEM_IA, 5u }, /* 五次谐波(L1-9 选线) */
};
#define PHASOR_INST_N (sizeof(k_inst) / sizeof(k_inst[0]))
/* ===== L2-7 双区(计算区 / 使用区)=====
* 本拍结果先写入"计算区",全部实例写完后**一次性**切换 s_use_zone
* (单字节 store,Cortex-M 上原子),消费侧读"使用区" → 无锁、无撕裂。
* s_use_zone 声明 volatile:阻止编译器把结果写入挪到切换之后
* (本平台单核顺序执行,无需额外内存屏障)。
* 写者 = adcTask(同拍);读者跨两个上下文:adcTask 心跳与 protectTask 判据
* (prot_elm_run → phasor_get)→ 无锁读正是为后者服务,勿改成加锁或去掉双区。 */
static phasor_t s_zone[2][PHASOR_INST_N];
static volatile uint8_t s_use_zone;
static uint8_t s_ready; /* 使用区是否已产出过有效结果 */
/*
* 函数功能:清零一个相量
* 入口参数:p - 目标相量
* 返 回 值:无
* 限定条件:p 非空
* 函数说明:静态辅助。
*/
static void phasor_zero(phasor_t *p) {
p->fRe = 0.0f;
p->fIm = 0.0f;
p->fAm = 0.0f;
p->fAmSQ = 0.0f;
}
/*
* 函数功能:对 n 点窗口算 harm 次相量(L2-1 系数表 + L2-3 有效值)
* 入口参数:x - 采样窗(按时间序,旧 → 新)
* n - 窗长(必须等于 PHASOR_N)
* harm - 谐波次数(>=1)
* out - 输出相量
* 返 回 值:无
* 限定条件:无
* 函数说明:纯函数。索引步进 = 谐波次数,故同一张表可算任意次谐波。
*/
void phasor_calc(const float *x, uint8_t n, uint8_t harm, phasor_t *out) {
float re = 0.0f;
float im = 0.0f;
uint8_t i;
if ((x == NULL) || (out == NULL) || (n != (uint8_t)PHASOR_N) || (harm == 0u)) {
return;
}
for (i = 0u; i < n; i++) {
uint8_t idx = (uint8_t)(((unsigned int)i * harm) % n);
re += x[i] * k_fft_sin[idx];
im += x[i] * k_fft_cos[idx];
}
out->fRe = re;
out->fIm = im;
out->fAmSQ = sqrtf((re * re) + (im * im)); /* 系数已折算 √2/N → 直接即有效值 */
out->fAm = out->fAmSQ * PHASOR_SQRT2; /* 幅值 = √2 × 有效值 */
}
/*
* 函数功能:相量层初始化
* 入口参数:无
* 返 回 值:0 - 成功
* 限定条件:内核启动前调用
* 函数说明:清双区结果与有效标志,并复位采样环缓冲;可重复调用(幂等)。
*/
int phasor_init(void) {
uint8_t i;
for (i = 0u; i < (uint8_t)PHASOR_INST_N; i++) {
phasor_zero(&s_zone[0][i]);
phasor_zero(&s_zone[1][i]);
}
s_use_zone = 0u;
s_ready = 0u;
sample_ring_init();
return 0;
}
/*
* 函数功能:取相量实例数(L2-2)
* 入口参数:无
* 返 回 值:实例个数
* 限定条件:无
* 函数说明:供遍历 / 日志打印。
*/
uint8_t phasor_inst_count(void) {
return (uint8_t)PHASOR_INST_N;
}
/*
* 函数功能:取相量实例描述
* 入口参数:idx - 实例下标
* 返 回 值:指向静态实例表的指针;idx 越界返回 NULL
* 限定条件:无
* 函数说明:返回静态存储地址,调用方不得修改。
*/
const phasor_inst_t *phasor_inst(uint8_t idx) {
if (idx >= (uint8_t)PHASOR_INST_N) {
return NULL;
}
return &k_inst[idx];
}
/*
* 函数功能:重算全部实例 + 双区原子切换(L2-6 / L2-7)
* 入口参数:无
* 返 回 值:无
* 限定条件:仅任务上下文调用;须先 sample_ring_push_frame() 压入本拍数据
* 函数说明:任一实例数据不足一周波 → 本拍整体不产出(不切换使用区),
* 避免出现"部分新部分旧"的混合结果。
*/
void phasor_run(void) {
float win[PHASOR_N];
uint8_t w = (uint8_t)(1u - s_use_zone); /* 计算区 = 非使用区 */
uint8_t i;
for (i = 0u; i < (uint8_t)PHASOR_INST_N; i++) {
if (sample_ring_window(k_inst[i].sem, (uint8_t)PHASOR_N, win) != 0) {
return; /* 尚未攒满一周波:本拍不产出 */
}
phasor_calc(win, (uint8_t)PHASOR_N, k_inst[i].harm, &s_zone[w][i]);
}
s_use_zone = w; /* 原子切换(单字节 store) */
s_ready = 1u;
}
/*
* 函数功能:读实例结果(使用区,无锁)
* 入口参数:idx - 实例下标
* out - 输出相量
* 返 回 值:0 - 成功;-1 - 参数错;-2 - 尚无有效结果
* 限定条件:无
* 函数说明:先取一次使用区下标再索引,避免读写期间下标变化。
*/
int phasor_get(uint8_t idx, phasor_t *out) {
uint8_t z;
if ((out == NULL) || (idx >= (uint8_t)PHASOR_INST_N)) {
return -1;
}
if (s_ready == 0u) {
return -2;
}
z = s_use_zone;
*out = s_zone[z][idx];
return 0;
}