Files
STM32F4-Base/App/algo/README.md
T
wandering1 c936441c83 门禁扩容:新增 lv-conf-sync、simulator/ 纳入全部钩子;含 L3 定值/元件框架落地
本轮把"自研代码落在门禁之外"的口子收掉,同时落地 LVGL 特性裁剪与
L3 保护定值/元件框架。

门禁(.pre-commit-config.yaml,26 个钩子)
- 新增 scripts/check_lv_conf_sync.py + 钩子 lv-conf-sync:比对两份
  lv_conf.h 的**生效值**(未定义则取 lv_conf_internal.h 兜底默认),
  抓 MISMATCH / LVGL 不认的宏名 / 同文件重复定义
- 顶层 exclude 删掉 simulator/:其下 21 个 tracked 文件全是本项目自研
  代码,生成物(build/、SDL2/、tests/ref/、shot*)已在 .gitignore;
  doc-links 的 exclude 同步删除
- lv-conf-sync 的 files: 覆盖两端配置 → 只改 simulator/lv_conf.h 也触发
  (pre-commit 4.x 已移除 always: true,实测只告警并跳过,无兜底开关)
- 新增 prot-set-unit-test / prot-elm-unit-test 两个主机侧单测钩子
- S11(2.3.5) 判定细化:禁的是"用路径找文件",尖括号系统头带子目录
  (如 <sys/stat.h>)不再误报;全仓库复核仅 2 处项目代码判定变化

模拟器代码整改(纳入钩子后暴露,全部修代码而非豁免)
- cppcheck 4 处:sim_syslog.c 判空分支内解引用 p_epoch(真实 bug)、
  2 处 struct tm 未初始化、1 处指针可加 const
- coding-standard 61 处:44 处函数左大括号并到右括号同行、8 处补函数
  注释、3 处补文件头注释块、2 处 __ 前缀保护宏改 XXX_H、2 处 sprintf
  改 snprintf(写入字节数与原来完全一致)

LVGL 特性裁剪(F-1/F-2/F-3)
- 两端 lv_conf.h 对齐同一特性子集:删 26 个 LVGL 9.3 不存在的选项名、
  3 处重复定义,10 项靠默认值隐式生效的改为显式固化
- 实测对镜像体积中性;价值在防漂移,且模拟器 golden 回归跑的正是固件
  实际编译的配置

L3 软件侧
- App/algo/prot_set.* Q10 定值模型(范围校验 / 整表 all-or-nothing /
  校验和封存-自检-闭锁)+ prot_elm.* 通用"滞回+定时限"元件框架 7 实例
- test/host/prot_set、test/host/prot_elm 主机侧单测;uvprojx 挂载两处

文档同步
- pre-commit使用说明:§2.8 S11 口径、§2.15 lv-conf-sync 详解、新增 §5.5
  复盘(教训:exclude 只写真正不该进钩子的路径)
- AGENTS.md、UI_设计架构 §1.4 第 10 条、RAM 分析、审查报告刷新与销项

验证
- @build BUILD_OK 0/0(Code=194276 RO=15872 RW=876 ZI=139892)
- 模拟器重建:菜单 8 状态像素逐字节一致(池 5368 B)、11 内容页黄金参考
  全过(往返 5448 B)、remo sink 0/25600 位差异、--logtest/--cfgtest PASS
- 全仓库 cppcheck / coding-standard / no-mojibake --all-files 全过
- check_doc_links --all 129/0;check_mem_numbers --check 内存数字一致
- 端到端:往 simulator/lv_conf.h 注入一行漂移 → 钩子 FAIL(exit 1)
2026-10-11 08:48:49 +08:00

20 KiB
Raw Blame History

App/algo/ — 采集与算法层(L1 / L2)

自研代码,属 docs/算法/继电保护功能复现规划.md §2.1 分层中的 L1(采集与预处理) 与 L2(相量计算)。原则:算法与 I/O、与调度分离——本层不建任务、不做保护判据、 不碰寄存器(唯一例外见 §3.1),只做"数"(原始码值 → 语义通道)与"相量" (采样序列 → 复相量),因而可被主机侧单元测试(L0.5)离线全覆盖(落实登记簿 A-4)。

