Files
STM32F4-Base/docs/产品需求/产品需求设计文档.md
T
wandering1 a275539c17 docs(全仓): 内容页黄金 47 → 53 的当前态同步——含钩子显示名,登记簿口径另笔
task-9 让内容页黄金从 47 涨到 53(通讯设置页六态),本笔只做**当前态陈述**的同步;真值全部本轮实测:
`simulator/tests/ref/*.bin` = **53**;`git diff 31ca4a1f..HEAD` 对该目录 = **
6 个新增、零修改** ⇒ 存量 47 张
逐字节未变(这条是关键:新增页不该让旧页重生成,否则回归基线就失去意义)。

**四类命中各走各路,这是本笔的全部难点**(分类比替换重要得多):
· 当前态 → 改,25 处页数,落在 16 只文件(AGENTS、钩子显示名、导航器 docstring、`lvgl-simulator` s
kill、
  门禁索引与 `ui-golden` 详解、UI 自动化流程、内容页架构、页面补齐规划与扩充方案、数据供给、自动化测
  试流程、覆盖率口径、测试开发标准流程、模拟器两份 README);
· 变更记录历史行 → **一条不动**,另在其下一行插入当日「47 → 53」新行 6 条(含判据与零漂移证据);
· **同数不同义 → 不动**:覆盖率口径里的「47 个 .c」「47 = 13+5+29」、ram_report 的表行号 47、`#47-D`
  诊断号、LSI 47kHz、47,360 栈字数、像素哈希 3476/3474 —— 一并替换就会毁掉六个不相干的事实;
· 带日期戳 / 自declare 冻结的快照 → 不臆改(数据供给篇那句上一笔已加「当时」,页面补齐规划里那张
  「2026-10-25 复核…剩 21 叶」的表自带「完成即冻结不回改」声明)。

复扫残留当前态 47 = **0**。⚠ 顺手纠一条**会教错方法**的陈旧:naive 计数 `grep -c '#page='` 上一笔写的是
22,现在真值是 **23**(文件头示例行也算一次)——旧文档里那个 22 **恰好等于新的正确 bound 数**,不改的话,
下一个人用错算法还能碰巧对上,比错更糟。

⚠ `AGENTS.md` 里本次有两件事同行相邻(本笔的内容页 53 页 + 批次三那句 `TC-*` 停发),**分两句说明**:
前者是计数同步,后者是昨天那条「批次三不坐」裁定的入口文案,二者不是同一件事。

钩子显示名改在 `.pre-commit-config.yaml`(47 → 53)。**这条必须与代码同批入库**:配置脏而未 stage 时
pre-commit 会直接报错挡住全仓任何提交(今天已实测两次),所以本笔先落它,把后面几笔的路清开。

自查:`check_doc_links --all` rc=0(203 篇)、`check_ids --strict` rc=0 且不可判定绝对
数 **979 未变**(新增
文本里的提交号与 `#page=comm` 没推高它)、`gen_task_table --check` 0、`check_bom_drift`
 0、18 只文件 noBOM /
LF / U+FFFD=0;行数变动 +49/−47 全部可归属(插 6、删 2、并 1),其余为 1:1 等行替换。登记簿销项与叶数
(`UI补齐.md` + `docs/任务规划/README.md`)**不在本笔** —— 那是口径变更不是计数同步。
2026-10-27 13:54:05 +08:00

