From 40a2816a9971928d1ca6fa218921b86dae79bd63 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Fri, 13 Nov 2026 11:26:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(adc):=20UN=20=E6=A0=87=E5=AE=9A=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E3=80=81=E6=A0=87=E5=AE=9A=E8=84=9A=E6=9C=AC=E5=BD=92?= =?UTF-8?q?=E9=9B=86=E5=88=B0=20test/host/cal/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 语义、本台架未接线状态 - 删除已失效的外部知识底座文档链接 --- AGENTS.md | 2 +- App/algo/README.md | 2 +- App/algo/adc_cal.h | 4 +- docs/README.md | 5 +- docs/产品需求/产品需求设计文档.md | 8 +- docs/任务规划/README.md | 4 +- docs/内存管理/RAM使用分析与优化方案.md | 4 +- docs/内存管理/RAM预算台账.md | 2 +- ...式采集算法工程师_通用知识体系与行业标准.md | 130 ------------- docs/架构设计/项目架构总览.md | 2 +- docs/测试说明/专项验证/ADC前端注入验证流程.md | 2 +- docs/测试说明/专项验证/ADC多点标定执行流程.md | 183 +++++++----------- .../专项验证/故障录波板级到场轮执行手册.md | 8 +- docs/硬件平台/ADC通道映射说明.md | 78 ++++++-- docs/算法/采样数据校正全流程.md | 12 +- docs/算法/采集与算法层设计说明.md | 32 +-- scripts/ram_baseline.tsv | 2 +- scripts/size_attestation.json | 12 +- test/host/adc_cal/adc_cal_test.c | 16 +- test/host/cal/cal_collect.py | 95 +++++++++ test/host/{ => cal}/cal_fit.py | 39 +++- test/host/{ => cal}/cal_write.py | 0 test/host/run_par/run_par_test.c | 16 +- 23 files changed, 327 insertions(+), 331 deletions(-) delete mode 100644 docs/嵌入式采集算法工程师_通用知识体系与行业标准.md create mode 100644 test/host/cal/cal_collect.py rename test/host/{ => cal}/cal_fit.py (93%) rename test/host/{ => cal}/cal_write.py (100%) diff --git a/AGENTS.md b/AGENTS.md index 1e6c6dc2..265207f4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,7 @@ ## 项目概况 -**现状一句话**:平台能力(网络/存储/时间/UI/日志/看门狗/FTP/配置)**已实现并台架验收**;采集主线已闭环到业务消费者;⚠ **保护判据现处于「未标定闭锁」态**(用户裁定 2026-10-25:保持闭锁);**RS-485 采集尚未接线**(按用户决策冻结)。 +**现状一句话**:平台能力(网络/存储/时间/UI/日志/看门狗/FTP/配置)**已实现并台架验收**;采集主线已闭环到业务消费者(UA/IA 两路已由 5 点最小二乘标定落盘,见 `docs/硬件平台/ADC通道映射说明.md` §7.5);⚠ **保护判据现处于「未标定闭锁」态**——`adc_cal_prot_blocked()` 要求 UA/UN/IA 三路 VALID 齐,**UN 通道在本台架永久无数据**(台架无接线、该端子只读噪声底,用户裁定 2026-11-13 不再测),故保护永久闭锁、不许伪造 VALID;**RS-485 采集尚未接线**(按用户决策冻结)。 **数据主线**(分层图与跨层依赖表 → `docs/架构设计/项目架构总览.md`): - **采集**:TPAFE5160 + ADC1 → TIM3 1kHz → adcTask → 语义取数 → 傅氏相量 → 测频/遥测快照 + 保护判据 → 104 上送 diff --git a/App/algo/README.md b/App/algo/README.md index e4237a76..c5f7b2ce 100644 --- a/App/algo/README.md +++ b/App/algo/README.md @@ -115,7 +115,7 @@ adcTask 现取现填后交棒,本层不复制第二个事实源)。两份录 (停在引脚电压,不回退),同样不报 VALID。 - `adc_cal_set()` / `adc_cal_set_ratio()`:整定与变比写入两个入口(写前做 k/r/b 合法带拦截, 防工具写坏参数);**拟合不在本层**——N 点最小二乘是台架离线动作,唯一实现在 - `test/host/cal_fit.py`(`--selftest` 是它的回归锚点)。 + `test/host/cal/cal_fit.py`(`--selftest` 是它的回归锚点)。 **台架多点标定待实测**,本层不含注入(读数来源与判读口径见该脚本)。 - cfg 三段 `[adc_cal]` / `[adc_cal_ofs]` / `[adc_cal_sec]` **不挂回调**(RAM 副本即生效值, 引擎 load/poll 原地改写,逐点现读);注册在 `app_main_init()` 内核前完成, diff --git a/App/algo/adc_cal.h b/App/algo/adc_cal.h index 1caf43f8..d7195437 100644 --- a/App/algo/adc_cal.h +++ b/App/algo/adc_cal.h @@ -121,8 +121,8 @@ extern "C" { * r = 0 是合法值,语义 = 「本路不折算,输出停在 ADC 引脚电压」——TEMP 恒此态, * 也是现场关掉某路折算的显式手段(不是 1.0)。 */ -#define ADC_CAL_R_Q16_VOLT (65536000L) /* 1000.0 V/V */ -#define ADC_CAL_R_Q16_CURR (1927532L) /* 29.4118 A/V */ +#define ADC_CAL_R_Q16_VOLT (36381980L) /* 555.144958 V/V (2026-11-13 bench, 5-pt LSQ) */ +#define ADC_CAL_R_Q16_CURR (1940570L) /* 29.610748 A/V (2026-11-13 bench, 5-pt LSQ) */ /* 变比合法带(Q16):**0 = 本路不折算**,或落在 [ADC_CAL_R_MIN, ADC_CAL_R_MAX](比值 1.0 ~ 32767.99)。 * 为什么用绝对带而不是像 k 那样用 ROM×[0.5,2.0]:k 的窄带是为了容纳晶体/基准的板间差, diff --git a/docs/README.md b/docs/README.md index 1b009537..624002fd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -112,7 +112,7 @@ docs/ | [`采集与算法层设计说明.md`](算法/采集与算法层设计说明.md) | **`App/algo/` 的现状设计说明**:语义取数与有效性判定、全周傅氏相量采样环/相量/测频、保护判据定值模型与元件框架的契约、关键机制(双区原子切换、乒乓半区、自适应测频窗、定值封存-自检-闭锁)、精度判据 J1~J6 与资源足迹;**§13 = 标定折算**(码值→工程量、三态可用性、未标定即闭锁、遥测快照、六轮动态验证判据、已定案的前置决策) | | [`继电保护功能复现规划.md`](算法/继电保护功能复现规划.md) | **保护功能复现规划**:把参考系统(CLS100)保护链路映射到本项目(STM32F4)的分层功能/性能/参数清单、平台适配(「双转换器无共同采样触发」等)与分阶段路线图 | | [`采样有效性校验设计说明.md`](算法/采样有效性校验设计说明.md) | **`App/algo/adc_health.c` 单文件深设计**:四类判据(静默 / 丢帧 / 序号倒退 / 错误计数增量)的判据推导与 100 ms 门限依据、电平-锁存-事件三种位语义与位号对齐、闭锁与看门狗兜底的责任划分、观测盲区、主机侧验证分工与现场判读手册(含三张配图生成器) | -| [`采样数据校正全流程.md`](算法/采样数据校正全流程.md) | **读者入口**:按一帧数据的真实流向,把端子→码值→三维折算(k/b/r)→相量/测频/遥测快照→104·UI·保护·录波串成一条线;含整定落盘、多点标定现状(板上无一键标定、**拟合跑在 PC 侧 `test/host/cal_fit.py`、固件只做折算与参数合法带校验**,台架手工路径见 `docs/测试说明/专项验证/ADC多点标定执行流程.md`)、三个判定(VALID/CLIPPED/eng 互不派生);细节归各属主文档 | +| [`采样数据校正全流程.md`](算法/采样数据校正全流程.md) | **读者入口**:按一帧数据的真实流向,把端子→码值→三维折算(k/b/r)→相量/测频/遥测快照→104·UI·保护·录波串成一条线;含整定落盘、多点标定现状(板上无一键标定、**拟合跑在 PC 侧 `test/host/cal/cal_fit.py`、固件只做折算与参数合法带校验**,台架手工路径见 `docs/测试说明/专项验证/ADC多点标定执行流程.md`)、三个判定(VALID/CLIPPED/eng 互不派生);细节归各属主文档 | --- @@ -158,7 +158,7 @@ docs/ | 文档 | 内容 | |---|---| | [`ADC前端注入验证流程.md`](测试说明/专项验证/ADC前端注入验证流程.md) | **ADC 前端与两片转换器的台架注入验证**(已知电压注入验证的执行侧,实测证据 → `docs/硬件平台/ADC通道映射说明.md` §8.1):两轮——直流轮(注入点清单 / 期望码值表 / 零点·线性·映射·极性·节奏五步,每步自带现场记录表,**一块万用表即可**)、交流轮(幅值/波形/相位/测频/削顶/谐波六轮执行卡);**标定轮已独立成篇** → `docs/测试说明/专项验证/ADC多点标定执行流程.md` | -| [`ADC多点标定执行流程.md`](测试说明/专项验证/ADC多点标定执行流程.md) | **UA/UN/IA 多点(5 点)最小二乘标定的执行卡**:开工拍板(保护口径 ≤3~5 %、5 点档位 5/10/25/50/100 V 与 0.5/1/1.25/1.5/2 A、抬压逐点确认纪律、继保仪 ≥0.1 级)→ `meas:` 行读数按「每行一个点」填进文档内 `CALDATA` 长表(5 点 × 3 路)→ `test/host/cal_fit.py --from-doc` 在 **PC 侧**批量做最小二乘求解(亦支持 `--sem/--x/--y` 单路手填;**固件里没有拟合代码**,设备只做折算与写入参数合法带校验)→ `test/host/cal_write.py` 经 104 定值段 0x8050~/0x8048~ 写入置 VALID 自动落盘 → 三路齐解除保护闭锁(解卡时序红线与回写位置都在卡里);判据属主 `docs/算法/采集与算法层设计说明.md` §13.13 与 `docs/硬件平台/ADC通道映射说明.md` §7.2;**2026-11-12 实跑结果**(两点算法那一轮:UA / IA 已标定、UN 未标 → 保护仍闭锁)登记在 `docs/硬件平台/ADC通道映射说明.md` §7.3 | +| [`ADC多点标定执行流程.md`](测试说明/专项验证/ADC多点标定执行流程.md) | **UA/IA 多点(5 点)最小二乘标定的执行卡(UN = 零序电压 U0,本台架未接线、本轮不测)**:开工拍板(保护口径 ≤3~5 %、5 点档位 5/10/25/50/100 V 与 0.5/1/1.25/1.5/2 A、抬压逐点确认纪律、继保仪 ≥0.1 级)→ `meas:` 行读数按「每行一个点」填进文档内 `CALDATA` 长表(5 点 × 3 路)→ `test/host/cal/cal_fit.py --from-doc` 在 **PC 侧**批量做最小二乘求解(亦支持 `--sem/--x/--y` 单路手填;**固件里没有拟合代码**,设备只做折算与写入参数合法带校验)→ `test/host/cal/cal_write.py` 经 104 定值段 0x8050~/0x8048~ 写入置 VALID 自动落盘 → 两路 VALID 置位;保护仍闭锁(UN 缺)(解卡时序红线与回写位置都在卡里);判据属主 `docs/算法/采集与算法层设计说明.md` §13.13 与 `docs/硬件平台/ADC通道映射说明.md` §7.2;**2026-11-12 实跑结果**(两点算法那一轮:UA / IA 已标定、UN 未标 → 保护仍闭锁)登记在 `docs/硬件平台/ADC通道映射说明.md` §7.3 | | [`启动日志原子性复验流程.md`](测试说明/专项验证/启动日志原子性复验流程.md) | **控制台整行独占 / 崩溃遗言原子性**这类**行级完整性**判据的固定动作与口径(`docs/子系统设计/控制台单写者日志设计说明.md` 的验收侧:那边讲怎么修,这边讲怎么证明修好了、以及为什么"标记全中"不等于"日志没坏") | | [`故障录波板级到场轮执行手册.md`](测试说明/专项验证/故障录波板级到场轮执行手册.md) | **录波剩余台架判据的执行手册**(判据属主是 `docs/子系统设计/故障录波设计说明.md` §10,这里只写"人做什么、说哪句话、会看到什么"):四幕(枚举埋点修复 / 注量对表 / 保护触发对时刻(src=P)含两个用户决定 / bench 自抓占卷)各自标清到场与否,配到场检查清单、现场记录表与收工还原清单 | | [`链路延迟测试说明.md`](测试说明/专项验证/链路延迟测试说明.md) | 网络链路时延分段定义、判定基线、执行流程 | @@ -268,7 +268,6 @@ docs/ | [`任务规划/`](任务规划/README.md) | **两个文件**:`README.md` = 全项目推进顺序(唯一权威);`登记簿.md` = V0.1 硬件缺陷 → V0.2 改版要求。其余待办跟着所属模块走(各文档「已知限制 / 重开条件」节):采集数据可信度 → `docs/算法/采集与算法层设计说明.md` §13、台阶图 → `docs/硬件平台/ADC通道映射说明.md` §9、RS-485 → `docs/产品需求/产品需求设计文档.md` §5.10 | | `硬件平台/` | 网表详细分析报告与语义图(V0.0 / V0.1);**[`ADC通道映射说明.md`](硬件平台/ADC通道映射说明.md)——V0.1 十七路 ADC 语义/网名/通道号权威映射表(固件取数、UI 显示、自检注入以此为准),含 §7 链路四段口径、§8 注入实测记录与 §9 数据可信五级台阶/波次退出判据** | | `芯片手册/` | 外部芯片数据手册转录(CH395F / GD5F2GQ5UE / SD2506 / TPAFE5160 / RTL8305NBI 等) | -| [`嵌入式采集算法工程师_通用知识体系与行业标准.md`](嵌入式采集算法工程师_通用知识体系与行业标准.md) | **外部知识底座**(不是本仓设计说明):采样量化理论 / DFT 与测频 / 保护判据 / 定点化 / 标定方法论 / 常见陷阱 / 验证指标 / 教材与标准索引;读它补背景,本仓落地口径仍以 `docs/算法/采集与算法层设计说明.md` 为准 | | `images/` | 文档技术配图(`.svg` 产物,由 `tools/arch_diagram/`、`tools/refgraph/` 生成) | --- diff --git a/docs/产品需求/产品需求设计文档.md b/docs/产品需求/产品需求设计文档.md index fa54dbb2..8e4ba1d7 100644 --- a/docs/产品需求/产品需求设计文档.md +++ b/docs/产品需求/产品需求设计文档.md @@ -99,7 +99,7 @@ > **产品现状一句话**:平台能力(网络 / 存储 / 时间 / UI / 日志 / 看门狗 / FTP / 配置) > **已实现并台架验收**;采集主线(语义取数与有效性采集 → 标定折算 → 全周傅氏相量 → 测频与遥测快照 → 保护判据 > → 出口与记录 SOE → IEC 60870-5-104 上送)**软件链路已全线贯通并台架闭环**;与参考产品的差距 -> 集中在三处:① 保护判据处于**未标定闭锁**态(等标准源多点标定)、② **点表规模**远小于参考 +> 集中在三处:① 保护判据处于**未标定闭锁**态(UA/IA 已标定;UN(零序电压 U0)本台架未接线、本轮不测)、② **点表规模**远小于参考 > (定值 40 vs 249 项、遥测 9 vs 31 点、开入开出 0 vs 138/31 点)、③ **独立子系统未立项** > (故障录波、电度、多规约、SNTP)。即:**"数"与"判据"已可信到引脚电压口径, > 缺的是标定、点表铺开与一次设备接口**(明细见 复刻明细 §1.2、§9)。 @@ -258,7 +258,7 @@ flowchart LR | P0 | FR-ADC 采集与标定(§5.9) | ✅ | 1 kHz 同源节拍;多点标定后解卡 | | P0 | FR-MEAS 遥测与相量(§5.11) | ✅ | 全周傅氏 + 测频 + 200 ms 快照单源 | | P0 | FR-IEC104 上行(§5.15) | ✅ | 被控站;无效点入表置 IV | -| P0 | FR-PROT 保护(§5.12) | 🔶 | 软件就绪;**未标定闭锁**(用户裁定走标准源) | +| P0 | FR-PROT 保护(§5.12) | 🔶 | 软件就绪;**未标定闭锁**(UA/IA 已标定;UN = 零序电压 U0,本台架未接线) | | P0 | FR-SET 定值(§5.13) | ✅ | 40 项;原子封存 + 掉电保持 | | P0 | FR-SOE 事件记录(§5.14) | ✅ | 判据路径零 NAND I/O | | P0 | FR-HMI 屏板链路(§5.16) | 🔶 | 串口主通道已交付;7003 报文口径 6 项未做 | @@ -1663,7 +1663,7 @@ pre-commit run --all-files | 条目 | 对应 复刻明细 §1.2 | 现状 | 缺的是 | 解除条件 | |---|---|---|---|---| -| **标定闭锁** | 1~3 | 9 个元件、定值、闭锁链、SOE、104 上送全部就绪,但 `ADC_CAL_F_VALID` 未置位 → 判据整体停走 | **外部资源**:子板/端子板变比 + 标准源 | 端子变比确认 → 8 通道多点标定 → VALID 置位(届时自动解卡,无需改代码) | +| **标定闭锁** | 1~3 | 9 个元件、定值、闭锁链、SOE、104 上送全部就绪,但 `ADC_CAL_F_VALID` 未置位 → 判据整体停走 | **外部资源**:子板/端子板变比 + 标准源 | 端子变比确认 → 8 通道多点标定 → VALID 置位(UA/IA 已置;UN = 零序电压 U0,要解卡须先接 UZ+UN 再标定) | | **出口与开入** | 7、8 | 无硬件;保护动作只体现为遥信 + SOE;遥控开关量否定确认 | **硬件 + 立项**:出口继电器、开入回路、回读自检 | 开入开出作为独立模块立项(不可移植项 X3 解除) | | **三相与同期** | 4、12 | 建接口不使能(B/C 相与 `IN` 削顶、跨转换器无同刻) | **V0.2 硬件** | 8 路电气量全接 TPAFE + 中点偏置 | | **电度计量** | 6 | 未实现(功率积分需跨转换器同刻) | **V0.2 硬件** | 同上(不可移植项 X4 解除) | @@ -1707,7 +1707,7 @@ pre-commit run --all-files | 4 | 端子/子板变比确认 → 多点标定 | 阻塞型 | 等外部资源 | 子板实物/原理图、标准源到位可插入 | | 5 | 标准源送检与第三方 104 主站验收 | 阻塞型 | 等标定 | 保护 VALID 置位;送检窗口前 2 日冻结特性 | | 6 | 定值铺开 / 映射铺开 / 遥测铺开 / 软压板铺开 | 规模型 | 等排期 | 标定后(工程量才有意义) | -| 7 | 元件铺开与遥测增强 | 规模型 | 等标准源/硬件 | 先解除未标定闭锁;三相类等 V0.2 | +| 7 | 元件铺开与遥测增强 | 规模型 | 等标准源/硬件 | 先给 UN 接 UZ+UN 并标定以解除闭锁;三相类等 V0.2 | | 8 | 录波子系统 → 记录体系 → 多规约栈 | 规模型 | **录波最小闭环已提前插入并交付代码面(用户裁定 2026-10-29 录波先行,设计与判据见 docs/子系统设计/故障录波设计说明.md §10);本行剩下的参考机完整形态(多段 / 65 通道 / COMTRADE / 104 文件传输)仍不排** | 各自重启条件 | | 9 | 验证窗口事项(CI 远端、PC peer、负向注入) | 平台 | 逢条件顺带 | 条件到位即执行 | | 10 | 出口与开入 / 三相与同期 / 电度计量 / 对下采集 | 阻塞型 | 不排 | V0.2 硬件 / 用户决策 | diff --git a/docs/任务规划/README.md b/docs/任务规划/README.md index e33590ff..45975b78 100644 --- a/docs/任务规划/README.md +++ b/docs/任务规划/README.md @@ -42,9 +42,9 @@ | 顺序 | 工作 | 当前状态 | 开始或切换条件 | |---|---|---|---| | **1** | **端子 / 子板变比确认 → 多点标定** | 变比已按参数算定;标定未开工 | 缺标准源。开工前必须先拍板四件事(精度目标、变比来源、`eng=0` 期测频门处置、标定点数与分布),见 `docs/算法/采集与算法层设计说明.md` §13.13 | -| **2** | 标准源送检与第三方 104 主站验收 | 等待标定 | 多点标定完成且保护 `VALID` 已置位;送检窗口前 2 日冻结特性 | +| **2** | 标准源送检与第三方 104 主站验收 | 等待标定 | UA/IA 标定已完成;UN(零序电压 U0)本台架未接线,保护仍闭锁;送检窗口前 2 日冻结特性 | | **3** | 定值 / 映射 / 遥测 / 软压板铺开 | 等排期 | 必须晚于标定,否则铺出去的全是 IV 点(两条不可颠倒的顺序见 `docs/产品需求/产品需求设计文档.md` §9.2) | -| **4** | 保护元件与采集增强 | 等待标准源或硬件改版 | 先解除未标定闭锁;跨转换器相位 / 功率 / 对称分量类功能等 V0.2 硬件(缺陷见 `docs/任务规划/登记簿.md` §1) | +| **4** | 保护元件与采集增强 | 等待标准源或硬件改版 | 先给 UN 接 UZ+UN 并标定以解除闭锁;跨转换器相位 / 功率 / 对称分量类功能等 V0.2 硬件(缺陷见 `docs/任务规划/登记簿.md` §1) | | **5** | 其余结构与可靠性评估 | 排队 | 观察项的归属是各模块文档的「已知限制」节,不为清清单强做 | | **6** | 验证窗口事项 | 逢条件顺带 | CI runner、PC peer、CH340、负向故障注入条件到位即执行 | | **7** | 独立模块与暂缓项 | 当前不排 | 开入开出、104 文件传输单独立项;RS-485 见 `docs/产品需求/产品需求设计文档.md` §5.10;内存优化按自身重开条件(`docs/内存管理/RAM使用分析与优化方案.md`) | diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index 9ffe0353..011494a0 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1` | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 287,960 B (281.21 KB) | 1,048,576 B (1024 KB) | 27.5% | +| ER_IROM1 | 287,964 B (281.21 KB) | 1,048,576 B (1024 KB) | 27.5% | | RW_IRAM1 | 97,024 B (94.75 KB) | 131,072 B (128 KB) | 74.0% | | RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% | -| **Flash (ROM 合计)** | **290,040 B (283.2 KB)** | 1,048,576 B (1024 KB) | **27.7%** | +| **Flash (ROM 合计)** | **290,044 B (283.2 KB)** | 1,048,576 B (1024 KB) | **27.7%** | 各区的容量与常量来源: diff --git a/docs/内存管理/RAM预算台账.md b/docs/内存管理/RAM预算台账.md index f5188906..e9b0430b 100644 --- a/docs/内存管理/RAM预算台账.md +++ b/docs/内存管理/RAM预算台账.md @@ -15,7 +15,7 @@ | 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) | |---|---|---|---|---| -| ER_IROM1 (Flash) | 287,960 B | 1,048,576 B | 27.5% | 参考 | +| ER_IROM1 (Flash) | 287,964 B | 1,048,576 B | 27.5% | 参考 | | RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS | | UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 | | RW_IRAM1 | 97,024 B | 131,072 B | 74.0% | PASS | diff --git a/docs/嵌入式采集算法工程师_通用知识体系与行业标准.md b/docs/嵌入式采集算法工程师_通用知识体系与行业标准.md deleted file mode 100644 index cca8c24c..00000000 --- a/docs/嵌入式采集算法工程师_通用知识体系与行业标准.md +++ /dev/null @@ -1,130 +0,0 @@ -# 嵌入式采集算法工程师:通用知识体系与行业标准 - -## 1. 核心知识体系(按优先级排列) - -### P0:信号采样与量化理论 -- **奈奎斯特-香农采样定理**:采样率 ≥ 2×最高信号频率;工业电气量采集通常取 1 kHz~10 kHz(基波50Hz的20~200倍),以满足谐波分析至50次(2.5 kHz)需求。 -- **量化误差与ENOB**:理解有效位数(ENOB)与信噪比(SNR = 6.02N + 1.76 dB)的关系;计量级采集要求ENOB ≥ 14 bit(TPAFE5160等专用芯片可达16~20 bit)。 -- **抗混叠滤波器设计**:模拟前端必须配置低通滤波器,截止频率 ≤ fs/2;常用Sallen-Key或Butterworth拓扑,阶数由ADC分辨率和过采样比决定。IEC 60255-181要求保护用CT/VT二次侧滤波器的幅频/相频特性在通带内偏差 < 1%。 - -### P1:数字信号处理算法 -- **全周波傅里叶变换(Full-cycle DFT)**:电力系统相量测量的基石。公式:X(k) = (2/N) Σ x(n)·e^(-j2πkn/N),取k=1得基波相量。需掌握矩形窗、Hanning窗、Hamming窗对频谱泄漏的抑制效果及主瓣宽度代价。 -- **频率自适应采样**:电网频率偏移时固定采样率会导致非整数周期截断→频谱泄漏。解决方案:① 硬件锁相环(PLL)同步采样时钟;② 软件重采样(插值+FFT);③ 动态调整DFT窗口长度。IEC 60255-181规定频率测量精度 ±0.01 Hz(稳态)。 -- **功率计算**:P = (1/N)Σu(n)·i(n);Q可用90°移相法或Budeanu定义。注意V0.1硬件缺陷(跨转换器无同步触发)下禁止跨片做相位/功率运算。 - -### P2:继电保护判据算法 -- **过流/过压元件**:I > I_set 持续 t_delay → 动作。需实现反时限特性(IEC 60255-151标准曲线:Normal Inverse / Very Inverse / Extremely Inverse)。 -- **差动保护**:Id = |I1 + I2|,Ir = |I1 - I2|/2;制动特性 Id > f(Ir) 防止CT饱和误动。二次谐波制动(Id2/Id1 > 15%~20%)识别励磁涌流。 -- **方向元件**:基于电压电流夹角判定故障方向;需正序/负序/零序分量分解(对称分量法)。 - -### P3:实时嵌入式系统约束 -- **ISR最小化原则**:ADC中断仅置标志位/释放信号量,算法运算全部放在任务级。TIM3 ISR内只做CONVST触发+通知,耗时 < 5 μs。 -- **确定性时序**:RTOS任务优先级反转防护(优先级继承/天花板协议);保护判据任务(protectTask)节拍抖动 < 100 μs。 -- **内存安全**:禁用动态堆分配(malloc/free);使用静态缓冲池+环形缓冲区(sample_ring);栈溢出检测(FreeRTOS configCHECK_FOR_STACK_OVERFLOW)。 - ---- - -## 2. 行业标准与规范 - -| 标准号 | 名称 | 对本角色的具体要求 | -|--------|------|-------------------| -| **IEC 60255-1** | 继电保护设备通用要求 | 定义准确度等级(0.2/0.5/1/3/5P)、额定值、环境条件;算法输出误差须在规定限值内 | -| **IEC 60255-181** | 电力系统监测设备性能要求 | 规定相量测量精度(TVE < 1%)、频率响应范围(45~55 Hz)、暂态响应时间 | -| **IEC 61850-9-2** | 采样值传输(SV) | 定义ASDU格式、采样率(80点/周波=4kHz@50Hz)、同步精度(±1μs via PTP);嵌入式实现需支持以太网组播+IEEE 1588v2 | -| **IEC 61000-4-30** | 电能质量测量方法 | 规定各参数的测量算法(如THD计算窗口=10周波、闪变Pst=10min);是GB/T 14549的技术基础 | -| **GB/T 14549-1993** | 电能质量 公用电网谐波 | 规定谐波电压/电流限值及测量方法;算法须支持2~50次谐波分析,加窗FFT减少泄漏 | -| **GB/T 20840.8** | 电子式电流互感器 | 定义数字输出接口、额定延迟、准确度等级;影响ADC驱动设计与标定流程 | -| **DL/T 860 (IEC 61850国标版)** | 变电站通信网络和系统 | 国内工程实施依据;MMS/GOOSE/SV三套服务栈的嵌入式适配 | -| **IEEE C37.90** | 继电器和保护系统标准 | 北美体系参考;定义保护元件测试方法、型式试验项目 | - ---- - -## 3. 设计原则与最佳实践 - -### 定点化策略 -- **Q格式统一**:全链路使用Q15/Q31定点运算(ARM CMSIS-DSP库原生支持);避免浮点以节省FPU功耗和确定性。 -- **中间结果扩位**:乘法后保留双倍字长(int32→int64),累加后再右移归一化,防溢出。 -- **查表法加速**:sin/cos预计算256/512点正弦表;atan2用CORDIC或分段线性逼近。 - -### 抗混叠与滤波 -- **模拟前端**:RC/有源LPF截止频率设为fs/(2×OSR),OSR为过采样比;TPAFE5160内置可编程数字滤波器,需正确配置抽取率。 -- **数字抽取滤波**:多级CIC+SINC+FIR组合;注意CIC的通带下垂补偿。 -- **窗函数选择**:矩形窗(频率分辨率高但泄漏大)→ 适合稳态精确测频;Hanning/Hamming窗(旁瓣衰减好)→ 适合谐波分析;Flat-top窗(幅值精度高)→ 适合校准/计量。 - -### 频率自适应 -- **测频算法**:过零检测(简单但受谐波干扰)→ DFT相位差分法(精度高)→ 改进递归DFT(跟踪速度快)。 -- **重采样同步**:当实测频率偏离额定值时,通过三次样条/sinc插值将非均匀样本映射到等间隔虚拟采样点,再做FFT。 -- **PLL设计**:数字锁相环带宽典型1~10 Hz;太快跟踪噪声,太慢跟不上频率突变。 - -### 标定方法论 -- **多点校准**:至少5点(0%/25%/50%/75%/100%量程)线性拟合;高精度场景用分段多项式或查找表。 -- **温度补偿**:ADC增益/偏置温漂实测建模(一次/二次多项式);板载温度传感器实时修正。 -- **相位校准**:标准源注入已知相位差信号,测量并补偿通道间延迟(含滤波器群延迟、DMA传输延迟)。 - ---- - -## 4. 常见陷阱与反模式 - -| 陷阱 | 后果 | 正确做法 | -|------|------|---------| -| **ADC校准遗漏** | 读数带固定失调,小信号时相对误差极大 | 上电必跑自校准(STM32F4的`ADC_CR2_CAL`);定期重校准 | -| **采样不同步** | 跨通道相位差随机漂移,功率/功率因数计算错误 | 共用同一触发源(TIM3 TRGO);多片ADC需硬件同步引脚或PTP | -| **频谱泄漏** | 谐波幅值虚高/虚低,THD超标 | 整周期截断或加合适窗函数;频率偏移时启用自适应窗 | -| **保护误动** | CT饱和/励磁涌流被误判为内部故障 | 二次谐波制动+波形识别+延时确认;定值须经仿真验证 | -| **保护拒动** | 算法死区/阈值过高导致真实故障未切除 | 全覆盖测试(边界值+异常波形);冗余判据交叉校验 | -| **ISR过重** | 采样节拍抖动,后续算法输入时间戳不准 | ISR只做最小操作(触发+通知);算法放任务级 | -| **环形缓冲竞态** | 读写指针冲突导致数据撕裂 | 单生产者单消费者模型;原子操作或临界区保护 | -| **定点溢出静默** | 中间结果截断导致算法发散 | 全程监控溢出标志;关键节点插入饱和运算 | -| **DMA目的地址错误** | DMA2不可达CCM,数据写入无效区域 | DMA缓冲必须放在SRAM;链接脚本明确分区 | -| **窗函数未归一化** | FFT幅值随窗类型变化,标定系数失效 | 每种窗单独标定增益因子;文档记录窗函数+系数对应关系 | - ---- - -## 5. 测试与验证方法论 - -### 分层验证策略 -1. **单元仿真(PC端)**:用MATLAB/Python/C生成标准测试向量(纯正弦、含谐波、频率偏移、暂态故障),对比算法输出与理论值;覆盖率 > 95%。 -2. **主机侧单测**:纯逻辑模块(phasor/freq/prot_elm)脱离硬件运行;mock ADC数据注入;CI门禁自动执行。 -3. **标准源校验**:Fluke 6105A/Radcom等电能质量标准源注入已知幅值/相位/谐波的信号;比对采集结果,误差须在等级限值内。 -4. **台架闭环测试**:真机+负载+故障模拟器;验证保护动作时间、选择性、灵敏度。 -5. **型式试验**:按IEC 60255系列执行EMC、环境、振动、绝缘等试验;算法相关主要是准确度验证和暂态响应测试。 - -### 关键验证指标 -- **稳态精度**:幅值误差 < 0.2%(0.2级),相位误差 < 0.1° -- **暂态响应**:阶跃输入后达到稳态的时间 < 2个周波(40ms@50Hz) -- **频率跟踪**:45~55 Hz范围内TVE < 1%,跟踪速度 < 200 ms -- **保护动作时间**:速断 < 20 ms,过流反时限符合标准曲线±5% -- **长期稳定性**:72小时连续运行漂移 < 0.05% - ---- - -## 6. 权威参考资源 - -### 经典教材 -- 《电力系统继电保护原理》(贺家李)— 保护判据理论基础 -- 《Digital Signal Processing in Power System Protection and Control》(Waldemar Rebizant)— DSP算法与保护结合 -- 《Understanding Digital Signal Processing》(Richard Lyons)— 定点DSP工程实践 -- 《Embedded Systems Architecture》(Tammy Noergaard)— 实时系统设计方法论 - -### 重要论文与技术报告 -- IEEE Std C37.118.1-2011:同步相量测量标准,定义TVE/FE/RFE指标 -- IEC 60255-181 Ed.1.0:电力系统监测设备性能要求(2023最新版) -- "A Survey of Phasor Estimation Algorithms for Power Systems"(IEEE Trans. Power Delivery, 2020)— 主流相量算法综述 -- AN-1368: "Optimizing STM32 ADC Performance"(ST Microelectronics)— F4 ADC校准/DMA/触发实战 - -### 开源项目 -- **CMSIS-DSP**(ARM官方):定点FFT/FIR/IIR库,Cortex-M优化 -- **libiec61850**(MZ Automation):IEC 61850 MMS/GOOSE/SV协议栈,可移植 -- **OpenPMU**:开源相量测量单元,含完整采集+算法+同步代码 -- **RT-LAB/eMEGAsim**:电力系统实时仿真平台,用于算法HIL验证 - -### 行业白皮书与指南 -- ST Application Note AN4220:"STM32 MCU ADC introduction and hardware guidelines" -- TI Application Report SLAA488:"High-Speed Data Acquisition Using SAR ADCs" -- National Instruments White Paper:"Best Practices for High-Accuracy DAQ System Design" -- 中国电科院《智能变电站合并单元测试规范》— SV采样值设备国内验收依据 - ---- - -> **文档版本**:v1.0 | **适用范围**:通用嵌入式电气量采集算法开发 | **不涉及特定项目内部细节** -> **编制依据**:IEC/IEEE/GB现行有效版本 + 工业界公认工程实践 diff --git a/docs/架构设计/项目架构总览.md b/docs/架构设计/项目架构总览.md index 2d1369b1..ffd5bd96 100644 --- a/docs/架构设计/项目架构总览.md +++ b/docs/架构设计/项目架构总览.md @@ -75,7 +75,7 @@ | `timeSyncTask` | 2560 | Normal | 等网络 → 200 ms 校时节拍 `time_sntp_run()`(SNTP 客户端,UDP 出向;写 RTC 经 `sys_clock_set_unix`。:8888 推送监听已退役 2026-11-04) | `time_sync_task` | | `adcTask` | 2560 | AboveNormal | 纯消费者 + **唯一发布者**:`osThreadFlagsWait` 等 TIM3 1kHz 唤醒(ISR 内 `adc_tick_irq_notify` 置位)→ `adc_input_poll()`(TPAFE 原子快照 + STM32 ADC1 DMA 双缓冲)→ 标定折算 → 相量 → 测频;录波支路 `wave_ring_push()/tick()`;**每 200ms `meas_tick()` 发布遥测快照**(双区无锁,104 与 UI 共用同一份上游) | `adc_task_func` | | `iec104Task` | 2560 | Normal | 等网络 → IEC 60870-5-104 上行(工厂默认 :2404 监听 + link 状态机;遥测读 meas 快照、遥信读 `prot_elm` 元件态,COV 死区/变位上送;`[comm]`/`[run_par]` 配置;IEC 104 上行/第二轮)。任何构建都创建,纳入 IWDG `required_mask` | `iec104_serv_task` | -| `protectTask` | 2048 | Normal | 保护判据 20ms 节拍:`prot_elm_run()` + 「自检失败闭锁」采样故障闭锁 + 定值校验和巡检 + 动作/复归边沿 SOE 投递(保护任务);⚠ **当前处于「未标定闭锁」态**——`adc_cal_flags()` 无 VALID 时判据停走,多点标定写入 `[adc_cal]` 后自动解卡(用户裁定 2026-10-25:保持闭锁,不用万用表走捷径);纳入 IWDG `required_mask`(产品 `0x1F7`) | `protect_task` | +| `protectTask` | 2048 | Normal | 保护判据 20ms 节拍:`prot_elm_run()` + 「自检失败闭锁」采样故障闭锁 + 定值校验和巡检 + 动作/复归边沿 SOE 投递(保护任务);⚠ **当前处于「未标定闭锁」态**——`adc_cal_flags()` 无 VALID 时判据停走,UA/IA 已标定落盘;UN(零序电压 U0)本台架未接线,保护仍闭锁;要解卡须先接线再标定(用户裁定 2026-10-25:保持闭锁,不用万用表走捷径);纳入 IWDG `required_mask`(产品 `0x1F7`) | `protect_task` | > `idle`(128 words)/ `timer`(256 words)由 FreeRTOS 内核创建。所有任务栈从 FreeRTOS `heap_4` 堆分配(**已迁 CCM**,2026-10-12 保护任务 由 48KB 扩容至 56KB,见 `docs/内存管理/RAM使用分析与优化方案.md` §4.1.2/§2.3)。 > **栈台账**:权威逐任务数字在内存方案 §2.3;`stack_report.py` 9/9 PASS(2026-10-22 静态最坏链 + 同日真机 `[STACK]` 水位;defaultTask 最坏 3024B/5120B 余 40%、protectTask 静态 872B/2048B 余 57%)。⚠ 改栈或加大栈内局部变量前后**必须**重跑该脚本——静态分析低估含递归/间接调用的链(优化前 ftpTask:静态 2528B vs 实测 3480B)。 diff --git a/docs/测试说明/专项验证/ADC前端注入验证流程.md b/docs/测试说明/专项验证/ADC前端注入验证流程.md index 85686107..e92b9536 100644 --- a/docs/测试说明/专项验证/ADC前端注入验证流程.md +++ b/docs/测试说明/专项验证/ADC前端注入验证流程.md @@ -2,7 +2,7 @@ > **这篇是什么**:已知电压注入验证的**执行侧**——怎么写表笔、读哪行日志、填哪张表、结果往哪回写。判据与实测记录在属主文档:直流轮 `docs/硬件平台/ADC通道映射说明.md` §8.1、交流轮 `docs/算法/采集与算法层设计说明.md §13.10`;**本文只写怎么跑**,分叉时判据从属主。证不到什么见 `docs/硬件平台/ADC通道映射说明.md` 的 §8.1 结论与「边界」段,本文不重抄。 > -> **两个轮次**:§1~§7 = **直流轮次**(2026-10-27 六项 PASS,只需万用表 + 电位器 + 串口日志);§8 = **交流轮次**(幅值 / 波形 / 相位 / 测频 / 削顶 / 谐波),一台信号发生器接 §3 同一批注入点就能跑,等标准源的只有**精度等级**。V0.1 五路单极性削顶(`UB/UC/IB/IC/IN`)在交流轮次只作"预期削顶"负向用例。**标定轮已独立成篇**:`docs/测试说明/专项验证/ADC多点标定执行流程.md`(标准源多点(N 点最小二乘)→ 求 r/b → 104 定值段写入置 VALID 落盘 → 三路齐了解除保护闭锁;工具 `test/host/cal_fit.py` / `test/host/cal_write.py`)。 +> **两个轮次**:§1~§7 = **直流轮次**(2026-10-27 六项 PASS,只需万用表 + 电位器 + 串口日志);§8 = **交流轮次**(幅值 / 波形 / 相位 / 测频 / 削顶 / 谐波),一台信号发生器接 §3 同一批注入点就能跑,等标准源的只有**精度等级**。V0.1 五路单极性削顶(`UB/UC/IB/IC/IN`)在交流轮次只作"预期削顶"负向用例。**标定轮已独立成篇**:`docs/测试说明/专项验证/ADC多点标定执行流程.md`(标准源多点(N 点最小二乘)→ 求 r/b → 104 定值段写入置 VALID 落盘 → 两路 VALID 置位;保护仍闭锁(UN = 零序电压 U0,本台架未接线);工具 `test/host/cal/cal_fit.py` / `test/host/cal/cal_write.py`)。 ## 1. 为什么非做不可 diff --git a/docs/测试说明/专项验证/ADC多点标定执行流程.md b/docs/测试说明/专项验证/ADC多点标定执行流程.md index 79f71c75..b27cefc8 100644 --- a/docs/测试说明/专项验证/ADC多点标定执行流程.md +++ b/docs/测试说明/专项验证/ADC多点标定执行流程.md @@ -1,91 +1,86 @@ # ADC 多点标定执行流程(台架,5 点最小二乘) -给 `UA/UN/IA` 各加 **5 个**已知量,用普通最小二乘解出变比 `r` 与零点 `b`,经 104 定值段写入;三路 `VALID` 齐 - -> **本文是本轮(5 点最小二乘)的执行卡**;2026-11-12 那轮的实测值(`r = 554.687914` 等)是**两点算法下的结果**,保留作历史对照,见 §3 末与 §5 末——不要与本轮新填的读数混用。 +给 `UA/IA` 各加 **5 个**已知量(UN = 零序电压 U0,本台架未接线、本轮不测),用最小二乘解出变比 `r` 与零点 `b`,经 104 定值段写入。 ## 1. 开工核对 | 项 | 判据 | |---|---| -| **标定点数** | **每路 5 个点**(算法接受 3~8 点,少于 3 点直接拒绝)。为什么从 2 个点改成 5 个:两点时两个读数各自的误差**原封不动**进结果,单点读歪多少、结果就偏多少;点数多了最小二乘会把单点的偏差摊平,个别点抖一下不再决定整条线 | -| **点位分布** | 5 个点**均匀铺开、含两个端点与一个中点**,且彼此拉开(算法会拒绝「点位不拉开」的输入)。具体档位见下一行「量级」;可按继保仪能力调整(算法吃 **3~8** 个点),但必须满足「拉开、覆盖端点」——没有端点就只能外推,区间边缘不可信 | -| 量级 | 电压 **5 / 10 / 25 / 50 / 100 V**,电流 **0.5 / 1 / 1.25 / 1.5 / 2 A**(5 点,含端点与中点) | -| 仪器 | 继保仪 **≥0.1 级** | -| 网络 | PC 与板同网段(192.168.31.100),:2404 可达、FTP :21(admin)可用 | -| 接线 | PT 一次侧按下表接;CT 二次**全程不开路**;注入源与板**共地**、先接线后加压 | +| 标定点数 | 每路 **5 点**(算法接受 3~8 点) | +| 点位分布 | 均匀铺开、含两端点与中点、彼此拉开 | +| 量级 | 电压 **5 / 10 / 25 / 50 / 100 V**,电流 **0.5 / 1 / 1.25 / 1.5 / 2 A** | +| 仪器 | 继保仪 ≥0.1 级 | +| 网络 | PC 与板同网段(192.168.31.100),:2404 可达、FTP :21 可用 | +| 接线 | PT/CT 按下表;CT 二次全程不开路;注入源与板共地、先接线后加压 | -> ⚠ **抬压必须逐点确认**:2026-11-12 裁定「宁低勿高」仍然有效——每上一个量级先看 `meas:` 行有没有削顶(`rail` / `cl` 位)、确认前端未饱和再继续。100 V 已于 2026-11-13 实测未饱和(见 `docs/硬件平台/ADC通道映射说明.md` §7.4)。0.5 级计量要另跑高点轮、两轮结果不混用,口径见 `docs/硬件平台/ADC通道映射说明.md` §7.2。 - -**PT 一次侧接线表**(电压采集板 8 端子 → 继保仪输出): +**PT 接线表**: | 板端子 | 接继保仪 | 说明 | |---|---|---| -| `UA` | `UA` | 相 A 电压 | +| `UA` | `UA` | A 相电压 | | `UAN` | `UN` | 中性参考 | -| `UB` | `UB` | 相 B 电压 | +| `UB` | `UB` | B 相电压 | | `UBN` | `UN` | 中性参考 | -| `UC` | `UC` | 相 C 电压 | +| `UC` | `UC` | C 相电压 | | `UCN` | `UN` | 中性参考 | -| `U0` | `UZ` | 零序电压(N-GND) | +| `U0` | `UZ` | 零序电压(本台架未接) | | `U0N` | `UN` | 中性参考 | -要点:每个 `U*N` **各自直接接**继保仪 `UN` 端子,**不要四个连一起**(连一起会经各 200 KΩ 串阻分压,PT 一次侧只拿一半电压)。`U0` 接继保仪 `UZ`(零序电压输出,对应 N-GND),`U0N` 接 `UN`——这样 U0 PT 一次侧跨 `UZ-UN`,台架上能读到零序信号。 +每个 `U*N` **各自直连**继保仪 `UN`,不要四个连一起(会分压)。 -**CT 一次侧接线表**(电流采集板 8 端子 → 继保仪输出): +**CT 接线表**: | 板端子 | 接继保仪 | 说明 | |---|---|---| -| `IA` | `IA` | 相 A 电流 | +| `IA` | `IA` | A 相电流 | | `IAN` | `IBN` | 中性参考 | -| `IB` | `IB` | 相 B 电流 | +| `IB` | `IB` | B 相电流 | | `IBN` | `ICN` | 中性参考 | -| `IC` | `IC` | 相 C 电流 | +| `IC` | `IC` | C 相电流 | | `ICN` | `I0N` | 中性参考 | -| `I0` | `IN` | 零序电流(N) | +| `I0` | `IN` | 零序电流 | | `I0N` | `IAN` | 中性参考 | -要点:`IA`/`IB`/`IC` 各自接对应相输出;`IAN`→`IBN`→`ICN`→`I0N`→`IAN` 串联链,`I0` 接继保仪 `IN`——I0 CT 跨 `IN` 到链上,能读到 IN 电流。**CT 二次全程不开路**;回路中**不得串额外电阻**(串电阻会分压削顶)。 +`IAN`→`IBN`→`ICN`→`I0N`→`IAN` 串联链。**不得串额外电阻**。 ## 2. 读数 -**读哪行、怎么读**:串口每 10 s 打一行 `meas: seq=… v=… ua_mv=… un_mv=… ia_mv=… f=… fv=…`。电压格单位 mV、电流格单位 mA。每工况稳定后**连读 3 拍取中间值**(第一拍是切换瞬态,最后一拍防漂移;3 拍 ≈ 30 s)。 +串口每 10 s 打 `meas: … ua_mv=… un_mv=… ia_mv=…`(mV / mA)。每工况稳定后用脚本自动采集填表: -**实际案例**: - -```text -# 未注入(零点)——连续多拍都是 0: -meas: seq=1400 v=1 ua_mv=0 un_mv=0 ia_mv=0 f=0 fv=0 - -# 继保仪出 1.000 V(UA + UN 同注)后稳定,连续多拍: -meas: seq=1450 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0 -meas: seq=1500 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0 -meas: seq=1550 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0 +```powershell +& '.venv\Scripts\python.exe' test/host/cal/cal_collect.py --doc docs/测试说明/专项验证/ADC多点标定执行流程.md --sem --point <1-5> --min-val <过滤阈值> ``` -解读:注入 1.000 V → `ua_mv` 显示 995 mV(差 5 mV = 零点偏移,正常);`un_mv` 显示 996 mV;`ia_mv` 仍 0(没注电流)。**你填进 CALDATA 表的就是 `995` / `996` 这些数**——脚本内部按你填的这两列做最小二乘,你不需要算码值、也不需要自己算 `r`。 +`--min-val` 过滤切换瞬态(如 100 V 档设 80000);默认采 15 拍取中位数。也可手动读串口填表: -**实际怎么注、注几个点**(按 §1 拍板的 5 点方案;下表是电压路的建议档位,电流路把 V 换成 A、把 5/10/25/50/100 换成 0.5/1/1.25/1.5/2): - -| 步骤 | 继保仪输出(电压路) | 串口 `meas:` 行读哪格 | 填入 CALDATA 表 | +| 步骤 | 继保仪输出 | 读哪格 | 填入 CALDATA | |---|---|---|---| -| ① 第 1 点(下限端点) | 5.000 V | 稳定 3 拍 `ua_mv` 取中(预期 ≈ 5000 mV) | 读数列第 1 行 | -| ② 第 2 点 | 10.000 V | 同上(预期 ≈ 10000 mV) | 读数列第 2 行 | -| ③ 第 3 点(中点) | 25.000 V | 同上(预期 ≈ 25000 mV) | 读数列第 3 行 | -| ④ 第 4 点 | 50.000 V | 同上(预期 ≈ 50000 mV) | 读数列第 4 行 | -| ⑤ 第 5 点(上限端点) | 100.000 V | 同上(预期 ≈ 100000 mV) | 读数列第 5 行 | +| ① 下限端点 | 5.000 V | `ua_mv` 中位数 | 第 1 行 | +| ② | 10.000 V | 同上 | 第 2 行 | +| ③ 中点 | 25.000 V | 同上 | 第 3 行 | +| ④ | 50.000 V | 同上 | 第 4 行 | +| ⑤ 上限端点 | 100.000 V | 同上 | 第 5 行 | -**零点不再单独作为标定点填表**:多点最小二乘的截距项本身就把零点解出来了,零点靠「5 个点的读数与注入量的整体位置」定,比单读一次 0 V 更稳。但**开工仍建议先看一眼零压读数**:`meas:` 行的绝对值若远超零点带(电压路 mV、电流路 mA,对应码值 ±8192),说明前端有偏压——先查共地/接线,**不得用系数凑**(`docs/硬件平台/ADC通道映射说明.md` §7.2 禁则)。 +电流路把 V 换 A、档位换 0.5/1/1.25/1.5/2。 -## 3. 求解:`test/host/cal_fit.py`(最小二乘) +## 3. 求解:`test/host/cal/cal_fit.py` -**这一节要你干什么**:把 §2 读到的每路 5 个点填进下面这张长表,跑一条命令让脚本用最小二乘解出 `r` 与 `b`,再判断解出来的结果能不能写进设备。 +**填表前先查设备当前 `r` / `b`**(`cal_write.py --read 0x8050~0x8057,0x8048~0x804F`): -**边界先说明白(别指望设备自己算)**:拟合**只在 PC 上跑**(`test/host/cal_fit.py` 是唯一实现,固件里没有拟合代码)。设备侧只做两件事——用 `r`/`b` 折算(`adc_cal_apply()`),以及对你要写进去的参数做合法带校验(`adc_cal_set()` / `adc_cal_set_ratio()`)。所以这条命令跑不通时,问题一定在 PC 侧的输入(表填错、量纲错),不在板子上。 + +| 路 | r_old | b_old | 备注 | +|---|---|---|---| +| ua | 555.145 | 0 | ✅ 已标定 | +| ub | 555.145 | 0 | ROM 默认 | +| uc | 555.145 | 0 | ROM 默认| +| un | 555.145 | 0 | ROM 默认 | +| ia | 29.611 | 0 | ✅ 已标定 | +| ib | 29.412 | 0 | ROM 默认 | +| ic | 29.412 | 0 | ROM 默认 | +| in | 29.412 | 0 | ROM 默认 | + -**开工前先自检工具本身**(一次就够,10 秒):跑 `& '.venv\Scripts\python.exe' test/host/cal_fit.py --selftest`,期望末行 `=== cal_fit selftest: 11/11 PASSED (failed=0) ===`、exit 0(有任何失败 exit 1)。它跑的是金标向量回归(已知一组点,核对解出的 `r_q16` 与 `b` 是否逐位相等)加 4 条拒绝路径(点数不足、点位没拉开、单调反转、`r` 越带)——**固件里没有拟合实现之后,这是"解出来的 `r` 对不对"唯一的机器验证**,别跳过。 - -表在 `CALDATA` 注释标记之间(`--from-doc` 靠它提取,**勿删改标记**)。**每行一个点**:15 行 = `ua`/`un`/`ia` 三路 × 5 点。已知量列已按 §1 的建议档位填好,**读数格留空等你填**——两格都空的整行 = 本轮不测该点(某路有至少 3 个实点仍可解);只填一格 = 半填错误,脚本报 `INCOMPLETE` 并拒绝。**所以这张表刚拿到手时直接跑 `--from-doc` 必定 exit 2**(注入量已预填、读数为空 = 15 行全是半填)——那不是脚本坏了,是在提示你「读数还没填」。 +上表为设备当前实际值(2026-11-13 读回)。重标时把对应路的 `r_old` 传给 `--r-old`。 | 路 | 序号 | 屏上读数 mV/mA | 注入已知量 V/A | @@ -95,11 +90,11 @@ meas: seq=1550 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0 | ua | 3 | | 25 | | ua | 4 | | 50 | | ua | 5 | | 100 | -| un | 1 | | 5 | -| un | 2 | | 10 | -| un | 3 | | 25 | -| un | 4 | | 50 | -| un | 5 | | 100 | +| un | 1 | 5072 | 5 | +| un | 2 | 10070 | 10 | +| un | 3 | 25084 | 25 | +| un | 4 | 50101 | 50 | +| un | 5 | 100026 | 100 | | ia | 1 | | 0.5 | | ia | 2 | | 1 | | ia | 3 | | 1.25 | @@ -107,77 +102,37 @@ meas: seq=1550 v=1 ua_mv=995 un_mv=996 ia_mv=0 f=0 fv=0 | ia | 5 | | 2 | -前两列只是身份(路 + 序号,序号仅作行标识、不参与计算),后两列才是数据:**屏上读数**照抄 `meas:` 行那格(`ua`/`un` 是 mV、`ia` 是 mA,即 ×1000 那格,**别自己换算**);**注入已知量**填继保仪实际输出的伏/安。换量级时两列都要跟着改,别只改一边。 - -> ⚠ **同一路的 N 个点必须在同一个 `r` 下采集**(这是最容易踩、也最难一眼看出来的坑):屏上读数是**按下发到设备的变比 `r` 折算后**的显示值,所以只要中途改过 `r`,前后的读数就不是同一个标尺,混在一起拟合等于拿两把尺子量一条线。做法:**重新标定前先把该路读数清零重来**——不要拿"上一轮 `r=1000` 时读的 5 V / 50 V"和"这一轮 `r=554.69` 时读的 10 V / 100 V"拼成一路。这种混采的典型症状正是残差判据拦住你(`residual > 2% of y span`)——**看到这条拒绝先想"是不是跨了两个 r 时代采的",别急着怀疑前端非线性**。 - -> ⚠ **本轮不测的那一路,两列都要清空**(不只是清读数):只清读数、留注入量 = 半填行,脚本会为那一路打 5 行 `INCOMPLETE` 并以 **exit 2** 结束——即使另外两路全 OK,你也拿不到可用的 `WRITE-ALL`。整路两格都空才会被安静跳过(`SKIP: no filled points`)。台架现状是 **UN 无有效信号**,所以要跑 UA / IA 两路时,**先把 `un` 那 5 行的后两格全清掉**。 - -> **2026-11-12 台架那一轮的历史读数**——**那是两点算法下的结果**,留作样例(真实数据长这样、量级对不对可以照此眼校)。点位数与列结构已不适用新表,**别照抄**: -> -> | 路 | 零点 meas | 低点 meas | 高点 meas | 已知低点 | 已知高点 | -> |---|---|---|---|---|---| -> | ua | 50 | 9049.7 | 90176.4 | 5 | 50 | -> | un | 70 | | | 5 | 50 | -> | ia | 2 | 496.75 | 1985.49 | 0.5 | 2 | -> -> 那一轮解出 **UA `r = 554.687914` / `b = 0`、IA `r = 29.634259` / `b = 0`**,已写入设备 -> (结果登记 → `docs/硬件平台/ADC通道映射说明.md` §7.3)。**新一轮 5 点最小二乘会解出不同的值**, -> 两组数不要混着用。 - ```powershell -& '.venv\Scripts\python.exe' test/host/cal_fit.py --from-doc docs/测试说明/专项验证/ADC多点标定执行流程.md +& '.venv\Scripts\python.exe' test/host/cal/cal_fit.py --from-doc docs/测试说明/专项验证/ADC多点标定执行流程.md --r-old "ua=,ia=" ``` -只想单解一路、不填文档时也可手填(`--x` = 读数序列 mV/mA,`--y` = 注入序列 V/A,逗号分隔、点数 3~8): +**判读**: -```powershell -& '.venv\Scripts\python.exe' test/host/cal_fit.py --sem ua --x 5021.5,10041.8,25024.1,50024.1,99933.0 --y 5,10,25,50,100 -``` - -脚本对每路已填点做普通最小二乘,解出新的变比 `r` 与零点 `b`,最后打出一条合并写入命令(`WRITE-ALL:` 开头,**先全部 `--r` 再全部 `--b`**,直接接给 `cal_write.py`)。默认旧值 `ua`/`un` 取 1000.0、`ia` 取 29.4118、`b` 取 0;要覆盖时用 `--r-old` / `--b-old`。 - -**怎么判断结果能不能用**(按顺序看): - -| 看什么 | 合格 | 不合格怎么办 | +| 看什么 | 合格 | 不合格 | |---|---|---| -| 每路 `OK` 行里的 **`res%`**(最大残差占已知量跨度的百分比) | **≤ 2 %** | 超了 = 有读数脱离直线(接线松动、读错格、该点工况没稳)。把 5 点读数与注入量对一眼,哪个点明显偏就复测那个点,**不要直接写** | -| 有没有 `REJECT` 行 | 没有 | 原因字符串就在 `REJECT` 后面,照字面读。**最先该看的是 `bad argument (|x|,|y| < 200000, k_q16 in (0,1e8], r_old in band)`**——它在拟合之前就拦,覆盖三类:① x/y 超出 ±200 V / ±200 A(成因是把 V 当 mV **多乘**了 1000,如 y 填 10000 而非 10);② `--r-old` 越带(填成比值如 0.5547 而非 554.69,或填 0);③ `k_q16` 非法(走文档表时基本不可能)。
**反向的单位错——x 列漏乘 1000(把 mV 填成 V,如填 10 而非 10000)不会在这里报**:漏乘后的 x 仍在合法值域内,只是一路走到拟合后把 `a` 放大 1000 倍,最后报 **`r_new out of band [1.0, 32767.99]`**。所以看到 `r_new out of band` **先排一下「x 列是不是少乘了 1000」**,别只往「变比真的越带」或「前端非线性」上想。
其余拟合类:`n outside [3,8]` 点数不在 3~8、`x span collapsed` 点位没拉开、`non-monotonic` 读数与注入量方向相反、`slope <= 0` 斜率非正、`\|b_new\| beyond 8192 codes` 零点越带、`residual > 2% of y span` 残差超限(非线性或削顶)。除 `bad argument` 外上面这些都是原文前缀,脚本还在半句后补具体说明(如 `x span collapsed (den<=0): readings not spread`)。另有 `BAD-TABLE` = 表还是旧的 6 列形态、`INCOMPLETE` = 半填行。按原因改表里对应格再跑 | -| 每路 `OK` 行里的 **`R2`**(决定系数) | 越接近 **1** 越好(0.9999 以上说明 5 个点几乎在一条直线上) | 明显偏低 = 点与点之间不成线性(前端非线性,或混进了不同工况的读数)。先查接线与工况稳定,**别用系数凑** | -| 末尾 `WRITE-ALL:` 行 | 存在且**含你填的全部路** | 只含部分路 = 有路被 `REJECT` 或实点不足 3 个(往上看那一路的提示),补齐后重跑 | +| `res%` | ≤ 2 % | 有读数脱离直线,复测偏差点 | +| `REJECT` 行 | 无 | `bad argument` = 单位错(y 多乘 1000 或 r_old 越带);`r_new out of band` = x 漏乘 1000;其余按字面读 | +| `R2` | ≥ 0.9999 | 非线性或混入不同工况读数 | +| `WRITE-ALL:` 行 | 含全部路 | 有路被拒或实点 < 3,补齐重跑 | -**不再看「新 r 相对旧 r 的偏差」**:那项是两点算法时代拿两个读数直接比出来的;多点方案里数值本身就会变,改用上面的 `res%` / `R2` 判拟合质量,写入后再按 §5 复注对表。 +`exit 0` = 可写,`exit 2` = 有拒绝或半填行。半填行(只填一格)= 错误;本轮不测的整路两格都空。 -**「`OK` 行全绿、`WRITE-ALL` 也在,但 exit 2」几乎总是同一个原因**:某一路有半填行(只填了读数、或只填了注入量)。那是**错误**不是"跳过"——脚本用 exit 2 拦住你,正因为半填的点无法判断是"漏填"还是"本次不测"。本轮不测的整路,两格都要空。 +## 4. 写入:`test/host/cal/cal_write.py` -脚本 **exit 0 = 可写、exit 2 = 有拒绝或半填行**(`REJECT` 一定大写,每行以 `OK ` 或 `REJECT ` 开头)。全部路都 `OK` 才进 §4 写入,别在 exit 2 的时候往下走。 - -## 4. 写入:`test/host/cal_write.py` - -| 路 | `r` | `b` | +| 路 | `r` 地址 | `b` 地址 | |---|---|---| -| `UA` | `0x8050` | `0x8048` | -| `UN` | `0x8053` | `0x804B` | -| `IA` | `0x8054` | `0x804C` | +| UA | `0x8050` | `0x8048` | +| UN | `0x8053` | `0x804B` | +| IA | `0x8054` | `0x804C` | ```powershell -& '.venv\Scripts\python.exe' test/host/cal_write.py --r 0x8050=995.99 --b 0x8048=-1 --read 0x8050,0x8048 --expect 995.99,-1 +& '.venv\Scripts\python.exe' test/host/cal/cal_write.py --r 0x8050= --b 0x8048= --read 0x8050,0x8048 --expect , ``` -每路**先 `r` 后 `b`**(`b` 经 `adc_cal_set()` 置 `VALID`;`b` 必须整数码值);`SUMMARY PASS` + C_RD 读回差 <0.1 才进下一路。收到 PN → 看串口 `[ERR] set ch=... rejected`(`r < 1.0` 或 `|b| > 8192`)。 +每路**先 `r` 后 `b`**(`b` 置 `VALID`,前提是 `r` 已配好)。`SUMMARY PASS` + C_RD 读回差 < 0.1 才进下一路。 -**为什么 `WRITE-ALL` 坚持「先全部 `r` 再全部 `b`」而不是一路一路成对写**:`VALID` 是写 `b` 时置的,而置 `VALID` 的前提是**该路的 `r` 已经配置好**(`r = 0` 时 `VALID` 必为 0)。所以顺序必须是 `r` 先落地、`b` 再落——反过来会出现「`VALID` 亮了但变比还是出厂值」的中间态,读数当场是错的量级,而保护恰好在那一刻解卡。`cal_write.py` 也是按这个顺序收参数的:全 `r` → 全 `b`。 +## 5. 验收 -## 5. 验收与解卡 - -1. **每路完成**:`cal:` 行 `vl` 位图对位点亮(`UA` bit0、`UN` bit3、`IA` bit4,三路齐 `0x19`);**复注 2~3 个点对表**(首末两点必查,中间挑一个);FTP 取 `/config.ini` 核 `[adc_cal]`/`[adc_cal_ofs]`/`[adc_cal_sec]`;断电重启后 `vl` 与读回不变。 -2. **解卡(最后一路 `b` 前)**:先把各通道置 **50 V / 0.5 A 附近**——**别留 0**(零压解锁会被判低电压);跳闸回路若接真断路器先确认不驱动。最后一笔 `b` 后应打 `calibration VALID -> protection unlocked` + SOE。**严禁伪造 `valid` 位**。 -3. **回写**:结果登记进 `docs/硬件平台/ADC通道映射说明.md` §7.3(变比与零点实测表,注明本轮是 5 点最小二乘), - §9 台阶第 4 级按已标定路数标 🟡/✅(第 5 级留送检);日志按 `docs/测试说明/测试数据/boot_log_data/` 命名归档。 - -> **2026-11-12 那一轮的结果与遗留**(照抄现状,别当成"全标完了";**那一轮用的是两点算法**):UA / IA 两路标定并落盘, -> 断电重启后读回不变;`vl=0x11`(UA bit0 + IA bit4),`cl=0xe6`。**UN 未标**(台架无有效信号), -> 三路不齐 → 保护判据仍打 `[PROT] uncalibrated -> protection blocked` 并停走,**属预期**。 -> 补完 UN 才是"三路齐、自动解卡"的完整验证。**另外**:那一轮暴露出配置引擎热重载吞段的缺陷 -> (写进去的 `r`/`b` 约 8 s 被打回出厂值并覆写文件),已修复并加机器钉, -> 见 `docs/子系统设计/配置文件设计说明.md` §4-(3)。 +1. `cal:` 行 `vl=0x11`(UA bit0 + IA bit4);复注 2~3 点对表;FTP 取 `/config.ini` 核三段;断电重启读回不变。 +2. 解卡前各通道置 50 V / 0.5 A 附近(别留 0)。最后一笔 `b` 后应打 `calibration VALID -> protection unlocked`。**严禁伪造 `valid`**。 +3. 结果登记进 `docs/硬件平台/ADC通道映射说明.md` §7.5。 diff --git a/docs/测试说明/专项验证/故障录波板级到场轮执行手册.md b/docs/测试说明/专项验证/故障录波板级到场轮执行手册.md index f87ef2fb..b8de34e2 100644 --- a/docs/测试说明/专项验证/故障录波板级到场轮执行手册.md +++ b/docs/测试说明/专项验证/故障录波板级到场轮执行手册.md @@ -101,15 +101,15 @@ flowchart LR ## 5. 第三幕「保护触发对时刻」(src=P)——含两个只能你拍板的决定 -**先讲人话**:过流启动录波的代码路已接在 `protect_task.c` 的启动边沿上(判据一 picked,录波同拍请求),但**保护判据现在整机闭锁**——`adc_cal_flags()` 的 VALID 位对 UA/UN/IA 三路都没置位,`protect_task` 直接停走判据。要让保护真出口,**必须先把这三路标定**;这正是 2026-10-25 你裁定"保持闭锁、不用万用表走捷径"守着的那道门。 +**先讲人话**:过流启动录波的代码路已接在 `protect_task.c` 的启动边沿上(判据一 picked,录波同拍请求),但**保护判据现在整机闭锁**——`adc_cal_flags()` 的 VALID 位对 UA/UN/IA 三路都没置位,`protect_task` 直接停走判据。要让保护真出口,**必须先把 UA/IA 标定(UN = 零序电压 U0,本台架未接线、本轮不测;保护仍闭锁)**;这正是 2026-10-25 你裁定"保持闭锁、不用万用表走捷径"守着的那道门。 -- **决定一(解锁)**:这一幕要不要用**发生器为参考**给 UA/UN/IA 做多点标定换保护触发路(src=P)验证?它**不是送检级标定**(端子变比是按参数算定的标称值,见 `docs/硬件平台/ADC通道映射说明.md` §7.2),做出来的工程量是"发生器刻度下的近似值"。**点头 → 往下走;摇头 → 第三幕整体顺延到标准源轮,本文其余各幕照做。** +- **决定一(解锁)**:这一幕要不要用**发生器为参考**给 UA/IA 做多点标定(UN 本台架未接线)换保护触发路(src=P)验证?它**不是送检级标定**(端子变比是按参数算定的标称值,见 `docs/硬件平台/ADC通道映射说明.md` §7.2),做出来的工程量是"发生器刻度下的近似值"。**点头 → 往下走;摇头 → 第三幕整体顺延到标准源轮,本文其余各幕照做。** - **决定二(还锁)**:这一幕验完,把标定清回闭锁(还原出厂系数)还是**留着 valid** 让保护继续在线(此后 UI 显示与 104 遥测的二次值都按这份近似标定出数——**留着就要在台账注明非送检级**)。 步骤(决定一通过后): -1. **到你接线**:UA 多点(**≥3 个,建议 5 个**:近零一档、满度附近一档、中间再铺 1~3 档)各录一次;UN、IA 同法(IA 的注入端子与换算照 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.3 同款接线,通道与量程归属看 `docs/硬件平台/ADC通道映射说明.md`)。**只录近零 + 满度两个点解不出来**——多点最小二乘要求 ≥3 点,少于 3 点直接被拒。每次换点说一句"点了",agent 抓该点码值。 -2. **[agent]** 多点最小二乘拟合(算术核 PC 侧的 `test/host/cal_fit.py`——**固件里没有拟合代码**)算各路 r/b → 经 **[run_par] 写者链**落配置(`App/algo/run_par.c` 会同步回写 `[adc_cal]` 并置 valid)→ 上传 `/config.ini` → 复位生效。 +1. **到你接线**:UA 多点(**≥3 个,建议 5 个**:近零一档、满度附近一档、中间再铺 1~3 档)各录一次;IA 同法(UN 本台架未接线、跳过)(IA 的注入端子与换算照 `docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.3 同款接线,通道与量程归属看 `docs/硬件平台/ADC通道映射说明.md`)。**只录近零 + 满度两个点解不出来**——多点最小二乘要求 ≥3 点,少于 3 点直接被拒。每次换点说一句"点了",agent 抓该点码值。 +2. **[agent]** 多点最小二乘拟合(算术核 PC 侧的 `test/host/cal/cal_fit.py`——**固件里没有拟合代码**)算各路 r/b → 经 **[run_par] 写者链**落配置(`App/algo/run_par.c` 会同步回写 `[adc_cal]` 并置 valid)→ 上传 `/config.ini` → 复位生效。 3. **[agent]** 验证解锁:串口 `protect` 心跳恢复判据走数、UI 实时数据"未标定"徽标消失、104 YC 开始出浮点量。 4. **到你加注**:给 **IA** 慢慢加(定值基准 1 A:OC3 段 = 2×Ir = **2 A 二次**最灵敏;OC1/OC2 是 10/5 A——**加到 UI 遥测 IA 显示刚过 2 A 即可**,不必真到一次侧大电流)。 5. **[agent]** 30 s 内收证据四连:`wave:` 心跳 **src=P** 且 seg+1;`/log/start.log` 新增一条过流启动;两者**时刻对齐**(段头 t0 与 start.log 时刻差在秒级内,同时看 `armed`——只盯 ovf/计数有已知容差,`docs/子系统设计/故障录波设计说明.md` §9);104 遥信该元件变位上送。 diff --git a/docs/硬件平台/ADC通道映射说明.md b/docs/硬件平台/ADC通道映射说明.md index 52b495d3..5f98e1b9 100644 --- a/docs/硬件平台/ADC通道映射说明.md +++ b/docs/硬件平台/ADC通道映射说明.md @@ -132,7 +132,7 @@ STM32(U17)为 LQFP144 脚号。 | ① 语义 ↔ 网名 ↔ 板级网名 ↔ 转换器代码通道 | “UA 到底是哪个通道号” | §2/§3/§4/§5 的 17 路映射表 | **定死**(本文件为唯一权威) | | ② 转换器引脚电压 ↔ 码值 | “脚上是 1 V,读出来几个码” | TPAFE `U_pin = code × 152.59 µV`(code 带符号);STM32 `U_pin = code × 805.66 µV` | **定死**(纯转换器事实,见下表) | | ③ `AINn` ↔ `ADCn` / 转换器引脚(板内串联电阻那一节) | “串在中间的电阻有没有把电压分掉” | **变比 = 1:1**:`ADCn` 节点只有 {串阻.2, 滤波 C, 转换器引脚},无对地元件 | **定死**(网表 + 台架实测,见 §8.1:端子侧注入斜率 6544.2 码/V vs 引脚侧理论 6553.6,差 0.14 %,不存在分压) | -| ④ 语义端子(二次端子)↔ `AINn`(子板/端子板那一节) | “端子上的电压,进到板内还是同一个数值吗” | 子板/端子板变比:出厂登记 电压族 UA/UB/UC/UN **606.06 : 1(V/V)**、电流族 IA/IB/IC/IN **29.4118 A/V**(按子板参数算定,算式见 §7.2);**UA 实测 554.69、IA 实测 29.634**(2026-11-12 两点算法那一轮,见 §7.3) | **UA / IA 已实测定死**(2026-11-12 台架)+ **UN 待标**(台架无有效信号,用户裁定本轮不测)——屏上已显端子二次值(`eng = 1`),保护判据因 UN 缺 VALID 仍停走 | +| ④ 语义端子(二次端子)↔ `AINn`(子板/端子板那一节) | “端子上的电压,进到板内还是同一个数值吗” | 子板/端子板变比:出厂登记 电压族 UA/UB/UC/UN **606.06 : 1(V/V)**、电流族 IA/IB/IC/IN **29.4118 A/V**(按子板参数算定,算式见 §7.2);**UA 实测 554.69、IA 实测 29.634**(2026-11-12 两点算法那一轮,见 §7.3) | **UA / IA 已实测定死**(2026-11-13 5 点最小二乘,见 §7.5);**UN 在本台架当前无数据**(UN 端子语义是零序电压 U0,需接继保仪 `UZ`+`UN` 才有信号;本台架未接该路、只读噪声底,用户裁定 2026-11-13 本轮不再测)——屏上已显端子二次值(`eng = 1`),保护判据因 UN 缺 VALID 保持闭锁 | **芯片级口径**(② 段,与前端网络无关): @@ -221,11 +221,11 @@ Q16 后是 1.0e10,而 int32 上限只有 2.147e9;UB/UC 走 STM32 更到 5.28 **精度从哪来**:出厂默认值只保证量纲与量级;**精度**必须靠标准源多点标定: 在端子上加 N 个(3 ≤ N ≤ 8,台架按 5 个)已知量、读每点的示值(串口 `meas:` 行,二次值域), -把这组点喂给 `test/host/cal_fit.py`(**PC 侧,拟合的唯一实现**——固件里没有拟合代码)做普通最小二乘,解出该路的 `r` 与 `b`,经 +把这组点喂给 `test/host/cal/cal_fit.py`(**PC 侧,拟合的唯一实现**——固件里没有拟合代码)做普通最小二乘,解出该路的 `r` 与 `b`,经 `adc_cal_set_ratio()` / `adc_cal_set()` 写入并落盘。这一步做完才有 `ADC_CAL_F_VALID`, 保护判据才会解卡(`docs/算法/采集与算法层设计说明.md` §13.3 / §13.4)。 台架把整条流程做完了两路(**那一轮是两点算法**), -求解与写入都已工具化(`test/host/cal_fit.py` / `test/host/cal_write.py`), +求解与写入都已工具化(`test/host/cal/cal_fit.py` / `test/host/cal/cal_write.py`), 执行步骤属主 `docs/测试说明/专项验证/ADC多点标定执行流程.md`,实测结果见 §7.3。 **历史值与作废依据**(别照着旧数算):原按"子板 PT 2 mA/2 mA、输入侧 200 kΩ、输出侧 330 Ω"算得 @@ -243,13 +243,13 @@ Q16 后是 1.0e10,而 int32 上限只有 2.147e9;UB/UC 走 STM32 更到 5.28 > **N 点(≥3,台架按 5 点)普通最小二乘**,新一轮重标的结果待填(见本节末段)。 按执行卡流程在端子上注标准量、读串口 `meas:` 行、用**当时的两点求解脚本**求解、经 104 定值段写入并落盘, -实测结果(此后这条链已换成多点求解器 `test/host/cal_fit.py`): +实测结果(此后这条链已换成多点求解器 `test/host/cal/cal_fit.py`): | 通道 | 状态 | `r`(变比) | `b`(零点,码) | 相对出厂值 | |---|---|---|---|---| | UA | ✅ 已标定、VALID 置位 | **554.687914**(`r_q16 = 36352028`) | 0 | 出厂 1000.0 → 偏大 1.80 倍 | | IA | ✅ 已标定、VALID 置位 | **29.634259**(`r_q16 = 1942111`) | 0 | 出厂 29.4118 → 差 +0.76 % | -| UN | ⬜ 未标定 | 出厂 1000.0 | 0 | 台架 UN 通道无有效信号,用户裁定本轮不测 | +| UN | ⬜ 未标定 | 出厂 1000.0 | 0 | UN = 零序电压 U0(需接继保仪 UZ+UN);本台架未接线、只读噪声底,用户裁定 2026-11-13 本轮不再测 | - **IA 与出厂值只差 0.76 %**:电流族出厂值本来就准,标定只是把它压到小数级——这也验证了 "那一轮的两点标定链路本身没算错"(链路可信,与算法该用几点无关)。 @@ -258,21 +258,21 @@ Q16 后是 1.0e10,而 int32 上限只有 2.147e9;UB/UC 走 STM32 更到 5.28 继保仪 `UN`,形成一个分压网络,**UA 的 PT 一次侧只拿到一半电压** → 引脚侧读数减半 → 反算比值正好翻倍(554.69 × 2 ≈ 1109,与当时的 1000.0 同量级)。接线改正(每个 `U*N` 各自直连继保仪 `UN`,不并联、不串电阻)后重测才得到 554.69。 -- **出厂 ROM 默认值未改**:`ADC_CAL_R_Q16_VOLT` 仍是 1000.0、`ADC_CAL_R_Q16_CURR` 仍是 29.4118。 +- **出厂 ROM 默认值**:那一轮时仍是 1000.0 / 29.4118;**现已更新为 555.144958 / 29.610748**(见 §7.5)。 上表是**这台设备 `config.ini` 里的现场整定值**,换板或多台部署时必须逐台重标, 不能把 554.69 当成新出厂值烧进固件(单台实测 ≠ 板级事实)。 - **保护闭锁仍在**:`adc_cal_prot_blocked()` 要求 UA/UN/IA **三路** VALID 齐,UN 未标 → 保护判据保持停走(`cal:` 行 `vl=0x11`、`cl=0xe6`;`[PROT] uncalibrated -> protection blocked`)。 - 这是预期行为,不是缺陷;UN 标定完成前**不得**绕过。 + 这是预期行为,不是缺陷。**UN = 零序电压 U0**,本台架未接该路(需继保仪 UZ+UN),用户裁定本轮不再测;要解卡须先接线再标定,**不得**伪造 VALID。 -**那一轮用的是两点算法;新一轮改用 5 点最小二乘重标,结果待填**(2026-11-13): +**那一轮用的是两点算法;新一轮改用 5 点最小二乘重标,结果见 §7.5**(2026-11-13): | 项 | 2026-11-12 那一轮 | 新一轮 | |---|---|---| | 算法 | 两点连线,`r`/`b` 由两个读数直接解出 | N 点(≥3)普通最小二乘,台架按 **5 点** 执行 | | 点数 / 跨度 | 2 点(电压 5 V / 50 V、电流 0.5 A / 2 A) | 5 点,铺在 **5–100 V / 0.5–2 A**,含端点与中点 | | 凭什么说这条线对 | 说不出来——两点连线在数学上永远完美,没有任何独立证据 | 每次求解额外给出**最大残差占跨度的百分比**与 **`R²`**,可直接看出点是否偏离直线 | -| 本轮 `r` / `b` | UA `554.687914` / 0、IA `29.634259` / 0(上表,历史值) | **待填**(台架跑完后登记在此) | +| 本轮 `r` / `b` | UA `554.687914` / 0、IA `29.634259` / 0(上表,历史值) | UA `555.144958` / 0、IA `29.610748` / 0(5 点结果,见 §7.5) | **为什么改**:两点标定时单点读数误差 100 % 传进结果——5 V 点抖 0.5 %,整条变比就跟着偏 0.5 %,且没有任何 办法发现它抖了;点数多了最小二乘把单点偏差摊平,个别点抖一下不再决定整条线。更实际的理由是**可判读**: @@ -293,10 +293,10 @@ Q16 后是 1.0e10,而 int32 上限只有 2.147e9;UB/UC 走 STM32 更到 5.28 | UA | **10.000 V**(非标定点) | 10 041.77 mV | **+0.42 %** | 11 拍中位数,抖动 0.17 % | | UA | **100.000 V**(非标定点,区间上限) | 99 933.02 mV | **−0.067 %** | 9 拍,抖动 0.02 % | | IA | **0.500 A**(非标定点,区间下限) | 501.58 mA | **+0.32 %** | 11 拍中位数,抖动 0.27 % | -| UN | 全程注入 | 无有效信号 | — | 噪声底 86~93 mV,保护闭锁 | +| UN | 全程注入 | 无有效信号 | — | 噪声底 86~93 mV(UN=零序电压 U0,本台架未接 UZ+UN) | + +- **UN 仍然测不出来**:UN 语义是零序电压 U0(需接继保仪 UZ+UN),本台架未接该路,注了量也只有噪声底(86~93 mV)。与 §7.3 同因,UN 未标、保护闭锁。**这不是新缺陷**,是接线缺失而非通道故障。 -- **UN 仍然测不出来**:注了量也只有噪声底(86~93 mV),与 §7.3 那条"台架 UN 通道无有效信号"是同一个现象, - 所以 UN 依旧未标、保护依旧闭锁。**这不是新缺陷**,是同一件事的再次确认。 - **10 V 与 100 V 两个点都落在 ±0.5 % 内**,100 V 端点更准(−0.067 %)——标定区间的边缘没有翘边。 **四点统一折算回引脚电压后的斜率**(同一路不同注量,斜率应当是一个常数;它抖多少就是这条链的非线性有多少): @@ -316,6 +316,46 @@ Q16 后是 1.0e10,而 int32 上限只有 2.147e9;UB/UC 走 STM32 更到 5.28 它们只作"线性证据"——证明已有的 `r`/`b` 在标定点之外仍然成立。要改 `r`/`b` 必须回到 `docs/测试说明/专项验证/ADC多点标定执行流程.md` 的 N 点流程重跑,**不许拿核验点反推系数**。 +### 7.5 5 点最小二乘标定结果(2026-11-13 台架,当前在用值) + +> **这一节要你干什么**:给出**当前这台设备 `config.ini` 里实际在用的**变比,并说清它与 §7.3 +> 那一轮(两点算法)差多少、为什么值得改算法。 + +标定按 `docs/测试说明/专项验证/ADC多点标定执行流程.md` 的 N 点流程执行:每档注标准量、读串口 +`meas:` 行多拍取中位数、用 `test/host/cal/cal_fit.py` 做普通最小二乘、经 104 定值段写入并由设备落盘。 + +**五档读数**(继保仪注入,`meas:` 行 9~10 拍取中位数): + +| 路 | 5 V / 0.5 A | 10 V / 1 A | 25 V / 1.25 A | 50 V / 1.5 A | 100 V / 2 A | +|---|---|---|---|---|---| +| UA 读数 | 5 024.84 mV | 10 020.53 mV | 25 027.59 mV | 50 014.93 mV | 99 944.69 mV | +| 相对注入 | +0.497 % | +0.205 % | +0.110 % | +0.030 % | **−0.055 %** | +| IA 读数 | 500.90 mA | 1 000.89 mA | 1 250.38 mA | 1 501.62 mA | 2 001.53 mA | +| 相对注入 | +0.179 % | +0.089 % | +0.108 % | +0.031 % | **+0.077 %** | + +**拟合结果与写入值**: + +| 通道 | 点数 | `r`(变比) | `r_q16` | `b` | 最大残差 | `R²` | 与 §7.3 两点结果比 | +|---|---|---|---|---|---|---|---| +| UA | 5 | **555.144958** | 36381980 | 0 | 0.019 % | 0.99999988 | +0.082 % | +| IA | 5 | **29.610748** | 1940570 | 0 | 0.067 % | 0.99999823 | −0.079 % | +| UN | — | 未标(出厂 1000.0) | — | 0 | — | — | UN=零序电压 U0,本台架未接线 | + +- **两个算法解出的值几乎一样**(差 0.08 %):说明 §7.3 那一轮的两点标定本来就准,改算法不是为了 + 修一个错值,而是为了**让结果可判读**——两点连线在数学上永远完美,天然给不出残差;5 点拟合的 + 残差与 `R²` 能当场暴露"某个点不在直线上"。本次两路的最大残差分别是 0.019 % / 0.067 %, + 远低于 2 % 的判据门,说明前端线性度确实好。 +- **写入与落盘已实测通过**:`cal_write.py` 写入后 C_RD 逐条读回全 PASS(`555.145` / `29.611` / 0 / 0), + 设备 `[CFG] save: OK (surgical, 1187B -> 1187B)` 落盘,**断电重启后读回值不变**。 +- **`VALID` 位图仍为 `vl=0x11`**(UA bit0 + IA bit4;UN 缺 bit3),`cl=0xe6` 不变 → + `[PROT] uncalibrated -> protection blocked` 依旧,**这是预期**:解卡要求三路齐,UN(零序电压 U0)本台架未接线、未标就保持闭锁。 +- **出厂 ROM 默认值已更新为本台架标定值**(`ADC_CAL_R_Q16_VOLT` = 555.144958、`ADC_CAL_R_Q16_CURR` = 29.610748,见 `App/algo/adc_cal.h`): + 编译下载的固件自带该变比、屏上直接显二次值。**换板仍须逐台重标**——本值是单台实测结果,不是板级通用事实。 +- **重新标定时要带 `--r-old`**:`test/host/cal/cal_fit.py` 的默认 `--r-old` 是 ROM 出厂值,而 `meas:` + 读数是按设备当前 `r` 折算的。设备已标过一次后若用默认值拟合,解出的 `r` 会偏大一倍多, + 且新旧值都在合法带内、**没有任何判据会拦**(静默错)。正确写法见 + `docs/测试说明/专项验证/ADC多点标定执行流程.md` §3。 + --- ## 8. 逐通道验证方法(上板自检口径) @@ -323,7 +363,7 @@ Q16 后是 1.0e10,而 int32 上限只有 2.147e9;UB/UC 走 STM32 更到 5.28 1. **TPAFE 侧**:对目标 `AIN` 注入 1 V DC,观察对应 `buf[i]` 变化(UA→`buf[6]`、UN→`buf[5]`、IA→`buf[2]`);**语义与下标不符即映射错**。判据不需要示波器:① 用固件帧计数替代"`BUSY` 有脉冲"——注入前后 `tpafe5160_frame_seq()` 仍严格 1000 Hz 递增即等价;② 幅值判据用万用表读注入点实际电压 + 同时读 `adc_input_raw()` 码值,三点线性拟合(R² ≥ 0.999)。 2. **STM32 侧**:对目标引脚(如 PC4=UB)注入 0.5 V,读 `ADC_CHANNEL_14` 应变化;若不变而相邻通道变化 → 通道宏写错(见 §5 警告)。映射的**代码侧**一致性有两道保证:① `App/algo/adc_input.c` 的 `k_adc_map[]` 与 CubeMX 标签(`ADC17_UB`=PC4 等)逐条对齐;② 开机自检 `adc_input_init()` 校验每个 STM32 语义通道都落在 `k_stm32_rank_ch[]` 扫描序列内(挡"映射到不扫描的通道 → 恒读陈旧值")。 3. **备用通道**:RESERVED 路不接入任何语义,UI 与上报里不得出现;若实测有读数漂移,只记录不消费。 -4. **端子侧变比**:上面 1/2 两条都只到 **ADC 引脚**。③ 段(`AINn` ↔ 转换器引脚)已由网表追线定死为 **1:1**(`ADCn` 节点无对地元件,拓扑图见 §7.1),**板内没有未知比值**;④ 子板/端子板变比出厂按子板参数登记(电压族 606.06 : 1、电流族 29.4118 A/V),**已由 2026-11-12 标定实测覆盖 UA / IA**(那一轮是两点算法;554.69 V/V、29.634 A/V,见 §7.3;UN 未标)。故**不得**用"2:1 分压"换算出二次值;端子口径一律按 §7.1 分段表与 §7.2 / §7.3 登记写。 +4. **端子侧变比**:上面 1/2 两条都只到 **ADC 引脚**。③ 段(`AINn` ↔ 转换器引脚)已由网表追线定死为 **1:1**(`ADCn` 节点无对地元件,拓扑图见 §7.1),**板内没有未知比值**;④ 子板/端子板变比出厂按子板参数登记(电压族 606.06 : 1、电流族 29.4118 A/V),**已由 2026-11-13 标定实测覆盖 UA / IA**(5 点最小二乘,见 §7.5;UN = 零序电压 U0,本台架未接线、未标)。故**不得**用"2:1 分压"换算出二次值;端子口径一律按 §7.1 分段表与 §7.2 / §7.3 登记写。 ### 8.1 静态注入实测记录 @@ -381,7 +421,7 @@ Q16 后是 1.0e10,而 int32 上限只有 2.147e9;UB/UC 走 STM32 更到 5.28 | 1 通路活 | 9 路都在转换、没串道、极性没反? | ✅ 2026-10-27 已证 | | 2 静态引脚电压 | 注 1 V 直流,读数是不是 1 V? | ✅ 2026-10-27 已证(六项判据全 PASS,§8.1) | | **3 动态引脚波形** | **注 Vpp 2.828 V 正弦(板上读数 ≈1 V),算出来还是不是 1 V?** | **✅ 2026-11-02 / 11-04 已跑两轮、六轮全部闭合**(削顶 2 Hz 档与复位复核经用户裁定 2026-11-04 关闭;谐波 B 行超差已归因板端前端 RC fc≈796 Hz → §8.2;实测结论 §8.2) | -| 4 端子二次值 | 引脚上这 1 V 对应端子侧几 V? | 🟡 **UA / IA 已由标定落地**(2026-11-12 两点算法那一轮,见 §7.3;屏上已显二次值、`eng = 1`);UN 台架无有效信号未标,保护仍闭锁 | +| 4 端子二次值 | 引脚上这 1 V 对应端子侧几 V? | 🟡 **UA / IA 已由标定落地并落盘**(2026-11-13 5 点最小二乘,见 §7.5;屏上已显二次值、`eng = 1`);UN(零序电压 U0)本台架未接线、未标,保护仍闭锁 | | 5 精度等级验收 | 额定工况下整条链误差够几级? | 🟡 等标准源(本轮继保仪标定只到保护口径 3~5 %,够不上 0.5 级计量) | **三条结论**: @@ -391,9 +431,9 @@ Q16 后是 1.0e10,而 int32 上限只有 2.147e9;UB/UC 走 STM32 更到 5.28 2. **第 3 级已于 2026-11-02 / 11-04 台架闭环,全程没用到任何外部资源**:一台能出 50 Hz 正弦的交流信号发生器, 接在**已经验证过的那个注入点**上就行。它比第 4、5 级安全一个量级——只需 ±2 V 以内的正弦, 而端子级验证要上 100 V / 220 V 二次值。 -3. **第 4 级已部分落地、第 5 级在等外部**:④ 端子变比已由 2026-11-12 标定**实测得出**(两点算法那一轮) - (UA / IA,见 §7.3),屏上显的已是二次值、`eng = 1`;UN 台架无有效信号未标, - 三路齐不了 → 保护判据仍停走。⑤ 精度等级要等标准源送检。开工前置见 +3. **第 4 级已部分落地、第 5 级在等外部**:④ 端子变比已由标定**实测得出**并落盘 + (UA / IA,2026-11-13 起用 5 点最小二乘结果见 §7.5),屏上显的已是二次值、`eng = 1`; + UN(零序电压 U0)本台架未接线、未标,三路齐不了 → 保护判据仍停走。⑤ 精度等级要等标准源送检。开工前置见 `docs/算法/采集与算法层设计说明.md` §13.13。 ### 9.2 波次退出判据与现状 @@ -406,11 +446,11 @@ Q16 后是 1.0e10,而 int32 上限只有 2.147e9;UB/UC 走 STM32 更到 5.28 | ② `adc_cal` 折算落地(三维:系数 / 零点 / 变比),采样环里不是裸码值 | ✅ | `App/task/adc_task.c` 的 `adc_cal_apply()`(全仓唯一折算点) | | ③ 8 通道系数 / 零点 / 变比可落盘,加载失败回退 ROM 默认 | ✅ | `[adc_cal]` / `[adc_cal_ofs]` / `[adc_cal_sec]`,口径 `docs/算法/采集与算法层设计说明.md` §13.8 | | ④ 三处下游常数按工程量重定并注明依据 | ✅(有尾巴) | `docs/算法/采集与算法层设计说明.md` §13.7;幅值门在 `eng=0` 恒失效已记因于 `freq.h:58-60` | -| ⑤ "未标定 → 保护闭锁"生效,且三路齐后能自动解卡 | 🟡 闭锁道 ✅ 台架实证;解锁道只差 UN 一路 | 闭锁:`[PROT] uncalibrated -> protection blocked` + `vl=0x11`(UA/IA 已 VALID、UN 缺,2026-11-12);解锁代码路径 = `adc_cal_prot_blocked()` + `App/task/protect_task.c` | +| ⑤ "未标定 → 保护闭锁"生效,且三路齐后能自动解卡 | 🟡 闭锁道 ✅ 台架实证;解锁道差 UN 一路(UN=零序电压 U0,本台架未接线) | 闭锁:`[PROT] uncalibrated -> protection blocked` + `vl=0x11`(UA/IA 已 VALID、UN 缺;2026-11-13 5 点重标后仍为 0x11,见 §7.5);解锁代码路径 = `adc_cal_prot_blocked()` + `App/task/protect_task.c`;要解卡须先给 UN 接 UZ+UN 再标定 | | ⑥ 量纲回归用例(不该动作时不动作)通过 | ✅ | `test/host/prot_dim/`,口径 `docs/算法/采集与算法层设计说明.md` §13.6 | **所以差的是"台架面",它分两半**:动态这半已于 2026-11-02 / 11-04 台架闭环(§8.2); -端子这半 2026-11-12 已由标定拿到 UA / IA 的实测变比(§7.3,那一轮是两点算法),剩 UN 一路与精度等级送检 +端子这半已由标定拿到 UA / IA 的实测变比并落盘(2026-11-13 5 点最小二乘结果见 §7.5),剩 UN(零序电压 U0,本台架未接线)与精度等级送检 (开工前置见 `docs/算法/采集与算法层设计说明.md` §13.13)。 遥测链(快照发布)与运行参数/点表均已达成,口径沉淀在 `App/algo/meas.h`、`App/algo/run_par.h` diff --git a/docs/算法/采样数据校正全流程.md b/docs/算法/采样数据校正全流程.md index 449168ff..90e56119 100644 --- a/docs/算法/采样数据校正全流程.md +++ b/docs/算法/采样数据校正全流程.md @@ -2,7 +2,7 @@ > **读这篇干什么**:按一帧数据的真实流向,把采样校正串成一条线——从端子码值到 104 上送、UI、保护,说清每步在哪个文件、用哪些系数、谁决定这数能不能信。本文是读者入口,推导、判据与实测表归各属主文档,这里只给「去哪查」。 > -> **一句话现状**:三维折算已上线、出厂变比已由 2026-11-11 台架实测写进固件(屏上直接显二次值);但**多点标定(N 点最小二乘)尚未接到生产流程**,`VALID` 未置位、保护判据仍闭锁、精度未验收。**「能显示二次值」≠「读数已达精度」**。 +> **一句话现状**:三维折算已上线、出厂变比已由台架实测写进固件(屏上直接显二次值);台架已用 **5 点最小二乘**标出 UA / IA 两路并落盘(`docs/硬件平台/ADC通道映射说明.md` §7.5),但**板上没有一键标定**(拟合只在 PC 侧)、**UN(零序电压 U0)本台架未接线、未标**,`VALID` 只齐两路、保护判据仍闭锁、精度未送检验收。**「能显示二次值」≠「读数已达精度」**。 > > **术语**:PT/CT = 电压/电流互感器(一次侧 → 端子二次值);104 = IEC 60870-5-104 上行规约;UI = 人机界面;Q16 = 16 位定点(整数 ÷ 65536 才是真值);eng = 本帧量纲标志;ROM / RAM = 固件出厂值 / 运行时内存副本。 @@ -69,7 +69,7 @@ flowchart TD |---|---|---|---| | `k` | 引脚电压 ↔ 码值(µV/码,Q16) | TPAFE `10000000L`(152.59)、STM32 `52800000L`(805.66) | ROM ×[0.5, 2.0] | | `b` | 零点(码值) | 多点标定得到,出厂 0 | ±8192 | -| `r` | 端子二次值 ÷ 引脚电压(Q16) | 电压族 `65536000L`(1000.0 V/V)、电流族 `1927532L`(29.4118 A/V) | 0 或 ≥1.0 | +| `r` | 端子二次值 ÷ 引脚电压(Q16) | 电压族 `36381980L`(555.144958 V/V)、电流族 `1940570L`(29.610748 A/V)——2026-11-13 台架标定值,见 `docs/硬件平台/ADC通道映射说明.md` §7.5 | 0 或 ≥1.0 | 定义在 `App/algo/adc_cal.h`,ROM 出厂表(`k` 与 `r`)在 `App/algo/adc_cal.c`;推导见 `docs/算法/采集与算法层设计说明.md` §13.2。要点: @@ -113,7 +113,7 @@ flowchart TD **这一节要你干什么**:别误以为标定已经在生产流程里跑——这正是保护还闭锁的原因。 -**求参在 PC 上做,不在设备上做**:`test/host/cal_fit.py` 以 ROM 的 `k` 为入参,对 N 个标定点(**3 ≤ N ≤ 8**)做普通最小二乘,解出 `r` 与 `b`——这是**离线台架动作,固件里没有拟合代码**。设备只需要「用」和「验」:用 `r`/`b` 折算(`adc_cal_apply()`)、对写入参数做合法带校验(`adc_cal_set()` / `adc_cal_set_ratio()`)。设备不需要自己算,所以不值得为它单养一份第二实现。 +**求参在 PC 上做,不在设备上做**:`test/host/cal/cal_fit.py` 以 ROM 的 `k` 为入参,对 N 个标定点(**3 ≤ N ≤ 8**)做普通最小二乘,解出 `r` 与 `b`——这是**离线台架动作,固件里没有拟合代码**。设备只需要「用」和「验」:用 `r`/`b` 折算(`adc_cal_apply()`)、对写入参数做合法带校验(`adc_cal_set()` / `adc_cal_set_ratio()`)。设备不需要自己算,所以不值得为它单养一份第二实现。 ``` 先拟合 注入 = a·示值 + c (普通最小二乘;横轴读表、纵轴注入) @@ -123,11 +123,11 @@ flowchart TD **为什么不用两点**:两点只吃两个读数,单点误差原封不动变成结果偏差,而且拟合结果没有任何自检面(两点连线在数学上永远完美)。点数多了最小二乘把单点偏差摊平,还顺带给出残差与 `R²` 两个"这条线直不直"的判据。标定点要**拉开、覆盖端点、3~8 个点**(台架推荐 5 点:点位太近会按 1/跨度放大读数误差,量化表属主 `docs/硬件平台/ADC通道映射说明.md` §7.2);本轮台架按安全优先裁定电压族 5–100 V、电流族 0.5–2 A 的区间取点,执行细节见 `docs/测试说明/专项验证/ADC多点标定执行流程.md` §1。 -**⚠ 现状(两件不同的事,别混)**:**板上没有一键标定**——固件**不含**拟合代码(2026-11-13 裁定移出,生产零调用、被 linker 移除),拟合的唯一实现是 PC 侧的 `test/host/cal_fit.py`;设备侧只有折算与合法带校验两个入口。**但台架手工路径是通的,且已经跑过一轮**——端子加一组成比例的已知量、读 `meas:` 行相量读数、`test/host/cal_fit.py` 离线做最小二乘解出 `r`/`b`、`test/host/cal_write.py` 经 104 定值段写入(`adc_cal_set()` 落 `b` 时置 `VALID`,脏→落盘→重启保持)。完整执行卡见 `docs/测试说明/专项验证/ADC多点标定执行流程.md`,开工前置与送检顺序见 `docs/算法/采集与算法层设计说明.md` §13.13。 +**⚠ 现状(两件不同的事,别混)**:**板上没有一键标定**——固件**不含**拟合代码(2026-11-13 裁定移出,生产零调用、被 linker 移除),拟合的唯一实现是 PC 侧的 `test/host/cal/cal_fit.py`;设备侧只有折算与合法带校验两个入口。**但台架手工路径是通的,且已经跑过一轮**——端子加一组成比例的已知量、读 `meas:` 行相量读数、`test/host/cal/cal_fit.py` 离线做最小二乘解出 `r`/`b`、`test/host/cal/cal_write.py` 经 104 定值段写入(`adc_cal_set()` 落 `b` 时置 `VALID`,脏→落盘→重启保持)。完整执行卡见 `docs/测试说明/专项验证/ADC多点标定执行流程.md`,开工前置与送检顺序见 `docs/算法/采集与算法层设计说明.md` §13.13。 -**为什么拟合不留在固件里**:整条标定链是「注入 → 读 `meas:` → PC 上解 `r`/`b` → 104 写入」,没有任何一步需要设备参与计算;把拟合留在固件只会多出一份与 PC 工具互为镜像的第二实现(改一处忘一处就是两个口径),而设备真正需要守的是**写入参数合法**与**折算正确**。删掉固件实现后,"解出的 `r` 对不对"由工具自带的金标向量回归守着——`test/host/cal_fit.py --selftest` 用一组已知点核对解出的 `r_q16` 与 `b` 是否逐位相等,外加 4 条拒绝路径(点数不足 / 点位没拉开 / 单调反转 / `r` 越带),11 项全过才 exit 0。**这条自检是替代品不是补充**:固件里没有第二实现了,它是算法正确性唯一的机器验证。 +**为什么拟合不留在固件里**:整条标定链是「注入 → 读 `meas:` → PC 上解 `r`/`b` → 104 写入」,没有任何一步需要设备参与计算;把拟合留在固件只会多出一份与 PC 工具互为镜像的第二实现(改一处忘一处就是两个口径),而设备真正需要守的是**写入参数合法**与**折算正确**。删掉固件实现后,"解出的 `r` 对不对"由工具自带的金标向量回归守着——`test/host/cal/cal_fit.py --selftest` 用一组已知点核对解出的 `r_q16` 与 `b` 是否逐位相等,外加 4 条拒绝路径(点数不足 / 点位没拉开 / 单调反转 / `r` 越带),11 项全过才 exit 0。**这条自检是替代品不是补充**:固件里没有第二实现了,它是算法正确性唯一的机器验证。 -**2026-11-12 台架结果**(**那一轮用的是两点算法,不是现在的最小二乘**):UA(`r = 554.69`)与 IA(`r = 29.63`)已标定并落盘,`VALID` 位图 `0x11`;**UN 未标**(台架该通道无有效信号)。保护判据要求 UA/UN/IA 三路齐,所以**现在仍然是闭锁态**——只差 UN 一路。新一轮改用 **5 点最小二乘**重标,结果待填;实测数据与「2026-11-11 那个 1000.0 为什么错」的归因见 `docs/硬件平台/ADC通道映射说明.md` §7.3,标定区间内外的独立核验见 `docs/硬件平台/ADC通道映射说明.md` §7.4。 +**2026-11-13 台架结果(5 点最小二乘,当前在用值)**:UA `r = 555.144958`、IA `r = 29.610748`,`b` 均为 0,最大残差 0.019 % / 0.067 %(`R²` 0.99999988 / 0.99999823);写入后逐条读回 PASS、设备落盘、断电重启读回不变。`VALID` 位图仍是 `0x11`(**UN(零序电压 U0)本台架未接线、只有噪声底**)→ 保护判据要求三路齐,所以**现在仍然是闭锁态**,差 UN 一路(需先接 UZ+UN 再标定)。完整数据见 `docs/硬件平台/ADC通道映射说明.md` §7.5;上一轮两点算法的历史值(`554.69` / `29.63`)与「2026-11-11 那个 1000.0 为什么错」的归因见 `docs/硬件平台/ADC通道映射说明.md` §7.3,标定区间内外的独立核验见 `docs/硬件平台/ADC通道映射说明.md` §7.4。 --- diff --git a/docs/算法/采集与算法层设计说明.md b/docs/算法/采集与算法层设计说明.md index 87e9673e..a9e091a4 100644 --- a/docs/算法/采集与算法层设计说明.md +++ b/docs/算法/采集与算法层设计说明.md @@ -527,11 +527,12 @@ adcTask 静态最坏链与余量的**台账在** `docs/内存管理/RAM使用分 **当前状态**(2026-11-13 更新):折算链已落地,量纲由**每路变比 `r`** 决定——出厂默认已带台架 实测反算的变比(电压 1000 V/V、电流 29.4118 A/V),所以烧进新固件后屏上直接显**二次值**。 -**标定已上板实跑**:UA(`r = 554.69`)与 IA(`r = 29.63`)标定并落盘、`VALID` 置位 -(`vl = 0x11`),实测值登记 → `docs/硬件平台/ADC通道映射说明.md` §7.3;UN 台架无有效信号未标。 -⚠ 那两个 `r` 是**两点算法**下的历史实测值;算法已改为 **N 点(3 ≤ N ≤ 8,台架按 5 点)最小二乘**, -新一轮结果待填(`docs/硬件平台/ADC通道映射说明.md` §7.3 末表)。 -保护判据仍处于**未标定闭锁**态(§13.3),因为 VALID 要求 **UA/UN/IA 三路齐**,现在只差 UN 一路—— +**标定已上板实跑**:UA(`r = 555.144958`)与 IA(`r = 29.610748`)2026-11-13 用 +**N 点(5 点)普通最小二乘**重标并落盘、`VALID` 仍置位(`vl = 0x11`,断电重启读回不变), +实测与拟合质量 → `docs/硬件平台/ADC通道映射说明.md` §7.5;UN(零序电压 U0)本台架未接线、未标。 +(2026-11-12 那轮两点算法给的是 `554.69` / `29.63`,与 5 点结果只差 0.08 %,历史值留档在 +`docs/硬件平台/ADC通道映射说明.md` §7.3。) +保护判据仍处于**未标定闭锁**态(§13.3),因为 VALID 要求 **UA/UN/IA 三路齐**,差 UN 一路(UN=零序电压 U0,本台架未接线;要解卡须先接线再标定)—— 这是预期行为,不是缺陷。 ### 13.1 通道量纲与折算点 @@ -564,7 +565,7 @@ adcTask 静态最坏链与余量的**台账在** `docs/内存管理/RAM使用分 |---|---|---|---| | `k` | **前端斜率**:引脚电压 ↔ 码值(板级量程事实) | `[adc_cal]`,int32 **Q16**(µV/码 × 65536) | TPAFE 10000000(152.59 µV/码)、STM32 52800000(805.66 µV/码) | | `b` | **零点偏移**(码值) | `[adc_cal_ofs]`,int32(可负) | 0 | -| `r` | **端子二次值 ↔ 引脚电压的变比**(现场标定产物) | `[adc_cal_sec]`,int32 **Q16**(比值 × 65536) | 电压族 65536000(1000.0 V/V)、电流族 1927532(≈29.4118 A/V) | +| `r` | **端子二次值 ↔ 引脚电压的变比**(现场标定产物) | `[adc_cal_sec]`,int32 **Q16**(比值 × 65536) | 电压族 36381980(555.144958 V/V)、电流族 1940570(29.610748 A/V)——2026-11-13 台架标定值 | - **为什么必须拆成三个、不能把变比并进 `k`**:并进 `k` 会直接溢出存储口径——UA 端子域 `k` = 152.59 × 1000 = 152 587.9 µV/码,Q16 就是 1.0e10,而 int32 上限只有 2.147e9。 @@ -588,11 +589,11 @@ adcTask 静态最坏链与余量的**台账在** `docs/内存管理/RAM使用分 上界是 int32 能表达的 32767.99。**`r = 0` 是合法值**,语义 = "本路不折算,输出停在引脚电压", 这也是现场关掉某一路折算的正规手段(改成 1.0 反而会留下一个假变比)。 - **标定今后标的是 `r`,不是 `k`**(拟合工具的 `k_q16` 是入参):`k` 是板级事实、现场不该改也改不动,真正未知的是"端子 ↔ 引脚"这一层。 -- **怎么从 N 个点解出 `r` 与 `b`**:这一步**在 PC 侧完成,固件只接收结果**——`test/host/cal_fit.py` 对 N 个点(**3 ≤ N ≤ 8**)做**普通最小二乘**,拟合 +- **怎么从 N 个点解出 `r` 与 `b`**:这一步**在 PC 侧完成,固件只接收结果**——`test/host/cal/cal_fit.py` 对 N 个点(**3 ≤ N ≤ 8**)做**普通最小二乘**,拟合 `注入 = a · 示值 + c` 这条直线(横轴是 `meas:` 读数、纵轴是注入已知量,`a` 是斜率、`c` 是截距);再由 `r_new = a · r_old` 得到新变比(注入正比于示值,比例偏差全由 `r` 吸收), 并把截距折回引脚域求零点 `b`。**定的是口径,不是实现**——数值在什么域里算(浮点还是定点)、 - 累加顺序、各条拒绝判据落在哪一步,都写在 `test/host/cal_fit.py` 里,本节不重复。设备侧只做两件事: + 累加顺序、各条拒绝判据落在哪一步,都写在 `test/host/cal/cal_fit.py` 里,本节不重复。设备侧只做两件事: 用 `r`/`b` 折算(`adc_cal_apply()`)、对写入的参数做合法带校验(`adc_cal_set()` / `adc_cal_set_ratio()`)。 - **为什么不再用两点**:两点解只用到两个读数,**单点误差 100 % 传进结果**且没有任何自检面; 最小二乘用全部点、把单点偏差摊平,还能顺手算出残差与 `R²`——前者回答"拟合线直不直", @@ -665,11 +666,12 @@ V0.1 恒命中 UB/UC/IB/IC/IN,位图 `0xE6`),它表示"这路不能测交 > **承诺边界**(2026-11-13 更新):**量纲**已到端子二次值(出厂默认变比已带硬件事实, > `eng` 判据见 §13.3);**精度**分两档说清——① 引脚层 ±2 % 已由动态六轮证过; -> ② 端子二次值这一档,UA / IA 已用继保仪标定**实测落盘**(`r = 554.69` / `29.63`, -> **那是两点算法下的结果**;单台、标定点 5 V–50 V 与 0.5 A–2 A,1 LSB 摊入约 0.7 % / 0.6 %, -> 够保护判据 3~5 %、**够不上 0.5 级计量**),UN 未标;**精度等级验收**仍等标准源送检。 -> 算法已改为 **N 点(3 ≤ N ≤ 8,台架按 5 点)最小二乘**,新一轮重标结果待填 → `docs/硬件平台/ADC通道映射说明.md` §7.3 末表。 -> 标定怎么开工见 §13.13,实测值 → `docs/硬件平台/ADC通道映射说明.md` §7.3。 +> ② 端子二次值这一档,UA / IA 已用继保仪标定**实测落盘**(2026-11-13 5 点最小二乘得 +> `r = 555.144958` / `29.610748`,见 `docs/硬件平台/ADC通道映射说明.md` §7.5;单台设备、标定点 +> 5–100 V 与 0.5–2 A,最大残差 0.019 % / 0.067 %,够保护判据 3~5 %、**够不上 0.5 级计量**), +> UN(零序电压 U0)本台架未接线、未标;**精度等级验收**仍等标准源送检。 +> 算法是 **N 点(3 ≤ N ≤ 8,台架按 5 点)普通最小二乘**,2026-11-13 已在台架跑完一轮 → +> `docs/硬件平台/ADC通道映射说明.md` §7.5。标定怎么开工见 §13.13。 **动态验证已跑两轮(2026-11-02 / 11-04),六轮全部闭合**——幅值 / 波形 / 相位 / 测频 / 谐波 / 削顶直流档都过了;实测数与逐轮结论只详述在 `docs/硬件平台/ADC通道映射说明.md` §8.2,本节不抄第二份。 @@ -677,7 +679,7 @@ V0.1 恒命中 UB/UC/IB/IC/IN,位图 `0xE6`),它表示"这路不能测交 | 下游 | 现在 | 为什么 | |---|---|---| -| 基波有效值 | ✅ 引脚层过(±2 % 内,三档幅值两点复现) | 台架幅值轮 + 波形轮(基波分量口径)实证;**二次值这一档**:UA / IA 已由继保仪标定实测落盘(`r = 554.69` / `29.63`,**两点算法下的历史值**),旧结论"出厂变比是单点粗测(5 V 注入只有 33 码、1 LSB = 3 %、真值区间约 909~1111)"仍成立且已在 2026-11-12 被实测定量:实测 554.69 说明那次粗测接线分压把比值放大了一倍(`docs/硬件平台/ADC通道映射说明.md` §7.3);2026-11-13 又用**非标定的 10 V / 100 V** 做了区间外核验(+0.42 % / −0.067 %,`docs/硬件平台/ADC通道映射说明.md` §7.4);**精度等级**仍待标准源送检 | +| 基波有效值 | ✅ 引脚层过(±2 % 内,三档幅值两点复现) | 台架幅值轮 + 波形轮(基波分量口径)实证;**二次值这一档**:UA / IA 已由继保仪标定实测落盘(2026-11-13 用 5 点最小二乘重标得 `r = 555.14` / `29.61`,`docs/硬件平台/ADC通道映射说明.md` §7.5;2026-11-12 两点算法那一轮是 `554.69` / `29.63`,历史值见 `docs/硬件平台/ADC通道映射说明.md` §7.3),旧结论"出厂变比是单点粗测(5 V 注入只有 33 码、1 LSB = 3 %、真值区间约 909~1111)"仍成立且已在 2026-11-12 被实测定量:实测 554.69 说明那次粗测接线分压把比值放大了一倍(`docs/硬件平台/ADC通道映射说明.md` §7.3);2026-11-13 又用**非标定的 10 V / 100 V** 做了区间外核验(+0.42 % / −0.067 %,`docs/硬件平台/ADC通道映射说明.md` §7.4);**精度等级**仍待标准源送检 | | 相位 | ✅ 同片内过(录波档 ≤2° 判据) | 同段 UA/UN 两路实测(0° 与 90° 两工况);**跨转换器仍禁比**——两片转换器无共同采样触发,是 V0.1 硬件边界(`docs/硬件平台/ADC通道映射说明.md` §7 通道归属行),不是待修缺陷 | | 频率 | ✅ 「如实报不可测」成立(这就是现状判据) | 含满幅注入段全不可测、UI 恒 `--`;**45~55 Hz 精度仍未验**——要验须降 `FREQ_AMP_MIN` 单跑一轮 | | 削顶 / 品质 | ✅ DC −1.00 V 档锁存 + sticky 实证;2.0 Hz 低频档与复位复核经用户裁定 2026-11-04 关闭 | `[ERR] rail: edge=0x2 sticky=0x2` 唯一一条、撤注恒持 | @@ -827,7 +829,7 @@ typedef struct { | `r` 的合法带 | **绝对带**:`0`(不折算)或 `≥ 1.0`,上界 int32 能表达的 32767.99 | `r` 是现场标定量,不同 CT/PT 差几个数量级,用 ROM×[0.5,2.0] 那种窄带没有意义 | | `r` 的出厂默认 | **非 0**:电压族 1000 V/V、电流族 29.4118 A/V | 变比是硬件事实,出厂就该带着;"准不准"由 VALID 回答,两者互不代替(§13.3) | | 标定标谁 | **标 `r`**,`k` 由 ROM 固化 | `k` 是板级事实、现场不该改也改不动;真正未知的是"端子 ↔ 引脚"这一层 | -| 标定用什么算法 | **N 点(3 ≤ N ≤ 8)普通最小二乘**,台架按 5 点执行,不再用两点连线 | 两点解只吃两个读数、单点误差 100 % 传进结果,且拟合结果没有任何自检面;最小二乘摊平单点偏差,并额外产出残差与 `R²` 两个可判读量。拒绝判据(点数越界 / 点位不拉开 / `r`·`b` 越带 / 非单调 / 残差超跨度 2 %)随拟合一起落在 PC 侧工具 `test/host/cal_fit.py`(固件只做写入参数的合法带校验) | +| 标定用什么算法 | **N 点(3 ≤ N ≤ 8)普通最小二乘**,台架按 5 点执行,不再用两点连线 | 两点解只吃两个读数、单点误差 100 % 传进结果,且拟合结果没有任何自检面;最小二乘摊平单点偏差,并额外产出残差与 `R²` 两个可判读量。拒绝判据(点数越界 / 点位不拉开 / `r`·`b` 越带 / 非单调 / 残差超跨度 2 %)随拟合一起落在 PC 侧工具 `test/host/cal/cal_fit.py`(固件只做写入参数的合法带校验) | | 整帧量纲标签 `eng` 的判据 | **每路要么已配变比、要么削顶不显示** | 削顶通道页面本来就显 `--`、不进读数;拿它们卡标签会让 V0.1 五路削顶把徽标永久钉在"未标定" | | 一次变比(PT/CT 一次额定)存哪 | **复用 `prot_set` 已有四项**,不新开段 | 已经在册(`PS_PT_PRI` / `PS_PT_SEC` / `PS_CT_PRI` / `PS_CT_SEC`),再开一段就是同一事实两个真源 | | 遥测快照的字段布局 | 由协议侧定、单测按偏移钉死(72 B × 双区) | 布局漂移只能靠"读侧按名字取"掩盖,抓不到真正的错位 | diff --git a/scripts/ram_baseline.tsv b/scripts/ram_baseline.tsv index af3ef3eb..0abfdea3 100644 --- a/scripts/ram_baseline.tsv +++ b/scripts/ram_baseline.tsv @@ -21,4 +21,4 @@ date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi 2026-11-09 255160 31204 2080 152152 286364 27.3 96808 73.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5296;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 2026-11-10 255160 31204 2080 152152 286364 27.3 96808 73.9 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5296;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 2026-11-11 256024 31760 2080 152368 287784 27.4 97024 74.0 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5332;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 -2026-11-13 256196 31764 2080 152368 287960 27.5 97024 74.0 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5332;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 +2026-11-13 256200 31764 2080 152368 287964 27.5 97024 74.0 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;nand_ftl.o=8288;lftpd.o=7884;ui_net_sink.o=6802;ui_port.o=6432;wave_ring.o=5332;sb_link.o=4376;ch395f.o=4162;console_log.o=4132 diff --git a/scripts/size_attestation.json b/scripts/size_attestation.json index caad3562..868cf8a6 100644 --- a/scripts/size_attestation.json +++ b/scripts/size_attestation.json @@ -1,15 +1,15 @@ { - "built_at": "2026-11-13T08:03:21+08:00", - "fingerprint": "09cfe12df7414b5c2a91aedf39623942a8be2f36c188e2186f54c245b47e30f2", + "built_at": "2026-11-13T11:25:00+08:00", + "fingerprint": "39f4f69a673f97c33cc59b301e723ec8f1e03686f4556ac6abb1a5a6cd438796", "fingerprint_files": 1479, "fingerprint_source": "git-index", "gated_regions": [ "RW_IRAM1", "RW_CCM" ], - "git_head": "1355dbe1b13e9bf43f2a0be0ff290a8a9dd208cd", + "git_head": "7c505b2457d977adf7750abbb841b0453fd6bfb9", "map": "MDK-ARM/STM32F407-Demo/STM32F407-Demo.map", - "map_mtime": 1794528195, + "map_mtime": 1794540294, "pct": { "ER_IROM1": 27.46, "RW_CCM": 87.89, @@ -19,7 +19,7 @@ "regions": { "ER_IROM1": { "max": 1048576, - "size": 287960 + "size": 287964 }, "RW_CCM": { "max": 65280, @@ -37,7 +37,7 @@ "schema": 2, "scope_note": "\u540c\u7801\u51ed\u8bc1\uff1a\u6307\u7eb9\u8986\u76d6\u56fa\u4ef6\u4f53\u79ef\u76f8\u5173\u6e90\u7801\uff0c\u53e3\u5f84\u89c1\u672c\u811a\u672c docstring", "sizes": { - "code": 256196, + "code": 256200, "ro_data": 31764, "rw_data": 2080, "zi_data": 152368 diff --git a/test/host/adc_cal/adc_cal_test.c b/test/host/adc_cal/adc_cal_test.c index d9ab0bca..d8c55592 100644 --- a/test/host/adc_cal/adc_cal_test.c +++ b/test/host/adc_cal/adc_cal_test.c @@ -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 不参与线性标定;越界通道同守卫 */ diff --git a/test/host/cal/cal_collect.py b/test/host/cal/cal_collect.py new file mode 100644 index 00000000..f49e8c29 --- /dev/null +++ b/test/host/cal/cal_collect.py @@ -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"(.*?)", 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()) diff --git a/test/host/cal_fit.py b/test/host/cal/cal_fit.py similarity index 93% rename from test/host/cal_fit.py rename to test/host/cal/cal_fit.py index 288f7714..7d17224e 100644 --- a/test/host/cal_fit.py +++ b/test/host/cal/cal_fit.py @@ -308,6 +308,34 @@ def parse_cal_table(path): return groups +def parse_rold_table(path): + """提取 ~ 之间的 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"(.*?)", + 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 diff --git a/test/host/cal_write.py b/test/host/cal/cal_write.py similarity index 100% rename from test/host/cal_write.py rename to test/host/cal/cal_write.py diff --git a/test/host/run_par/run_par_test.c b/test/host/run_par/run_par_test.c index b5c16f6f..e10cac48 100644 --- a/test/host/run_par/run_par_test.c +++ b/test/host/run_par/run_par_test.c @@ -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");