/* * 模块名称:遥测快照发布层(全周傅氏相量派生·发布侧,「数据可信」§4.5) * 模块功能:实现见 meas.h——200 ms 基波有效值 + 品质快照,双区无锁读。 * 本文件纯算法:输入 = sample_ring 取窗 + 调用方注入(频率/削顶位图), * 无 RTOS / 无寄存器 / 无日志(「数据可信」层内禁令:本目录不写日志)。 * 双区不变量(docs/任务规划/数据可信.md §4.5,双区指针原子交换 同型,先例 = App/algo/ *(保护功能清单见 docs/算法/继电保护功能复现规划.md) * phasor.c 的双区注释):写侧只碰**非使用区**,全部字段填完后一次 * volatile 单字节 store 翻转使用区;读者先取索引再整体拷贝 → 拷贝期 * 间写者不可能改读者所在区(两帧间隔 200 ms 远大于 72 B 拷贝)。 * 单核顺序执行,无需内存屏障;volatile 阻编译器把字段写入挪到翻转后。 * 适用平台:STM32F407ZGTx(Cortex-M4F,硬件 FPU) * 作者:DTU 项目组 * 创建日期:2026-10-25(道D 遥测快照发布) * 修改记录: * v1.0 2026-10-25 DTU 项目组 创建:200 ms 遥测快照 + 双区无锁读 */ #include "meas.h" #include #include "phasor.h" /* phasor_calc():基波 DFT 纯函数核(与使用区同核) */ #include "sample_ring.h" /* sample_ring_window():以本拍为终止的一周波窗 */ /* ===== 双区(计算区 / 使用区),口径同 App/algo/phasor.c 的 双区指针原子交换===== */ static meas_snap_t s_ms_snap[2]; static volatile uint8_t s_ms_use; static uint8_t s_ms_ready; /* 使用区是否已产出过有效结果 */ static uint32_t s_ms_seq; /* 只增帧号(写者单写者,读者原子单字读) */ /* * 函数功能:清零一份快照(含 rsv 与全部数组元素) * 入口参数:p - 目标快照 * 返 回 值:无 * 限定条件:p 非空 * 函数说明:静态辅助;逐字段清零(不依赖 memset,行为与本目录 phasor_zero 同型)。 */ static void meas_zero(meas_snap_t *p) { uint8_t i; p->seq = 0u; p->tick_ms = 0u; p->valid = 0u; p->rsv[0] = 0u; p->rsv[1] = 0u; p->rsv[2] = 0u; p->freq_valid = 0u; p->freq_hz = 0.0f; for (i = 0u; i < (uint8_t)ADC_SEM_COUNT; i++) { p->yc[i] = 0.0f; p->qds[i] = 0u; } } /* * 函数功能:遥测快照层初始化 * 入口参数:无 * 返 回 值:0 - 成功 * 限定条件:内核启动前或任务上下文皆可;幂等,可重复调用 * 函数说明:清双区 + 索引/就绪/序号复位。init 后到首次发布前,meas_get() 恒 * 返 -2(out 清零)、meas_seq() 恒返 0。 */ int meas_init(void) { meas_zero(&s_ms_snap[0]); meas_zero(&s_ms_snap[1]); s_ms_use = 0u; s_ms_ready = 0u; s_ms_seq = 0u; return 0; } /* * 函数功能:一拍遥测发布(被调即发布,节拍把关在调用方) * 入口参数:tick_ms - 本拍时基(毫秒) uint32_t * freq_valid_in - 频率读数新鲜度(freq_valid() 值) uint8_t * freq_hz_in - 频率读数(freq_last() 值) float * clip_mask - 削顶通道位图(bit s = 语义通道 s,来源唯一 = * adc_cal_flags(),编排方注入) uint32_t * 返 回 值:无 * 限定条件:仅 adcTask 上下文、全仓唯一调用点(meas.h 发布纪律);须在 * phasor_run() 之后调用(本拍窗含最新点,与使用区同窗同核) * 函数说明:电气 8 路(语义序中 TEMP 之前的全部通道,映射表属主 = * docs/硬件平台/ADC通道映射说明.md)逐路一周波基波 DFT;任一路取窗 * 不足(开机未满一周波)→ 该路 IV|NT + 整帧 valid=0,但照常发布 * (品质节拍性刷新,见 meas_tick 头注)。TEMP 只取最新一点透传 + * NT(「数据可信」决策 5)。品质值不因 IV 清零——"值在场但不可信" * 与"值缺失"分开,消费者按 qds 裁决。 */ void meas_tick(uint32_t tick_ms, uint8_t freq_valid_in, float freq_hz_in, uint32_t clip_mask) { meas_snap_t *z = &s_ms_snap[1u - s_ms_use]; /* 计算区 = 非使用区 */ float win[PHASOR_N]; phasor_t p; uint8_t all_ok = 1u; uint8_t s; meas_zero(z); z->seq = s_ms_seq + 1u; z->tick_ms = tick_ms; for (s = 0u; s < (uint8_t)ADC_SEM_TEMP; s++) { if (sample_ring_window((adc_semantic_t)s, (uint8_t)PHASOR_N, win) == 0) { phasor_calc(win, (uint8_t)PHASOR_N, 1u, &p); z->yc[s] = p.fAmSQ; /* 有效值口径(系数已折算,同 phasor 层) */ if ((clip_mask & (1uL << s)) != 0u) { z->qds[s] |= (uint16_t)MEAS_QDS_CLIPPED; } } else { /* 本路窗不足:值缺位(yc 保持 0)+ IV|NT;整帧判不可信 */ z->qds[s] = (uint16_t)(MEAS_QDS_IV | MEAS_QDS_NT); all_ok = 0u; } } /* TEMP:非交流量语义(「数据可信」决策 5),最新一点原始帧值透传 + NT; * clip_mask 位 8 不参与(adc_cal_flags(TEMP) 恒 0,无削顶语义) */ { float tv = 0.0f; if (sample_ring_window(ADC_SEM_TEMP, 1u, &tv) == 0) { z->yc[ADC_SEM_TEMP] = tv; z->qds[ADC_SEM_TEMP] = (uint16_t)MEAS_QDS_NT; } else { z->qds[ADC_SEM_TEMP] = (uint16_t)(MEAS_QDS_IV | MEAS_QDS_NT); } } z->valid = all_ok; z->freq_valid = (freq_valid_in != 0u) ? 1u : 0u; z->freq_hz = freq_hz_in; /* 全部字段就绪后才翻索引 + 置就绪(顺序即协议,勿交换) */ s_ms_seq = z->seq; s_ms_use = (uint8_t)(1u - s_ms_use); s_ms_ready = 1u; } /* * 函数功能:读当前使用区快照(无锁整体拷贝) * 入口参数:out - 输出快照;失败路径同样清零填充 * 返 回 值:0 - 成功;-1 - 参数错;-2 - 从未发布过 int * 限定条件:任意上下文;无(读 volatile 索引 + 普通拷贝) * 函数说明:先取索引再索引拷贝,避免拷贝中途索引被翻转导致"半旧半新"。 * 返回 0 时帧内部自洽(同一发布拍的 seq/tick_ms/yc/qds 成套)。 */ int meas_get(meas_snap_t *out) { uint8_t z; if (out == NULL) { return -1; } if (s_ms_ready == 0u) { meas_zero(out); return -2; } z = s_ms_use; *out = s_ms_snap[z]; return 0; } /* * 函数功能:读已发布帧数(只增) * 入口参数:无 * 返 回 值:seq,0 = 从未发布(单字读,原子) uint32_t * 限定条件:任意上下文 * 函数说明:轻量"是否换帧"探针,正式取数以 meas_get() 为准。 */ uint32_t meas_seq(void) { return s_ms_seq; }