881 lines
54 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# STM32F4-Base 产品需求设计文档
> **文档定位**:本项目**面向产品的需求基线**——把散落在架构、子系统、测试、问题记录各文档中的
> "产品做什么 / 做到什么程度 / 依据是什么"收敛成一份可评审、可追溯的需求说明,供甲方、产品、
> 开发、测试与评审者建立统一认知。
> **范围**:硬件规格、系统架构、功能需求(FR)、接口规范、非功能需求(NFR)、UI 规格、平台约束、
> 待实现项与路线图、验收判据。
> **事实来源**:本文所有数值与结论均取自仓库内**受管事实源**(代码、`.ioc`、`.map`、网表分析报告与
> 各模块设计文档),凡与其它文档冲突处,以本文标注的权威来源为准。
> **版本**:v1.0 **日期**:2026-10-05 **状态**:现状基线(含已实现项 + 待实现项)
> **阅读前提**:建议先读 [`docs/README.md`](../../docs/README.md)(文档索引)与
> [`docs/架构设计/项目架构总览.md`](../../docs/架构设计/项目架构总览.md)(全景架构)。
---
## 1. 文档说明
### 1.1 目的与范围
本文回答三个问题:
1. **产品是什么**——一块工业级嵌入式 DTU 平台固件,做什么、面向谁、部署在哪;
2. **产品要做到什么程度**——每一项功能/接口/性能/可靠性的**可验收要求**;
3. **产品当前做到哪一步**——已实现、已验收、待实现的**明确边界**。
**不含**:具体代码实现细节(见各模块设计文档)、测试用例步骤(见 `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/配置源码 |
| **@build / @test** | 仓库约定的构建 / 自动化测试命令别名 |
### 1.3 读者与相关文档
| 角色 | 建议读法 |
|---|---|
| 甲方 / 产品 | §2 产品概述 → §5 功能需求 → §9 待实现项与路线图 → §10 验收判据 |
| 开发 | 全文;深入时按 §11 指向各模块设计文档 |
| 测试 / 验证 | §5、§6、§7、§10 + `docs/测试说明/` |
| 评审 | §2、§7、§8、§9 |
---
## 2. 产品概述
### 2.1 产品定位
**STM32F4-Base 是一块工业级嵌入式 DTU 平台固件**,基于 STM32F407ZGTx,集成
**以太网通信、SPI NAND 存储、ADC 采集、RS-485 通信与 FTP 文件服务**,并自带
**远程显示(UI 推流)、墙钟对时、看门狗自恢复、运行/自检记录**等工业现场能力。
它既是一台可独立部署的现场设备(通过以太网与远端客户端交互),也是一套
**可复用的平台底座**——后续产品(保护测控、馈线自动化终端等)在此底座上补齐采集与协议。
> **产品现状一句话**:平台能力(网络 / 存储 / 时间 / UI / 日志 / 看门狗 / FTP / 配置)
> **已实现并台架验收**;DTU 的**核心采集职责(ADC / RS-485)尚未接线**,是当前最大功能缺口
> (见 §9)。这不是缺陷难修,而是**产品定义未落定**。
### 2.2 产品形态与部署
```mermaid
flowchart LR
subgraph DEV["开发机(Windows)"]
K[Keil MDK-ARM v5<br/>编译/烧录]
SIM[LVGL PC 模拟器<br/>CMake + SDL2]
PY[Python .venv<br/>脚本/测试]
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 串口 USART1 @115200 调试日志" --> TGT
TGT -- "以太网 CH395F<br/>FTP:21 / 对时:8888 / 远程显示:7003" --> RMT
```
| 环境 | 角色 | 必需性 |
|---|---|---|
| ① 开发机 | 编译/烧录、UI 模拟器、脚本工具链 | 开发/测试必需 |
| ② 目标板 | 运行固件的 DTU 硬件 | 上机必需 |
| ③ 远端客户端 | 远程看图/按键、对时推送、FTP 文件操作 | 现场使用必需 |
**关键产品特征**:**MCU 端无物理屏幕**——UI 画面以 160×160 单色位图(1bpp)经 TCP 推送到远端
客户端渲染,按键亦由远端下发(见 §5.6、§8)。
### 2.3 设计目标与原则
| # | 目标 | 落地手段 |
|---|---|---|
| G1 | **可靠**:现场死机可自恢复、掉电不丢配置、错误可追溯 | IWDG + 任务心跳位图、原子写 + 迁移、错误码通道四要素 |
| G2 | **可维护**:分层解耦、单一事实源、双端复用、门禁强制 | 六层架构、就绪屏障、生成器(菜单/字体/任务表)、pre-commit 18 钩子 |
| G3 | **可移植**:UI/配置源码 MCU 与 PC 模拟器共用 | 平台后端抽象(`*_mcu.c` / `*_sim.c`),核心零平台依赖 |
| G4 | **可扩展**:新子系统/新配置段/新页面按模板接入 | 事件位扩展、段注册三步、页面插件描述符、`App/protocol/` 预留 |
| G5 | **可验收**:每项能力有明确判据与自动化门禁 | `@build` / `@test` / L0 静态 / L0.5 主机侧单测 / 台架验证 |
**设计原则**(贯穿全项目):
1. **上层只依赖相邻下层(单向)**;禁止反向依赖(例外经登记)。
2. **状态位常驻、命令位消费**——Event Flags 就绪位必须 `osFlagsNoClear`。
3. **能推导的就不存**——持久化"不可推导"的状态(导航历史、配置、日志),其余现算。
4. **单写者**——配置文件属主 = `defaultTask`;syslog 唯一写者 = `defaultTask`;控制台唯一发送者 = `defaultTask`。
5. **一切写入 = 临时文件 + rename**(原子替换),半截窗口结构消除。
### 2.4 产品范围
**本期(已实现)**:网络通信与配置、NAND 存储栈、时间子系统、运行/自检记录、UI 远程显示、
看门狗、FTP 服务、LED 指示、启动屏障与任务调度。
**非本期(待实现 / 待定义)**:ADC 采集链路、RS-485 采集链路与协议状态机、DTU 核心转发职责
(MODBUS RTU / 上行 TCP 等,取决于产品定义)、UI 部分叶子内容页、SNTP 客户端等。
---
## 3. 系统总体架构
### 3.1 六层分层架构
![STM32F4-Base 项目分层架构](../images/项目架构总览.svg)
> 上图由 `tools/arch_diagram/gen_arch_svg.py` 生成(手绘 SVG,零连线交叉);改架构后编辑脚本顶部
> `ROWS` 数据重跑即可,源数据与下表一致。
分层原则:**上层只依赖相邻下层(单向)**;**① 业务层为顶层**(仅 `net_cfg`,被 UI 与基础服务共用),
**② UI 层与 ③ 基础服务层同级并列**。
| 层 | 目录 | 职责 | 关键模块 |
|---|---|---|---|
| ① 业务层 | `Drivers/BSP/NETCFG/` | 顶层共享配置(被 UI 与基础服务共用) | `net_cfg` |
| ② UI 层 | `Drivers/BSP/UI/` | LVGL 界面(与 PC 模拟器共用源码) | `ui_port` `ui_menu` `ui_menu_view` `ui_menu_tab` `ui_content_page` `ui_page_*` `ui_net_sink` `ui_font_cn_12` `ui_deco` |
| ③ 基础服务层 | `App/`、`Drivers/BSP/NET/lftpd/` | 应用侧服务与编排 | `app_main` `sys_events` `app_cfg` `task/*` `lftpd` `time_sync` |
| ④ 中间件层 | `Drivers/BSP/NET/`、`Lib/`、`App/util/` | 通用库 / 协议栈 | `net_socket` `net_select` `FatFs` `nand_ftl`(dhara) `LVGL` `sys_clock` |
| ⑤ 驱动层 | `Drivers/BSP/{CH395F,GD5F2GQ5UE,TPAFE5160,SD2506,RS485}/` | 芯片寄存器 / 时序 / 中断 | `ch395f` `gd5f2gq5ue` `tpafe5160` `sd2506` `rs485` |
| ⑥ 内核 / HAL | `Middlewares/`、`Drivers/STM32F4xx_HAL_Driver/`、`Drivers/CMSIS/` | 任务调度、外设 HAL、启动 | FreeRTOS V10.3.1 · STM32 HAL · CMSIS |
| ⑦ 硬件 | — | STM32F407ZGTx @168MHz | SPI1 / SPI2 / I2C1 / UART4 / USART1 / DMA / GPIO / TIM7 |
### 3.2 运行时任务模型
六个 FreeRTOS 任务(`Src/freertos.c` 创建,任务体在 `App/task/`、`App/sync/`):
| 任务 | 栈 | 优先级 | 职责 |
|---|---|---|---|
| `defaultTask` | 4096 B | Normal | FatFS 挂载(无卷 `f_mkfs`)→ 卷健康探针/自愈 → `net_cfg_load`(配置引擎属主)→ 置 `STORAGE_READY` → 日志落盘(唯一写者)+ 配置保存执行/2s poll + LED 闪烁 + 栈水位观测 + **看门狗唯一喂狗者** |
| `netTask` | 4096 B | Normal | 等存储 → `net_stack_bring_up()` → 置 `NET_READY` → `net_poll` 轮询(CH395F 操作串行化) |
| `ftpTask` | 4096 B | Normal | 等网络**且**存储(`WaitAll`)→ `lftpd_start("/", 21)` → 单客户端会话循环 |
| `testTask` | 8192 B | BelowNormal | 测试套件入口(`test/board/test_config.h` 选择)。**产品构建不创建**(未选套件时立即 `vTaskDelete` 回收其 8 KB CCM 栈) |
| `uiTask` | 16384 B | BelowNormal | 等网络 → `ui_port_init`/`ui_menu_init`/`ui_standby_init`(空闲 60s 切待机屏)/`ui_net_sink` → 10 ms 刷新 |
| `timeSyncTask` | 2048 B | Normal | 等网络 → TCP :8888 收 PC 推送时间 → 写 RTC |
> `idle`(128 words)/ `timer`(256 words)由 FreeRTOS 内核创建。所有任务栈从 48 KB `heap_4` 堆(CCM)分配。
> 任务表与 `Src/freertos.c` 的 `osThreadAttr_t` 一致,由 `scripts/gen_task_table.py` 生成并门禁校验。
### 3.3 就绪屏障(Event Flags)
![就绪屏障(Event Flags)](../images/就绪屏障.svg)
用 CMSIS-RTOS2 Event Flags(`sys_evtHandle`)表达"子系统就绪",取代魔法延时与全局标志轮询。
生产者与消费者互不知晓,只经事件组解耦;新增子系统只需定义新 bit + 各自 wait/set。
| 位 | 名称 | 类型 | 语义 |
|---|---|---|---|
| bit0 | `SYS_EVT_NET_READY` | 状态位 | netTask:`net_init` + PHY 稳定完成 |
| bit1 | `SYS_EVT_STORAGE_READY` | 状态位 | defaultTask:FTL/FatFS 挂载 + 配置就绪 |
| bit2 | `SYS_EVT_FTP_READY` | 状态位 | ftpTask:`lftpd_start` 完成 |
| bit3 | `SYS_EVT_TIMESYNC_READY` | 状态位 | timeSyncTask:首次时间同步完成 |
| bit4 | `SYS_EVT_NET_RECONFIG` | 命令位 | UI:请求立即应用新网口配置 |
| bit5 | `SYS_EVT_CFG_SAVE` | 命令位 | UI:网口配置保存请求(defaultTask 消费并写盘) |
| bit6 | `SYS_EVT_CONSOLE_TX` | 命令位 | 控制台单写者:生产者入环形缓冲后置位 |
> ⚠️ **铁律**:状态位(bit0~3)表达**持久状态**,任何消费者都**不许消费**——必须
> `osFlagsWaitAny|osFlagsNoClear`。命令位(bit4~6)表达"请做一件事",必须**恰好被消费一次**(消费型等待)。
> 历史事故:就绪位被先到的等待者消费,导致 `ftpTask` 的 `WaitAll(bit0|bit1)` 永不成立、21 端口从未监听(静默)。
### 3.4 启动时序
```mermaid
sequenceDiagram
autonumber
participant M as main
participant A as app_main_init
participant K as osKernelStart
participant D as defaultTask
participant N as netTask
participant F as ftpTask
participant U as uiTask
participant T as timeSyncTask
M->>M: HAL_Init + CubeMX 外设初始化<br/>(SPI1/SPI2/I2C1/UART4/USART1/DMA/GPIO/TIM7)
M->>A: app_main_init()
Note over A: 时间子系统优先,使后续日志带时间戳
A->>A: sd2506_init() → sys_clock_init()(墙钟基准)
A->>A: rs485_init() → gd5f2gq5ue_init()(BBT 扫描)
A->>A: tpafe5160_init() → SD2506 诊断 → start_conv_irq()
A-->>M: "App init OK"
M->>K: osKernelStart()
K->>D: 调度
D->>D: f_mount(无卷则 f_mkfs FAT32)→ 卷健康探针/自愈 → net_cfg_load()
D-->>N: set SYS_EVT_STORAGE_READY
N->>N: wait STORAGE_READY(Any|NoClear)→ net_init(ip,mask,gw,mac)<br/>→ PHY 强制 100M 全双工 → 等链路 ≤3s
N-->>F: set SYS_EVT_NET_READY
F->>F: wait NET_READY|STORAGE_READY(All|NoClear)→ lftpd_start("/", 21)
U->>U: wait NET_READY(Any|NoClear)→ ui_port_init → ui_menu_init → ui_net_sink_start(7003)
T->>T: wait NET_READY(Any|NoClear)→ TCP listen :8888
```
### 3.5 线程间通信
| 机制 | 对象 | 用途 |
|---|---|---|
| Event Flags | `sys_evtHandle` | 启动就绪屏障 + 命令位(§3.3) |
| Message Queue | `netMsgQueue`(深度 8,`net_msg_t`) | 所有 CH395F 操作经队列**串行化**到 netTask 执行,保证 SPI 单线程访问 |
---
## 4. 硬件规格
### 4.1 主控与内存
| 项 | 值 | 说明 |
|---|---|---|
| MCU | STM32F407ZGTx | Cortex-M4(FPU),LQFP144 |
| 主频 | 168 MHz | HSE 8 MHz → PLL M=4 / N=168 / P=2 |
| Flash | 1 MB(0x08000000) | 片内 |
| SRAM | 192 KB(128 KB 主 SRAM1+SRAM2 + 64 KB CCM) | 链接器映射 128 KB 主 SRAM;CCM 用于 FreeRTOS 堆(任务栈) |
| RTOS | FreeRTOS V10.3.1(CMSIS-RTOS2) | HAL 时基 TIM7,tick = 1 ms;NVIC 4 位抢占,外设中断优先级 ≥5 |
| 工具链 | Keil MDK-ARM v5(ARMCC v5) | 工程锁 `uAC6=0`,**不支持 armclang** |
| CubeMX | 6.17.0 + STM32Cube FW_F4 V1.28.3 | 与 `.ioc` 记录一致,再生成须用此组合 |
### 4.2 外设与引脚分配
> **权威来源**:V0.1 网表分析报告 + `Inc/main.h`(代码已迁移至 V0.1)。下表为 **V0.1 硬件**的引脚。
| 外设 | 接口 | 引脚 | 说明 |
|---|---|---|---|
| **CH395F 以太网** | SPI2 | PB12 CS / PB13 SCK / PB14 SDO / PB15 SDI | 10/100M 以太网控制器;PHY 强制 100M 全双工;**SPI 分频系数必须为 8**(PCB 设计约束) |
| **GD5F2GQ5UE NAND** | SPI1 | PE0 CS / PB3 SCK / PB4 MISO / PB5 MOSI / PB8 WP / PE1 HOLD | 256 MB SPI NAND(2 Gbit) |
| **TPAFE5160 ADC** | 并行 16 位 | PG0~PG15 数据 / **PC6 FRSTDATA** / **PC7 BUSY**(EXTI) / **PC8 RD** / **PC9 CONVST** / **PA8 OS2** / **PA11 OS1** / **PA12 OS0** | 高精度多通道 ADC |
| **SD2506 RTC** | I2C1 | PB6 SCL / PB7 SDA | 提供墙钟基准与日志时间戳 |
| **USART1(调试串口)** | UART | PA9 TX / PA10 RX | 115200 8N1;printf 日志 + `@test` 采集通道 |
| **UART4(RS-485)** | UART | PC10 TX / PC11 RX / **PA15 DE** | 半双工通信 |
| **状态 LED ×6** | GPIO | PC5 / PB0 / PB1 / PB2 / PF11 / PF12 | **低电平点亮** |
| SWD | — | PA13 SWDIO / PA14 SWCLK | V0.1 已修复 V0.0 的 SWCLK 引脚缺陷 |
| 调试口 | — | CN2(XH-3A,1=SWDIO/2=SWCLK/3=GND) | V0.1 由 H2 排针改为 XH 端子 |
> ⚠️ **V0.0 / V0.1 差异**:V0.1 相比 V0.0 做了**接口裁剪(删 8080 LCD 与 8 键)+ U17 大面积引脚重排 +
> ADC 前端通道置换(17 路中 13 路变化)+ 调试口改型**。固件 BSP 与 ADC 通道映射表**必须按 V0.1 重映射**;
> 详见 `docs/硬件平台/V0.1_网表详细分析报告.md`。
>
> ⚠️ **文档遗留**:根 `README.md` 的"外设接口"表与 `docs/环境与部署/项目部署环境要求.md` §3.2 仍为
> **V0.0 口径**(TPAFE 控制组、RS-485 实例、LED 序号),与 V0.1 不符——**本文以 V0.1 为准**。
### 4.3 存储器件
| 器件 | 容量 | 组织 | 软件栈 |
|---|---|---|---|
| GD5F2GQ5UE | 2 Gbit(256 MB) | 2048 块 × 64 页 × 2048 B | FatFS(FAT32)→ dhara FTL(磨损均衡 / 坏块管理 / GC)→ NAND 驱动 |
| SD2506 | — | I2C1 | RTC(时间/温度/电池诊断) |
- **BBT 持久化**:坏块表存于保留块池(顶部 4 块),不交给 dhara 管理。
- **单写者纪律**:`defaultTask` 拥有存储;`netTask` 只读 RAM 配置副本,不触卷。
- **FatFs 约束**:`FF_USE_LFN=0`(严格 8.3 文件名);`FF_FS_REENTRANT=1` + `ffsystem.c` 互斥钩子(超时 1 s);`FF_FS_NORTC=0`(`get_fattime` 直引用墙钟)。
### 4.4 硬件版本与兼容性
| 版本 | 状态 | 说明 |
|---|---|---|
| V0.0 | 历史 | 含 8080 LCD、8 按键;存在 2 处设计缺陷(SWCLK 引脚错误致 SWD 不通、ADC17 落在非 ADC 引脚) |
| **V0.1** | **当前** | 架构与 V0.0 一致;裁剪 LCD/按键、U17 引脚重排、修复上述 2 处缺陷;固件已迁移 |
| V0.2 | 规划关注 | 新增关注项:VCAP 电容值未标注、PC13/14/15 子板 LED 3 mA 限流、SPI1 无法双落点 |
### 4.5 平台约束
| 约束 | 内容 |
|---|---|
| **CubeMX 生成文件** | `Inc/`、`Src/` 中带 `USER CODE BEGIN/END` 标记;自定义代码**只能**写标记区内 |
| **生成区外的定制** | 仅限 `Src/iwdg.c` 延后启动、4 个 Fault Handler 实体(`scripts/check_cubemx_survival.py` 审计兜底) |
| **`osKernelStart` 前** | 禁止任何 FreeRTOS / CMSIS-RTOS API 调用 |
| **BOM / 编码** | 部分源文件带 BOM(`Drivers/BSP/UI/*`、`App/task/ui_task.c`、`App/cfg/cfg_file.c`、`Lib/lv_conf.h`),被工具改写丢 BOM 会导致 ARMCC 按 CP936 解码、中文字面量编译失败 |
| **SDIO** | V0.1 主板 SDIO 固定引脚组被 TPAFE/UART 全部占用 → **无 SDIO 能力**(当前设计不受影响,FatFs 卷在 SPI NAND 上) |
| **新源文件** | 必须双端注册(Keil `.uvprojx` + `simulator/CMakeLists.txt`),否则不参与编译 |
---
## 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)✅
![存储栈分层](../images/存储栈分层.svg)
**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)✅
![配置文件分层架构](../images/配置文件分层架构.svg)
**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)经 TCP :7003 推送到远端客户端渲染;远端按键回注 UI。
- 实现:LVGL 160×160 1bpp 离屏渲染到 `g_ui_fbuf`(3200 B)→ `ui_net_sink` 经 RemoDispBus 推流;远端按键经协议注入。
- 判据:DTU-RemoteLCD 连 7003 出图(160×160 1bpp)+ 远端菜单导航可用。
**FR-UI-2 菜单与内容页**
- 需求:42 节点层级菜单(最大 4 级)+ 内容页(装置信息/交流量/直流量等)。
- 实现:三层拆分(数据层 `ui_menu_tab` 生成物 / 逻辑层 `ui_menu` 17 B 导航状态 / 显示层 `ui_menu_view` 级联框);内容页框架 + 页面插件描述符。
- 判据:`UI menu init: OK`;菜单像素回归全 PASS。
**FR-UI-3 待机屏**
- 需求:空闲超时(默认 60 s)自动切待机屏,仅确认键回菜单。
- 判据:空闲后进入待机屏,按键返回菜单。
> 详见 §8 UI 规格。
### 5.7 看门狗与故障自恢复(FR-WD)✅
![看门狗心跳模型](../images/看门狗心跳模型.svg)
**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 随系统状态变化。
### 5.9 ADC 采集(FR-ADC)🔶 采集链路已实现(B1);保护/遥测算法与上行待实现
- **需求(B0 已确认,2026-10-10)**:**持续采集** 8 路电气量(Ua/Ub/Uc/U0、Ia/Ib/Ic/I0,**1000 Hz / 20 点/周波**),经保护/遥测算法后**上送 IEC 60870-5-104**(以太网 TCP 2404;遥测上送短浮点、采集上传系数 = 1)。
- **现状(采集链路 B1 + L1 已实现,2026-10-10)**:启动序列调用一次 `tpafe5160_init()`;**运行期周期 CONVST 重触发已由 TIM6 更新中断(1 kHz)接管**(`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback`,2026-10-10)+ `adcTask` 经 L1 层 `adc_input_poll()` 消费(`tpafe5160_snapshot()` 8 通道 + STM32 ADC1 9 通道扫描)+ 帧统计,台架 `@test` 确认 `frame_seq` 严格 1000 Hz 递增、`frst_err=0`、`adc1_rc=0` / `adc1_err=0`;STM32 侧 ADC1 **消费者已落地**(`App/algo/adc_input.c` 的 `stm32_scan()`,「STM32 ADC1 启动与扫描」,2026-10-10),受双转换器无共同采样触发所限,仅逐通道独立监视。
- **后果**:上电后仅采集 1 个样本,此后数据永久陈旧。
- **B0 结论**(2026-10-10,详见 `docs/算法/继电保护功能复现规划.md` §1.5):① 持续采集 ② 1000 Hz(20 点/周波),通道 = 4U+4I(点表 `4011~4018`)③ 上行 **IEC 60870-5-104**(2404,非 ModBus RTU 上行)④ ADC 采集链路为遥测/保护的必需来源 → **常驻编译**;原 `APP_ADC_ENABLE` 占位宏零消费点,已于 2026-10-22 删除(将来若要裁剪,需在真实初始化点接入门控并登记进 `App/app/app_cfg.h`)。
- **实现约束**(见 §9):周期 CONVST 重触发 + `adc_task` 消费 `tpafe5160_snapshot()`;**原子快照 API 已就位**(2026-10-10 修三条采集契约缺陷,旧的 `get_buf` / `data_ready` / `clear_ready` 已删 → 取数不再撕裂、重触发不再丢帧、芯片不驱动总线会被 `init()` 首帧自检抓到);栈预算核算;V0.1 通道映射已固化 → `docs/硬件平台/ADC通道映射说明.md`(13/17 路置换;语义 UA/UB/UC/UN、IA/IB/IC/IN、V_ZL2=温度,其余备用;网名 ADC17 = PC4 用 `ADC_CHANNEL_14`,**不是** `ADC_CHANNEL_12`)。⚠ **8 路电气量分属 TPAFE / STM32 两片转换器属 V0.1 硬件缺陷**(跨转换器无同一时刻性、精度与量程不一致,登记簿条目「双转换器无共同采样触发」,V0.2 改版要求全部走 TPAFE)→ 在 V0.1 上主板侧只做**逐通道独立监视**,功率 / 功率因数 / 相角 / 矢量类量一律取子板计量模块结果,不得由主板混算。
### 5.10 RS-485 采集(FR-RS485)⬜ 待实现
- **需求(B0 已确认,2026-10-10)**:半双工 RS-485 **对下 ModBus RTU 采集**(下行采集口,非上行口;上行走以太网 IEC 60870-5-104)。
- **现状**:`rs485_init()` 已调用;`rs485_task.c` 为空桩,未在 `freertos.c` 创建;`App/protocol/` 预留(仅 README + `.gitkeep`)。
- **实现约束**:双缓冲 + 序号(防包撕裂)+ DE 收尾(超时路径等 TC)+ 协议状态机;解析必须与硬件 I/O 分离(`App/protocol/` 正为此预留)。B0 结论详见 `docs/算法/继电保护功能复现规划.md` §1.5。
---
## 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** | 被控站接入:遥测/遥信上送、总召、遥控/定值下发(B4) | 任意 104 主站连 2404,STARTDT+总召往返(→ `docs/子系统设计/IEC104上行设计说明.md`) |
> **现场组网要求**:设备与客户端**同网段**(默认 .100/24、网关 .1);跨网段先改 IP 再上线。
### 6.2 RemoDispBus 协议
![RemoDispBus 帧格式](../images/RemoDispBus帧格式.svg)
| 项目 | 内容 |
|---|---|
| 传输 | TCP,端口 **7003**;单连接(`backlog=1`) |
| 帧格式 | `[TAG(1)][CMD(1)][LEN_H(1)][LEN_LO(1)][DATA(n)][CRC(1)]` |
| 方向标识 | TAG `0xAA` = 客户端→服务端,`0xBB` = 服务端→客户端 |
| 校验 | CRC = 数据段逐字节**异或**(低 8 位,不含 TAG/CMD/LEN) |
| 字节序 | 多字节字段**大端**(网络字节序) |
| 最短帧 | 5 字节(LEN=0) |
| 接收方式 | 逐字节有限状态机(TAG 定界 + 长度定长 + CRC 校验,自动重同步) |
**命令集**:
| CMD | 名称 | 请求数据段 | 应答数据段 |
|---|---|---|---|
| 0 | KEEPLIVE | 无 | 无(原样回显) |
| 1 | INIT | 无 | `[宽u16][高u16][帧字节数u32]`(**8 B**) |
| 2 | KEY | `[按键码u8]` | 无(确认) |
| 3 | LCDMEM | `[起始偏移u32]` | `[偏移u32][1bpp位图 3200B]`(3204 B) |
**按键码**:`UP=0x02` / `DOWN=0x40` / `LEFT=0x10` / `RIGHT=0x08` / `ENTER=0x20` / `ESC=0x01`。
**位图格式**: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。
> 详见 `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 调试串口
| 项 | 值 |
|---|---|
| 端口 | USART1(PA9 TX / PA10 RX),115200 8N1 |
| 用途 | printf 日志 + `@test` 采集通道 |
| 配置源 | `.serialtest/config.json`(单一配置源) |
| 工具 | 仓库内置 Python 串口助手(`.dsh/skills/serial-assistant/`,常驻 daemon + CLI) |
**启动标记(`@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` + 7 个事件位 | 启动屏障 |
---
## 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 设计 |
### 7.3 内存与资源预算
> **权威来源**:`.map` 受管快照 + V5 30 min 运行时水位(2026-10-04)。详见
> `docs/内存管理/RAM使用分析与优化方案.md`。
**静态区(产品构建)**:
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| Flash ER_IROM1 | 266,632 B | 1024 KB | 25.4% |
| ROM 合计 | 267,372 B | — | — |
| RW_IRAM1 | 84,960 B | 131,072 B | 64.8% |
| RW_CCM | 49,184 B | 65,280 B | 75.3% |
**运行时(V5 30 min 真实负载)**:
| 项 | 值 | 判定 |
|---|---|---|
| 实测堆 | free=8,664 / min_ever=4,728 B | — |
| `defaultTask` 栈峰值 | 3664 / 4096 B(余量 432 B ≈ **10%**) | ⚠️ **低于 30% 判据,需处理** |
| `netTask` 栈峰值 | 1228 / 4096 B(余量 70%) | ✅ |
| `ftpTask` 栈峰值 | 2516 / 4096 B(余量 38%) | ✅ |
| `uiTask` 栈峰值 | 4060 / 16384 B(余量 75%) | ✅ |
| `timeSyncTask` 栈峰值 | 792 / 2048 B(余量 61%) | ✅ |
| `testTask` 栈峰值(产品态空转) | 496 / 8192 B(余量 93%) | ✅ |
> **定栈规则**:改栈或加大栈内局部变量前后**必须**重跑 `scripts/stack_report.py`
> (Keil 静态调用图 × 串口 `[STACK]` 水位,判定取两者更坏者)。
> **LVGL 内存池** 16 KB(静态占用,2026-10-15 由 40 KB 收缩);**FreeRTOS 堆** 56 KB(CCM)。
### 7.4 可维护性(测试与门禁)
**测试分层**:
| 层 | 内容 | 运行位置 |
|---|---|---|
| L0 | 静态门禁(pre-commit 18 钩子) | 本地 / CI |
| L0.5 | 主机侧单元测试(MSVC 离线编译真实源码) | 本地 / CI |
| L1 / L2 | 板端套件 / 模拟器回归 | 本地(需硬件 / 模拟器) |
| L3 | 真机台架(`@test` 闭环 + 探针) | 本地(需真机 + ST-LINK) |
**构建 / 测试命令**:
```powershell
# 构建(@build):0 错 0 警
& '.venv\Scripts\python.exe' .dsh/skills/keil-build/scripts/keil_build.py # 退出码 0=PASS / 1=构建失败 / 2=环境错误
# 自动化测试(@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 18 钩子**:trailing-whitespace、end-of-file-fixer、check-added-large-files、
check-merge-conflicts、check-yaml、cppcheck、test-cppcheck、no-mojibake、coding-standard、
doc-links、task-table、mem-numbers、cubemx-survival、watchdog-unit-test、sys-clock-unit-test、
cfg-file-unit-test、test-suite-switch、gitlint。
**受管事实源(禁止手改,由脚本生成)**:
- 任务表(`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/宿主文件) |
| 验证 | `@build` 0/0 → `@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、无运行期设置页 |
| 远程显示 | 无鉴权,单连接;现场同网段部署 |
---
## 8. UI 规格
### 8.1 屏幕与渲染
| 项 | 值 |
|---|---|
| 屏幕尺寸 | 160 × 160 px |
| 色深 | 1bpp 单色(MSB 优先),帧缓冲 3200 B |
| 渲染 | 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 菜单树(42 节点)
![级联框总览](../images/级联框总览.svg)
**规模**:菜单节点 **9** 个(含根)+ 叶子节点 **33** 个 = **42** 节点;最大深度 **4 级(L0~L3)**。
**根页(主界面)7 个一级条目**:装置信息 / 实时数据 / 参数定值 / 三遥设置 / 装置维护 / 通讯参数 / 记录查询。
**完整层级树**:
```text
主界面 (L0)
├─ 装置信息 [叶子]
├─ 实时数据 ▶ [菜单]
│ ├─ 交流量 ├─ 直流量 └─ 遥信量
├─ 参数定值 ▶
│ ├─ 整定 ▶(参数 / 定值 / 控制字 / 软压板)
│ └─ 查看 ▶(参数 / 定值 / 控制字 / 软压板)
├─ 三遥设置 ▶(遥测死区 / 遥测系数 / 遥信类型 / 遥信防抖 / 双点遥信)
├─ 装置维护 ▶(时钟设置 / 强制复归 / 手动录波 / 清除记录)
├─ 通讯参数 ▶(通讯设置 / 网口设置 / SNTP设置)
└─ 记录查询 ▶(SOE记录 / 事故记录 / 操作记录 / 保护告警 / 保护启动 /
遥控记录 / 自检记录 / 运行记录 / 运行报告)
```
**三层代码架构**:数据层 `ui_menu_tab`(生成物,`g_menu_tab[42]` 扁平父子索引表,≈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 内容页
| 页 | 节点 | 类型 | 规格 |
|---|---|---|---|
| 装置信息 | node 1 | 静态键值行 | 6 行不分页;副标题"馈线自动化终端" |
| 交流量 | node 3 | 数据列表 | 24 行 / 每页 8(3 页) |
| 直流量 | node 4 | 数据列表 | 3 行单页 |
| 用户密钥 | 受保护叶子 | 表单/操作页 | 3 个 14×14 数字框 + 反显光标 |
- 内容页为**独立全屏屏**,由框架一次性自绘(**零 LVGL 子对象**),`ESC` 返回菜单屏。
- 菜单导航状态**不压层**,返回即原样还原;同一时刻至多一个内容页打开。
- 节点→页映射在逻辑层 `menu_content_page_for(node)`;其余叶子暂无内容页(进空白终点框)。
### 8.4 密码与权限
| 项 | 值 |
|---|---|
| 查看密码 | `000`(授只读权限) |
| 修改密码 | `123`(授修改权限) |
| 存储 | `ui_pwd_def.h` **编译期**写入,不落 Flash |
| 流程 | 受保护叶子先 arm → 开密码页 → 确认一致后回调 → **先关页再续做进入动作**(携授予权限) |
### 8.5 中文字体
- 字体 `ui_font_cn_12.c`:宋体 12px 1bpp **子集**(当前 122 唯一字 / 217 字形)。
- 生成流程:`tools/font_gen/extract_cn.py`(扫 UI 源码 → `ui_cn_chars.txt`)→ `tools/font_gen/make_font.py`。
- **约束**:任何 UI 文案新增汉字后必须走此流程,否则显示为空白方块。
---
## 9. 待实现项与路线图
### 9.1 功能缺口(当前最大产品空洞)
| 缺口 | 现状 | 影响 |
|---|---|---|
| **ADC 采集链路** | ✅ **采集链路已实现(B1 + L1,2026-10-10)**:采样节拍由 TIM6 更新中断(1 kHz)硬件触发;`adcTask` 经 L1 层 `adc_input_poll()` 消费(TPAFE 8 通道快照 + **STM32 ADC1 9 通道扫描**),台架 `@test` 闭环(1000 Hz、`adc1_rc=0`);取数按语义(「STM32 ADC1 启动与扫描」,双转换器无共同采样触发下逐通道独立监视) | 数据路径已通;剩余为上层保护/遥测算法与 104 上行 |
| **RS-485 采集链路** | `rs485_task.c` 空桩;`App/protocol/` 仅 README | 无 RS-485 数据收发(B0 定:**对下 ModBus RTU 采集**) |
| **DTU 核心转发职责** | **B0 已定(2026-10-10)**:上行 **IEC 60870-5-104**(以太网 TCP 2404,遥测短浮点系数 1);RS-485 对下 ModBus RTU 采集 | 转发目标已明确,待实现 |
> **根因不是"缺陷难修",而是"功能没接"**——B1/B2 的规模取决于 B0 产品定义,**B0 已于 2026-10-10 确认**(见 `docs/算法/继电保护功能复现规划.md` §1.5)。
### 9.2 阶段路线图
> **顺序唯一权威 → [`../任务规划/README.md`](../任务规划/README.md) §2(统一推进顺序,波次见 §2.0;
> RS-485/B2 整簇暂缓见其 §2.4,2026-10-12 用户决定)**。
> 本节自 2026-10-11 起不再维护路线图(原阶段图删除,避免与登记簿漂移;原 C4"defaultTask 栈余量 10%"
> 已于 2026-10-10 削链闭环销项,见 `../内存管理/RAM使用分析与优化方案.md`)。
> 功能缺口口径见 §9.1;B0 产品定义结论见 `../算法/继电保护功能复现规划.md` §1.5。
### 9.3 未接线开关说明
| 开关 | 位置 | 现状 |
|---|---|---|
| 功能裁剪开关 | `App/app/app_cfg.h`(当前无宏) | **没有任何生效的裁剪开关**:原三个 `APP_*_ENABLE` 占位宏因全库零消费点已于 2026-10-22 删除,各模块一律编译进产品——B0 已确认 ADC / RS-485 均为必需链路(ADC=遥测/保护来源,RS-485=对下 ModBus RTU 采集)。将来确需裁剪时在真实初始化点接入门控,并同步登记回该文件 |
| `TEST_SUITE_*` | `test/board/test_config.h` | 测试套件**互斥**,一次构建只开一个;全注释 = 正常产品固件 |
---
## 10. 验收判据
### 10.1 构建与静态门禁
| 项 | 判据 |
|---|---|
| 构建 | `@build` → `BUILD_OK: 0 errors, 0 warnings`(退出码 0) |
| 静态门禁 | `pre-commit run --all-files` 全部通过(18 钩子) |
| 主机侧单测 | 看门狗 39 用例 / 系统时钟 100 断言 / 配置文件 149 断言 全 PASS |
| 双端构建 | Keil 与 CMake 均 0 错 0 警 |
| UI 回归 | `check_menu_rows.py` + `check_content_pages.py` PASS |
### 10.2 上电自检(`@test`)
| 项 | 判据 |
|---|---|
| Required 标记 | `App init OK` / `Network Init` / `net init: OK` / `PHY: 100M FULL` |
| Forbidden 标记 | 无 `FAIL` / `ERROR` / `HardFault` / `[ERR]` |
| 自检记录 | `selftest: no fault record` |
| 退出码 | `@test` → 0(PASS) |
### 10.3 现场验收清单
| # | 项目 | 方法 | 通过标准 |
|---|---|---|---|
| 1 | 上电自启动 | 断电上电 + 串口采集 | `@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` 按新值生效) |
| 8 | LED 状态 | 目检 | 6 路 LED 随系统状态变化(低电平点亮) |
### 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 | ✅ |
---
## 11. 相关文档
| 主题 | 文档 |
|---|---|
| 文档索引 | [`docs/README.md`](../../docs/README.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/硬件平台/V0.1_网表详细分析报告.md`](../../docs/硬件平台/V0.1_网表详细分析报告.md) |
| 待办登记簿 | [`docs/任务规划/README.md`](../任务规划/README.md) |
| 实机测试指南 | [`docs/测试说明/实机测试实施指南.md`](../../docs/测试说明/实机测试实施指南.md) |
| 测试标准流程 | [`docs/测试说明/测试开发标准流程.md`](../../docs/测试说明/测试开发标准流程.md) |
| RAM 预算与优化 | [`docs/内存管理/RAM使用分析与优化方案.md`](../../docs/内存管理/RAM使用分析与优化方案.md) |
| AI 开发规范 | `AGENTS.md`(仓库根) |
| 开发者入门 | `README.md`(仓库根) |
---
## 12. 变更记录
| 日期 | 变更 | 说明 |
|---|---|---|
| 2026-10-05 | 初版 | 基于项目全量内容(架构 / 子系统 / UI / 测试 / 硬件网表 / 待办)编写完整产品需求设计文档:产品定位、六层架构与任务模型、V0.1 硬件规格、10 类功能需求(含 2 项待实现)、接口规范、非功能需求、UI 规格、待实现项与路线图、验收判据 |
| 2026-10-10 | FR-CFG-4 旧档迁移**取消** | 删除旧固件 `/cfg/netcfg.bin`(NCFG 二进制)的首启自动迁移:旧档若存在则被忽略(不读、不删、不迁移),配置回落工厂默认。理由:该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏栈链 +600 B,而本仓库不存在携带旧档的设备;`net_cfg_legacy.c/h` 与 `cfg_store_legacy_path()` 一并移除。§7.1「掉电不丢配置」同步 |