feat(adc): 新增变比维 r 三维折算,录波段格式升 v2

采样板注入 Ua=5V/Ia=0.5A 读数偏小,根因是固件只输出引脚电压、
缺「端子→引脚」变比。折算改三维:

工程量 = (code + b) × k[µV/码] × 1e-6 × r

k 保持 ROM 引脚 LSB,b 为零点,r 为端子变比(int32 Q16)。
变比不能并进 k:UA 端子域 k 达 1.5e5 µV/码、Q16 溢出 int32;
r 单独存 Q16 不溢出。两点标定改标 r,k 仍是板级事实。

- 新开单段 [adc_cal_sec](8 键 r_ua..r_in),cfg 段数达 8/8 上限
- 一次变比复用 prot_set 四项,不新开段;r=0 表示本路不折算
- ROM 默认带台架实测变比(电压族 1000、电流族 29.4118),
  烧进固件即显二次值;VALID 仍须两点标定,两者互不派生
- eng 判据改「每路要么已配变比、要么削顶不显示」,不再看 VALID
- 104 地址 0x8050~0x8057 写 r,run_par_cal_r_set 写值与置脏同函数
- 录波 v2:通道表加 int32 r_q16,头 128→160、整段 5250→5282;
  code_to_milli 用余数分解换等价单次圆整,20 万组大整数对拍 0 不符
- 文档同步:采集与算法层/通道映射/故障录波/网络配置/配置文件/
  产品需求/内存台账,历史带日期实测数字按冻结口径不改
- 单测 13 套全绿,/keil-build 0 错误 0 警告(256024/31760/
  2080/152368),RAM 实测回填内存内容分配策略 §6.1
