Files
STM32F4-Base/App/algo/adc_health.c
T
wandering1 593c5d0c84 refactor(编号): 采集链分层 L1/L1.5/L2/L2.5/L3/L4/L5 → 语义名(批次 1)
用户裁定(2026-10-28):L1/L1.5/L2/L2.5/L3 这套层号「谁能看懂」,改用语义名。
映射(同一批定稿,含 L4/L5):
  L1   → 语义取数与有效性
  L1.5 → 标定折算
  L2   → 全周傅氏相量
  L2.5 → 测频与遥测快照
  L3   → 保护判据
  L4   → 出口与记录
  L5   → 动作留痕

实测规模:384 站点 / 62 文件(App/algo、App/task、test/host 六个套件、docs/算法、
产品需求、架构总览、启动顺序、ADC 采样节拍、IEC104 上行、采集短板评测、内存管理、
AGENTS.md、README.md、docs/README.md、.pre-commit-config.yaml 与 scripts 里的注释)。

改法(不是全局替换,逐模式有上下文规则):
- 先吃同位语再落裸词:L1 有效性校验 → 语义取数与有效性校验、L2 相量层 → 全周傅氏
  相量层、L3 保护 → 保护判据、L2.5 遥测快照 → 测频与遥测快照,避免「保护判据保护判据」
  这类重复(第一版就是这么坏掉的,已整体回滚重做)。
- .c/.h 只改注释:先把字符串字面量切出去(引号数为奇数的跨行注释按整行处理),
  C 字符串里的 L2-in ring 之类原样保留——AGENTS 铁律「字符串字面量必须纯 ASCII」。
- 层内条目号(L1-3 / L2-3 这类)**本批不动**,属批次 2;另外三族(UI 菜单层级
  L0~L3、链路延迟分段 L1~L8、CubeMX 防丢失三层)也不动,属批次 3。
- 顺手修的措辞:范围写法 L1~L3 → 「分层(语义取数与有效性 → 保护判据)」、
  CI 段「不含 L1/L2/L3」→「不含采集链分层」、目录地图里同一处并列表述。

验证:@build 0 错 0 警(Code=236456,与改前逐字节同尺寸——纯注释改动);
内存布局配图已按新脚本重生成(自检通过);doc-links --all --sections-strict 通过
(195 文档);mojibake / bom-guard / coding-standard / 新编号门禁自查 全 rc=0;
git diff --check 干净。
2026-10-28 13:04:02 +08:00

