Files
STM32F4-Base/App/algo/adc_health.h
T
wandering1 04997e48aa refactor(编号): test/host 与 App/Drivers 注释去测试层级号(73 站点 / 64 文件)
同族第二道:代码注释面。41 个 run_unit_test.py 文件头与引用行、common harness 与两份
README、4 个 *_test.c 文件头、App/** 14 处(含 BOM 文件 App/cfg/cfg_file.c)、
GD5F nand_ftl.c 1 处,共 73 站点 / 64 文件;措辞取「主机侧纯逻辑单元测试运行器
(纯标准库)」避免同行「主机侧」重复。

零行为改动(机器证据,不是口头):
- 17 个 .c/.h 剥掉注释后与 HEAD **逐字节相同**;
- 42 个 .py 走 ast.dump 比较、仅归一 docstring(真实字符串字面量参与比较,故旗标或
  输出文本被动过必然报红)→ 全部相等;behaviour_changed=0。
- py_compile 42/42;抽跑 watchdog 47/47、freq 46/46(动手前先取基线,改后同数);
  CR 字节 0、EF BF BD 0、BOM 与 HEAD 逐一一致、check_bom_drift rc=0。

刻意未动:6 处字符串字面量(FTP 应答 "215 UNIX Type: L8"、lftpd_test.c SYST 断言、
console_log_test.c 的 "L1\r\n"、phasor_test.c 的 "L2-in ring" 文案与 printf 标题)——
改了就是改测试输出/行为;UI 设计令牌层 L0(ui_tokens.h、ui_content_page.c)与
test/host/README.md 的算法层号是**另外的族**。

两条 SyntaxWarning(ftp_cfg / net_cfg 文件头 \S 转义)**HEAD 同样存在**,已对 HEAD 副本
复现证明非本次引入,未顺手改。
2026-10-28 17:52:55 +08:00

218 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_input` / `tpafe5160` 驱动提供,本模块只做判定与告警位):
* ① 静默(stall):帧序号连续 >= ADC_HEALTH_STALL_MS 无前进;
* ② 丢帧(gap):帧序号一次前进 >1(消费者超拍,中间帧被覆盖);
* ③ 单调性破坏:帧序号倒退(计数器被改写 / 驱动 bug,锁存);
* ④ 错误计数增量:FRSTDATA 跳变 / HAL ADC·DMA 错误 / STM32 停摆计数。
* ⚠ 本层**纯逻辑**:不碰寄存器、不取数、不打日志——输入是调用方
* 采集的计数快照,输出是故障位;日志与自检上报由消费者
* (`App/task/adc_task.c`)按位执行 → 主机侧可纯逻辑单测。
* 适用平台:STM32F407ZGTx + TPAFE5160
* 作者:王建锋
* 创建日期:2026-10-11
* 修改记录:
* v1.0 2026-10-11 王建锋 创建:采样有效性校验(静默/丢帧/单调性/错误增量)
* v1.1 2026-10-21 王建锋 新增 adc_health_age_stale()(无模块状态的帧年龄
* 纯判据,参照系由调用方自存)与 ADC_HF_STALE 位:
* 判定层的 stall/backward 位只在 adcTask 每拍跑
* check() 时才可能置起,节拍源停摆会把本任务卡在
* osThreadFlagsWait 上 → 那些位永远是 0,而相量冻在
* 最后一帧仍被 protectTask 用于判据。该纯判据让保护侧
* 能用**自己的时基 + 中断侧计数器**独立判"数据还新不新" * v1.2 2026-10-25 DTU 项目组 新增 采样通道自检 残余「码值越界(贴轨)检出」:
* adc_health_rail_watch()/rail_sticky()/rail_init()
* ——连续 N 拍贴满量程带且该通道见过释放沿才锁存
* (空载恒 0 / 开机恒 0 是合法工况,不误报);
* 判据动因 docs/问题记录/采集与数据处理短板评测.md
* §6.2,门限来源 adc_cal_rail_ths()(按 FS 百分比)
*/
#ifndef ADC_HEALTH_H
#define ADC_HEALTH_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* 静默判定门限(ms):帧序号连续无前进达到该时长即判静默。
* 名义节拍 1 kHz → 正常每拍 +1;100 拍无前进不可能是抖动,只可能是
* 节拍源/转换器/EXTI 真停(消费者超拍只会造成 gap=d>1,不会 d=0)。 */
#define ADC_HEALTH_STALL_MS 100U
/* 电平故障位(adc_health_flags(),当前活跃;stall 可自愈清除,backward 锁存)
* ——供后续「自检失败闭锁」直接消费 */
#define ADC_HF_TPAFE_STALL (1uL << 0) /* TPAFE 帧序号静默 */
#define ADC_HF_STM32_STALL (1uL << 1) /* STM32 DMA 完成计数静默 */
#define ADC_HF_SEQ_BACKWARD (1uL << 2) /* 帧序号倒退(锁存,不自愈) */
/* 锁存故障位(adc_health_sticky(),出现过即置位、直到 init 才清;
* 与电平位共用高位编号,gap 类"常态性超拍"不入锁存) */
#define ADC_HF_FRSTDATA (1uL << 3) /* FRSTDATA 跳变计数有增量(锁存) */
#define ADC_HF_STM32_ERR (1uL << 4) /* STM32 停摆计数有增量(锁存) */
#define ADC_HF_HW_ERR (1uL << 5) /* HAL ADC/DMA 错误回调有增量(锁存) */
/* 边沿事件位(adc_health_check() 出参 events,仅本次调用有效)。
* ⚠ FRSTDATA/STM32_ERR/HW_ERR 三个事件位与同名锁存位**编号一致**——
* 消费者可直接用 events 掩码对 sticky/logged 位图做"每类留痕一次"的编排 */
#define ADC_HE_TPAFE_GAP (1uL << 6) /* 本次 TPAFE 序号前进 >1 */
#define ADC_HE_STM32_GAP (1uL << 7) /* 本次 STM32 gap 计数有增量 */
#define ADC_HE_STM32_ERR (1uL << 4) /* 本次 STM32 停摆计数有增量 */
#define ADC_HE_FRSTDATA (1uL << 3) /* 本次 FRSTDATA 计数有增量 */
#define ADC_HE_HW_ERR (1uL << 5) /* 本次 HAL 错误计数有增量 */
/* 兜底新鲜度位(bit8,与判定层 bit0..bit7 位段错开)
* ⚠ 本位**不由本模块置位**:由 protectTask 调 adc_health_age_stale() 独立观测后
* OR 进闭锁位图。因此它不进 sticky、不进 adc_task 编排层的 logged 位图、
* 也不由本模块打日志——留痕走 protect 侧既有的闭锁边沿日志与 SOE。
* 位段纪律见 docs/算法/采样有效性校验设计说明.md §14 */
#define ADC_HF_STALE (1uL << 8) /* 帧年龄超限(电平,序号恢复前进即清) */
/* 计数快照(由调用方采集,本模块不触碰任何驱动/外设) */
typedef struct {
uint32_t tpafe_seq; /* tpafe5160_frame_seq() */
uint32_t tpafe_frst_err; /* tpafe5160_frstdata_err()(驱动为 uint16,此处放宽) */
uint32_t stm32_seq; /* adc_input_stm32_seq() */
uint32_t stm32_errs; /* adc_input_stm32_errs() */
uint32_t stm32_gaps; /* adc_input_stm32_gaps() */
uint32_t stm32_hw_errs; /* adc_input_stm32_hw_errs() */
uint32_t now_ms; /* osKernelGetTickCount()(tick 回绕由差值法吸收) */
} adc_health_in_t;
/* 判定结果快照 */
typedef struct {
uint32_t flags; /* 电平故障位 ADC_HF_TPAFE/STM32_STALL/SEQ_BACKWARD */
uint32_t events; /* 本次边沿事件位 ADC_HE_xxx */
uint32_t sticky; /* 锁存故障位(含电平位的历史并集) */
uint32_t tpafe_gaps; /* TPAFE 累计丢帧点数(序号前进 >1 的超出部分之和) */
uint32_t tpafe_stall_ms; /* TPAFE 当前静默时长(0=正常) */
} adc_health_status_t;
/* 帧年龄参照系(adc_health_age_stale() 用)。状态**归调用方所有**,所以多个观察者
* 各判各的、互不污染——这是它与本模块其余接口的关键区别(check() 用的是模块级
* 单写者状态,只能有一个每拍调用者) */
typedef struct {
uint32_t seq; /* 上次看到的序号 */
uint32_t adv_ms; /* 最后一次看到序号前进的时刻(与调用方时基同源) */
uint8_t primed; /* 0 = 尚未建立基线(首拍只记录、不判定) */
} adc_health_age_t;
/*
* 函数功能:采样有效性校验初始化(幂等,内核启动前可调)
* 入口参数:无
* 返 回 值:无
* 限定条件:无
* 函数说明:清全部状态;首次 adc_health_check() 只建基线、不判定。
*/
void adc_health_init(void);
/*
* 函数功能:按计数快照做一次有效性判定(每拍调用,开销为常数次整数运算)
* 入口参数:in - 计数快照(NULL 则本次跳过,out 不动)
* out - 判定结果(NULL 则仅更新内部状态)
* 返 回 值:无
* 限定条件:仅任务上下文(与 adc_input_poll() 同节奏调用)
* 函数说明:首次调用建立基线(不产生事件/故障)。序号差值按
* `(int32_t)(now - prev)` 口径计算,tick/计数回绕均安全。
*/
void adc_health_check(const adc_health_in_t *in, adc_health_status_t *out);
/*
* 函数功能:帧年龄判定(无模块状态,供与消费者存活解耦的兜底门使用)
* 入口参数:a_p_age - 调用方自存的参照系
* a_cur_seq - 本次读到的序号(须是**中断侧**递增的计数器,如
* tpafe5160_frame_seq() / adc_input_stm32_seq())
* a_now_ms - 本次时基(与 a_p_age->adv_ms 取同一个任务的 tick)
* a_age_ms - 判老门限(ms)
* 返 回 值:1 - 已陈旧(调用方应据此闭锁);0 - 新鲜,或仍在建立基线
* 限定条件:任意任务上下文;零内核依赖、零共享状态 → 主机侧免桩可直接单测
* 函数说明:差值口径与 adc_health_check() 一致((int32_t) 差值,计数与 tick
* 回绕均安全)。首次调用只建基线并返回 0;序号有任何前进即返回 0
* (一次前进多帧属消费者超拍,不是故障);倒退不在此判老,单调性
* 破坏归 ADC_HF_SEQ_BACKWARD 管。参数错返回 0——宁可不锁,不可误锁。
*/
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);
/*
* 函数功能:读当前电平故障位
* 入口参数:无
* 返 回 值:ADC_HF_TPAFE_STALL / ADC_HF_STM32_STALL / ADC_HF_SEQ_BACKWARD 组合
* 限定条件:无
* 函数说明:供自检失败闭锁判定与 UI;与 check() 出参 flags 同源。
*/
uint32_t adc_health_flags(void);
/*
* 函数功能:读锁存故障位(出现过即置位,直到 init 才清)
* 入口参数:无
* 返 回 值:电平位 + ADC_HF_FRSTDATA/STM32_ERR/HW_ERR 的并集
* 限定条件:无
* 函数说明:stall 自愈后 sticky 仍保留该次故障痕迹("曾经停摆过"必须留痕)。
*/
uint32_t adc_health_sticky(void);
/*
* 函数功能:读 TPAFE 累计丢帧点数
* 入口参数:无
* 返 回 值:序号前进 >1 的超出部分累计(与 STM32 侧 adc_input_stm32_gaps() 对称)
* 限定条件:无
* 函数说明:消费者超拍(单拍耗时 > 1 ms)所致;非零本身不是闭锁条件,
* 但持续增长说明相量窗出现时间空洞,须排查耗时。
*/
uint32_t adc_health_tpafe_gaps(void);
/* ===================== 采样通道自检 残余:码值越界(贴轨)检出,2026-10-25 ===================== */
/* 贴轨锁存的连续拍数:20 拍 = 1 kHz 节拍下一个 50 Hz 周波。削顶/触轨波形的
* 贴带段持续 ≥ 半周波;噪声毛刺不可能连续 20 拍钉在同侧轨带内(短板评测 §6.2
* 判据①「连续 N 点贴满量程」的定标)。 */
#define ADC_RAIL_N_RUN 20u
/* 贴轨监视的通道数上限(9 = 8 电气量 + 温度;温度是否监视由调用方经
* adc_cal_rail_ths() 返回 -1 决定,本层不内嵌语义知识) */
#define ADC_RAIL_CH_NUM 9u
/*
* 函数功能:贴轨观察器复位(adc_health_init() 已内含,单独暴露供编排层复用)
* 入口参数:无
* 返 回 值:无
* 限定条件:与 check() 同——仅任务上下文(单写者)
*/
void adc_health_rail_init(void);
/*
* 函数功能:单通道单拍贴轨观察(采样通道自检 码值越界检出,纯算术)
* 入口参数:ch - 通道号(0..ADC_RAIL_CH_NUM-1,越界忽略) uint8_t
* code - 本拍原始码值(**码值域**,与标定无关) int32_t
* lo_th - 下轨带上界(code ≤ 此值算贴底) int32_t
* hi_th - 上轨带下界(code ≥ 此值算贴顶) int32_t
* 返 回 值:1 - 本拍**新锁存**该通道(边沿,供编排层落日志);0 - 无新事件 uint32_t
* 限定条件:每拍每通道至多一次(与采样帧同节奏,单写者)
* 函数说明:锁存条件 = 连续 ADC_RAIL_N_RUN 拍贴**同一侧**轨带,且该通道此前
* 出现过中带样本(「释放沿」)。释放沿要求的理由:空载信号恒 0、
* 开机未采到帧恒 0 都是**合法工况**(短板评测 §6.2:空载与零值都是
* 合法工况)——只有「动过又钉死在轨上」才是削顶/越界签名。锁存后
* 不再重复报边沿,直到 rail_init/health_init 重置。
*/
uint32_t adc_health_rail_watch(uint8_t ch, int32_t code, int32_t lo_th, int32_t hi_th);
/*
* 函数功能:读贴轨锁存位图
* 入口参数:无
* 返 回 值:bit ch = 该通道曾检出贴轨(1 kHz × 9 路 → 低 9 位有效) uint32_t
* 限定条件:无
* 函数说明:供心跳日志 / 后续品质位映射(遥测快照发布与 104 消费的 qds 消费者)只读。
*/
uint32_t adc_health_rail_sticky(void);
#ifdef __cplusplus
}
#endif
#endif /* ADC_HEALTH_H */