Files
STM32F4-Base/App/algo/adc_health.h
T
wandering1 3528644e9d feat(采集标定): 卡2 adc_cal 折算模块 + 卡4 系数落盘 + L5-2 码值贴轨检出
卡 2(App/algo/adc_cal.*,新增;adc_task.c:335 折算点收口):
- 模型:工程量 =(code+b)×k(「数据可信」§4.1);k 以 int32 Q16(µV/码×65536)
  落盘、b 以 int32 码值落盘;ROM 默认 = 卡 1 定死的引脚侧 LSB,Q16 下整除无
  舍入(TPAFE 10,000,000=152.587890625 µV/码;STM32 52,800,000=805.6640625)
- adc_task 每拍 frame[s] 改 adc_cal_apply()(现单位 = ADC 引脚电压 V,eng=0,
  二次值语义等 VALID);TEMP 透传码值(决策 5);不缓存 k_f 副本——逐点由
  RAM 现值派生(无回调设计下杜绝陈旧系数;18k flops/s 可忽略)
- 三态 flags(§4.2):VALID = 文件位图 ∧ k 在合法带;CLIPPED 按来源运行期
  派生(V0.2/N-18 后自动恒 0);消费者判可用性只查 adc_cal_flags()
- 合法带哨兵:k∉ROM×[0.5,2.0] 或 |b|>8192 → 折算回退 ROM + 每通道每开机
  一条 [ERR] + VALID 压制(位图说谎也兜住)——同时挡坏文件与坏标定
- 两点标定公式 adc_cal_two_point() 算术核齐备(int64、±舍入界、斜率/零点
  守卫 -1/-2 分档);台架两点标定(卡 12)按用户指示未跑,留台架注入口径:
  心跳新增 cal: 行(ua/un/ia_mv 定点 + vl/cl/rail 位图,未标定 vl 应恒 0x00、
  cl 应恒 0xE6,注入饱和电平 rail 位应起)

卡 4([adc_cal] k_ua..k_in+valid / [adc_cal_ofs] b_ua..b_in,§4.4 布局):
- 两段注册进 cfg 引擎(data_size 33/32,不挂回调——RAM 副本即生效值);
  adc_cal_init() 落 app_main_init 内核前(注册必须先于 defaultTask 的
  cfg_file_load;跨任务读走 4 字节对齐字,µs 级混窗为「数据可信」§4.4 明载
  设计);文件缺失/损坏 = 引擎回退工厂默认 = ROM 表本体(默认即理论系数)
- adc_cal_set():整定写入口(属主上下文),越带拒绝 RAM 不动
- 单测走真实 cfg_file 引擎集成:文件值生效 / 越界 k 哨兵兜住且不报 VALID /
  超长损坏文件回退默认且 flags 无 VALID(卡 4 DoD 原句)/ 段缺失默认

L5-2(adc_health v1.2,越界门限按 FS 百分比、码值域与标定正交):
- adc_health_rail_watch():连续 20 拍(=一周波)贴同侧轨带(TPAFE±98%半量程、
  STM32 顶 98%/底 2%,门限由 adc_cal_rail_ths 供)且此前见过中带释放沿才
  锁存——空载恒 0 / 开机恒 0 合法工况不误报(短板评测 §6.2 判据①);
  adc_task 编排:边沿 DBG_ERROR + SYSLOG 每开机一次 + 心跳 rail/edge 位图
- 健康用例 60/60(+13):释放沿/清零/交替/双侧/init 复位/越界通道守卫

测试与注册:新套件 test/host/adc_cal(89/89,8 通道×≥3 码点期望值由 LSB
常数独立推导、Q16 无舍入、set 边界含端点、两点还原合成 k/b 往返);
编号形态返工一轮:TC-ADCCAL-00A~00D 改 014~017、注释 FS-1 字样改写、
id_registry 族 pattern 转义修正、族游标登至 017;coding-standard 返工:
公开实现函数补齐函数头注释块(CODE-RULE-09)
uvprojx 仅增 adc_cal.c 一条挂载(diff 已核零抖动);SIM_EXEMPT/id_registry
(TC-HOST-ADCCAL 族)/adc-cal-unit-test 钩子行按 new-source-file 与 B 层口径
补齐;覆盖率 34/34 套件 89.6%,adc_cal.c 99.2%(baseline --update 属共享
台账文件,留 lead done 后统一回填)

gate:@build 0 错 0 警(Code=215440 RO=19780 RW=1220 ZI=131980);
RAM 增量 +144 B(RW+16/ZI+128:贴轨 9×6B + 两段 RAM 副本,IRAM1 57.7%→
约 57.8%);Flash +1336B/+920B RO。adcTask 栈 worst=1344B margin 1216B
(47%) PASS(stack_report 实测)。staged 门禁除 mem-numbers/mem-budget 外
全绿(该两条 = 台账漂移,回填 --write 落 docs/内存管理/** 单写者区,按
Lead 既定例外归 lead done 后处理):
gate: only mem-budget/mem-numbers pending lead ledger backfill
BOM/CR/U+FFFD/字面量 ASCII 已逐文件自查(本次未触碰任何带 BOM 文件)
2026-10-25 04:21:12 +08:00

217 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 采样有效性校验(L1 采集与预处理,规划项 L1-6)
* 模块功能:对两片转换器的"节拍是否还在喂数据"做**判定层**(观测计数由
* `adc_input` / `tpafe5160` 驱动提供,本模块只做判定与告警位):
* ① 静默(stall):帧序号连续 >= ADC_HEALTH_STALL_MS 无前进;
* ② 丢帧(gap):帧序号一次前进 >1(消费者超拍,中间帧被覆盖);
* ③ 单调性破坏:帧序号倒退(计数器被改写 / 驱动 bug,锁存);
* ④ 错误计数增量:FRSTDATA 跳变 / HAL ADC·DMA 错误 / STM32 停摆计数。
* ⚠ 本层**纯逻辑**:不碰寄存器、不取数、不打日志——输入是调用方
* 采集的计数快照,输出是故障位;日志与自检上报由消费者
* (`App/task/adc_task.c`)按位执行 → 主机侧可单测(L0.5,A-4)。
* 适用平台:STM32F407ZGTx + TPAFE5160
* 作者:王建锋
* 创建日期:2026-10-11
* 修改记录:
* v1.0 2026-10-11 王建锋 创建:L1-6 采样有效性校验(静默/丢帧/单调性/错误增量)
* 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 项目组 新增 L5-2 残余「码值越界(贴轨)检出」:
* 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 锁存)
* ——供后续 L5-4「自检失败闭锁」直接消费 */
#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 组合
* 限定条件:无
* 函数说明:供 L5-4 闭锁判定与 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);
/* ===================== L5-2 残余:码值越界(贴轨)检出,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);
/*
* 函数功能:单通道单拍贴轨观察(L5-2 码值越界检出,纯算术)
* 入口参数: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
* 限定条件:无
* 函数说明:供心跳日志 / 后续品质位映射(卡 8/9 的 qds 消费者)只读。
*/
uint32_t adc_health_rail_sticky(void);
#ifdef __cplusplus
}
#endif
#endif /* ADC_HEALTH_H */