Files
STM32F4-Base/docs/测试说明/专项验证/ADC前端注入验证流程.md
T
wandering1 4a4ad6bc5f chore(全仓): @build/@test 旧文档别名清退为技能调用名
现状口径:/keil-build 与 /serial-boot-test 是 DSH 会话的技能调用名,
不是 shell 命令;「名 + 命令行参数」的命令形态改为可执行体
run_boot_test.py 的真实调用。

- AGENTS.md:别名说明重写为「技能调用名不是 shell 命令」,两条目改
  「编译(/keil-build)」「串口回归(/serial-boot-test)」
- keil-build / serial-boot-test 两 skill 调用名与示例同步;task-worktree
  方案文档去掉 @build/@test 惯例从句;@task 保留(不在点名范围)
- 123 文件现状陈述全改;台架捕获、变更记录/时间线、rename 映射输入
  等 41 行历史证据保留旧名
- gen_memory_svg.py 6 处措辞随改,3 张 SVG 产物同批重跑
- 顺手补并行方案同行漏网计数 51→52 钩子

验证:提交期快钩子对 123 文件实跑全 Passed;check_doc_links --all
rc=0(3038 处、无法判定 0);check_mermaid --all rc=0;YAML 解析 52
钩子不变;skill-size rc=0;py_compile 全过;字节卫生零漂移。
2026-10-30 13:03:12 +08:00

