# 故障录波设计说明(1 kHz · 8 路原始码值 · 现场单通道回放) > **模块名称**:故障录波——采样源沿用 TIM3 1 kHz 节拍,专用录波环(拟新增 `App/algo/wave_ring.c`)、触发、落盘(defaultTask 是文件系统唯一写者)、取回(FTP 21 端口)、现场回放(UI 单通道页)。 > **文档定位**:本波的**设计属主**——口径、通道集、数据形态、触发模型、文件格式、预算与所有权边界、显示契约、二期不返工的结构约束在此定稿。**排期与卡片判据在 `docs/任务规划/故障录波.md`**,本文不写排期。 > **状态**:P0 设计定稿(2026-10-29),**代码一行未动**。三条已裁定口径见 §1.2。 > **相关文档**: > `docs/子系统设计/ADC采样节拍设计说明.md`(节拍与时序账的属主;本文 §3 的数据源就是它) > `docs/硬件平台/ADC通道映射说明.md`(网名 `AINn` 不等于板级 `ADCn`,也不等于转换器通道号;链路分段与削顶归属) > `docs/任务规划/数据可信.md` §4.6(动态波形验证判据;录波落地后它的读数出口升级,见 §11) > `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1(FatFs 两条实证陷阱与 8.3 短名限制) > `docs/内存管理/内存内容分配策略.md` §6 与 R6 规则(新增静态缓冲大于 4 KB 的前置动作) > `docs/UI设计/内容页架构.md`、`docs/UI设计/RemoDispBus协议与接收设计说明.md`(回放页与远端看图的能力边界) ## 目录 - `§1` 三条已裁定口径:1 kHz、8 路全录、一屏一路 - `§2` 与产品需求 80 点/周波口径的偏离,与二期不返工的四条结构约束 - `§3` 录什么:通道归属、削顶类标记、为什么存原始码值 - `§4` 触发模型与段长冻结语义 - `§5` 四个所有者与三条并发陷阱 - `§6` 段文件字节布局 - `§7` 预算(R6 是硬前置) - `§8` 现场回放显示契约 - `§9` 观测:只加一条周期行 - `§10` 测试与门禁,含 bench 自抓红线 - `§11` 与数据可信任务卡的三处口径变更 - `§12` 风险 `§13` 范围边界 `§14` 与其它文档的关系 --- ## 1. 这一波要解决什么 ### 1.1 两个动机,一个数据面 | 动机 | 谁提的 | 要求 | |---|---|---| | **现场能大概看一眼波形** | 产品(用户裁定 2026-10-29) | 装置上按几下就能看「这一路现在是不是正弦、削没削顶」,不必插串口、不必带笔记本 | | **台架能把样本拿出来对表** | `docs/任务规划/数据可信.md` §4.6 | 把板上采到的原始样本导出,在 PC 上独立重算全周傅氏,与板上派生读数逐个对——这才是「验证我们后面的各种计算」唯一硬的做法 | 两条共用同一个数据面:**录波环 + 段文件 + 取回通路**。区别只在「谁来触发、给谁看」。分开做会变成两套导出机制,所以并成一件。 ### 1.2 三条已裁定口径(本文其余章节都以此为前提) | # | 口径 | 裁定 | 代价与换来什么 | |---|---|---|---| | 1 | **采样率 = 1 kHz(20 点/周波)** | 复用现有 TIM3 节拍,不改采集 | 与产品需求的「80 点/周波(4 kHz)」口径存在**已登记偏离**(§2);换来的是采集主线零改动——不动 `Src/tim.c`、不动 CubeMX、不动相量与测频 | | 2 | **8 路电气量全录** | 五路削顶也照录 | 数据不筛选,现场看见的是真实形状;代价是文件必须自带「这一路可信吗」的标记(§3.1 与 §6.2),否则削顶会被读成「系统里真有零序」 | | 3 | **一屏只画一路,支持 8 路切换** | 160×160 单色屏画不出多路 | 不需要图例(本来也没有颜色);8 路切换用方向键在同一页内换通道,**不新增菜单叶子**(§8.2) | --- ## 2. 与产品需求口径的偏离(显式登记,不许静默降级) 产品需求 `docs/产品需求/产品需求设计文档.md` 的「录波采样率」行写着:录波 = 20 × 4 = **80 点/周波(50 Hz 下 4 kHz)**,理由也写在那一行——「保护计算用 20 点/周波,录波要 4 倍过采样才能看清波形」。本波只做 20 点/周波,是**明知故犯的偏离**,把理由与代价写在明处: | 项 | 内容 | |---|---| | 为什么先做 1 kHz | 抬到 4 kHz 的第一块骨头不在录波,而在采集主线:一拍时序账要重算(现在 TPAFE 转换约 23 µs、EXTI 读 8 通道约 0.5 µs、ADC1 第 9 点约 67 µs、读窗与下次 CONVST 相隔约 977 µs,见 `docs/子系统设计/ADC采样节拍设计说明.md` 的时序账),半区语义与「恒定小偏移」都要重推;算法层还有两处按 1 kHz 写死的假设(`App/algo/phasor.h` 的 `PHASOR_N 20U`、`App/algo/freq.h` 的 `FREQ_FS_HZ 1000.0f`)会被一起牵动 | | 芯片支持吗 | **支持**:TPAFE5160 是 16 bit / 350 kSPS 全通道同时采样(`docs/芯片手册/TPAFE5160.md` 首行)→ 4 kHz 是纯固件与算法的工程账,不是硬件上限 | | 1 kHz 能看到什么 | 基波形状、削顶、直流偏置、明显畸变、频率(数过零)。**看不到**:2/5 次谐波的形状细节(每周期只剩 10 / 4 个点)、暂态前沿 | | 偏离登记 | 落码时在产品需求那一行补一句指向本文;「看清波形」这一目标在 1 kHz 下**只算部分达成**,不许拿 1 kHz 的图当满足 80 点口径的证据 | ### 2.1 二期不返工的结构约束(本波就写进代码注释) 将来抬到 4 kHz 时**只允许加、不允许改**的四条规则,本波先立着: 1. **录波环按「全速原始序列」建模**:「每周波点数」(现 20)与「采样率」(现 1000)是**写进文件头的数据**,不是编译期假设——回放端与 PC 脚本一律从头里读; 2. **算法层永远看 1 kHz 视图**:抬频时在 ADC 侧加 4:1 抽取喂 `sample_ring` 与 `freq`,`PHASOR_N` 与 `FREQ_FS_HZ` 不许被改; 3. **录波环与 `sample_ring` 解耦**(各有独立缓冲与指针,见 §5.1),二期全速只扩录波环,不牵动相量输入; 4. 文件格式的版本号字段本波置 1,二期换格式时递增,PC 侧按版本分派。 --- ## 3. 数据源:录什么、从哪一帧录 ### 3.1 语义通道顺序与转换器归属(不可混为一谈的两类) 语义枚举与归属见 `App/algo/adc_input.h`(`ADC_SEM_UA` 到 `ADC_SEM_IN`,再加 `ADC_SEM_TEMP` 与 `ADC_SEM_COUNT`): | 通道 | 转换器 | 位宽与量程 | 在录波里的含义 | |---|---|---|---| | UA / UN / IA | TPAFE5160 | 16 bit,±5 V **双极性** | **可信类**:能录出真实的双向工频波形 | | UB / UC / IB / IC / IN | STM32 ADC1 | 12 bit,0 到 3.3 V **单极性、无中点偏置** | **形状受限类**:交流必然把负半周钉在下轨。录它是为了如实呈现 V0.1 现状,不是为了当工频量用 | | TEMP | STM32 ADC1 | 12 bit | 可选附带单点(非线性未折算,`docs/任务规划/数据可信.md` §7 已定案) | 两条硬件事实决定回放能做到什么,**必须进文件头,也必须进页面**: - **跨转换器无共同采样触发**(V0.1 硬件缺陷):8 路分属两片转换器,没有同一时刻性 → **禁止把 U 与 I 叠在同一张图上比相位**。文件头按通道写「来源转换器 + 采样时刻偏移」;回放只画单通道,这不是妥协,是这条硬件事实逼出来的形态; - **STM32 侧另有 1 拍乒乓滞后**(`App/algo/adc_input.h` 的取数口径注释)→ 同一转换器内也不是严格同刻。 ### 3.2 存原始码值,不存折算工程量(本波最重要的一条数据形态决策) | 理由 | 说明 | |---|---| | 现在根本没有工程量 | 两点标定未做,`adc_cal_flags()` 的 `ADC_CAL_F_VALID` 未置位 → `adc_cal_prot_blocked()` 返 1,帧值仍是引脚域电压(`docs/任务规划/数据可信.md` §4.3)。「录波必须先有工程量」这条旧前提被本条推翻 | | 绕开测频幅值门 | `App/algo/freq.h` 的 `FREQ_AMP_MIN 50.0f` 以二次值 V 为口径,`eng=0` 期间帧值远小于它恒不过门 → `freq_valid()=0`。**录波取的是这道门之前的原始帧**,所以不受影响,动态验证才有样本可对 | | 最省且可追溯 | int16 码值 2 B/点;标定完成后不必重录,PC 端拿头里的 `k` / `b` 就能把老文件重算成工程量 | | 诚实 | 文件里装的是「板子看见的东西」,不是「我们希望它看见的东西」 | **文件头因此必须带**:每路的来源转换器、位宽、LSV 用 µV 计的定点值(TPAFE 152.59 µV、STM32 805.66 µV)、封存瞬间的 `eng` 档位、折算 `k` / `b` 快照、有效性位图与削顶位图。缺任何一项,老文件在新固件上就会被解释错。 --- ## 4. 触发模型 ### 4.1 四种触发,本波做前三种 | 触发 | 来源 | 本波 | |---|---|---| | **手动** | UI「手动录波」操作页(该叶子早已在菜单里:`ui_menu_tree.txt` 装置维护组,现未绑页) | 做 | | **保护启动** | `App/algo/prot_elm.c` 的 `picked` 上升沿(元件启动),与 `/log/start.log` 同源,避免两处各自判定 | 做(复用事件点,不新增判据) | | 定周期自抓 | `DTU_BENCH` 构建下每 N 秒强制一段,供信号发生器对表 | 做,**只在 bench 构建**,产品构建无此路径 | | **突变量启动** | 需要增量化 `Δi` 类算法与定值 | 不做,归二期(`docs/算法/继电保护功能复现规划.md` 的「故障启动(波形启动)」行) | ### 4.2 段长与冻结语义(RAM 就靠这一条省下来) - 一段 = **预触发 8 周波 + 后触发 8 周波 = 320 点/路**(1 kHz 下 320 ms); - **触发即冻结环**:停止推进录波写指针,defaultTask 直接从已冻结的环落盘,落完解冻; - **冻结期间丢的是录波样本,不是采集**——`sample_ring`、相量、保护判据照常跑(这就是 §2.1 第 3 条要求解耦的原因),冻结不拖慢保护动作,也不让 104 断数; - 代价:一次触发的落盘窗口(约几十 ms)内再来触发只记第一次(冷却期),**不做多段排队**——那是二期「6 段 / RAM 16 个」的形态。 ```mermaid flowchart TD IDLE[空闲: 录波环照常滚动写入] --> ARM{触发源有效?} ARM -- 无 --> IDLE ARM -- 手动 / 保护启动 / bench 定时 --> FRZ[冻结环: 停止推进写指针] FRZ --> POST[后触发计满 8 周波] POST --> PACK[封段: 写文件头与通道表] PACK --> W[defaultTask 落盘到 /wave 8.3 短名] W --> OK{写成功且字节数相符?} OK -- 否 --> ERR[记 wave: 失败日志, 丢弃本段] OK -- 是 --> MARK[段计数与最新段号入快照] ERR --> REL MARK --> REL[解冻: 恢复滚动写入] REL --> COOL[冷却期: 期间新触发只计数不重录] COOL -. 计满 .-> IDLE COOL -. 期间又来触发 .-> SKIP[丢弃: 溢出计数加一] SKIP --> COOL ``` 图内不写步骤号;冻结 → 后触发 → 封段 → 落盘 的四段动作在 §4.2 与 §5 正文里。 ## 5. 所有权与并发边界(这块最容易做错) ### 5.1 谁写谁读 | 角色 | 职责 | 不许做 | |---|---|---| | **adcTask**(生产者) | 每拍把 8 路原始码值(可带 TEMP)压进录波环;查触发标志;触发时冻结 | 不碰 FatFs、不碰网络 | | **protectTask** | 在 `picked` 上升沿置一个**请求标志**(原子置位,与 `prot_elm_request_reset()` 同一模式) | 不写环、不落盘 | | **defaultTask**(文件系统唯一写者) | 排空已封存的段 → `f_write` → 校验字节数 → 更新段账 | 不读活缓冲,只读**已封存**区 | | **uiTask**(回放页) | 只读窗口取段数据 → 自绘(与 `App/log/soe_store.c` 读侧同构) | 不写文件 | | **netTask / lftpd** | 把 /wave 暴露给 FTP 取回 | 不在 netTask 里读录波环 | ### 5.2 三条已知陷阱,本波必须绕开 | 陷阱 | 事实出处 | 本波处置 | | | ------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | --------------------------------------------------------- | | **取窗竞态** | `App/algo/sample_ring.c` 的 `sample_ring_window()` 没有临界区(读 `s_head` 后逐点拷),现在只有 adcTask 同任务调用才安全 | 录波**不复用它**:adcTask 在自己的节拍里取「本拍帧」压专用环;跨任务只传递「已封存」标志,杜绝跨任务读活缓冲 | | | **FatFs 追加写** | 该移植 `FA_OPEN_APPEND` 不含 `FA_WRITE`,且 `f_write` 簇分配失败时返回 `FR_OK` 加短写、无错误码(`docs/架构设计/STM32F4-Base存储架构说明.md` §10.1) | 打开一律 `FA_WRITE | FA_OPEN_APPEND`;**判成功必须比对写回字节数**,短写按失败处理(丢段 + 日志),绝不当成功记账 | | **只能 8.3 短名** | 出处同一张存储架构说明的短名限制:`FF_USE_LFN=0`,不得以点开头、不得长名 | 文件名 = WAVE 加两位十进制再加 .BIN,共 100 个名字轮转覆盖;目录 /wave(与 /log 平级,FTP 根下可见) | | ![录波数据的四个所有者与跨任务边界:adcTask 只写环,defaultTask 独占落盘,uiTask 与 PC 只读](../images/故障录波数据流.svg) 图由 `tools/arch_diagram/gen_wave_record_flow_svg.py` 生成(改图改脚本重跑,勿手改 SVG)。 取窗竞态的原因: ![image.png](https://lsky.bitnasdaq.vip/5rJRrZ.png) --- ## 6. 段文件格式 ### 6.1 本波为什么不用 COMTRADE | 判据 | 结论 | |---|---| | 谁要用 | 本波消费者只有两个:装置自己的回放页、仓内一个 PC 脚本——**没有任何第三方要求 COMTRADE** | | 代价 | COMTRADE 要「配置 + 数据」两份文件(扩展名 CFG 与 DAT)、ASCII 通道表、时标与采样率段;FatFs 只有 8.3 短名,两套文件名要各占一个号,段数一多就不够分;多段与时标在 Flash 余量里换不来可读性收益 | | 归属 | 送检或第三方交接时才需要 → 二期(`docs/问题记录/采集与数据处理短板评测.md` 的记录链缺口行已登记 COMTRADE) | ### 6.2 段文件布局(小端;文件名 WAVE 加两位十进制) | 偏移 | 字段 | 宽度 | 说明 | |---|---|---|---| | 0 | magic | 3 | 前两字节固定 WR,第三字节是版本号(本波写 1);版本号并进 magic,换格式即变(§2.1 第 4 条) | | 3 | ver | 1 | 恒 1 | | 4 | fs_hz | 2 | 采样率,本波恒 1000 | | 6 | pts_per_cycle | 1 | 每周波点数,本波 20 | | 7 | ch_count | 1 | 8(带 TEMP 则 9) | | 8 | pts_per_ch | 2 | 320 | | 10 | pre_pts | 2 | 160(预触发点数,回放画触发光标用) | | 12 | trig_src | 1 | 1 是手动,2 是保护启动,3 是 bench 定时 | | 13 | trig_elm | 1 | 触发元件序号(`prot_elm` 实例下标;手动与定时为 0) | | 14 | t0_ms | 4 | 段首相对上电时刻;配合墙钟还原绝对时刻 | | 18 | wall | 4 | 对时后的 epoch 秒(未对时为 0) | | 22 | eng | 1 | 封存瞬间的折算档位:0 引脚域、1 二次值、2 一次值 | | 23 | flags_seq | 1 | 有效性位图(引 `adc_cal_flags()` 口径) | | 24 | clip_mask | 2 | 削顶归属(V0.1 恒 0xE6,见 `docs/任务规划/数据可信.md` §2.7) | | 26 | frame_seq | 4 | 段首帧序号,可与串口周期行的 frames 对账 | | 30 | 通道表 | 每路 12 | 语义号 1 + 来源转换器 1 + 位宽 1 + 有效位 1 + LSB(µV) 4 + k 2(定点)+ b 2(定点) | | — | CRC16(头) | 2 | 与屏板链路同一张 CRC-16/MODBUS 表,不新造 | | — | 数据区 | pts_per_ch × ch_count × 2 | **按通道分列**:一路连续 320 点,单通道回放只读那一路 | | — | CRC16(数据区) | 2 | 长度为 0 或 CRC 不符 → 整段作废,页面必须显示「该段不可读」 | **为什么按通道分列而不是按帧交错**:回放页只画一路 → 只需连续读那一路;PC 重算全周傅氏也是逐路独立。交错布局两种用法都要跨步长取数,白付代价。 --- ## 7. 预算(先算后写,R6 是硬前置) | 项 | 算式 | 结果 | |---|---|---| | 录波环 | int16 × 320 点 × 8 路 | **5,120 B** | | 环索引与状态 | 头尾指针、封存标志、段计数、冷却计数 | 约 32 B | | 落盘搬运缓冲 | 256 B × 2(分次写,不做整段驻留) | 512 B | | 回放页读窗 | 256 B(与 `App/log/soe_store.c` 读窗同量级) | 256 B | | **合计静态增量** | — | **约 5.9 KB** | - **大于 4 KB ⇒ 触发 R6**:`docs/内存管理/内存内容分配策略.md` 的 R6 要求「任何新增静态缓冲 >4 KB 必须先核 §6 台账余量,改后同步 §6 数字、.sct 头注释与 AGENTS 栈表」;加大缓冲本身属 **Ask-first**; - **落位 = SRAM1,不进 CCM**:CCM 受管区已 87.9 %、只剩 7,904 B,SRAM1 为 62.2 %(数字出自 `docs/内存管理/RAM预算台账.md` §1 受管区表);录波环不是 DMA 可见对象,没有「CCM 不可达」的约束,但 CCM 也**没有余量**给它; - **UI 子系统配额早已超**(同一张表的子系统行)→ 回放页的描述符与名表仍要在台账记账,别拿「反正已超」当免检; - 前后各跑一次 `scripts/ram_report.py` 与 `scripts/stack_report.py`(defaultTask 多了落盘链、uiTask 多了读窗与自绘);`mem-budget` 钩子是总量红线,`ram_baseline.py --update` 回填趋势; - **Flash**:段格式编解码 + 状态机 + 回放页 + 一条日志行,粗估 3 到 5 KB(Flash 基线在 25 % 量级,不是瓶颈); - **NAND**:一段约 5.2 KB 数据加头。轮转 100 段约 0.5 MB,对 256 MB 单分区是零头——**瓶颈不是容量,是写次数**(§10 红线)。 ## 8. 现场回放显示契约 ### 8.1 一屏画什么 单通道,8 路切换(上下键切通道,左右键切段),页面元素: | 元素 | 内容 | 为什么这么定 | |---|---|---| | 顶部 | 通道名 + 段号 + 触发源缩写(M 手动 / P 保护 / T 定时) | 一眼知道「哪一路、哪一段、怎么来的」 | | 波形区 | 约 120×90 px,画 **2 个周波(40 点)**,**阶梯连线**(每点占 3 px 宽) | 1 bpp 下平滑折线的平缓段会被阈值吃掉(`Drivers/BSP/UI/ui_deco.h` 的实测口径:斜线边缘易被吃、须加粗);正弦的过零段正是最平缓的部分。阶梯线每段非水平即垂直,恰好绕开这条,而且更贴近「这是离散采样」的事实 | | 参考线 | 0 线(虚线)+ 峰值标注(定点 mV) | 没有 0 线就看不出直流偏置与削顶 | | 底行 | 单位徽标:引脚域 / 二次值(取头里 `eng`) | 与 UI 交流量页第 7 行同一诚实性口径(`docs/UI设计/页面扩充详细方案.md` §3.1.1 的落地注) | | 徽标 | 该路属「形状受限」类时右上打「削」 | 让钉下轨不被读成系统故障 | | 空态 | 「无录波段」/「该段不可读」 | CRC 不符或零长度必须显式说;**画成一条直线等于伪造证据** | ### 8.2 控件与菜单:两条省事且省 RAM 的裁定 - **不开 `LV_USE_CHART`**:它在 `Lib/lv_conf.h` 与 `simulator/lv_conf.h` 两份里都是 0,开它要过 `lv-conf-sync` 同步门禁、吃 Flash 与 RAM,而 UI 子系统配额已超;内容页框架本身是「一次性自绘、无子对象」,绘制链尾就是页面的 `draw_cb`(`docs/UI设计/内容页架构.md` 的「什么是内容页」一节),自绘线条有先例(`ui_deco`); - **显示页要新增一条菜单叶子**(「录波回放」,放记录查询组),触发入口仍用装置维护组那条既有的「手动录波」叶子。新增叶子会改菜单树叶数与页面绑定口径:落地时必须同步 `docs/UI设计/页面补齐规划.md` 与 `docs/任务规划/UI补齐.md` 的叶数闭合,重跑 `tools/menu_gen/gen_menu.py`(`generated-fresh` 逐字节),新增汉字走中文字库链(`font-charset`)。菜单树与字库都是整文件生成物 → **UI 道单写者,同刻只允许一条道写**(`docs/任务规划/README.md` §1 的执行纪律)。 ### 8.3 触发入口与回放页的分工 | 页 | 干什么 | 归哪张卡 | |---|---|---| | 「手动录波」操作页 | 确认即触发一段,形态沿用已规划好的操作页确认模式(`docs/UI设计/页面补齐规划.md` §二 第 26 叶) | 录波 P2 | | 「录波回放」页 | 列段 → 选通道 → 画单通道 | 录波 P3 | ### 8.4 现场之外,PC 侧必须同时具备 - **取回**:FTP 拉 /wave/WAVE 段文件(lftpd 与 config.ini 的 ftp 段鉴权现成,无需新代码); - **PC 脚本**:仓内新增一个纯标准库脚本(与 `test/host/iec104_master.py` 同族风格)——读段、画图、重算全周傅氏,输出「板上报数 vs PC 重算」对照表。**这才是 `docs/任务规划/数据可信.md` §4.6 那套判据想要的东西**; - **远端看图不是波形通路**:RemoDispBus 一帧 3204 B、最坏约 4 s(`Drivers/BSP/UI/ui_net_sink.h` 的阻塞口径),只适合拍一张回放页的屏做留证。 --- ## 9. 观测(日志不是免费的) 新增**一条**周期行,不铺开: wave: seg=NN armed=1 src=P pts=320 err=N ovf=N drop=N - `err` 是落盘失败或 CRC 自弃次数(**字节数不符就算失败**,见 §5.2);`ovf` 是冷却期丢弃的触发数;`drop` 是冻结窗口丢掉的录波点数; - 只在原先静默的黑洞处补锚点,别重复已有步骤——每条 DBG 日志约 90 B Flash 加 216 B 栈(`AGENTS.md` 的日志与工具链节); - 该行落地时同步进 `docs/测试说明/ADC前端注入验证流程.md` 的读数出口表。 --- ## 10. 测试与门禁 | 层 | 内容 | |---|---| | 主机侧纯逻辑单测(新增,进 `test/host/`) | ① 环:预触发与后触发点数、冻结与解冻、冷却丢弃计数;② 段格式编解码往返,含 CRC 不符、零长度、字节数不符三条失败路径;③ 触发状态机三条来路(手动 / 保护 / bench)。**环与格式必须是纯逻辑**(不碰寄存器、不碰 RTOS)才能这样测——这是 `App/algo/` 既有分层纪律 | | 板级 | 信号发生器注 1 V / 50 Hz → 手动一段 → FTP 拉回 → PC 脚本量出周波数、幅值、极性、削顶路数与 `clip_mask` 一致;再把 PC 重算的基波有效值与板上 `meas:` 与 104 浮点 YC 对表 | | UI | `ui-verify` 五步;回放页入 `ui-golden` 黄金参考(**参考图一旦入库只能改代码对齐,禁止刷参考掩盖**);新叶走 `gen_menu.py` 重生成 + 字库链 | | 构建 | `@build` 要求 0 错误 0 警告;新 .c 双端注册走 `new-source-file`(`MDK-ARM/STM32F407-Demo.uvprojx` 与 `simulator/CMakeLists.txt` 两处显式列表) | | 台账 | R6 三处同步 + `scripts/ram_baseline.py --update` + `mem-numbers` 与 `mem-budget` 钩子 | | 图与生成物 | 本设计文档的配图生成器受 `generated-fresh` 逐字节幂等约束;脚本与产物同一笔提交 | **bench 定时自抓的红线**:定周期自抓在 1 kHz / 8 路下每 2 s 一段,每 5 分钟就是一轮 NAND 写。**长时间挂着它白烧写次数**,而 dhara 的坏块记账是持久代价、普通恢复清不掉(`AGENTS.md` 测试构建节,`docs/测试说明/存储维护与容量恢复.md`)→ 定时模式必须**默认关闭 + 有时长上限 + 启动时打一次告警**,用完立刻回产品构建。 --- ## 11. 与 `docs/任务规划/数据可信.md` 的口径变更(三处) | 旧口径 | 新口径 | 原因 | |---|---|---| | 「录波通道必须先有工程量,排在本波之后」 | 录波录**原始码值**,不等工程量;解释靠头里的系数快照 | §3.2:现在根本没有工程量,等下去等于永远不动 | | 动态波形验证的读数出口 = 派生标量(`meas:` / 104 / `phasor(hb)`) | 派生标量仍是**判据**,但样本改从录波段导出 → 判据升级为「PC 独立重算 vs 板上读数」对表 | 录波段就是现成的样本出口,不必再造一条串口通路 | | 动态波形验证「手边可做、排在最前」 | 排在录波 P1 之后(用户裁定 2026-10-29:录波先行) | 先有样本通路,验证才不留遗憾 | 卡片表与顺序图在 `docs/任务规划/故障录波.md`;本文只定设计,不写排期。 --- ## 12. 风险 | 风险 | 后果 | 处置 | |---|---|---| | 削顶五路被读成「系统有零序 / 有谐波」 | 现场误判 | 头里 `clip_mask` + 页面「削」徽标 + 空态不许画直线(§8.1) | | 有人把 U 与 I 两段叠图比相位 | 得出**没有物理意义**的相位差 | 回放不做多路叠加;头里写明来源转换器;与 `docs/任务规划/数据可信.md` §9 的 V0.1 禁止行同源 | | 落盘把 defaultTask 拖住 | 日志与配置引擎变慢(defaultTask 是两者唯一写者) | 分次写(256 B 双缓冲)、一次心跳最多一段、`f_write` 前后有耗时观测;不做整段驻留再写 | | 触发风暴 + 轮转覆盖 | 现场唯一一段有效波形被后到的抖动冲掉 | 冷却期 + `ovf` 计数;「最新段号」进快照;分段优先级属二期 | | 定时自抓忘关 | 白烧 NAND 写次数(持久代价) | 默认关 + 上限 + 告警(§10 红线) | | 标定落地后老文件被解释错 | 同一个系数字段值变了含义漂移 | 头里存**封存瞬间**的 `eng` 与系数快照;PC 脚本按头解释,绝不按当前固件口径解释 | | 环写成 float 或按帧交错 | RAM 翻倍 / 单通道回放跨步取数 | §6.2 与 §7 把形态钉死为 int16 分列 | | 拿 1 kHz 的图宣称「看清波形」 | 掩盖与产品 80 点口径的偏离 | §2 偏离登记 + §2.1 结构约束写进代码注释 | --- ## 13. 范围边界(本波不做) | 不做 | 归属 | |---|---| | 4 kHz 全速、每周波 80 点 | 二期;触发条件 = 采集节拍改造与算法层抽取结构一并评审(§2.1) | | 突变量启动 | `docs/算法/继电保护功能复现规划.md` 的「故障启动(波形启动)」行 | | COMTRADE 导出、104 文件传输 | 送检与第三方交接时才需要(产品需求「文件传输(录波)」行至今标未开始) | | 参考机形态(录波 65 通道;3 类型 / 6 段 / RAM 16 个) | 二期对标;本波只有 8 路单段 | | 电度与暂态量录波 | 不属本波 | | 60 Hz 工况自适应 | `pts_per_cycle` 已留字段,但不做实现 | --- ## 14. 与其它文档的关系 | 文档 / 工作 | 关系 | |---|---| | `docs/任务规划/故障录波.md` | **排期与卡片判据的属主**;本文是设计属主,两份不互相抄 | | `docs/任务规划/数据可信.md` | 上游:静态与动态信度、削顶归属由它定义;本文 §11 记录因录波而变的三处口径 | | `docs/子系统设计/ADC采样节拍设计说明.md` | 数据源与节拍属主;本波**不改**它,二期改节拍时必须回头改它 | | `docs/架构设计/STM32F4-Base存储架构说明.md` §10.1 | FatFs 陷阱与 8.3 短名的属主;本文 §5.2 只是消费者 | | `docs/UI设计/页面补齐规划.md`、`docs/任务规划/UI补齐.md` | 「手动录波」叶子旧状态是「不做(无录波模块)」;P2 与 P3 落地时两处的叶数闭合与重开条件要同步 | | `docs/问题记录/采集与数据处理短板评测.md` | 记录链缺口(波形数据面 / COMTRADE / 104 文件服务)的登记处;P1 落地后该行改指本文 | | `docs/产品需求/产品需求设计文档.md` | 「录波采样率」行是本文 §2 偏离的对照基准 | | `docs/硬件平台/ADC通道映射说明.md` | 通道归属与注入点清单的属主;回放页「削」徽标的判据出处 |