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:
+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