# STM32F4-Base 产品需求设计文档
> **文档定位**:本项目**面向产品的需求基线**——回答三个问题:产品是什么(§2 背景·用户·场景)、
> 要做到什么程度(§4~§8 需求与验收标准)、当前做到哪一步(§4 总览表 + §9 路线图)。
> 散落在架构、子系统、测试文档中的"产品做什么 / 做到什么程度 / 依据是什么"以本文为唯一收敛点。
> **版本**:v2.0 **日期**:2026-10-30 **状态**:现状基线(含已实现项 + 待实现项)
> **范围口径:1:1 复刻**——本产品是参考产品 **CLS100**(`Scatter_P_HV03.01_SV01.01`,OMAP-L138
> ARM+DSP 双核 + VxWorks 配电保护测控装置)的**逐模块复刻**;差异只允许来自物理不可移植项
> (§3.2)。能力基线与逐项明细已独立成档:
> [`参考产品复刻明细.md`](参考产品复刻明细.md)(能力清单、点表/定值/元件/规约逐项差额)。
> **配套权威档**:运行时架构(就绪位表、启动时序、线程通信)→
> [`../架构设计/软件架构与运行时规格.md`](../架构设计/软件架构与运行时规格.md);硬件规格与
> 能力边界(引脚表、H1~H5)→ [`../架构设计/硬件规格与能力边界.md`](../架构设计/硬件规格与能力边界.md)。
> **事实来源**:本文所有数值与结论均取自仓库内**受管事实源**(代码、`.ioc`、`.map`、网表分析
> 报告与各模块设计文档),凡与其它文档冲突处,以本文标注的权威来源为准。
> **阅读前提**:建议先读 [`docs/README.md`](../../docs/README.md)(文档索引)。
> 产品定义的技术细化(保护功能复现)见
> [`docs/算法/继电保护功能复现规划.md`](../算法/继电保护功能复现规划.md)。
## 1. 文档说明
### 1.1 目的与范围
本文回答三个问题:
1. **产品是什么**——一块工业级嵌入式 DTU 平台固件,做什么、面向谁、部署在哪(§2);
2. **产品要做到什么程度**——每一项功能/接口/性能/可靠性的**可验收要求**(§4~§8);
3. **产品当前做到哪一步**——已实现、已验收、待实现的**明确边界**(§4 总览 + §9)。
**不含**(各有权威档,本文不重复):
- 代码实现细节 → 各模块设计文档(`docs/子系统设计/`、`docs/算法/`、`docs/网络/`);
- 运行时架构(分层/任务/就绪位/启动时序)→ [`../架构设计/软件架构与运行时规格.md`](../架构设计/软件架构与运行时规格.md);
- 硬件规格与能力边界(引脚/硬件边界)→ [`../架构设计/硬件规格与能力边界.md`](../架构设计/硬件规格与能力边界.md);
- 参考产品能力基线与复刻逐项明细 → [`参考产品复刻明细.md`](参考产品复刻明细.md);
- 测试用例步骤 → `docs/测试说明/`;未闭环事项逐条跟踪 → [`docs/任务规划/README.md`](../任务规划/README.md)。
### 1.2 术语与缩写
| 术语 | 全称 / 含义 |
|---|---|
| **DTU** | Data Transfer Unit,数据传输单元;本产品的设备角色 |
| **MCU** | STM32F407ZGTx(Cortex-M4,LQFP144) |
| **FTL** | Flash Translation Layer,闪存转换层(本项目用 dhara 实现) |
| **BBT** | Bad Block Table,NAND 坏块表(持久化在保留块池) |
| **FatFs** | 通用 FAT 文件系统模块(`Lib/FatFs`) |
| **BSD Socket API** | 本项目在 CH395F 之上封装的 `net_socket` 接口(`socket/bind/listen/accept/recv/send/close`) |
| **RemoDispBus** | 远程显示总线协议(MCU 无屏,UI 以 1bpp 帧经 TCP 推流到远端客户端) |
| **cfg_file** | 通用配置引擎(INI 段键表,MCU/模拟器双端共享核心) |
| **sys_clock** | 系统墙钟时间服务(RTC 基准 + tick 推进) |
| **sys_log** | 定长 64B 记录引擎(运行/自检日志) |
| **IWDG** | Independent Watchdog,独立看门狗(LSI 时钟源) |
| **就绪屏障** | 用 CMSIS-RTOS2 Event Flags 表达"子系统就绪"的启动顺序机制 |
| **双端** | MCU(Keil/ARMCC)与 PC 模拟器(CMake/MSVC)共用同一份 UI/配置源码 |
| **/keil-build / /serial-boot-test** | 仓库约定的构建 / 自动化测试命令别名 |
| **语义取数与有效性 / 标定折算 / 全周傅氏相量 / 测频与遥测快照 / 保护判据** | 采集链路分层:语义取数与有效性(语义取数与有效性)→ 标定折算(标定折算)→ 全周傅氏相量(全周傅氏相量)→ 测频与遥测快照(测频与遥测快照)→ 保护判据(保护判据) |
| **全周傅氏** | 单频 DFT 取基波相量,窗长 = 整周波(20 点);幅值即基波有效值 |
| **相量** | `{fRe, fIm, fAm, fAmSQ}`,实部 / 虚部 / 幅值 / 有效值 |
| **双区交换** | 相量(及遥测快照)计算区 / 使用区索引单字节翻转,读者无锁取整份快照 |
| **等周波采样** | 软件测频 + 调采样周期,使窗长恒等于实际周波(严格 20 点/周波) |
| **标定 k / b** | 通道折算系数(µV/码,Q16 定点)与零点(码值);`工程量 = (code + b) × k` |
| **QDS** | IEC 60870-5-104 的品质描述符字节(OV / NT / IV 等位) |
| **SOE** | Sequence of Event,带时标的事件顺序记录(本产品落 `/log/soe.log`) |
| **CP56Time2a** | 104 的 7 字节时标格式(毫秒 / 分 / 时 / 日 / 月 / 年 + 时标标志位) |
| **点表 / IOA** | 104 上送对象表与信息体地址;本产品点表口径见 §6.8 |
| **固有 / 运行 / 保护定值** | 三类参数:只读身份信息、可整定的运行参数、可整定的保护定值(§5.13) |
| **软压板** | 软件投退开关(全局软压板 = 保护总投入,可经 104 遥控投退) |
| **遥测死区** | 遥测变化上送门槛;本产品按"连续 2 个新测量点越限才确认" |
| **软件架构档** | [`docs/架构设计/软件架构与运行时规格.md`](../架构设计/软件架构与运行时规格.md) 的简称(本文与复刻明细档共用此简称) |
| **硬件规格档** | [`docs/架构设计/硬件规格与能力边界.md`](../架构设计/硬件规格与能力边界.md) 的简称(本文与复刻明细档共用此简称) |
| **复刻明细档** | [`docs/产品需求/参考产品复刻明细.md`](参考产品复刻明细.md) 的简称 |
| **CLS100** | 参考产品(配电保护测控装置,ARM + DSP 双核 / VxWorks),仅作能力对照,非本产品代次 |
### 1.3 读者与相关文档
| 角色 | 建议读法 |
|---|---|
| 甲方 / 产品 | §2 背景·用户·场景 → §4 需求总览 → §9 路线图 → §10 验收判据 |
| 开发 | 全文;深入时按 §11 指向各模块设计文档 |
| 测试 / 验证 | §5、§6、§7、§10 + `docs/测试说明/` |
| 评审 | §2、§3、§7、§9 |
---
## 2. 背景、目标用户与产品目标
### 2.1 背景与产品定位
**STM32F4-Base 是一块工业级嵌入式 DTU 平台固件**,基于 STM32F407ZGTx,集成
**以太网通信、SPI NAND 存储、ADC 采集、RS-485 通信与 FTP 文件服务**,并自带
**远程显示(UI 推流)、墙钟对时、看门狗自恢复、运行/自检记录**等工业现场能力。
它的**产品目标是 1:1 复刻参考产品 CLS100**(配电保护测控装置)——按参考工程源码的
常量表与模块文件逐项对齐点表、定值、保护元件与规约栈(§3.1、复刻明细 §1)。在此过程中沉淀出的
分层底座(采集链 语义取数与有效性~出口与记录、配置引擎、就绪屏障、双端测试)同时是可复用的平台能力。
> **产品现状一句话**:平台能力(网络 / 存储 / 时间 / UI / 日志 / 看门狗 / FTP / 配置)
> **已实现并台架验收**;采集主线(语义取数与有效性采集 → 标定折算 → 全周傅氏相量 → 测频与遥测快照 → 保护判据
> → 出口与记录 SOE → IEC 60870-5-104 上送)**软件链路已全线贯通并台架闭环**;与参考产品的差距
> 集中在三处:① 保护判据处于**未标定闭锁**态(等标准源两点标定)、② **点表规模**远小于参考
> (定值 40 vs 249 项、遥测 9 vs 31 点、开入开出 0 vs 138/31 点)、③ **独立子系统未立项**
> (故障录波、电度、多规约、SNTP)。即:**"数"与"判据"已可信到引脚电压口径,
> 缺的是标定、点表铺开与一次设备接口**(明细见 复刻明细 §1.2、§9)。
> 交付节奏与验收锚点见 §9、§10;本文对"为什么是这四点能力组合"不再另立论证——
> 它是 DTU 数据接入类产品的工业现场最小完备集(通信、存储、采集、对外规约),
> 并以 1:1 复刻 CLS100 为纵深(§3)。
### 2.2 产品形态与部署
```mermaid
flowchart LR
subgraph DEV["开发机(Windows)"]
K[Keil MDK-ARM v5
编译/烧录]
SIM[LVGL PC 模拟器
CMake + SDL2]
PY[Python .venv
脚本/测试]
end
subgraph TGT["目标板(DTU 硬件)"]
M[STM32F407ZGTx @168MHz]
P[CH395F / GD5F2GQ5UE / TPAFE5160 / SD2506 / RS-485 / LED×6]
end
subgraph RMT["远端客户端(PC/浏览器)"]
C[DTU-RemoteLCD 或任意 TCP 客户端]
end
DEV -- "USB:ST-Link 下载器" --> TGT
DEV -- "USB→RS-232 适配器:RS232#1 @115200 调试日志" --> TGT
TGT -- "以太网 CH395F
FTP:21 / 对时:8888 / 远程显示:7003" --> RMT
```
| 环境 | 角色 | 必需性 |
|---|---|---|
| ① 开发机 | 编译/烧录、UI 模拟器、脚本工具链 | 开发/测试必需 |
| ② 目标板 | 运行固件的 DTU 硬件 | 上机必需 |
| ③ 远端客户端 | 远程看图/按键、对时推送、FTP 文件操作 | 现场使用必需 |
**关键产品特征**:**MCU 端无物理屏幕**——UI 画面以 160×160 单色位图(1bpp)经 TCP 推送到远端
客户端渲染,按键亦由远端下发(见 §5.6、§8)。
### 2.3 目标用户
| 角色 | 与产品的接触面 | 关心的事 |
|---|---|---|
| **主站操作员 / 调度** | IEC 60870-5-104(工厂默认 :2404):总召、变位、遥控、定值、对时 | 数据可信(品质位如实)、动作可追溯(SOE 时标)、异常可自恢复 |
| **现场调试与运维** | 串口屏板(本地带屏运行)、UART5 日志口(:115200)、FTP(:21)、ST-LINK 烧录 | 上电即用的接线口径、看得见的状态灯与日志、改配置不重启、掉电不丢参数 |
| **工厂生产 / 出厂** | `/config.ini` 工厂默认、UI 工厂菜单、自检记录 | 产线可批量预置网络与口令、自检留痕可查 |
| **远程看图 / 注键人员** | TCP :7003(RemoDispBus)+ DTU-RemoteLCD | 与本地屏板同源的画面与按键(两出口并发互不干扰) |
| **送检 / 验收方** | §10 验收判据、第三方 104 主站往返 | 每项要求都有可跑通的判据,"没做"与"接线错"可区分 |
### 2.4 典型使用场景
1. **开关柜现场部署**:设备上电 → 按 `/config.ini` 出厂 IP 组网 → 主站连工厂默认 :2404 总召 →
现场用屏板看测量、按按键翻菜单;死机由看门狗自恢复(无需到场)。
2. **远程运维**:PC 经 :7003 看同一份 UI、注键操作;FTP 取 `/log`(运行/自检/SOE 记录)
与 `/config.ini`;改 IP 后 2~5 s 生效不重启;PC 推 :8888 对时。
3. **脱离 PC 独立带屏运行**:串口屏板直连 USART1(700 kbaud),主机 275 ms 召唤、
≤200 ms 回 4208 B 帧(3200 B 显存 + 状态灯掩码 + info)——无 PC 也能看与按(§5.16)。
4. **保护运行(等标定解锁)**:1 kHz 采样 → 全周傅氏相量 → 20 ms 判据节拍 → 动作只落
遥信 + SOE + 日志(V0.1 无出口硬件);**未标定态判据整体停走且 `YX31` 如实置位**——
这是刻意的安全设计,不是缺陷。
5. **复刻验收**:按 [`参考产品复刻明细.md`](参考产品复刻明细.md) 逐项对照点表/定值/元件/规约,
差额即排期输入(§9)。
### 2.5 目标与成功指标
| # | 目标 | 落地手段 |
|---|---|---|
| G1 | **可靠**:现场死机可自恢复、掉电不丢配置、错误可追溯 | IWDG + 任务心跳位图、原子写 + 迁移、错误码通道四要素 |
| G2 | **可维护**:分层解耦、单一事实源、双端复用、门禁强制 | 六层架构、就绪屏障、生成器(菜单/字体/任务表)、pre-commit 钩子族 |
| G3 | **可移植**:UI/配置源码 MCU 与 PC 模拟器共用 | 平台后端抽象(`*_mcu.c` / `*_sim.c`),核心零平台依赖 |
| G4 | **可扩展**:新子系统/新配置段/新页面按模板接入 | 事件位扩展、段注册三步、页面插件描述符、`App/protocol/` 预留 |
| G5 | **可验收**:每项能力有明确判据与自动化门禁 | `/keil-build` / `/serial-boot-test` / 静态门禁 / 主机侧纯逻辑单测 / 台架验证 |
**设计原则**(贯穿全项目):
1. **上层只依赖相邻下层(单向)**;禁止反向依赖(例外经登记)。
2. **状态位常驻、命令位消费**——Event Flags 就绪位必须 `osFlagsNoClear`。
3. **能推导的就不存**——持久化"不可推导"的状态(导航历史、配置、日志),其余现算。
4. **单写者**——配置文件属主 = `defaultTask`;syslog 唯一写者 = `defaultTask`;控制台唯一发送者 = `defaultTask`。
5. **一切写入 = 临时文件 + rename**(原子替换),半截窗口结构消除。
6. **测量不可信就不判据**——未标定(VALID 未置位)或采样故障时保护停走,品质位如实上送,
绝不用"看起来对"的数去动作(§5.12、§5.15)。
7. **点表优先于实现**——本期做不了的 104 点照常入表并置 IV;缺行比置 IV 更糟,
主站将无法区分"没做"与"接线错"(§6.8)。
**成功指标(可判定)**:
| 指标 | 目标 | 当前状态 |
|---|---|---|
| 平台能力台架验收 | 网络/存储/时间/UI/日志/看门狗/FTP/配置 全部闭环 | ✅ 已达成(§10.4) |
| 采集主线到业务出口 | 采样→相量→测频→遥测快照→判据→104/UI 全链路数据同源 | ✅ 软件链路闭环;判据等标定(§9.1.1) |
| 屏板独立带屏 | 串口通道交付并台架验收 | ✅ 主机侧交付(§5.16;尾巴见屏板链路设计说明 §9.2) |
| 送检就绪 | 两点标定 → 保护解卡 → 第三方主站验收 | ⬜ 等标准源与端子变比(§9.2 第 4/5 步) |
| 复刻完成 | 复刻明细各项达"做完了"口径 | 🔶 见 [`参考产品复刻明细.md`](参考产品复刻明细.md) §1.2 |
---
## 3. 范围与产品级非目标
### 3.1 产品范围(1:1 复刻口径)
**产品范围 = 参考产品 CLS100 的可移植能力全集。** 本产品不是"在参考产品里挑一部分做",
而是**按参考工程源码逐模块复刻**:凡参考源码中存在的点表、定值单元、保护元件、记录类型、
规约栈,都属本产品目标范围;差异只允许来自**物理不可移植项**(§3.1.1)。
范围按**复刻完成度**分三档,判定口径见 `参考产品复刻明细.md` §1.1:
| 档 | 含义 | 处置 |
|---|---|---|
| ✅ **已对齐** | 参考源码能力在本产品上已实现,且有台架/单测证据 | 进 §10 验收判据 |
| 🔶 **部分对齐** | 骨架、口径、地址编排已对齐,覆盖范围小于参考(通道数 / 元件数 / 点数) | 差额逐条进 §9 |
| ⬜ **未开始** | 参考源码存在、本产品无对应实现 | 逐条进 §9,**不等于"不做"** |
**当前分布**(20 项能力,明细见 `参考产品复刻明细.md` §1.2):✅ **2 项**(采样节拍、自检类型)、🔶 **10 项**、
⬜ **7 项**、— **1 项**(双核分工,不可移植)。
#### 3.1.1 不可移植项(产品级非目标)(唯一允许落在范围外的部分)
| # | 不可移植项 | 参考实现 | 本产品 | 归因 |
|---|---|---|---|---|
| X1 | 双核 ARM + DSP 分工 | OMAP-L138(ARM 跑通信/HMI,DSP 跑保护) | 单核 Cortex-M4F 按任务划分承担 | 芯片形态 |
| X2 | 实时操作系统与工具链 | VxWorks 5.x + CCS | FreeRTOS V10.3.1 + Keil ARMCC | 平台形态 |
| X3 | 一次设备接口 | KO / KI 板卡(出口、开入、硬压板) | V0.1 无该硬件(硬件规格档 §5 H3) | 硬件缺失 |
| X4 | 独立计量通道 | 专用计量芯片(电度) | 无(跨转换器无同刻,§4.6 H1) | 硬件缺失 |
> **X1~X4 是范围外项的全部。** 除此以外**不存在"参考有、我们决定不做"的能力**—
### 3.2 产品级非目标(本期不做,与"范围外"不同义)
| 非目标 | 边界 | 理由 |
|---|---|---|
| 屏板硬件本体固件 | `DTU-LCD` 独立工程 | 不在本仓;主机侧以需求档 §5.16 契约为黑盒验收线 |
| 多定值区 / 定值文件下装 | 参考有 4 区 | 本产品只定义单定值区(§5.15 未实现项表) |
| 主站 IP 白名单 | — | 单 socket 语义下"拒绝"= 断链重听,可被扫描打成 SPI 风暴(§5.15) |
| LOGO 下发 | 帧 info[4..5] 保留位恒 0 | 参考该功能无消费方,协议保留位存在但主机不提供操作(§5.16) |
| 归一化 / 标度化遥测 | 点表要求短浮点(系数 1) | 协议语义决定,非进度缺口 |
> "非目标"是**产品决策**;复刻差额不是(那是进度,见 §9 与复刻明细档)。两者不混。
---
## 4. 需求总览(优先级与状态)
> 优先级是**排期语义**:P0 = 交付/送检主线;P1 = 复刻铺开项(可并行、依赖标定或硬件);
> P2 = 冻结或等立项。状态口径同 §5 章头(✅ / 🔶 / ⬜)。细节与验收判据在 §5 各节。
| 优先级 | 需求域 | 状态 | 一句话边界 |
|---|---|---|---|
| P0 | FR-NET 网络通信(§5.1) | ✅ | CH395F + BSD 抽象 + 单连接自愈 |
| P0 | FR-STO 存储(§5.2) | ✅ | SPI NAND→dhara→FatFs,卷自愈 |
| P0 | FR-TIME 时间(§5.3) | ✅ | SD2506 墙钟 + :8888 对时(2038 已知限) |
| P0 | FR-CFG 配置引擎(§5.4) | ✅ | INI 段键表 + 外科保存 + 免重启生效 |
| P0 | FR-LOG 记录与日志(§5.5) | ✅ | 定长记录 + 控制台单写者 |
| P0 | FR-WD 看门狗(§5.7) | ✅ | 心跳位图 + 复位原因 + Fault 现场 |
| P0 | FR-ADC 采集与标定(§5.9) | ✅ | 1 kHz 同源节拍;两点标定后解卡 |
| P0 | FR-MEAS 遥测与相量(§5.11) | ✅ | 全周傅氏 + 测频 + 200 ms 快照单源 |
| P0 | FR-IEC104 上行(§5.15) | ✅ | 被控站;无效点入表置 IV |
| P0 | FR-PROT 保护(§5.12) | 🔶 | 软件就绪;**未标定闭锁**(用户裁定走标准源) |
| P0 | FR-SET 定值(§5.13) | ✅ | 40 项;原子封存 + 掉电保持 |
| P0 | FR-SOE 事件记录(§5.14) | ✅ | 判据路径零 NAND I/O |
| P0 | FR-HMI 屏板链路(§5.16) | 🔶 | 串口主通道已交付;7003 报文口径 6 项未做 |
| P0 | FR-UI 远程显示(§5.6) | ✅ | 两出口同源推流 |
| P1 | FR-LED 状态指示(§5.8) | ✅ | 主板 6 路;屏板 20 路属 FR-HMI |
| P1 | 复刻铺开(定值/遥测/元件/规约/软压板) | 🔶 | 明细见复刻明细档;顺序不可早于标定(§9.2) |
| P1 | 故障录波 | 🔶 | 最小闭环代码面已交付;4 kHz/多段/COMTRADE 二期 |
| P2 | FR-RS485 对下采集(§5.10) | ⬜ | 整簇冻结(用户决定),重启门禁见该节 |
| P2 | FR-PROT-5 出口(§5.12) | ⬜ | 等 V0.2 硬件 |
---
## 5. 功能需求
> 每项功能给出:**需求 → 实现要点 → 验收判据**。状态标注:✅ 已实现并验收 / 🔶 部分对齐(含已实现待专项、软件就绪待条件)/ ⬜ 待实现。
### 5.1 网络通信子系统(FR-NET)✅
**FR-NET-1 以太网组网**
- 需求:上电后按配置的 IP/掩码/网关/MAC 完成以太网初始化,PHY 强制 100M 全双工,等链路稳定后置就绪位。
- 实现:`netTask` 等 `STORAGE_READY` → 读 RAM 配置副本 → `net_init(ip,mask,gw,mac)` → 等链路 ≤3 s → 置 `NET_READY`。
- 判据:串口出现 `Network Init` / `net init: OK` / `PHY: 100M FULL`;PC 能 ping 通设备 IP。
**FR-NET-2 网络抽象层(BSD Socket API)**
- 需求:为上层提供 `socket/bind/listen/accept/recv/send/close` 与 `select/poll` 多路复用;所有 CH395F 操作串行化。
- 实现:`net_socket.c`(BSD API)→ `netMsgQueue`(深度 8)→ `netTask` 串行执行;`net_select.c` 提供 I/O 多路复用。
- 判据:三个 TCP 服务(FTP / 对时 / 远程显示)均经同一 API 工作;单连接模型下连接断开自动回到 LISTEN。
**FR-NET-3 单连接模型与自愈**
- 需求:CH395F 配置为 8 Socket(索引寻址)+ TCP Server 单连接(`set_fun_para(0x08)`,bit3 SOCKET_CLOSE;bit0 为保留位必须为 0),Socket 0 复用监听与数据通道;网络重建后服务自动重连。
- 实现:三个 TCP 服务**串行**占用连接,断开自动重听;代次号(`net_get_generation()`)驱动服务自愈。
- 判据:FTP 旧会话异常断开后约 30 s 恢复监听;网络重建(改 IP)后服务自动重建监听。
> 网络层是全项目**最成熟子系统**(pending 机制 + 四份 Trap 记录 + 量化基线 + 探针族)。
### 5.2 存储子系统(FR-STO)✅