330 lines
12 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.
/*
* 模块名称:ADC 采样有效性校验(语义取数与预处理)
*(保护功能清单见 docs/算法/继电保护功能复现规划.md)
* 模块功能:见 adc_health.h 文件头。本文件为**纯逻辑实现**:输入计数快照、
* 输出故障位/事件位,零平台依赖(仅 stdint.h/string.h 标准 C)→
* 主机侧免桩单测。
* 适用平台:STM32F407ZGTx + TPAFE5160
* 作者:王建锋
* 创建日期:2026-10-11
* 修改记录:
* v1.0 2026-10-11 王建锋 创建:采样有效性校验
* v1.1 2026-10-21 王建锋 新增 adc_health_age_stale():无模块状态的帧年龄
* 纯判据(参照系由调用方自存),供 protectTask 的
* 兜底新鲜度门使用,见 adc_health.h 同名声明
* v1.2 2026-10-25 DTU 项目组 新增 采样通道自检 残余:码值越界(贴轨)观察器
* (rail_watch/rail_sticky/rail_init),判据与释放
* 沿理由见 adc_health.h 采样通道自检 段注释
*/
#include "adc_health.h"
#include <string.h> /* memset(采样通道自检 贴轨观察器数组复位) */
/* ===================== 内部状态 ===================== */
/* 基线是否已建立:首次 check() 只记基线、不判定(开机首拍无参照系) */
static uint8_t s_primed;
/* 上次看到的计数(差值法判"有无前进",回绕安全) */
static uint32_t s_tpafe_seq;
static uint32_t s_tpafe_frst;
static uint32_t s_stm32_seq;
static uint32_t s_stm32_errs;
static uint32_t s_stm32_gaps;
static uint32_t s_stm32_hw;
/* 两侧"最后一次看到序号前进"的时刻(判静默) */
static uint32_t s_tpafe_adv_ms;
static uint32_t s_stm32_adv_ms;
/* 故障位 */
static uint32_t s_flags; /* 电平位(stall 可自愈;backward 锁存在此) */
static uint32_t s_sticky; /* 锁存位(含电平位历史并集) */
static uint32_t s_tpafe_gaps;
/* 采样通道自检 贴轨观察器(每通道独立小状态机,单写者 = adcTask) */
typedef struct {
uint16_t run_hi; /* 连续贴上轨计数(饱和 0xFFFF) */
uint16_t run_lo; /* 连续贴下轨计数 */
uint8_t seen_mid; /* 见过中带样本(释放沿)——锁存的必要前提 */
uint8_t latched; /* 已报过边沿(不重复报) */
} adc_rail_t;
static adc_rail_t s_rail[ADC_RAIL_CH_NUM];
static uint32_t s_rail_sticky;
/* ===================== 对外接口 ===================== */
/*
* 函数功能:采样有效性校验初始化(幂等)
* 入口参数:无
* 返 回 值:无
* 限定条件:无(内核启动前可调)
* 函数说明:清全部内部状态;判定从下一次 check() 的首拍基线重新开始。
*/
void adc_health_init(void) {
adc_health_rail_init();
s_primed = 0u;
s_tpafe_seq = 0u;
s_tpafe_frst = 0u;
s_stm32_seq = 0u;
s_stm32_errs = 0u;
s_stm32_gaps = 0u;
s_stm32_hw = 0u;
s_tpafe_adv_ms = 0u;
s_stm32_adv_ms = 0u;
s_flags = 0u;
s_sticky = 0u;
s_tpafe_gaps = 0u;
}
/*
* 函数功能:按计数快照做一次有效性判定
* 入口参数:in - 计数快照;NULL 则本次跳过(out 不动)
* out - 判定结果;NULL 则仅更新内部状态
* 返 回 值:无
* 限定条件:仅任务上下文(与 adc_input_poll() 同节奏,每拍一次)
* 函数说明:判定顺序 = 建基线 → 序号差值(倒退/丢帧/前进)→ 静默时长 →
* 错误计数增量。events 仅本次有效;flags/sticky 为持续状态。
*/
void adc_health_check(const adc_health_in_t *in, adc_health_status_t *out) {
uint32_t events = 0u;
if (in == 0) {
return;
}
/* 1) 首拍基线:只记录,不判定(开机时序号已非 0,无"上一拍"参照) */
if (s_primed == 0u) {
s_primed = 1u;
s_tpafe_seq = in->tpafe_seq;
s_tpafe_frst = in->tpafe_frst_err;
s_stm32_seq = in->stm32_seq;
s_stm32_errs = in->stm32_errs;
s_stm32_gaps = in->stm32_gaps;
s_stm32_hw = in->stm32_hw_errs;
s_tpafe_adv_ms = in->now_ms;
s_stm32_adv_ms = in->now_ms;
} else {
uint32_t d; /* 序号差值(两侧共用,cppcheck:作用域收在本块) */
/* 2) TPAFE 序号差值:倒退 = 单调性破坏(锁存);>1 = 丢帧(事件 + 计数) */
d = in->tpafe_seq - s_tpafe_seq;
if ((int32_t)d < 0) {
s_flags |= ADC_HF_SEQ_BACKWARD;
s_sticky |= ADC_HF_SEQ_BACKWARD;
s_tpafe_seq = in->tpafe_seq; /* 重新对齐,避免持续误判 */
s_tpafe_adv_ms = in->now_ms;
} else {
if (d > 1u) {
s_tpafe_gaps += (d - 1u);
events |= ADC_HE_TPAFE_GAP;
}
if (d >= 1u) {
s_tpafe_seq = in->tpafe_seq;
s_tpafe_adv_ms = in->now_ms;
}
}
/* 3) STM32 序号差值:同一口径(gap 计数在 adc_input 侧另有细账,
* 这里只取"前进/倒退/静默"三态,避免两处判据打架) */
d = in->stm32_seq - s_stm32_seq;
if ((int32_t)d < 0) {
s_flags |= ADC_HF_SEQ_BACKWARD;
s_sticky |= ADC_HF_SEQ_BACKWARD;
s_stm32_seq = in->stm32_seq;
s_stm32_adv_ms = in->now_ms;
} else if (d >= 1u) {
s_stm32_seq = in->stm32_seq;
s_stm32_adv_ms = in->now_ms;
}
/* 4) 静默判定(电平位,可自愈):距最后一次前进 >= 门限 */
if ((uint32_t)(int32_t)(in->now_ms - s_tpafe_adv_ms) >= ADC_HEALTH_STALL_MS) {
s_flags |= ADC_HF_TPAFE_STALL;
s_sticky |= ADC_HF_TPAFE_STALL;
} else {
s_flags &= ~ADC_HF_TPAFE_STALL;
}
if ((uint32_t)(int32_t)(in->now_ms - s_stm32_adv_ms) >= ADC_HEALTH_STALL_MS) {
s_flags |= ADC_HF_STM32_STALL;
s_sticky |= ADC_HF_STM32_STALL;
} else {
s_flags &= ~ADC_HF_STM32_STALL;
}
/* 5) 错误计数增量(事件 + 锁存):三类计数正常应恒为 0 增量 */
if (in->tpafe_frst_err != s_tpafe_frst) {
events |= ADC_HE_FRSTDATA;
s_sticky |= ADC_HF_FRSTDATA;
s_tpafe_frst = in->tpafe_frst_err;
}
if (in->stm32_errs != s_stm32_errs) {
events |= ADC_HE_STM32_ERR;
s_sticky |= ADC_HF_STM32_ERR;
s_stm32_errs = in->stm32_errs;
}
if (in->stm32_gaps != s_stm32_gaps) {
events |= ADC_HE_STM32_GAP;
s_stm32_gaps = in->stm32_gaps;
}
if (in->stm32_hw_errs != s_stm32_hw) {
events |= ADC_HE_HW_ERR;
s_sticky |= ADC_HF_HW_ERR;
s_stm32_hw = in->stm32_hw_errs;
}
}
if (out != 0) {
out->flags = s_flags;
out->events = events;
out->sticky = s_sticky;
out->tpafe_gaps = s_tpafe_gaps;
out->tpafe_stall_ms =
((s_flags & ADC_HF_TPAFE_STALL) != 0u)
? (uint32_t)(int32_t)(in->now_ms - s_tpafe_adv_ms)
: 0u;
}
}
/*
* 函数功能:帧年龄判定(无模块状态,保护侧兜底门专用)
* 入口参数:见 adc_health.h 同名声明
* 返 回 值:1 - 已陈旧;0 - 新鲜或仍在建立基线
* 限定条件:任意任务上下文;不读写本模块任何 static(与 check() 的关键区别)
* 函数说明:三步——建基线 → 有前进即刷新参照系 → 原地不动才量"距最后一次前进"。
* 倒退也按"有变化"处理(本函数不判单调性,那归 ADC_HF_SEQ_BACKWARD)。
*/
uint8_t adc_health_age_stale(adc_health_age_t *a_p_age, uint32_t a_cur_seq,
uint32_t a_now_ms, uint32_t a_age_ms) {
if (a_p_age == 0) {
return 0u;
}
/* 1) 首拍建基线:此刻序号往往已非 0(TIM3 比调度器早约 100 ms 就在生产帧),
* 没有"上一拍"就没有参照系——开机窗口内绝不允许把保护锁掉 */
if (a_p_age->primed == 0u) {
a_p_age->primed = 1u;
a_p_age->seq = a_cur_seq;
a_p_age->adv_ms = a_now_ms;
return 0u;
}
/* 2) 有任何前进(含跨多帧、含跨回绕、含倒退)即算新鲜,并刷新参照系 */
if (a_cur_seq != a_p_age->seq) {
a_p_age->seq = a_cur_seq;
a_p_age->adv_ms = a_now_ms;
return 0u;
}
/* 3) 序号原地不动:只看"距最后一次前进多久",含等号(与 stall 判据同边界) */
if ((uint32_t)(int32_t)(a_now_ms - a_p_age->adv_ms) >= a_age_ms) {
return 1u;
}
return 0u;
}
/*
* 函数功能:读当前电平故障位
* 入口参数:无
* 返 回 值:见 adc_health.h ADC_HF_TPAFE_STALL/STM32_STALL/SEQ_BACKWARD
* 限定条件:无
* 函数说明:与 check() 出参 flags 同源。
*/
uint32_t adc_health_flags(void) {
return s_flags;
}
/*
* 函数功能:读锁存故障位
* 入口参数:无
* 返 回 值:电平位 + FRSTDATA/STM32_ERR/HW_ERR 的并集
* 限定条件:无
* 函数说明:出现过即留痕,直到 adc_health_init() 才清。
*/
uint32_t adc_health_sticky(void) {
return s_sticky;
}
/*
* 函数功能:读 TPAFE 累计丢帧点数
* 入口参数:无
* 返 回 值:序号前进 >1 的超出部分累计
* 限定条件:无
* 函数说明:与 STM32 侧 adc_input_stm32_gaps() 对称的 TPAFE 侧细账。
*/
uint32_t adc_health_tpafe_gaps(void) {
return s_tpafe_gaps;
}
/* ===================== 采样通道自检 贴轨观察器实现 ===================== */
/*
* 函数功能:贴轨观察器复位(幂等;adc_health_init() 已内含)
* 入口参数:无
* 返 回 值:无
* 限定条件:与 check() 同为单写者任务上下文
*/
void adc_health_rail_init(void) {
(void)memset(s_rail, 0, sizeof(s_rail));
s_rail_sticky = 0u;
}
/*
* 函数功能:单通道单拍贴轨观察(判据与释放沿前提见 adc_health.h 同名声明)
* 入口参数:ch / code / lo_th / hi_th - 见 adc_health.h
* 返 回 值:1 - 本拍新锁存(边沿);0 - 无新事件(含 ch 越界忽略) uint32_t
* 限定条件:每拍每通道至多一次;纯算术、零平台依赖
*/
uint32_t adc_health_rail_watch(uint8_t ch, int32_t code, int32_t lo_th, int32_t hi_th) {
adc_rail_t *p;
if (ch >= (uint8_t)ADC_RAIL_CH_NUM) {
return 0u;
}
p = &s_rail[ch];
if (code >= hi_th) { /* 贴上轨带:清对侧计数 */
p->run_lo = 0u;
if (p->run_hi < 0xFFFFu) {
p->run_hi++;
}
if ((p->run_hi >= (uint16_t)ADC_RAIL_N_RUN) && (p->seen_mid != 0u)
&& (p->latched == 0u)) {
p->latched = 1u;
s_rail_sticky |= (1uL << ch);
return 1u;
}
return 0u;
}
if (code <= lo_th) { /* 贴下轨带(对称) */
p->run_hi = 0u;
if (p->run_lo < 0xFFFFu) {
p->run_lo++;
}
if ((p->run_lo >= (uint16_t)ADC_RAIL_N_RUN) && (p->seen_mid != 0u)
&& (p->latched == 0u)) {
p->latched = 1u;
s_rail_sticky |= (1uL << ch);
return 1u;
}
return 0u;
}
/* 中带:记释放沿(锁存的必要前提),并清两侧连续计数 */
p->seen_mid = 1u;
p->run_hi = 0u;
p->run_lo = 0u;
return 0u;
}
/*
* 函数功能:读贴轨锁存位图
* 入口参数:无
* 返 回 值:bit ch = 曾检出贴轨(低 9 位有效) uint32_t
* 限定条件:无
*/
uint32_t adc_health_rail_sticky(void) {
return s_rail_sticky;
}