929cacbd72
- 设计说明第 5 节已详述「为什么需要 / 设计思想 / start 推导」,函数内不再复述: 环内布局示意、两条坑的逐句论证、取值范围推导全部删去,改为一句结论 + 指针 (docs/算法/采集与算法层设计说明.md §5 与 §5.3)。 - 留在代码里的都是「就地可判」的落地要点:通道号为何先转 unsigned、 n 的两条拒收理由、-2 与 -1 的语义区分、先加 DEPTH 的同余理由、逐点取模。 - .h 的指针从 .c 改指设计说明,避免同一事实两处详述。 - 净减 81 行;去注释后代码 token 与 HEAD 逐字相同;@build 0 错 0 警。
139 lines
5.3 KiB
C
139 lines
5.3 KiB
C
/*
|
||
* 模块名称:采样环缓冲(全周傅氏相量计算的输入)
|
||
* 模块功能:按语义通道保存最近 2 个周波(40 点)采样值,供 全周傅氏相量计算按窗取数。
|
||
* 适用平台:STM32F407ZGTx(纯算法,无寄存器/无 RTOS 依赖)
|
||
* 作者:王建锋
|
||
* 创建日期:2026-10-10
|
||
* 修改记录:
|
||
* v1.0 2026-10-10 王建锋 创建:全周傅氏相量输入环缓冲(20 点/周波 × 2 周波)
|
||
*/
|
||
|
||
#include "sample_ring.h"
|
||
|
||
#include <stddef.h>
|
||
|
||
/* 采样环:[语义通道][点]。按语义通道索引(0..ADC_SEM_COUNT-1),
|
||
* 故消费侧"按语义取数",不依赖任何物理通道号或转换器归属。 */
|
||
static float s_ring[ADC_SEM_COUNT][SAMPLE_RING_DEPTH];
|
||
/* 写指针:下一个待写入的点下标(0..SAMPLE_RING_DEPTH-1) */
|
||
static uint16_t s_head;
|
||
/* 已累积点数(饱和于 SAMPLE_RING_DEPTH) */
|
||
static uint16_t s_filled;
|
||
/* 累计入环帧数(不饱和,供心跳/丢帧观测) */
|
||
static uint32_t s_frames;
|
||
|
||
/*
|
||
* 函数功能:采样环缓冲初始化
|
||
* 入口参数:无
|
||
* 返 回 值:无
|
||
* 限定条件:无
|
||
* 函数说明:清空全部通道历史与计数;可重复调用(幂等)。
|
||
*/
|
||
void sample_ring_init(void) {
|
||
uint8_t c;
|
||
uint16_t i;
|
||
|
||
for (c = 0u; c < (uint8_t)ADC_SEM_COUNT; c++) {
|
||
for (i = 0u; i < SAMPLE_RING_DEPTH; i++) {
|
||
s_ring[c][i] = 0.0f;
|
||
}
|
||
}
|
||
s_head = 0u;
|
||
s_filled = 0u;
|
||
s_frames = 0u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:压入一拍(一帧)全部语义通道的采样值
|
||
* 入口参数:values - 按语义通道顺序排列的一帧采样值(长度须 = ADC_SEM_COUNT)
|
||
* n - 实际长度(必须等于 ADC_SEM_COUNT)
|
||
* 返 回 值:0 - 成功;-1 - 参数错
|
||
* 限定条件:同一帧内各通道须同刻(同族量同刻性由上游 语义取数与有效性负责)
|
||
* 函数说明:整帧写入完毕后**统一**推进写指针——单帧内不存在"半新半旧",
|
||
* 消费侧取到的窗永远落在整帧边界上(无帧内撕裂)。
|
||
*/
|
||
int sample_ring_push_frame(const float *values, uint8_t n) {
|
||
uint8_t c;
|
||
|
||
if ((values == NULL) || (n != (uint8_t)ADC_SEM_COUNT)) {
|
||
return -1;
|
||
}
|
||
|
||
for (c = 0u; c < (uint8_t)ADC_SEM_COUNT; c++) {
|
||
s_ring[c][s_head] = values[c];
|
||
}
|
||
|
||
s_head = (uint16_t)((s_head + 1u) % SAMPLE_RING_DEPTH);
|
||
if (s_filled < SAMPLE_RING_DEPTH) {
|
||
s_filled++;
|
||
}
|
||
s_frames++;
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:取指定语义通道最近 n 点的窗(按时间序,旧 → 新)
|
||
* 入口参数:ch - 语义通道(0 .. ADC_SEM_COUNT-1)
|
||
* n - 取点数(1 .. SAMPLE_RING_DEPTH)
|
||
* out - 输出缓冲(调用方保证长度 >= n,本层不校验容量)
|
||
* 返 回 值:0 - 成功;-1 - 参数错;-2 - 已累积点数不足 n
|
||
* 限定条件:仅 adcTask 上下文(写者与读者同任务串行,故不加锁、不用 volatile);
|
||
* 须在同拍 sample_ring_push_frame() 之后调用。
|
||
* 函数说明:以最新一点为终点、向前回溯 n 点;out[0] 最老、out[n-1] 最新,
|
||
* 与 phasor_run() 的求和方向一致。
|
||
* 为什么需要这一层、环内布局与窗首下标的推导 →
|
||
* docs/算法/采集与算法层设计说明.md §5(本文件只留落地要点)。
|
||
*/
|
||
int sample_ring_window(adc_semantic_t ch, uint8_t n, float *out) {
|
||
uint16_t start;
|
||
uint16_t i;
|
||
|
||
/* 通道号先转 unsigned 再比:枚举底层可为有符号,负值会骗过 ch >= COUNT 的判据。 */
|
||
if ((out == NULL) || ((unsigned int)ch >= (unsigned int)ADC_SEM_COUNT)) {
|
||
return -1;
|
||
}
|
||
/* n = 0 是空窗;n > DEPTH 一个环装不下连续 n 点(回绕会把同一点重复读出)。 */
|
||
if ((n == 0u) || (n > SAMPLE_RING_DEPTH)) {
|
||
return -1;
|
||
}
|
||
/* -2 = 窗还没攒够,调用方跳过本拍即可(首周波启动期必然出现),不是故障。 */
|
||
if (s_filled < (uint16_t)n) {
|
||
return -2;
|
||
}
|
||
|
||
/* 窗首 = s_head - n(s_head 是"下一待写"位置,故窗尾 = s_head - 1)。
|
||
* 必须先加 DEPTH 再减:直接减在 s_head < n 时下溢或得负数,而
|
||
* (x mod 65536) mod 40 不等于 x mod 40、C99 的负数取模也不是同余解。
|
||
* 加 DEPTH 不改同余类,并把中间值锁在 [0, 2·DEPTH - 2]。详见设计说明 §5.3。 */
|
||
start = (uint16_t)((s_head + SAMPLE_RING_DEPTH - (uint16_t)n) % SAMPLE_RING_DEPTH);
|
||
|
||
/* 逐点取模拷贝:窗可能跨环尾,环在内存里首尾不相连,不能用一次 memcpy。 */
|
||
for (i = 0u; i < (uint16_t)n; i++) {
|
||
out[i] = s_ring[ch][(uint16_t)((start + i) % SAMPLE_RING_DEPTH)];
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读已累积点数(饱和于 SAMPLE_RING_DEPTH)
|
||
* 入口参数:无
|
||
* 返 回 值:已累积点数(0..SAMPLE_RING_DEPTH)
|
||
* 限定条件:无
|
||
* 函数说明:供上层判断"数据是否够一周波"。
|
||
*/
|
||
uint16_t sample_ring_filled(void) {
|
||
return s_filled;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读累计入环帧数(不饱和)
|
||
* 入口参数:无
|
||
* 返 回 值:累计入环帧数
|
||
* 限定条件:无
|
||
* 函数说明:供心跳日志 / 丢帧观测(采样有效性校验 判据之一)。
|
||
*(保护功能清单见 docs/算法/继电保护功能复现规划.md)
|
||
*/
|
||
uint32_t sample_ring_frames(void) {
|
||
return s_frames;
|
||
}
|