**FR-STO-1 三层存储栈**
- 需求:在 SPI NAND 上提供 FAT32 文件系统;具备磨损均衡、坏块管理、垃圾回收;上电无卷自动格式化。
- 实现:FatFS(FAT32)→ dhara FTL → gd5f2gq5ue 驱动;`defaultTask` 挂载(无卷 `f_mkfs`)+ 卷健康探针/自愈。
- 判据:FTP 可列目录/读写卷根文件;掉电后数据仍在。
**FR-STO-2 坏块管理与持久化 BBT**
- 需求:坏块表持久化,重启后不丢失;不交给 dhara 管理。
- 判据:全片擦除后容量恢复流程可用(见 `docs/测试说明/专项验证/存储维护与容量恢复.md`)。
**FR-STO-3 卷并发一致性**
- 需求:FTP 传输(ftpTask)与配置读写(defaultTask)并发访问卷时不得损坏。
- 实现:`FF_FS_REENTRANT=1` + 逐次加锁/放锁(超时 1 s);三条一致性协议(写 tmp+rename / 沉降去抖 / 单写者)。
- 判据:并发 FTP 下载 + UI 保存无卷损坏、无死锁。
### 5.3 时间子系统(FR-TIME)✅
**FR-TIME-1 墙钟维护**
- 需求:以 SD2506 RTC 建立 Unix epoch 墙钟基准,运行期以 tick 推进(零 I2C 流量),提供字符串格式时间。
- 实现:`sys_clock_init()` 读一次 RTC 建基准(无效时按默认值自动校时);base+delta 法;30 天惰性再基准化(防 49.7 天 tick 回绕);`sys_clock_get_str()` → `"YYYY-MM-DD HH:MM:SS"`。
- 判据:日志时间戳真实正确;主机侧单测 100 断言点通过。
**FR-TIME-2 网络对时**
- 需求:PC 主动通过 TCP :8888 推送时间到设备,设备写回 RTC。
- 实现:`timeSyncTask` 等 `NET_READY` → TCP listen :8888 → 收 PC 推送 → 写 RTC;30 s 无包则掉线重建监听。
- 判据:对时后设备墙钟更新、日志时间戳正确。
> **已知限制**:uint32 epoch → **2038 年**上限。
### 5.4 配置引擎(FR-CFG)✅

**FR-CFG-1 通用配置文件**
- 需求:网络参数(IP/掩码/网关/MAC)持久化到 `/config.ini`(INI `[network]` 段);文件可经 FTP 下载/上传;格式纯文本、带注释、可编辑器编辑。
- 实现:`cfg_file` 段键表引擎(段 RAM 副本复用模块静态结构体,键表 `const` 映射键→偏移);工厂模板首启自动生成。
- 判据:文件缺失/损坏回退工厂默认并重建模板;FTP 上传后 2~5 s 自动生效。
**FR-CFG-2 外科手术式保存 + 原子写**
- 需求:保存时保留用户注释/未知段/未知键(降级安全);写入原子(并发读者只见旧完整或新完整文件)。
- 实现:受管键行替换,其余原样保留;临时文件 + `unlink` 目标 + `f_rename`;输出 >4096 B 拒绝保存。
- 判据:设备保存后下载文件,用户注释与未来版本段落原样保留。
**FR-CFG-3 变更检测与立即应用**
- 需求:FTP 上传配置后无需重启即生效;网络段变化触发网络重建。
- 实现:`defaultTask` 每 2 s `f_stat` 比对 (size, mtime),连续 2 次稳定才重载(沉降去抖);`[network]` 段变化 → `SYS_EVT_NET_RECONFIG` → 网络重建(代次 +1,服务自愈)。
- 判据:FTP 改 IP 后 2~5 s 生效(网络重建含等链路 ≤3 s)。
**FR-CFG-4 旧档迁移**(已取消 · 2026-10-10)
- 原需求:旧固件的 `/cfg/netcfg.bin`(NCFG 二进制)首启自动迁移为 INI 并删除旧文件——**已取消,不再实现**。
- 现口径:旧档若存在则被**忽略**(不读、不删、不迁移),配置直接回落工厂默认(`/config.ini` `[network]` 段模板)。
- 取消理由:该一次性路径把整条配置写链(`net_cfg_save → cfg_file_save → cfg_store_write_atomic → f_rename → FatFS → dhara FTL`)挂进启动期调用图,defaultTask 静态最坏栈链 +600 B;本仓库不存在携带旧档的设备。
- 判据:卷上残留 `/cfg/netcfg.bin` 时开机不产生任何迁移日志、旧档不被改动,`/config.ini` 按工厂默认生成。
**FR-CFG-5 扩展性**
- 需求:未来所有模块配置(ADC/RS-485/端口等)进同一文件、按段组织。
- 实现:未来模块接入三步——① 定义段结构体(静态工厂默认)② 写键表 ③ `cfg_file_register()`。
### 5.5 运行 / 自检记录与日志(FR-LOG)✅
**FR-LOG-1 定长记录引擎**
- 需求:运行记录与自检记录持久化到 NAND,FTP 可直下。
- 实现:`sys_log` 定长 64 B 文本记录(一行一条);`/log/run.log`(1024 条 / 64 KB 上限)、`/log/selftest.log`(512 条 / 32 KB);`defaultTask` 唯一写者;ISO8601 时间戳 + epoch。
- 判据:UI"运行记录/自检记录"页可显示;FTP 可下载。
**FR-LOG-2 控制台单写者日志**
- 需求:多任务并发日志不得字符级交错;异常场景(HardFault)日志可控。
- 实现:4 KB 环形缓冲 + `defaultTask` 唯一发送(行级原子);内核前/ISR/异常钩子有界直发回退。
- 判据:日志无交错;崩溃时能输出归因日志。
**FR-LOG-3 上电/重启分类与待机显示**
- 需求:区分上电与重启;空闲切待机屏。
- 实现:`run.log` 末条 epoch 时间差推断;硬件复位原因(`RCC_CSR`)优先;`ui_standby` 空闲 60 s 切入。
### 5.6 UI 与远程显示(FR-UI)✅
**FR-UI-1 离屏渲染 + 双出口推流**
- 需求:MCU 无物理屏幕,UI 以 160×160 单色位图(1bpp,3200 B)渲染到离屏显存,
经**两个出口**送出:**串口屏板通道**(主,§5.16.2)与 **TCP :7003**(镜像);两路的按键都回注 UI。
- 实现:LVGL 160×160 1bpp 离屏渲染到 `g_ui_fbuf`(3200 B)→ `ui_net_sink` 经 RemoDispBus 推流;
远端按键经协议注入。**串口屏板通道尚未实现**(§5.16.9 第 11 条)。
- 判据:DTU-RemoteLCD 连 7003 出图(160×160 1bpp)+ 远端菜单导航可用;
屏板实物接入后 280 ms 节拍出图 + 状态灯正确(§5.16.10 第 8~15 条)。
**FR-UI-2 菜单与内容页**
- 需求:42 节点层级菜单(最大 4 级)+ 内容页(装置信息/交流量/直流量等)。
- 实现:三层拆分(数据层 `ui_menu_tab` 生成物 / 逻辑层 `ui_menu` 17 B 导航状态 / 显示层 `ui_menu_view` 级联框);内容页框架 + 页面插件描述符。2026-10-29 实到 43 节点(新增「录波回放」叶,需求原文的 42 是 V0.1 立项数)。
- 判据:`UI menu init: OK`;菜单像素回归全 PASS。
**FR-UI-3 待机屏**
- 需求:空闲超时(默认 60 s)自动切待机屏,仅确认键回菜单。
- 判据:空闲后进入待机屏,按键返回菜单。
> 详见 §8 UI 规格。
### 5.7 看门狗与故障自恢复(FR-WD)✅

