728d60675f
三条道在同一棵树上交付,故合并成**一次权威构建**(@build 0 错 0 警,Code=235236 RO=26308 RW=1572
ZI=136924)后同笔入库;台账已按该构建回填(`ram_baseline.py --update` + `--write`、`chec
k_mem_numbers.py
--write`),凭证与实际 .map 同源。归属如下:
· **通讯设置页**(道G task-9):新增 `Drivers/BSP/UI/ui_page_comm.{c,h}`(通道六注入,与死
区页同骨架同纪
律),`#page=comm` 绑定使 bound **21 → 22**、内容页黄金 **47 → 53**(旧 47 页字节零变化,
新增六态含
WAIT/OK 两张);`iec104_comm` 脏位 + store_save(**无事件位**)挂 defaultTask 同点位
轮询;`port_104` 由
宏改走 getter、`IEC104_SERV_PORT` 删除;字体 +听/监(185 字全覆)。**实机链已闭环**:FTP 外改
[comm]
的 ca/port → 复位 → 串口出现 `section 'comm' (2 keys)` 与 `i104: listening o
n 9004` → 还原 2404,全程无
FAIL ⇒「重启即生效 + 端口真值」不是推断是实测。上限核对 6/8 段、2/16 键,超限现象由 TC-CONFIG-056/057
钉死(注册 -1 + `comm init: FAIL` 锚 + getter 回 ROM),**不抬宏**。
· **开机账本**(道F task-12):新增 `App/util/boot_stats.{c,h}`——上电次数落 SD2506 用户
SRAM(magic +
小端计数 + 版本 + 异或校验),一次上电 = 1 读 + ≤1 写,**零 NAND、零卷锁、零配置段**;四态互斥
UNKNOWN / FRESH / CONTINUED / RESTARTED(BLF=1 才算电池域掉过电),**读失败绝不回写**;
文案口径「自 RTC
电池周期起」,**禁写终身次数**。复位原因优先级链从 `app_main.c` 的 if-else 收进一份真源(串口与屏幕同
源);104 四个只增不清计数 + `iec104_serv_get_stats()` 留在 serv、未塞进 link(会被 open
/close 洗掉)。
运行时长按用户裁定 = **只报本次开机以来**,零新增代码。新套件 `test/host/boot_stats` 21/21,五处注入
**全部有牙**(含「漂一字节被校验和挡下而不是报 256 倍错账」)。
· **adc_cal 可诊断化**(道E task-43):三个 volatile 计数 + `adc_cal_get_stats()`
/ `adc_cal_note_persisted()`
(水位**盖写非自增**,因为一次 save 覆盖整档,数 save 会留假未落盘);「差额 × 脏位」三态语义唯一落在
`adc_cal.h`,其中「差额 > 0 且脏位恒 0」= 绕过语义 setter 的静默丢失;TC-ADCCAL-019~023。
三条**必须留档的自纠**(都是执行道自己报的,我不替他们润色):
① stdint 那条断链是**宿主套件抓到、通讯道先修**,采集道承认自己是重复添加者并删掉自己那份;
② 「exit 2 不是证据,断言变红才是」——采集道第一版反证注入了未声明标识符导致编译错,差点当成功;
③ 套件汇总行原写 `s_total/s_total`,失败时会打印「21/21 PASSED (failed=1)」这种自相矛盾的行,已改。
另修:`check_registration.py` 的 `SIM_EXEMPT` 加一行豁免 `App/util/boot_stats.c
`(依赖 I2C 与 RCC 位掩码,
MCU 专属;核过 `sys_clock.c`/`sha256.c` 同形态先例),注册反向校验 exit 1 → 0。发号 TC-IEC1
04-034、
TC-UIDATA-029 已实抬游标(`TC-IEC104-033→034`、`TC-UIDATA-026→029`)。
门禁:id-check --strict 与 plain 均 rc=0、doc-links --all rc=0、注册校验 0、cppche
ck / coding-standard /
bom-drift 0;iec104 179/179、ui_data 295/295、boot_stats 21/21、adc_cal 11
7/117、run_par 67/67;菜单 8 状态
池 5368 不变、内容页 53/53。**已知遗留**:内容页数 47 → 53 的全仓文本同步尚未做(另卡跟踪),本笔只保
证代码与黄金一致;面板注键真机那一腿缺 CLI,交台架道补。
456 lines
35 KiB
Markdown
456 lines
35 KiB
Markdown
# 采集与算法层设计说明(`App/algo/`:L1 → L2 → L3)
|
||
|
||
> **模块名称**:自研采集与算法层,代码在 `App/algo/`(七个 `.c`,全部登记进 Keil 工程)。
|
||
> **文档定位**:**现状设计说明**——本层当前如何组织、每个模块的契约与关键机制、为什么这样设计、
|
||
> 边界与代价。**目录级索引(文件清单、构建与测试命令、新增模块动作)在 `App/algo/README.md`**,
|
||
> 本文不重复其内容;本文不写历史演进、门禁复测记录与修订日志(需要时查 git 历史)。
|
||
> **状态**:L1 → L2 → L3 主线已闭环到板端消费者(`protectTask`,20 ms 判据节拍);
|
||
> 标定折算层 `App/algo/adc_cal` 已于 2026-10-25 落地(`docs/任务规划/数据可信.md` 卡 2/4,
|
||
> 引脚电压量纲 + 系数落盘 + 未标定闭锁);**幅值精度判据**仍待标准源两点注入复核(卡 12)。
|
||
> **相关文档**:
|
||
> 分层与功能项(L1~L5)的唯一属主登记处:`docs/算法/继电保护功能复现规划.md` §2.1(分层)、§3(功能清单)、§4.2(精度)、§7(路线图)
|
||
> `docs/硬件平台/ADC通道映射说明.md`(**语义映射唯一权威口径**)
|
||
> `docs/子系统设计/ADC采样节拍设计说明.md`(TIM3 1 kHz 同源节拍、ADC1+DMA 乒乓缓冲、`adcTask` 唤醒)
|
||
> `docs/算法/傅氏算法详解.md` §2.3(有效值口径怎么来的)
|
||
> `docs/测试说明/相量层单元测试说明.md`、`docs/测试说明/ADC输入层单元测试说明.md`(主机单测细则)
|
||
|
||
## 目录
|
||
|
||
- [1. 定位与边界](#1-定位与边界)
|
||
- [2. 一拍的数据流与初始化落点](#2-一拍的数据流与初始化落点)
|
||
- [3. L1 语义取数 `adc_input.c`](#3-l1-语义取数-adcinputc)
|
||
- [4. L1 有效性判定 `adc_health.c`](#4-l1-有效性判定-adchealthc)
|
||
- [5. L2 输入 `sample_ring.c`](#5-l2-输入-sampleringc)
|
||
- [6. L2 `phasor.c` 相量计算](#6-l2-phasorc-相量计算)
|
||
- [7. L2 派生量:频率测量 `freq.c`](#7-l2-派生量频率测量-freqc)
|
||
- [8. L3 输入:保护定值模型 `prot_set.c`](#8-l3-输入保护定值模型-protsetc)
|
||
- [9. L3 保护元件框架 `prot_elm.c`](#9-l3-保护元件框架-protelmc)
|
||
- [10. 关键约定与禁令](#10-关键约定与禁令)
|
||
- [11. 资源足迹(改容量前先看这里)](#11-资源足迹改容量前先看这里)
|
||
- [12. 验证与判读](#12-验证与判读)
|
||
- [13. 相关文档](#13-相关文档)
|
||
|
||
---
|
||
|
||
## 1. 定位与边界
|
||
|
||
- **原则:算法与 I/O、与调度分离。** 本层只做两件事——把原始码值变成**语义通道**的"数"、把采样序列
|
||
变成**相量/频率/判据状态**。不建任务、不等事件、不碰寄存器(唯一例外 `adc_input.c`,它必须驱动
|
||
ADC1)、不做出口动作。
|
||
- **这样分离换到什么**:`sample_ring` / `phasor` / `adc_health` / `prot_set` / `prot_elm` /
|
||
`freq` **零平台依赖**(只用 `stdint.h` / `math.h` / `stddef.h` 与本目录头),因此可被主机侧
|
||
单测以 `#include` 原样纳入、离线全覆盖(落实 `docs/任务规划/测试与架构专项.md`「采集链路测试可验证性约束」),不需要真机就能钉死判据。
|
||
- **上下文约束**:`*_init()` 一律无 RTOS 依赖、幂等,由 `app_main_init()` 在**内核启动前**调用;
|
||
`adc_input_poll()` / `phasor_run()` / `freq_run()` **仅任务上下文**(快照走临界区)。
|
||
- **消费者在 `App/task/`**:`adcTask` 采集并压环、`protectTask`(B5)跑 `prot_elm_run()`;
|
||
新算法一律落在 `App/algo/`,**勿塞进 `App/task/`**(任务只做消费与编排)。
|
||
|
||
---
|
||
|
||
## 2. 一拍的数据流与初始化落点
|
||
|
||
一拍 = TIM3 的一个 1 kHz 更新事件(节拍与消费者唤醒同源,见
|
||
`docs/子系统设计/ADC采样节拍设计说明.md` §1)。
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
TIM3["TIM3 更新事件 1 kHz<br/>ISR: 脉冲 CONVST + 置唤醒标志<br/>同一事件经 TRGO 触发 ADC1"]
|
||
TIM3 --> TPAFE["TPAFE5160 转换<br/>BUSY EXTI 收帧"]
|
||
TIM3 --> ADC["STM32 ADC1 一轮 9 通道<br/>DMA2 循环模式自动搬运"]
|
||
ADC --> PP["乒乓双缓冲<br/>半传输 / 整传输回调推进完成计数"]
|
||
TIM3 -. "唤醒 adcTask" .-> TASK
|
||
TPAFE --> POLL
|
||
PP --> POLL
|
||
TASK["adcTask(事件驱动唤醒,非定时轮询)"] --> POLL["adc_input_poll()<br/>TPAFE 原子快照 + 消费刚完成的半区"]
|
||
POLL --> RAW["adc_input_raw(ch) × 9<br/>按语义取原始码值"]
|
||
RAW --> RING["sample_ring_push_frame()<br/>整帧入环"]
|
||
RING --> RUN["phasor_run()<br/>逐实例重算 + 双区切换"]
|
||
RUN --> FREQ["freq_run()<br/>共轭乘积相位增量 + 自适应窗均值"]
|
||
RUN --> GET["phasor_get(idx)"]
|
||
FREQ --> FGET["freq_get() / freq_valid()"]
|
||
GET --> CONS["消费者:adcTask 心跳日志"]
|
||
FGET --> PCONS["消费者:prot_elm_run() 的 OF / UF 判据(B8)"]
|
||
```
|
||
|
||
| 落点 | 做什么 |
|
||
|---|---|
|
||
| `app_main_init()`(内核启动前) | `adc_input_init()`(映射自检 + 挂 DMA 缓冲)→ `phasor_init()`(内部连带 `sample_ring_init()`)→ `freq_init()` → `prot_set_init()` → `prot_elm_init()` |
|
||
| `adcTask`(每拍) | `adc_input_poll()` → `adc_input_raw()` ×9 → `sample_ring_push_frame()` → `phasor_run()` → `freq_run()` → 心跳与健康判定 `adc_health_check()` |
|
||
| `protectTask`(20 ms) | 取 `phasor_get()` rms 与 `freq_get()` → `prot_elm_run()`;「自检失败闭锁」采样故障闭锁、 定值校验和巡检 |
|
||
|
||
`prot_elm_init()` 须在 `phasor_init()` **之后**(它按名前缀解析相量实例索引);`prot_set` 独立
|
||
(只依赖 `stdint.h`),可在任意点初始化。
|
||
|
||
---
|
||
|
||
## 3. L1 语义取数 `adc_input.c`
|
||
|
||
**解决的问题**:V0.1 上 8 路电气量被两片转换器分片(硬件缺陷**双转换器无共同采样触发**),且网名 `AINn`
|
||
≠ 板级 `ADCn` ≠ 转换器通道号(17 路里 13 路置换)。消费侧若直接索引驱动缓冲,改版即全线错位。
|
||
本层把"哪一路"收进一张表 `k_adc_map[]`,上层**按语义取数、不写死下标**。
|
||
|
||
| API | 契约 | 违约后果 |
|
||
|---|---|---|
|
||
| `adc_input_init()` | 清缓冲与统计 + **映射自检**(每个 STM32 语义通道必须落在扫描 Rank 序列内)+ `HAL_ADC_Start_DMA()` 挂载乒乓缓冲(**只挂一次**);`-1` = 映射指向从不扫描的通道,`-2` = DMA 挂载失败 | 映射错位是**静默**的:该路恒读上一个陈旧值,不报错 |
|
||
| `adc_input_poll()` | 采一拍:`tpafe5160_snapshot()` 原子快照(8 通道)+ `stm32_take()` 消费**刚完成的那半区**(按完成计数奇偶推半区,零忙等) | **仅任务上下文**(快照是临界区);在 ISR 里调会破坏 `tpafe5160_snapshot()` 的"仅任务上下文"约定 |
|
||
| `adc_input_raw(ch, &out)` | 按语义读原始码值(TPAFE 16 位补码 / STM32 `0..4095`);`-2` = 该来源尚无有效数据 | 消费侧取数**唯一入口**;须先成功 `poll()` |
|
||
| `adc_input_desc(ch)` | 语义 → 物理通道描述(来源 + 下标 / 通道号),供自检注入与 UI 通道序 | 返回静态地址,**勿改** |
|
||
| `adc_input_stm32_rc()` / `_seq()` / `_errs()` / `_gaps()` / `_hw_errs()` | 最近一拍状态码 / DMA 整帧完成计数 / 停摆(连续 ≥2 拍无新帧)/ 丢帧**点数**(Σ 每次前进的超出部分,与 `tgap` 同单位)/ HAL 错误回调 | **四类成因不同,勿混读**:对应心跳字段 `adc1_rc` / `adc1_seq` / `adc1_err` / `adc1_gap` / `adc1_hw`(判读见 `docs/子系统设计/ADC采样节拍设计说明.md` §8) |
|
||
|
||
**要点**
|
||
|
||
- **映射表内容的唯一权威口径**是 `docs/硬件平台/ADC通道映射说明.md`:`UA/UN/IA` → TPAFE
|
||
`buf[6]/[5]/[2]`;`UB/UC/IB/IC/IN/TEMP` → ADC1 `CH14/3/6/1/2/5`。
|
||
**V0.2 改版只改这张表的内容**,本层 API 与上层语义不动。
|
||
- **STM32 侧是顺序扫描**(Rank1..8 = CH0..CH7、Rank9 = CH14,与 `MX_ADC1_Init()` 严格一致),
|
||
9 个通道**不是同一时刻**(一轮 ≈67 µs);由 TIM3 TRGO 硬件触发 + DMA 自动搬运(
|
||
`docs/子系统设计/ADC采样节拍设计说明.md` §4)。STM32 侧数据**固定滞后 1 拍(1 ms)**——与 TPAFE
|
||
侧本来就没有同刻性(双转换器无共同采样触发),无新增误差。
|
||
- **开机映射自检**挡住"映射到一个从不扫描的通道"这类静默错(启动日志 `ADC input layer init: OK`)。
|
||
- 本文件是本层**唯一**允许出现 HAL 调用的地方;新增模块若不需要外设,请保持 `sample_ring` /
|
||
`phasor` 那种零依赖形态,才能免桩单测。
|
||
|
||
---
|
||
## 4. L1 有效性判定 `adc_health.c`
|
||
|
||
**解决的问题**:驱动侧与 L1 只有"观测计数"(`tpafe5160_frame_seq()` /
|
||
`tpafe5160_frstdata_err()`、`adc_input_stm32_*()`),**没有判定**——芯片静默、消费者超拍丢帧、
|
||
序号倒退这些故障原本只能靠人读心跳日志发现。本层把判定收进纯逻辑,**告警编排留在消费者**
|
||
(`adc_task.c` 的 `adc_health_report()`)→ 判定可主机免桩单测。
|
||
|
||
| API | 契约 |
|
||
|---|---|
|
||
| `adc_health_init()` | 清状态;**首拍 `check()` 只建基线、不判定** |
|
||
| `adc_health_check(&in, &out)` | 吃"计数快照"做一次判定(每拍调,常数次整数运算);差值法 `(int32_t)(now - prev)` → 计数与 tick 回绕都安全 |
|
||
| `adc_health_flags()` | **电平**故障位:`TPAFE_STALL` / `STM32_STALL`(≥100 ms 无前进,可自愈)/ `SEQ_BACKWARD`(锁存)——为「自检失败闭锁」闭锁供数 |
|
||
| `adc_health_sticky()` | **锁存**位:`FRSTDATA` / `STM32_ERR` / `HW_ERR` 的增量痕迹,自愈后仍留痕 |
|
||
| `adc_health_tpafe_gaps()` | TPAFE 累计丢帧点数(序号前进 >1 的超出部分),与 STM32 侧 `adc_input_stm32_gaps()` **同单位**(心跳字段 `tgap` / `adc1_gap` 可直接对比) |
|
||
|
||
**要点**
|
||
|
||
- **三类语义严格分开**:**电平**位(stall,可自愈)/ **锁存**位(出现过必留痕)/ **边沿事件**位
|
||
(仅当拍有效)。`ADC_HE_*` 与同名 `ADC_HF_*` **编号对齐**,消费者直接用事件掩码对"已留痕位图"
|
||
做"每类每次开机一次 `SYSLOG_SELFTEST`"的编排。
|
||
- **静默 ≠ 丢帧**:前进量 d = 0 才是静默候选;d > 1 只说明消费者没跟上、生产者还活着——
|
||
两者处置完全不同,判据不许混。
|
||
- **gap 不告警**:消费者超拍(心跳拍 ≈2 ms)是已知现象,`adc1_gap` / `tgap` 在 10 s 心跳里可见;
|
||
若按事件告警会把真故障淹掉。
|
||
- 告警策略(在 `adc_task.c`,**不在本层**):严重事件与电平上升沿 `DBG_ERROR` +
|
||
`SYSLOG_SELFTEST`(每类每次开机一次,防刷屏写满 `selftest.log`);下降沿 `DBG_INFO`
|
||
("恢复"必须可见,否则只看到一次错误无从判断是否还在)。
|
||
|
||
**深读**:本模块的判据推导(100 ms 门限的三条依据)、位号对齐的收益与代价、闭锁与兜底的责任
|
||
划分、观测盲区、验证分工与现场判读手册 → `docs/算法/采样有效性校验设计说明.md`。
|
||
|
||
---
|
||
|
||
## 5. L2 输入 `sample_ring.c`
|
||
|
||
**解决的问题**:相量计算要"以当前时刻为终止的一周波",而 L1 每次只给最新一帧。本层按**语义通道**
|
||
存最近 `SAMPLE_RING_CYCLES = 2` 个周波(`SAMPLE_RING_DEPTH = 40` 点),与参考系统
|
||
`CN_BUFSAM_CYCLE = 2` 一致,并为后续加长数据窗(1.5~2 周波抗衰减直流)预留空间。
|
||
|
||
| API | 契约 |
|
||
|---|---|
|
||
| `sample_ring_push_frame(values, n)` | 整帧压入,长度必须 `= ADC_SEM_COUNT` |
|
||
| `sample_ring_window(ch, n, out)` | 取该语义通道最近 `n` 点,**按时间序旧 → 新**(`out[n-1]` 最新);`-2` = 点数不足 |
|
||
| `sample_ring_filled()` | 已累积点数(**饱和**于 40)——"够不够一周波"的判据 |
|
||
| `sample_ring_frames()` | 累计入环帧数(**不饱和**,1 kHz 下约 49.7 天回绕)——与 `tpafe5160_frame_seq()` 对照即判丢帧 |
|
||
|
||
**要点**
|
||
|
||
- **整帧写入后才统一推进写指针** → 单帧内不可能"半新半旧",消费侧取到的窗永远落在整帧边界。
|
||
- 本层**只做按时序存 / 取,不做任何跨通道运算**:同刻性由上游 L1 负责,本层既不保证也不假设。
|
||
判定也不在本层(本层只出计数)。
|
||
- 某通道本拍取数失败 → **整拍跳过入环**(现 `adc_task.c` 的 `s_frames_skip` 即此),
|
||
**切勿补 0 点**——单个 0 点会显著污染 20 点 DFT 窗。
|
||
|
||
---
|
||
|
||
## 6. L2 `phasor.c` 相量计算
|
||
|
||
**算法**:全周傅氏(单频 DFT),`N = PHASOR_N = 20`(1 kHz @ 50 Hz)。系数表
|
||
`k_fft_sin` / `k_fft_cos` **离线生成**并**预乘 √2/N**(口径推导见
|
||
`docs/算法/傅氏算法详解.md` §2.3),故 `fAmSQ = √(fRe²+fIm²)` **直接就是有效值**,
|
||
`fAm = √2·fAmSQ` 为峰值。运行期不调 `sinf/cosf`(省算力,也让结果可逐点复算验证)。
|
||
|
||
| API | 契约 |
|
||
|---|---|
|
||
| `phasor_init()` | 双区清零 + 使用区/就绪复位 + `sample_ring_init()`(等价"刚上电");幂等、内核前可调 |
|
||
| `phasor_inst_count()` / `phasor_inst(idx)` | 遍历实例表 `{name, sem, harm}`;返回静态地址**勿改**,越界 `NULL` |
|
||
| `phasor_run()` | 逐实例取 20 点窗 → 重算 → 写计算区 → **全部实例写完后一次性切换使用区**;仅任务上下文,须先 `push_frame()` |
|
||
| `phasor_get(idx, &out)` | 读**使用区**结果;`-2` = 未攒满一周波 |
|
||
| `phasor_calc(x, n, harm, &out)` | 对给定窗口算 `harm` 次相量——**纯函数**,可脱离采样环独立单测 |
|
||
|
||
**要点**
|
||
|
||
- **实例表数据驱动**(`k_inst[]`,现 5 行):`C01-IA-F1`(过流 / 过负荷)、`C02-UA-F1`
|
||
(过压 / 欠压 / 频率)、`C03-UN-F1`(零序过压)、`C04-IA-F2`(二次谐波制动)、
|
||
`C05-IA-F5`(五次谐波选线)。**新增实例只在表尾加一行**,遍历 / 日志 / 单测自动适配。
|
||
- **双区(计算区 / 使用区)**:结果先写非使用区,全部实例写完后单字节 `volatile` store 切换
|
||
`s_use_zone`(Cortex-M 上原子)→ 读者无锁、无撕裂。写者是 `adcTask`(同拍),**读者跨两个
|
||
上下文**:`adcTask` 心跳与 `protectTask` 判据(`prot_elm_run()` → `phasor_get()`)——无锁读
|
||
的收益正在被使用,**勿改成加锁、也勿取消双区**。
|
||
- **任一实例数据不足一周波 → 本拍整体不产出**(不切换使用区):宁可整体不刷新,也不给出
|
||
"部分新部分旧"的混合结果。
|
||
- **每拍 20 点全部重算**(暴力法)。不用"加尾项去头项"的增量式:增量递推会累积误差,
|
||
暴力法每次都是干净的一周波。代价 = 每拍 20 × 实例数次乘加,相量刷新周期 1 ms。
|
||
- **相位(「相位输出」)不常驻结构体**:需要时自行 `atan2f(fIm, fRe)`(`φ` 约定 = 窗首初相,见 §7)。
|
||
|
||
---
|
||
## 7. L2 派生量:频率测量 `freq.c`
|
||
|
||
**解决的问题**:过频 / 低频(`docs/算法/继电保护功能复现规划.md` §3.3 「过频 / 低频」)需要系统频率读数,
|
||
而 L2 只出相量。本层把"相邻两拍基波相量的相位差"变成"频率",并解决**非整周波窗长的 DFT 泄漏纹波**
|
||
——这是决定精度的关键问题。
|
||
|
||
| API | 契约 |
|
||
|---|---|
|
||
| `freq_init()` | 清增量环 / 计数 / 读数 / 相位基准 / 有效标志;幂等,内核前可调 |
|
||
| `freq_run()` | 每拍步进:选源(UA → IA 过幅值门)→ 共轭乘积 `P = Z(m)·conj(Z(m-1))` 取角 → 假值钳制 → 自适应窗均值。**仅 `adcTask` 上下文,须在 `phasor_run()` 之后同拍调** |
|
||
| `freq_get(out)` | `0` 有读数(可能是保持值)/ `-1` 参数错 / `-2` 从未收敛(写 0) |
|
||
| `freq_valid()` | "本拍新鲜":幅值门 OK 且已收敛。**保护消费口径 = `freq_get() == 0 && freq_valid() == 1`** |
|
||
| `freq_last()` | 最近有效读数;从未收敛返回 `0.0` |
|
||
|
||
### 7.1 为什么是"相位微分 + 自适应窗"
|
||
|
||
- **免解缠绕**:`arg(Z_now · conj(Z_prev))` 天然落在 `(-π, π]`,无需维护累积相位。
|
||
本层相量约定 `φ = atan2(fIm, fRe)` = 窗首初相,窗每拍前滑一点 → 纯音下每拍角**增大**
|
||
`2πf/Fs`;45~55 Hz 段 |Δφ| ≈ 0.28~0.35 rad,距 ±π 裕度巨大。
|
||
- **定长窗为什么不够(自适应窗的理由)**:20 点矩形窗在非整周波时混入**负序泄漏项**
|
||
(45 / 55 Hz 处 |S₂/S₁| ≈ 3~5 %),相量端点轨迹成椭圆 → 逐拍相位差带上周期 `δ(φ)`
|
||
纹波,纹波周期 = `fs/(2f)` ≈ 9.1~11.1 拍。**定长平均不整除纹波周期**,残差留在均值里:
|
||
扫频最坏 0.317 Hz(40 拍算术平均)、0.197 Hz(60 拍复数和的 Kay 法——交叉项的和角不随
|
||
`K²` 衰减,**勿再照这条路优化**)。改 `K_eff = round(8·Fs / (2·f̂))`(45~55 Hz 段 73~89 拍)
|
||
使窗长恰为纹波周期的整数倍 → 纹波相消,扫频最坏降到 **0.0101 Hz**。
|
||
- **假值钳制带取 35~65 Hz,而不是系统合理带**:单拍相位差本身带 ±5 Hz 纹波,钳制带贴边会
|
||
**非对称丢弃纹波半周** → 均值偏置(实测 45 Hz 点 +0.16 Hz,已修)。35~65 Hz 宽于系统合理带
|
||
40.5~60.5 Hz(参考 `CN_HERTZ_SOFTFR_MIN/MAX`,见
|
||
`docs/算法/继电保护功能复现规划.md` §10.1),仍能拦住建"窗滑 2 拍"造成的 ≈2f(90~110 Hz)
|
||
与相位跳变假值;系统合理带由 OF / UF 的**定值范围**在消费侧把关,不在这里重复把关。
|
||
- **幅值门** `FREQ_AMP_MIN = 50.0`(2026-10-25 卡 6 语义重定为**二次值 V**:50 V = 0.5×Un,
|
||
数值沿旧 50.0 属巧合,量纲依据 `App/algo/prot_set.c` 头部量纲基准块):低于门视为
|
||
无有效波形(浮空 / PT 断线时 rms ≈ 0,相位无意义)→ 停止更新、`valid` 清零、读数保持,
|
||
台架浮空因此**不发散**;信号刚消失的过渡段(≤20 ms 残窗)读数漂移有界 ≤0.3 Hz。
|
||
- **源优先级 UA(C02) → IA(C01)**,按名前缀解析、每拍动态回退(台架只注电流也能测频);
|
||
**源切换即废相位基准重建**——UA 与 IA 之间没有共同相位基准(双转换器无共同采样触发),跨源相位差无意义。
|
||
|
||
### 7.2 精度判据(J1~J6,与 `test/host/freq/freq_test.c` 文件头同口径)
|
||
|
||
| 判据 | 条件 | 门槛 | 当前实测最坏 |
|
||
|---|---|---|---|
|
||
| J1 扫频 | 纯音 45.00~55.00 Hz,0.5 Hz 步进(21 点) | **≤ 0.02 Hz** | **0.0101 Hz** @55.0 Hz |
|
||
| J2 噪声 | 幅值 1000 码、噪声 rms = 1 % 幅值 | **≤ 0.10 Hz** | 0.0160 Hz |
|
||
| J3 启动收敛 | 首个有效相量 → 首个有效读数 | **≤ 40 ms** | 20 ms |
|
||
| J4 频率阶跃 | 50 → 51 Hz 进入 ±0.05 Hz 并保持 | **≤ 120 ms** | 98 ms(稳态误差 0.0053 Hz) |
|
||
| J5 零幅 / 浮空 | rms < 幅值门 | `valid = 0`、不发散、保持漂移 ≤ 0.3 Hz | 漂移 0.13 Hz,无 NaN |
|
||
| J6 假值钳制 | 幅值 + 频率双跳变拍 | 60 ms 内回到 ±0.05 Hz | 0.0000 Hz |
|
||
|
||
附加鲁棒性(同一套件):直流偏置 8000 码 → 0.0018 Hz;3 / 5 次谐波 20 % / 10 % → 0.0065 Hz;
|
||
UA 无幅值落 IA → 0.0020 Hz。
|
||
|
||
**J4 的 120 ms 是"精度优先于响应"的显式取舍**:自适应窗(M = 8 ≈ 4 个泄漏纹波周期 ≈ 4 个信号
|
||
周波)换来 0.0101 Hz 量级的精度,代价是频率骤变后的窗冲刷(实测 98 ms)。OF / UF 定时限默认
|
||
**1000 ms** → 测频响应占保护总动作时间 **< 10 %**,暂态假值另有 J6 兜底。**若将来要压响应时间,
|
||
必须同时重估精度**:缩短窗就是把精度退回 0.2~0.3 Hz 档,不是免费的。
|
||
|
||
---
|
||
|
||
## 8. L3 输入:保护定值模型 `prot_set.c`
|
||
|
||
**解决的问题**:保护判据的"输入宪法"。参考系统把定值放 EEPROM + 常量表做校验和自检;本层做
|
||
**RAM 模型 + 持久化纯逻辑**(blob 序列化 / 解析,文件胶住在 `App/cfg/prot_set_store.c`),
|
||
把**编号 / 通讯地址 / 范围 / 默认值 / 校验和**闭环钉死。
|
||
|
||
| API | 契约 | 为什么 |
|
||
|---|---|---|
|
||
| `prot_set_init()` | 装默认值 + 封存校验和;幂等 | 内核前可调 |
|
||
| `prot_set_load_raw(vals)` | 整表装载,逐项范围校验 | **all-or-nothing**:任一项越界则**整表不装载**,`prot_set_bad_mask()` 指认越界项;影子区 + 单字交换 → 读侧永不见中间态 |
|
||
| `prot_set_set(id, q10)` | 单项写入(范围校验,批处理路径) | **不自动重封存**——批量改完必须 `prot_set_seal()`,否则 selfcheck 判"非法改写" |
|
||
| `prot_set_set_sealed(id, q10)` | **原子写 + 封存**:影子区改好重封后单字交换 | 自检永不见中间态(收口 B5 发现的竞态窗口);置脏标志 |
|
||
| `prot_set_dirty_peek()` / `_take()` / `_mark()` | 脏标志三件套(落盘握手) | peek = 通知 iec104Task;take = 属主落盘前消费;mark = 写失败回置重试 |
|
||
| `prot_set_store_serialize()` / `_parse()` | 持久化 blob:`PSET` + 版本 + 值区 + sum32 正 / 反码对 | parse **整表原子**:magic / 版本 / 长度 / 校验对 / 范围任一失败 = **一字不动** |
|
||
| `prot_set_get_f()` / `get_ctl()` | 工程量浮点 / 投退读取 | 单字读原子 |
|
||
| `prot_set_selfcheck()` | 重算 sum32 与封存正 / 反码比对,失配 → 闭锁(「定值校验和」) | seqlock 式复核:提交时撞见改写 = 本轮跳过而**不误锁**;`prot_set_seal()` 是解锁的**唯一**合法路径 |
|
||
|
||
**要点**
|
||
|
||
- **Q10 定点**(×1024)与参考系统 `2^10` 折算口径一致;元数据表(地址 / 范围 / 默认)是 ROM
|
||
常量,**校验和只覆盖 RAM 值区**。
|
||
- **通讯地址逐项对照参考** `Comm/Pub/Const_CommSetPara.c` 的 `g_tCommSetParaTab`(基址
|
||
`0x8820`);单测 TC-PROTSET-006 钉死"地址不重复、`min ≤ def ≤ max`、投退项 0..1"。
|
||
- ⚠ **默认值与范围是拟定值**(参考 EEPROM 出厂表不在本仓库),待对照修订——进度注见
|
||
`docs/算法/继电保护功能复现规划.md` §5.4。
|
||
- **OF / UF 八项尾追加**(B8,32 → 40 项):`PS_OF_EN / OF_HZ / OF_DLY / UF_EN / UF_HZ / UF_DLY
|
||
/ RET_OF / RET_UF`——**编号不回收,枚举序 = 数组序 = 校验和覆盖序**,故只许尾部追加。
|
||
这八项的逐项通讯地址不在本仓库(参考表只示"含低压 / 低频"类目)→ 全部按 **本地项
|
||
`comm_addr = 0`** 登记(104 C_SE 不可寻址),待对照补。
|
||
- **频率返回系数独立**:`RET_OF = 0.99` / `RET_UF = 1.01`(返回门槛 = 定值 × 系数)——
|
||
频率滞回带与电气量的 0.95 / 1.05 不同量级,**勿共用一个返回系数**。
|
||
- **换代语义**(B7 → B8):项数变 → blob 144 → 176 B、`PROT_SET_BLOB_VER` 1 → 2。旧档(ver = 1 /
|
||
32 项)在新固件 parse 时被**版本 / 长度闸**拒绝 → 回落出厂默认,`prot_set_store_load()`
|
||
**返回 2 = 换代**(打 INFO,**不是** CORRUPT、不记自检),`-1` 才是真损坏。
|
||
升级路径**不得**报成损坏(判读细节见 §12.3)。
|
||
- **bad_mask 随项数扩为 `uint64_t`**(bit i = 第 i 项越界);消费方目前只有主机单测,
|
||
板端闭锁走 `prot_set_locked()`。
|
||
|
||
---
|
||
|
||
## 9. L3 保护元件框架 `prot_elm.c`
|
||
|
||
**解决的问题**:参考系统 89 个 `PRS_*.c` 各自实现"滞回 + 定时限"——本框架把这段公共核收敛成
|
||
**一个通用核 + 数据驱动实例表**(与 `phasor` 的 `k_inst` 同一模式):新增元件 = 表加一行 +
|
||
定值加几项。
|
||
|
||
| API | 契约 |
|
||
|---|---|
|
||
| `prot_elm_init()` | 复位状态 + 按名前缀(`C01/C02/C03`)解析相量实例索引;须在 `phasor_init()` 之后;`src = FREQ` 行无相量绑定(`pidx = 0xFF`) |
|
||
| `prot_elm_eval(now_ms, meas)` | **纯判据**:定值 RAM + 测量数组 → 全元件状态机;主机单测入口;`NULL` 测量按 0 |
|
||
| `prot_elm_run(now_ms)` | 适配层:按实例表 `src` 组测量(`0` 取 `phasor_get()` rms,`1` 取 `freq_get()`)再 eval |
|
||
| `prot_elm_state()` / `prot_elm_active_mask()` | 状态快照 / 动作位图,供 L4 出口与心跳日志 |
|
||
| `prot_elm_request_reset()` | 跨上下文复归入口(104 YK11 落请求,`eval` 头部消费) |
|
||
|
||
**要点**
|
||
|
||
- **滞回**:已启动用**返回**门槛(定值 × 返回系数),未启动用**动作**门槛;过量 / 欠量方向由实例表
|
||
`over` 位统一处理(`UV` / `UF` 是欠量实例)。
|
||
- **定时限**用差值法 `(uint32_t)(int32_t)(now - t0) >= dly` → tick 回绕安全;`dly = 0` =
|
||
启动同拍即动作。**返回是即时的**(参考系统带 10 ms 返回延时,接出口硬件时再补)。
|
||
- **闭锁联动**:`prot_set_locked()`(「定值校验和」)或该元件投退 = 0 → 状态整体复位、不再判据。
|
||
- **数据缺失时的两种处理刻意不同**(这是本层最容易改错的一处):
|
||
- 相量类**任一未就绪 → 整拍跳过**(不给判据喂陈旧值);
|
||
- 频率类**无有效读数 → 按方向填"必不启动"值**(过量填 `0`、欠量填 `1e30` → 滞回比较恒假 →
|
||
元件复位),**不整拍跳过**——频率源缺失不得饿死电流 / 电压类元件。
|
||
- **V0.1 实例表**(受双转换器无共同采样触发约束):`OC1 / OC2 / OC3 / OL`(IA)、`OV / UV`(UA)、`3U0`
|
||
(UN 外部通道)、`OF / UF`(`freq` 测频,B8)。**零序过流(IN 走 STM32)故意不在表中**
|
||
——跨转换器不构成同刻矢量。
|
||
- **OF / UF 的出口可见性**:两元件**未单独映射 104 遥信点**(送检点表口径见
|
||
`docs/算法/继电保护功能复现规划.md` §7),动作目前**只经 YX15 保护总聚合可见**
|
||
(`iec104_pts` 的 fault-total 掩码含 OC / 3U0;OF / UF 置总位但无独立分点)。
|
||
**文档与代码都必须按此口径,勿写"独立遥信"。**
|
||
- **板端接线**:`App/task/protect_task.c`(protectTask,20 ms)消费 `prot_elm_run()` +
|
||
自检失败闭锁 + 定值校验和巡检;判据结果经 104 遥信上送(`iec104_pts` YX15~23 / 31)
|
||
→ `docs/算法/继电保护功能复现规划.md` §7。实例数变化会同时改变启动锚点 `elm=` 数字与
|
||
`prot_elm_active_mask()` 位宽(B8:7 → 9 实例),串口锚点表若含该数字须同步核对。
|
||
|
||
---
|
||
## 10. 关键约定与禁令
|
||
|
||
1. **语义映射只有一处权威**:`docs/硬件平台/ADC通道映射说明.md`。取数一律经 `adc_input_raw()`,
|
||
**禁止**在消费侧直接索引驱动缓冲或写死下标。
|
||
2. **禁止跨转换器运算**(V0.1 硬件缺陷**双转换器无共同采样触发**):`UA/UN/IA` 在 TPAFE、`UB/UC/IB/IC/IN` 在
|
||
STM32 ADC1,两片转换器没有共同采样触发 → 无同一时刻性、相位不确定。故本层**不做**跨转换器的
|
||
相位 / 功率 / 功率因数 / 负序零序类矢量运算;**8 路全接 TPAFE(V0.2 硬件)后方可解禁**。
|
||
3. **标定折算已落地、精度复核未做**(2026-10-25 `App/algo/adc_cal`;标准源两点注入见
|
||
`docs/任务规划/数据可信.md` 卡 12):帧值现 = 引脚侧工程量(ROM 默认系数 = 转换器 LSB 事实值),
|
||
前端分压读法判别与端子变比仍待台架——**精度结论前不得把幅值当计量证据**。
|
||
4. **本层无副作用外溢**:不建任务、不等事件、不写日志(日志由调用侧打)、不碰寄存器
|
||
(例外只有 `adc_input.c` 驱动 ADC1)。
|
||
5. **上下文纪律**:内核启动前只允许调 `*_init()`;`adc_input_poll()` / `phasor_run()` /
|
||
`freq_run()` / `prot_elm_run()` 只在各自任务里调。
|
||
6. **表只许尾部追加**:`k_inst[]`(相量实例)、`prot_elm` 实例表、定值元数据表——
|
||
枚举序 = 数组序 = 校验和覆盖序,插入或重排等于改校验和与 104 点表。
|
||
|
||
### 10.1 标定折算 `adc_cal`:谁写 RAM、谁落盘、什么时机(道G task-43)
|
||
|
||
这一小节存在的理由很具体:**此前全仓没有任何一处写清"k/b 改完为什么会丢"**,连写卡片的人都猜错过一次
|
||
(以为 `cfg_file_poll()` 的 2 s 轮询能兜住未落盘的改动——方向恰好相反)。三条事实链,每条可复算:
|
||
|
||
| 问题 | 唯一答案 | 代码落点 |
|
||
|---|---|---|
|
||
| 谁是 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` |
|
||
| 谁落盘、什么时机 | 只有 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 的重载**(外部改档 → 沉降去抖 → 重载,见
|
||
`App/cfg/cfg_file.h` 同族契约与 `docs/子系统设计/配置文件设计说明.md`)。拿它"兜住"未落盘的改值,
|
||
等于把 FTP 侧的旧档倒灌回 RAM,比不兜更坏。**任何"改了就等于存了"的意图式陈述一律按本表改写**
|
||
(`docs/代码规范/pre-commit使用说明.md` §2.5 第二条)。
|
||
|
||
**"改了没落盘"现在是可诊断事实**(第②项;先例是 `App/fs/vol_lock.h` 的观测计数设计:只计数、
|
||
不取锁、不在调用点打日志、不打 `[ERR]`)。三个观测计数的**定义只在 `App/algo/adc_cal.h` 一处**,
|
||
本文只讲怎么读:
|
||
|
||
| `set_ok − persisted_set_ok` | `run_par_dirty_peek()` | 定性 |
|
||
|---|---|---|
|
||
| 0 | 0 | 盘与 RAM 齐(或压根没改过)——健康 |
|
||
| >0 | 1 | 改了、还没到落盘时机——正常在途 |
|
||
| >0 | 0 | **改了且永远不会落盘**:有写者绕过语义 setter 直调 `adc_cal_set()` |
|
||
|
||
`persisted_set_ok` 是**水位**而不是计数:`run_par_store_save()` 成功路径上盖写为当时的 `set_ok`
|
||
(一次 save 覆盖整档、可能对应多次逐通道整定,自增会留下"假未落盘";回归锁 = TC-ADCCAL-021)。
|
||
两个盲区写在这里,别当成"看得见全部":① 本层刻意不挂 cfg_file 回调,**盘 → RAM 的覆盖不产生任何计数**,
|
||
未落盘的改值被一次重载无声吞掉照不到;② 三个数之间没有共同时刻,定性只按上表**成对读**,
|
||
需要时刻请在消费者侧自盖并冠 `obs_` 前缀(先例:喂狗点单写者 latch)。
|
||
|
||
---
|
||
|
||
## 11. 资源足迹(改容量前先看这里)
|
||
|
||
| 位置 | 量 | 说明 |
|
||
|---|---|---|
|
||
| `sample_ring.c` `s_ring[9][40]`(`float`) | **1440 B** | 周波数 / 通道数一改就成倍变化 |
|
||
| `phasor.c` `s_zone[2][5]`(`phasor_t`) | 160 B | 随实例数线性 |
|
||
| `freq.c` 相位增量环 `fq_d[96]` + 状态 | **≈ 520 B** | 随 `FREQ_AVG_TAPS` 变化 |
|
||
| `prot_set.c` `s_val[2][40]` + 封存 / `uint64_t` 位图 / 闭锁 | 336 B | 项数 32 → 40(B8)后的量 |
|
||
| `prot_elm.c` `s_rt[9]`(16 B / 实例) | 144 B | 实例 7 → 9(B8)后的量 |
|
||
| `adc_input.c` `s_stm32_raw[16]` + `s_tpafe_raw[8]` | 48 B | — |
|
||
| `adc_input.c` `s_stm32_dma[2×9]`(`uint16_t`) | 36 B | 必须在 `RW_IRAM1`(DMA2 不可达 CCM)→ `docs/子系统设计/ADC采样节拍设计说明.md` §4.3 |
|
||
| `adc_health.c` 内部状态 | ≈ 40 B | — |
|
||
| `adc_cal.c` 三个观测计数(`volatile uint32_t`) | **12 B** | 只增不清零、不参与判定;定义与读法见 §10.1 |
|
||
| 运行期栈 `phasor_run()` 的 `win[20]` | 80 B / 帧 | 抬高 `PHASOR_N` 直接抬栈 |
|
||
| 运行期栈 `freq_run()` 局部 | ≈ 30 B / 帧 | 无大局部数组 |
|
||
|
||
**改 `SAMPLE_RING_CYCLES` / 实例数 / `ADC_SEM_COUNT` / `FREQ_AVG_TAPS` / 栈之前后各跑一次**
|
||
`scripts/stack_report.py`,新增缓冲跑 `scripts/ram_report.py`(口径见 `mem-budget` skill);
|
||
adcTask 静态最坏链与余量的**台账在** `docs/内存管理/RAM使用分析与优化方案.md` §2.3,本表不代记台账。
|
||
|
||
---
|
||
|
||
## 12. 验证与判读
|
||
|
||
### 12.1 主机侧单测(生产源文件以 `#include` 原样纳入,不改产品代码)
|
||
|
||
| 模块 | 套件钉死的东西 | 细则 |
|
||
|---|---|---|
|
||
| `phasor` / `sample_ring` | 系数表逐项复核、幅值与相位口径、谐波抑制与提取、频偏、采样环时序、双区交换 | `docs/测试说明/相量层单元测试说明.md` |
|
||
| `adc_input` | 逐 Rank 注入互异码值钉死映射、HAL 启动失败与"本拍无新帧"分支、乒乓半区推导、gap / stall 分开计数 | `docs/测试说明/ADC输入层单元测试说明.md` |
|
||
| `adc_health` | 100 ms 静默门限两侧、丢帧边沿、序号倒退锁存、错误增量、tick 回绕、`NULL` 守卫、`init` 幂等 | `test/host/adc_health/` |
|
||
| `prot_set` | 范围校验、all-or-nothing 装载、封存-自检-闭锁、Q10 往返、元数据表自检、blob 往返与损坏拒绝、换代拒绝、`uint64_t` bad_mask | `test/host/prot_set/` |
|
||
| `prot_elm` | 滞回与定时限、欠量方向、投退、闭锁联动、时基回绕、位图聚合、测频接线与"必不启动值" | `test/host/prot_elm/` |
|
||
| `freq` | J1~J6(§7.2)+ 直流与谐波抑制 + UA→IA 源回退 + 守卫与幂等 | `test/host/freq/` |
|
||
| `adc_cal` / `run_par` | Q16 无舍入与合法带含端点、坏文件/坏段/坏零点回退、真 cfg 引擎往返、未标定闭锁单点,以及 §10.1 那三态观测计数(含"一次 save 覆盖多次整定"的回归锁) | `test/host/adc_cal/`、`test/host/run_par/`(用例清单在两个套件文件头,本文不复制条数) |
|
||
|
||
命令与对应 pre-commit 钩子名见 `App/algo/README.md` §4(钩子清单的属主是
|
||
`.pre-commit-config.yaml`,用例清单的属主是各 `test/host/<模块>/` 与上表细则文档——本文不复制数字,
|
||
免得三处打架)。
|
||
|
||
### 12.2 板端闭环
|
||
|
||
`@build` 0 错误 0 警告 → `@test` 看 `[ADC_TASK]` 首拍与心跳:`phasor(...)` 行、`freq(...)` 行
|
||
(`f=5000 valid=1`,`×0.01` 定点,MicroLIB 无 `%f`)、`ring` / `skip` / `adc1_*` / `tgap`
|
||
计数;健康判据 = **零 `[ERR]`**(`adc_health_report()` 的告警全部以 `[ERR]` 开头 → 误报会被闭环
|
||
直接抓住)。浮空工况应为 `valid = 0` 且读数为保持值。
|
||
|
||
### 12.3 定值档"换代"与"损坏"是两件事(易误判)
|
||
|
||
- `prot_set_store_load()` **返回 2 = 换代**(旧固件 / 旧项数的档被版本闸拒绝)→ 只打 INFO
|
||
`prot set blob version change, defaults in use`,**不记自检**;
|
||
- **返回负值 = 真损坏** → `[ERR] prot set blob CORRUPT` + 自检留痕。
|
||
|
||
升级固件后首启看到 "version change" 一行属**预期**,不要当故障排查;反过来,把真损坏读成
|
||
换代等于放过 NAND 数据事故——分流依据是返回码,不是日志文本。
|
||
|
||
---
|
||
|
||
## 13. 相关文档
|
||
|
||
- 目录索引与构建 / 测试入口:`App/algo/README.md`
|
||
- 「采样有效性校验」判定层深设计(判据推导 / 盲区 / 验证分工):`docs/算法/采样有效性校验设计说明.md`
|
||
- 分层与功能项清单(L1~L5 的属主登记处):`docs/算法/继电保护功能复现规划.md`
|
||
- 通道映射权威口径:`docs/硬件平台/ADC通道映射说明.md`
|
||
- 采样节拍与 DMA 搬运:`docs/子系统设计/ADC采样节拍设计说明.md`
|
||
- 傅氏算法与有效值口径:`docs/算法/傅氏算法详解.md` §2.3
|
||
- 主机单测细则:`docs/测试说明/相量层单元测试说明.md`、`docs/测试说明/ADC输入层单元测试说明.md`
|
||
- RAM 与栈台账:`docs/内存管理/RAM使用分析与优化方案.md` §2.3
|