任务侧(App/task/adc_task.c)只做消费与编排;任何新算法一律落在本目录,勿塞进 App/task/。

1. 文件清单(一眼版)

文件 层 作用(一句话) 主要对外入口 覆盖规划项
adc_input.h / adc_input.c L1 统一语义取数层:把两片转换器的原始码值按语义(UA…IN/温度)暴露,并消费 STM32 ADC1 的 DMA 乒乓双缓冲 adc_input_init() adc_input_poll() adc_input_raw() L1-2 / L1-3 / L1-4
adc_health.h / adc_health.c L1 采样有效性判定层:吃"计数快照"(帧序号/错误计数),产出静默/丢帧/单调性/错误增量故障位——判定与告警编排分离 adc_health_init() adc_health_check() adc_health_flags() L1-6
sample_ring.h / sample_ring.c L2 输入 采样环缓冲:按语义通道保存最近 2 个周波(40 点)采样值,供相量层"以当前时刻为终止"取窗 sample_ring_push_frame() sample_ring_window() L2 输入(为 L2-6 滑动窗供数)
phasor.h / phasor.c L2 全周傅氏相量计算:一周波 20 点单频 DFT → 基波/谐波相量(有效值口径),双区原子切换供无锁读 phasor_run() phasor_get() phasor_calc() L2-1 / L2-2 / L2-3 / L2-6 / L2-7
prot_set.h / prot_set.c L3 输入 保护定值模型:Q10 定点 RAM 表(通讯地址对照参考 g_tCommSetParaTab)+ 范围校验 + 校验和封存自检闭锁 prot_set_init() prot_set_load_raw() prot_set_get_f() prot_set_selfcheck() §5 定值模型 / L5-1
prot_elm.h / prot_elm.c L3 保护元件框架:通用"越限 + 返回系数滞回 + 定时限"核 + 数据驱动实例表(过流三段 / 过负荷 / 过压 / 欠压 / 零序过压) prot_elm_init() prot_elm_eval() prot_elm_run() prot_elm_active_mask() L3-1 / L3-3 / L3-4 / L3-6(软件侧)

依赖关系(自下而上,无环):adc_input.h(纯枚举 + 声明)← sample_ring ← phasor; prot_set 独立(仅 stdint.h);prot_elm ← prot_set + phasor。 sample_ring.c / phasor.c / adc_health.c / prot_set.c / prot_elm.c 零平台依赖 (只用 math.h / stddef.h / stdint.h 与本目录头); adc_input.c 另依赖 adc.h(hadc1)与 tpafe5160.h(快照),主机侧以桩头替身。

2. 数据流(一拍 = TIM3 1 kHz 节拍)

flowchart TD
    TIM3["TIM3 更新事件 1 kHz(PSC 83 / ARR 999)<br/>ISR: tpafe5160_start_conv_irq() + adc_tick_irq_notify()<br/>同一事件经 TRGO 触发 ADC1"] --> TPAFE["TPAFE5160 转换<br/>BUSY EXTI 收帧"]
    TIM3 --> ADC["STM32 ADC1 一轮 9 通道(≈67 us)<br/>DMA2_Stream4 循环模式自动搬运"]
    ADC --> PP["乒乓双缓冲 s_stm32_dma[2×9]<br/>半传输 / 传输完成回调各推进 seq"]
    TIM3 -. "ADC_WAKE_FLAG<br/>osThreadFlagsWait" .-> TASK
    TPAFE --> POLL
    PP --> POLL
    TASK["adcTask(事件驱动唤醒,非定时轮询)<br/>App/task/adc_task.c"] --> POLL["adc_input_poll()<br/>TPAFE 快照 + 消费刚完成的那半区(零忙等)"]
    POLL --> RAW["adc_input_raw(ch) × 9<br/>按语义取原始码值"]
    RAW --> RING["sample_ring_push_frame()<br/>整帧入环(40 点窗)"]
    RING --> RUN["phasor_run()<br/>逐实例 20 点重算 + 双区切换"]
    RUN --> GET["phasor_get(idx)"]
    GET --> CONS["消费者:adcTask 日志(现状)<br/>UI / 自检 / L3 保护任务(待接)"]