**FR-WD-1 IWDG + 任务心跳位图**
- 需求:任务级死锁/饿死/断言死循环可自恢复(硬件复位),无需人工到场;不误杀正常长耗时窗口。
- 实现:IWDG(LSI 独立时钟,PR=4 / RLR=1249 → 标称 **2498 ms**、最坏 **≈1701 ms**);任务心跳位图(各自置位,无锁单字)+ `defaultTask` 单一喂狗者(`task_wd_poll` 每 100 ms 抽样);连续 8 次(800 ms)未达标 → 停喂 → 约 2.5 s 后复位;必需位掩码 `0x37`。
- 判据:V2 任务饿死 ≤2.5 s 复位;V4 关中断死循环每 ~4.9 s 稳定复位;V5 30 min 真实负载零复位。
**FR-WD-2 复位原因诊断**
- 需求:复位后可区分上电 / 看门狗 / 软件 / 引脚 / 欠压。
- 实现:`HAL_Init()` 后一次性读 `RCC_CSR` 锁存并清标志;优先级 POR > PIN > IWDG > WWDG > SFT > LPWR > BOR。
- 判据:串口 `[APP] reset cause: <类型>`;`[WD] armed:` 自检标记。
**FR-WD-3 故障现场采集**
- 需求:HardFault / MemManage / BusFault / UsageFault 触发时输出完整现场。
- 实现:4 个 Fault Handler 采集(PC/LR/xPSR/R0-R3/CFSR/HFSR/BFAR/MMFAR)+ `fault_handler_enable()` 使能 SHCSR 后三者。
- 判据:日志形如 `[ERR] MEMFAULT PC=... CFSR=... MMFAR=...`。
> **已知边界**:启动段(`app_main_init` + bring-up)无看门狗保护;`ftpTask` 首期不纳入监视(掩码剔除)。
### 5.8 LED 状态指示(FR-LED)✅
- 需求:6 路 LED(低电平点亮)随系统状态变化。
- 实现:`defaultTask` 循环驱动(闪烁指示系统活跃)。
- 判据:目检 6 路 LED 随系统状态变化。
> ⚠ **本节只讲主板 LED。** 屏板上另有 **20 路状态指示灯**(ID 0 电源常亮 + ID 1~19 位掩码),
> 由主机每帧下发 4 B 控制,属**另一套输出**,规格见 §5.16.3 ①。
> 两套 LED 的"位 → 业务状态"语义**各自独立**,不要混用同一个状态表。
### 5.9 ADC 采集与标定(FR-ADC)✅
**FR-ADC-1 采样节拍与触发**
- 需求:8 路电气量(`UA/UB/UC/UN`、`IA/IB/IC/IN`)**持续采集**,1000 Hz(50 Hz 下 20 点/周波),
采集节拍与任务唤醒**同源**;不得用任务周期软定时冒充硬件节拍。
- 实现:TIM3 更新事件(1 kHz)在 ISR 内触发 TPAFE `CONVST`,同一事件经 TRGO 触发 ADC1 一轮
9 通道并由 DMA2 循环搬入乒乓双缓冲;ISR 只置"消费者就绪"门并唤醒 `adcTask`(不做重运算)。
- 判据:`frame_seq` 每 10 s 精确 +10000(1000.0 Hz)、`frst_err=0`、`adc1_err=0`、`adc1_hw=0`;
单拍消费耗时 ≤10 µs(DMA 搬运后实测 8 µs,软件轮询时为 122 µs)。
**FR-ADC-2 语义通道映射**
- 需求:按**语义**取数(`UA`/`UN`/`IA` 等),不写死缓冲区下标——V0.2 换映射表即可切换硬件归属。
- 实现:`App/algo/adc_input.c` 的语义表(TPAFE `buf[6]/[5]/[2]`;ADC1 `CH14/3/6/1/2/5`)+ 开机
**映射自检**(STM32 映射通道必须落在扫描序列内,否则拒绝并自检留痕)。
- 判据:`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/码);
三态可用性 `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 通道系数/零点往返一致。
- 证据与现场步骤:[`docs/硬件平台/ADC通道映射说明.md`](../硬件平台/ADC通道映射说明.md) §8.1、
[`docs/测试说明/专项验证/ADC前端注入验证流程.md`](../测试说明/专项验证/ADC前端注入验证流程.md)。
**FR-ADC-4 采样有效性校验**
- 需求:芯片静默、丢帧、序号倒退、转换错误必须被检出并可闭锁保护,且**不误报**。
- 实现:`App/algo/adc_health.c` 纯逻辑判定层——静默 100 ms 门限(电平位、可自愈)、丢帧边沿计数、
序号倒退锁存、FRSTDATA/HAL 错误/STM32 停摆计数增量;严重事件与上升沿写自检记录(每类每次开机一次)。
- 判据:主机侧 31 用例全绿;台架零 `[ERR]`(健康不误报),人为制造丢帧时 `tgap` 与 `adc1_gap` 同步可见。
**FR-ADC-5 前端物理口径(已实测盖章)**
- `1k 串联 + 1k 到地` 是**源阻抗匹配、不是分压**:`ADCn` 节点只有 {串阻、滤波电容、转换器引脚},
「1k 到地」接在差分负端(`ADC7_GND` → R32 → AGND),故 AINn↔引脚变比 **1:1**。
- 芯片手册 ±10 V 档的 LSB(305.18 / 1611.3 µV/码)是**另一回事**,不可与本板变比混用。
- 剩余未知量只有**子板/端子板变比**,因此系数只能推到"µV/码 @ 引脚"(硬件规格档 §5 量纲口径)。
- 拓扑图 [`docs/images/ADC前端电路.svg`](../images/ADC前端电路.svg)。
### 5.10 RS-485 采集(FR-RS485)⬜ 冻结
- **需求(已确认)**:半双工 RS-485 **对下 ModBus RTU 采集**(下行采集口;上行走以太网 104)。
- **现状**:`rs485_init()` 已调用;`rs485_task.c` 为空桩、未在 `freertos.c` 创建;`App/protocol/` 为该
协议族预留(当前仅 104 在用)。**整簇冻结**(用户决定,2026-10-12)。
- **重启门禁**:先修「单缓冲包撕裂」「TX 超时拉 DE 未等 TC」两条,再谈接消费者;实现须满足
双缓冲 + 序号、DE 收尾超时路径、协议解析与硬件 I/O 分离。
- 详见 [`docs/任务规划/暂缓-RS485.md`](../任务规划/暂缓-RS485.md)。
### 5.11 遥测与相量(FR-MEAS)✅
**FR-MEAS-1 全周傅氏相量**
- 需求:每 1 ms 产出基波/谐波相量,幅值直接是**基波有效值**,读者无锁取整份快照。
- 实现:`App/algo/phasor.c`——N=20 全周 DFT,系数表离线预乘 `√2/N`;5 个实例
(`IA`/`UA`/`UN` 基波 + `IA` 二次/五次);每来新点整窗重算,算完单字节翻转计算区/使用区。
- 判据:主机侧 57 用例全绿(系数表逐项复核、合成正弦幅值误差 ≤1%、双区交换);台架开机即产出 5 行
`phasor(first)`,心跳持续刷新。
- **双轨有效值**:保护用**基波有效值**(抗衰减直流与谐波误动),计量/遥测要**真有效值**(谐波方和根)
——两者不可混用;本产品当前只实现基波轨,谐波方和根轨未做(§9)。
**FR-MEAS-2 频率测量(测频与遥测快照)**
- 需求:45~55 Hz 内测频,精度优先于响应速度。
- 实现:`App/algo/freq.c`——共轭乘积相位增量 + 解缠绕 + **自适应窗长**(对齐纹波周期整数倍);
假值钳制带与系统合理带分离;幅值门 <50 码判无效;`UA` → `IA` 优先级。
- 判据:45~55 Hz 扫频最坏误差 **0.0101 Hz**、1% 噪声 0.0160 Hz、启动收敛 ≤40 ms、阶跃 50→51 Hz ≤120 ms;
浮空(无信号)时 `valid=0` 不发散。
**FR-MEAS-3 遥测快照(200 ms)**
- 需求:104 与 UI **共用同一份上游**——"什么时候算新值、品质怎么判"只此一处决定;快照必须可无锁整体读。
- 实现:`App/algo/meas.c`——每 200 ms(**经过时间**分频,非拍计数)取基波有效值 + 逐点品质,双区 + `seq`;
**唯一发布者 = adcTask**(`meas_tick` 全仓仅一处调用),其余任务只读。
- 判据:主机侧布局用例钉死字段偏移(改字段 = 破协议);台架 104 与 UI 读数同源一致;快照未发布时
消费者得到 `IV|NT` 而不是 0。
**FR-MEAS-4 品质与死区**
- 需求:品质必须如实反映"这一路能不能信";遥测变化上送不得被单点噪声触发。
- 实现:品质位低字节与 104 `QDS` 同值直传;`CLIPPED`(削顶)由 104 侧映射为 `IV`;
死区 = **连续 2 个新测量点越限才确认**(基准恒为上次上送值,防慢漂移吃掉基准),品质翻转当拍直报。
- 判据:`UB/UC/IB/IC/IN` 五路恒 IV(品质驱动,V0.2 消除 H1 后零改动自动启用);死区两拍确认、
回摆撤销;`TEMP` 通道恒 NT。
### 5.12 保护功能(FR-PROT)🔶 软件就绪,判据现闭锁
**FR-PROT-1 元件框架**
- 需求:统一"越限 + 返回系数滞回 + 定时限"元件核,判据为**纯逻辑**(吃测量值 + 定值),可离线单测;
新增元件只加实例表一行,不改核。
- 实现:`App/algo/prot_elm.c`——数据驱动实例表 9 行;`prot_elm_eval()` 纯判据、`prot_elm_run()` 为
相量/频率到测量值的薄适配;延时用 `now_ms` 差值法(与节拍无关,精度不受 20 ms 节拍影响)。
- 判据:主机侧 47 断言全绿(1.01× 启动 / 0.9× 返回 / 滞回保持 / 欠量方向 / 投退 / 闭锁联动 / 时基回绕)。
**FR-PROT-2 元件清单(V0.1 有效子集)**
| 元件 | 测量量 | 判据要点 | 104 遥信 |
|---|---|---|---|
| 过流 Ⅰ/Ⅱ/Ⅲ 段 | `IA` | 越定值经延时动作,返回系数滞回 | `YX16/17/18`(并汇入 `YX15` 故障总) |
| 过负荷 | `IA` | 越定值告警(默认不跳闸) | `YX23` 过载 |
| 过压 / 欠压 | `UA` | 过量 / 欠量方向,各自延时 | **无遥信点**(仅 SOE + 日志) |
| 零序过压 | `UN`(外部零序专用通道) | 越定值经延时 | 汇入 `YX15` 故障总 |
| 过频 / 低频 | 测频结果 | 过量 / 欠量方向,默认 51.0 / 49.0 Hz | **无遥信点**(仅 SOE + 日志) |
> ⚠ **`YX15` 故障总的构成是 OC1∨OC2∨OC3∨3U0**——电压类与频率类元件**不在其中**,其动作目前只能从
> SOE、`[PROT]` 日志与 UI 记录看到。这是**点表层面的已知缺口**(送检点表要求独立分点时再补),
> 不要把它误读成"元件没动作"。
- **未复刻的元件**(建接口不使能,等条件):三相完整过流(B/C 相)、零序过流、方向/功率类、
PT/CT 断线、涌流制动、重合闸、故障隔离——原因见硬件规格档 §5 的 H1/H2,属需求档 §9.1.1「三相与同期」,
解除条件为 V0.2 硬件。
- 判据:`[PROT] elm` 边沿日志与 104 变位一致;未使能元件不误动。
**FR-PROT-3 判据节拍**
- 需求:判据独立于采集任务,周期稳定,且不得阻塞在存储 I/O 上。
- 实现:`protectTask` 20 ms 节拍(元件吃 50 Hz 基波有效值,相量每工频周更新,20 ms 足够);
纳入 IWDG 必需位;动作只投递 SOE(SPSC 环),**不做 NAND I/O**。
- 判据:静态最坏链 872 B / 配置 2048 B(余 57%);动作到 104 上送端到端 ≤120 ms。
**FR-PROT-4 闭锁(四源,任一命中即停走判据)**
| 闭锁源 | 判据 | 行为 |
|---|---|---|
| **未标定** | `UA`/`UN`/`IA` 的 `ADC_CAL_F_VALID` 未全部置位 | 整条判据链停走(产品构建默认态,等两点标定) |
| **采样故障** | `adc_health_flags()` 电平位(TPAFE/STM32 静默、序号倒退) | 元件全部复位 + 本拍不判据,自愈后自动恢复 |
| **定值非法** | 定值校验和(sum32 正/反码)复核失败 | 闭锁相关保护并告警,需重写定值解除 |
| **全局软压板退出** | `PS_GLOBAL_EN` = OFF(104 遥控 `YK12`) | 元件复位 + 停判据,压板恢复即重返联 |
- 判据:`YX31` 保护闭锁遥信如实反映;锁存类事件位(FRSTDATA/HAL 错误)**不构成**"当前测量不可信",
只登记不闭锁(口径见 [`docs/算法/继电保护功能复现规划.md`](../算法/继电保护功能复现规划.md) §3.5)。
**FR-PROT-5 出口(⬜ 未实现)**
- 需求:判据满足驱动跳闸/告警出口继电器,带保持/脉冲与回读自检。
- 现状:**V0.1 无出口硬件** → 保护"动作"= 遥信 + SOE + 日志;104 遥控 `YK1~4` 为否定确认。
- 判据(立项后):动作驱动对应出口;出口回路自检可检出断线;遥控执行有确认与回读。
### 5.13 定值与运行参数(FR-SET)✅
**FR-SET-1 三类参数**
| 类 | 地址范围 | 内容 | 属性 | 本产品状态 |
|---|---|---|---|---|
| 固有参数 | `8001H~800AH` | 终端类型 / 操作系统 / 制造商 / 硬件版本 / 软件版本 / 校验码 / 规约类型 / 型号 / ID / MAC | 只读 | ⬜ 未实现(字符串类无法短浮点上送,未知 IOA 应答) |
| 运行参数 | `8020H~8058H` | 遥测死区、PT/CT 变比、负荷越限/重载/过载报警、录波投退与定值、功率置负 | 可整定 | 🔶 已落死区 9 点 + `k/b` 覆盖 16 点(`0x8030~0x804F`);其余类别无消费者,留白不预埋 |
| 保护定值 | `8820H~8882H` | 过流 Ⅰ/Ⅱ/Ⅲ 段、零序过流、重合闸、低压/低频、励磁涌流闭锁、小电流接地等 | 可整定 | 🔶 **40 项**(Q10 定点):过流三段、过负荷、过压/欠压、零序过压、过频/低频 + 本地项(投退/返回系数/全局软压板) |
**FR-SET-2 定值模型与封存**
- 需求:定值改动必须**原子可见**——保护巡检任何时刻都看到自洽快照;范围校验失败整笔拒绝。
- 实现:`App/algo/prot_set.c`——影子区写好后重封 sum32 正/反码,再**单字交换**活跃区;
`prot_set_selfcheck()` 用 seqlock 式复核(提交撞见则本轮跳过);范围校验 + 整表 all-or-nothing 装载。
- 判据:主机侧 90 断言(含伪造自洽越界对抗样本);104 写定值与巡检并发无 µs 级误锁。
**FR-SET-3 持久化**
- 需求:定值掉电保持;档案损坏与版本换代必须能区分,损坏不得静默使用。
- 实现:`/prot_set.bin`(magic + 版本 + 计数 + 值区 + sum32 正/反码对),走临时文件 + rename 原子写;
写路径 = 脏标志 → 事件位 → defaultTask 落盘(失败保留脏位下轮重试);
装载在 `STORAGE_READY` 之前整表原子完成。
- 判据:非默认定值跨复位保持(台架 7.5 A 往返读回一致);**版本换代**(旧档)记 INFO,
**真损坏**记 ERROR + 自检留痕并回落出厂默认。
**FR-SET-4 运行参数段**
- 需求:死区与通道系数可由主站整定并持久化,且**系数只有一份真源**。
- 实现:`App/algo/run_par.c`——`[run_par]` 段 9 键(死区,毫单位 int32)+ `k/b` 覆盖 16 点;
`k/b` 写为 `adc_cal_set` **代理**(合法带 `ROM×[0.5,2.0]` 由执行段裁决),run_par 不存系数。
- 判据:`0x8027~0x802A`(过压/欠压告警段)永不被覆盖(路由 run_par 先、未命中落 prot_set);
越带写回否定确认;整档保存一趟覆盖 `[run_par]` 与被改写的 `[adc_cal]`。
**FR-SET-5 主站读写**
- 需求:主站可读定值、两段式写定值,失配/越界必须明确否定。
- 实现:`C_RD` 读 + `C_SE_NC` 两段写;地址 = prot_set `comm_addr`(**点表不另立地址表**);
`comm_addr = 0` 的本地项显式不可寻址(防 IOA=0 误命中)。
- 判据:读回值 = 写入值(COT 正确);本地项读回 COT=14 + EVI;执行段写失败不影响旧值。
### 5.14 SOE 与动作记录(FR-SOE)✅
**FR-SOE-1 记录格式**
- 需求:动作与状态变化必须带时标留痕,且**保护路径不得因写盘被拖慢**。
- 实现:`App/log/soe_rec.c`——64 B 定长记录 = 类型 + 元件号 + IV 位 + `seq` + `CP56Time2a` + epoch + 值 + 校验和;
六类边沿:元件动作 / 复归、软压板投 / 退、采样数据块 / 恢复。
- 判据:`/log/soe.log` 经 FTP 下载后逐条解码通过(校验和、`seq` 严格 +1、CP56 与 epoch 交叉一致)。
**FR-SOE-2 落盘与上送**
- 需求:写盘单写者;104 变位与落盘记录同源同时基。
- 实现:protectTask 投递 SPSC 环(深度 8,**禁 DBG**)→ `defaultTask` 排空落盘;
104 自发单点换 `M_SP_TB_1`(带 CP56 时标),总召/周期保持 `M_SP_NA_1`。
- 判据:动作路径零 NAND I/O(protectTask 静态链 872 B 改前后不变);104 时标 = 事件上送时刻,
与落盘记录(事件发生时刻)差 ≤ 一拍 + 排空延迟(≤50 ms)。
**FR-SOE-3 记录体系(✅ 软件面已落地;真机判据待台架)**
- 现状:七类文本记录(运行 / 自检 / 事故 / 告警 / 启动 / 操作 / 遥控)+ 二进制 SOE 已落地,
「记录类型 = 日志 ID 本身」(一类记录一个文件,记录体内无类型字段位);UI 六页已绑
(记录 5 页 + 运行报告,2026-10-28)。运行报告的四个数取自 boot_stats(SD2506 用户 SRAM
上电次数 + 本次复位原因快照)与 iec104_serv 的四个只读观测计数,经 info 帧下发
(口径 docs/UI设计/UI_数据供给设计说明.md §4.4)。
- 判据:五类记录各有写入点、可翻最近 20 条、空记录明确显示"无记录"、共用 reader——**软件面已满足**
(空态与四个范围标签由 66 张像素黄金钉住;模拟器侧这些文件没有写入方,只拍到空态)。
真记录翻页、掉电后保持、清除记录后归零、boot_count 自增与四态判定**待台架复核**
(docs/任务规划/登记簿.md §7)。
### 5.15 IEC 60870-5-104 上行(FR-IEC104)✅
本产品为**被控站(受控端)**:104 **TCP 服务器**,端口 `[comm]` 段 `port`(工厂默认 **2404**),主站主动连入。
点表口径见 §6.8,设计细节见 [`docs/子系统设计/IEC104上行设计说明.md`](../子系统设计/IEC104上行设计说明.md)。
**FR-IEC104-1 链路层**
- 需求:APCI U/S/I 帧、STARTDT/STOPDT/TESTFR、k/w 窗、t1/t2/t3;TCP 流重组与坏帧自恢复。
- 实现:`App/protocol/iec104/iec104_link.c`(纯逻辑:收=喂流、发=回调、时基注入);参数 k=12 / w=8 /
t1=15 s / t2=10 s / t3=20 s;序号 15 bit 回绕、超时一律差值法。
- 判据:主机侧 174 断言全绿;台架主站往返 STARTDT → 总召四组 → actterm 全通过。
**FR-IEC104-2 遥测上送**
- 需求:遥测短浮点、系数 1、点号落在 `0x4001` 起的遥测段(段基址见 复刻明细 §1);支持背景/总召、
周期(60 s)、死区与品质上送。
- 实现:数据源 = 测频与遥测快照(§5.11);品质直传,`CLIPPED` 映射为 `IV`;死区运行时副本(运行参数段可读写)。
- 判据:总召读回值与 UI 同源一致;死区两拍确认、品质翻转直报;削顶五路恒 IV。
**FR-IEC104-3 遥信上送**
- 需求:双位置遥信上双点、其余单点;变位突发 + 背景扫描;**无效点也入表并置 IV**。
- 实现:`iec104_pts.c` 点表(33 点)+ COV 判定;突发单点带 CP56 时标。
- 判据:`YX15~18/23/31` 为活数据;`YX1~14/19~22/24~30/32~33` 恒 IV(缺行比置 IV 更糟)。
**FR-IEC104-4 遥控**
- 需求:双点命令两段式(选择 `82H` / 执行 `02H`),失配或越界否定确认。
- 实现:`YK11` 信号复归 → 跨上下文复归请求;`YK12` 全局软压板 → 定值原子写;
`YK1~4` 开关、`YK13` 电池活化、`YK14` 解除闭锁 = 否定确认(无硬件)。
- 判据:`YK11`/`YK12` 两段正答且行为可观测(压板退出态跨复位保持);否定确认带正确 COT + PN。
**FR-IEC104-5 定值与运行参数读写**
- 需求:`C_RD` 读、`C_SE_NC` 两段写;地址即定值表 `comm_addr`;写成功即持久化。
- 实现:见 §5.13;路由顺序 = run_par 表先、未命中落 prot_set。
- 判据:读回一致、越界否定、断电重启保持。
**FR-IEC104-6 对时**
- 需求:主站 `C_CS` 对时写入设备墙钟;时标无效如实置 IV。
- 实现:CP56 → epoch ms → `sys_clock`(含 RTC 写回);epoch >2^32 显式拒绝回 PN。
- 判据:对时后日志时间戳跟随主站;`C_CS` 后 SOE 时标同步。
**FR-IEC104-7 响应时间(点表推荐值)**
| 参数 | 要求 | 本产品实测 / 设计 |
|---|---|---|
| 终端启动时间 | ≤100 s | 开机到 `net init: OK` ≈0.6 s,到 104 监听 <2 s |
| 104 连接 / 总召 | ≤3 s / ≤8 s | 连接 <1 s;总召分片 + 单轮 4 ASDU 预算,实测远小于 8 s |
| SOE 上送 | ≤5 s | 变位到上送 ≤120 ms |
| 遥控选择 / 撤销 / 执行 | ≤5 s | 同轮应答(ms 级) |
| 读定值 / 改定值预置 / 执行 | ≤10 s / ≤5 s | 同轮应答(ms 级) |
| 读文件目录 / 文件 | ≤30 s | ⬜ 未实现(文件传输未立项) |
| 录波文件存储 | ≤15 s | ⬜ 未验收(代码面 2026-10-29 已交付:/wave 段文件 + FTP 取回;本波按 1 kHz / 8 路 / 单段做;≤15 s 时限未测,80 点/周波属二期;判据见 docs/子系统设计/故障录波设计说明.md §10) |
**FR-IEC104-8 单连接与自愈**
- 需求:单连接语义(CH395F 单 socket);断链自动重听;网络重建(改 IP)后服务自愈;协议故障主动拆链。
- 实现:监听与数据通道复用同一 socket(EOF 时 net 层 auto_relisten,应用层不关 fd);
`net_get_generation()` 变化即重建;坏帧 / N(R) 倒退 / t1 超时 / 发送 3 连败 → 本端拆链促主站重连。
- 判据:断链后自动恢复监听;改 IP 后重建;**V0.1 为首到优先**(后连优先需 net 层支持,未实现)。
**FR-IEC104-9 本期未实现项(如实登记,均为 §9.1 复刻进度条目)**
| 项 | 原因 | 归属 |
|---|---|---|
| 多主站 / 双链路冗余 | 单连接 socket 语义;后连优先需 net 层扩展 | 规模型 |
| 归一化值 / 标度化值上送 | 点表要求短浮点(系数 1),无需另做 | 不适用 |
| 双点遥信带时标 | V0.1 双点恒 IV,无意义 | 随开入硬件(X3) |
| 定值区切换 / 定值文件下装 | 产品未定义多定值区(参考有 4 区) | 规模型 |
| 文件传输(目录 / 读文件 / 录波) | 录波已立项,段文件取回走现成 FTP(21 端口);**规约侧文件服务仍未做** | 规模型 |
| 固有参数 `8001H~800AH` | 字符串类无法短浮点上送 | 规模型 |
| 主站 IP 白名单 | 单 socket 上"拒绝"= 断链重听,可被扫描打成 SPI 风暴且易锁死真主站 | 不适用 |
### 5.16 屏板链路:串口主通道 + 网络镜像(FR-HMI)🔶
> **口径(2026-10-28 定版)**:参考产品的屏与键**不在主机板上**。主机侧源码里 5 个"碰硬件"的函数
> `KeyGet()` / `WriteScreenByByte()` / `LCD_LED_Update()` / `getRs485Key()` / `anyNewKey()`
> **全树只有声明、没有实现**(由屏板 BSP 库提供);按键轮询函数名即 `Key_Rs485_Process()`
> (注释"轮询从485传来的按键值");屏板插槽号 `CN_SLOT_DISP = 0`(注释"显示板插槽号固定为0")。
> 结论:**屏和键都在一块独立的屏板板件上,主机只负责"送帧 + 收键"**。本产品与参考同构,
> 不在本仓实现的只剩那块**屏板板件硬件本体**(独立工程 DTU-LCD);主机侧"送帧 + 收键"链路是**范围内待做**(§5.16)。
>
> **两条通道,串口为主、网络为镜像**:参考把**同一份显存**同时喂给两个出口——**串口屏板通道**
> (`LCD_LED_Update` 变化驱动 + `WriteScreenByByte` 500 ms 兜底)和**网络显示总线**(TCP 7003)。
> 也就是说"显示信息不只走网络,还走串口"。**本产品目前只有网络这一路**,串口屏板通道整条缺失
> (§5.16.9 第 11 条),这是本项复刻的最大缺口——没有它,产品无法脱离 PC 独立带屏运行。
>
> **屏板固件已到手(2026-10-28)**:屏板侧源码在 `D:\Code\DTU Code\DTU-LCD`(STM32F103ZE +
> UC1698U 160×160 单色 + **20 路状态指示灯** + 7 个物理按键),**物理层与帧格式全部可从代码确定**,见 §5.16.2、§5.16.3。
> 该链路**不是可选项**:屏板是**主动召唤方**,主机不应答则屏板**无画面、无灯、无键**。
#### 5.16.1 一份显存、两个出口(串口为主,网络为镜像)
参考的 HMI 只有**一份显存**(`g_tVsr[0].pwbyLCDMemory`,物理基址 `CN_VIDEO_ADDRESS_BASE = 0xC1F00000`,
非 cache 区,3200 B),但有**两个互不影响的出口**——**串口屏板通道是主通道,网络总线是镜像**:
| 出口 | 归属任务 | 调用 | 下行载荷 | 节拍 |
|---|---|---|---|---|
| **串口屏板通道**(主) | `tDisp`(`Dsp_Main`) | `LCD_LED_Update(ptr, 3200, ledbuf, 4)` | 显存 3200 B + 面板 LED **4 B** | **变化驱动**(`anyNewKey() \|\| LCD_UPDATE_FLAG`) |
| **串口屏板通道**(周期兜底) | `tRemoDisp`(`RDisp_Main`) | `WriteScreenByByte(byLcdMemory)` | 显存 3200 B | **500 ms**(`tTimerLCD`) |
| 网络显示总线(镜像) | `tRemoDisp`(`RDisp_Main`) | `RDisp_Proc` / `RDisp_Send` | `0xAA`/`0xBB` 帧(INIT/KEY/LCDMEM) | 客户端召唤驱动 |
上行(按键)同样是两条,最后在 `Key_Read()` 汇合:
| 上行通道 | 采集方式 | 载荷 | 与另一路的关系 |
|---|---|---|---|
| **串口屏板通道**(主) | `getRs485Key()` 轮询 + `anyNewKey()` / `ClearNewKeyEvent()` 事件 | 物理键号 `1~9`(实测 7 个物理键,§5.16.3 ②) | 映射为逻辑键值写入 `g_tKey` |
| 网络显示总线(镜像) | `RDisp_Proc_SaveKey` → `g_tRemoteKey` | 键值位掩码 1 B | 写入 `g_tRemoteKey` |
| 汇合点 | `Key_Read()`(`Key.c:199`) | — | **先取远程、再被本地覆盖**——两键同时挂起时**本地(串口)优先** |
**串口通道的物理层与协议以屏板源码为准**(§5.16.2 / §5.16.3)。参考主机侧源码给不出物理层,
只能给出**调用契约**——能确定的只有这几项:
| 契约项 | 已确定的内容 | 依据 |
|---|---|---|
| 下行粒度 | 3200 B 整帧 + 4 B LED,**一次调用一帧**(无分片参数) | `LCD_LED_Update(ptr,len,ledbuf,ledbuflen)` 签名 |
| 上行粒度 | 单字节物理键号,`0` 表示无键 | `getRs485Key()` 返回 `uint8_t`,`Key.c:176` 判 `key == 0` 即返回 |
| 事件语义 | 有"新键事件"与"清除事件"两个动作 | `anyNewKey()` / `ClearNewKeyEvent()` |
| 屏板地址 | 板件槽号 **0**(`CN_SLOT_DISP`) | `Key.c:8` 注释"显示板插槽号固定为0" |
| 屏板初始化 | `Lcd_init_display(0xC1F00000)` 由 `LocalDisp_StartTask()` 调用,**不经过任何 UART 打开动作** | `Disp_Main.c:56` |
#### 5.16.2 串口通道:物理层与帧格式(屏板源码实测)
屏板固件工程 `DTU-LCD` 已在手(STM32F103ZE / 512 KB Flash / 64 KB RAM / 72 MHz,UC1698U 160×160 单色),
**链路协议逐字节可确定**,本节即**主机侧必须实现的契约**。
> ⚠ **角色与直觉相反:屏板是 Modbus 主站,主机是从站。** 屏板主动发请求,主机在**请求后**应答;
> 主机**从不主动推送**,也不允许主动推送(半双工总线,谁先说谁占用)。
> 这与 §5.16.4 的 TCP 7003 通道方向相反——网络通道是**客户端召唤**,串口通道是**屏板召唤**。
**物理层**
| 项 | 屏板侧实测值 | 依据 |
|---|---|---|
| 接口 | RS-485 **半双工** | `rs485.c` |
| 引脚 | TX = PA2、RX = PA3、**DE = PA1** | `rs485.h` |
| DE 极性 | **低 = 发送,高 = 接收** | `RS485_SEND_ENABLE()` 拉低、`RS485_RECEIVE_ENABLE()` 拉高 |
| 波特率 | **700000** bps | `#define BAUDRATE 700000` |
| 数据格式 | 8 数据位 / 无校验 / 1 停止位,无流控,**16 倍过采样** | `rs485.c` 初始化 |
| 接收 | `HAL_UARTEx_ReceiveToIdle_IT`(空闲断帧),**DMA 版本被注释掉** | `rs485.c:192-195` |
| 发送 | `HAL_UART_Transmit(..., 1000)` 阻塞发完,随即切回接收 | `RS485_SendBuff()` |
| 缓冲 | RX **4208 B**、TX **8 B** | `UART_RX_LEN` / `UART_TX_LEN` |
> **700 kbaud 在两种候选时钟下都零误差**:16 倍过采样(`OVER8 = 0`)时
> `USARTDIV = fCK / (16 × 波特率)`,写入值 `BRR = (整数部分 << 4) | (小数部分 × 16)`。
> - 屏板链路的**默认端口 USART1 挂 APB2 = 84 MHz**:`84 000 000 / (16 × 700 000) = 7.5` →
> 整数 7、小数 8 → **`BRR = 0x0078`**,误差 **0 %**。
> - 若改用 APB1 上的实例(UART4 / USART2 / USART3,42 MHz):`42 000 000 / (16 × 700 000) = 3.75` →
> 整数 3、小数 12 → **`BRR = 0x003C`**,误差 **0 %**。
>
> 若改时钟树或改用 HSI,**必须重算**——700 kbaud 下 2 % 误差已足以丢帧。
>
> **屏板自己那侧不是零误差**:它的串口挂 APB1 = 36 MHz,`UART_BRR_SAMPLING16(36e6, 700000)` →
> `BRR = 0x33`(USARTDIV = 3.1875)→ 实跑 **705 882 bps,比标称高 0.84 %**;它源码里「手动写精确 BRR」
> 那段注释后面**没有对应代码**。与主机侧的 0 % 合起来 0.84 %,8 位数据 / 1 停止位(10 bit/帧)下在容差内;
> 但**位宽与整帧耗时的实测判据要按 705 882 算,不是按 700 000**(1 字符 ≈14.17 µs、整帧 ≈59.6 ms)。
>
> **端口裁定(用户决定,2026-10-28):屏板走 USART1,日志口让位。**
> 屏板链路改用 **USART1(PA9/PA10)**——这条通道是 **MCU 引脚直出 TTL,不经过板上任何收发器**,
> 因此**收发器速率上限问题不复存在**:本板 485 通道上的 `U15 = TP8485E-SR` 只有 250 kbps
> (`docs/芯片手册/TP8485E-SR.md`)这件事,对屏板链路**不再构成约束**。
> 作为交换,**调试日志迁到 RS-232 通道**(V0.1 板上 RS232#3 = **USART2 PD5/PD6**,
> 备选 RS232#1 = UART5 PC12/PD2,两者都经隔离器 → `U13` TPT3232E → CN1),
> 迁移已实施——控制台现走 UART5/RS232#1,见下方"主机侧端口重排"。
>
> **PA10 一直是接线的**(2026-10-28 用户确认)→ 屏板链路**不需要额外飞线**。
>
> ORE 锁存的充分条件与接线无关——**RX 使能了却从不读 `DR`**:线上只要出现
> 任意一个字节或一个噪声沿,`RXNE` 置位后没人读,下一个事件就把 **ORE 锁存**;而 ORE 只能用
> "先读 SR 再读 DR"清除,于是**永不自愈**。这条对本实现有一条硬要求:
> 屏板链路要用 RX,就必须真的消费 `DR`,并在 `HAL_UART_ErrorCallback` 里**清 ORE 后重挂接收**
> (否则一次噪声就永久锁死,症状与"屏板不回帧"一模一样)。
**帧 A:召唤 / 按键(屏板 → 主机,固定 6 B)**
| 偏移 | 长度 | 字段 | 取值 |
|---|---|---|---|
| 0 | 1 | 从站地址 | 固定 `0x01`(**主机的从站地址**,不是屏板地址) |
| 1 | 1 | 功能码 | 固定 `0x03`(读保持寄存器) |
| 2 | 1 | 命令类型 | `0x00` = `KEY_CMD`(按键事件,**立即**召唤)
`0x01` = `RT_CMD`(周期刷新,**280 ms** 一次:`RT_count > 55`,第 56 拍 × 5 ms) |
| 3 | 1 | 按键值 | `KEY_CMD` 时有效(见 §5.16.3 键值表);`RT_CMD` 时**恒为 `0`**(按键发完即清 `key_bk`,见 `RS485_Process()`) |
| 4 | 1 | CRC-16 **低**字节 | CRC-16/MODBUS |
| 5 | 1 | CRC-16 **高**字节 | 同上 |
CRC-16/MODBUS(多项式 `0xA001` 反转式、初值 `0xFFFF`、查表法),
**只覆盖前 4 字节**,**低字节在前**——这是标准 Modbus RTU 的线上顺序(教科书校验值:数据 `123456789`
→ `0x4B37`,线上写 `37 4B`)。
> ⚠ **屏板源码里的字节序注释是反的,别照抄**:`Calculate_CRC()` 返回的**不是**标准 CRC 值,而是高低
> 互换后的「线上字」(`123456789` 它返回 `0x374B`)。于是 `RS485_RefreshScreenCMD()` 里那句
> `cmdbuf[4] = crc>>8`(注释写"CRC校验高字节")实际写出去的是**低字节**——两处命名都反,**线上字节是对的**。
> 主机侧按「标准值 + 低字节在前」实现,两侧字节逐一相同;若照字面写成"高字节在前",屏板会把每一帧
> 都判成 CRC 错,而它的超时判定只看"收没收到一整包"、不看 CRC,现场只表现为画面不刷新,
> 极易误判成波特率或时序问题。实测向量与实现见 `Drivers/BSP/ScreenLink/sb_proto.c` 文件头。
**帧 B:图像应答(主机 → 屏板,固定 4208 B)**
| 偏移 | 长度 | 字段 | 说明 |
|---|---|---|---|
| 0 | 3200 | `Picture[]` | 显存位图:160 行 × **20 B/行**,**1 bpp,MSB 在前**,bit=1 → 白 |
| 3200 | 4 | `info[0..3]` | 普通图:**LED 状态,32 位位掩码,小端**(`info[0]` = bit0~7)
LOGO 图:`info[0..1]` = 宽、`info[2..3]` = 高(均大端) |
| 3204 | 1 | `info[4]` | LOGO 魔数高字节 `0x89` |
| 3205 | 1 | `info[5]` | LOGO 魔数低字节 `0x45` |
| 3206 | 2 | `info[6..7]` | 保留,屏板不读 |
| 3208 | 998 | 保留填充 | 屏板不读,**但参与 CRC 计算**,须固定填充(建议 `0x00`) |
| 4206 | 2 | CRC-16 | 覆盖 `[0, 4205]`,**低字节在前**(与帧 A 同一口径,见上方的字节序说明) |
> ⚠ **帧长以代码为准:4208 B**(屏板 readme 与 `BMP_LEN` 写的"3208 字节"不作准):接收侧实参是
> `Check_CRC(DR, UART_RX_LEN)`,`UART_RX_LEN = 4208` → CRC 取 `DR[4206]/DR[4207]`、覆盖 `DR[0..4205]`。
> 有效载荷只有前 3208 B,其后 998 B 为凑齐 CRC 窗口的填充;**主机只发 3210 B 时屏板会在
> `DR[4206]` 读到陈旧字节,CRC 必失败并静默丢弃**。**已台架与真屏板实测固化**(屏板出图 = 端到端证据,`docs/子系统设计/屏板链路设计说明.md` §9.1)。
> ⚠ **应答帧不是合法 Modbus RTU 响应**:没有地址、没有功能码、没有字节数域,是一段裸二进制块。
> 因此**不能套用现成 Modbus 从站栈**,只能按上表手工组帧。
> ⚠ **同一 4 字节双解释**:`info[0..3]` 在普通图里是 LED 位掩码、在 LOGO 图里是宽高。
> 区分只靠 `info[4..5] == 0x89 0x45`。**先判魔数再解释这 4 字节**,顺序不能反。
**时序与预算**
| 参数 | 屏板侧常量 | 实际时间 | 含义 |
|---|---|---|---|
| 基准节拍 | 5 ms 定时器 | — | `Process_Count()` 每 5 ms 递增 |
| 周期召唤 | `RS485_RT_COUNT_MAX = 55`,判据 `RT_count > 55` | **280 ms**(第 56 拍 × 5 ms) | 无按键时每 280 ms 召唤一次;屏板 readme 与常见口径写的 275 ms 是按 55×5 算的 |
| 应答超时 | `RS485_ACK_COUNT_MAX = 40`,判据 `ACK_count > 40` | **205 ms**(第 41 拍 × 5 ms) | 到点只置「可以重发」标志 + 累计一次超时——**不丢已收字节、不清缓冲、不重启串口**;计时起点是召唤帧**最后一位的 TC** |
| 迟到的应答 | 收帧处理与超时判定**相互独立**,帧里没有序号 | — | 超过 205 ms 才到的完整帧**照样显示**(当成下一轮的应答,无从区分);代价 = 多一轮画面滞后 + 一次超时计数 |
| 有按键待报时 | 收到帧或超时即打开发送闸门 | 最早 **约 205 ms** 就可能再来一帧 | 上一答还在总线上 → **双向对撞**;且键**只发一次不重试**(发完立刻清备份)→ 错过那一轮 = **丢键** |
| 收到任意一包 | 无论 CRC 是否通过 | — | 超时阶梯全部清零(`ACK_count` / `ACK_OverTimeCnt`)→ **主机把帧发断,屏板也不报超时**,只表现为画面不更新 |
| 连续超时 | `RS485_ACK_OVERTIME_MAX = 5` | 5 次 | 连续 5 次超时 → 强制允许重发(**不重启串口**,重初始化被注释) |
| 屏板自复位 | `sysRunTime > 1 000 000` | **≈5000 s(83 min)** | 屏板**自己**无条件 `HAL_NVIC_SystemReset()` |
**真正的硬约束不是 200 ms,是下面三条**(按风险排序,均出自屏板源码):
1. **整帧零间隙**:屏板按「总线空闲 1 个字符时间」断帧,而它的串口 IP **没有接收超时寄存器**可调
(该系列 USART 只有 SR / DR / BRR / CR1 / CR2 / CR3 / GTPR)。705 882 bps 下 1 字符 ≈14.17 µs,
**间隙 ≥2 字符(约 28 µs)必把一帧切成两帧**,两半都过不了 CRC——**这条才是卡主机实现方式的约束**。
2. **必须正好 4208 B**:CRC 固定取 `DR[4206]/DR[4207]`、覆盖 `DR[0..4205]`,实收长度被丢弃 →
**短帧永远通不过**(998 B 填充是承重的);超过 4208 B 会让它把正在处理的那一帧撕裂(收满即回调、缓冲从头再填)。
3. **绝不让它溢出**:一旦 ORE 就**永久收不到**(它错误回调里的重启被注释掉),而屏幕上没有任何通信状态指示
(运行灯翻转被注释、故障显示函数无人调用)→ 唯一症状是「画面与状态灯一起冻死」,只能断电重上或等
83 min 自复位(自复位后是**白屏**,没有开机 LOGO)。此外它下发 LOGO 时要写 Flash + 4×500 ms 延时,
那段时间既不能处理也不能应答,是最容易撞上本条的窗口。
**所以主机侧整帧必须一次性交给 DMA + 发送完成中断,不要逐块阻塞发**:任务态轮询随时可能被几十 µs 的
ISR 抢占,一抢就撞第 1 条,而后果在屏板侧完全不可见。时间预算 4208 × 10 bit ÷ 705 882 ≈ **59.6 ms**
(按标称 700 000 算是 60.1 ms),占 280 ms 周期约 21 %;「≤200 ms 内发完」仍是我们对自己的要求——
理由是**不与下一轮召唤撞总线、不多一轮画面滞后**,不是因为屏板会把迟到的帧判废。宿主与起发方式见
`docs/子系统设计/屏板链路设计说明.md` §1(`uiTask` 组帧 + USART1 空闲中断点 DMA 起发)。
**主机侧端口重排(2026-10-28 用户裁定)**
| 串口 | 引脚 | V0.1 板上通道(`docs/硬件平台/V0.1_网表详细分析报告.md` §4.2) | 重排后用途 |
|---|---|---|---|
| **USART1** | PA9 / PA10 | 调试口 + **U5 直连 TTL**(ST_TX0/RX0),**MCU 直出、不经任何收发器** | ✅ **屏板链路**(700 kbaud) |
| **USART2** | PD5 / PD6 | **RS232#3**(U10 隔离 → U13 TPT3232E → CN1) | 已初始化、固件未占用(日志最终选了 UART5) |
| UART5 | PC12 / PD2 | **RS232#1**(U9 隔离 → U13 → CN1) | ✅ **调试日志**(115200,已迁移、台架在用;端子见 `docs/子系统设计/屏板链路设计说明.md` §3.2) |
| UART4 | PC10 / PC11,DE = **PA15** | **RS485#4**(U11 隔离 → U15 → A/B → CN1) | 冻结簇,本项不碰 |
| USART3 | PB10 / PB11 | U5 直连 TTL(ST_TX2/RX2) | 空闲 |
**为什么是 USART1**:它是唯一一条**不经收发器、MCU 引脚直出**的通道,而 700 kbaud 恰好卡在收发器上
(`U15 = TP8485E-SR` 只有 250 kbps,`docs/芯片手册/TP8485E-SR.md`)。换到这条线上,速率问题从根上消失,
也不用改任何硬件。UART4 保持 RS-485 冻结簇不动——`rs485_init(&g_rs485, &huart4, …)` 与 V0.1 一致,**无需摘除**。
**这次重排的落地形态(2026-10-29 已实施)**:控制台端口已收口成 `App/log/console_log.c` 的
**`CON_UART*` 单点宏**(句柄 / IRQn / 时钟 / 自愈 `con_uart_reinit()` 一体,现指向 UART5/RS232#1),
换口只改那一段——当初"日志口迁移不是一行改动、建议收口成一处可配置端口宏"的顾虑由此闭环。
> ⚠ **TTL 直连没有方向控制**:主机侧不再有半双工方向切换,建立时间、"等 TC 收尾"一类约束全部不适用。
> ⚠ 主机 RX 只需吃下 **6 B 召唤帧**(不像屏板要吃 4208 B):16 B 缓冲 + 空闲中断断帧就够——TX / RX 是两根独立线,无自发自收。
#### 5.16.3 屏板侧行为:状态灯 / 按键 / 背光 / LOGO
本节描述**屏板固件实际做什么**——决定主机侧要下发什么、以及哪些语义必须由产品自己定义。
**① 状态指示灯(4 B 位掩码下行)**
屏板有 **20 路 LED**,由主机每帧的 `info[0..3]` 控制。解码规则(`LED_Printf`):
```c
leddata = (ledbuf[3]<<24) | (ledbuf[2]<<16) | (ledbuf[1]<<8) | ledbuf[0]; /* 32 位小端 */
LED_POW(LED_ON); /* ID 0 无条件常亮 */
for (i = 1; i < LED_COUNT; i++) /* LED_COUNT = 20 */
LED_Set(i, (leddata & (1UL << i)) ? LED_ON : LED_OFF);
```
- **ID 0 = 电源灯,硬件常亮,不受位掩码控制**(`leddata` 的 bit0 被忽略)。
- **ID 1~19 一一对应 bit1~bit19**;bit20~bit31 未使用,填 0。
- 全部**低电平点亮**(`LED_ON` = 低)、推挽输出、内部上拉、高速模式。
- **只在普通图像帧到达时刷新**;LOGO 帧走另一分支,**不动 LED**。
屏板侧 LED 引脚表(`led_config[20]`,仅备查——主机不直接驱动):
| ID | 引脚 | 备注 | ID | 引脚 | ID | 引脚 | ID | 引脚 |
|---|---|---|---|---|---|---|---|---|
| 0 | PC8 | 电源灯,常亮 | 5 | PC11 | 10 | PC7 | 15 | PC12 |
| 1 | PC9 | 运行灯 | 6 | PD0 | 11 | PA8 | 16 | PD1 |
| 2 | PA9 | | 7 | PD2 | 12 | PA10 | 17 | PD3 |
| 3 | PA11 | | 8 | PD4 | 13 | PA12 | 18 | PD5 |
| 4 | PA15 | | 9 | PD6 | 14 | PC10 | 19 | PD7 |
**对本产品的含义**:§5.8 的主板 6 路 LED(`LED1~6`)与屏板 20 路指示灯是**两套独立输出**,
语义不同——主板 LED 表"设备自身状态",屏板指示灯是"操作员面前的状态/告警灯组"。
> ⚠ **位掩码 → 业务状态的映射表,参考源码里没有**。参考的 `LCD_LED_Update(ptr, 3200, ledbuf, 4)`
> 只证明"主机侧填这 4 B",填哪几位对应哪个业务状态在**未实现的屏板 BSP 里**。
> 因此"哪几号灯表哪个状态"**必须由本产品定义**(建议按 §5.8 的 6 路语义扩展到 19 路,
> 并保留 1~2 路作屏板自检/通信状态)。**这是本项唯一需要"新设计"而非"照抄"的地方。**
**② 按键上行**
| 项 | 屏板侧实测 |
|---|---|
| 库 | MultiButton,只绑 `BTN_SINGLE_CLICK`(单击事件) |
| 扫描 | 5 ms 定时器内 `Button_Ticks()` |
| 电气 | 输入模式、**无内部上拉/下拉**(依赖外部)、低电平有效 |
| 引脚 | `KEY_ENTER`=PD12、`KEY_UP`=PD11、`KEY_DOWN`=PD10、`KEY_LEFT`=PD13、`KEY_RIGHT`=PD8、`KEY_ESC`=PB15、`KEY_RESET`=PB3 |
| JTAG | PB3 需 `__HAL_AFIO_REMAP_SWJ_NOJTAG()` 才能当普通 GPIO |
| 回调 | 任意键按下 → `BackLight_ON()` + 更新 `keyStatus`,主循环转交 `RS485_Process()` |
键值枚举(`KEY_TYPE`,**即帧 A 偏移 3 的 `key` 字节**):
| 值 | 名称 | 值 | 名称 |
|---|---|---|---|
| 0 | `KEY_NONE`(无键) | 5 | `KEY_ADD` |
| 1 | `KEY_ENTER` | 6 | `KEY_DEC` |
| 2 | `KEY_UP` | 7 | `KEY_LEFT` |
| 3 | `KEY_DOWN` | 8 | `KEY_RIGHT` |
| 4 | `KEY_ESC` | 9 | `KEY_RESET` |
| | | 10 | `KEY_FACTORY` |
> ⚠ **两套键号只在 1~8 对齐,9/10 语义不同**,必须显式建映射:
>
> | 语义 | 屏板 `KEY_TYPE`(帧 A 的 `key`) | 参考主机 `getRs485Key()` 物理键号 |
> |---|---|---|
> | 确认 ENT | 1 | 1 |
> | 上 UP | 2 | 2 |
> | 下 DOWN | 3 | 3 |
> | 取消 ESC | 4 | 4 |
> | 加 ADD | 5 | 5 |
> | 减 SUB | 6 | 6 |
> | 左 LEFT | 7 | 7 |
> | 右 RIGHT | 8 | 8 |
> | **9** | **`KEY_RESET`(复位键)** | **F1** |
> | **10** | **`KEY_FACTORY`(工厂模式键)** | **`F1\|ENT` 组合** |
>
> 帧 A 的 `key` 字节**只能按屏板枚举解释**。主机收到 9/10 时**不得**按 `F1` / `F1|ENT` 处理。
> 若产品要求保留 F1/F2 语义,只有两条路:**改屏板固件** 或 **在主机侧加语义映射表**——二者选一,**须先定**。
> ⚠ **`key_configs[]` 只有 7 项,且与枚举不自洽**:表内第 7 项为 `{PB3, KEY_RESET}`(注释却写"加键");
> 枚举有 `KEY_ADD`(5) / `KEY_DEC`(6) / `KEY_FACTORY`(10),但 **`key_configs[]` 没有映射任何 ADD/DEC 物理键**。
> 即**实物只有 7 个物理键**(上/下/左/右/确认/取消/复位),ADD/DEC/FACTORY 有枚举无入口。
> 屏板 readme 的按键表列了 8 个键,与代码不符——**以 `key_configs[]` 的 7 项为准**。
> 这直接影响 §5.16.9 第 8 条"补 F1/F2"是否成立:**屏板上根本没有 F1/F2 物理键**,
> 网络通道的 8 键表是给**远程工具**用的;串口通道**只可能收到 `1/2/3/4/7/8/9`** 这 7 个值
> (`5`/`6`/`10` 有枚举无入口)。
**③ 背光**
- 任意键按下 → `BackLight_ON()`(按键回调里,不在 5 ms 定时器内)。
- 5 ms 定时器每拍调 `BackLight_Close()`,超时熄灭(时长在 `160160D.C`,**屏板自管**)。
- LOGO 帧到达时也会 `BackLight_ON()`。
- **主机侧不参与背光控制**——参考主机的 `Lcd_BackLight()` / 空闲 120 s 熄灭在**主机 BSP**里,
而屏板固件自己就管背光,**两条路径不能同时启用**。本产品按"屏板自管"实现,主机不做(§5.16.9 第 14 条)。
**④ LOGO 与 Flash**
- 魔数命中(`info[4]==0x89 && info[5]==0x45`)→ 按 LOGO 处理:
`Picture` 存入屏板 Flash **`0x0803F000`(扇区 126)**,宽高取 `info[0..1]` / `info[2..3]`(大端),
然后点背光、`ClearScreen()`、`LOGO_Printf()`、延时 **2 s**(4 × `delay_ms(500)`)。
- 上电时 `BMP_Get()` 从同一地址读回并显示。
- **LOGO 帧不刷新 LED、不进入常规画面**;一帧 LOGO 会**永久覆盖**屏板上一次的 LOGO。
- **LOGO 下发已出局、不实现**:主机侧不提供该操作,总线上不会出现 `info[4..5] = 0x89 0x45` 的帧。
**⑤ 屏板自身的健壮性机制**
| 机制 | 实现 | 对本产品的影响 |
|---|---|---|
| CRC 错计数 | 连续 ≥3 次错 → 计数清零(**重初始化串口被注释掉**) | 错帧静默丢弃,链路**不会自愈**,只能靠下一次召唤重试 |
| 应答超时 | 第 41 拍 = **205 ms**;连续 5 次 → 只是打开重发闸门(**重初始化被注释掉**) | 主机长时间不应答**不会锁死**屏板,恢复后自动续上。但两个坑:① **收到任意一包就清零计数、不看 CRC** → 主机把帧发断时屏板也不报超时,只表现为画面不更新;② 真会永久锁死的是**接收溢出**(§5.16.2 硬约束 3),不是超时 |
| 系统自复位 | `sysRunTime > 1 000 000`(≈5000 s)→ `HAL_NVIC_SystemReset()` | **每 83 分钟屏板重启一次**,约 1 s 空窗 |
| 看门狗 | 5 ms 定时器内 `Clear_Watchdog()` | 主循环卡死 → 屏板复位 |
| DMA 接收 | 屏板提交记录明写"DMA 接收的问题没有解决" | 长帧接收可靠性**未经充分验证**,台架必须压测 |
> ⚠ **5000 s 周期性自复位必须被主机容忍**:复位期间屏板约 1 s 不响应召唤。
> 主机的链路监视**不能**把这种周期性空窗判成"屏板掉线",更不能联动告警/保护。
> 判据建议:连续 **≥ 10 s** 无任何应答才置"屏板离线"。
**待与屏板固件方确认的 4 项**(不影响本产品先开工,但验收前必须闭环):
1. `Check_CRC` 用缓冲区容量而非实际接收长度,**短帧会被误判**;是否计划改为传 `Size`。
2. 键值 9 / 10 是否要改成 `F1` / `F1|ENT`(对齐参考),还是保持"复位 / 工厂"语义。
3. `key_configs[]` 是否要补 ADD / DEC 物理键(当前 7 键,枚举 11 项)。
4. 屏板 4208 B 接收缓冲与 700 kbaud 在长线(> 50 m)下的误码率实测数据。
#### 5.16.4 链路 B 帧格式(逐字节)
| 项 | 规格 |
|---|---|
| 传输 | TCP **7003**;设备为 **Server**(`TcpAddServer` + `TcpAddConnection(..., EN_NET_MODE_SERVER, RDisp_MsgRecv)`);目标 IP 填 `255.255.255.255` = 接受任意客户端 |
| 连接参数 | `TcpSetConnPara(iTcpNo, 10240, 10240, 0, 30, 5)`;收发缓冲各 **10240 B** |
| 建链条件 | `getnetport() != 1` 才建 Server(该函数源码无注释,疑为单网口板件跳过本链路;**未验证,勿照抄此条件**) |
| 帧结构 | `[TAG 1B][CMD 1B][LEN_H 1B][LEN_LO 1B][DATA nB][CRC 1B]` |
| TAG | `0xAA` = 客户端→设备(`CN_RDISP_C_TAG`);`0xBB` = 设备→客户端(`CN_RDISP_R_TAG`) |
| LEN | **数据段**长度(不含 4 B 头与 CRC),大端 |
| CRC | 数据段逐字节**异或**(`RDisp_CalCrc`),**不覆盖头与 CRC 自身** |
| 最长帧 | `CN_RDISP_FRAME_LEN_MAX = 9800`(数据段上限) |
| 接收定界 | 逐字节找 `0xAA`(最多 10240 次防死循环)→ 再收 3 B(CMD+LEN)→ `LEN > 10237` 视为噪声重新同步 → 再收 `LEN+1` B(数据+CRC)→ 返回 `LEN+4` |
> **接收侧不校验 CRC**:`RDisp_MsgRecv` 只做定界;CRC 只在 `RDisp_Proc_LcdMem` /
> `RDisp_Proc_SaveKey` 内比对,且 **INIT / KEEPLIVE 帧根本不校验**。这是参考的既有行为,
> 本产品在全帧校验上更严(见 §5.16.9 第 10 行),不作退让。
#### 5.16.5 命令集与数据段布局
| CMD | 名称 | 客户端请求 | 设备应答 | 备注 |
|---|---|---|---|---|
| 0 | `KEEPLIVE` | 空 | **不应答** | 源码 `return`,注释"无 keep alive 报文";仅重置 1 分钟通信计时 |
| 1 | `INIT` | 空 | **16 B**(见下表) | 工具召唤屏幕参数 |
| 2 | `KEY` | `[键值 1B]` | 空(LEN=0) | 键值写入 `g_tRemoteKey`,`byKeyValid = EN_KEY_FLAG_NEW` |
| 3 | `LCDMEM` | `[起始偏移 4B 大端]` | `[起始偏移 4B][显存 nB]` | 支持断点续传;拒绝语义见 §5.16.6 |
| 4 | `ACK` | — | — | 参考 `RDisp_Send` 中为空分支,未使用 |
| 5 | `NCK` | — | — | 同上,未使用 |
| `0xFF` | `IDLE` | — | — | 内部空闲码,不上线 |
**INIT 应答数据段(16 B,即 `RDisp_Form_InitInf` 的单色子集)**:
| 偏移 | 长度 | 字段 | 取值 |
|---|---|---|---|
| 0 | 2 | LCD 像素宽(大端) | `UI_SCREEN_W` = 160 |
| 2 | 2 | LCD 像素高(大端) | `UI_SCREEN_H` = 160 |
| 4 | 4 | 显存字节数(大端) | `UI_FB_SIZE` = `(160*160)/8` = **3200** |
| 8 | 8 | 键值表 `F1/F2/U/D/L/R/ENT/ESC` | `0x04/0x80/0x02/0x40/0x10/0x08/0x20/0x01` |
> **为什么是 16 B 而不是参考的 29 B**:参考的 29 B 里有 **13 B 颜色信息**(1 B 颜色位
> `CN_RDISP_LCD_TYPE` + 12 B RGB `CN_RGB_RED/GREEN/BLUE`),而这 13 B 在**参考自己的构建里就是死字段**:
> `RemoDispBus.h:46` 的 `CN_RDISP_TYPE_MONO = TRUE` → 链路显存本身就是 1 bpp;三个 RGB 值是编译期
> 常量(每次召唤发同样的 12 B);`RDisp_LcdColor_To_Mono()` 对 `CN_MODE_GREY_MONO` 直接 `return TRUE`,
> RGB565 分支永不进入(§3.1.1)。本产品屏板同为单色 1 bpp(`DTU-LCD` 的 `ScreenPrintf()` 只吃 1 bpp
> 点阵),这 13 B **既无接收方也无语义** → **整段删除,不对齐多色协议**:29 − 13 = **16 B**。
**键值是位掩码,不是枚举**——可组合下发:`CN_KEY_F1|CN_KEY_ENT = 0x24`(`Key.c` 物理键号 `10` 即此值)。
`CN_KEY_ADD=0x82`、`CN_KEY_SUB=0x84` 只在**串口屏板通道**使用,不进 7003 键值表。
#### 5.16.6 显存上送的续传与拒绝语义
`RDisp_Form_LcdMem` / `RDisp_Proc_LcdMem` 的完整规则:
1. **分片**:`dwSendBase + 9800 > 3200` 成立 → 本帧发 `3200 - dwSendBase` 字节;否则发 9800 B。
因 `MEMSIZE = 3200 < 9800`,**实际恒为单帧 3204 B**(4 B 偏移 + 3200 B 位图);
分片逻辑为"显存变大"预留,当前不触发。
2. **推进**:发完后 `dwSendBase += dwLen`;若 `>= MEMSIZE` → **`bNeedFlash = TRUE`**、`dwSendBase = 0`。
3. **拒绝**:召唤时若 `bNeedFlash` 为真(上一帧刚发完、新图还没拷贝)→ 返回 **`-1`**,且把
`dwSendBase` 清 0,**不应答**。含义是"**一帧图像只允许被完整上送一次**",客户端要下一帧
必须等主机完成下一次刷新。
4. **越界**:`dwSendBase >= MEMSIZE` → 返回 **`-2`**,不应答。
5. **成功**:返回 `1`,置 `wSendCode = LCDMEM`,由 `RDisp_Send` 组帧发出。
6. **刷新来源**:`RDisp_Main` 每轮检查 `bNeedFlash`,为真则 `memcpy(byLcdMemory, __Lcd_Pub_GetScreenAddr(0), 3200)`
→ `RDisp_LcdColor_To_Mono()` 转单色 → 清 `bNeedFlash`、`dwSendBase = 0`。
> **`RDisp_LcdColor_To_Mono()` 这一步本产品不需要(参考自己也不走)**:该函数按 `CN_LCD_TYPE` 分派,
> 而本构建 `CN_LCD_TYPE = CN_MODE_GREY_MONO` → 命中第一分支**直接 `return TRUE`**,不做任何转换;
> 只有 `CN_MODE_COLOR_16B` 才调 `RDisp_Lcd16B_To_Mono()` 做 RGB565 → 单色,那条路是**死代码**。
> 本产品的 `g_ui_fbuf` **本来就是 1 bpp 单色**(LVGL 单色渲染,`UI_FB_SIZE = 3200`),拷贝即可送,
> **没有"颜色 → 单色"的转换环节**。这是"不对齐多色协议"带来的简化之一。
> **为什么必须有 `bNeedFlash` 握手**:显存由显示任务异步改写,若允许客户端随时按偏移拉取,
> 跨两次刷新的位图会在客户端拼成**撕裂帧**。参考用"整帧一次性 + 拒绝重拉"消除这个窗口,
> 本产品当前无此保护(§5.16.9 第 4 行)。
#### 5.16.7 按键注入与状态机
| 项 | 规格 |
|---|---|
| 存储 | `tagRKeyCtrl g_tRemoteKey { byUsrFlg, byKeyValid, byKeyValue, bUseRkey }` |
| 有效性 | `EN_KEY_FLAG_NULL=0`(无新键)/ `EN_KEY_FLAG_NEW=1`(有新键)/ `EN_KEY_FLAG_SAM=2`(采样中) |
| 注入 | 收 KEY 帧 → `byKeyValue = DATA[0]`、`byKeyValid = EN_KEY_FLAG_NEW` |
| 消费 | `Key_Read()` 读到 NEW 后**立即清 NULL**(边沿消费,不重复触发) |
| 合并顺序 | 有本地键板时:**先取远程 `g_tRemoteKey`,再被本地 `g_tKey` 覆盖**——两键同时挂起时**本地优先** |
| 连接联动 | TCP 连上 → `bUseRkey = TRUE`;断开 → `FALSE` + `wSendCode = IDLE` + `dwSendBase = 0` |
| 背光 | `Key_HaveNewKey()` 每次调用都执行 `MenuProc_Have_Key()`(无新键也点背光),再返回是否有新键 |
#### 5.16.8 时序、超时与重试
| 定时器 | 周期 | 作用 |
|---|---|---|
| `tTimerLCD` | **500 ms** | 参考主机侧**串口屏板通道**的周期兜底节拍(`WriteScreenByByte`)。
⚠ 本产品**不照抄**:屏板固件自己按 **275 ms** 主动召唤(§5.16.2),主机只需"**来一帧答一帧**",无需自带节拍 |
| `tTimerComBrk` | **1 分钟** | 通信中断判定;**每收到任一帧、每次发送成功**都重置 |
| `tTimerAbandon` | **200 ms** | 发送未完成时的放弃窗口 |
**发送状态机**(`RDisp_Send`):
1. `TcpGetSendState(iTcpNo) <= 0` → 直接返回(链路未建或芯片发送忙),不组帧。
2. `wSendFlag == CN_RDISP_SEND_BUSY` → 重发上次缓冲;成功 → `IDLE` + 重置 `tTimerComBrk`;
失败且 `tTimerAbandon` 未到 → 返回;已到 → `IDLE` 后**重新组帧**(宁可丢帧不阻塞)。
3. 组帧:`byCRC = XOR(DATA)`;填 `TAG=0xBB`、`CMD`、`LEN`;`CRC` 写在 `DATA` 之后。
4. `TcpSend` 返回值 **必须等于** `wMsgLen` 才算成功(部分发送视为失败,进 BUSY 重试)。
**显存刷新互斥**:`g_tShareSetBuf.dwReserSet[3]` 三态——`0xaa` = 被其他模块占用(本轮跳过)、
`0x55` = 显示已刷新(供远程工具判断)、`0` = 空闲。取用前查 `0xaa`、用后清 `0`。
**任务模型**:`taskSpawn("tRemoDisp%d", 111, 0x0008, 0x35000, RDisp_Main, ...)`
——prio **111**、栈 **0x35000**(212 KB,VxWorks 字节语义)、主循环 `taskDelay(1)`,
每轮 `DogTask_InfoUpdate(EN_TASK_DSP, ...)` 喂狗,退出时 `DogTask_InfoError` 报异常。
#### 5.16.9 与参考的差异(本产品当前实现)
| # | 项 | 参考 | 本产品 | 处置 |
|---|---|---|---|---|
| 1 | INIT 应答长度 | **29 B**(= 宽高 4 + **颜色位 1** + 显存 4 + **RGB 三色 12** + 键值表 8) | **8 B**(宽 2 + 高 2 + 帧字节 4) | **补 8 B 键值表 → 16 B**;**颜色 13 B 不对齐**(单色屏无接收方,§5.16.5) |
| 2 | KEEPLIVE 应答 | 不应答 | 回显空 KEEPLIVE 帧 | 改为不应答(或留兼容开关) |
| 3 | LCDMEM 非 0 偏移 | 支持续传(从 `dwSendBase` 起) | **仅支持偏移 0**,非 0 静默忽略 | 补续传 |
| 4 | 召唤拒绝语义 | `bNeedFlash` 为真 → `-1` 不发 | 无 | 补(防撕裂帧) |
| 5 | 越界语义 | `>= 3200` → `-2` 不发 | 无 | 补 |
| 6 | 链路监视 | `tTimerComBrk` 1 分钟 | 无 | 补(掉线判定 / 重连驱动) |
| 7 | 发送重试 | BUSY + 200 ms 放弃 | 依赖 `net_send` 阻塞模型 | 评估是否需独立实现 |
| 8 | 键值集(网络通道) | 8 键(含 F1 / F2) | 6 键(无 F1 / F2) | 补 F1 / F2;**仅网络通道**——屏板无 F1/F2 物理键(§5.16.3 ②) |
| 9 | 任务模型 | 独立任务 prio 111 / 栈 212 KB | `uiTask` 10 ms 轮询(prio BelowNormal / 16 KB) | **形态差异,不追平**(X1 / X2) |
| 10 | 接收侧 CRC | 仅 LCDMEM / KEY 校验 | 全帧校验 | **本产品更严,保留** |
| 11 | **串口屏板通道** | **有**:显存 3200 B + 状态灯 4 B 下行、键号上行,变化驱动 + 500 ms 兜底 | **已交付**:700000 8N1、一召一答 4208 B、键号上行(docs/子系统设计/屏板链路设计说明.md) | 验收尾巴见该设计说明 §9.2 |
| 12 | 双出口并发 | 同一份显存同时喂串口与网络,互不阻塞 | 两出口并存(7003 + 串口通道),60 s 并发台架无互扰 | —(屏板链路设计说明 §9.1) |
| 13 | 屏板状态指示灯 | 20 路(ID 0 电源常亮 + ID 1~19 位掩码),**4 B 小端**随每帧下发 | **已交付**:5 位映射 `App/algo/sb_led_map.h`(屏板链路设计说明 §6);主板另有 6 路 GPIO LED(`LED1~6`) | 逐位目视验收未闭环(设计说明 §9.2) |
| 14 | 背光 / 对比度 | 主机 BSP 有 `Lcd_BackLight` / `Lcd_SetContrast` + 空闲 120 s 熄灭 | 无 | **不做**——屏板固件自管背光,两条路径不能同时启用 |
**来自屏板固件的新增约束(参考主机侧源码里看不出来,但对实现是硬约束)**:
- **角色反转**:屏板是 **Modbus 主站(主动召唤)**,主机是**从站 `0x01`**;与网络通道"客户端召唤"方向相反。
- **帧长 4208 B**(3208 B 有效载荷 + 998 B 填充 + 2 B CRC,§5.16.2)。
- **700000 8N1**,主机侧走 **USART1 TTL 直连**(无方向控制)。
- **屏板每 ≈5000 s 自复位一次**,约 1 s 空窗;主机链路监视须容忍(建议连续 ≥10 s 无应答才判离线)。
- **屏板只有 7 个物理键**,键值 `9`/`10` 是"复位 / 工厂",与参考主机的 `F1` / `F1|ENT` **语义不同**。
- **单色**:屏板是 1 bpp 单色面板,参考的 13 B 颜色信息(颜色位 + RGB 三色)**无接收方**,不对齐。
> 第 1 / 3 / 4 / 5 / 8 条是**客户端可见的报文口径差异**——现有 `DTU-RemoteLCD` 按 8 B INIT 实现,
> 补到 **16 B** 后**客户端必须同步改**,否则会错位解析。这是本项复刻唯一的跨端风险点。
> 注意:**16 B 与参考的 29 B 不是同一口径**——客户端**不得**按 29 B 预留缓冲再截取前 16 B 使用
> (**从偏移 4 起含义即不同**:参考偏移 4 是颜色位,本产品偏移 4 是显存大小的高字节)。
>
> 第 11 / 12 / 13 条(串口通道三件套)**主机侧已交付**(docs/子系统设计/屏板链路设计说明.md)——本产品已可脱离 PC 独立带屏运行;仍开放的是第 2~9 条(7003 网络通道报文口径)。
>
> 第 14 条**已决定不做**:屏板固件自管背光与超时熄灭,主机再插一手会打架。
#### 5.16.10 复刻完成判据
1. 任意 TCP 客户端连 7003 → 发 `INIT` → 收到 **16 B**(宽 2 + 高 2 + 显存 4 + 8 键值表),
字段值与 §5.16.5 逐字节一致;**不得**出现颜色位或 RGB 字段。
2. 发 `LCDMEM(0)` → 收到 `3204 B`;**再次**发 `LCDMEM(0)`(新图未就绪)→ **无应答**(对应参考 `-1`)。
3. 发 `LCDMEM(3200)` → **无应答**(对应参考 `-2`)。注:该分支在 `bNeedFlash` 为真时先被 `-1` 截住,
只能在"新图已拷贝、尚未上送"的窗口内触发,属**边角路径**,判据以"无应答且不越界读"为准。
4. 发 `KEEPLIVE` → **无应答**,且链路 1 分钟内不被本端主动断开。
5. 发 `KEY(0x24)` → 收到空 KEY 应答,界面表现为 `F1+ENT` 组合键。
6. 断开 TCP 60 s 以上 → 设备侧可观测到"通信中断"(日志 / 状态位),重连后自动恢复推流。
7. 8 键逐个下发 → 界面动作与按键表一致;组合键 `F1|ENT` 生效。
**串口屏板通道(与上面 7 条并列,待建后验收)**:
8. 主机上电后,屏板按 **280 ms** 节拍发帧 A(6 B,形如 `01 03 01 xx CRC_H CRC_L`);主机每帧在
**≤200 ms** 内**发完**帧 B(**4208 B**,整帧无 2 个字符时间以上的间隙,CRC 覆盖 `[0, 4205]`),
**主机侧**连续 10 分钟零超时、零 CRC 错帧(屏板不指示通信状态,计数只能在主机侧取)。
9. 帧 B 前 3200 B 与主机 `g_ui_fbuf` **逐字节一致**(160 行 × 20 B,1 bpp,MSB 在前,bit = 1 → 白)。
10. 屏板 7 个物理键逐个按下 → 主机收到的 `key` 依次为 `1/2/3/4/7/8/9`
(ENTER / UP / DOWN / ESC / LEFT / RIGHT / RESET),界面动作与 §8.2 菜单树一致;
`0` 表示无键、不产生事件。
11. **状态灯下行**:主机把 19 路业务状态映射为位掩码 → 屏板 LED 1~19 的亮灭与掩码逐位一致;
LED 0 恒亮、不受掩码控制(bit0 置位不改变任何灯)。
12. **双通道并发**:同一时刻 7003 客户端在拉帧、屏板在召唤,两侧画面一致,**互不丢帧、不串帧**;
串口侧 4208 B 发送阻塞不得导致 7003 侧超时(反之亦然)。
13. **屏板自复位容忍**:屏板跑满 ≈5000 s 触发自复位,主机**不误判"屏板离线"**(无告警、不触发保护),
约 1 s 后自动续上。
14. 拔掉屏板 → 7003 侧不受影响;插回后自动恢复,无需复位主机。
15. **LOGO 下发(出局,不实现)**:主机不提供 LOGO 帧,本条不参与验收;本表串口侧判据按其余十四条计。
---
## 6. 接口规范
### 6.1 网络服务端口
| 服务 | 协议 / 端口 | 用途 | 核对方法 |
|---|---|---|---|
| FTP | TCP **21** | 文件上传/下载(根目录 `/`,NAND 存储) | 登录后任意 FTP 客户端列目录/读写(鉴权见 §7.6) |
| 墙钟对时 | TCP **8888** | PC 主动推送时间到设备 | 对时后核对设备时间 / 日志时间戳 |
| 远程显示(RemoDispBus) | TCP **7003** | 远端看图(160×160 1bpp)+ 远端导航按键 | DTU-RemoteLCD 或任意 TCP 客户端连 7003 收帧 |
| IEC 60870-5-104 上行 | TCP **工厂默认 2404** | 被控站接入:遥测/遥信上送、总召、遥控/定值下发(点表见 §6.8) | 任意 104 主站连 2404,STARTDT+总召往返(→ `docs/子系统设计/IEC104上行设计说明.md`) |
> **现场组网要求**:设备与客户端**同网段**(默认 .100/24、网关 .1);跨网段先改 IP 再上线。
### 6.2 RemoDispBus 协议

> **口径**:本节按**参考源码**(`HMI/RemoeDisp/RemoDispBus.c`)给线上格式;本产品当前实现与该口径的
> 逐项差异、以及为什么会有差异,见 §5.16.9。**以参考为准**,差异项按 `参考产品复刻明细.md` §11 排期收敛。
>
> 本节只讲**网络**通道(TCP 7003)。**串口屏板通道不走本协议**——它是 RS-485 上的
> **Modbus RTU 风格裸二进制帧**(6 B 召唤 + **4208 B** 图像应答,CRC-16/MODBUS **低字节在前**),
> 帧格式已按屏板源码逐字节定版,见 §5.16.2。
> 两条通道**共用同一份显存**,但帧格式、校验、时序、主从方向全都不同,**不能共用组帧代码**。
| 项目 | 内容 |
|---|---|
| 传输 | TCP,端口 **7003**;设备为 **Server**,目标 IP `255.255.255.255` = 接受任意客户端 |
| 帧格式 | `[TAG(1)][CMD(1)][LEN_H(1)][LEN_LO(1)][DATA(n)][CRC(1)]` |
| 方向标识 | TAG `0xAA` = 客户端→服务端,`0xBB` = 服务端→客户端 |
| 校验 | CRC = **数据段**逐字节异或(低 8 位,**不含** TAG/CMD/LEN,也不含 CRC 自身) |
| 字节序 | 多字节字段**大端**(网络字节序) |
| 最短帧 | 5 字节(LEN=0) |
| 最长帧 | 数据段 ≤ **9800 B**(`CN_RDISP_FRAME_LEN_MAX`) |
| 缓冲 | 收发各 **10240 B**(`CN_RDISP_BUF_RECV/SEND`) |
| 接收方式 | 逐字节有限状态机(TAG 定界 + 长度定长 + CRC 校验,自动重同步) |
**命令集**:
| CMD | 名称 | 请求数据段 | 应答数据段 |
|---|---|---|---|
| 0 | KEEPLIVE | 无 | **无应答**(仅重置 1 分钟通信计时) |
| 1 | INIT | 无 | `[宽u16][高u16][显存u32][8 键值]`(**16 B**;参考的 29 B 含 13 B 颜色信息,**本产品不对齐**) |
| 2 | KEY | `[按键码u8]` | 无(空帧确认,LEN=0) |
| 3 | LCDMEM | `[起始偏移u32]` | `[偏移u32][1bpp位图 3200B]`(3204 B);偏移非 0 时续传,`bNeedFlash` 期间不应答 |
**INIT 字段明细与按键码表**:见 §5.16.5(含 16 B 逐字段偏移)。
> **单色口径**:本产品**只实现单色分支**。参考 `INIT` 里的颜色位 `CN_RDISP_LCD_TYPE` 与
> `CN_RGB_RED/GREEN/BLUE`(共 13 B)**整段删除**,不做多色对齐——屏板是 1 bpp 单色面板,
> 没有彩色/灰度的接收方;参考侧 `RDisp_LcdColor_To_Mono()` 这道"彩色转单色"也因此**不存在**
> (本产品显存本来就是单色)。
**按键码**:`F1=0x04` / `F2=0x80` / `UP=0x02` / `DOWN=0x40` / `LEFT=0x10` / `RIGHT=0x08` /
`ENTER=0x20` / `ESC=0x01`;**位掩码可组合**(如 `F1|ENTER = 0x24`)。
**位图格式**:1bpp 单色,**MSB 优先**(bit7 = 该字节第一个像素);像素 (x,y) → `byte_i=(y*W+x)/8`、`bit_i=7-(x%8)`;位值 `1`=白、`0`=黑;160×160 → 3200 B。
**关键常量**:`UI_NET_SINK_PORT=7003`、`RM_MAX_RX_PAYLOAD=256`、`RM_RX_BUF=64`、帧重组缓冲 261 B
(本产品侧常量;参考侧对应 `CN_RDISP_TCP_PORT` / `CN_RDISP_FRAME_LEN_MAX` / `CN_RDISP_MEMSIZE`)。
> 详见 `docs/UI设计/RemoDispBus协议与接收设计说明.md`。MCU 与模拟器**共用同一份** `ui_net_sink.c`。
### 6.3 对时协议
- **载体**:TCP :8888,PC 主动推送,设备接收并写 RTC。
- **行为**:`timeSyncTask` 监听 → 收包 → 写回 RTC;30 s 无包则掉线重建监听(约 4 s)。
- **待补充**:推送报文的具体帧格式(当前实现细节见 `App/sync/time_sync.c` 与 `test/host/time_sync_server.py`)。
### 6.4 FTP 服务
- **实现**:lftpd(轻量 FTP 服务器),root = `/`(FatFS 卷根),端口 21,**单客户端**。
- **支持命令**:登录 / TYPE I / PASV / STOR / RETR / LIST / DELE / QUIT。
- **鉴权**(已鉴权 · 2026-10-11):口令登录(`[ftp]` 段 `user` / `pwd_hash` = SHA-256(user‖password) / `write`);未登录仅 USER/PASS/QUIT/FEAT/SYST/NOOP 放行,其余一律 530;`write=0` 可禁 STOR/DELE;连续 5 次失败锁 60 s;出厂默认 `admin`/`admin`,默认口令在用有启动告警。口径详见 `docs/子系统设计/网络配置说明.md` §3.4。
- **启动门**:`ftpTask` 必须等 `NET_READY` **且** `STORAGE_READY`(两位 `NoClear`)才 `lftpd_start`。
- **注意**:同一时刻只服务 1 个会话,旧会话异常断开后约 30 s 恢复监听;测试脚本须内置连接重试,**不要用裸 TCP 工具探测 21 端口**。
- **实测**:128 KB 上传下载逐字节一致(约 49/60 KB/s)。
### 6.5 配置文件格式
**位置**:MCU `/config.ini`(卷根);模拟器 `sim_config.ini`(工作目录)。
**语法**:`key=value`(key `[a-z0-9_]+` 大小写敏感);`[section]`;`;`/`#` 注释行;UTF-8(容忍 BOM);CRLF(设备写出)/ LF 均接受;行长 >127 字符该行无效;文件 >4096 B 视为损坏。
**出厂模板**(首启自动生成):
```ini
; DTU 通用配置文件
[network]
; IP 地址
ip=192.168.1.100
; 子网掩码
mask=255.255.255.0
; 默认网关
gateway=192.168.1.1
; MAC 地址 (全零 = 使用芯片内置 MAC)
mac=00:00:00:00:00:00
```
**值类型**:`IP4`(`a.b.c.d`)、`MAC`(`aa:bb:cc:dd:ee:ff`)、`UINT`(十进制,兼容 `0x`)、`STR`(≤31 字符)。
**工厂默认**:IP `192.168.1.100` / 掩码 `255.255.255.0` / 网关 `192.168.1.1` / MAC 全零(= 芯片内置)。
> MAC 全零是"使用芯片内置 MAC"的**哨兵**,不是"MAC 为 0"。恢复出厂:FTP `DELE /config.ini` → 下次上电重新生成模板。
### 6.6 调试串口
| 项 | 值 |
|---|---|
| 端口 | UART5(RS232#1:PC12 TX / PD2 RX,经 U9 隔离 → U13 → CN1 端子),115200 8N1;台架端子接法见 `docs/子系统设计/屏板链路设计说明.md` §3.2 |
| 用途 | printf 日志 + `/serial-boot-test` 采集通道 |
| 配置源 | `.serialtest/config.json`(单一配置源) |
| 工具 | 仓库内置 Python 串口助手(`.dsh/skills/serial-assistant/`,常驻 daemon + CLI) |
**启动标记(`/serial-boot-test` 判据)**:
- Required:`App init OK` / `Network Init` / `net init: OK` / `PHY: 100M FULL`
- Forbidden:`FAIL` / `ERROR` / `HardFault` / `[ERR]`
### 6.7 对外 API 一览
| 模块 | 关键 API | 说明 |
|---|---|---|
| `net_socket` | `socket/bind/listen/accept/recv/send/close` + `select/poll` | BSD Socket 抽象 |
| `net_cfg` | `net_cfg_load/save/get/set/get_mac/set_active_mac/fmt_ipv4/set_apply_cb/request_apply` | 网络配置(签名与语义稳定) |
| `cfg_file` | `cfg_file_register/load/save/poll/section_data` | 配置引擎核心(双端共享) |
| `sys_clock` | `sys_clock_init/get/get_str/get_time` | 墙钟时间服务 |
| `sys_log` | `syslog_post`(RUN / SELFTEST) | 定长记录引擎 |
| `ui_net_sink` | `ui_net_sink_start(port)` / `ui_net_sink_poll()` | 远程显示服务端 |
| `ui_content_page` | `ui_content_page_open/close/invalidate/is_open` | 内容页框架 |
| `task_watchdog` | `task_wd_beat/poll/start` + 观测接口 | 看门狗判定状态机 |
| `sys_events` | `sys_evtHandle` + 9 个事件位 | 启动屏障 + 落盘请求 |
| `adc_input` | `adc_input_init/poll/raw/desc` | 语义取数与有效性语义取数(8 电气量 + 温度) |
| `adc_health` | `adc_health_init/report/flags` | 语义取数与有效性采样有效性判定(纯逻辑) |
| `adc_cal` | `adc_cal_init/apply/flags/set/b_codes/prot_blocked` | 标定折算 + 三态可用性 |
| `phasor` | `phasor_init/run/read` | 全周傅氏相量(双区) |
| `freq` | `freq_run/get/valid` | 测频与遥测快照测频(自适应窗) |
| `meas` | `meas_tick/snap` | 测频与遥测快照(200 ms,双区) |
| `prot_set` | `prot_set_init/load/selfcheck/set_sealed/get/locked` | 保护判据定值模型(原子写 + 封存) |
| `prot_elm` | `prot_elm_init/run/eval/state/request_reset` | 保护判据元件框架(9 实例) |
| `run_par` | `run_par_init/get/set_104_write/dirty_peek` | 运行参数段(死区 + 系数代理) |
| `soe_rec` / `soe_store` | `soe_rec_encode/decode` · `soe_store_post/drain` | SOE 编解码 + 落盘投递 |
| `iec104` | `iec104_link_*` / `iec104_pts_*` / `iec104_serv_*` | 104 链路 / 点表 / 板端胶水 |
### 6.8 IEC 60870-5-104 点表摘要
> **权威口径**:验收以《检测平台配电终端送检要求(点表)》为准;本表是 V0.1 实现摘要,
> 逐点细节与未实现项见 [`docs/子系统设计/IEC104上行设计说明.md`](../子系统设计/IEC104上行设计说明.md) §3。
> **段基址与参考产品逐字对齐**(8 段全表见 复刻明细 §1):遥信 `0x0001`、保护 `0x1001`、遥测 `0x4001`、
> 参数 `0x5001`、遥控 `0x6001`、设定 `0x6201`、电能 `0x6401`、步位置 `0x6601`。
> **点表规模是当前最大差距**:本产品 9 遥测 / 33 遥信 / 2 遥控,参考产品 31 遥测 / 138 开入 / 31 开出。
**遥测(`0x4001` 段起,短浮点,系数 1)**
| IOA | 点 | 来源 | V0.1 |
|---|---|---|---|
| `4011` / `4014` / `4015` | `1Ua` / `1U0` / `1Ia` | 遥测快照(TPAFE 侧,有效) | ✅ |
| `4012` / `4013` / `4016~4018` | `1Ub`/`1Uc`/`1Ib`/`1Ic`/`1I0` | 快照(STM32 侧,削顶) | ❌ 恒 IV |
| `4019~401C` | `1P`/`1Q`/`1S`/`1Pf` | 需跨转换器同刻 | ❌ 恒 IV(V0.2) |
| `401D` | `1f` 频率 | 测频结果 | ✅(未收敛则 IV) |
| `4010` / `401E` / `401F` | `UBA` 后备电源 / `V1`/`V2` 直流 | 无硬件通道 | ❌ 未入表(X3) |
> **与参考的差额**:参考 `g_tYcIndexTab` 共 **31 点**——双回线各三相电流/电压(12)、双回线各三组
> 线电压(6)、双回零序电流/电压(3)、频率(1)、直流 2 路(2)、有功/无功/视在/功率因数
> 单相 3 + 三相 1(4)、电度 8 种另列。本产品 9 点,缺的是**第二回线、线电压、零序双回与
> 功率四量**——前者等通道(X4),后两者等标定后的功率运算。
**遥信(`0x0001` 段起,V0.1 定义 33 点)**:`1~4` 开关位置(双点,恒 IV)、`5~14` 预留(IV)、
`15` 故障总、`16~18` 过流 Ⅰ/Ⅱ/Ⅲ 段、`19~20` 零序过流 Ⅰ/Ⅱ(IV)、`21~22` 接地故障/重载(IV)、
`23` 过载、`24~30` 外部遥信(RS-485 冻结 → IV)、`31` 保护闭锁、`32~33` 预留(IV)。
有效位图 `YX_VALID_MASK` = YX15~18 / 23 / 31(6 点)。
> **与参考的差额**:参考 `g_tInputModelTab` 共 **138 点**(开关合/分位、未储能、就地远方、接地位置、
> 隔离位置、停用重合闸、保护投入、检修压板、远方信号、手合手分、电源输入、活化状态、电池欠压…),
> 另 `g_tOutputModelTab` **31 点**开出(保跳/保合/遥分/遥合/遥控备用/电池活化/电机保护/闭锁…)。
> 两者都需要一次设备接口硬件(X4),属 §9.1 阻塞型。
**遥控(`0x6001` 段起,双点两段式)**:`YK1~4` 开关(否定确认)、`YK11` 信号复归、`YK12` 遥控软压板、
`YK13` 电池活化(否定)、`YK14` 解除闭锁(否定)。
**定值与运行参数**:保护定值地址 = `prot_set.comm_addr`(基址 `0x8820`,过压/欠压沿用告警段
`0x8027~0x802A`);运行参数段 `0x8030~0x8038`(死区 9 点)/ `0x8040~0x8047`(`k` 覆盖 8 路)/
`0x8048~0x804F`(`b` 覆盖 8 路),共 25 项。
**逐项地址为本仓自定,送检点表到位后以送检为准替换常量段。**
> **与参考的差额**:参考 104 定值映射 **107 项**占 `0x8820~0x8891`(8 个空洞),
> 底层定值表 **249 项 / 23 单元**。本产品 40 项,缺的是重合闸(16)、电压时间型自愈(17)、
> 同期(13)、看门狗(63)、遥测越限(10)等整单元(复刻明细 §1)。
**链路参数**:k=12 / w=8 / t1=15 s / t2=10 s / t3=20 s;IOA 3 字节、公共地址 2 字节、传送原因 2 字节;
公共地址与端口可配(`[comm]` 段,工厂默认 1 / 2404)。**生效语义**(用户裁定 2026-10-30):两者都按「保存即生效」要求;现实现 = 公共地址改后重启、端口下次重 bind 才生效,差距已登记 `docs/任务规划/登记簿.md` §1「104 端口改后立刻重 bind」。
---
---
## 7. 非功能需求
### 7.1 可靠性
| 需求 | 要求 | 落地 |
|---|---|---|
| **自恢复** | 任务死锁/饿死/断言死循环 → 硬件复位 | IWDG + 心跳位图(§5.7) |
| **掉电不丢配置** | 配置原子写(旧档迁移已取消,2026-10-10) | tmp+rename;旧 `/cfg/netcfg.bin` 不读、不删、不迁移 |
| **错误可追溯** | 错误码分级/透传/留痕,不吞不猜 | 错误码通道四要素(分类/透传/分级/留痕) |
| **存储自愈** | 卷损坏可探针/自愈;丢弃可观测 | 卷健康探针 + 丢弃计数 + TX 僵尸通道自愈 |
| **不误杀** | 正常长耗时窗口不触发复位 | 启动段不喂狗 + 心跳切片 + 白名单 |
> **错误码通道四要素**(CH395F 范式,后续任何驱动/子系统建立错误通道时应沿用):
> ① 分类(超时/真故障/参数错)② 透传(逐层上报,禁 `(void)ret`)③ 分级(可恢复重试/不可恢复上报)
> ④ 留痕(不可恢复写 syslog selftest,可恢复计数并 UI/日志可见)。
> **纪律**:自愈逻辑严禁把"名字/参数类错误码"归入"卷损坏触发集"。
### 7.2 性能指标
| 指标 | 基线值 | 来源 |
|---|---|---|
| M3 压力档帧周期 | med 60.1 / p95 66.4 / max 69.3 ms(零尖峰) | `docs/测试说明/专项验证/链路延迟测试说明.md` |
| 队列响应性(挂起客户端) | 92.5 ms(pending 一期) | `test/host/e02_probe.py` |
| FTP 传输速率 | 约 49 / 60 KB/s(上传/下载) | 128 KB 实测 |
| 网络重建生效 | 2~5 s(含等链路 ≤3 s) | 配置设计 |
| UI 刷新 | 10 ms 循环 | `ui_task_func` |
| IWDG 停喂到复位 | ≤2.5 s | IWDG 设计 |
| **采样节拍** | **1000 Hz**(每 10 s 帧号 +10000,零丢帧) | 台架 `/serial-boot-test` |
| **单拍采集消费** | ≤10 µs(DMA 搬运后实测 8 µs) | `adc_task` 实测 |
| **相量刷新** | 1 ms(每拍整窗重算 5 实例) | `phasor_run` |
| **遥测快照周期** | 200 ms(经过时间分频) | `meas_tick` |
| **测频精度** | 45~55 Hz 最坏 0.0101 Hz | 主机侧扫频用例 |
| **保护判据节拍** | 20 ms(延时精度由 `now_ms` 差值保证) | `protectTask` |
| **动作到 104 上送** | ≤120 ms(判据拍 + COV + 服务周期) | 台架主站往返 |
### 7.3 内存与资源预算
> **权威来源**:`.map` 受管快照(`docs/内存管理/RAM使用分析与优化方案.md` §2.1,由
> `scripts/check_mem_numbers.py --write` 生成)+ 2026-10-22 栈预算快照
> (`docs/内存管理/data/stack_report_20261022.md`)。**本节数值随代码演进会过期,提交前以受管快照为准。**
**静态区(产品构建,2026-10-24 `.map`)**:
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| Flash ER_IROM1 | 261,928 B | 1024 KB | 25.0% |
| ROM 合计 | 263,500 B | 1024 KB | 25.1% |
| RW_IRAM1 | 81,072 B | 131,072 B | 61.9% |
| RW_CCM | 57,376 B | 65,280 B | 87.9% |
**运行时栈预算(9 任务,静态最坏链 × 真机水位取更坏者)**:
| 任务 | 配置 | 静态最坏链 | 真机峰值 | 余量 | 判定 |
|---|---|---|---|---|---|
| `defaultTask` | 5120 B | 3024 B | 2152 B | 40% | ✅ |
| `netTask` | 3840 B | 1912 B | 1116 B | 50% | ✅ |
| `ftpTask` | 4096 B | 1456 B | 740 B | 64% | ✅ |
| `uiTask` | 16384 B | 1336 B | 4052 B | 75% | ✅ |
| `testTask` | 8192 B | 736 B | — | 91% | ✅ |
| `timeSyncTask` | 2560 B | 1264 B | 616 B | 50% | ✅ |
| `adcTask` | 2560 B | 1320 B | 952 B | 48% | ✅ |
| `iec104Task` | 2560 B | 1456 B | 580 B | 43% | ✅ |
| `protectTask` | 2048 B | 872 B | 828 B | 57% | ✅ |
> **定栈规则**:改栈或加大栈内局部变量前后**必须**重跑 `scripts/stack_report.py`
> (Keil 静态调用图 × 串口 `[STACK]` 水位,判定取两者更坏者;余量须 ≥ max(1024 B ISR, 30%))。
> **LVGL 内存池** 16 KB(静态占用,2026-10-15 由 40 KB 收缩);**FreeRTOS 堆** 56 KB(CCM,水位 87.9% < 92% 门禁)。
> **卡点已从主 SRAM 移到 CCM**(余 ~7.9 KB)——再加任务或抬栈前先看
> `docs/内存管理/RAM使用分析与优化方案.md` §4。
### 7.4 可维护性(测试与门禁)
**测试分层**:
| 层 | 内容 | 运行位置 |
|---|---|---|
| 静态门禁 | pre-commit 钩子族:格式 / 编码 / 静态分析 / 文档链接 / 体积 / 生成物新鲜度 / 编号 | 本地 / CI |
| 主机侧纯逻辑单测 | MSVC 离线编译真实源码 | 本地 / CI |
| 板端测试套件 | 板端套件(testTask 串口 `[PASS]/[FAIL]` 汇总) | 本地(需真机 + 串口) |
| 主机侧回归 | 模拟器像素 / 池水位回归 + 真机 `/serial-boot-test` 闭环(含探针) | 本地(模拟器免硬件;`/serial-boot-test` 需真机 + ST-LINK) |
**构建 / 测试命令**:
```powershell
# 构建(/keil-build):0 错 0 警
& '.venv\Scripts\python.exe' .dsh/skills/keil-build/scripts/keil_build.py # 退出码 0=PASS / 1=构建失败 / 2=环境错误
# 自动化测试(/serial-boot-test):串口采集 → 烧录 → 日志校验
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py # 0=PASS / 1=FAIL / 2=采集或烧录错误 / 3=用法错误
# 全库门禁
pre-commit run --all-files
```
**pre-commit 门禁族**(钩子数量随套件增长,以 `.pre-commit-config.yaml` 为准):
| 族 | 代表钩子 | 作用 |
|---|---|---|
| 格式 / 仓库卫生 | `trailing-whitespace`、`end-of-file-fixer`、`check-added-large-files`、`check-merge-conflict`、`check-yaml`、`gitlint` | 行尾与空白、超大文件、冲突标记、提交信息规范 |
| 编码与源码纪律 | `no-mojibake`、`coding-standard`、`nested-comment`、`bom-guard`、`layer-deps`、`bsp-err` | 乱码、命名与常量位置、注释嵌套、BOM 漂移、分层依赖、BSP 错误码契约 |
| 静态分析 | `cppcheck`、`test-cppcheck` | 主机侧与测试代码静态检查 |
| 文档一致性 | `doc-links`、`id-check`、`task-table`、`mem-numbers`、`mem-budget`、`size-gate` | 链接与章节引用、编号、任务表、内存数字、体积门禁 |
| 生成物新鲜度 | `generated-fresh`、`font-charset`、`lv-conf-sync`、`ui-golden`、`setview-check`、`uinav-selftest` | 菜单/字体/像素黄金等生成物与源一致 |
| 主机侧单测 | `watchdog` / `sys-clock` / `cfg-file` / `phasor` / `adc-input` / `adc-health` / `adc-cal` / `meas` / `freq` / `prot-set` / `prot-elm` / `run-par` / `iec104` / `soe` / `nand-port` / `ui-data` 等 `*-unit-test` | MSVC 离线编译真实源码跑断言 |
| 结构与注册 | `registration-check`、`test-suite-switch`、`cubemx-survival` | 双端注册、套件互斥、CubeMX 保护区审计 |
**受管事实源(禁止手改,由脚本生成)**:
- 任务表(`README.md` / `AGENTS.md`)← `scripts/gen_task_table.py`
- 内存/Flash 快照 ← `scripts/check_mem_numbers.py`
- 菜单表 ← `tools/menu_gen/gen_menu.py`
- 中文字体 ← `tools/font_gen/make_font.py`
### 7.5 可移植性(双端)
| 项 | MCU | 模拟器 |
|---|---|---|
| 构建 | Keil MDK-ARM v5(ARMCC) | CMake + SDL2 + MSVC |
| 共用源码 | `Drivers/BSP/UI/*`、`Drivers/BSP/NETCFG/net_cfg.c`、`App/cfg/cfg_file.c` | 同左 |
| 平台后端 | `*_mcu.c`(CH395F/FatFS) | `*_sim.c`(Winsock/宿主文件) |
| 验证 | `/keil-build` 0/0 → `/serial-boot-test` 串口闭环 | `check_menu_rows.py` + `check_content_pages.py`(像素黄金参考) |
> **新源文件必须双端注册**(Keil `.uvprojx` + `simulator/CMakeLists.txt`),否则不参与编译。
### 7.6 安全与权限
| 项 | 现状 |
|---|---|
| FTP 鉴权 | **已鉴权**(2026-10-11):口令登录(`[ftp]` 段存 SHA-256 摘要,不存明文)+ 连续 5 次失败锁 60 s + `write=0` 可选只读;出厂默认 `admin`/`admin`,默认口令在用有启动告警。边界:FTP 明文传口令,挡未授权访问、不挡网络嗅探(详见 `docs/子系统设计/网络配置说明.md` §3.4) |
| UI 菜单权限 | 受保护叶子(行尾 `#P`)先经密码页;密码**仅编译期写入**(查看 `000` / 修改 `123`),不落 Flash、无运行期设置页 |
| 远程显示 | 无鉴权,单连接;现场同网段部署 |
| 104 上行 | **无应用层鉴权**(标准 104 本身不带认证):安全边界 = 现场同网段部署 + 单连接语义 + 首到优先。主站 IP 白名单**不实现**(单 socket 上"拒绝"= close + 重听,可被端口扫描打成 SPI 风暴,且配错即锁死真主站;属协议语义决定,非范围裁剪,见 §9 卷首)→ 需冗余/白名单时随 net 层立项 |
| 配置面 | 配置与定值只能经 FTP(需口令)或 104(同网段)改写;UI 侧修改需修改密码。**本地无串口配置命令通道** |
---
---
## 8. UI 规格
### 8.1 屏幕与渲染
| 项 | 值 |
|---|---|
| 屏幕尺寸 | 160 × 160 px |
| 色深 | **1bpp 单色**(MSB 优先),帧缓冲 3200 B;**无灰度、无彩色**——参考与本产品的链路都是单色,7003 `INIT` 的颜色字段已删(§5.16.5) |
| 渲染 | LVGL 离屏渲染到 `g_ui_fbuf`;MCU 无物理屏,经**串口屏板通道(主)+ TCP 7003(镜像)**双出口送出 |
| 内存池 | `LV_MEM_SIZE` = 16 KB(2026-10-15 由 40 KB 收缩,实测峰值占 40%) |
| 主题 | dark mono(单色深色) |
| 刷新 | `uiTask` 10 ms 循环 |
| 绘制缓冲 | I1 draw buffer 需 +8 B palette 头 |
### 8.2 菜单树(43 节点)

> 图里的节点数、绑页与密码门读数由 `tools/arch_diagram/gen_menu_tree_svg.py` 现算自真值 `ui_menu_tree.txt`(改菜单树 → 同步脚本内 `NODE_SRC` 重跑);级联框在屏上怎么摆(几何)→ `docs/UI设计/菜单页面设计.md` §4.1。
**规模**:菜单节点 **9** 个(含根)+ 叶子节点 **34** 个 = **43** 节点;最大深度 **4 级(主界面 / 一级 / 二级 / 三级菜单)**。
**绑定现状**(2026-10-29 复扫):**已绑内容页 30 叶 / 未绑 4 叶**(页面注册表 `UI_PAGE_MAP_COUNT` = 30,
与已绑叶数一致);未绑 4 叶 = 无开入硬件 3 + SNTP 1(取舍与重开条件见
docs/任务规划/UI补齐.md §5);原「当前执行 6 项」(记录 5 页 + 运行报告)已于 2026-10-28 落地绑页,
「手动录波」与「录波回放」两叶于 2026-10-29 随录波道绑页(判据见 docs/子系统设计/故障录波设计说明.md §4.1 与 §8.1)。
**根页(主界面)7 个一级条目**:装置信息 / 实时数据 / 参数定值 / 三遥设置 / 装置维护 / 通讯参数 / 记录查询。
**完整层级树**:
```text
主界面
├─ 装置信息 [叶子]
├─ 实时数据 ▶ [菜单]
│ ├─ 交流量 ├─ 直流量 └─ 遥信量
├─ 参数定值 ▶
│ ├─ 整定 ▶(参数 / 定值 / 控制字 / 软压板)
│ └─ 查看 ▶(参数 / 定值 / 控制字 / 软压板)
├─ 三遥设置 ▶(遥测死区 / 遥测系数 / 遥信类型 / 遥信防抖 / 双点遥信)
├─ 装置维护 ▶(时钟设置 / 强制复归 / 手动录波 / 清除记录)
├─ 通讯参数 ▶(通讯设置 / 网口设置 / SNTP设置)
└─ 记录查询 ▶(SOE记录 / 事故记录 / 操作记录 / 保护告警 / 保护启动 /
遥控记录 / 自检记录 / 运行记录 / 运行报告 / 录波回放)
```
**三层代码架构**:数据层 `ui_menu_tab`(生成物,`g_menu_tab[43]` 扁平父子索引表,≈0.7 KB Flash)
+ 逻辑层 `ui_menu`(唯一 **17 B** 导航状态)+ 显示层 `ui_menu_view`(持全部 LVGL 对象,零导航状态)。
**导航模型**:树形逐级进入——`ENTER`/`→` 进入子页,`ESC`/`←` 返回上一级;页面 = 对表的一次
`parent == 节点` 过滤(**现算不落地**)。
**显示模型**:横向级联子框——每级一个框,子框左缘 = 父框右缘(共享 1px 边框列);子框顶边对齐父框
"选中行"行顶,溢出时整框上移使底边对齐选中行行底;选中行整行反色(白带黑字),菜单行带 `▶`。
**改菜单流程**:编辑 `ui_menu_tree.txt`(缩进即层级)→ 重跑 `tools/menu_gen/gen_menu.py` → 导航/渲染代码**零改动**。
### 8.3 内容页
**已绑 30 页**(按分组;逐页形态见 [`docs/UI设计/页面补齐规划.md`](../UI设计/页面补齐规划.md)):
| 分组 | 页面 | 形态 |
|---|---|---|
| 装置信息 | 装置信息 | 静态键值行 6 行不分页;副标题"馈线自动化终端" |
| 实时数据 | 交流量 / 直流量 / 遥信量 | 数据列表(交流量 24 行 / 每页 8,共 3 页;直流量 3 行单页) |
| 参数定值 | 查看 4 页(参数 / 定值 / 控制字 / 软压板) | 只读列表 |
| 参数定值 | 整定 4 页(参数 / 定值 / 控制字 / 软压板) | 可编辑(写值 + 置脏 + 保存反馈) |
| 三遥设置 | 遥测死区 / 遥测系数 | 可编辑(运行参数段,`0x8030~0x804F`) |
| 装置维护 | 时钟设置 / 强制复归 / 清除记录 / 手动录波 | 操作页(执行类必须出真反馈,不得"点了没效果";手动录波触发后回读同一份计数,判据见 docs/子系统设计/故障录波设计说明.md §8.2) |
| 通讯参数 | 通讯设置 / 网口设置 | 可编辑(`[comm]` / `[network]` 段,保存后按各自生效时机) |
| 记录查询 | SOE 记录 / 自检记录 / 运行记录 | 记录列表(共用 reader) |
| 记录查询 | 事故记录 / 操作记录 / 保护告警 / 保护启动 / 遥控记录 | 记录列表(每记录 2 行、空态显「无记录」;写入点与判据见 App/log/README.md §五类记录) |
| 记录查询 | 运行报告 | 只读列表 13 行分 2 页;四个数逐个挂范围标签(自电池周期 / 本次开机),跨开机累计按裁定不做 |
| 记录查询 | 录波回放 | 单通道波形页(阶梯线自绘、8 路切换、单位徽标、削顶标记、空态与不可读态显式文案);显示契约见 docs/子系统设计/故障录波设计说明.md §8.1 |
**未绑 4 叶**(2026-10-28:记录 5 页与运行报告页已落地绑页,原「当前执行 6 项」清零;2026-10-29:
录波道落地,「手动录波」叶子随之一并绑页):
遥信类型 / 遥信防抖 / 双点遥信 = 等开入硬件(§9.1.1「出口与开入」);SNTP 设置 = 等 SNTP 对时(§9.1.2「SNTP 对时」)。
- 内容页为**独立全屏屏**,由框架一次性自绘(**零 LVGL 子对象**),`ESC` 返回菜单屏。
- 菜单导航状态**不压层**,返回即原样还原;同一时刻至多一个内容页打开。
- 节点→页映射在逻辑层 `menu_content_page_for(node)`;未绑叶子进入空白终点框(不伪造数据)。
- 数据供给走**注入通道**(rt / par / info / 遥测死区与系数第四表),页面不直接触算法层;
像素黄金用例当前 **66 张**(菜单 8 状态另计;2026-10-28 记录 5 页与运行报告 2 页落地 +7 张;2026-10-29 录波 UI 道 6 张:手动录波 3 态 + 录波回放 3 态)。
### 8.4 密码与权限
| 项 | 值 |
|---|---|
| 查看密码 | `000`(授只读权限) |
| 修改密码 | `123`(授修改权限) |
| 存储 | `ui_pwd_def.h` **编译期**写入,不落 Flash |
| 流程 | 受保护叶子先 arm → 开密码页 → 确认一致后回调 → **先关页再续做进入动作**(携授予权限) |
### 8.5 中文字体
- 字体 `ui_font_cn_12.c`:宋体 12px 1bpp **子集**(当前 **224 唯一字 / 319 字形**,2026-10-29 实测;
122/217、179/274、185/280 均为历史快照。字形数 = 汉字/符号数 + ASCII 0x20~0x7E 共 95 个)。
- 生成流程:`tools/font_gen/extract_cn.py`(扫 UI 源码 → `ui_cn_chars.txt`)→ `tools/font_gen/make_font.py`。
- **约束**:任何 UI 文案新增汉字后必须走此流程,否则显示为空白方块。
---
---
## 9. 缺口清单与推进顺序
> **口径**:本章条目全部来自 复刻明细 §1.2 的 🔶 / ⬜ 行——即"参考源码有、本产品还没做或只做了一部分"。
> **"不做"一词在本章不出现**:1:1 复刻口径下没有主动放弃的能力,只有尚未排期的能力。
> 唯二例外是 复刻明细 §1.7 标为"不适用"的两条(归一化值上送、主站 IP 白名单),那是**协议语义决定**,
> 不是范围裁剪。
>
> **命名**:本节条目一律用**语义短语**(编号规范 P-8 语义优先),不发字母+数字号——
> 避免与其它文档的 `S`/`A`/`P` 编号族撞名。
### 9.1 复刻缺口总表
按**缺口性质**分两类:**阻塞型**(缺外部资源或硬件,排期无用)与**规模型**(无外部依赖,只等排期)。
> 本节是**清单**;**逐项明细在 [`参考产品复刻明细.md`](参考产品复刻明细.md) §2~§10**——参考源码到底有哪几项、本产品有哪几项、
> 差额具体落在哪个定值 / 哪个点 / 哪个模块上、以及"怎么算做完了"。排期与估算以明细为准。
#### 9.1.1 阻塞型(等外部条件,排期不能解决)
| 条目 | 对应 复刻明细 §1.2 | 现状 | 缺的是 | 解除条件 |
|---|---|---|---|---|
| **标定闭锁** | 1~3 | 9 个元件、定值、闭锁链、SOE、104 上送全部就绪,但 `ADC_CAL_F_VALID` 未置位 → 判据整体停走 | **外部资源**:子板/端子板变比 + 标准源 | 端子变比确认 → 8 通道两点标定 → VALID 置位(届时自动解卡,无需改代码) |
| **出口与开入** | 7、8 | 无硬件;保护动作只体现为遥信 + SOE;遥控开关量否定确认 | **硬件 + 立项**:出口继电器、开入回路、回读自检 | 开入开出作为独立模块立项(不可移植项 X3 解除) |
| **三相与同期** | 4、12 | 建接口不使能(B/C 相与 `IN` 削顶、跨转换器无同刻) | **V0.2 硬件** | 8 路电气量全接 TPAFE + 中点偏置 |
| **电度计量** | 6 | 未实现(功率积分需跨转换器同刻) | **V0.2 硬件** | 同上(不可移植项 X4 解除) |
| **对下采集** | 17(子站侧) | `rs485_task.c` 空桩、未建任务、协议族未实现;整簇冻结 | **决策 + 门禁**:用户已决定暂缓 | 先修「单缓冲包撕裂」「TX 超时拉 DE 未等 TC」→ 接消费者 |
| **第三方送检** | 17 | 自有主站工具往返已通过 | **标定 + 第三方环境** | 标定闭锁解除后送检 |
#### 9.1.2 规模型(无外部依赖,只等排期)
| 条目 | 对应 复刻明细 §1.2 | 参考 | 现状 | 差额 |
|---|---|---|---|---|
| **定值铺开** | 1、2 | 249 项 / 23 单元 | 40 项(覆盖参考 35 + 本地 5)/ 5 组 | +214 项 / +18 单元 |
| **映射铺开** | 3 | 107 项 `0x8820~0x8891` | 40 项局部 | +67 项 |
| **元件铺开** | 12 | 45 个模块 | 8 个模块 / 9 个实例 | +37 个模块(过流类 6 / 电压类 5 / 重合闸 5 / 方向功率 4 / 涌流故障 5 / 自动控制 5 / 记录 5 / 录波采样 2) |
| **遥测铺开** | 4、5 | 31 点 + 模型 35 行 | 9 点 | +22 点 + 模型表 |
| **录波子系统** | 10、15 | 3 类型 / 6 段 / 65 通道 / RAM 16 个 / COMTRADE 64·64·16 | **最小闭环代码面已交付**(2026-10-29):1 kHz / 8 路原始码值 / 单段 320 点 / 轮转段文件 / 手动与保护启动 / 单通道回放页;**板级判据一轮未跑** | 仍差 4 kHz 全速与 80 点/周波、多段与 65 通道、COMTRADE、104 文件传输;口径偏离登记见 docs/子系统设计/故障录波设计说明.md §2 |
| **记录体系** | 9、13 | 13 类队列(128/256 条) | 8 类 | +5 类(越限 / 闭锁 / COS / 顺控 / 顺控执行) |
| **UI 记录页** | 9、13 | 参考有完整记录页与运行报告 | 六页已绑并纳入像素黄金回归(2026-10-28) | 只剩真机判据(docs/任务规划/登记簿.md §7) |
| **多规约栈** | 17 | 12 种 | 1 种(104) | +11 种(101 / 102 / 103 / CDT / ModBus / Whm_GB / 61850 / CAN + 子站侧 3 类) |
| **国密算法** | 18 | `Comm/Ecc/` 全套 | 0 | SM2 / SM3 / ECDSA |
| **软压板铺开** | 11 | 9 面(表容量 32) | 1 面 | +8 面 |
| **SNTP 对时** | 17 | 参考对时走 SNTP | 私有 TCP 8888 + 104 `C_CS` | SNTP 客户端 |
| **固有参数** | 1 | `8001H~800AH` 字符串类参数 | 未实现(无法短浮点上送) | 随送检点表设计 |
| **遥测增强** | 4、5 | k=1~9 谐波方和根 + 4 点平均 | 仅基波轨 | 谐波 / 等周波调频 / 零序自产外接切换 三条算法轨 |
| **屏板链路对齐** | 20 | **串口屏板通道**(**700000 8N1 / USART1 TTL 直连、无 DE**,帧 A 6 B 召唤、帧 B **4208 B** 应答:显存 3200 B + **状态灯 4 B 位掩码** + 4 B info)+ **7003 四命令**(INIT **16 B**,单色口径)+ 续传 / 拒绝握手 / 链路监视 / F1·F2 | **串口通道已交付**(4208 B 应答 / 状态灯掩码 / 键上行 / 双出口并发,屏板链路设计说明 §9.1);7003 四命令(INIT **8 B**);无续传、无 `bNeedFlash` 拒绝、无 1 分钟监视、键值 6 个 | 7003 报文口径 6 项(`参考产品复刻明细.md` §11 第 2~9 条);串口侧验收尾巴见屏板链路设计说明 §9.2 |
> **一句话**:**「标定闭锁」是唯一"再排期也没用"的缺口**(等标准源);「出口与开入」「三相与同期」
> 「电度计量」等 V0.2 硬件;「对下采集」等用户决策;其余规模型条目**全部可在现有硬件上推进**,
> 只取决于排期。
> 这张表与 [`docs/任务规划/登记簿.md`](../任务规划/登记簿.md) 一一对应;状态只在登记簿维护,
> 本节不另建状态。
### 9.2 推进顺序
> **顺序唯一权威 → [`docs/任务规划/README.md`](../任务规划/README.md) §2.0**(本表只是产品视角摘要,
> 冲突时以该节为准)。
| 顺序 | 工作 | 类别 | 当前状态 | 开始条件 |
|---|---|---|---|---|
| 1 | UI 补齐(记录 5 页 → 运行报告) | 规模型 | **清单已清空(2026-10-28)** | 六页已落地;余 4 叶属不做项(手动录波与录波回放两叶已随录波道绑页),重开条件见 docs/任务规划/UI补齐.md §5 |
| 2 | net 层跨上下文收口 | 平台 | **下一顺位(未开工)** | UI 清单已清空,切换条件已满足;**开工待用户点头** |
| 3 | 其余结构与可靠性评估 | 平台 | 排队 | net 收口后 |
| 4 | 端子/子板变比确认 → 两点标定 | 阻塞型 | 等外部资源 | 子板实物/原理图、标准源到位可插入 |
| 5 | 标准源送检与第三方 104 主站验收 | 阻塞型 | 等标定 | 保护 VALID 置位;送检窗口前 2 日冻结特性 |
| 6 | 定值铺开 / 映射铺开 / 遥测铺开 / 软压板铺开 | 规模型 | 等排期 | 标定后(工程量才有意义) |
| 7 | 元件铺开与遥测增强 | 规模型 | 等标准源/硬件 | 先解除未标定闭锁;三相类等 V0.2 |
| 8 | 录波子系统 → 记录体系 → 多规约栈 | 规模型 | **录波最小闭环已提前插入并交付代码面(用户裁定 2026-10-29 录波先行,设计与判据见 docs/子系统设计/故障录波设计说明.md §10);本行剩下的参考机完整形态(多段 / 65 通道 / COMTRADE / 104 文件传输)仍不排** | 各自重启条件 |
| 9 | 验证窗口事项(CI 远端、PC peer、负向注入) | 平台 | 逢条件顺带 | 条件到位即执行 |
| 10 | 出口与开入 / 三相与同期 / 电度计量 / 对下采集 | 阻塞型 | 不排 | V0.2 硬件 / 用户决策 |
> **不可颠倒的顺序(一)**:变比确认 → 两点标定 → 保护解卡 → 送检。未完成标定就送检,
> 保护表现为**不动作**;保持该闭锁,不用非标准源走捷径(用户裁定,2026-10-25)。
>
> **不可颠倒的顺序(二)**:**点表铺开必须晚于标定**——否则铺出去的全是 IV 点,
> 主站无法区分"没做"与"接线错",比不铺更糟。
### 9.3 未接线开关说明
| 开关 | 位置 | 现状 |
|---|---|---|
| 功能裁剪开关 | `App/app/app_cfg.h`(当前无宏) | **没有任何生效的裁剪开关**:原三个 `APP_*_ENABLE` 占位宏因全库零消费点已于 2026-10-22 删除,各模块一律编译进产品——ADC / RS-485 均为必需链路(ADC = 遥测/保护来源,RS-485 = 对下 ModBus RTU 采集)。将来确需裁剪时在真实初始化点接入门控,并同步登记回该文件 |
| `TEST_SUITE_*` | `test/board/test_config.h` | 测试套件**互斥**,一次构建只开一个;全注释 = 正常产品固件 |
| `rs485_task.c` | `App/task/`(未建任务、白名单登记) | 唯一的结构白名单缺口:RS-485 整簇冻结期间**刻意不接线**,随该簇一并出白名单 |
| `DTU_BENCH` | 构建宏(`protect_task.c` 等) | 台架构建下允许保护判据在未标定时动作(仅测试用);产品构建(`#ifndef DTU_BENCH`)下保护闭锁 |
> **逐项明细(定值 249 / 遥测 31 / 开入开出 / 动作事件 / 元件 / 录波 / 记录 / 规约 / 完成判据 / 屏板规划)已独立成档** → [`参考产品复刻明细.md`](参考产品复刻明细.md) §2~§11。
---
## 10. 验收判据
### 10.1 构建与静态门禁
| 项 | 判据 |
|---|---|
| 构建 | `/keil-build` → `BUILD_OK: 0 errors, 0 warnings`(退出码 0) |
| 静态门禁 | `pre-commit run --all-files` 全部通过(钩子族见 §7.4) |
| 主机侧单测 | **全部套件全绿**(看门狗、时钟、配置、相量、采集层、标定、遥测、测频、定值、元件、运行参数、104、SOE 等;套件与断言数随代码演进,以 `.pre-commit-config.yaml` 与 `test/host/` 为准) |
| 栈门禁 | `scripts/stack_report.py` 9/9 PASS(余量 ≥ max(1024 B, 30%)) |
| 体积门禁 | `check_size_gate.py --ci` PASS(体积凭证与 git 索引一致) |
| 双端构建 | Keil 与 CMake 均 0 错 0 警 |
| UI 回归 | 菜单行 + 内容页像素黄金 + 导航自检全 PASS |
### 10.2 上电自检(`/serial-boot-test`)
| 层 | 判据 |
|---|---|
| Required 标记(基础) | `App init OK` / `Network Init` / `net init: OK` / `PHY: 100M FULL` |
| Required 标记(采集链,可选启用) | `ADC input layer init: OK` / `phasor(first)` / `freq(hb)`(顺序锚点 `App init OK>phasor(first)>phasor(hb)`) |
| Required 标记(保护链,可选启用) | `protect task start` / `set=40 elm=9` 计数锚 |
| Required 标记(104,可选启用) | `[I104]` 监听/接入行 |
| Forbidden 标记 | 无 `FAIL` / `ERROR` / `HardFault` / `[ERR]`(四个 Fault 的崩溃日志都以 `[ERR]` 开头,出现即不通过) |
| 自检记录 | `selftest: no fault record` |
| 固件身份 | `fw build` 身份戳匹配 + 烧录后回读 `No difference found` |
| 退出码 | `/serial-boot-test` → 0(PASS) |
### 10.3 现场验收清单
| # | 项目 | 方法 | 通过标准 |
|---|---|---|---|
| 1 | 上电自启动 | 断电上电 + 串口采集 | `/serial-boot-test` PASS,无 Forbidden 标记 |
| 2 | 网络连通 | PC ping 设备 IP | 通(PHY 100M FULL) |
| 3 | 远程显示 | DTU-RemoteLCD 连 7003 | 出图(160×160 1bpp)+ 远端菜单导航可用 |
| 4 | FTP | FTP 客户端连 :21 | 列目录 / 读写 `/` 下文件 |
| 5 | UI 功能 | 远端操作 | 菜单 7 项导航、内容页、密码(查看 `000` / 修改 `123`)、网口设置保存后立即生效 |
| 6 | 对时 | PC 推送 8888 | 设备墙钟更新、日志时间戳正确 |
| 7 | 掉电保持 | 写文件 / 改网口配置 / 改定值 → 重启 | 数据仍在(`/config.ini`、`/prot_set.bin` 按新值生效) |
| 8 | LED 状态 | 目检 | 6 路 LED 随系统状态变化(低电平点亮) |
| 9 | 104 总召与变位 | PC 主站连工厂默认 :2404 → STARTDT → 总召 | 四组数据回全、`actterm` 正常;制造变位后 COT=3 上送 |
| 10 | 104 遥控与定值 | `YK12` 软压板投退、`C_SE` 写定值 | 两段式正答、压板行为可观测、定值跨复位保持 |
| 11 | 保护闭锁态 | 未标定状态下观察 `YX31` 与日志 | 判据停走且**明确可见**(不得静默不动作) |
| 12 | 记录可追溯 | FTP 下载 `/log/soe.log`、`/log/run.log`、`/log/selftest.log` | 记录连续、时标正确、解码校验通过 |
| 13 | 屏板链路一致性 | 串口屏板 + TCP 客户端同时接入,按 §5.16.10 **十四条**逐项验证(第 15 条 LOGO 出局) | 串口侧:275 ms 召唤 / ≤200 ms 回 4208 B、显存 3200 B 逐字节一致、**状态灯 4 B 掩码逐位一致**、7 键动作正确、自复位不误判离线;网络侧:INIT **16 B**、重复召唤 / 越界无应答、KEEPLIVE 静默、8 键(含组合)动作正确 |
### 10.4 台架验收(已闭环)
| 用例 | 内容 | 结论 |
|---|---|---|
| V2 | 任务饿死 → IWDG 复位 | ✅ 800 ms 声明 stall,≤2.5 s 复位(`reset cause: IWDG`) |
| V4 | 关中断死循环 → IWDG 复位 | ✅ 每 ~4.9 s 稳定复位(IWDG 独立于中断) |
| V5 | 30 min 真实负载长跑 | ✅ 120/120 FTP PASS,零 IWDG 复位 |
| 堆分配改造 | 配置引擎堆分配改造(真机) | ✅ `[CFG] load: OK`(经 FreeRTOS 堆);FTP 改 IP 复位即生效 |
| 存储/网络套件 | 16/16 PASS | ✅ |
| **采样节拍与相量** | 帧号 1000 Hz、5 实例产出、双区刷新 | ✅ 2026-10-11 |
| **ADC 前端注入** | 已知电压注入:零点 / 线性 / 斜率 / 映射 / 极性 | ✅ 2026-10-27 五项 PASS(斜率偏差 −0.14%,R²=0.9999997) |
| **104 主站往返** | STARTDT / 总召四组 / 变位 / 遥控 / 定值读回 | ✅ 2026-10-12 18/18 PASS |
| **定值与压板持久化** | `YK12` 投退 + 非默认定值跨复位 | ✅ 2026-10-12 21/21 + 13/13 + 4/4 + 4/4 PASS |
| **SOE 落盘** | 自发 `M_SP_TB_1` 带时标 + FTP 下载解码 | ✅ 2026-10-12 56 条记录全 PASS |
---
## 11. 相关文档
| 主题 | 文档 |
|---|---|
| 文档索引 | [`docs/README.md`](../../docs/README.md) |
| 软件架构与运行时规格(本文拆出的就绪位表/启动时序/线程通信权威) | [`docs/架构设计/软件架构与运行时规格.md`](../架构设计/软件架构与运行时规格.md) |
| 硬件规格与能力边界(本文拆出的引脚表与 H1~H5/量纲权威) | [`docs/架构设计/硬件规格与能力边界.md`](../架构设计/硬件规格与能力边界.md) |
| 参考产品复刻明细(能力基线与逐项差额) | [`docs/产品需求/参考产品复刻明细.md`](参考产品复刻明细.md) |
| 项目全景架构 | [`docs/架构设计/项目架构总览.md`](../../docs/架构设计/项目架构总览.md) |
| 存储架构 | [`docs/架构设计/STM32F4-Base存储架构说明.md`](../../docs/架构设计/STM32F4-Base存储架构说明.md) |
| 启动顺序 / 就绪屏障 | [`docs/架构设计/项目启动顺序管理.md`](../../docs/架构设计/项目启动顺序管理.md) |
| 配置引擎 | [`docs/子系统设计/配置文件设计说明.md`](../../docs/子系统设计/配置文件设计说明.md) |
| 网络配置 | [`docs/子系统设计/网络配置说明.md`](../../docs/子系统设计/网络配置说明.md) |
| 墙钟时间 | [`docs/子系统设计/系统墙钟时间维护说明.md`](../../docs/子系统设计/系统墙钟时间维护说明.md) |
| 运行/自检记录 | [`docs/子系统设计/运行与自检记录设计说明.md`](../../docs/子系统设计/运行与自检记录设计说明.md) |
| 控制台单写者日志 | [`docs/子系统设计/控制台单写者日志设计说明.md`](../../docs/子系统设计/控制台单写者日志设计说明.md) |
| 看门狗 | [`docs/子系统设计/看门狗设计说明.md`](../../docs/子系统设计/看门狗设计说明.md) |
| BSD Socket API | [`docs/网络/BSD_Socket_API_使用指南.md`](../../docs/网络/BSD_Socket_API_使用指南.md) |
| Net 层阻塞模型 | [`docs/网络/Net层阻塞模型架构设计.md`](../../docs/网络/Net层阻塞模型架构设计.md) |
| UI 架构 | [`docs/UI设计/UI_设计架构.md`](../../docs/UI设计/UI_设计架构.md) |
| 菜单页面 | [`docs/UI设计/菜单页面设计.md`](../../docs/UI设计/菜单页面设计.md) |
| 内容页 | [`docs/UI设计/内容页架构.md`](../../docs/UI设计/内容页架构.md) |
| 远程显示协议 | [`docs/UI设计/RemoDispBus协议与接收设计说明.md`](../../docs/UI设计/RemoDispBus协议与接收设计说明.md) |
| 采集与算法层(语义取数与有效性~保护判据) | [`docs/算法/采集与算法层设计说明.md`](../../docs/算法/采集与算法层设计说明.md) |
| 保护功能复现规划(产品定义 / 定值 / 路线) | [`docs/算法/继电保护功能复现规划.md`](../../docs/算法/继电保护功能复现规划.md) |
| 全周傅氏算法原理 | [`docs/算法/傅氏算法详解.md`](../../docs/算法/傅氏算法详解.md) |
| 采样有效性校验 | [`docs/算法/采样有效性校验设计说明.md`](../../docs/算法/采样有效性校验设计说明.md) |
| ADC 采样节拍与 DMA | [`docs/子系统设计/ADC采样节拍设计说明.md`](../../docs/子系统设计/ADC采样节拍设计说明.md) |
| ADC 通道映射与实测 | [`docs/硬件平台/ADC通道映射说明.md`](../../docs/硬件平台/ADC通道映射说明.md) |
| ADC 前端注入验证流程 | [`docs/测试说明/专项验证/ADC前端注入验证流程.md`](../测试说明/专项验证/ADC前端注入验证流程.md) |
| IEC 60870-5-104 上行 | [`docs/子系统设计/IEC104上行设计说明.md`](../../docs/子系统设计/IEC104上行设计说明.md) |
| 数据可信(标定 / 快照 / 运行参数) | [`docs/任务规划/数据可信.md`](../任务规划/数据可信.md) |
| 页面补齐规划 | [`docs/UI设计/页面补齐规划.md`](../../docs/UI设计/页面补齐规划.md) |
| 编号规范 | [`docs/编号规范.md`](../../docs/编号规范.md) |
| 部署环境 | [`docs/环境与部署/项目部署环境要求.md`](../../docs/环境与部署/项目部署环境要求.md) |
| 硬件网表 | [`docs/硬件平台/V0.1_网表详细分析报告.md`](../../docs/硬件平台/V0.1_网表详细分析报告.md) |
| 待办登记簿 | [`docs/任务规划/README.md`](../任务规划/README.md) |
| 实机测试指南 | [`docs/测试说明/板级测试/实机测试实施指南.md`](../测试说明/板级测试/实机测试实施指南.md) |
| 测试标准流程 | [`docs/测试说明/流程与规范/测试开发标准流程.md`](../测试说明/流程与规范/测试开发标准流程.md) |
| RAM 预算与优化 | [`docs/内存管理/RAM使用分析与优化方案.md`](../../docs/内存管理/RAM使用分析与优化方案.md) |
| AI 开发规范 | `AGENTS.md`(仓库根) |
| 开发者入门 | `README.md`(仓库根) |