701 lines
61 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADC 前端注入验证流程(台架)
> **这篇是什么**:已知电压注入验证的**执行侧**——判据与实测记录在 `docs/硬件平台/ADC通道映射说明.md` §8.1,
> 这边写怎么接表笔、读哪一行日志、填哪张表、结果往哪回写。
>
> **本文分两个轮次**:§1~§7 = **直流轮次**(零点 / 线性 / 通道映射 / 极性,2026-10-27 六项 PASS,
> 证的是"一条水平线的高度";**不需要**示波器、信号源、标准源,**只需要**一块万用表 + 一个电位器 + 串口日志)。
> §8 = **交流(正弦)动态验证轮次**(六轮:幅值 / 波形 / 相位 / 测频 / 削顶 / 谐波,证的是"给定电压算得对不对、品质报得实不实、节拍稳不稳")——
> 一台信号发生器接在本文 §3 的同一批注入点上就能跑,**同样不用等标准源**;
> 等标准源的只是**精度等级**那一步(`docs/任务规划/数据可信.md` §5 的「两点标定与精度验收」)。
> V0.1 有五路单极性削顶(`UB/UC/IB/IC/IN`),它们在交流轮次里只能作"预期削顶"的负向用例。
>
> **判据的属主是 `docs/任务规划/数据可信.md` §4.6.2**(六轮各验什么、过判据是多少),本文 §8 只写**怎么跑**。
> 两边于 2026-10-29 对齐;若将来再分叉,分两类裁定:**「验什么、算不算过」从
> `docs/任务规划/数据可信.md` §4.6.2**,**「期望值怎么算、怎么接线怎么跑」从本文 §8**。
> 这一跳为什么不用等标准源、又证不到什么,见其 §4.6.1 与 §4.6.5,本文不重抄。
---
## 1. 为什么非做不可
下游已经全部按"工程量"消费:保护判据、104 遥测、UI 交流量页读的都是 `adc_cal_apply()` 的输出。系数错
一倍,三处一起错,且**没有任何一处会报错**。"通路活"(9 通道转换成功、零超时、互异非饱和、跨次开机
读数不同)不证明读数对。所以这一步的性质是**给整条链路的量纲盖章**,不是"补充测试"。
## 2. 前置事实:这个前端不是分压器
2026-10-27 按 `docs/硬件平台/V0.1.tel` 逐通道追线,以 UA 为例(图由
`tools/arch_diagram/gen_adc_frontend_svg.py` 生成,改图请改脚本重跑,勿手改 SVG):
![UA 通道前端:端子 → R31 → ADC7 → TPAFE AIN_7P;C44 / R32 在差分负端,不在信号通路上](../../images/ADC前端电路.svg)
`ADC7` 这个节点上**只有** R31、C44、U3.61 三样东西,**没有对地电阻**。所谓"1k 到地"是 R32,
它接在 TPAFE 的**负输入脚 AIN_7GND**(芯片手册 pin62)上,是把差分负端经 1k 拴到 AGND,
属于差分滤波,不是信号分压。STM32 侧更直接(图 B):`AIN2 —[R52 1k]— ADC17 —[C111]— AGND`,
同样没有分流电阻,连 R32 那个接地电阻都没有。
**由此得到两条可直接用的结论**:
1. **不存在 2:1 分压**。旧口径"板级系数 305.18 / 1611.3 µV/码"(按 2:1 折算到 `AINn` 侧的条件值)
在本板上不成立;`docs/任务规划/数据可信.md` §2.1 已于 2026-10-27 改为引脚侧系数(152.59 / 805.66)。
所以已知电压注入验证只需**确认转换器 LSB 与逐通道映射**,不必再判别两种读法。
2. **端子侧斜率 = 引脚侧斜率**。所以在**端子侧**注入既安全(有 1k 串阻护着芯片)又能直接验 LSB,
不必去碰 LQFP 引脚。
期望斜率(由转换器满量程事实推出,与前端无关):
| 转换器 | 满量程 | LSB | **期望斜率** |
| ---------- | ----------------------- | ---------------- | -------------- |
| TPAFE5160 | ±5 V(10 V 跨度 / 65536 码) | 152.587890625 µV | **6553.6 码/V** |
| STM32 ADC1 | 0~3.3 V(3.3 V / 4096 码) | 805.6640625 µV | **1241.2 码/V** |
## 3. 注入点清单
串联电阻的两只焊盘就是两个注入层级:**1 脚 = 端子侧(靠 U5)**,**2 脚 = 引脚侧(靠转换器)**。
| 语义 | 串阻 | 端子侧节点 | 引脚侧节点 | 转换器脚 | 期望斜率 |
| -- | --- | --------------- | ------- | ----------- | ------------- |
| UA | R31 | `AIN1`(U5.A3) | `ADC7` | U3.61 | 6553.6 |
| UN | R28 | `AIN4`(U5.A4) | `ADC6` | U3.59 | 6553.6 |
| IA | R19 | `AIN8`(U5.B5) | `ADC3` | U3.53 | 6553.6 |
| UB | R52 | `AIN2`(U5.B7) | `ADC17` | U17.44(PC4) | 1241.2 |
| UC | R20 | `AIN3`(U5.C14) | `ADC12` | U17.37(PA3) | 1241.2 |
| IB | R49 | `AIN9`(U5.A8) | `ADC15` | U17.42(PA6) | 1241.2 |
| IC | R46 | `AIN10`(U5.C9) | `ADC10` | U17.35(PA1) | 1241.2 |
| IN | R48 | `AIN11`(U5.C10) | `ADC11` | U17.36(PA2) | 1241.2 |
| 温度 | R47 | `V_ZL2`(U5.C11) | `ADC14` | U17.41(PA5) | 不验(非线性,不参与标定) |
> 引脚侧没有串阻保护,注入前**必须外串 10 k 限流**。除非端子侧结果对不上、需要分离
> "转换器的错"和"前端的错",否则不要用引脚侧。
## 4. 读数出口(固件只有下面这些周期行,没有命令控制台)
> **样本出口已落地(代码侧,2026-10-29)**:故障录波的数据面已进仓(立项与卡片见 docs/任务规划/故障录波.md,格式与判据属主见 docs/子系统设计/故障录波设计说明.md §6.2 与 §6.3、口径变更见该文 §11)——每次触发一段,段文件直接给出 8 路 × 320 点原始码值,动态验证的判据因此**可以**从「比对下表这些派生标量」升级为「PC 独立重算 vs 板上读数」对表。下表仍是固件唯一的**周期行**出口(段文件不是周期行,只在触发时产生),两种出口对表用。
| 日志行 | 频率 | 内容 | 用途 |
| ----------------------------------------------------------- | ------ | ------------------------ | ------------------- |
| `adc: UA=.. UB=.. UC=.. UN=.. IA=.. IB=.. IC=.. IN=.. T=..` | 每 10 s | **9 路原始码值** | 本次验证的主读数 |
| `cal: ua_mv=.. un_mv=.. ia_mv=.. vl=.. cl=..` | 每 10 s | UA/UN/IA 折算后毫伏 + 标定/削顶位图 | 旁证:码值 → 电压是否自洽 |
| `first: UA=.. … T=..` | 每次开机一次 | 9 路原始码值 | 与 `adc:` 行互校(同序同格式) |
| `frames=.. idle=.. adc1_err=.. tgap=..` | 每 10 s | 采集健康计数 | 步骤 4 的节奏判据 |
| `phasor(hb) C0x-…: Re=.. Im=.. rms=..`(逐实例)、`freq(hb): f=.. valid=..` | 每 10 s | 基波相量分量与测频读数(×0.1 / ×0.01 定点) | **交流轮次的主读数**(直流轮次只作旁证) |
| `meas: seq=.. v=.. ua_mv=.. un_mv=.. ia_mv=.. f=.. fv=..` | 每 10 s | 200 ms 遥测快照轨(基波有效值 mV) | 交流轮次的幅值比对(1 mV 分辨率,比 phasor 行细) |
| **段文件** `/wave/WAVE00.BIN` 起共 100 个号轮转(FTP 根下可见,与 /log 平级) | 每次触发一段(手动 / 保护启动 / bench 定时) | 8 路 × 320 点原始码值 + 封存瞬间的标定与品质快照(布局见 docs/子系统设计/故障录波设计说明.md §6.2) | **样本出口**:交流轮次的主对表源——预触发 160 点 + 后触发 160 点,触发瞬间落在段内点 159 |
| PC 侧脚本 `test/host/wave_read.py`(不是固件日志,bench 工具,纯标准库) | 手动跑;`--selftest` 是它的自带回归 | 四个子命令:dump(逐字段与双 CRC)/ fft(全周傅氏重算基波有效值与相位)/ shape(削顶与形态判定,可 `--strict`)/ plot(出 SVG 或 PNG,PNG 用 `-o` 落文件) | 把段文件还原成周波数、幅值、相位、削顶判定与图 = 「PC 独立重算」这条判据的执行体;CRC 不符或零长度它必须**拒绝**而不是画出东西 |
> ⚠ 直流轮次用不到后两行,但**它们一直都在**。交流轮次(判据见 `docs/任务规划/数据可信.md` §4.6)
> 必须用 `meas:` 或 104 YC 比幅值,别用 `phasor(hb)` 行——那行是 ×0.1 定点,注 1 V 只剩 10 格。
`adc:` 行是 2026-10-27 新增的(`App/task/adc_task.c` 心跳块),此前只有 `cal:` 的三路,
验 8 个通道要复位二十余次。现在**全程不必复位**。
**「码」是什么**:ADC 输出的**原始整数**,中文常写「码值」/「AD 码」,英文对应 raw code / counts。
它表示"多少个最小刻度(LSB)",**本身不是物理量**——电压 = 码 × LSB,而两片转换器的 LSB 不同:
| 转换器 | 位数与极性 | 码的范围 | 1 码 = | 换算示例 |
|---|---|---|---|---|
| TPAFE5160 | 16 位双极性 | −32768 ~ +32767(共 65536 码 = 10 V 跨度) | 152.59 µV | 11430 码 × 152.59 µV ≈ **1.744 V** |
| STM32 ADC1 | 12 位单极性 | 0 ~ 4095(共 4096 码 = 3.3 V 跨度) | 805.66 µV | 1000 码 × 805.66 µV ≈ **0.806 V** |
同一个整数落在两把刻度尺上代表不同电压(图由 `tools/arch_diagram/gen_adc_code_scale_svg.py`
生成,改图请改脚本重跑,勿手改 SVG):
![两把刻度尺:TPAFE5160 16 位双极性 −32768~+32767、1 码 152.59 µV、11430 码≈1.744 V;STM32 ADC1 12 位单极性 0~4095、1 码 805.66 µV、1000 码≈0.806 V](../../images/ADC码值刻度.svg)
**两把尺子不能混用**:同样是"1000",在 TPAFE 上是 0.153 V,在 STM32 上是 0.806 V,差 5.3 倍。
所以 §5 各步的记录表里每一格都必须写明是哪一路(UA/UB/… 属 TPAFE,T 属 STM32),
抄错一行全盘结论作废。
本文一律用"码"而不是"伏",有两个理由:① 固件 `adc:` 行打印的就是码,抄数不用心算;
② **标定还没做,把码换成伏正是本次要验证的事**——先换算再用,等于默认了尚未验证的系数。
判据按转换器分两档:**TPAFE 侧 ≤ 30 码、STM32 侧 ≤ 5 码**——即短接后读数离 0 不超过
30 / 5 个刻度,折算 **≈4.6 mV / ≈4.0 mV**。两档数值差 6 倍是因为两种刻度差 5.3 倍
(TPAFE 152.59 µV/码、STM32 805.66 µV/码),统一到物理量后都在 4~5 mV 量级。
⚠ **TPAFE 档于 2026-10-27 由 ≤10 放宽到 ≤30**:首次实测三路残差 UA −14 / UN −15 / IA −13
(≈2 mV),按 16 位 /10 V 跨度算只有 0.02% FS。原来的 ≤10(≈1.5 mV)定判据时**没有实测依据**,
比实测噪声还紧,必然误判失败。**这不是"把判据改成能过"**——若残差继续增大(>30 码 / ≈4.6 mV),
说明前端确有真实失调(R32 侧接地断、TPAFE 失调),那时必须回前端查,不得再放宽。
⚠ `adc:` 行某路取数失败时该槽打 0,所以"9 路全 0"要读作"没数据",判据是同一份心跳里
`frames` 是否还在涨。
## 5. 步骤
### 5.1 步骤 0:断电,用万用表电阻档核对(5 分钟)
断电,万用表电阻档量两处,**期望值不一样**:
| 量哪里 | 期望 | 验的是什么 |
|---|---|---|
| **串阻两端**(1 脚 ↔ 2 脚,即端子侧焊盘 ↔ 引脚侧焊盘) | **≈ 1 kΩ** | 串阻焊好、没虚焊 |
| **注入点 ↔ AGND**(TPAFE 侧,UA/UN/IA) | **≈ 1 MΩ 量级** | 无意外短路;**不是 1 kΩ** |
| **注入点 ↔ AGND**(STM32 侧,UB/UC/IB/IC/IN) | 高阻,不定量 | 无设计直流通路,只作短路排查 |
### 5.2 步骤 1:零点(这一步是整套测试的分水岭)
**做什么**(8 路逐个重复,每路约 1 分钟):
1. 断电,把该路**注入点短接到 AGND**(短接线之前先用 0 Ω 档确认该点与 AGND 直通);
2. 上电,等 `adc:` 行出现(每 10 s 一行,9 路码值都在里面);
3. 把该路码值抄进**下面的零点记录表**;
4. 断电拆短接线,换下一路。
**记录**(现场填;判据只看"短接后"那一列,"空载"列是留证用):
> ⚠ **本表行序 = `adc:` 日志的列序**(UA UB UC UN IA IB IC IN),照着日志**从左往右**抄,
> 不要按"TPAFE 三路 / STM32 五路"重新分组。2026-10-27 现场实测就栽在这里:按语义分组抄,
> 结果 UN/IA 填成了 STM32 的 ~1000、UB/UC 填成了 TPAFE 的 ~11450——而 UN/IA 属 TPAFE、
> UB/UC 属 STM32,这两个量级在物理上不可能。**"转换器"列就是给这一眼准备的**。
| 通道 | 转换器 | 端子侧节点 | 空载码值(浮空) | 短接 AGND 后码值 | 判据 |
| --- | --- | --- | --- | --- | --- |
| UA | TPAFE | `AIN1`(U5.A3) | 11428~11429 | 1~3 | ≤ 30 |
| UB | STM32 | `AIN2`(U5.B7) | 993~1018 | 0~1 | ≤ 5 |
| UC | STM32 | `AIN3`(U5.C14) | 975~1033 | 0 | ≤ 5 |
| UN | TPAFE | `AIN4`(U5.A4) | 11452~11455 | 2~4 | ≤ 30 |
| IA | TPAFE | `AIN8`(U5.B5) | 11449~11451 | 2~3 | ≤ 30 |
| IB | STM32 | `AIN9`(U5.A8) | 937~960 |2~5 | ≤ 5 |
| IC | STM32 | `AIN10`(U5.C9) | 991~1092 | 0 | ≤ 5 |
| IN | STM32 | `AIN11`(U5.C10) | 979~1065 |1~2 | ≤ 5 |
**为什么这一步最关键**:空载(输入浮空)读数比判据高两个数量级,最可能的解释是输入悬空后
漏电流在滤波电容上建立的平衡点——短接 AGND 后应塌回 0。但如果**短接后仍读 11430**,那就是
真实偏置,且超过零点补偿上限 `ADC_CAL_B_MAX`(8192 码)——`adc_cal_set()` 会直接拒掉、
回退 ROM 系数、永远置不了 `VALID`,保护也就永远解不了锁。**那种情况必须回前端查**
(R32 那侧接地是否断、TPAFE 是否失调),不能靠软件补偿。
**2026-10-27 实测结论:8 路全部 PASS。** 短接后落在 **0~5 码**(TPAFE ≤4.6 mV、STM32 ≤4.0 mV),
全部远小于零点补偿上限 `ADC_CAL_B_MAX`(8192 码),**可以正常标定、不会触发回退**。
空载 → 短接的塌陷幅度:TPAFE 约 11430 → 0~4(三个数量级),STM32 约 1000 → 0~5(两个数量级),
两者都证明短接真实到位、输入前端没有意外直流通路。
⚠ **IB 最贴边**:2~5 码,上限 5 码正好触到。折算 1.6~4.0 mV,物理上没问题,但**它是八路里
余量最小的一路**;若日后 IB 零点继续变大,第一个该查的是 R49 那侧(`AIN9` / U5.A8)。
### 5.3 步骤 2:三点线性(用 UA 做,免复位)
3.3 V 轨经 10 k 电位器分压作源,逐点注入。**每点同时记两个数**:万用表读注入点实际电压、
`adc:` 行读对应码值。
**记录**(UA 一路,三点;"目标"列是调电位器的靶子,"实测"列填万用表读数):
| 目标注入 | 实测电压(万用表) | 码值 | 反算 µV/码 | TPAFE 期望码 | STM32 期望码 |
| -------- | --------- | -- | ------- | --------- | --------- |
| ≈ 0.3 V | 0.300 | 1954 | 153.53 | 1966 | 372 |
| ≈ 1.0 V | 1.001 | 6547 | 152.89 | 6554 | 1241 |
| ≈ 2.0 V | 2.004 | 13106 | 152.91 | 13107 | 2482 |
拟合 R² = 0.9999997 斜率 = 6544.2 码/V 偏差 = −0.14 % 截距 = −7.2 码(≈ −1.1 mV)
反算 LSB = 152.81 µV/码,与 TPAFE 期望 152.59 差 0.14 %——**与斜率偏差同源,是同一个数**。
期望码两列并列,是因为同样的三个注入点也可能拿去复验 STM32 侧某一路(UA 对不上时换路交叉验证);
两列数值差 5.3 倍,一眼能确认手上这支表笔当前接的是哪一片转换器。
判据:三点线性拟合 **R² ≥ 0.999**,斜率与期望值偏差 **≤ 2 %**。
偏差 > 2 % 时**先查前端**(焊点、串阻实际值、电位器负载效应),不要用系数去凑——
把硬件的错固化成"标定值"是最坏的结果。
**2026-10-27 实测结论:PASS。** R² = 0.9999997(判据 ≥0.999)、斜率偏差 −0.14 %(判据 ≤2 %)。
三条残差 −2.0 / +3.5 / −1.4 码,落在读数噪声内,**无系统性弯曲**——即前端在这三个点上确实是
线性的,不存在未识别的分压元件。这是"③ 段变比 = 1:1"在台架上的第一次直接证据。
### 5.4 步骤 3:极性(只 TPAFE 三路)
TPAFE 是双极性、二进制补码,可以注负压;STM32 是单极性,负半周测不了。给 UA/UN/IA 各加一个
**−1.0 V** 点。
**记录**(三路各两个点):
| 通道 | +1.0 V 码值 | −1.0 V 码值 | 对称性偏差 |
| -- | --------- | --------- | ----- |
| UA | 6544 | −6566 | 0.34 % |
| UN | 6549 | −6568 | 0.29 % |
| IA | 6548 | −6566 | 0.27 % |
判据:−1.0 V 点码值为**负**且落在 **−6554** 附近,幅值与 +1.0 V 点对称(差值 ≤ 2 %)
→ 符号位与补码解释正确。这一步专门补"连极性都没证过"这个缺口。
**2026-10-27 实测结论:PASS。** 三路 −1.0 V 点全部为负,落在 −6566~−6568(相对 −6554 偏
−0.18 %~−0.21 %);对称性偏差 0.27 %~0.34 %,远小于 2 % 判据。**补码符号位解释正确**。
### 5.5 步骤 4:节奏(注入期间顺带看)
注入期间 `frames=` 应仍**严格 1000 Hz 递增**(10 s 间隔 +10000 左右),`idle=0`,
`adc1_err=0`。这一条替代了原方案里"用示波器看 U3.14 BUSY 有脉冲"的判据——不需要示波器。
**2026-10-27 实测结论:PASS。** 注入**前**(20:58 那次开机)与注入**后**(21:29)各取两行相隔
10 s 的心跳,两边都是 **Δ9998 码 / ≈10.0 s ≈ 1000.1 Hz**,`idle=0`、`adc1_rc=0`、`adc1_err=0`:
| 时点 | `frames=` 两行 | Δ | 折算 |
|---|---|---|---|
| 注入前(20:58) | 29997 → 39995 | 9998 | 1000.5 Hz |
| 注入后(21:29) | 1819639 → 1829637 | 9998 | 1000.1 Hz |
⚠ **口径说明**:这两次是"注入**前后**",不是"注入**进行中**"——已知电压注入验证判据 ⑤ 要的正是"注入前后
`tpafe5160_frame_seq()` 仍严格 1000 Hz 递增",故按此口径已达标。若日后要连"进行中"也钉住,
在任一注入过程里再抓两行即可(注入不改变 TIM3 硬件触发,理论上无影响,但没测过就不算证过)。
`adc1_gap` / `tgap` / `skip` 是**本次开机以来的累计值**(548 / 363 / 183),10 s 内只增 3 / 2 / 1,
占比约 0.03 %,属正常漏检量级,不是停摆。
### 5.6 步骤 5:回写
1. 把实测斜率、零点、映射结果写进 `docs/硬件平台/ADC通道映射说明.md` §8,把"尚未执行"改成
实测值 + 日期;
2. 若实测确认无分压,同步在 `docs/硬件平台/ADC通道映射说明.md` §7.1 的链路分段表把第 ③ 段
(`AINn` ↔ 转换器引脚)从"待台架实测"改为实测结论;
3. `docs/任务规划/数据可信.md` 的已知电压注入验证行按"完成即删"销项;判据与实测证据的属主是
`docs/硬件平台/ADC通道映射说明.md` §8.1。
## 6. 判据汇总
| 项 | 判据 | 2026-10-27 实测 | 结论 | 不通过意味着 |
| -- | ---------------------------- | -------------------------------- | ---- | --------------------------- |
| 零点 | TPAFE 码值绝对值 ≤ 30;STM32 码值绝对值 ≤ 5 | 8 路全部 **0~5 码**(≤4.0 mV) | PASS | 有真实偏置,需查前端;可能超出补偿上限 |
| 线性 | R² ≥ 0.999 | **0.9999997**(UA 三点) | PASS | 前端非线性或注入源带载失真 |
| 斜率 | 偏差 ≤ 2 % | **−0.14 %**(6544.2 vs 6553.6 码/V) | PASS | 前端有未识别元件,或转换器档位/参考错 |
| 映射 | 注入哪路只动哪路 | 8 路短接各自塌到**本路**(步骤 1 代偿;串扰未单独证) | PASS | 语义表与物理通道不符(`k_adc_map[]` 错) |
| 极性 | −1.0 V → −6554 码 | −6566 / −6568 / −6566,对称性 ≤0.34 % | PASS | 补码符号处理错 |
| 节奏 | 注入期间仍 1000 Hz | 注入前/后各 **Δ9998 / 10 s ≈ 1000 Hz**,`idle=0`、`adc1_err=0` | PASS | 注入改变了输入共模,触发转换异常 |
**总体结论(2026-10-27)**:**六项全部 PASS**。合起来证明了三件事:
① ③ 段(`AINn` ↔ 转换器引脚)**变比确为 1:1**,无隐藏分压(斜率偏差 −0.14 %);
② 8 路语义 ↔ 物理端子**一一对应**(零点步骤逐路代偿);
③ TPAFE 补码符号位解释正确(对称性 ≤0.34 %)。
已知电压注入验证的判据 ①②③④⑤⑥ 至此全部闭环(判据与实测记录 → `docs/硬件平台/ADC通道映射说明.md` §8.1)。
⚠ **两项残留口径**(不影响本轮 PASS,但别当成已验证):**串扰**未单独证(见上表"映射"行);
**节奏**只测了注入前后、没测注入进行中。两项若日后怀疑,各自的补做方法已写在对应小节。
## 7. 安全红线
1. **STM32 侧严禁超过 3.3 V**(VDDA + 0.3 V 即击穿),电位器上限就接 3.3 V 轨,不要接 5 V。
2. **TPAFE 侧按判据只到 ±2 V**,虽芯片容 ±5 V,但判据不需要更大。
3. **端子侧不许直接上额定电压**(220 V / 100 V 二次值)。本次是直流低压注入,
与"加压试保护"是两件事,后者要等端子/子板变比确认(`docs/硬件平台/ADC通道映射说明.md` §7.1 链路分段表 ④ 行)之后单独排。
4. **引脚侧注入必须外串 10 k**,且先接线后上电、先断电后拆线。
5. 注入源与板子**共地**;电位器一端接 3.3 V、一端接 AGND、中间抽头串 10 k 到注入点。
---
## 8. 交流(正弦)动态验证轮次(六轮:幅值 / 波形 / 相位 / 测频 / 削顶 / 谐波)
> **判据的属主是 `docs/任务规划/数据可信.md` §4.6.2**(六轮各验什么、过判据是多少),本节只写**怎么跑**:
> 接哪个点、发生器怎么设、读哪一行的哪个字段、期望值怎么算、结果往哪回写。两边于 2026-10-29 对齐;将来
> 若再分叉,按两类裁定——**「验什么、算不算过」从 `docs/任务规划/数据可信.md` §4.6.2,「期望值怎么算、
> 怎么接线怎么跑」从本节**。为什么这一跳不用等标准源、以及它证不到什么,看其 §4.6.1 与 §4.6.5,本节不重抄。
>
> **引用写法**:本节凡引用判据文档的小节,一律写成「文件名 + 小节号」的紧邻形式;只写 §8.x 的是本文自己的节。
先下图建立全局轮廓——每一轮注什么波形、验什么、读哪个出口,再进 §8.5 逐轮执行卡:
![六轮一图速查:注入信号形状 → 接法与设定 → 判据一句话 → 主读数出口;过判据属主在 数据可信.md §4.6.2](../../images/交流六轮速查.svg)
### 8.1 动手前先把出口分辨率算清楚(决定今天能判到哪一档)
这一轮和直流轮次**不一样:样本出口已经有了**。2026-10-29 故障录波的数据面并入主干,一次触发就是一段
`/wave/WAVE<NN>.BIN`(`NN` = 00~99 轮转号;8 路 × 320 点原始码值 + 封存瞬间的标定与品质快照),PC 侧 `test/host/wave_read.py` 把它还原成
周波数、幅值、**相位**、谐波与削顶判定。所以本轮每条用例都有两个档:**周期行标量档**(只读串口 / 104 / UI,
快,不用 FTP)与**段文件对表档**(多一次触发 + 一次取段 + 跑脚本,判得到位)。各出口的实际分辨率如下——
这张表决定每条用例走哪个档:
| 出口 | 打印处 | 分辨率 | 能判 | 判不动 |
|---|---|---|---|---|
| `meas: seq=.. v=.. ua_mv=.. un_mv=.. ia_mv=.. f=.. fv=..` | `App/task/adc_task.c:605` | **1 mV**(整数毫伏,只发 UA/UN/IA) | 幅值轮、波形轮的幅值 ≤2 % | 相位、谐波、其余五路 |
| 104 YC `0x4011`~`0x4018` + `0x401D`(短浮点 + 品质) | `App/protocol/iec104/iec104_pts.c:70` | 3 位有效数字 ≈ 0.01 V | 单个静态工作点的幅值交叉读数、品质 IV | **小步进**(每点 0.5 V / 0.005 A 变化死区)、相位、谐波 |
| `phasor(hb) C0x-…-F1: Re=.. Im=.. rms=.. (x0.1)` | `App/task/adc_task.c:209` | **0.1 V,且向下截断** | 有数没数、量级、象限 | ⚠ **相位轮的 ≤2° 与谐波轮的 ±2 %**:注 1.000 Vrms 时 `rms` 只有 10 格,20 % 的谐波只有 2 格 |
| `freq(hb): f=.. valid=.. (x0.01)` | `App/task/adc_task.c:224` | 0.01 Hz | 测频轮的读数与新鲜度 | — |
| UI 交流量页 `Ua/U0/Ia/Ia2/Ia5`(3 位小数)、`Hz`(2 位) | `Drivers/BSP/UI/ui_page_ac.c:47` | **1 mV**(人工目检 + 拍照留证) | 谐波轮谐波幅值、幅值轮交叉旁证、测频轮的 `Hz` 显 `--` | 相位(页面上没有角度) |
| 录波段文件 `/wave/WAVE<NN>.BIN`(`NN` = 00~99,100 个号轮转;FTP 根下,与 `/log` 平级)+ `test/host/wave_read.py`(`dump` / `fft` / `shape` / `plot`,纯标准库) | **已落地**(2026-10-29 并入 `v2-on-ui`;格式属主 `docs/子系统设计/故障录波设计说明.md` §6.2 与 §6.3) | 一次触发 8 路 × 320 点原始码值;`fft` 给基波 RMS、相位、2 次与 5 次谐波 RMS(mV) | **相位轮的 ≤2° 与谐波轮的 ±2 % 现在判得动**:PC 独立重算 vs 板上读数对表 | 段与发生器**无绝对相位锁定**(触发点在段内点 159)→ 只能比同段内相对相位;320 ms 快照不等于长时间稳定性 |
**所以每条用例按两个档摆**(出口从"缺样本"变成"有段文件"的经过与口径变更,见 `docs/任务规划/故障录波.md` §2 末
与 `docs/子系统设计/故障录波设计说明.md` §11):
| 档 | 条目 | 说明 |
|---|---|---|
| **周期行就判得动** | 幅值、波形、测频、削顶四轮(削顶轮的接法要按 §8.5 改,原因写在那张卡里) | 判据在 `meas:` / 104 / `freq(hb)` / `rail:` 上分辨率都够,不取段也能收工 |
| **段文件档才判到位** | 相位轮、谐波轮 | 段文件已落地 → 浮点重算,≤2° 与 ±2 % 都是**定量**判据;周期行退为旁证,只在没取到段时走定性档。触发动作与命令写在 §8.5 的相位卡 / 谐波卡 |
| **要改代码的退路** | 相位轮抬倍数、测频轮 B 档 | 段文件落地后相位轮这条基本不必走;测频轮 B 档(放宽 `FREQ_AMP_MIN`)仍是唯一路子。两者都要重编 + 烧录 + 按 §8.9 还原,**动手前先问用户** |
### 8.2 设备清单与开机前置门
| 项 | 要求 | 为什么 / 怎么核 |
|---|---|---|
| 信号发生器 | 正弦 / 方波 / 三角,**并有 DC 档**;频率可锁 50.000 Hz、也能设到 2 Hz;幅度 0~3 Vpp | **只要求"波形干净、幅度稳、频率锁得住"**,它的刻度不当基准(`docs/任务规划/数据可信.md` §7 #9)。DC 档是给削顶轮改法 A 用的 |
| ⚠ 发生器输出端接设置 | 接板子时设成**高阻负载**,不要按 50 Ω 端接 | 按 50 Ω 端接而实际接的是高阻板输入 → 面板值与真实电压差 2 倍;这是台架"差一倍"最常见来源 |
| 万用表 AC 档 | 能读到 mV;**记明型号与响应类型**(真 RMS / 均值响应) | 基准就是它;波形轮那一步两种表的读数都与基波期望差很多,不记响应类型就说不清是谁错 |
| 板子 | 产品构建(`test/board/test_config.h` 四个 `TEST_SUITE_*` 全注释)+ `/keil-build` 0 错误 0 警告 + 烧录 `-Rst -Run` 与回读校验 | → `stlink-flash` skill 与 AGENTS.md「构建与验证」 |
| `[adc_cal]` 状态 | **保持未标定**(`eng=0`),别为"读数好看"先写标定值 | 现在 `meas:` / `phasor` 的量纲是 **ADC 引脚电压 V**(`App/algo/meas.h` 文件头的 A-lite 口径);标定一生效就换成二次值,本节全部期望值作废 |
| 串口 | 全程抓 RX 落盘:用 `serial-assistant` skill(`open` → `clear` → 跑 → 读日志文件或 `log --tail`) | 日志文件就是证据本体,§8.10 归档归档的就是它;**长跑别用 `/serial-boot-test`**——它绑定一轮启动语义(静默 8 s、总超时 30 s、`max_boot_count` = 1,且 forbidden 含 `[ERR]`,而削顶轮故意造 `[ERR]`),约 135 min 的 session 必然超时或误判;daemon 全量写 `state/serial.log`(20 MB 上限自动截头保尾 10 MB)→ `docs/测试说明/流程与规范/自动化测试流程.md` 长时间采集一节 |
| 网口(可选) | 要读 104 那一路就插网线、PC 与板同网段(出厂 192.168.1.100),跑现成主站 `test/host/iec104_master.py`(`--ip` / `--port` / `--addr`) | 104 是浮点无量化 + 带品质,是 `meas:` 之外的第二条腿 |
| FTP 客户端 | 走现成 21 端口取段文件(出厂 `[ftp]` 账号,连续 5 次失败锁 60 s) | 段文件是相位 / 谐波轮定量档的样本出口;段文件名 `/wave/WAVE<NN>.BIN`(00~99 轮转),**取完立刻改名归档**,否则下一次触发把它盖掉 |
| 录波触发路径 | 产品构建里唯一**免改代码**的路径 = UI 菜单第 26 叶「手动录波」确认页(`Drivers/BSP/UI/ui_page_wave_manual.c` → `App/task/ui_task.c:605`) | 保护启动上升沿也算一次触发,但保护判据现处**未标定闭锁**态(AGENTS.md「现状」),别指望它自动来段;bench 定时自抓在双层编译门 `DTU_BENCH` 且 `WAVE_BENCH_AUTOCAP` 非 0 之内(默认 0,`App/task/adc_task.c:153`),开它就走 §8.9 的还原判据 |
| 共地 | 发生器 LO/GND 与板 AGND **单点**相连 | 不共地量到的是两地之差;现象见 §8.8 |
**开机前置门**——接发生器**之前**日志就该长这样,不满足先回 §5 步骤 5.5 查节奏,别往下做:
- `adc:` 行 10 s 一行,同一心跳里 `frames` 在涨("9 路全 0"要读成"没数据",§4 尾注);`idle=0`、`adc1_err=0`;
- `meas:` 的 `v=1`、`seq` 每 10 s 约 +500(200 ms 一帧;不涨 = 发布闸门停);
- `cal:` 的 `cl=0xE6` —— **这是来源派生**(V0.1 那五路本就触底,见 `docs/任务规划/数据可信.md` §2.6),既不是本轮战果也不是本轮故障;
- 四个位图 `vl` / `cl` / `rail` / `edge` 同一个位序(语义通道枚举 `App/algo/adc_input.h:38`):bit0 `UA`、bit1 `UB`、
bit2 `UC`、bit3 `UN`、bit4 `IA`、bit5 `IB`、bit6 `IC`、bit7 `IN`、bit8 `TEMP` → `0xE6` = bit1/2/5/6/7 = `UB/UC/IB/IC/IN`;
- **先上电、在基线里看到 `adc: UB` 有正常摆动,再开发生器输出**:贴轨锁存要求该通道"此前出现过中带样本"(释放沿前提,
`App/algo/adc_health.c` 的 `adc_health_rail_watch()`),开机就注大信号会让削顶轮的锁存**装不上**,测出假不通过。
- 开机各有一条 `first:` 与 `phasor(first)` / `freq(first)` 单发行(`App/task/adc_task.c:478` 与 `App/task/adc_task.c:201`
的 `tag` 参数),与 10 s 一轮的 `hb` 行是两拨——数心跳时别把 `first` 计进去;
- `phasor` 行**只要有数就打**:打印条件只有 `phasor_get() == 0`,不带任何 valid 位(`App/task/adc_task.c:209`)→
「phasor 有数」既不说明标定可用、也不说明这一路没在削顶,判读必须配 `vl` / `cl` / `rail` 三个位图一起看;
- 注入正弦**不会**点亮 `health:` 或 `sampling fault:` 那几行:`adc_health` 的位只吃序号差、错误计数增量与 tick 差
(`App/algo/adc_health.c:91-189`),与波形幅值无关;它们若亮是节拍 / 丢帧问题,别归因给发生器(§8.8 同一条)。
- `wave:` 行(`App/task/adc_task.c:782`)基线:`armed=0`、`err=0`、`ovf=0`、`drop=0`,`seg=` 是本开机以来的成段数(**可以是 0**)。
本轮每触发一段就核 `seg` +1 且那三个计数不涨;涨了先按 §8.8 的落盘行分诊,别接着往上下注入。
### 8.3 接线的四条(与直流轮次比,差别只在第 2、3 条)
1. **注入点与直流轮次完全相同**:§3 清单的**端子侧**焊盘(`U5` 排针,串阻的 1 脚侧)。③ 段 1:1 已由直流轮次实测
(斜率偏差 −0.14 %,§6),所以端子侧电压就是引脚电压,不用再算变比。引脚侧仍须外串 10 k(§7 第 4 条)。
2. **幅度按峰值核,别看面板上的 Vrms**:1.000 Vrms 正弦峰值已 1.414 V。
| 侧 | 判据上限(§7 红线 2) | 允许的最大正弦 Vrms | 本节用量 |
|---|---|---|---|
| TPAFE(`UA` / `UN` / `IA`) | ±2 V 峰值 | 1.414 Vrms | **1.000 Vrms**(峰值 1.414 V) |
| STM32(`UB` / `UC` / `IB` / `IC` / `IN`) | 0~3.3 V 单极性 | 无正向意义 | 只在削顶轮用:注交流 = **故意触底**,负半周本就钉 0 |
3. **一次只注一路**(相位轮除外,它要 `UA` 与 `UN` 两路同源),换表笔前先关输出。端子侧串阻是 **1 k**(R31 / R28 / R19 / R52 …),
STM32 那五路负半周经**片内**钳位 / ESD 路径灌向 VDDA——网表 `docs/硬件平台/V0.1.tel` :228-241 逐条实证这些节点只有
「电容 .2 + 串阻 .2 + MCU 脚」三件,**无外置钳位二极管、无隔直电容**,所以这条电流走的是芯片内部路径;
1.000 Vrms 时峰值电流约 (1.414 − 0.3) / 1 k ≈ **1.1 mA**(按片内钳位压降 0.3 V 估、仓内无实测,只用来论证量级),
但**别往上加幅度**——这种注入只允许出现在削顶轮这类"故意削顶"的用例里。
4. **电流通道也是注电压**:`IA` 的注入点同样是电压进 R19 端子侧。这一轮证的是"给定引脚电压算得对不对",
不是"一次侧 1 A 怎么办"——后者要变比,属 `docs/任务规划/数据可信.md` §5 的端子 / 子板变比确认。
四条接线画在一张图上(发生器 → 端子侧焊盘是一条主干,万用表并接、共地是两条必须存在的旁路):
```mermaid
flowchart LR
G["信号发生器<br/>输出端接 = 高阻<br/>(按 50 Ω 端接读数差 2 倍)"] -->|"注 UA / UN / IA:端子侧焊盘<br/>(1:1 已由直流轮次实测)"| R31["串阻 1 k 的 1 脚侧<br/>R31 / R28 / R19 …"]
R31 --> FE["前端分压 / 滤波"] --> U5["U5 排针 → 转换器引脚<br/>(TPAFE AIN_x 或 STM32 脚)"]
M["万用表 AC 档<br/>记响应类型"] -.->|"并接同一注入点<br/>量真实加到板上的电压"| R31
G -.->|"LO/GND 与板 AGND<br/>单点共地"| AGND["板 AGND"]
AGND -->|"不共地量到的是两地之差"| U5
G -.->|"注 UB 等 STM32 那五路(仅削顶轮)<br/>负半周经片内钳位 ≈1.1 mA"| R52["R52 端子侧"] --> STM["STM32 ADC 脚<br/>(无外置钳位 / 隔直电容)"]
```
### 8.4 记录表抬头(每条用例一份,与日志一起归档)
```text
日期 ____ / 操作人 ____ / 板子序列号 ____
固件 commit ________(/keil-build 0 错误 0 警告:是 / 否)
发生器:型号 ______ 输出端接 = 高阻 / 50 Ω 波形 ____ 频率设定 ____
万用表:型号 ______ AC 响应类型 = 真 RMS / 均值 共地方式 = 单点 / 发生器浮地
注入点:路名 ____(端子侧 U5 / 引脚侧外串 ____ Ω)
[adc_cal] = 未标定(eng=0)—— 必须是这个状态
基线(未注时):meas: ua_mv=____ un_mv=____ ia_mv=____ rail=0x____ cl=0x____
段文件(走定量档的用例):文件名 ________ 触发方式 = 手动 / 保护 / bench 取回时刻 ________
脚本:test/host/wave_read.py(先跑 --selftest:通过 / 否) fft / shape / dump 输出与 plot 图归档在 ________
```
### 8.5 逐条执行卡
**每条卡的通用动作**:接好 → 开输出 → 等 ≥30 s(相量窗 + `meas` 200 ms 轨稳定)→ 取**连续 ≥10 行**心跳 →
先确认 `frames` 还在涨、`adc1_err=0`,再核数 → 关输出 → 填表。"连续 ≥10 行"不是保守:`meas:` 是
**瞬时基波有效值透传**、200 ms 是上送节拍不是平均窗(`docs/任务规划/数据可信.md` §4.5),
单行读数带拍频式跳动,必须看一片取**中间水平(中位数)**才能定数。
通用动作主干与两处分岔(采集链没清 → 回头修;偏差没进 → 先复核查再判):
```mermaid
flowchart TD
W["接好本路,开机前置门已过(§8.2)"] --> ON["开发生器输出"]
ON --> ST["等 ≥30 s:相量窗与 meas 轨稳定"]
ST --> HB["取连续 ≥10 行心跳"]
HB --> Q1{"frames 在涨、adc1_err = 0 ?"}
Q1 -->|"否:是节拍 / 丢帧问题"| TRI["先查 §8.8 分诊<br/>别归因给波形"]
TRI -.->|"修复后重来"| ST
Q1 -->|是| MED["10 行读数排序<br/>取第 5、6 个的平均 = 中位数"]
MED --> Q2{"与期望的偏差在判据内?"}
Q2 -->|是| OK["记通过"]
Q2 -->|"否"| CHK["先复核查:发生器端接、<br/>万用表响应类型、共地"]
CHK -->|"复查仍超"| BAD["记不通过,进 §8.8 分诊"]
CHK -->|"查出问题改完重测"| ST
OK --> OFF["关输出,填记录表"]
BAD --> OFF
OFF --> NEXT["下一条 / 下一轮"]
```
为什么这一步是"中位数"而不是单行或平均——机理与操作:
![为什么连取 10 行取中位数:窗滑动让读数拍频式上下跳(左);中位数贴着密集簇,平均被毛刺拖走(右)](../../images/交流定数取中位数.svg)
#### 幅值轮:基波幅值(主用例,先做)
| 项 | 内容 |
|---|---|
| 接法 | 发生器 → `UA`(R31 / `AIN1` / U5.A3)端子侧;万用表 AC 档**并同一注入点**——量真实加到板上的电压,不是面板值 |
| 设定 | 正弦、50.000 Hz、**1.000 Vrms**(峰值 1.414 V) |
| 主读数 | `meas: ua_mv=`(1 mV) |
| 期望与判据 | `ua_mv` ≈ 万用表读数 × 1000,偏差 **≤2 %**(与静态同档) |
| 交叉读数 | 104 `0x4011`(短浮点)应 ≈ 万用表读数,单位是**引脚电压域**(`eng=0`,不是二次值) |
| 坑 ① | **别用 `phasor(hb)` 比幅值**——×0.1 定点,1 V 只剩 10 格(§4 尾注同一条) |
| 坑 ② | 104 每点 0.5 V(电流 0.005 A)变化死区,生效值在 `App/algo/run_par.c` 的 `[run_par]` 副本:1.000 → 1.100 V **不会**上报变化,那是死区不是坏了;连续扫幅度只看 `meas:` |
| 坑 ③ | **`cal: ua_mv` 与 `meas: ua_mv` 同名不同物**:前者是当拍瞬时码折算(`App/task/adc_task.c:587`),后者是 200 ms 快照里那一周波的基波有效值(`App/task/adc_task.c:605`),50 Hz 下二者可以差很多——`App/task/adc_task.c:595-599` 的原注就是**勿互为判据**,幅值轮只认 `meas:` |
记录表(记录表每一行 = 连续 10 行心跳 `ua_mv` 的**中位数**:排序后取第 5、6 个的平均;
不取单行、不取峰值——单行带拍频跳动,毛刺行会拖偏算术平均。表里"序号"是一次重测):
| 序号 | 万用表 AC (V) | `meas: ua_mv` | 期望 = 万用表 × 1000 | 偏差 % | 结论 |
|---|---|---|---|---|---|
| 1 | | | | | |
| 2 | | | | | |
| 3 | | | | | |
**加做(3 min,非判据)**:0.500 / 0.200 Vrms 两点看是否**等比**——不是验精度,是看有没有隐藏的触底或死区。
0.2 Vrms 那点 104 必然被死区吞掉,只读 `meas:`。
#### 波形轮:读数是否只跟基波走(同峰值改波形)
按**峰值对齐**(不是按 Vrms 对齐):三者峰值都取 1.414 V,发生器面板 Vrms 分别约 正弦 1.000 V、方波 1.414 V、
三角 0.816 V。期望的**基波有效值**(全周傅氏基波口径:`App/algo/phasor.c:3` 的系数已预乘 √2/N、`fAmSQ` 直接就是
有效值;下表的 20 点整周期 DFT 值就是按固件同一个窗算的):
| 波形 | 基波有效值 / 峰值 | 峰值 1.414 V 时的期望 `ua_mv` | 真有效值 / 峰值(对照) |
|---|---|---|---|
| 正弦 | 0.707 | 1000 | 0.707 |
| 方波 | 0.904(闭式 4/π/√2 = 0.900) | ≈1278 | 1.000 |
| 三角 | 0.578(闭式 8/π²/√2 = 0.573) | ≈817 | 0.583 |
- 判据:读数 = **基波**期望 ±2 %,**不是**真有效值。方波那栏最直观——真 RMS 表会读 ≈1414 mV、
均值响应表读 ≈1570 mV,而板上读 ≈1278 mV,**这正是"这台仪表读的是基波"的证据,不是 bug**;
所以抬头里必须写清万用表响应类型(判据文档同一条:拿真有效值表去对会以为差了 10 %)。
- 系数的来路:方波基波幅值 = 4/π × 峰值、三角 = 8/π² × 峰值,再除 √2 得基波有效值 → 闭式 **0.900 / 0.573**,
20 点整周期 DFT 实算 **0.904 / 0.578**;`docs/任务规划/数据可信.md` §4.6.2 的"波形轮"行与本节同值,算术以本节为准。
记录表:
| 波形 | 峰值设定 (V) | 面板 Vrms | 万用表 AC 实测 | `ua_mv` | 期望 | 偏差 % | 结论 |
|---|---|---|---|---|---|---|---|
| 方波 | 1.414 | | | | ≈1278 | | |
| 三角 | 1.414 | | | | ≈817 | | |
#### 相位轮:相位(只许比同片内)
| 项 | 内容 |
|---|---|
| 接法 | `UA`(R31)与 `UN`(R28)**都走 TPAFE**,同幅 1.000 Vrms、同相——发生器单路输出分两路即天然同相;双通道机型则两通道都设 0° |
| 读数 | `phasor(hb) C02-UA-F1: Re=.. Im=..` 与 `C03-UN-F1: Re=.. Im=..`,各取 `atan2(Im, Re)` 再比差值 |
| 判据(§4.6.2) | **定量档**:同相时同段内两路相位差 ≤2°,把发生器(或 B 通道)改 90° 后差值跟到 ≈90°;**定性档只判跟随**(差值落进 80°~100°,容差写进记录表) |
| ⚠ 分辨率 | 出口只有 ×0.1 且向下截断:1.000 Vrms 时 Re/Im 各只有 ±10 格,**一格就是 0.1 V** → 角度量化误差最坏 ≈ atan(0.1 / 0.707) ≈ 8°,**≤2° 今天用串口判不出来** |
| 三个档 | ① **段文件定量档(首选,零改代码)**:同一次注入触发一段,`test/host/wave_read.py fft` 重算基波相位,比**同段内** `UA`(表序 0)与 `UN`(表序 3)的相位差——浮点重算,不受 ×0.1 量化限制,≤2° 判得动,动作见下面第 2 段;② **定性档**:没取到段时退而用,同相 ≈0°、改 90° 后差值落进 80°~100° 即算跟随,容差写进记录表;③ **抬倍数退路**:临时把 `log_phasors()` 的倍数改成 ×1000 并改掉行尾 `(x0.1)`,跑完按 §8.9 还原——段文件档已落地,这条基本不必走,要走仍**先问用户** |
| 红线 | **只许比同片内**:跨转换器无同刻性(V0.1 硬件缺陷)+ STM32 那五路另有 1 拍乒乓滞后 → `docs/硬件平台/ADC通道映射说明.md` §7 |
记录表(定性档;定量档记脚本输出,归档口径见 §8.10 第 1 条):
| 发生器相位设定 | C02-UA `Re` / `Im` | C03-UN `Re` / `Im` | 两路 `atan2` 之差 | 结论 |
|---|---|---|---|---|
| 0° / 0° | | | | |
| 0° / 90° | | | | |
**段文件定量档的具体动作(相位轮首选,零改代码)**
1. `UA`(R31)与 `UN`(R28)两路接同一发生器输出(同相),注 **1.000 Vrms / 50.000 Hz**;
2. 稳定 ≥30 s 后,在 UI「手动录波」确认页触发一次;成段有**秒级**延迟(落盘在 defaultTask,卷空闲后一次心跳搬完整段)
→ 等串口 `wave:` 行的 `seg=` +1、`armed` 回 0 再去取文件;
3. FTP 取 `/wave/WAVE<NN>.BIN`(本轮那一段的确切号),**取完立刻改名**——100 个号轮转会被覆盖;
4. 主机侧跑:`& '.venv\Scripts\python.exe' test/host/wave_read.py fft WAVE<NN>.BIN --all`
—— 每个整周波给一行:基波 RMS、相位、2 次与 5 次谐波 RMS(单位 mV,系数镜像 `App/algo/phasor.c`);
5. 判据:`UA`(`--ch 0`)与 `UN`(`--ch 3`)在**同一周波**内的相位差——同相注入应 ≈0°,把发生器(或 B 通道)改 90° 后应 ≈90°,
容差 ±2°(`docs/任务规划/数据可信.md` §4.6.2"相位轮"行);
6. ⚠ 段的**绝对**相位与发生器没有锁定关系(触发瞬间固定落在段内点 159),所以**只能比同段内两路的相对差**;
跨段比相位、或拿发生器相位当绝对基准都不成立(段内点序见 `docs/子系统设计/故障录波设计说明.md` §6.2);
7. 交叉旁证:同一段先 `dump`(逐字段 + 双 CRC16,看封存瞬间的标定与品质快照),再 `plot --ch 0`(CRC 不符或零长度它
**必须拒收**而不是画出半张图);板上 `phasor(hb)` 的 `Re` / `Im` 象限应与脚本给的象限一致(×0.1 只够核象限)。
#### 测频轮(默认 A 档 = 判"如实报不可测")
| 项 | 内容 |
|---|---|
| 接法与设定 | 同幅值轮(`UA`,1.000 Vrms / 50.000 Hz) |
| 读数 | `freq(hb): f=.. valid=..`;UI 交流量页 `Hz`;104 `0x401D` |
| 期望(A 档 = 现状) | **就是不出数**:幅值门 `FREQ_AMP_MIN 50.0f`(`App/algo/freq.h:62`,`App/algo/freq.c:231` 拿它与基波有效值比)在引脚电压域恒不过 → `valid=0`、`f=0`、UI `Hz` 显 `--`、104 `0x401D` 上送 IV |
| 判据 | **如实报不可测**,而不是报一个看起来对的 50.00。若门限没改却报出 50.00 → 反而是 bug,停下查 `App/algo/freq.c` |
| B 档(要用户先点头) | 临时把 `FREQ_AMP_MIN` 由 `50.0f` 降到 `0.5f` → 重编(`/keil-build`,要 build 锁)+ 重烧 → 扫 45.000 / 50.000 / 55.000 Hz 三点,PC 侧判据 0.02 Hz → 跑完**必须还原**,收尾判据见 §8.9 |
| 坑 | 非整 50 Hz 时 `meas:` 会拍频式跳动,属预期 → **扫频读 `freq(hb)`,别读 `meas:`**(`docs/任务规划/数据可信.md` §8 风险表同一条);测频的取源顺序是先 `UA`(相量实例 C02)再退 `IA`(C01)(`App/algo/freq.c:47`)——本卡注 `UA` 走的就是 UA 源;若哪天只注 `IA`,测频会跟着 IA 走,别以为它锁在 UA |
记录表(A 档):
| 频率设定 (Hz) | `freq(hb)` f | valid | UI `Hz` | 104 `0x401D` 品质 | 结论 |
|---|---|---|---|---|---|
| 50.000 | | | | | |
#### 削顶轮:触底与品质路由(负向用例)
- **判据**(`docs/任务规划/数据可信.md` §4.6.2"削顶轮"行):给 `UB`(R52 / `AIN2` / U5.B7)注 **DC −1.00 V 保持 ≥5 s**
或 **1.000 Vrms / 2.0 Hz** 的正弦 → 负半周钉下轨 → 连续 20 拍同侧后该路贴轨位锁存、`edge` 起沿、104 该路 IV 且值清零。
- ⚠ **注 50 Hz 同幅正弦造不出这个锁存,这是实现约束不是固件缺功能**:锁存条件是"连续 `ADC_RAIL_N_RUN = 20` 拍
贴同一侧轨带"(`App/algo/adc_health.h:174`),而 `adc_health_rail_watch()` 里**任何一拍回到中带就把两侧计数清零**;
1 kHz 下一个 50 Hz 周波只有 20 个点、正半周必然落在中带 → 连续贴底最长 ≈ 209°/360° × 20 ≈ **11 拍 < 20**。
- **轨带数值**(`App/algo/adc_cal.c:452` 的 `adc_cal_rail_ths()`,TEMP 不监视):STM32 侧贴底 = 码 ≤ **81**(2 % FS ≈ 65 mV)、
贴顶 = 码 ≥ 4013;TPAFE 侧 = ±32112 码(≈ ±4.9 V)→ **TPAFE 侧这一条在 ±2 V 判据上限内根本造不出来**,
只有 STM32 那五路能做。
- **改法 A(首选,零固件改动)**:发生器切 **DC 输出 −1.00 V** 注 `UB` 端子侧,保持 ≥5 s。期望:`cal:` 的 `rail=0x002`、
`edge=0x002`、一条 `[ERR] rail: edge=0x2 sticky=0x2 (code-domain, 20-run+release)`(`App/task/adc_task.c:428`)、
104 `0x4012` 该路 IV 且值清零、`adc: UB` 码值贴 0。撤掉注入后 **sticky 位不随输入恢复而清**(锁存语义,
只有 `adc_health_rail_init()` / 复位清)→ 复位一次并把"是否随复位清"记进表:这条实测行为是本轮的产出之一。
- **改法 B**:发生器 1.000 Vrms 但**频率设 2.0 Hz** → 负半周 250 ms = 250 拍贴底 ≥ 20 → 锁存;
顺带证"贴轨检测与工频无关"这条口径。
- **50 Hz 档照跑,判据就是"不锁存"**:记下 `adc: UB` 负半周钉 0 而 `rail` 仍 `0x000` —— 这一档证的是
"锁存要连续 20 拍同侧"这条实现约束本身,不是失败,也不是战果。
- **反向假阳性**:反过来注一个峰值 <65 mV(<46 mVrms)的 50 Hz 小信号,会让 `UB` 一路贴底而**锁存**——
那是"信号小到进不了中带",不是削顶。读 `rail` 时要把这条一起排除。
- **与 `cl` 区分**:`cl=0xE6` 是**来源派生**的静态触底掩码(`docs/任务规划/数据可信.md` §2.6),削顶轮要的是动态
`rail=` / `edge=`;别拿 `cl` 当本轮战果。
记录表:
| 步 | 设定 | `rail` | `edge` | `[ERR] rail:` 行数 | 104 `0x4012` | 复位后 `rail` | 结论 |
|---|---|---|---|---|---|---|---|
| 1 | 50.000 Hz / 1.000 Vrms 正弦 | | | | | 不适用 | |
| 2 | DC −1.00 V(改法 A) | | | | | | |
| 3 | 2.0 Hz / 1.000 Vrms(改法 B) | | | | | | |
**段文件档的两把尺子(现在能把"来源派生 vs 实测"一次核清)**
`shape` 拿**实测**贴轨判定与段头 `clip_mask` 对表;而段头那一位**来自通道来源、从不实测**(`test/host/wave_read.py`
文件头的实证与本卡上面的 `cl=0xE6` 同源,两处互证)→ 三种注入分别配三把尺子:
| 注入 | 命令 | 期望 |
|---|---|---|
| 改法 A(`UB` 注 DC −1.00 V 保持 ≥5 s) | `shape WAVE<NN>.BIN --strict` | 实测贴底落进 bit1;段头静态已含 bit1 → 等式成立。**实测冒出段头没有的位**才算失败 = 有别的路被推到轨上,先查接线 |
| 极小注入 / 未驱动通道 | 同上再加 `--clip-superset` | 换成"段头覆盖实测"这把尺子:容忍"封存了但本轮没贴轨";实测有、段头没有仍然失败 |
| 原接法(50 Hz 正弦注 `UB`) | `shape WAVE<NN>.BIN`(不带 `--strict`) | 只读逐通道贴轨游程:正弦过零必回中带,`--min-run` 默认 4 也可能判不出贴轨——**别把"没判"读成 bug** |
`--min-run`(连续几点算贴轨)与固件的"连续 20 拍"是**两把不同的尺子**,别互相换算;固件侧结论仍以 `rail:` / `edge=`
位图为准(本卡上半段)。
#### 谐波轮:谐波轨(**段文件 `fft` 定量档 + UI 目检旁证**)
| 项 | 内容 |
|---|---|
| 接法 | 发生器 → `IA`(R19 / `AIN8` / U5.B5)端子侧 |
| 设定 | 基波 + 20 % 二次 + 20 % 五次(发生器有谐波叠加 / ARB 模式就用它) |
| ⚠ 峰值预算 | 1.000 Vrms 基波 + 两个 20 % 分量的最坏对齐峰值 ≈ 1.414 + 0.283 + 0.283 = **1.98 V,已顶到 ±2 V 判据上限**。所以基波改 **0.800 Vrms**、二次与五次各 0.160 Vrms,最坏峰值 ≈ 1.58 V 留余量;期望值随之换算 |
| 主读数 | UI 交流量页 `Ia2` / `Ia5` 两行——**3 位小数 = 1 mV**(`Drivers/BSP/UI/ui_page_ac.c:47`),目检 + 拍照;远端可走 7003 看图截屏 |
| 期望与判据 | 期望 ≈ 各分量注入值(0.160 Vrms)±2 % |
| 旁证 | `phasor(hb) C04-IA-F2` / `C05-IA-F5` 的 `rms=`:×0.1 只有 1~2 格,**只能证明这根轨不是恒 0**,判不了 ±2 %(§8.1) |
| 没有谐波叠加功能时 | 分三次单注:只注 0.160 Vrms / 100 Hz → `Ia2` 有数、`Ia5` 应 ≈0;只注 0.160 Vrms / 250 Hz → `Ia5` 有数、`Ia2` 应 ≈0;再注纯基波 → 两行都 ≈0。这一档证的是"两根轨各认各的次",**不证叠加工况**,结论里要写明 |
| 口径 | 这两根轨**目前没有任何下游消费者**(`App/algo/prot_elm.c` 无引用、`meas` 只算基波)→ 谐波轮只证"算得对",不证"有人用";出口归属 `docs/任务规划/数据可信.md` §9 的谐波轨立项 |
记录表:
| 注入分量 | 发生器设定 | UI 读数 (V) | 期望 (V) | 偏差 % | `phasor` 旁证 | 结论 |
|---|---|---|---|---|---|---|
| 基波 0.800 Vrms + 二次 0.160 + 五次 0.160 | | `Ia2=` / `Ia5=` | 0.160 / 0.160 | | | |
**段文件定量档(谐波现在判得到位)**
同一次注入(基波 0.800 Vrms + 二次 0.160 + 五次 0.160,峰值预算见上表)触发一段、FTP 取回后跑:
```text
& '.venv\Scripts\python.exe' test/host/wave_read.py fft WAVE<NN>.BIN --ch 4 --all
```
`fft` 逐整周波给**基波 RMS + 2 次与 5 次谐波 RMS**(mV,浮点重算),±2 % 在这条档是定量判据;`--ch` 是段内通道表序,
`IA` = 4(表序 `UA UB UC UN IA IB IC IN`,TEMP 不录,`App/algo/wave_ring.h:216`)。UI 目检与 7003 截图降为**旁证**——
它证的是"板上 `Ia2` / `Ia5` 与脚本算的是同一件事"(差值应在显示位数之内),不是替代判据。THD 与方和根仍不在本轮,
口径见 `docs/任务规划/数据可信.md` §4.6.5。
### 8.6 顺序与耗时(一次台架 session 跑完)
| 序 | 做什么 | 预计 | 备注 |
|---|---|---|---|
| 0 | 前置门 + 基线 30 s | 10 min | §8.2,不满足就停 |
| 1 | 幅值轮(含 0.5 / 0.2 Vrms 加做) | 15 min | 主用例 |
| 2 | 波形轮(方波 / 三角,峰值对齐) | 15 min | 抬头里记万用表响应类型 |
| 3 | 谐波轮:段文件 `fft` 定量档 + UI 目检旁证(同一次注入触发一段) | 25 min | 命令见 §8.5 的谐波卡 |
| 4 | 相位轮:段文件相对相位档(`UA` / `UN` 同源,再移 90°) | 20 min | 定性档只在没取到段时退用 |
| 5 | 测频轮 A 档 | 5 min | 判"如实报不可测" |
| 6 | 削顶轮:50 Hz 档 + 改法 A + 改法 B + 复位复观(每档各触发一段) | 30 min | **单独一份日志**,判读规则见 §8.7 |
| 7 | 同一份接线下把 104 交叉读数复跑一遍 | 15 min | `test/host/iec104_master.py` |
| 8 | 取段与脚本产物归档(FTP `get` + `dump` / `fft` / `shape` / `plot`) | 15 min | 每段取完立刻改名,100 号轮转会覆盖 |
| — | (需用户点头)测频轮 B 档 / 相位轮抬倍数档 | +40 min | 各含一次重编、烧录与还原;段文件落地后相位轮这条基本不必走 |
合计约 **135 min**(不含"需用户点头"那两档)。取段与跑脚本集中在末尾第 8 行统一做,中间各条只负责"触发 + 让
`seg=` +1",免得一次 session 里反复插拔网线。
### 8.7 `[ERR]` 的判读规则(削顶轮单独跑、单独归档)
- 削顶轮会**故意造出** `[ERR] rail: edge=… sticky=… (code-domain, 20-run+release)`,而 `/serial-boot-test` 的 forbidden 标记含
`[ERR]`(AGENTS.md「构建与验证」)→ **这一轮不要把 `/serial-boot-test` 的标记校验当结论**:改人工判读,并在归档文件名与
抬头里写明「预期 [ERR]:贴轨锁存负向用例」;§8.6 已把它排在最后单独一轮(`docs/任务规划/数据可信.md` §8 风险表「削顶负向用例被当成固件 bug」行同一条)。
- 除这一条 `[ERR] rail:` 之外,**任何 `[ERR]` 都是不通过**;四个 Fault 的崩溃日志也以 `[ERR]` 开头 →
出现 Fault 立即停线取现场(`docs/子系统设计/看门狗设计说明.md` §10),别再继续注。
- 每次开机 `rail:` 边沿只打一条、`SYSLOG` 也只留痕一次("adc code rail fault" 进 `selftest.log`)——这是设计不是漏报;
要看第二次边沿就复位一次。
### 8.8 失败分诊(先查这一列,别急着改固件)
| 现象 | 先查 | 依据 |
|---|---|---|
| 与万用表差约 2 倍 | 发生器输出端接设成了 50 Ω | §8.2 表第 2 行 |
| 差约 5.3 倍,或数字带 6553.6 / 1241.2 的痕迹 | 拿错刻度,或把 `adc:` 的码值当 mV 读 | §2 两把尺子、§4「码是什么」 |
| `phasor` 行 `rms` 只有 10 格量级 | 正常(×0.1),不是 bug | §4 尾注、§8.1 |
| 非整 50 Hz 时 `meas:` 来回跳 | 预期:瞬时透传、200 ms 不是平均窗 → 改读 `freq(hb)` | `docs/任务规划/数据可信.md` §8 风险表 |
| 104 读数不动 | 每点 0.5 V / 0.005 A 变化死区(生效值 `App/algo/run_par.c`) | §8.5 的幅值卡坑 ② |
| 50 Hz 正弦注 `UB` 但 `rail` 不锁存 | **预期**(连续贴底不足 20 拍)→ 改法 A / B | §8.5 的削顶卡 |
| 改 DC 也不锁存 | 释放沿前提没满足:开机后该通道没出现过中带样本 → 先建基线再注入 | `App/algo/adc_health.c`、§8.2 前置门末条 |
| 撤掉注入后 `rail` 仍非 0 | 锁存语义(sticky),只 `adc_health_rail_init()` / 复位清 → 复位后仍不清**才是** bug | §8.5 的削顶卡改法 A |
| 读数带与幅度无关的偏置或漂移 | 共地 / 地环 | §8.2 末行 |
| `meas: seq` 不涨 | 发布闸门停 → 先查采集链,别判交流 | §8.2 前置门 |
| `health:` 或 `sampling fault:` 亮 | 与注入的波形无关:`adc_health` 的位只吃序号差、错误计数增量与 tick 差(`App/algo/adc_health.c:91-189`)→ 查节拍 / 丢帧,别查发生器 | §8.2 前置门 |
| 9 路全 0 | 读作"没数据",不是"输入为零" | §4 尾注 |
| `wave_read.py` 报错退出 / 拒绝出图 | **这是正确行为**:CRC 不符或零长度它必须拒收而不是画出东西 → 重取一份、核对是不是搬到一半的半成品;仍拒收就 `dump` 看两处 CRC16 哪个不符 | `test/host/wave_read.py` 文件头 |
| `shape --strict` 说实测与段头不等 | 先分清尺子:段头 `CLIPPED` 位来自**通道来源、从不实测**;DC 偏置 / 极小注入 / 未驱动通道应改用 `--clip-superset` | §8.5 的削顶卡段文件档 |
| 触发后 `seg=` 不涨或 `drop` / `ovf` 涨 | 落盘道在 defaultTask 卷空闲后才搬段,先等几秒;仍不涨查 `wave:` 行三计数与 `/wave` 目录可写(`[ftp]` 写权限) | `App/task/adc_task.c:782` |
### 8.9 收尾判据(走过临时改动的一条都不许留)
| 临时改动 | 收尾判据 |
|---|---|
| 测频轮 B 档:`FREQ_AMP_MIN` 50.0f → 0.5f(`App/algo/freq.h:62`) | 还原 + `/keil-build` 0 错误 0 警告 + **`git diff` 里 `App/algo/freq.h` 必须为空** + 在 `docs/任务规划/数据可信.md` §7 #8 那行留一句"本轮台架已还原" |
| 相位轮改倍数:`log_phasors()` 的 `* 10.0f` → `* 1000.0f`(`App/task/adc_task.c:209`) | 同上,`git diff` 里 `App/task/adc_task.c` 必须为空 |
| 为 bench 定时自抓开过编译门(`DTU_BENCH` + `WAVE_BENCH_AUTOCAP` 非 0,`App/task/adc_task.c:153`) | 还原成默认 0 + `/keil-build` 0 错误 0 警告 + `git diff` 里 `App/task/adc_task.c` 必须为空(产品镜像里这段必须零存在) |
| 只动接线与发生器、没动代码 | `git status` 除归档目录外干净 |
| 全程 | `test/board/test_config.h` 四个套件宏保持全注释(产品构建);烧录仍须 `-Rst -Run` + 回读校验 |
### 8.10 归档与回写(跑完必做,缺一算没收尾)
1. **证据归档** → `docs/测试说明/测试数据/boot_log_data/`,沿用现有命名惯例(日期_主题_类型.txt):
`20261104_ac_amp_meas_log.txt`、`20261104_ac_clip_rail_log.txt`、`20261104_ac_iec104_log.txt`;UI 目检照片同目录
(该目录若不收图,就在抬头里写清照片存放位置)。**走段文件定量档的用例另加两样**:取回的段原件(原始字节,
`20261104_ac_phase_wave<nn>.BIN`)与脚本输出(`fft` / `shape` / `dump` 三份文本 + `plot` 的图)——定量档的证据本体是
**那一段原始码值**,日志只是旁证;段原件一旦覆盖就再也拿不回来,先改名再触发下一段。
2. **实测结论回写** → `docs/硬件平台/ADC通道映射说明.md`,在 §8.1(直流轮次)之后**新开一节**——
任务卡 `docs/任务规划/数据可信.md` §5 的 DoD 就是这么分的。本文只留"怎么跑",**不抄实测数**。
3. **本文回写** → 只在 §8.1 那张表下加一行指针级别的句子("YYYY-MM-DD 已按本节跑通,实测见 …");
不在本文建第二份结论表,否则两份必然漂移(`docs/文档规范.md` 同一事实只在一处详述)。
4. **判据文档的一致性维护** → 本文 §8 的期望值算术、接法与出口分档已写进
`docs/任务规划/数据可信.md` §4.6.2(2026-10-29:波形轮的逐波形系数、削顶轮的 DC / 2 Hz 接法与 50 Hz 档的
"不锁存属预期"、相位轮与谐波轮的"定量档走段文件、定性档走 `phasor(hb)`")。今后两边若再分叉,按开头引导块的
两类裁定处理(语义从那节、算术与步骤从本文),**别在本文另立第二份判据表**。
5. **销项** → `docs/任务规划/数据可信.md` §2.7 与 §6 卡片表里这一条按登记簿「完成即销项」处理,属 Ask first,
须用户明确指示后再做。
### 8.11 这一轮证不到(别让读数长得像证据)
`docs/任务规划/数据可信.md` §4.6.5 那张五行表整条适用(端子二次值 / 精度等级 / 跨转换器相位与功率与对称分量 /
THD 与谐波方和根 / 温升 EMC 长时间漂移)只适用,本节按出口已落地改三条现状(2026-10-29 录波数据面并入主干之后):
**样本出口已成立**(`/wave/WAVE<NN>.BIN` + `test/host/wave_read.py`)→ 相位轮 / 谐波轮走定量档时结论是**形状级**,不再只是标量级;
但**板级那一轮对表本身还没跑过**(卡片判据见 `docs/任务规划/故障录波.md`),本轮跑出来的就是第一批实测;
段与发生器**没有绝对相位锁定**(触发瞬间只钉在段内点 159)→ 能核的是同段内两路的**相对**相位与极性,
全局相角基准仍证不到;一次触发只有 320 点 = 320 ms,**不等于长时间稳定性**。示波器仍旧只作旁证、不进判据。
再补一条「别把台架做成功能性重复」的边界:**算法本体的精度已经在纯数字域钉住了**——`test/host/phasor/phasor_test.c`
的 TC-PHASOR-003 与 TC-PHASOR-004(基波 = 峰值 / √2 容差 1 %、注入相角容差 0.5°)和 TC-PHASOR-006(2 / 3 / 5 次
谐波 1 %),以及 `test/host/freq/freq_test.c` 的扫频判据 J1(45~55 Hz 内 ≤0.02 Hz),都是把已知正弦**直接喂进
`sample_ring`**,绕过了 `adc_cal` 的折算(那里的幅值是工程量口径,不是引脚 µV)。所以 §8 这一轮真正要证的只有
「信号走到 `sample_ring` 之前」那一段:发生器刻度与输出端接、前端串阻与滤波电容的幅频、两片转换器的同刻性、
码到 mV 的折算、出口的量化。这一轮过了**不等于**「算法精度已实测验证」,结论里别那么写。