feat(adc): UN 标定完成、标定脚本归集到 test/host/cal/
- UN 通道 5 点最小二乘标定完成:r=555.382141, b=-1, 残差 0.038%, R²=0.99999967 - UA/IA/UN 三路 VALID 置位,保护解卡 - ROM 默认值更新为当前标定值(ADC_CAL_R_Q16_VOLT=555.144958) - UB/UC/UN 通过 104 写入 ROM 默认值,config.ini 同步 - cal_fit.py 新增 ROLD 表自动读取,不再需要手传 --r-old - 执行卡精简至 126 行,新增 cal_collect.py 采集脚本与 ROLD 填表 - 标定脚本(cal_fit/cal_write/cal_collect)归集到 test/host/cal/ - 全库文档路径引用同步更新(8 文件) - UN 当前态文档改写:明确零序电压 U0 语义、本台架未接线状态 - 删除已失效的外部知识底座文档链接
This commit is contained in:
@@ -195,8 +195,8 @@ static int s_total = 0, s_passed = 0, s_failed = 0;
|
||||
* STM32 侧 UB/UC 是电压族、IB/IC/IN 是电流族(V0.1 双转换器分配,见
|
||||
* docs/硬件平台/ADC通道映射说明.md 与 adc_cal.c 的 ROM 表)。
|
||||
* 容差只需盖住 float32 中间积舍入(相对 ~1e-7)与打印粒度。 */
|
||||
#define EXP_R_VOLT (1000.0) /* 电压族变比 V/V */
|
||||
#define EXP_R_CURR (29.4118) /* 电流族变比 A/V(1927532/65536 的十进制口径) */
|
||||
#define EXP_R_VOLT (555.144958) /* 电压族变比 V/V */
|
||||
#define EXP_R_CURR (29.610748) /* 电流族变比 A/V(1927532/65536 的十进制口径) */
|
||||
|
||||
static double exp_ratio(adc_semantic_t sem) {
|
||||
switch (sem) {
|
||||
@@ -463,14 +463,14 @@ static void tc_024_ratio_defaults(void) {
|
||||
adc_cal_init();
|
||||
/* 电压族 1000.0 V/V、电流族 29.4118 A/V——十进制口径在此独立写出,
|
||||
* Q16 原值由 比值 × 65536 就地算出(1927532 = 29.4118 x 65536 的回落) */
|
||||
CHECK(near_d((double)adc_cal_ratio(ADC_SEM_UA), EXP_R_VOLT), "UA ratio = 1000.0 V/V");
|
||||
CHECK(near_d((double)adc_cal_ratio(ADC_SEM_UN), EXP_R_VOLT), "UN ratio = 1000.0 V/V");
|
||||
CHECK(near_d((double)adc_cal_ratio(ADC_SEM_IA), EXP_R_CURR), "IA ratio = 29.4118 A/V");
|
||||
CHECK(near_d((double)adc_cal_ratio(ADC_SEM_IN), EXP_R_CURR), "IN ratio = 29.4118 A/V");
|
||||
CHECK(near_d((double)adc_cal_ratio(ADC_SEM_UA), EXP_R_VOLT), "UA ratio = 555.144958 V/V");
|
||||
CHECK(near_d((double)adc_cal_ratio(ADC_SEM_UN), EXP_R_VOLT), "UN ratio = 555.144958 V/V");
|
||||
CHECK(near_d((double)adc_cal_ratio(ADC_SEM_IA), EXP_R_CURR), "IA ratio = 29.610748 A/V");
|
||||
CHECK(near_d((double)adc_cal_ratio(ADC_SEM_IN), EXP_R_CURR), "IN ratio = 29.610748 A/V");
|
||||
CHECK(adc_cal_ratio_q16(ADC_SEM_UA) == (int32_t)my_lround(EXP_R_VOLT * 65536.0),
|
||||
"UA ratio q16 = 65536000 (LSB exact)");
|
||||
"UA ratio q16 = 36381980 (LSB exact)");
|
||||
CHECK(adc_cal_ratio_q16(ADC_SEM_IA) == (int32_t)my_lround(EXP_R_CURR * 65536.0),
|
||||
"IA ratio q16 = 1927532 (LSB exact)");
|
||||
"IA ratio q16 = 1940570 (LSB exact)");
|
||||
CHECK(adc_cal_ratio_configured(ADC_SEM_UA) == 1u, "UA ratio configured at boot");
|
||||
CHECK(adc_cal_ratio_configured(ADC_SEM_IA) == 1u, "IA ratio configured at boot");
|
||||
/* TEMP 不参与线性标定;越界通道同守卫 */
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""从串口日志采集 meas: 读数并填入执行卡 CALDATA 表。
|
||||
|
||||
用法:
|
||||
cal_collect.py --doc <执行卡路径> --sem ua --point 1 [--samples N] [--log <日志路径>]
|
||||
|
||||
自动读取 serial.log 最近 N 拍(默认 15),取中位数,写入 CALDATA 表对应行。
|
||||
重复运行同一 --point 会覆盖旧值。
|
||||
"""
|
||||
import argparse
|
||||
import re
|
||||
import statistics
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DEFAULT_LOG = ".dsh/skills/serial-assistant/scripts/state/serial.log"
|
||||
SEM_FIELD = {"ua": "ua_mv", "un": "un_mv", "ia": "ia_mv"}
|
||||
|
||||
|
||||
def read_median(log_path, sem, n_samples=15, min_val=None):
|
||||
field = SEM_FIELD.get(sem)
|
||||
if not field:
|
||||
print("ERROR: unknown sem %r (use ua/un/ia)" % sem)
|
||||
return None
|
||||
text = Path(log_path).read_text(encoding="utf-8", errors="replace")
|
||||
lines = text.strip().splitlines()
|
||||
meas = [l for l in lines if "meas:" in l and field + "=" in l]
|
||||
recent = meas[-n_samples * 2:] if len(meas) >= n_samples * 2 else meas
|
||||
vals = []
|
||||
for l in recent:
|
||||
m = re.search(field + r"=(\d+\.?\d*)", l)
|
||||
if m:
|
||||
v = float(m.group(1))
|
||||
if min_val is not None and v < min_val:
|
||||
continue
|
||||
vals.append(v)
|
||||
if not vals:
|
||||
return None
|
||||
return round(statistics.median(vals))
|
||||
|
||||
|
||||
def update_caldata(doc_path, sem, point, value):
|
||||
text = Path(doc_path).read_text(encoding="utf-8")
|
||||
m = re.search(r"(<!--\s*CALDATA:BEGIN\s*-->.*?<!--\s*CALDATA:END\s*-->)", text, re.S)
|
||||
if not m:
|
||||
print("ERROR: no CALDATA markers in %s" % doc_path)
|
||||
return False
|
||||
block = m.group(1)
|
||||
pattern = r"(\|\s*%s\s*\|\s*%d\s*\|\s*)(\S*|)(\s*\|.*)" % (re.escape(sem), point)
|
||||
new_block, count = re.subn(pattern, r"\g<1>%d\3" % value, block)
|
||||
if count != 1:
|
||||
print("ERROR: expected 1 match for %s point %d, got %d" % (sem, point, count))
|
||||
return False
|
||||
text = text.replace(block, new_block)
|
||||
Path(doc_path).write_text(text, encoding="utf-8", newline="\n")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="采集串口读数填入 CALDATA 表")
|
||||
ap.add_argument("--doc", required=True, help="执行卡文档路径")
|
||||
ap.add_argument("--sem", required=True, choices=["ua", "un", "ia"], help="通道")
|
||||
ap.add_argument("--point", required=True, type=int, help="点序号 (1-5)")
|
||||
ap.add_argument("--samples", type=int, default=15, help="采样拍数 (默认 15)")
|
||||
ap.add_argument("--log", default=DEFAULT_LOG, help="串口日志路径")
|
||||
ap.add_argument("--min-val", type=float, default=None,
|
||||
help="过滤低于此值的读数(切换瞬态)")
|
||||
ap.add_argument("--wait", type=float, default=3.0,
|
||||
help="等待新数据到达的秒数 (默认 3)")
|
||||
a = ap.parse_args()
|
||||
|
||||
if a.point < 1 or a.point > 8:
|
||||
print("ERROR: point must be 1-8")
|
||||
return 1
|
||||
|
||||
time.sleep(a.wait)
|
||||
med = read_median(a.log, a.sem, a.samples, a.min_val)
|
||||
if med is None:
|
||||
print("ERROR: no valid %s data in last %d samples" % (a.sem, a.samples))
|
||||
return 1
|
||||
|
||||
print("%s point %d: %d mV/mA (median of %d samples)" % (a.sem, a.point, med, a.samples))
|
||||
|
||||
if not update_caldata(a.doc, a.sem, a.point, med):
|
||||
return 1
|
||||
|
||||
print("OK: updated CALDATA table in %s" % a.doc)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -308,6 +308,34 @@ def parse_cal_table(path):
|
||||
return groups
|
||||
|
||||
|
||||
def parse_rold_table(path):
|
||||
"""提取 <!-- ROLD:BEGIN --> ~ <!-- ROLD:END --> 之间的 r/b 旧值表。
|
||||
|
||||
列序:路 | r_old | b_old [| 备注],返回 {sem: {"r": float, "b": int}}。
|
||||
标记不存在或表为空返回空 dict(调用方回退到 --r-old / SEM 默认)。
|
||||
"""
|
||||
with open(path, encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
m = re.search(r"<!--\s*ROLD:BEGIN\s*-->(.*?)<!--\s*ROLD:END\s*-->",
|
||||
text, re.S)
|
||||
if m is None:
|
||||
return {}
|
||||
result = {}
|
||||
for line in m.group(1).splitlines():
|
||||
line = line.strip()
|
||||
if not line.startswith("|"):
|
||||
continue
|
||||
cells = [c.strip() for c in line.strip("|").split("|")]
|
||||
sem = cells[0].lower()
|
||||
if sem not in SEM:
|
||||
continue
|
||||
if len(cells) < 3 or not cells[1] or not cells[2]:
|
||||
continue
|
||||
try:
|
||||
result[sem] = {"r": float(cells[1]), "b": int(float(cells[2]))}
|
||||
except ValueError:
|
||||
continue
|
||||
return result
|
||||
def parse_cell(s):
|
||||
if s in ("", "-", "—"):
|
||||
return None
|
||||
@@ -386,9 +414,16 @@ def run_from_doc(a):
|
||||
if not groups:
|
||||
print("EMPTY: CALDATA table has no ua/un/ia rows")
|
||||
return 2
|
||||
# ROLD table in doc overrides --r-old defaults; CLI --r-old still wins if given explicitly
|
||||
rold = parse_rold_table(a.from_doc)
|
||||
defaults = {s: SEM[s]["r_old"] for s in SEM}
|
||||
if rold:
|
||||
for s in rold:
|
||||
defaults[s] = rold[s]["r"]
|
||||
try:
|
||||
r_map = parse_map(a.r_old, {s: SEM[s]["r_old"] for s in SEM}, float)
|
||||
b_map = parse_map(a.b_old, {s: 0 for s in SEM}, int)
|
||||
r_map = parse_map(a.r_old, defaults, float)
|
||||
b_defaults = {s: (rold[s]["b"] if s in rold else 0) for s in SEM}
|
||||
b_map = parse_map(a.b_old, b_defaults, int)
|
||||
except ValueError as exc:
|
||||
print("ERROR: %s" % exc)
|
||||
return 2
|
||||
@@ -282,10 +282,10 @@ static void tc_104_read(void)
|
||||
"TC-RUNPAR-004 k echo last channel (IN, STM32 ROM)");
|
||||
CHECK((run_par_104_read(0x8048u, &v) == 0) && (v == 0.0f),
|
||||
"TC-RUNPAR-004 b echo factory zero");
|
||||
CHECK((run_par_104_read(0x8050u, &v) == 0) && (fabsf(v - 1000.0f) <= 1e-3f),
|
||||
"TC-RUNPAR-004 r echo first slot = ROM voltage family 1000.0");
|
||||
CHECK((run_par_104_read(0x8054u, &v) == 0) && (fabsf(v - 29.4118f) <= 1e-3f),
|
||||
"TC-RUNPAR-004 r echo current family = 29.4118");
|
||||
CHECK((run_par_104_read(0x8050u, &v) == 0) && (fabsf(v - 555.144958f) <= 1e-3f),
|
||||
"TC-RUNPAR-004 r echo first slot = ROM voltage family 555.144958");
|
||||
CHECK((run_par_104_read(0x8054u, &v) == 0) && (fabsf(v - 29.610748f) <= 1e-3f),
|
||||
"TC-RUNPAR-004 r echo current family = 29.610748");
|
||||
CHECK(run_par_104_read(0x8058u, &v) == -1, "TC-RUNPAR-004 above r block not owned");
|
||||
}
|
||||
|
||||
@@ -420,14 +420,14 @@ static void tc_104_ratio(void)
|
||||
/* 选择段:只粗守(NaN/Inf),0.5 这类"执行段会拒"的值在选择段返 0 */
|
||||
CHECK(run_par_104_write(0x8051u, 0.5f, 1u) == 0,
|
||||
"TC-RUNPAR-009 select only guards finiteness (0.5 passes)");
|
||||
CHECK((run_par_104_read(0x8051u, &v) == 0) && (fabsf(v - 1000.0f) <= 1e-3f),
|
||||
CHECK((run_par_104_read(0x8051u, &v) == 0) && (fabsf(v - 555.144958f) <= 1e-3f),
|
||||
"TC-RUNPAR-009 select did not touch the ratio");
|
||||
CHECK(run_par_104_write(0x8051u, 0.5f, 0u) == -1, "TC-RUNPAR-009 execute rejects the same 0.5");
|
||||
CHECK((run_par_104_read(0x8051u, &v) == 0) && (fabsf(v - 1000.0f) <= 1e-3f),
|
||||
CHECK((run_par_104_read(0x8051u, &v) == 0) && (fabsf(v - 555.144958f) <= 1e-3f),
|
||||
"TC-RUNPAR-009 rejected execute left the ratio intact");
|
||||
|
||||
/* 段边界:0x8057 是 r 块末地址,0x8058 起非本段 */
|
||||
CHECK(run_par_104_write(0x8057u, 29.4118f, 0u) == 0, "TC-RUNPAR-009 last r slot writable");
|
||||
CHECK(run_par_104_write(0x8057u, 29.610748f, 0u) == 0, "TC-RUNPAR-009 last r slot writable");
|
||||
CHECK(run_par_104_write(0x8058u, 2.0f, 0u) == -2, "TC-RUNPAR-009 0x8058 passes through to prot_set");
|
||||
CHECK(run_par_104_read(0x8058u, &v) == -1, "TC-RUNPAR-009 0x8058 not owned on read");
|
||||
|
||||
@@ -438,7 +438,7 @@ static void tc_104_ratio(void)
|
||||
"TC-RUNPAR-009 save cleared dirty and wrote file");
|
||||
CHECK(strstr((const char *)s_vfs, "r_ua=131072000") != NULL,
|
||||
"TC-RUNPAR-009 file carries r_ua=131072000 (= 2000 x 65536)");
|
||||
(void)run_par_104_write(0x8050u, 1000.0f, 0u); /* 覆盖内存态 */
|
||||
(void)run_par_104_write(0x8050u, 555.144958f, 0u); /* 覆盖内存态 */
|
||||
CHECK(cfg_file_load() == 0, "TC-RUNPAR-009 engine reload ok");
|
||||
CHECK((run_par_104_read(0x8050u, &v) == 0) && (fabsf(v - 2000.0f) <= 0.01f),
|
||||
"TC-RUNPAR-009 reload restores saved ratio");
|
||||
|
||||
Reference in New Issue
Block a user