This commit is contained in:
2026-11-11 11:31:34 +08:00
parent 8115fb9a87
commit 3675b89e8e
35 changed files with 1653 additions and 505 deletions
@@ -447,9 +447,12 @@ flowchart LR
- 判据:`ADC input layer init: OK`;8 路 + 温度首帧全部读出、互异、非饱和。
**FR-ADC-3 标定与量纲折算(标定折算)**
- 需求:把码值折算为工程量 `(code + b) × k`,系数与零点可落盘、掉电保持;未标定不得冒充二次值。
- 实现:`App/algo/adc_cal.c`——`k` 以 Q16(µV/码 ×65536)、`b` 以码值存 `[adc_cal]` / `[adc_cal_ofs]`
段;ROM 默认 = 引脚侧 LSB(TPAFE 152.59 µV/码、STM32 805.66 µV/码);
- 需求:把码值折算为工程量 `(code + b) × k × r`(`k` 前端斜率、`b` 零点、`r` 端子变比),
三者可落盘、掉电保持;未标定不得冒充二次值("准不准"由 `VALID` 回答,"什么单位"由 `r` 决定)。
- 实现:`App/algo/adc_cal.c`——`k` 以 Q16(µV/码 ×65536)存 `[adc_cal]`、`b` 以码值存 `[adc_cal_ofs]`、
`r` 以 Q16 比值存 `[adc_cal_sec]`;`k` 的 ROM 默认 = 引脚侧 LSB(TPAFE 152.59 µV/码、
STM32 805.66 µV/码),`r` 的 ROM 默认 = 台架实测反算的端子变比(电压 1000 V/V、电流
29.4118 A/V,`docs/硬件平台/ADC通道映射说明.md` §7.2),`r = 0` = 本路不折算;
三态可用性 `ADC_CAL_F_VALID` / `ADC_CAL_F_CLIPPED` 由 `adc_cal_flags()` 单点查询,104 / UI / 保护闭锁共用。
- 判据:已知电压注入斜率与理论 LSB 偏差 ≤2%(2026-10-27 台架实测端子侧 **6544.2 码/V**,与引脚侧
理论 6553.6 差 **−0.14%**,零点 0~5 码、R²=0.9999997、极性对称 ≤0.34%);8 通道系数/零点往返一致。
@@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 286,364 B (279.65 KB) | 1,048,576 B (1024 KB) | 27.3% |
| RW_IRAM1 | 96,808 B (94.54 KB) | 131,072 B (128 KB) | 73.9% |
| ER_IROM1 | 287,784 B (281.04 KB) | 1,048,576 B (1024 KB) | 27.4% |
| 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 合计)** | **288,444 B (281.7 KB)** | 1,048,576 B (1024 KB) | **27.5%** |
| **Flash (ROM 合计)** | **289,864 B (283.1 KB)** | 1,048,576 B (1024 KB) | **27.6%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
+4 -4
View File
@@ -15,18 +15,18 @@
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|---|---|---|---|---|
| ER_IROM1 (Flash) | 286,364 B | 1,048,576 B | 27.3% | 参考 |
| ER_IROM1 (Flash) | 287,784 B | 1,048,576 B | 27.4% | 参考 |
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
| RW_IRAM1 | 96,808 B | 131,072 B | 73.9% | PASS |
| RW_IRAM1 | 97,024 B | 131,072 B | 74.0% | PASS |
| 子系统(静态 RW+ZI;除 RTOS 堆在 CCM 外均在 SRAM1) | 现值 | 配额 | 状态 |
|---|---|---|---|
| 网络 | 13,958 B | 12 KB | ⚠ 超 1.6 KB |
| 存储 | 9,144 B | 12 KB | OK |
| UI | 36,475 B | 16 KB | ⚠ 超 19.6 KB |
| UI | 36,511 B | 16 KB | ⚠ 超 19.7 KB |
| RTOS 堆 | 57,376 B | 56 KB | 堆=配额 |
| 应用 | 26,433 B | 8 KB | ⚠ 超 17.8 KB |
| 应用 | 26,609 B | 8 KB | ⚠ 超 18.0 KB |
| 其它 | 10,792 B | — | 参考 |
<!-- MEM_BUDGET:END -->
+23 -1
View File
@@ -110,7 +110,7 @@ CCM 64KB 只能容纳 {FreeRTOS 堆 48KB} 与 {LVGL 集合 ~52.5KB} 之一(两
触发条件:测试构建 SRAM 余量不足,**且**已耗尽 `RAM使用分析与优化方案.md` 的帧副本去重(−6.4~9.6KB)与 LVGL 池缩减等低代价手段。
流程:改 scatter → 走 §7 完整验证阶梯 → 同步更新本文档、`AGENTS.md`、`.sct` 头注释。在此之前,方案 A 是唯一有效布局。
## 6. 当前容量台账(2026-10-29,产品构建 task/wave-record 分支终值;2026-09-21 最坏构建=存储套件的历史基线见 `docs/内存管理/RAM预算台账.md` 趋势)
## 6. 容量台账(2026-10-29 冻结值;**本次改动后的当前值见 §6.1**,两处链接时刻不同、不互相加减;原为产品构建 task/wave-record 分支终值;2026-09-21 最坏构建=存储套件的历史基线见 `docs/内存管理/RAM预算台账.md` 趋势)
| 区域 | 总量 | 已用 | 空闲 | 驻留内容 |
|---|---|---|---|---|
@@ -139,6 +139,28 @@ LVGL 集合明细(均在 SRAM):LVGL 池 16,384B(`lv_mem_core_builtin.o`
- **为什么比上一版低**:本表 2026-10-12 版记 95,816 B / 73.1 %,高于今天的 89,936 B。原因是此后 LVGL 内部池由 40 KB 缩到 16 KB(`docs/内存管理/RAM预算台账.md` §3「缩池 ✅ 2026-10-15」行;`scripts/ram_baseline.tsv` 2026-10-12 与 2026-10-24 两行的 `top_rwzi` 列可见 `lv_mem_core_builtin.o` 40960 → 16384)——**不是本波回收了什么**。
- **子系统归集的坑(本卡实测 → 2026-10-29 已修)**:`scripts/ram_baseline.py` 的 `subsystem_of()` 按对象名前缀首匹配,当时表里没有 `wave_` 前缀 → 录波环 `wave_ring.o` 5,300 B 落进 **「其它」(当时 15,853 B,参考值、无配额)**,配额 8 KB 的「应用」行看不见它;落盘道 `default_task.o`(+1,204 B)与 `adc_task.o` 才计入「应用」(当时现值 16,888 B,本波前该行已在配额外,成因见 `docs/内存管理/RAM预算台账.md` §2 的应用条目)。**同日后续**:补齐 12 个 `App/` 对象(9,445 B)的前缀,并加兜底——`App/**/*.c` 编出的对象只要落进「其它」,`python scripts/ram_baseline.py --gate` 就逐条 WARN 点名(软提醒不拦)。结论仍然有效:**子系统配额行不是录波预算的判据**,要按本节 per-file 台账读;差别只在「应用」行现在真含录波支路,两个口径不再互相看不见。
### 6.1 变比维 + 录波 v2 布局(2026-11-11 权威构建)
**这一节记录本次改动后 RAM 的当前实测值与它的构建身份**:上面 §6 主表是 2026-10-29
那次构建的冻结值(历史行不改);本节是 2026-11-11 新一次 `/keil-build`(0 错误 0 警告)
后的独立测量,两处各有自己的链接时刻,不互相加减。结构增量的推导(通道表 12 → 16 B/路、
每份段头副本 +36 B、共 5 份)在 `docs/子系统设计/故障录波设计说明.md` §7,本节只留实测数。
- **测量身份**:本工作树,2026-11-11 10:52 链接,
`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`;`scripts/ram_baseline.tsv` 当日行
Code/RO/RW/ZI = 256,024 / 31,760 / 2,080 / 152,368。
- **区域水位**:RW_IRAM1 **97,024 B / 131,072 B(74.0%)**、RW_CCM **57,376 B / 65,280 B
(87.9%,零增量)**、ER_IROM1 287,784 B(27.4%)、Flash 合计 289,864 B(27.6%)。
受管快照区(`docs/内存管理/RAM使用分析与优化方案.md`)与子系统配额表
(`docs/内存管理/RAM预算台账.md`)已由 `check_mem_numbers.py --write` /
`ram_baseline.py --write` 从本份 .map 重渲染,`--gate` / `--scan` 均返 0。
- **逐对象 RW+ZI(供下次对账)**:`wave_ring.o` 5,332 B(ZI 5,292 + RW 40)、
`default_task.o` 4,012 B(ZI 3,992 + RW 20)、`ui_task.o` 1,681 B(ZI 1,676 + RW 5)、
`adc_task.o` 200 B(ZI 168 + RW 32)、`adc_cal.o` 116 B(ZI 100 + RW 16,含新增
`[adc_cal_sec]` RAM 副本 `s_rcfg` 32 B)。
- **栈与 CCM**:本次只加静态结构,未抬栈、未加大局部缓冲;变比段副本与全部段头副本
都落 RW_IRAM1(无 section 属性),设计文档 §7「不进 CCM」的落位继续成立。
## 7. 布局变更验证阶梯(强制执行)
任何 scatter / CCM / 堆大小变更后,依次通过以下全部关卡方可认定生效:
@@ -135,7 +135,7 @@
`run_par_104_write` 执行段入口,加一处即可。
- 持久化(运行参数段):任一写成功置 `run_par` 脏 → iec104Task 同款 peek 投递
`SYS_EVT_RUN_PAR_SAVE`(1<<8) → defaultTask `run_par_store_save()` 调 `cfg_file_save()`
整档外科保存(一趟覆盖 [run_par] 9 键 + 被代理改写的 [adc_cal]/[adc_cal_ofs],
整档外科保存(一趟覆盖 [run_par] 9 键 + 被代理改写的 [adc_cal]/[adc_cal_ofs]/[adc_cal_sec],
lead 裁②一条事件位一趟够用);失败脏保留 = 下轮重试。消费侧(COV 判定)**实时读
引擎副本**,无缓存无投递(104 改读快照时的 pts 层 s_yc_db 副本已收编进 run_par)。
@@ -185,8 +185,8 @@ flowchart TD
| 偏移 | 字段 | 宽度 | 说明 |
|---|---|---|---|
| 0 | magic | 3 | 前两字节固定 WR,第三字节是版本号(本波写 1);版本号并进 magic,换格式即变(§2.1 第 4 条) |
| 3 | ver | 1 | 恒 1 |
| 0 | magic | 3 | 前两字节固定 WR,第三字节是版本号(本波写 2);版本号并进 magic,换格式即变(§2.1 第 4 条) |
| 3 | ver | 1 | 恒 2(v1 旧段一律判废,见 §6.3「v1 旧段兼容」行) |
| 4 | fs_hz | 2 | 采样率,本波恒 1000 |
| 6 | pts_per_cycle | 1 | 每周波点数,本波 20 |
| 7 | ch_count | 1 | 8(带 TEMP 则 9) |
@@ -200,10 +200,10 @@ flowchart TD
| 23 | flags_seq | 1 | 每路 VALID 压成的位图:bit s = 语义通道 s 的 `ADC_CAL_F_VALID`(电气 8 路恰好占满 1 字节;TEMP 不参与标定,若要带 TEMP 连本行与下一行一起升版本) |
| 24 | clip_mask | 2 | 削顶归属位图:bit s = 语义通道 s,与 `App/algo/meas.h` 同名参数同口径(封存瞬间 `ADC_CAL_F_CLIPPED` 快照,V0.1 恒 0xE6 = UB/UC/IB/IC/IN,见 `docs/算法/采集与算法层设计说明.md §13.5`)。⚠ **这个位图的本体是「链路可信性标记(按来源派生)」,不是「本段实测饱和标记」**:置位条件一次样本都不看(`App/algo/adc_cal.c` 291 到 296 行,该路 ROM 系数属片内 ADC 侧即置位);「这一段有没有真钉轨」走另一条链——`App/algo/adc_health.c` 的 `adc_health_rail_watch()`(280 行起)与 `adc_health_rail_sticky()`,门限按满量程百分比由 `adc_cal_rail_ths()` 给、须连续 `ADC_RAIL_N_RUN` 拍。V0.2(8 路全接 TPAFE)后本位自动恒 0,届时「与 clip_mask 对表」这条判据退化为只查钉轨实测(§10) |
| 26 | frame_seq | 4 | 段首帧序号,可与串口周期行的 frames 对账 |
| 30 | 通道表 | 每路 12 | 语义号 1 + 来源转换器 1 + 位宽 1 + 可用性 1(= `adc_cal_flags(sem)` 低 8 位原值,不自造位义)+ `k_q16` 4(int32,µV/码 × 65536)+ `b_codes` 4(int32,码值)。**没有独立 LSB 字段**,LSB 就是 `k_q16`,见 §6.3 |
| — | CRC16(头) | 2 | CRC-16/MODBUS,**低字节在前**(与全文件小端统一);覆盖 `[0, 30+12×ch_count)`,本字段自身紧随其后 |
| 30 | 通道表 | 每路 16 | 语义号 1 + 来源转换器 1 + 位宽 1 + 可用性 1(= `adc_cal_flags(sem)` 低 8 位原值,不自造位义)+ `k_q16` 4(int32,µV/码 × 65536)+ `b_codes` 4(int32,码值)+ `r_q16` 4(int32,变比 × 65536,0 = 本路不折算)。**没有独立 LSB 字段**,LSB 就是 `k_q16`;**变比也不并进 k_q16**(并入会超 int32,见 §6.3),见 §6.3 |
| — | CRC16(头) | 2 | CRC-16/MODBUS,**低字节在前**(与全文件小端统一);覆盖 `[0, 30+16×ch_count)`,本字段自身紧随其后 |
| — | 数据区 | pts_per_ch × ch_count × 2 | **按通道分列**:一路连续 320 点,单通道回放只读那一路 |
| — | CRC16(数据区) | 2 | 同上字节序;覆盖整个数据区、自身在文件尾(8 路 320 点整段 5250 B);长度为 0、任一 CRC 不符、或 `ver` 与魔数第三字节不等 → 整段作废,页面必须显示「该段不可读」 |
| — | CRC16(数据区) | 2 | 同上字节序;覆盖整个数据区、自身在文件尾(8 路 320 点整段 5282 B = 160 头 + 5120 数据 + 2;v1 为 5250 B);长度为 0、任一 CRC 不符、或 `ver` 与魔数第三字节不等 → 整段作废,页面必须显示「该段不可读」 |
**段名轮转的呈现语义**(回放页与 PC 侧取「哪一段最新」都按这条):段表按**数值升序**呈现,开屏落点取**数值最大的段号**。回绕(99 之后回到 00)之后「数值最大」不再等于「时间最新」——要真时间序必须逐段读段头 `frame_seq` / `t0_ms`,代价是 100 次开文件,**本波不做**。所以页顶把段号本身显示出来(§8.1 顶部行),让读者自己看见这是第几号,不去伪造一个「最新」的假象。
@@ -214,13 +214,16 @@ flowchart TD
| 问题 | 裁定 |
|---|---|
| CRC 字节序 | **低字节在前**,与全文件小端一致。原稿「与屏板链路同一张 CRC-16/MODBUS 表」不成立:全仓**没有**可复用的 CRC-16 表——`App/util/README.md` 记 `crc.c/h` 已于 2026-10-02 删除,屏板链路 `Drivers/BSP/UI/ui_net_sink.c` 的 `remo_crc()` 是单字节异或、不是 CRC-16。本波在 `wave_store.c` 内自带**无表位运算** CRC-16/MODBUS(0x8005 反射、初值 0xFFFF、异或输出 0),单测锚点:串 123456789 的 CRC = 0x4B37 |
| CRC 覆盖范围 | 头 CRC 覆盖 `[0, 30+12×ch_count)`、自身在 `30+12×ch_count`;数据区自 `32+12×ch_count` 起;数据 CRC 覆盖整个数据区、自身在文件尾。8 路 320 点整段 5250 B |
| CRC 覆盖范围 | 头 CRC 覆盖 `[0, 30+16×ch_count)`、自身在 `30+16×ch_count`;数据区自 `32+16×ch_count` 起;数据 CRC 覆盖整个数据区、自身在文件尾。8 路 320 点:头 158 字节 CRC 覆盖区 + 2 = 160 整头,数据区 5120,整段 5282 B(v1 口径为 12 字节表项 / 126 / 128 / 5250,v2 一并升位) |
| LSB 从哪来 | **不设独立 LSB 字段**:通道表的 `k_q16` 就是 LSB(µV/码 × 65536),与 `App/algo/adc_cal.h` 的 `ADC_CAL_K_Q16_TPAFE 10000000` / `ADC_CAL_K_Q16_STM32 52800000` 同口径,Q16 下无舍入(选型依据 `docs/算法/采集与算法层设计说明.md §13.2`)。PC 侧 `µV = k_q16 / 65536`,不许再乘一遍 |
| k / b 宽度与角色 | `k_q16` int32、`b_codes` int32 = 封存瞬间该路生效的两个标定量。`adc_cal_apply()` 的算式是 `eng = (code + b) × k × 1e-6`,文件里存的正是等号右边两个数加原始 code,PC 照此重算。**必须取 int32 原值**,不得经 `adc_cal_k_uv()` 的 float 通道回算(float 在 5.28e7 量级掉尾数)→ 接线道在 `App/algo/adc_cal.[ch]` 补一个 int32 直读入口 `adc_cal_k_q16()`(与 `adc_cal_k_uv()` 孪生);eng=0 时两者是 ROM 默认值,PC 折出来的单位是**引脚域 µV** 而非二次值——单位一律以头里的 `eng` 为准,PC 不许按当前固件口径猜 |
| k / b / r 宽度与角色 | `k_q16` int32、`b_codes` int32、`r_q16` int32 = 封存瞬间该路生效的三个标定量。`adc_cal_apply()` 的算式是 `eng = (code + b) × k × 1e-6 × r`,文件里存的正是等号右边三个数加原始 code,PC 照此重算。**k 与 r 必须取 int32 原值**,不得经 `adc_cal_k_uv()` / `adc_cal_ratio()` 的 float 通道回算(float 在 5.28e7 / 6.5e7 量级掉尾数)→ 接线道用 `App/algo/adc_cal.[ch]` 的两个 int32 直读入口 `adc_cal_k_q16()` / `adc_cal_ratio_q16()`;`b_codes` 经 float 回乘在本域无损(|b| 限幅 8192 远小于 2^24,float32 精确表示该范围整数)。`r_q16 = 0` 表示**本路不折算**(停在引脚域),不是「乘 0」也不是缺省错误:这是合法值,与「刻意不折算」在文件里长得一样,属设计取舍。eng=0 时两者是 ROM 默认值,PC 折出来的单位是**引脚域 mV** 而非二次值——单位一律以头里的 `eng` 为准,PC 不许按当前固件口径猜 |
| 折算算式与防溢出 | PC 与板端同一条定点算式:`t = (code + b_codes) × k_q16`;`r_q16 = 0` → `m = 圆整(t / 65536000)`(引脚域 mV,与 v1 逐位一致);`r_q16 ≠ 0` → `m = 圆整(t × r_q16 / (65536000 × 65536))`(录波域毫单位)。**只允许在最后圆整一次**:先圆整成整数 mV 再乘变比,会让 `r_q16 = 65536`(比值 1.0)的输出与引脚域差 ±1 LSB,而「r = 1.0 等同引脚域」正是两份实现对齐的锚点。**一次乘完会溢出 int64**(t ≈ 4.3e12、r_q16 ≤ 2.147e9,乘积 9e21 > 9.2e18)→ 板端用余数分解换等价单次圆整(`App/algo/wave_store.c` 的 `wave_store_code_to_milli`,注释里写明分解式与各项上界);PC 侧 Python 用大整数直接算同一个值。输出是**录波域毫单位**:eng=1 时二次 mV/mA、eng=0 时引脚 mV,两端口径一致 |
| 变比为什么不并进 `k_q16` | `k_q16` 是**板级前端斜率**(µV/码 × 65536,TPAFE `10000000` / 片内 ADC `52800000`),`r_q16` 是**端子变比**(PT/CT 变比 × 65536)。若把 r 折进 k,电压族 = `10000000 × 65536000 / 65536 = 1.0e10`,已超 int32 上限 2.147e9 → Q16 定标下无法在同一个 int32 里同时表达两个量纲,故必须独立成字段 |
| `flags_seq` 位义 | bit s = 语义通道 s 的 `ADC_CAL_F_VALID`;本波 `ch_count` 固定 8 |
| 可用性字段真值表 | 两行,实现期按 `App/algo/adc_cal.c` 280 到 298 行核出(VALID 需文件位图与 k 合法带**同时**成立;CLIPPED 纯由来源派生——该路 ROM 系数属片内 ADC 侧即置位,与样本内容无关):① **未标定**(产品构建,两点标定未做)逐路 availability = UA / UN / IA 取 `0x00`,UB / UC / IB / IC / IN 取 `0x02`,`flags_seq` = `0x00`,`clip_mask` = 0xE6;② **已标定**(电气 8 路系数全部写入且落合法带)逐路 = `0x01`(TPAFE 三路)/ `0x03`(片内 ADC 五路,VALID 与 CLIPPED 同时为真),`flags_seq` = `0xFF`,`clip_mask` 仍是 0xE6。**VALID 与 CLIPPED 是两件独立事实**:写侧如实透传 `adc_cal_flags()` 逐路原值(`App/task/adc_task.c` 548 行),读侧不得据任何品质组合判废,PC 与回放页只给软告警(见下面「判废面与软告警面」一行) |
| eng 派生式 | `eng` 不是独立事实,是逐路有效位图的派生量:**电气 8 路全 VALID → 二次值(1),缺任意一路 → 引脚域(0)**。真机两处必须同口径,行号一并登记:判据本体 `App/app/ui_data_feed.c` 226 到 235 行的 `feed_eng()`(屏显侧),段头字段填写 `App/task/adc_task.c` 532 到 553 行(其中 552 到 553 行是段头侧的同一派生)。两处分布在**不同任务里现算**,理论上会撞上配置重载的毫秒级沉降窗而不一致——这是允许态,见下一行 |
| 判废面与软告警面 | **完整性判废面自始至终只有这些**:精确尺寸等式、`magic` 与 `ver`(含 `ver` 须等于魔数第三字节)、头 CRC 与数据 CRC、表序 `sem` 等于下标、`ch_count` 上限、`pts_per_cycle` / `pts_per_ch` / `pre_pts` 下限、长度为零、文件短读。**品质组合与单位标签永远不进这道门**。本轮新增的只是**软告警面**:`avail` 同时带 VALID 与 CLIPPED 时 PC 在 NOTE 列打「同时为真,两点标定完成后属正常,非故障」;`eng` 与 `flags_seq` 违反上一条派生式时打 WARN。两者都**不改退出码、不影响出图、不拒渲染**。理由:把「单位标签不一致」当数据损坏,等于让一次改标定文件的沉降窗销毁故障证据 |
| eng 派生式 | `eng` 不是独立事实,是**逐路变比配置状态**的派生量:**每一路电气量「要么已配变比、要么削顶(页面本来就显 `-`,不参与整帧量纲断言)」,八路全满足 → 二次值(1),缺任意一路 → 引脚域(0)**。判据本体 `App/app/ui_data_feed.c` 的 `feed_eng()`(屏显侧),段头字段填写在 `App/task/adc_task.c` 的封存交棒块里现算。**v2 起判据换成变比配置,不再看 `flags_seq`**:`ADC_CAL_F_VALID` 回答「准不准」、`r_q16` 回答「什么单位」,两件事;V0.1 五路片内 ADC 削顶永远拿不到 VALID,用 VALID 当量纲判据会让档位永不解除。两处分布在**不同任务里现算**,理论上会撞上配置重载的毫秒级沉降窗而不一致——这是允许态,见下一行 |
| 判废面与软告警面 | **完整性判废面自始至终只有这些**:精确尺寸等式、`magic` 与 `ver`(含 `ver` 须等于魔数第三字节)、头 CRC 与数据 CRC、表序 `sem` 等于下标、`ch_count` 上限、`pts_per_cycle` / `pts_per_ch` / `pre_pts` 下限、长度为零、文件短读。**品质组合与单位标签永远不进这道门**。软告警面:`avail` 同时带 VALID 与 CLIPPED 时 PC 在 NOTE 列打「同时为真,两点标定完成后属正常,非故障」;`eng` 与逐路变比配置违反上一条派生式时打 WARN。两者都**不改退出码、不影响出图、不拒渲染**。理由:把「单位标签不一致」当数据损坏,等于让一次改标定文件的沉降窗销毁故障证据 |
| v1 旧段兼容 | **不做**:`ver` 与 magic 第三字节必须都等于 2,v1 段(`ver` = 1)一律按「该段不可读」处理。理由:v1 没有 `r_q16`,同一份 code 在 v1 里只能解释成引脚域;让读侧按版本分支去猜「这一路到底折没折算」会把「格式已知」降级成「推断已知」,而旧段的有效寿命只到下一次清 `/wave`。升版时全部旧段作废是可接受的代价(`/wave` 是 100 槽轮转,本波不清库) |
| 通道表序与列序 | 第 i 个通道表项 ↔ 数据区第 i 列,按语义号**升序**:UA=0 UB=1 UC=2 UN=3 IA=4 IB=5 IC=6 IN=7(`App/algo/adc_input.h` 的 `adc_semantic_t`)。PC 必须校验表内语义号等于下标,不等即整段作废。交叉验证:`clip_mask` 恒 0xE6 = 2+4+32+64+128,恰好是 UB/UC/IB/IC/IN 五个语义号位,这份算术就是这个顺序的证据 |
| 样本编码 | int16 小端;TPAFE 侧 16 位补码双极性;STM32 侧 0..4095 原样存正数,**不加中点偏置**——V0.1 单极性无偏置是硬件现状,加偏置等于伪造双极性(`docs/算法/采集与算法层设计说明.md §13.5`) |
| 三个时间字段 | `t0_ms`(上电毫秒)、`wall`(epoch 秒,未对时 0)、`frame_seq`(段首帧序号)均 u32 无符号小端 |
@@ -264,6 +267,7 @@ flowchart TD
| 任务栈 | 未估 | 九任务全 PASS、**未抬栈**:adcTask 静态最坏链 1,424 B(余 1,136 B / 44 %)、defaultTask 3,040 B(余 2,080 B / 40 %)、uiTask 1,336 B(余 15,048 B / 91 %) |
- **差额构成(比预估多 860 B,可对账)**:原估漏记 `FIL` 528 B(`FF_FS_TINY=0`,落盘道刻意不上栈)、`DIR`/`FILINFO`、已封存段头副本 136 B、打包上下文(内部又含一份 136 B 段头);反向少记一项——落盘实际是**单缓冲 256 B**,不是预估的 512 B 双缓冲。**预估与实测都留在本节**:只写实测,下一次估算就看不见原估漏在哪。
- ⚠ **上行是 v1 布局(通道表 12 B/路、`sizeof(wave_seg_meta_t)` = 136)的实测数,冻结不改**——它记的是 2026-10-29 那次权威构建,改写数字等于伪造一次没做过的测量。v2(通道表 16 B/路、`sizeof(wave_seg_meta_t)` = 172,`wave_ch_desc_t` 12→16 × `WAVE_ST_CH_MAX` = 9 即 +36 B **每份段头副本**)落地后本节各项都会变,届时按**新一次构建**的 `.map` 重测重填,而不是在这几个数上加减。段头副本的份数:`App/algo/wave_ring.c` 的 `s_sealed`、`App/task/default_task.c` 的 `s_wmeta` + 打包上下文内含的一份、`App/task/ui_task.c` 的 `s_wave_meta`(UI 道)、`App/task/adc_task.c` 的 `s_wave_cal`(`wave_seal_cal_t`,116→152,**同样 +36**)。**这次重测已完成**:v2 落地的 2026-11-11 权威构建实测值(RW_IRAM1 97,024 B / 74.0%、逐对象 RW+ZI)登记在 `docs/内存管理/内存内容分配策略.md` §6.1,本节冻结的 v1 数字保持原样,两处不互相加减。
- **回放页读窗不在数据面那 6,760 B 内**:它计入 UI 道的 +1,640 B;两张表不能相加当总量,逐对象拆分见 `docs/内存管理/内存内容分配策略.md` §6。
## 8. 现场回放显示契约
@@ -646,7 +650,7 @@ Before/After 表与串口原文。
| 削顶五路被读成「系统有零序 / 有谐波」 | 现场误判 | 头里 `clip_mask` + 页面「削」徽标 + 空态不许画直线(§8.1) |
| 有人把 U 与 I 两段叠图比相位 | 得出**没有物理意义**的相位差 | 回放不做多路叠加;头里写明来源转换器;与 `docs/硬件平台/ADC通道映射说明.md` §7 通道归属行的 V0.1 禁止行同源 |
| 落盘把 defaultTask 拖住 | 日志与配置引擎变慢(defaultTask 是两者唯一写者) | 分次写(256 B 双缓冲)、一次心跳最多一段、`f_write` 前后有耗时观测;不做整段驻留再写 |
| **回放读路径把 uiTask 拖住**(2026-10-29 lead 评审补,此前风险表只有写侧没有读侧) | 一次渲染的实码 I/O(`App/task/ui_task.c` 376 到 398 行):段表刷新 1 次 `f_opendir` + 至多 100 次 `f_readdir`;未缓存段的段头装载 = 1 次 `f_open` + 1 次 128 B 头读 + **20 次 256 B 数据区读并边读边算数据 CRC** + 尾 2 B 一次 = **最坏 22 次小读**;已验过则只剩窗装载 = 1 次 `f_open` + 1 次 `f_lseek` + 1 次 80 B 窗读 + 40 点解码与峰值扫描(**一屏只碰一路通道一个窗,不是 8 路 320 点**)。uiTask 在 IWDG `required_mask` 内、窗 800 ms(口径同 `docs/子系统设计/看门狗设计说明.md` §11.3 的解耦心跳)→ **现象先是 LVGL 停摆与远程推流缺席,不是复位**(心跳与画屏解耦) | 卷锁 try 非阻塞(拿不到就显示「存储忙」,不等)+ 256 B 分次读 + 已验段结果缓存(同段同道不重复验)+ 只在首次进页 / 切段 / 切道时读。**单轮耗时上界未实测**:不新加埋点,用现成的 `task_prof` 读数(板级动作见 §10) |
| **回放读路径把 uiTask 拖住**(2026-10-29 lead 评审补,此前风险表只有写侧没有读侧) | 一次渲染的实码 I/O(`App/task/ui_task.c` 376 到 398 行):段表刷新 1 次 `f_opendir` + 至多 100 次 `f_readdir`;未缓存段的段头装载 = 1 次 `f_open` + 1 次 160 B 头读 + **20 次 256 B 数据区读并边读边算数据 CRC** + 尾 2 B 一次 = **最坏 22 次小读**;已验过则只剩窗装载 = 1 次 `f_open` + 1 次 `f_lseek` + 1 次 80 B 窗读 + 40 点解码与峰值扫描(**一屏只碰一路通道一个窗,不是 8 路 320 点**)。uiTask 在 IWDG `required_mask` 内、窗 800 ms(口径同 `docs/子系统设计/看门狗设计说明.md` §11.3 的解耦心跳)→ **现象先是 LVGL 停摆与远程推流缺席,不是复位**(心跳与画屏解耦) | 卷锁 try 非阻塞(拿不到就显示「存储忙」,不等)+ 256 B 分次读 + 已验段结果缓存(同段同道不重复验)+ 只在首次进页 / 切段 / 切道时读。**单轮耗时上界未实测**:不新加埋点,用现成的 `task_prof` 读数(板级动作见 §10) |
| 触发风暴 + 轮转覆盖 | 现场唯一一段有效波形被后到的抖动冲掉 | 冷却期 + `ovf` 计数;「最新段号」进快照;分段优先级属二期 |
| 定时自抓忘关 | 白烧 NAND 写次数(持久代价) | 默认关 + 上限 + 告警(§10 红线) |
| 标定落地后老文件被解释错 | 同一个系数字段值变了含义漂移 | 头里存**封存瞬间**的 `eng` 与系数快照;PC 脚本按头解释,绝不按当前固件口径解释 |
+12 -7
View File
@@ -163,9 +163,10 @@ write=1
| `db_ia` `db_ib` `db_ic` `db_in` | 电流死区 | 5 | 0 ~ 1000(1 A) |
| `db_freq` | 频率死区 | 500 | 0 ~ 5000(5 Hz) |
- 键数 9 ≤ 16,**通道系数覆盖不落本段**——系数唯一真源 = `[adc_cal]` / `[adc_cal_ofs]`
两段(标定落盘与整定入口布局,docs/算法/采集与算法层设计说明.md §13.8;104 `0x8040~0x804F` 写 =
`adc_cal_set` 代理,lead 裁②a)。
- 键数 9 ≤ 16,**通道系数覆盖不落本段**——系数与变比的唯一真源 =
`[adc_cal]` / `[adc_cal_ofs]` / `[adc_cal_sec]` 三段(标定落盘与整定入口布局,
docs/算法/采集与算法层设计说明.md §13.8;104 `0x8040~0x804F` 写 k/b、
`0x8050~0x8057` 写 r,均经 `adc_cal_set` / `adc_cal_set_ratio` 代理,lead 裁②a)。
- 守卫在**写路径**(104/壳 API 拒 NaN/负/越程);**文件通路引擎照收**(同 adc_cal 的
「人工编辑 + 沉降去抖」契约)——死区超程只让上送变钝、不毁量纲,无合法带哨兵。
- 生效链路:load/poll 原地改副本 → 104 COV 每拍实时读(无回调,104 改读快照与运行参数段契约);
@@ -194,9 +195,13 @@ write=1
配错即锁死真主站,待送检/现场需求立项;读者勿假设该键已存在。
- 落盘:UI 保存(task-9)经 `net_cfg_save_request()` → `SYS_EVT_CFG_SAVE` 整档
外科 save;引擎属主 = defaultTask 不变。
- 容量台账:本段上线后 `/config.ini` 受管段 = 6/8([network] 4 键 / [ftp] 3 键 /
[adc_cal] 9 键 / [adc_cal_ofs] 8 键 / [run_par] 9 键 / [comm] 2 键);段/键上限
的机器钉 = `test/host/cfg_file` TC-CONFIG-056/057。
- 容量台账:本段上线后 `/config.ini` 受管段 = **8/8——已满**([network] 4 键 / [ftp] 3 键 /
[adc_cal] 9 键 / [adc_cal_ofs] 8 键 / [adc_cal_sec] 8 键 / [run_par] 9 键 / [comm] 2 键 /
[sntp] 4 键)。⚠ **下一个受管段无处可放**:第九次 `cfg_file_register()` 会被直接拒,
只留一行 `[ERR]`,且**注册顺序决定谁被拒**(内核前 adc_cal 三段 + run_par + comm 先占 5 位,
defaultTask 的 net_cfg_load 再占 network / ftp / sntp 3 位)——再加段前必须先抬
`CFG_FILE_MAX_SECTIONS`(8)或合并既有段。
段/键上限的机器钉 = `test/host/cfg_file` TC-CONFIG-056/057。
### 3.7 `[sntp]` 段:SNTP 校时参数(2026-11-04 新增,板端 SNTP 客户端接入,#57 调研结论)
@@ -227,7 +232,7 @@ layer-deps 口径),段描述符由 `net_cfg.c` 的 `s_sntp_register()` 组
不拨针(防 RTC 无谓磨损)。冷启动(钟面无效)直接采用服务器 T3。
防欺骗/退避/KoD 细则 → `App/sync/time_sntp.c` 文件头 +
`test/host/sntp`(TC 命名即判据)。
- 容量台账:本段上线后 `/config.ini` 受管段 = **7/8**(前 6 段见 §3.6,本段 4 键)。
- 容量台账:本段上线后 `/config.ini` 受管段 = **8/8 满额**(前 7 段见 §3.6,本段 4 键)。
---
@@ -594,3 +594,4 @@ void adc_cfg_init(void)
| 2026-10-26 | v1.15:**新增 `[comm]` 段(道G task-8,「UI 补齐」波次里的非 UI 前置项)**——`App/protocol/iec104/iec104_comm.h/.c` 配对模块(104 公共地址 / 监听端口 2 键,getter 合法带自愈,adc_cal 先例);内核前 `iec104_comm_init()` 注册([adc_cal] / [run_par] 同「先注册后 load」契约);生效 = **改后重启**(不挂回调、不新增事件位,区别于 [network] 免重启重建);段占位 5→6 / 8,键表与主站地址不做理由 → `docs/子系统设计/网络配置说明.md` §3.6;机器钉 = `test/host/cfg_file` TC-CONFIG-056/057(段/键上限)+ `test/host/iec104` TC-IEC104-031~033(模板/重载/带外/ca 端到端)
| 2026-11-04 | v1.16:**新增 `[sntp]` 段(板端 SNTP 客户端接入,#57 调研结论落地)**——`Drivers/BSP/NETCFG/sntp_cfg.h/.c`(enable / server1 / server2 / poll_min 4 键;出厂默认**关闭 + 服务器留空 + 60 分钟** = "未配置零网络足迹"口径;`poll_min` 带外自愈 1..1440 → 60 不写回 RAM);数据本体 = `Drivers/BSP/NETCFG/sntp_cfg.h/.c`(不依赖引擎头,分层门禁 layer-deps 口径),段描述符由 `net_cfg.c` 的 `s_sntp_register()` 组装,注册编排 = `net_cfg_load()`(与 [ftp] 同编排,先注册后 load);消费方 = `App/sync/time_sntp.c`(挂 timeSyncTask 既有循环,vendor `Lib/coreSNTP` serializer 做 48B 报文编解码/防欺骗校验);保存立即生效 = 每轮直读 RAM 副本 + 网络代次重建弃 UDP socket 重散布,**无段回调、不新增事件位**;段占位 6→7 / 8;键表与生效细则 → `docs/子系统设计/网络配置说明.md` §3.7;机器钉 = `test/host/net_cfg` TC-NETCFG-005/006b..d/007(段注册契约)+ `test/host/sntp`(RFC 4330 §10 节奏 / §5.3 防欺骗 / §5.5 步进策略 / poll 钳位)
| 2026-11-05 | v1.17:**热重载/保存路径视图一致性守卫(issue #73 台架实证根治)**——真机 8/8 复现"运行中改配置触发热重载打回出厂默认"且此后任何保存吞段(1066B→986B):卷层在 stat→read 窗口给回旧/短视图,引擎照单应用(全段 memcpy 默认、零解析错误行)或照短视图外科重写文件。① `cfg_file_poll` 沉降读取后三点校验 + 内容判据(读长 == `st.size` + 读后复 stat (size, mtime) 不变 + `s_view_bad`:INI 文本任意偏移含 NUL = 坏块——台架 15:21 再实证**同尺寸整块全 0 旧映射块**可骗过纯三点校验,解析零错误 RAM 照打回,内容判据是该形态唯一引擎侧拦截):不符 → **拒绝应用**(RAM 原样 / 基线不刷新 / 2s 重试自愈),首次拒绝 `cfg poll view stale` 自检 + ERROR 一行(该行打印"读长B != stat B",内容判据或复 stat 命中时两个数可相等,命中的是内容/读中改写项),此后每 30 趟(≈60s)一条 WARN 计数;② `cfg_file_save` 同族守卫:视图与 stat 不符或内容坏 → **拒存返回 -1**(调用方保留 dirty 重试)原文件一字节不动 + `cfg save view stale` 自检——"短/坏视图 → 外科重写 → 非默认段被删"链就此斩断;③ 边界如实披露:一致但陈旧的整卷视图引擎层无法检出(dhara/FTL 属卷层),重启必然收敛,卷层根治前"改过配置后重启一次"仍是最稳口径。机器钉 = TC-CONFIG-058(poll HELD + 自愈)/ 059(save 拒存文件不动)/ 060(读中改写检出 + 日志限速)/ 061(同尺寸全 0 块内容判据拦截 + 自愈)/ 062(save 遇全 0 块拒存不动盘);`cfg_file.c` v1.6 / `cfg_file.h` v1.5 契约同步
| 2026-11-11 | v1.18:**新增 `[adc_cal_sec]` 段(端子变比 r,8 键 int32 Q16)**——标定折算由二维 `(code+b)×k` 升为三维 `(code+b)×k×r`(k = 引脚侧 LSB、b = 零点、r = 端子二次值↔引脚电压);**段数 7→8 = 达 `CFG_FILE_MAX_SECTIONS` 上限**,此后第九段必被 `cfg_file_register()` 拒(只留一行 `[ERR]`,且先注册者留位),再加受管段须先抬上限或合并段;注册在 `adc_cal_init()`(内核前,与另两段同批),`p_cb = NULL`、段 ROM 默认**非 0**(电压族 65536000 = 1000.0、电流族 1927532 = 29.4118,台架实测反算);键表 / 合法带 / 与 VALID 的关系 → `docs/算法/采集与算法层设计说明.md` §13.2 / §13.8,台架来源 → `docs/硬件平台/ADC通道映射说明.md` §7.2 |
+49 -46
View File
@@ -156,61 +156,64 @@ STM32(U17)为 LQFP144 脚号。
3. **满量程必须写明参考点**:TPAFE 通道满量程 = ±5 V **@ 引脚**(跨度 10 V);③④ 均已定死(④ 值见 §7.2),端子侧换算允许写"引脚侧数值 × ④ 段变比";但两点标定置 `VALID` 之前**严禁**把"码 × 152.59 µV"当端子二次值写进代码/文档(那是引脚电压),§7.2 的标称系数只是算定值、不是已验证二次值。
4. **仍待实测的只有两点标定**(给端子域系数、置 `VALID`,`eng` 随之升 1);完成前 `eng` 保持 0、输出单位仍是 ADC 引脚电压。实测斜率与 ② 段理论值偏差 >2 % 时**先查前端**(分压/变比/焊接),**不得用系数凑错斜率**——那会把硬件的错固化成"标定值"。
### 7.2 ④ 段变比登记(2026-11-06 定死)
### 7.2 ④ 段变比登记(2026-11-11 台架实测修正)
> **来源**:用户现场报的子板参数(2026-11-06,Issue #41),按 `docs/算法/采集与算法层设计说明.md §13.12` #2 的路 ①「按图(参数)直接算」
> 算得;子板原理图仍不在仓库内,路 ②「万用表同时量端子与引脚」未做——数值上的理想化偏差与逐路差归两点标定
> (见本节末「复核口径」)。
> **这一节要你干什么**:写清"端子二次值 ÷ ADC 引脚电压"这个比值到底是多少、凭什么。
> 它是固件里 `[adc_cal_sec]` 段出厂默认值的唯一来源(`App/algo/adc_cal.h` 的 `ADC_CAL_R_Q16_*`),
> 也是屏上读数从"引脚电压"变成"二次值"的唯一依据。
>
> **2026-11-11 修正**:本节原先按用户报的子板参数(200 kΩ / 330 Ω)算定电压族为 **606.06 : 1**。
> 台架用继电保护仪在**端子上**注入标准量、读屏面示数反算,得到的是 **1000.0 : 1**——
> 两者差 60 %,远超屏面分辨能力,**实测为准,606.06 作废**(保留在本节末的「历史值与作废依据」里,
> 供后来者判断"是不是又算错了一遍")。子板原理图仍不在仓库内,所以这条比值目前只有实测证据、
> 没有图纸旁证。
**电压族 UA/UB/UC/UN——子板 PT:2 mA/2 mA,输入侧 200 kΩ,输出侧 330 Ω**:
**实测数据(2026-11-11,继保仪端子注入,屏面 3 位小数)**:
- 变比 = U端子 ÷ UAINn = (2 mA × 200 kΩ) ÷ (2 mA × 330 Ω):电流比 1:1 电流相消,只剩两侧负担电阻之比 =
200000 ÷ 330 = **606.060606… ≈ 606.06 : 1**(V/V,无量纲);
- 额定点(按参数推读):端子 **≈400 V**(2 mA × 200 kΩ)→ `AINn` 侧 **0.66 V**(2 mA × 330 Ω)。
| 通道 | 端子注入(有效值) | 屏面读数 | 反算变比 | 该点码值(RMS) | 1 LSB 占读数 |
|---|---|---|---|---|---|
| UA | 5.000 V | 0.005 V | 5.000 / 0.005 = **1000.0 V/V** | 约 33 码 | 3.05 % |
| IA | 0.500 A | 0.017 V(折算前是引脚电压) | 0.500 / 0.017 = **29.4118 A/V** | 约 112 码 | — |
**电流族 IA/IB/IC/IN——子板 CT:5 A/2.5 mA,输出侧负担电阻 68 Ω**:
- **电流族 29.4118 A/V 与本节原登记值精确一致**(= 匝比 2000 ÷ 68 Ω),两条独立来路
(参数算定 + 实测反算)互证,可信度最高。
- **电压族 1000.0 V/V 只有单点粗测**:5 V 注入时引脚侧只有约 5 mV ≈ 33 码,1 LSB = 3 % 读数,
所以 3 位小数给出的比值区间是 **909 ~ 1111**。这个数是"量级对、精度未定",
**不是**可以写进铭牌的精度指标——送检前必须用标准源两点标定重写(见本节末「精度从哪来」)。
- 若真值是 606.06,5 V 注入应显 0.008 V(差 60 %),而屏面实际给 0.005——分辨率完全够分辨这两个值,
所以这不是"测量误差",是原参数算定假设与实物不符(最可能是分压电阻实际值≠报参数,
或子板型号与报参不是同一款)。
- ADC 读的是负担电阻两端电压,所以这个"变比"是**带单位的比**:I端子 ÷ UAINn = 5 A ÷ (2.5 mA × 68 Ω) =
5 ÷ 0.17 = **29.4118 A/V**(= 匝比 2000 ÷ 68);与 V/V 无量纲量不可混写,进系数表须分注单位;
- 额定点:端子 **5 A** → `AINn` 侧 **0.17 V**。
**码值量化与标定点怎么选**(同一条链上的实用结论):注入点越高,1 LSB 占读数越小——
**算术复核**:200000 ÷ 330 = 606.060606… ✓(用户给值 606.0606060606061);5 ÷ 0.17 = 29.41176470… ✓
(用户给值 29.41176470588235,与 2000 ÷ 68 同源)。两数与报参口径一致,未发现算误。
| 端子注入 | 引脚侧 | 码值(RMS) | 1 LSB 占读数 |
|---|---|---|---|
| 5 V | 5 mV | 33 | 3.05 % |
| 20 V | 20 mV | 131 | 0.76 % |
| 57.7 V(额定相电压) | 57.7 mV | 378 | 0.26 % |
| 100 V | 100 mV | 655 | 0.15 % |
| 120 V | 120 mV | 786 | 0.13 % |
**②×③×④ 端到端标称系数**(③ 已定 1:1)——只作两点标定的初值与量级核对用,**ROM 不动**(ROM 出厂表
仍按 ② 引脚口径,§7.1 可评审结论 1):
故两点标定**不要用 5 V 这种小注入**:建议取 20 V 与 120 V 两点(都在 TPAFE ±5 V 引脚量程内,
且码值足够大)。这也是本节的 1000.0 只能算"粗测"的原因。
| 族 | 转换器 | 端子域系数(标称) | 额定点码数(RMS) | 占转换器跨度 |
|---|---|---|---|---|
| 电压 UA/UN | TPAFE | 152.59 µV/码 × 606.06 = **92.48 mV/码** | 0.66 V ≈ 4325 码 | 10 V 跨度的 6.6 % |
| 电压 UB/UC | STM32 | 805.66 µV/码 × 606.06 = **488.28 mV/码** | 0.66 V ≈ 819 码 | 3.3 V 跨度的 20 % |
| 电流 IA | TPAFE | 152.59 µV/码 × 29.4118 A/V = **4.488 mA/码** | 0.17 V ≈ 1114 码 | 10 V 跨度的 1.7 % |
| 电流 IB/IC/IN | STM32 | 805.66 µV/码 × 29.4118 A/V = **23.70 mA/码** | 0.17 V ≈ 211 码 | 3.3 V 跨度的 5.2 % |
**变比进固件的形式(为什么是独立一维,不并进 `k`)**:`r` 存 int32 Q16 比值,
电压族 `ADC_CAL_R_Q16_VOLT = 65 536 000`(= 1000.0 × 65536,无舍入)、
电流族 `ADC_CAL_R_Q16_CURR = 1 927 532`(= 29.4118 × 65536)。
**不能**把它乘进 `k`:UA 端子域系数 = 152.59 µV/码 × 1000 = 152 587.9 µV/码,
Q16 后是 1.0e10,而 int32 上限只有 2.147e9;UB/UC 走 STM32 更到 5.28e10。折算模型见
`docs/算法/采集与算法层设计说明.md` §13.2。
补一句:额定点正弦峰值 = RMS × √2(电压族 ≈0.93 V、电流族 ≈0.24 V),两片转换器的量程都装得下;STM32
五路交流负半周削到 0 的问题仍是 §7「前端偏置」行的 V0.1 既有口径,不是本节新发现。变比口径只圈电气 8 路,
温度与备用路不进 ④ 换算。
**精度从哪来**:出厂默认值只保证量纲与量级;**精度**必须靠标准源两点标定:
在端子上加两个已知量、读两点的原始码值(串口 `adc:` 行),代进
`adc_cal_two_point()` 算出该路的 `r` 与 `b`,经 `adc_cal_set_ratio()` / `adc_cal_set()` 写入
并落盘。这一步做完才有 `ADC_CAL_F_VALID`,保护判据才会解卡
(`docs/算法/采集与算法层设计说明.md` §13.3 / §13.4)。
**eng 判定(「端子/子板变比确认」卡(#41)的完成判据)**:
- `eng = 1`(出端子二次值)两条件:④ 变比到手——**已满足**(本节,电压/电流两族);电气 8 路
`ADC_CAL_F_VALID` 全置位——**未满足**(`App/app/ui_data_feed.c` 的 `feed_eng()` 门,「两点标定与精度验收」卡承担)。
- 判定:**`eng` 保持 0**,输出单位仍是 ADC 引脚电压,M4 承诺边界不变。本节改变的是:两点标定从
「开不了工」变成「可开工」——端子侧参照已定死,本节标称系数即其起点。
**复核口径(别把本节数值当本板实测)**:
- 参数是铭牌/板面值:不含理想变压器偏差(励磁电流、绕组阻抗、负担误差),不含负担电阻容差,也不含逐路差
(电压 4 路共用 606.06、电流 4 路共用 29.4118,逐路真值以标定实测为准)。
- 下游机器面核对跟着在:两点标定的逐路实测系数吸收 ④ 段实际偏差;`App/algo/adc_cal.c` 合法带哨兵
(k ∉ ROM×[0.5, 2.0] → 回退 ROM)是「④ 数量级错了」的兜底。
- **本节标称系数是给「两点标定与精度验收」卡的输入,且带着两道存储口径题**(本卡不替它定案):
k 按 µV/码 int32 Q16 存储、上限 32767 µV/码(`docs/算法/采集与算法层设计说明.md §13.2` 的"远够"论断做在引脚域
假设上)——电压族端子域标称已越上限(92.48 mV/码 ≈ 92479 µV/码,UB/UC 达 488.28 mV/码 ≈ 488278 µV/码),
电流族两档(4488 / 23696 µV 等效)尚在带内;且合法带基准读 ROM 出厂表(现为引脚域系数),端子域 k
会被 [0.5, 2.0] 哨兵顶回。标定写入端子域系数之前,存储单位(或每路标度因子)与出厂表基准须一并换血,
否则正确的标定值也会被拒。
- 端子级上额定电压仍是独立动作:变比有数不等于可以在端子上加额定电压,先量端子对地通断的前置与风险次序不变,本节点不动高压。
**历史值与作废依据**(别照着旧数算):原按"子板 PT 2 mA/2 mA、输入侧 200 kΩ、输出侧 330 Ω"算得
变比 = 200000 ÷ 330 = 606.0606…。算术没问题(复核过两遍),**错在前提**:这组参数与实物不符。
同一组参数推出的"额定点端子 ≈400 V → `AINn` 侧 0.66 V"也因此不再成立。若将来拿到子板原理图,
正确的做法是**用图纸复核实测量级**、而不是用图纸替换实测——两者冲突时以实测为准。
---
@@ -299,7 +302,7 @@ STM32(U17)为 LQFP144 脚号。
|---|---|---|
| ① 至少 1 路电压通道经已知电压注入验证,斜率与理论 LSB 偏差 ≤2 % | ✅ 2026-10-27 | UA 6544.2 vs 6553.6 码/V(−0.14 %),§8.1 |
| ② `adc_cal` 折算落地,采样环里是**引脚电压 V** 而非码值 | ✅ | `App/task/adc_task.c:425`(全仓唯一折算点) |
| ③ 8 通道系数 / 零点可落盘,加载失败回退 ROM 默认 | ✅ | `[adc_cal]` / `[adc_cal_ofs]`,口径 `docs/算法/采集与算法层设计说明.md` §13.8 |
| ③ 8 通道系数 / 零点 / 变比可落盘,加载失败回退 ROM 默认 | ✅ | `[adc_cal]` / `[adc_cal_ofs]` / `[adc_cal_sec]`,口径 `docs/算法/采集与算法层设计说明.md` §13.8 |
| ④ 三处下游常数按工程量重定并注明依据 | ✅(有尾巴) | `docs/算法/采集与算法层设计说明.md` §13.7;幅值门在 `eng=0` 恒失效已记因于 `freq.h:58-60` |
| ⑤ "未标定 → 保护闭锁"生效 | 🟡 软件道 ✅;**"解锁"这半程要等 `VALID` 置位**才能上台架验 | `adc_cal_prot_blocked()` + `App/task/protect_task.c:268` |
| ⑥ 量纲回归用例(不该动作时不动作)通过 | ✅ | `test/host/prot_dim/`,口径 `docs/算法/采集与算法层设计说明.md` §13.6 |
+83 -34
View File
@@ -434,8 +434,8 @@ UA 无幅值落 IA → 0.0020 Hz。
| 问题 | 唯一答案 | 代码落点 |
|---|---|---|
| 谁是 k/b 的真源 | `[adc_cal]` / `[adc_cal_ofs]` 两段的引擎 RAM 副本(`App/algo/adc_cal.c`),别处不得再存一份系数 | `s_kcfg` / `s_bcfg`;`App/algo/run_par.h` 头注释「不另存一份系数」 |
| 谁能写 RAM | **只有 run_par 的两支语义 setter** `run_par_cal_k_set()` / `run_par_cal_b_set()`——"写值 + 置脏"成对发生(task-7 落地,lead 裁②消除两份置脏逻辑);`adc_cal_set()` 本身**只写 RAM、不置脏、不落盘、不返回"稍后会存"** | `App/algo/run_par.c` 语义 setter 内 `adc_cal_set()` 之后才 `s_dirty = 1u` |
| 谁是 k/b/r 的真源 | `[adc_cal]` / `[adc_cal_ofs]` / `[adc_cal_sec]` 三段的引擎 RAM 副本(`App/algo/adc_cal.c`),别处不得再存一份系数或变比 | `s_kcfg` / `s_bcfg` / `s_rcfg`;`App/algo/run_par.h` 头注释「不另存一份系数」 |
| 谁能写 RAM | **只有 run_par 的三支语义 setter** `run_par_cal_k_set()` / `run_par_cal_b_set()` / `run_par_cal_r_set()`——「写值 + 置脏」成对发生(lead 裁②消除两份置脏逻辑);`adc_cal_set()` / `adc_cal_set_ratio()` 本身**只写 RAM、不置脏、不落盘、不返回「稍后会存」** | `App/algo/run_par.c` 语义 setter 内 `adc_cal_set()` / `adc_cal_set_ratio()` 之后才 `s_dirty = 1u` |
| 谁落盘、什么时机 | 只有 defaultTask(配置引擎属主)的 `run_par_store_save()`:iec104Task 主循环 `run_par_dirty_peek()` → 置 `SYS_EVT_RUN_PAR_SAVE` → 属主 `cfg_file_save()` 整档外科保存 → 成功清脏,失败留脏下轮重试 | `App/algo/run_par.c` 落盘函数;本模块自身零文件 IO |
**`cfg_file_poll()` 不是保存路径**:它的方向是**盘 → RAM 的重载**(外部改档 → 沉降去抖 → 重载,见
@@ -525,8 +525,10 @@ adcTask 静态最坏链与余量的**台账在** `docs/内存管理/RAM使用分
104 与 UI 拿到的数从哪来一份**。它是 `App/algo/adc_cal.*`、`meas.*`、`App/cfg/cfg_file.*`、
`App/task/protect_task.c`、`Drivers/BSP/UI/ui_data*` 这些模块头注释里引用的口径出处。
**当前状态**:折算链已落地,量纲停在 **ADC 引脚电压 V**(`eng = 0`);保护判据因此处于
**未标定闭锁**态(§13.3)。精度、端子二次值都在等外部资源(子板变比、标准源)。
**当前状态**(2026-11-11 起):折算链已落地,量纲由**每路变比 `r`** 决定——出厂默认已带台架
实测反算的变比(电压 1000 V/V、电流 29.4118 A/V),所以烧进新固件后屏上直接显**二次值**;
精度仍待标准源两点标定改写 `r`。保护判据仍处于**未标定闭锁**态(§13.3),因为 VALID 还要求
两点标定这一步真的做过。
### 13.1 通道量纲与折算点
@@ -541,52 +543,82 @@ adcTask 静态最坏链与余量的**台账在** `docs/内存管理/RAM使用分
- 通道数 `ADC_SEM_COUNT = 9`(8 电气 + 温度),枚举定义 `App/algo/adc_input.h:38-49`(`adc_semantic_t`)。
- **唯一折算点**:`App/task/adc_task.c:425` 的 `frame[s] = adc_cal_apply((adc_semantic_t)s, code)`——
全仓只有这一处把码值变成工程量,现单位 = **ADC 引脚电压 V**(`eng = 0`)。
全仓只有这一处把码值变成工程量,输出单位由该路变比 `r` 决定(§13.2):`r ≠ 0` → 二次值 V/A,
`r = 0` → ADC 引脚电压 V。
- 端到端链路分四段(① 语义↔网名↔通道、② 引脚电压↔码值、③ `AINn` ↔ 引脚、④ 端子 ↔ `AINn`),
属主 `docs/硬件平台/ADC通道映射说明.md` §7.1:①②③ 已定死(③ 串阻不分压、比值 1:1),**只有 ④ 是缺口**。
属主 `docs/硬件平台/ADC通道映射说明.md` §7.1:①②③ 已定死(③ 串阻不分压、比值 1:1),
④ 的登记值 = 电压族 1000 : 1、电流族 29.4118 A/V(§7.2,2026-11-11 台架实测修正,
该节原按子板参数算定的 606.06 已被实测推翻)。
### 13.2 折算模型:板级事实进 ROM,现场标定进 cfg
### 13.2 折算模型:三个系数各管一件事
```
工程量[V 或 A] = (code + b) × k_f
k_f = k[µV/码] × 1e-6 (float,init/load 时算一次)
工程量[V 或 A] = (code + b) × k[µV/码] × 1e-6 × r
```
| 符号 | 含义 | 存哪 | 定标 |
| 符号 | 管什么 | 存哪 | 默认值 |
|---|---|---|---|
| `k` | µV/码(通道灵敏度) | cfg 段,int32 **Q16**(µV/码 × 65536) | 默认 = 板级理论系数(§13.1 表) |
| `b` | 零点偏移(码值) | cfg 段,int32 | 默认 0 |
| `k_f` | V/码(运行时) | RAM,`adc_cal_init/load` 时由 `k` 派生 | 不落盘 |
| `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) |
- **为什么必须拆成三个、不能把变比并进 `k`**:并进 `k` 会直接溢出存储口径——UA 端子域
`k` = 152.59 × 1000 = 152 587.9 µV/码,Q16 就是 1.0e10,而 int32 上限只有 2.147e9。
拆开之后 `r` 单独存 Q16 绰绰有余(1000 → 65 536 000)。更根本的理由:`k` 是板级前端斜率、
`r` 是子板/端子板的分压比,两者是**不同的事实**,合并就是把硬件比伪装成标定系数
(`docs/硬件平台/ADC通道映射说明.md` §7.2 明令禁止"用系数凑硬件")。
- **换算写成"先引脚电压、再乘变比"两步**,不把 `k×r` 先并成一个大系数:`k` 的 Q16 上界
5.28e7 乘码值仍落在 float32 的 2^24 精度区内;若先并成 60 万级 Q16(→4e10)再乘码值,
float32 只剩 7 位有效数字,末位抖动会跑到屏面 3 位小数上。
- **为什么存 µV 而不是 V/码**:`k` 在 V/码 量级是 3×10⁻⁴,Q16 只有 20 个刻度(5 % 分辨率);
换成 µV/码是 305,Q16 有 2000 万个刻度。
- **为什么 Q16 而不是参考项目的 Q10**:TPAFE 通道 Q10 只能表达 312(+0.16 %),Q16 误差 < 0.001 %;
两者都占 4 字节,Q16 把"系数定点舍入"这一项从误差预算里直接抹掉。参考项目用 2^10 是因为定点 DSP
乘法成本,本机有 FPU、运行时本来就是 float。int32 上限可表达 32767 µV/码,远够。
- **运行时不走定点**:`k` 预乘成 `float k_f`,逐点只做一次浮点乘——1 kHz × 9 通道 = 9k 次/秒,可忽略。
- **系数合法带**:`adc_cal_set()` / `adc_cal_two_point()` 只接受落在板级 ROM 系数 ×[0.5, 2.0] 内的 `k`。
实测斜率与理论值偏差大时**先查前端**(分压 / 变比 / 焊接),不许用系数把硬件的错凑回来——
那等于把故障固化成"标定值"。零点越 ±`ADC_CAL_B_MAX` 同样直接拒绝(口径在 `App/algo/adc_cal.h`)。
- **运行时不走定点**:逐点只做两次浮点乘(`×k` 再 `×r`)——1 kHz × 9 通道 = 9k 次/秒,可忽略。
- **`k` 的合法带**:`adc_cal_set()` 只接受落在板级 ROM 系数 ×[0.5, 2.0] 内的 `k`。实测斜率与理论值
偏差大时**先查前端**(分压 / 变比 / 焊接),不许用系数把硬件的错凑回来——那等于把故障固化成
"标定值"。零点越 ±`ADC_CAL_B_MAX`(8192 码)同样直接拒绝。
- **`r` 的合法带是绝对带,不是 ROM×[0.5,2.0]**:`r` 是现场标定量,不同 CT/PT 变比差几个数量级,
窄带没有意义;下界取 1.0(分压与负担电阻只会把端子值变小,比"比值 < 1"更可能的是把 mV 当 V 填错),
上界是 int32 能表达的 32767.99。**`r = 0` 是合法值**,语义 = "本路不折算,输出停在引脚电压",
这也是现场关掉某一路折算的正规手段(改成 1.0 反而会留下一个假变比)。
- **两点标定今后标的是 `r`,不是 `k`**(`adc_cal_two_point()` 已改签名,`k_q16` 变成入参):
`k` 是板级事实、现场不该改也改不动,真正未知的是"端子 ↔ 引脚"这一层。公式在公式核的注释里,
要点是分两步算(`t = dc × r` 先出来,再还原 Q16)——一次算 `dv×2^32/(dc×k_q16)` 会在
高压注入时把 long long 顶穿。
### 13.3 三态可用性:标定 ≠ 可测
`k` 的默认值是**板级理论系数**,此时工程量单位是"ADC 引脚电压",不是二次值——还缺互感器/子板变比。
所以通道状态必须让消费者区分三档,不能把引脚电压当二次值用:
`r` 的出厂默认值是**台架实测反算的变比**,所以出厂状态下每路的**量纲**就已经是二次值;
但"这个数准不准"要靠标准源两点标定才能回答。所以通道状态仍必须让消费者区分三档:
```c
#define ADC_CAL_F_VALID 0x01u /* 该通道 k 已按变比标定 → 工程量为二次值 */
#define ADC_CAL_F_VALID 0x01u /* 该路已两点标定确认(k/b 在带内 且 r 已配置)→ 读数可信 */
#define ADC_CAL_F_CLIPPED 0x02u /* 硬件削顶(单极性无偏置)→ 交流量不可信 */
```
**两个事实互不派生、别互相反推**:`VALID` 回答"准不准",`r` 回答"什么单位"。
`r = 0` 时 `VALID` 必为 0(合法带要求 r 已配置),所以"VALID 未置位却有二次值读数"
这种组合不可能出现;反过来说,只配了变比、没做两点标定的通道,输出**确实是**二次值、
只是精度未定——量纲标签若跟着 VALID 走,屏上就会出现"数值是 5.000(二次)却标未标定 V"
这种自相矛盾。
| 通道 | `VALID` | `CLIPPED` | 消费者应如何对待 |
|---|---|---|---|
| UA / UN / IA | 标定后置位 | 0 | 可用于保护、计量、上送 |
| UA / UN / IA | 两点标定后置位 | 0 | 可用于保护、计量、上送 |
| UB / UC / IB / IC / IN | 永不置位(V0.2 硬件改版前) | **1,且是来源派生**(不是实测削顶,判据见 §13.5) | 只作码值监视;点表**已绑定**,靠品质位把 104 侧驱动成 IV、值清零 |
| TEMP | 0 | 0 | 不参与线性标定,页面显示 `--` |
| TEMP | 0 | 0 | 不参与线性标定(`r` 恒 0),页面显示 `--` |
`adc_cal_flags(sem)` 是三处(UI / 104 / 保护)唯一的可用性查询入口;`adc_cal_apply()` **不返回**可用性,
所以"只看系数不看 flags"这种用法在接口层就被堵掉(CLIPPED 通道恒报 IV 的单测见 `test/host/prot_dim/`)。
**整帧量纲标签(`eng`)的判据与上面两个都不同**:它看的是"每路要么已配变比、要么削顶不显示"
(`adc_cal_ratio_configured()`),与 `VALID` 无关。理由:削顶通道页面本来就显 `--`,不进任何读数,
拿它们卡住整帧标签只会让 V0.1 那五路削顶把徽标永久钉在"未标定"上。判据在
`App/app/ui_data_feed.c` 的 `feed_eng()`,口径真源写在 `App/algo/adc_cal.h` 的
`adc_cal_prot_blocked()` 注释 ③。
### 13.4 未标定即闭锁
保护判据拿"ADC 引脚电压"跟"二次值定值"比必然误动作,所以 `protect_task` 多一个闭锁源:
@@ -620,9 +652,9 @@ V0.1 恒命中 UB/UC/IB/IC/IN,位图 `0xE6`),它表示"这路不能测交
`cal:` 行的 `rail=` 位图与 `rail: edge=…` 那行 `[ERR]`——这条路径至今只在直流饱和里起过(判据见
`docs/测试说明/专项验证/ADC前端注入验证流程.md` §8.5 削顶卡)。
> **承诺边界**:只到 **ADC 引脚电压**(`eng = 0`)。"端子二次值"要等子板变比确认、
> 标准源两点标定写入 `[adc_cal]` 并置 `VALID`;`eng` 升 1 的条件是电气 8 路 VALID **全置位**
> (`App/app/ui_data_feed.c` 的 `feed_eng()`)。两点标定怎么开工见 §13.13。
> **承诺边界**(2026-11-11 更新):**量纲**已到端子二次值(出厂默认变比已带硬件事实,
> `eng` 判据见 §13.3);**精度**仍只到"ADC 引脚电压这一层的 ±2 %"——标准源两点标定
> 写入 `[adc_cal_sec]` 的 `r` 并置 `VALID` 之后,二次值才谈得上精度。两点标定怎么开工见 §13.13。
**动态验证已跑两轮(2026-11-02 / 11-04),六轮全部闭合**——幅值 / 波形 / 相位 / 测频 / 谐波 /
削顶直流档都过了;实测数与逐轮结论只详述在 `docs/硬件平台/ADC通道映射说明.md` §8.2,本节不抄第二份。
@@ -630,7 +662,7 @@ V0.1 恒命中 UB/UC/IB/IC/IN,位图 `0xE6`),它表示"这路不能测交
| 下游 | 现在 | 为什么 |
|---|---|---|
| 基波有效值 | ✅ 过(±2 % 内,三档幅值两点复现) | 台架幅值轮 + 波形轮(基波分量口径)实证;**端子二次值仍不归这一档**——从引脚到端子还隔着 ④ 段变比 |
| 基波有效值 | ✅ 引脚层过(±2 % 内,三档幅值两点复现) | 台架幅值轮 + 波形轮(基波分量口径)实证;**二次值这一档的精度仍未验**——出厂变比是单点粗测反算(5 V 注入只有 33 码,1 LSB = 3 %,电压族真值区间约 909~1111),标准源两点标定后才算数 |
| 相位 | ✅ 同片内过(录波档 ≤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` 唯一一条、撤注恒持 |
@@ -652,7 +684,7 @@ V0.1 恒命中 UB/UC/IB/IC/IN,位图 `0xE6`),它表示"这路不能测交
| 量纲档 | 怎么进入这个档 | 现在的处置 |
|---|---|---|
| `eng = 0`(ADC 引脚电压 V) | `VALID` 未置位——**当前产品构建恒此态** | §13.4 闭锁停走整条判据链;UI 第 7 行显示"未标定"徽标 |
| `eng = 1`(二次值 V / A) | ④ 变比到手 **且** 电气 8 路 `adc_cal_flags()` 的 VALID 全置位(`App/app/ui_data_feed.c` 的 `feed_eng()`) | 定值与工程量同域,判据才名副其实 |
| `eng = 1`(二次值 V / A) | 每路要么已配变比(`adc_cal_ratio_configured()`)、要么削顶不显示(`App/app/ui_data_feed.c` 的 `feed_eng()`) | 定值与工程量同域,判据才名副其实 |
| 混档(最坏情况) | 只改折算系数、不改下游常数 | §13.7 那三处必须跟着动,这正是 §13.7 存在的理由 |
### 13.7 三个必须同步重定的下游常数
@@ -665,22 +697,33 @@ V0.1 恒命中 UB/UC/IB/IC/IN,位图 `0xE6`),它表示"这路不能测交
| 2 | `App/protocol/iec104/iec104_pts.c:69-80` | `k_yc[].deadband` 已按点重定:电压 0.5 V、电流 0.005 A、频率 0.5 Hz(真源在 `run_par`,UI 可改) | ✅ 已重定;④ 变比到手后需再复核一次量纲 |
| 3 | `App/algo/prot_set.c:25-70` | `g_prot_set_meta[]` 定值 = 二次值 A / V(基准 100 V / 1 A):OC1 10 A、OV 120 V、UV 20 V、3U0 15 V… | 🟡 口径对,但**标定前无法在板上验真**;精度目标定了之后连同 `min`/`max` 复核 |
### 13.8 落盘:拆两段,不碰引擎
### 13.8 落盘:拆三段,不碰引擎
`CFG_FILE_MAX_KEYS = 16`,8 通道 ×(k + b)= 16 正好顶格,将来加一个键就爆。故拆两段:
`CFG_FILE_MAX_KEYS = 16`,8 通道 ×(k + b + r)= 24 个键,一段放不下
(8 通道 ×(k + b)= 16 已经正好顶格,加第三个维必爆)。故拆三段:
| 段名 | 键 | 字节 | 回调 |
|---|---|---|---|
| `[adc_cal]` | `k_ua` `k_un` `k_ia` `k_ub` `k_uc` `k_ib` `k_ic` `k_in` + `valid`(uint8 位图) | 8×4 + 1 = 33 | 不挂(RAM 副本即生效值) |
| `[adc_cal_ofs]` | `b_ua` … `b_in` | 8×4 = 32 | 不挂 |
| `[adc_cal_sec]` | `r_ua` `r_un` `r_ia` `r_ub` `r_uc` `r_ib` `r_ic` `r_in`(int32 Q16 比值) | 8×4 = 32 | 不挂 |
段名 7 / 11 字符、键名 4 字符,均在 15 字符上限内;段数总计 5/8(含 `[run_par]` 9 键死区),未逼近上限。
段名 7 / 11 / 11 字符、键名 4~5 字符,均在 15 字符上限内;**段数总计 8/8——段位已满**
(本层三段 + `[run_par]` 9 键 + `[comm]` 2 键 + `[network]` / `[ftp]` / `[sntp]`,
与全仓 8 处 `cfg_file_register()` 调用一一对应,上限 `CFG_FILE_MAX_SECTIONS = 8`)。
⚠ **再加受管段必须先抬上限或合并段**:第九段会被 `cfg_file_register()` 直接拒掉,
只剩一行 `[ERR]`,而注册顺序决定谁被拒(谁先注册谁留下)。
- 系数唯一真源仍是这两段——104 写 k/b 经 `adc_cal_set()` 代理,`run_par` **不存第二份**;
落盘链走 `SYS_EVT_RUN_PAR_SAVE`(iec104Task peek 投递 → defaultTask 整档 save),`p_cb` 依旧未用。
谁写 RAM / 谁落盘 / 什么时机的完整链条见 §10.1。
- 系数唯一真源仍是这三段——104 写 k/b/r 经 `adc_cal_set()` / `adc_cal_set_ratio()` 代理,
`run_par` **不存第二份**;落盘链走 `SYS_EVT_RUN_PAR_SAVE`(iec104Task peek 投递 →
defaultTask 整档 save),`p_cb` 依旧未用。谁写 RAM / 谁落盘 / 什么时机的完整链条见 §10.1。
- ⚠ **变比段的 ROM 默认不是 0**:烧进新固件后屏上立刻显二次值。这与"没标定过"不矛盾——
变比是硬件事实(出厂就该带着),`VALID`(准不准)仍然要求两点标定,两者互不代替(§13.3)。
- ⚠ **负零点**:零点偏移是带符号量,引擎只有 `CFG_VAL_UINT`(`strtoul` + 上界校验)时 −5 会落成
`4294967291`。所以 `[adc_cal_ofs]` 走 `CFG_VAL_INT`(定案口径见 §13.12)。
- **一次变比不新开段**:PT/CT 一次额定已在 `prot_set` 里(`PS_PT_PRI` / `PS_PT_SEC` / `PS_CT_PRI` /
`PS_CT_SEC`,104 地址 0x8820/0x8821/0x8825/0x8826),再开一段就是同一事实两个真源。
变比段只登记**二次值口径**的 `r`;`eng = 2`(一次值)继续预留。
### 13.9 遥测快照:让 104 与 UI 共用一份上游
@@ -765,6 +808,12 @@ typedef struct {
| 零点负值怎么存 | 配置引擎**新增 `CFG_VAL_INT`**(有符号十进制、小端补码),而不是把零点整体偏置成正数再回读时减法还原 | 偏置方案要在每个读点都记得减,漏一处就是静默错值;INT 类型是一次性成本 |
| 系数落盘用什么单位 | **µV/码的 Q16 定点**,不存 V/码 浮点 | Q16 只剩 20 个刻度,V/码 直存会把有效位丢掉 |
| 系数的合法带 | 只接受板级 ROM 系数的 **×[0.5, 2.0]** | 超带说明前端错了,应该去查硬件而不是用系数凑 |
| 变比 `r` 存哪 | **新开一段 `[adc_cal_sec]`,int32 Q16 比值**,不并进 `k` | 并进 `k` 直接溢出 int32(UA 端子域 k 的 Q16 = 1.0e10 > 2.147e9);且变比是硬件事实、`k` 是前端斜率,混一起就是"用系数凑硬件" |
| `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` 是板级事实、现场不该改也改不动;真正未知的是"端子 ↔ 引脚"这一层 |
| 整帧量纲标签 `eng` 的判据 | **每路要么已配变比、要么削顶不显示** | 削顶通道页面本来就显 `--`、不进读数;拿它们卡标签会让 V0.1 五路削顶把徽标永久钉在"未标定" |
| 一次变比(PT/CT 一次额定)存哪 | **复用 `prot_set` 已有四项**,不新开段 | 已经在册(`PS_PT_PRI` / `PS_PT_SEC` / `PS_CT_PRI` / `PS_CT_SEC`),再开一段就是同一事实两个真源 |
| 遥测快照的字段布局 | 由协议侧定、单测按偏移钉死(72 B × 双区) | 布局漂移只能靠"读侧按名字取"掩盖,抓不到真正的错位 |
| 非交流量(TEMP) | **不参与线性标定**,`qds` 恒置 `NT`;也不进录波 | 温度是片内 12 位单极性原始值、非线性;进录波只多占 640 字节,还会让回放页多一格被误读成电气量 |
| 快照的并发读 | **双区 + 指针原子交换**,读侧无锁 | 加锁会把读侧拖进临界区,而快照是 200 ms 一次的发布、读侧是 104 与 UI 两个消费者 |
@@ -780,7 +829,7 @@ typedef struct {
| 待定 | 备选 | 为什么必须先定 |
|---|---|---|
| **精度目标** | 0.5 级 / 1 级 | 决定两点标定的判据,否则"误差 ≤ 目标精度"这条无法判定。保护判据本身只需 3~5 %,0.5 级是计量口径 |
| **端子 / 子板变比从哪来** | ① 有原理图(直接算)② 只有实物(万用表同时量端子与引脚)③ 都没有 | 决定 `eng` 能否置 1。现状已按 ① 的参数算定(`docs/硬件平台/ADC通道映射说明.md` §7.2),等实物复核;② 同样可行且够用;③ 则只交付引脚电压、二次值登记为缺口 |
| **端子 / 子板变比从哪来** | ① 有原理图(直接算)② 只有实物(万用表同时量端子与引脚)③ 都没有 | 决定变比登记的**可信度**(注意:量纲已不依赖它——出厂默认变比已按台架实测反算写进 `[adc_cal_sec]`,`eng` 能升 1;缺的是精度)。2026-11-11 用继保仪单点注入反算得 1000.0 / 29.4118,推翻了原先按子板参数算定的 606.06(详见 `docs/硬件平台/ADC通道映射说明.md` §7.2);② 两点标定是拿到精度的正规路径;③ 则二次值只有单点粗测精度(电压族真值区间约 909~1111) |
| **`eng = 0` 期间测频幅值门怎么处置** | 保持恒失效(现状,UI 恒 `--`)/ 台架临时把 `FREQ_AMP_MIN` 降到引脚域量级跑一轮再还原 / 换成相对量门限 | 建议**常态保持恒失效 + 需要时临时降门跑一轮**;换相对量门限要先重新论证,别在过渡期改。临时降门会改产品头文件,收尾必须还原到 `git diff` 为空并重跑构建 |
**送检顺序不可倒过来**:产品构建当前要求 UA/UN/IA 的标定 `VALID` 全部置位才走保护判据。