初始化落点:adc_input_init() 与 phasor_init() 由 App/app/app_main.c 的 app_main_init() 在内核启动前调用(三者 *_init() 均无 RTOS 依赖、幂等); phasor_init() 内部会连带 sample_ring_init(),等价于"刚上电"。

3. 逐文件说明

3.1 adc_input.c / adc_input.h — L1 ADC 输入层

解决的问题:V0.1 上 8 路电气量被两片转换器分片(硬件缺陷 N-18),且网名 AINn ≠ 板级 ADCn ≠ 转换器通道号(13/17 路置换)。若消费侧直接索引驱动缓冲,改版即全线错位。 本层把"哪一路"收进一张表,上层按语义取数、不写死下标。

API 干什么 注意
adc_input_init() 清缓冲与统计 + 映射表自检(每个 STM32 语义通道必须落在扫描 Rank 序列内)+ HAL_ADC_Start_DMA() 挂载乒乓缓冲(只挂一次) 内核启动前调用;-1 = 映射指向了从不扫描的通道(会恒读陈旧值);-2 = DMA 挂载失败
adc_input_poll() 采一拍:tpafe5160_snapshot() 原子快照(8 通道)+ 消费刚完成的那半区(stm32_take(),按 seq 奇偶选半区,零忙等) 仅任务上下文(快照走临界区);只搬码值,不做标定/运算;STM32 侧固定滞后 1 拍
adc_input_raw(ch, &out) 按语义读原始码值(TPAFE 16 位补码 / STM32 0..4095) 消费侧取数唯一入口;须先成功 poll();-2 = 该来源尚无有效数据
adc_input_desc(ch) 语义 → 物理通道描述(来源 + 下标/通道号) 供自检注入、UI 通道序;返回静态地址,勿改
adc_input_stm32_rc() STM32 侧最近一拍状态码(0 OK / -2 本拍无新帧) 心跳日志 adc1_rc
adc_input_stm32_seq() / _errs() / _gaps() / _hw_errs() DMA 整帧完成计数 / 停摆(连续 ≥2 拍无新帧)/ 丢帧(seq 一次前进 >1)/ HAL 错误回调 心跳日志 adc1_seq / adc1_err / adc1_gap / adc1_hw——四类成因不同,勿混读

要点

  • 语义映射表 k_adc_map[] 的唯一权威口径是 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)。v2.0 起这一轮由 TIM3 TRGO 硬件触发 + DMA 自动搬运, 消费侧零忙等(旧实现逐通道轮询、单通道超时 2 ms,已于 2026-10-10 移除)。 代价:STM32 侧数据固定滞后 1 拍(1 ms)——与 TPAFE 侧本来就没有同刻性(N-18),无新增误差。
  • 本文件是本层唯一允许出现 HAL 调用的地方(它必须驱动 ADC1);新增模块若不需要外设, 请保持 sample_ring / phasor 那种零依赖形态,才能免桩单测。

3.2 sample_ring.c / sample_ring.h — L2 输入采样环

解决的问题:相量计算要"以当前时刻为终止的一周波",而 L1 每次只给最新一帧。 本层按语义通道存最近 SAMPLE_RING_CYCLES = 2 个周波(SAMPLE_RING_DEPTH = 40 点), 与参考系统 CN_BUFSAM_CYCLE = 2 一致,并为后续加长数据窗(1.5~2 周波抗衰减直流)预留空间。

API 干什么 返回
sample_ring_init() 清全部通道历史与计数(幂等,内核启动前可调) —
sample_ring_push_frame(values, n) 整帧压入(长度须 = ADC_SEM_COUNT) 0 / -1 参数错
sample_ring_window(ch, n, out) 取该语义通道最近 n 点,按时间序旧 → 新(out[n-1] 最新) 0 / -1 参数错 / -2 点数不足
sample_ring_filled() 已累积点数(饱和于 40)——判"够不够一周波" 0..40
sample_ring_frames() 累计入环帧数(不饱和,1 kHz 下约 49.7 天回绕) 帧数

