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 文件)
This commit is contained in:
2026-10-25 04:21:12 +08:00
parent 65c5e7d310
commit 3528644e9d
15 changed files with 1545 additions and 24 deletions
+99 -10
View File
@@ -84,6 +84,14 @@
* 者;该入口同时不再打日志——它落在本任务静态最坏链上,
* 每条 DBG_* 约 216 B 栈,而这一行毫无诊断价值(本任务
* 存活由首帧日志与 TASK_WD_ADC 心跳证明)
* v3.1 2026-10-25 DTU 项目组 L1.5 折算 + L5-2 贴轨观测(「数据可信」卡 2,道A):
* frame[s] 由 (float)code 改 adc_cal_apply()——工程量 =
* (code+b)×k,现口径 = ADC 引脚电压 V(eng=0,卡 1 冻结的
* ROM 系数,docs/硬件平台/ADC通道映射说明.md §7.1);同拍
* 经 adc_health_rail_watch() 逐通道贴轨观察(L5-2 残余,
* 门限 adc_cal_rail_ths,码值域与标定正交),锁存边沿 →
* DBG_ERROR + 每开机一次 SYSLOG;心跳增 cal: 行(mV 定点
* + valid/clipped 位图 + rail 锁存),台架注入口径见该行判读
*/
#include "adc_task.h"
@@ -93,7 +101,8 @@
#include "sample_ring.h" /* L2 输入:sample_ring_push_frame()(采样环缓冲) */
#include "phasor.h" /* L2 相量层:phasor_run()/phasor_get()(全周傅氏) */
#include "freq.h" /* L2 派生量:freq_run()/freq_get()(测频,B8) */
#include "adc_health.h" /* L1-6:采样有效性校验(静默/丢帧/单调性/错误增量) */
#include "adc_health.h" /* L1-6:采样有效性校验(静默/丢帧/单调性/错误增量/贴轨) */
#include "adc_cal.h" /* L1.5:通道标定折算(「数据可信」卡 2;flags 供 cal 心跳行) */
#include "tpafe5160.h"
#include "task_watchdog.h"
#include "console_log.h"
@@ -122,6 +131,12 @@ static uint32_t s_frames_skip;
static uint32_t s_health_prev;
static uint32_t s_health_logged;
/* L5-2 贴轨编排状态(锁存本体在 adc_health 内,这里只留编排层视角):
* s_rail_edge_latched - 本开机以来出现过锁存边沿的通道位图(心跳 cal: 行展示)
* s_rail_syslogged - SYSLOG 留痕只写一次的闸门(init 重置) */
static uint32_t s_rail_edge_latched;
static uint8_t s_rail_syslogged;
/* 消费者就绪门:由 adcTask 自己在进入等待前置 1;单写者 = 本任务,ISR 只读。
* ⚠ 为什么必须有这道门:TIM3 在 app_main_init()(Src/main.c:140)里就
@@ -159,7 +174,9 @@ void adc_tick_irq_notify(void) {
* 限定条件:仅任务上下文;调用方须保证 dbg_console_writer_ready() 为真
* 函数说明:MicroLIB 的 snprintf **不支持 %f** → 以 ×0.1 定点整数打印
* (打印值 = 实际值 × 10,分辨率 0.1)。逐实例数据驱动输出,
* 实例表加行即自动覆盖。⚠ L1-5 标定未实现 → 读数单位是**原始码值**。
* 实例表加行即自动覆盖。输入自 v3.1 起为 L1.5 折算后的工程量
* (现口径 = ADC 引脚电压 V,见 cal: 心跳行与文件头 v3.1 记录);
* 二次值语义仅在 adc_cal_flags() 出 VALID 后成立。
*/
static void log_phasors(const char *tag) {
uint8_t k;
@@ -246,7 +263,9 @@ int adc_task_init(void) {
s_frames_consumed = 0u;
s_frames_idle = 0u;
s_frames_skip = 0u;
adc_health_init();
s_rail_edge_latched = 0u;
s_rail_syslogged = 0u;
adc_health_init(); /* 内含 L5-2 贴轨观察器复位(rail_init) */
return 0;
}
@@ -319,20 +338,35 @@ void adc_task_func(void *arg) {
* 任一通道读不到(尚未首次成功)→ 本拍**整帧跳过**:不向窗内注入 0 点
* (单个 0 点会显著污染 20 点窗),并计入 skip 观测。 */
if (r == 1) {
float frame[ADC_SEM_COUNT];
uint8_t s;
uint8_t all_ok = 1u;
float frame[ADC_SEM_COUNT];
uint8_t s;
uint8_t all_ok = 1u;
uint32_t rail_edge = 0u;
for (s = 0u; s < (uint8_t)ADC_SEM_COUNT; s++) {
int32_t code = 0;
int32_t code = 0;
int32_t lo_th = 0;
int32_t hi_th = 0;
if (adc_input_raw((adc_semantic_t)s, &code) != 0) {
all_ok = 0u;
break;
}
/* ⚠ L1-5 通道标定尚未实现(缺标准源)→ 暂以**原始码值**充作工程量
* (系数 = 1)。标定落地后此处改为 工程量 = (code + b) × k(定点 2^10)。 */
frame[s] = (float)code;
/* L1.5 折算(「数据可信」卡 2,§4.1 模型):工程量 = (code + b) × k。
* 现口径 = **ADC 引脚电压 V**(eng=0,卡 1 冻结的 ROM 系数,见
* docs/硬件平台/ADC通道映射说明.md §7.1);卡 0b/卡 12 写入标定
* 系数(adc_cal_flags() 出 VALID)后才按二次值消费。TEMP 透传码值
* 不折算(决策 5)。*/
frame[s] = adc_cal_apply((adc_semantic_t)s, code);
/* L5-2 码值越界(贴轨)观察:在**码值域**判定,与标定正交——
* 连续一周波贴轨带且此前见过释放沿才锁存(空载恒 0 不误报)。
* TEMP 无「轨」语义,adc_cal_rail_ths() 返 -1 自然跳过。*/
if (adc_cal_rail_ths((adc_semantic_t)s, &lo_th, &hi_th) == 0) {
if (adc_health_rail_watch(s, code, lo_th, hi_th) != 0u) {
rail_edge |= (1uL << s);
}
}
}
if (all_ok != 0u) {
@@ -344,6 +378,22 @@ void adc_task_func(void *arg) {
} else {
s_frames_skip++;
}
/* 贴轨锁存边沿留痕:帧循环后统一处理(skip 拍也已在循环内逐通道
* 观察过,不因取数半途失败而漏报已观察的通道)。SYSLOG 每开机只
* 留痕一次——贴轨是持续工况,逐拍写会把 selftest.log 刷满 */
if (rail_edge != 0u) {
s_rail_edge_latched |= rail_edge;
if (dbg_console_writer_ready() != 0u) {
DBG_ERROR("rail: edge=0x%lx sticky=0x%lx (code-domain, 20-run+release)",
(unsigned long)rail_edge,
(unsigned long)adc_health_rail_sticky());
}
if (s_rail_syslogged == 0u) {
s_rail_syslogged = 1u;
(void)syslog_post(SYSLOG_SELFTEST, "adc code rail fault");
}
}
}
/* 2b) L1-6 采样有效性校验(每拍,常数次整数运算):把驱动侧观测计数交给
@@ -436,6 +486,45 @@ void adc_task_func(void *arg) {
(unsigned long)adc_health_tpafe_gaps());
log_phasors("hb");
/* L1.5/L5-2 台架观测面(×1000 mV 定点,MicroLIB 无 %f):
* ua/un/ia_mv = 三路 TPAFE 引脚电压毫伏(未标定域,**不是**
* 二次值;卡 12 注入验证的读数落点,偏差判据 ≤2 %)
* vl = VALID 位图(默认构建应恒 0x00——未标定即未标定,
* 卡 5 的闭锁判据在 protect 侧同源消费这个位)
* cl = CLIPPED 位图(V0.1 应恒 0xE6 = UB/UC/IB/IC/IN)
* rail/edge = 贴轨锁存/历史边沿位图(注入饱和电平后应起)*/
{
int32_t c = 0;
long ua_mv = 0L, un_mv = 0L, ia_mv = 0L;
uint32_t vl = 0u, cl = 0u;
uint8_t i;
if (adc_input_raw(ADC_SEM_UA, &c) == 0) {
ua_mv = (long)(adc_cal_apply(ADC_SEM_UA, c) * 1000.0f);
}
if (adc_input_raw(ADC_SEM_UN, &c) == 0) {
un_mv = (long)(adc_cal_apply(ADC_SEM_UN, c) * 1000.0f);
}
if (adc_input_raw(ADC_SEM_IA, &c) == 0) {
ia_mv = (long)(adc_cal_apply(ADC_SEM_IA, c) * 1000.0f);
}
for (i = 0u; i < 8u; i++) {
const uint32_t f = adc_cal_flags((adc_semantic_t)i);
if ((f & ADC_CAL_F_VALID) != 0u) {
vl |= (1uL << i);
}
if ((f & ADC_CAL_F_CLIPPED) != 0u) {
cl |= (1uL << i);
}
}
DBG_INFO("cal: ua_mv=%ld un_mv=%ld ia_mv=%ld vl=0x%02lx cl=0x%02lx rail=0x%03lx edge=0x%03lx",
ua_mv, un_mv, ia_mv,
(unsigned long)vl, (unsigned long)cl,
(unsigned long)adc_health_rail_sticky(),
(unsigned long)s_rail_edge_latched);
}
}
report_at = now + ADC_TASK_REPORT_MS;
}