L1-6 采样有效性校验:adc_health 判定层 + 告警编排 + 31 用例单测 + 台架闭环
新增 App/algo/adc_health.c/h(纯逻辑判定层,零平台依赖免桩单测): - 静默:两侧帧序号连续 >=100ms 无前进(电平位可自愈,flags() 为 L5-4 闭锁预留) - 丢帧:TPAFE 序号一次前进 >1 计 tgap(与 STM32 侧 adc1_gap 对称,边沿事件) - 单调性:序号倒退锁存位;tick/计数差值法 (int32_t)(a-b) 回绕安全 - 错误增量:FRSTDATA / HAL 错误 / STM32 停摆计数增量,事件位与锁存位编号对齐, 消费者按位图做"每类每次开机留痕一次"编排 adc_task.c v2.9 告警编排(判定与编排分离):严重事件/电平上升沿 DBG_ERROR + SYSLOG_SELFTEST(每类每次开机一次防刷屏),下降沿 DBG_INFO 恢复;gap 不告警 (消费者超拍是已知现象,告警会淹没真故障);心跳行增 tgap 观测字段。 主机单测 test/host/adc_health/ 31 用例全绿(100ms 门限两侧 / 边沿语义 / 倒退锁存 / tick 回绕 / NULL 守卫 / init 幂等),登记 adc-health-unit-test 钩子。 台架 @test PASS:零 [ERR](健康不误报)、tgap=1->2 与 adc1_gap 同步可见、 frames 1000.0 Hz;证据 boot_log_data/20261011_prod_adc_health_boot_log.txt。 抬栈:adc_health_report->syslog_post 控制台回显尾使 adcTask 静态最坏链 832->1080 B(512 words 余 968 B < 1024 B ISR 底线)-> 512->640 words, 复测余 1480 B / 57%,堆 min_ever 7152->6640 B(-512 B 符合预算)。 AGENTS/README 任务表、RAM 方案 §2.3、规划文档 §3.1 L1-6 已同步置完成。
This commit is contained in:
+36
-4
@@ -12,11 +12,13 @@
|
||||
| 文件 | 层 | 作用(一句话) | 主要对外入口 | 覆盖规划项 |
|
||||
|---|---|---|---|---|
|
||||
| `adc_input.h` / `adc_input.c` | L1 | **统一语义取数层**:把两片转换器的原始码值按语义(`UA…IN`/温度)暴露,并消费 STM32 ADC1 的 DMA 乒乓双缓冲 | `adc_input_init()` `adc_input_poll()` `adc_input_raw()` | L1-2 / L1-3 / L1-4 |
|
||||
| `adc_health.h` / `adc_health.c` | L1 | **采样有效性判定层**:吃"计数快照"(帧序号/错误计数),产出静默/丢帧/单调性/错误增量故障位——判定与告警编排分离 | `adc_health_init()` `adc_health_check()` `adc_health_flags()` | L1-6 |
|
||||
| `sample_ring.h` / `sample_ring.c` | L2 输入 | **采样环缓冲**:按语义通道保存最近 2 个周波(40 点)采样值,供相量层"以当前时刻为终止"取窗 | `sample_ring_push_frame()` `sample_ring_window()` | L2 输入(为 L2-6 滑动窗供数) |
|
||||
| `phasor.h` / `phasor.c` | L2 | **全周傅氏相量计算**:一周波 20 点单频 DFT → 基波/谐波相量(有效值口径),双区原子切换供无锁读 | `phasor_run()` `phasor_get()` `phasor_calc()` | L2-1 / L2-2 / L2-3 / L2-6 / L2-7 |
|
||||
|
||||
依赖关系(自下而上,无环):`adc_input.h`(纯枚举 + 声明)← `sample_ring` ← `phasor`。
|
||||
`sample_ring.c` / `phasor.c` **零平台依赖**(只用 `math.h` / `stddef.h` 与本目录头);
|
||||
依赖关系(自下而上,无环):`adc_input.h`(纯枚举 + 声明)← `sample_ring` ← `phasor`;
|
||||
`adc_health` **独立**(仅 `stdint.h`,不依赖本目录其它头)。
|
||||
`sample_ring.c` / `phasor.c` / `adc_health.c` **零平台依赖**(只用 `math.h` / `stddef.h` / `stdint.h` 与本目录头);
|
||||
`adc_input.c` 另依赖 `adc.h`(`hadc1`)与 `tpafe5160.h`(快照),主机侧以桩头替身。
|
||||
|
||||
## 2. 数据流(一拍 = TIM3 1 kHz 节拍)
|
||||
@@ -119,6 +121,33 @@ flowchart TD
|
||||
- 每拍 20 点**全部重算**(暴力法,无累积误差;迭代加减首尾项会累积)。相量刷新周期 = 1 ms。
|
||||
- 相位(L2-4)**未常驻**结构体,需要时自行 `atan2f(fIm, fRe)`。
|
||||
|
||||
### 3.4 `adc_health.c` / `adc_health.h` — L1-6 采样有效性判定
|
||||
|
||||
**解决的问题**:驱动侧只有"观测计数"(`tpafe5160_frame_seq/_frstdata_err`、
|
||||
`adc_input_stm32_seq/_errs/_gaps/_hw_errs`),**没有判定**——芯片静默、消费者超拍丢帧、
|
||||
计数器被改写这些故障此前只能靠人读心跳日志发现。本层把判定收进纯逻辑,
|
||||
告警编排留在消费者(`adc_task.c` 的 `adc_health_report()`)→ 判定可主机免桩单测。
|
||||
|
||||
| API | 干什么 | 注意 |
|
||||
|---|---|---|
|
||||
| `adc_health_init()` | 清全部状态(幂等) | 首拍 `check()` 只建基线、不判定 |
|
||||
| `adc_health_check(&in, &out)` | 吃计数快照做一次判定(每拍调,常数次整数运算) | 差值法 `(int32_t)(now-prev)`,tick/计数回绕安全 |
|
||||
| `adc_health_flags()` | 电平故障位:`TPAFE_STALL` / `STM32_STALL`(≥100 ms 无前进,可自愈)/ `SEQ_BACKWARD`(锁存) | 为 L5-4 闭锁预留 |
|
||||
| `adc_health_sticky()` | 锁存位(含 `FRSTDATA` / `STM32_ERR` / `HW_ERR` 增量痕迹) | 自愈后仍留痕 |
|
||||
| `adc_health_tpafe_gaps()` | TPAFE 累计丢帧点数(序号前进 >1 的超出部分) | 与 STM32 侧 `adc_input_stm32_gaps()` 对称 |
|
||||
|
||||
**要点**
|
||||
|
||||
- **三类语义严格分开**:电平位(stall,可自愈)/ 锁存位(出现过必留痕)/ 边沿事件位
|
||||
(仅当拍有效)。`ADC_HE_FRSTDATA/STM32_ERR/HW_ERR` 与同名 `ADC_HF_*` **编号对齐**,
|
||||
消费者直接用事件掩码对"已留痕位图"做每类一次 `SYSLOG_SELFTEST` 的编排。
|
||||
- **gap 不告警**:消费者超拍(心跳日志 ~2 ms)是已知现象,`adc1_gap`/`tgap` 每 10 s 心跳
|
||||
可见;若按事件告警会把真故障淹掉(台架实测开机 `adc1_gap≈24`、稳态每 10 s +2)。
|
||||
- **静默 ≠ 丢帧**:d=0 才是静默候选;d>1 只说明消费者没跟上,生产者还活着——两者
|
||||
处置完全不同,判据不许混。
|
||||
- 告警策略(在 `adc_task.c`,不在本层):严重事件/电平上升沿 `DBG_ERROR` +
|
||||
`SYSLOG_SELFTEST`(每类每次开机一次,防刷屏写满 32KB selftest.log);下降沿 `DBG_INFO`。
|
||||
|
||||
## 4. 关键约定
|
||||
|
||||
- **语义映射唯一权威口径**:`docs/硬件平台/ADC通道映射说明.md`。取数一律经 `adc_input_raw()`,
|
||||
@@ -138,6 +167,7 @@ flowchart TD
|
||||
| `sample_ring.c` `s_ring[9][40]` float | **1440 B** |
|
||||
| `phasor.c` `s_zone[2][5]` `phasor_t` | 160 B |
|
||||
| `adc_input.c` `s_stm32_raw[16]` + `s_tpafe_raw[8]` | 48 B |
|
||||
| `adc_health.c` 内部状态(9 × uint32 + uint8) | ≈ 40 B |
|
||||
| 运行期栈:`phasor_run()` 的 `win[20]` | 80 B/帧 |
|
||||
|
||||
改容量(`SAMPLE_RING_CYCLES` / 实例数 / `ADC_SEM_COUNT`)前后各跑一次
|
||||
@@ -145,7 +175,7 @@ flowchart TD
|
||||
|
||||
## 6. 构建与测试
|
||||
|
||||
- **挂载**:三个 `.c` 已登记在 `MDK-ARM/STM32F407-Demo.uvprojx`,`../App/algo` 已在 `<IncludePath>` 内。
|
||||
- **挂载**:四个 `.c` 已登记在 `MDK-ARM/STM32F407-Demo.uvprojx`,`../App/algo` 已在 `<IncludePath>` 内。
|
||||
本层**不进** `simulator/CMakeLists.txt`(UI 模拟器不跑采集链路)。
|
||||
- **主机侧单测(L0.5)**——生产源文件以 `#include` 原样纳入,无需改产品代码:
|
||||
|
||||
@@ -153,10 +183,12 @@ flowchart TD
|
||||
|---|---|---|
|
||||
| 相量层(57 用例:系数表逐项复核 / 幅值·相位 / 谐波抑制与提取 / 频偏 / 采样环 / 双区交换) | `& '.venv\Scripts\python.exe' test/host/phasor/run_unit_test.py` | `phasor-unit-test` |
|
||||
| ADC 输入层(逐 Rank 注入互异码值钉死映射 + HAL 启动失败/超时分支) | `& '.venv\Scripts\python.exe' test/host/adc_input/run_unit_test.py` | `adc-input-unit-test` |
|
||||
| 有效性校验(31 用例:100 ms 静默门限两侧 / 丢帧边沿 / 倒退锁存 / 错误增量 / tick 回绕 / NULL 守卫 / init 幂等) | `& '.venv\Scripts\python.exe' test/host/adc_health/run_unit_test.py` | `adc-health-unit-test` |
|
||||
|
||||
改本目录任一 `.c/.h`,提交时对应钩子自动跑;详见 `docs/测试说明/相量层单元测试说明.md`。
|
||||
- **板端**:`@build` 0 错误 0 警告 → `@test` 看 `[ADC_TASK]` 首拍/心跳日志(`phasor(...)` 行、
|
||||
`ring` / `skip` 计数、`adc1_rc` / `adc1_err`)。
|
||||
`ring` / `skip` 计数、`adc1_rc` / `adc1_err`、`tgap`);健康判据 = 零 `[ERR]`
|
||||
(`adc_health_report` 的告警全部以 `[ERR]` 开头 → 误报会被闭环直接抓住)。
|
||||
|
||||
## 7. 新增一个算法模块的固定动作
|
||||
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 模块名称:ADC 采样有效性校验(L1 采集与预处理,规划项 L1-6)
|
||||
* 模块功能:见 adc_health.h 文件头。本文件为**纯逻辑实现**:输入计数快照、
|
||||
* 输出故障位/事件位,零平台依赖(仅 stdint.h)→ 主机侧免桩单测。
|
||||
* 适用平台:STM32F407ZGTx + TPAFE5160
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-10-11
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-11 王建锋 创建:L1-6 采样有效性校验
|
||||
*/
|
||||
|
||||
#include "adc_health.h"
|
||||
|
||||
/* ===================== 内部状态 ===================== */
|
||||
|
||||
/* 基线是否已建立:首次 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;
|
||||
|
||||
/* ===================== 对外接口 ===================== */
|
||||
|
||||
/*
|
||||
* 函数功能:采样有效性校验初始化(幂等)
|
||||
* 入口参数:无
|
||||
* 返 回 值:无
|
||||
* 限定条件:无(内核启动前可调)
|
||||
* 函数说明:清全部内部状态;判定从下一次 check() 的首拍基线重新开始。
|
||||
*/
|
||||
void adc_health_init(void) {
|
||||
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 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;
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 模块名称: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 采样有效性校验(静默/丢帧/单调性/错误增量)
|
||||
*/
|
||||
|
||||
#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 错误计数有增量 */
|
||||
|
||||
/* 计数快照(由调用方采集,本模块不触碰任何驱动/外设) */
|
||||
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_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);
|
||||
|
||||
/*
|
||||
* 函数功能:读当前电平故障位
|
||||
* 入口参数:无
|
||||
* 返 回 值: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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ADC_HEALTH_H */
|
||||
+85
-6
@@ -70,6 +70,13 @@
|
||||
* 两片转换器由同一硬件事件驱动);STM32 侧改 DMA 双缓冲
|
||||
* 消费,adc_input_poll() 不再忙等;心跳日志增
|
||||
* adc1_seq / adc1_hw(adc1_err 语义改为"本拍无新帧")
|
||||
* v2.9 2026-10-11 王建锋 L1-6 采样有效性校验:每拍调 adc_health_check()
|
||||
* (App/algo/adc_health.c,纯逻辑判定:静默/丢帧/
|
||||
* 单调性/错误计数增量)——严重事件与故障电平上升沿
|
||||
* 打 DBG_ERROR + 每类每次开机一次 SYSLOG_SELFTEST;
|
||||
* 恢复打 DBG_INFO;心跳日志增 tgap(TPAFE 侧丢帧,
|
||||
* 与 adc1_gap 对称)。判定不在本文件(A-4:算法与
|
||||
* I/O 分离),本文件只做"消费计数 → 告警"的编排
|
||||
*/
|
||||
|
||||
#include "adc_task.h"
|
||||
@@ -77,9 +84,11 @@
|
||||
#include "adc_input.h" /* L1 采集层:adc_input_poll()/adc_input_raw()(按语义取数) */
|
||||
#include "sample_ring.h" /* L2 输入:sample_ring_push_frame()(采样环缓冲) */
|
||||
#include "phasor.h" /* L2 相量层:phasor_run()/phasor_get()(全周傅氏) */
|
||||
#include "adc_health.h" /* L1-6:采样有效性校验(静默/丢帧/单调性/错误增量) */
|
||||
#include "tpafe5160.h"
|
||||
#include "task_watchdog.h"
|
||||
#include "console_log.h"
|
||||
#include "sys_log.h" /* SYSLOG_SELFTEST:采样故障留痕(仅故障才写) */
|
||||
#include "task_prof.h" /* P2 常开探针:单拍耗时(Task #32) */
|
||||
|
||||
#define DBG_TAG "[ADC_TASK]"
|
||||
@@ -97,6 +106,13 @@ static uint32_t s_frames_idle;
|
||||
/* 因通道尚未首次成功而**整拍跳过入环**的次数(不向相量窗注入 0 点) */
|
||||
static uint32_t s_frames_skip;
|
||||
|
||||
/* L1-6 告警编排状态:
|
||||
* s_health_prev - 上次电平故障位(判上升/下降沿,避免每拍重复告警)
|
||||
* s_health_logged- 已写 SYSLOG_SELFTEST 的位(每类每次开机只留痕一次,
|
||||
* 防持续故障刷屏把 32KB selftest.log 写满) */
|
||||
static uint32_t s_health_prev;
|
||||
static uint32_t s_health_logged;
|
||||
|
||||
/* adcTask 的内核句柄,定义在 Src/freertos.c(引用方式同 App/task/default_task.c) */
|
||||
extern osThreadId_t adcTaskHandle;
|
||||
|
||||
@@ -155,6 +171,48 @@ static void log_phasors(const char *tag) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:L1-6 判定结果的告警编排(边沿 → 日志 + 自检留痕)
|
||||
* 入口参数:hs - adc_health_check() 判定结果快照
|
||||
* 返 回 值:无
|
||||
* 限定条件:仅任务上下文;日志仍受写者就绪门控(SYSLOG 不受——内核前有
|
||||
* s_preboot 缓冲,故障痕迹不因开机窗口丢失)
|
||||
* 函数说明:策略分级(判定本身不在本文件,见 App/algo/adc_health.c)——
|
||||
* ① 严重事件(FRSTDATA / HAL 错误 / STM32 停摆计数增量):这三类
|
||||
* 计数增量恒应为 0,出现即故障 → 每次 DBG_ERROR;
|
||||
* ② 电平故障上升沿(静默/序号倒退)→ DBG_ERROR;下降沿(静默自愈)
|
||||
* → DBG_INFO("恢复"必须可见,否则只看到一次错误无从判断是否还在);
|
||||
* ③ SYSLOG_SELFTEST 每类每次开机**只留痕一次**(持续故障刷屏会把
|
||||
* 32KB selftest.log 写满,也淹没其它模块的故障记录);
|
||||
* ④ gap(丢帧)**不告警**:消费者超拍是已知现象(心跳日志
|
||||
* adc1_gap / tgap 即观测面),告警会淹没真故障。
|
||||
*/
|
||||
static void adc_health_report(const adc_health_status_t *hs) {
|
||||
uint32_t serious = hs->events & (ADC_HE_FRSTDATA | ADC_HE_STM32_ERR | ADC_HE_HW_ERR);
|
||||
uint32_t rise = hs->flags & ~s_health_prev;
|
||||
uint32_t fall = s_health_prev & ~hs->flags;
|
||||
|
||||
if ((serious != 0u) && (dbg_console_writer_ready() != 0u)) {
|
||||
DBG_ERROR("sampling fault: events=0x%lx sticky=0x%lx",
|
||||
(unsigned long)hs->events, (unsigned long)hs->sticky);
|
||||
}
|
||||
if ((rise != 0u) && (dbg_console_writer_ready() != 0u)) {
|
||||
DBG_ERROR("health: flags=0x%lx stall_ms=%lu tgap=%lu",
|
||||
(unsigned long)hs->flags,
|
||||
(unsigned long)hs->tpafe_stall_ms,
|
||||
(unsigned long)hs->tpafe_gaps);
|
||||
}
|
||||
if (((serious | rise) & ~s_health_logged) != 0u) {
|
||||
(void)syslog_post(SYSLOG_SELFTEST, "adc sampling fault");
|
||||
s_health_logged |= (serious | rise);
|
||||
}
|
||||
if ((fall != 0u) && (dbg_console_writer_ready() != 0u)) {
|
||||
DBG_INFO("health: recovered flags=0x%lx sticky=0x%lx",
|
||||
(unsigned long)hs->flags, (unsigned long)hs->sticky);
|
||||
}
|
||||
s_health_prev = hs->flags;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:ADC 任务初始化
|
||||
* 入口参数:无
|
||||
@@ -166,6 +224,7 @@ int adc_task_init(void) {
|
||||
s_frames_consumed = 0u;
|
||||
s_frames_idle = 0u;
|
||||
s_frames_skip = 0u;
|
||||
adc_health_init();
|
||||
DBG_INFO("adc_task_init");
|
||||
return 0;
|
||||
}
|
||||
@@ -195,7 +254,8 @@ void adc_task_func(void *arg) {
|
||||
s_consumer_ready = 1u;
|
||||
|
||||
for (;;) {
|
||||
int r;
|
||||
int r;
|
||||
uint32_t now = osKernelGetTickCount(); /* 本拍时基:健康判定 + 心跳共用一份 */
|
||||
|
||||
/* P2 探针:单拍起点。名义节拍 1 ms = 168000 周期;若实测**持续**超过,
|
||||
* 本任务(v2.7 起为 osPriorityAboveNormal)会持续抢占 uiTask
|
||||
@@ -259,6 +319,24 @@ void adc_task_func(void *arg) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 2b) L1-6 采样有效性校验(每拍,常数次整数运算):把驱动侧观测计数交给
|
||||
* 纯逻辑判定层,本任务只做告警编排。⚠ 计数一律取**本拍 poll 之后**
|
||||
* 的值——与刚消费的帧同一时点,避免"下一拍的增量被提前看到"。 */
|
||||
{
|
||||
adc_health_in_t h_in;
|
||||
adc_health_status_t h_st;
|
||||
|
||||
h_in.tpafe_seq = tpafe5160_frame_seq();
|
||||
h_in.tpafe_frst_err = (uint32_t)tpafe5160_frstdata_err();
|
||||
h_in.stm32_seq = adc_input_stm32_seq();
|
||||
h_in.stm32_errs = adc_input_stm32_errs();
|
||||
h_in.stm32_gaps = adc_input_stm32_gaps();
|
||||
h_in.stm32_hw_errs = adc_input_stm32_hw_errs();
|
||||
h_in.now_ms = now;
|
||||
adc_health_check(&h_in, &h_st);
|
||||
adc_health_report(&h_st);
|
||||
}
|
||||
|
||||
/* 3) 首帧码值上报:**写者就绪后**才落日志。写者未注册时 con_append 走
|
||||
* 无锁直发路径,与 defaultTask 的存储初始化日志会字节级交错
|
||||
* (2026-10-10 台架实测);此时挂起上报,就绪后补报最近一帧。
|
||||
@@ -303,8 +381,6 @@ void adc_task_func(void *arg) {
|
||||
/* 6) 周期心跳日志:帧统计 + 驱动侧 FRSTDATA 错误计数 + L2 相量。
|
||||
* 写者就绪前抑制(同 3) 的交错理由);到点无论是否落日志都推进下个上报点 */
|
||||
{
|
||||
uint32_t now = osKernelGetTickCount();
|
||||
|
||||
if ((int32_t)(now - report_at) >= 0) {
|
||||
if (dbg_console_writer_ready() != 0u) {
|
||||
/* STM32 侧观测量(v2.0 DMA 版),四类各有独立成因:
|
||||
@@ -314,9 +390,11 @@ void adc_task_func(void *arg) {
|
||||
* adc1_err = 停摆:连续 >=2 拍无新帧 → 节拍源/DMA 真停
|
||||
* adc1_hw = HAL ADC/DMA 错误回调
|
||||
* 判读:adc1_seq 停增而 seq 仍涨 → STM32 侧停摆;
|
||||
* adc1_gap 增长 → 本任务耗时超拍(看 adcm/adcn) */
|
||||
* adc1_gap 增长 → 本任务耗时超拍(看 adcm/adcn);
|
||||
* tgap = TPAFE 侧同类丢帧计数(L1-6,与 adc1_gap 对称) */
|
||||
DBG_INFO("frames=%lu idle=%lu skip=%lu seq=%lu frst_err=%u ring=%lu"
|
||||
" adc1_rc=%d adc1_err=%lu adc1_seq=%lu adc1_gap=%lu adc1_hw=%lu",
|
||||
" adc1_rc=%d adc1_err=%lu adc1_seq=%lu adc1_gap=%lu adc1_hw=%lu"
|
||||
" tgap=%lu",
|
||||
(unsigned long)s_frames_consumed,
|
||||
(unsigned long)s_frames_idle,
|
||||
(unsigned long)s_frames_skip,
|
||||
@@ -327,7 +405,8 @@ void adc_task_func(void *arg) {
|
||||
(unsigned long)adc_input_stm32_errs(),
|
||||
(unsigned long)adc_input_stm32_seq(),
|
||||
(unsigned long)adc_input_stm32_gaps(),
|
||||
(unsigned long)adc_input_stm32_hw_errs());
|
||||
(unsigned long)adc_input_stm32_hw_errs(),
|
||||
(unsigned long)adc_health_tpafe_gaps());
|
||||
|
||||
log_phasors("hb");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user