要点

  • 整帧写入后统一推进写指针 → 单帧内不可能"半新半旧",消费侧取到的窗永远落在整帧边界。
  • 本层只做按时序存/取,不做任何跨通道运算:同刻性由上游 L1 负责,本层既不保证也不假设。
  • frames() 与 L1 的 tpafe5160_frame_seq() 对照即可判丢帧(L1-6 的判据之一); 本层只提供计数,判定不在这里。
  • 上层若某通道本拍取数失败,应整拍跳过入环(现 adc_task 的 s_frames_skip 即此), 切勿补 0 点——0 会污染相量窗。

3.3 phasor.c / phasor.h — L2 相量计算

算法:全周傅氏(单频 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) 读使用区结果 0 / -1 参数错 / -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 自身, 该机制为后续独立 L3 保护任务预留。
  • 任一实例数据不足一周波 → 本拍整体不产出(不切换使用区),避免"部分新部分旧"的混合结果。
  • 每拍 20 点全部重算(暴力法,无累积误差;迭代加减首尾项会累积)。相量刷新周期 = 1 ms。
  • 相位(L2-4)未常驻结构体,需要时自行 atan2f(fIm, fRe)。

3.4 adc_health.c / adc_health.h — L1-6 采样有效性判定

解决的问题:驱动侧只有"观测计数"(tpafe5160_frame_seq/_frstdata_err、 adc_input_stm32_seq/_errs/_gaps/_hw_errs),没有判定——芯片静默、消费者超拍丢帧、 计数器被改写这些故障此前只能靠人读心跳日志发现。本层把判定收进纯逻辑, 告警编排留在消费者(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(锁存) 为 L5-4 闭锁预留
adc_health_sticky() 锁存位(含 FRSTDATA / STM32_ERR / HW_ERR 增量痕迹) 自愈后仍留痕
adc_health_tpafe_gaps() TPAFE 累计丢帧点数(序号前进 >1 的超出部分) 与 STM32 侧 adc_input_stm32_gaps() 对称

要点

  • 三类语义严格分开:电平位(stall,可自愈)/ 锁存位(出现过必留痕)/ 边沿事件位 (仅当拍有效)。ADC_HE_FRSTDATA/STM32_ERR/HW_ERR 与同名 ADC_HF_* 编号对齐, 消费者直接用事件掩码对"已留痕位图"做每类一次 SYSLOG_SELFTEST 的编排。
  • gap 不告警:消费者超拍(心跳日志 ~2 ms)是已知现象,adc1_gap/tgap 每 10 s 心跳 可见;若按事件告警会把真故障淹掉(台架实测开机 adc1_gap≈24、稳态每 10 s +2)。
  • 静默 ≠ 丢帧:d=0 才是静默候选;d>1 只说明消费者没跟上,生产者还活着——两者 处置完全不同,判据不许混。
  • 告警策略(在 adc_task.c,不在本层):严重事件/电平上升沿 DBG_ERROR + SYSLOG_SELFTEST(每类每次开机一次,防刷屏写满 32KB selftest.log);下降沿 DBG_INFO。

3.5 prot_set.c / prot_set.h — 保护定值模型(P1 / L5-1 载体)

解决的问题:保护判据的"输入宪法"。参考系统把定值放 EEPROM + tagPRS 常量表 校验和自检;本层先做 RAM 模型(cfg_file 持久化属 P4),把编号 / 通讯地址 / 范围 / 默认值 / 校验和闭环钉死。

API 干什么 注意
prot_set_init() 装默认值 + 封存校验和(幂等) 内核启动前可调
prot_set_load_raw(vals) 整表装载(P4 起 cfg_file 调用),逐项范围校验 all-or-nothing:任一项越界整表不装载,prot_set_bad_mask() 指认越界项
prot_set_set(id, q10) 单项写入(范围校验) 不自动重封存——批量改完必须 prot_set_seal(),否则 selfcheck 判"非法改写"
prot_set_get_f() / get_ctl() 工程量浮点 / 投退读取 单字读原子
prot_set_selfcheck() 重算 sum32 与封存正/反码比对,失配 → 闭锁 L5-1;prot_set_seal() 是解锁的唯一合法路径

要点

  • Q10 定点(×1024)与参考 2^10 折算口径一致;元数据表(地址/范围/默认)是 ROM 常量,校验和只覆盖 RAM 值区。
  • 通讯地址逐项对照参考 Comm/Pub/Const_CommSetPara.c g_tCommSetParaTab (基址 0x8820);单测 TC-PS-006 保证地址不重复、min ≤ def ≤ max、投退项 0..1。
  • ⚠ 默认值/范围为拟定值(参考 EEPROM 出厂表不在本仓库),待对照修订—— 见规划 §5.4 落实进度注。

3.6 prot_elm.c / prot_elm.h — 保护元件框架(L3,P1 子集)

解决的问题:参考系统 89 个 PRS_*.c 各自实现"滞回 + 定时限"——本框架把这段 公共核收敛为一个通用核 + 数据驱动实例表(同 phasor 的 k_inst 模式), 新增元件 = 表加一行 + 定值加几项。

API 干什么 注意
prot_elm_init() 复位状态 + 解析相量实例索引(按名前缀 C01/C02/C03) 须在 phasor_init() 之后调
prot_elm_eval(now_ms, meas) 纯判据:定值 RAM + 测量数组 → 全元件状态机 主机单测入口;NULL 测量按 0
prot_elm_run(now_ms) 适配层:phasor_get() 组测量 → eval 任一相量未就绪 → 整拍跳过(不引入陈旧判据)
prot_elm_state() / prot_elm_active_mask() 状态快照 / 动作位图 供 L4 出口与心跳日志

要点

  • 滞回:已启动用返回门槛(定值 × 返回系数),未启动用动作门槛—— 过量/欠量方向由实例表 over 位统一处理(UV 是唯一欠量实例)。
  • 定时限:(uint32_t)(int32_t)(now - t0) >= dly 差值法,tick 回绕安全; dly=0 → 启动同拍即动作。返回即时(参考带 10 ms 返回延时,P4 接出口硬件时再补)。
  • 闭锁联动:prot_set_locked()(L5-1)或投退=0 → 该元件状态整体复位、不判据。
  • V0.1 实例(N-18):OC1/OC2/OC3/OL(IA)、OV/UV(UA)、3U0(UN 外部通道)。 零序过流(IN 走 STM32)与过频/低频(待频率算法)故意不在表中。
  • 尚未接线:无任务调用 prot_elm_run()(链接器整对象移除,零足迹); 板端接线随 protectTask 立项(栈预算 + IWDG 心跳门禁,AGENTS E-59 同款流程)。

4. 关键约定

  • 语义映射唯一权威口径:docs/硬件平台/ADC通道映射说明.md。取数一律经 adc_input_raw(), 禁止在消费侧直接索引驱动缓冲或写死下标。
  • 禁止跨转换器运算(V0.1 硬件缺陷 N-18):UA/UN/IA 在 TPAFE、UB/UC/IB/IC/IN 在 STM32 ADC1, 两者无共同采样触发 → 无同一时刻性、相位不确定。故本层不做跨转换器的相位 / 功率 / 功率因数 / 负序零序类矢量运算(V0.2 把 8 路电气量全接 TPAFE 后方可解禁)。
  • 标定(L1-5)尚未实现(缺标准源):现以原始码值充当工程量(系数 = 1), 因此相量读数只证明链路通,不可作幅值精度判据。
  • 本层不建任务、不等事件、不写日志之外的副作用;日志由调用侧(adcTask)打。
  • 内核启动前只允许调 *_init();adc_input_poll() / phasor_run() 仅任务上下文。

5. 静态足迹(BSS,供栈/RAM 预算参考)

位置 量
sample_ring.c s_ring[9][40] float 1440 B
phasor.c s_zone[2][5] phasor_t 160 B
adc_input.c s_stm32_raw[16] + s_tpafe_raw[8] 48 B
adc_health.c 内部状态(9 × uint32 + uint8) ≈ 40 B
prot_set.c s_val[31] + 封存/位图/闭锁 136 B(未接线前被链接器移除,实测 0)
prot_elm.c s_rt[7](16 B/实例) 112 B(同上,实测 0)
运行期栈:phasor_run() 的 win[20] 80 B/帧

改容量(SAMPLE_RING_CYCLES / 实例数 / ADC_SEM_COUNT)前后各跑一次 & '.venv\Scripts\python.exe' scripts/stack_report.py(口径见 mem-budget skill)。

6. 构建与测试

  • 挂载:七个 .c 已登记在 MDK-ARM/STM32F407-Demo.uvprojx,../App/algo 已在 <IncludePath> 内。 本层不进 simulator/CMakeLists.txt(UI 模拟器不跑采集链路)。

  • 主机侧单测(L0.5)——生产源文件以 #include 原样纳入,无需改产品代码:

    套件 命令 pre-commit 钩子
    相量层(57 用例:系数表逐项复核 / 幅值·相位 / 谐波抑制与提取 / 频偏 / 采样环 / 双区交换) & '.venv\Scripts\python.exe' test/host/phasor/run_unit_test.py phasor-unit-test
    ADC 输入层(逐 Rank 注入互异码值钉死映射 + HAL 启动失败/超时分支) & '.venv\Scripts\python.exe' test/host/adc_input/run_unit_test.py adc-input-unit-test
    有效性校验(31 用例:100 ms 静默门限两侧 / 丢帧边沿 / 倒退锁存 / 错误增量 / tick 回绕 / NULL 守卫 / init 幂等) & '.venv\Scripts\python.exe' test/host/adc_health/run_unit_test.py adc-health-unit-test
    定值模型(30 用例:范围校验 / all-or-nothing 装载 / 封存-自检-闭锁 / Q10 往返 / 元数据表自检) & '.venv\Scripts\python.exe' test/host/prot_set/run_unit_test.py prot-set-unit-test
    元件框架(27 用例:滞回与定时限 / 欠量方向 / 投退 / 闭锁联动 / 时基回绕 / 位图聚合) & '.venv\Scripts\python.exe' test/host/prot_elm/run_unit_test.py prot-elm-unit-test

    改本目录任一 .c/.h,提交时对应钩子自动跑;详见 docs/测试说明/相量层单元测试说明.md。

  • 板端:@build 0 错误 0 警告 → @test 看 [ADC_TASK] 首拍/心跳日志(phasor(...) 行、 ring / skip 计数、adc1_rc / adc1_err、tgap);健康判据 = 零 [ERR] (adc_health_report 的告警全部以 [ERR] 开头 → 误报会被闭环直接抓住)。

7. 新增一个算法模块的固定动作

  1. 在本目录建 xxx.c/h,文件头写模块注释块(规范 V1.0),尽量零平台依赖以便免桩单测。
  2. 用 new-source-file skill 注册到 MDK-ARM/STM32F407-Demo.uvprojx(目录已在 IncludePath, 若新建子目录则必须补 IncludePath)。
  3. 加 test/host/<模块>/ 主机单测 + .pre-commit-config.yaml 钩子(照 phasor-unit-test 抄)。
  4. 消费者写在 App/task/,本层只出"数";跨转换器运算的禁令见 §4。

8. 相关文档

  • 通道映射权威口径:docs/硬件平台/ADC通道映射说明.md
  • 分层与路线图(L1/L2 清单与状态):docs/算法/继电保护功能复现规划.md
  • 傅氏算法与有效值口径:docs/算法/傅氏算法详解.md
  • 采样节拍(TIM3 1 kHz + TRGO 触发 ADC1/DMA):docs/子系统设计/ADC采样节拍设计说明.md
  • 相量层单测细则:docs/测试说明/相量层单元测试说明.md